diff --git a/.all-contributorsrc b/.all-contributorsrc index ced3b155b..753598d6c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -158,7 +158,7 @@ ] }, { - "login": "sircodemane", + "login": "codydbentley", "name": "Cody Bentley", "avatar_url": "https://avatars.githubusercontent.com/u/6968902?v=4", "profile": "https://codybentley.dev/", diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9faf71704..180057d45 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,7 @@ body: - type: markdown attributes: value: | - ***Please note: No bug reports are currently being accepted for Wails v3*** + ***Please note: No new bug reports are being accepted for Wails v1*** 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. - Prove that the error is indeed a Wails bug and not an application bug, with a specific set of steps to reproduce. @@ -70,7 +70,7 @@ body: validations: required: false - type: textarea - id: systemdetails + id: systemetails attributes: label: System Details description: Please add the output of `wails doctor`. @@ -84,4 +84,4 @@ body: description: Add any other context about the problem here. placeholder: Add any other context about the problem here. validations: - required: false + required: false \ No newline at end of file diff --git a/.github/file-labeler.yml b/.github/file-labeler.yml deleted file mode 100644 index 69494cbae..000000000 --- a/.github/file-labeler.yml +++ /dev/null @@ -1,44 +0,0 @@ -# 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 deleted file mode 100644 index 0a7949051..000000000 --- a/.github/issue-labeler.yml +++ /dev/null @@ -1,144 +0,0 @@ -# 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 d73efffa8..23cc01b17 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,49 +1,35 @@ - - - # Description -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. +Please include a summary of the change and which issue is fixed. Please also +include relevant motivation and context. List any dependencies that are required +for this change. Fixes # (issue) ## Type of change - -Please select the option that is relevant. + +Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Breaking change (fix or feature that would cause existing functionality to + not work as expected) - [ ] This change requires a documentation update # How Has This Been Tested? - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration using `wails doctor`. + +Please describe the tests that you ran to verify your changes. Provide +instructions so we can reproduce. Please also list any relevant details for your +test configuration using `wails doctor`. - [ ] Windows - [ ] macOS - [ ] Linux - -If you checked Linux, please specify the distro and version. - + ## Test Configuration -Please paste the output of `wails doctor`. If you are unable to run this command, please describe your environment in as much detail as possible. +Please paste the output of `wails doctor`. If you are unable to run this +command, please describe your environment in as much detail as possible. # Checklist: diff --git a/.github/stale.yml b/.github/stale.yml index d8bcc83ec..805bd589d 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 45 +daysUntilStale: 30 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 10 +daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - pinned @@ -9,28 +9,14 @@ exemptLabels: - onhold - inprogress - "Selected For Development" - - bug - - enhancement - - v3-alpha - - high-priority # Label to use when marking an issue as stale -staleLabel: "stale" +staleLabel: "Wont Fix" # 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 within the next 10 days. - - If this issue is still relevant, please add a comment to keep it open. - Thank you for your contributions. + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - This issue has been automatically closed due to lack of activity. - Please feel free to reopen it if it's still relevant. +closeComment: false 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 deleted file mode 100644 index 3d7a86450..000000000 --- a/.github/workflows/auto-label-issues.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Auto Label Issues - -on: - issues: - types: [opened, edited, reopened] - pull_request: - 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/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml deleted file mode 100644 index bfcef85a3..000000000 --- a/.github/workflows/build-and-test-v3.yml +++ /dev/null @@ -1,201 +0,0 @@ -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_go: - name: Run Go Tests v3 - needs: check_approval - 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-dependency-path: "v3/go.sum" - - - name: Install Task - uses: arduino/setup-task@v2 - with: - version: 3.x - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Build Examples - working-directory: v3 - run: task test:examples - - - name: Run tests (mac) - if: matrix.os == 'macos-latest' - env: - CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 - working-directory: v3 - run: go test -v ./... - - - name: Run tests (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 - - 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 dependencies - run: npm install - working-directory: v2/internal/frontend/runtime - - - name: Run tests - run: npm test - working-directory: v2/internal/frontend/runtime - - 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-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/build-and-test.yml b/.github/workflows/build-and-test.yml index 8fe647c6f..f208e446a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ name: Build + Test v2 on: push: - branches: [release/*, master, bugfix/*] + branches: [release/*, master] workflow_dispatch: jobs: @@ -12,30 +12,21 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04, ubuntu-24.04, windows-latest, macos-latest] - go-version: ['1.22'] + os: [ubuntu-latest, windows-latest, macos-latest] + go-version: [1.18, 1.19] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 - - uses: awalsh128/cache-apt-pkgs-action@latest - if: matrix.os == 'ubuntu-22.04' - with: - packages: libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config - version: 1.0 - - - uses: awalsh128/cache-apt-pkgs-action@latest - if: matrix.os == 'ubuntu-24.04' - with: - packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config libegl1 - version: 1.0 + - name: Install linux dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - cache-dependency-path: ./v2/go.sum - name: Run tests (mac) if: matrix.os == 'macos-latest' @@ -45,26 +36,21 @@ jobs: run: go test -v ./... - name: Run tests (!mac) - if: matrix.os != 'macos-latest' && matrix.os != 'ubuntu-24.04' + if: matrix.os != 'macos-latest' working-directory: ./v2 run: go test -v ./... - - name: Run tests (Ubuntu 24.04) - if: matrix.os == 'ubuntu-24.04' - working-directory: ./v2 - run: go test -v -tags webkit2_41 ./... - test_js: name: Run JS Tests if: github.repository == 'wailsapp/wails' runs-on: ubuntu-latest strategy: matrix: - node-version: [20.x] + node-version: [16.x] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 @@ -86,7 +72,7 @@ jobs: strategy: fail-fast: true matrix: - os: [ubuntu-22.04, windows-latest, macos-latest, ubuntu-24.04] + os: [ubuntu-latest, windows-latest, macos-latest] template: [ svelte, @@ -103,16 +89,15 @@ jobs: vanilla-ts, plain, ] - go-version: ['1.22'] + go-version: [1.18, 1.19] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - cache-dependency-path: ./v2/go.sum - name: Build Wails CLI run: | @@ -120,41 +105,14 @@ jobs: go install wails -help - - uses: awalsh128/cache-apt-pkgs-action@latest - if: matrix.os == 'ubuntu-22.04' - with: - packages: libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config - version: 1.0 + - name: Install linux dependencies + if: matrix.os == 'ubuntu-latest' + 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 ( 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 - - - uses: awalsh128/cache-apt-pkgs-action@latest - if: matrix.os == 'ubuntu-24.04' - with: - packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config libegl1 - version: 1.0 - -# - 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 - - - name: Generate & Build template '${{ matrix.template }}' - if: matrix.os != 'ubuntu-24.04' + - name: Generate template '${{ matrix.template }}' run: | mkdir -p ./test-${{ matrix.template }} cd ./test-${{ matrix.template }} wails init -n ${{ matrix.template }} -t ${{ matrix.template }} -ci cd ${{ matrix.template }} wails build -v 2 - - - name: Generate & Build template '${{ matrix.template }}' (ubuntu-24.04) - if: matrix.os == 'ubuntu-24.04' - run: | - mkdir -p ./test-${{ matrix.template }} - cd ./test-${{ matrix.template }} - wails init -n ${{ matrix.template }} -t ${{ matrix.template }} -ci - cd ${{ matrix.template }} - wails build -v 2 -tags webkit2_41 - diff --git a/.github/workflows/build-cross-image.yml b/.github/workflows/build-cross-image.yml deleted file mode 100644 index 83b40f2be..000000000 --- a/.github/workflows/build-cross-image.yml +++ /dev/null @@ -1,423 +0,0 @@ -name: Build Cross-Compiler Image - -on: - workflow_dispatch: - inputs: - branch: - description: 'Branch containing Dockerfile' - required: true - default: 'v3-alpha' - sdk_version: - description: 'macOS SDK version' - required: true - default: '14.5' - zig_version: - description: 'Zig version' - required: true - default: '0.14.0' - image_version: - description: 'Image version tag' - required: true - default: 'latest' - skip_tests: - description: 'Skip cross-compilation tests' - required: false - default: 'false' - type: boolean - push: - branches: - - v3-alpha - paths: - - 'v3/internal/commands/build_assets/docker/Dockerfile.cross' - -env: - REGISTRY: ghcr.io - IMAGE_NAME: wailsapp/wails-cross - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - outputs: - image_tag: ${{ steps.vars.outputs.image_version }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch || github.ref }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set build variables - id: vars - run: | - echo "sdk_version=${{ inputs.sdk_version || '14.5' }}" >> $GITHUB_OUTPUT - echo "zig_version=${{ inputs.zig_version || '0.14.0' }}" >> $GITHUB_OUTPUT - echo "image_version=${{ inputs.image_version || 'latest' }}" >> $GITHUB_OUTPUT - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=latest - type=raw,value=${{ steps.vars.outputs.image_version }} - type=raw,value=sdk-${{ steps.vars.outputs.sdk_version }} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: v3/internal/commands/build_assets/docker - file: v3/internal/commands/build_assets/docker/Dockerfile.cross - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: | - ${{ steps.meta.outputs.labels }} - io.wails.zig.version=${{ steps.vars.outputs.zig_version }} - io.wails.sdk.version=${{ steps.vars.outputs.sdk_version }} - build-args: | - ZIG_VERSION=${{ steps.vars.outputs.zig_version }} - MACOS_SDK_VERSION=${{ steps.vars.outputs.sdk_version }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Test cross-compilation for all platforms - test-cross-compile: - needs: build - if: ${{ inputs.skip_tests != 'true' }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - # Darwin targets (Zig + macOS SDK) - no platform emulation needed - - os: darwin - arch: arm64 - platform: "" - expected_file: "Mach-O 64-bit.*arm64" - - os: darwin - arch: amd64 - platform: "" - expected_file: "Mach-O 64-bit.*x86_64" - # Linux targets (GCC) - need platform to match architecture - - os: linux - arch: amd64 - platform: "linux/amd64" - expected_file: "ELF 64-bit LSB.*x86-64" - - os: linux - arch: arm64 - platform: "linux/arm64" - expected_file: "ELF 64-bit LSB.*ARM aarch64" - # Windows targets (Zig + mingw) - no platform emulation needed - - os: windows - arch: amd64 - platform: "" - expected_file: "PE32\\+ executable.*x86-64" - - os: windows - arch: arm64 - platform: "" - expected_file: "PE32\\+ executable.*Aarch64" - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch || github.ref }} - - - name: Set up QEMU - if: matrix.platform != '' - uses: docker/setup-qemu-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create test CGO project - run: | - mkdir -p test-project - cd test-project - - # Create a minimal CGO test program - cat > main.go << 'EOF' - package main - - /* - #include - - int add(int a, int b) { - return a + b; - } - */ - import "C" - import "fmt" - - func main() { - result := C.add(1, 2) - fmt.Printf("CGO test: 1 + 2 = %d\n", result) - } - EOF - - cat > go.mod << 'EOF' - module test-cgo - - go 1.21 - EOF - - - name: Build ${{ matrix.os }}/${{ matrix.arch }} (CGO) - run: | - cd test-project - PLATFORM_FLAG="" - if [ -n "${{ matrix.platform }}" ]; then - PLATFORM_FLAG="--platform ${{ matrix.platform }}" - fi - - docker run --rm $PLATFORM_FLAG \ - -v "$(pwd):/app" \ - -e APP_NAME="test-cgo" \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }} \ - ${{ matrix.os }} ${{ matrix.arch }} - - - name: Verify binary format - run: | - cd test-project/bin - ls -la - - # Find the built binary - if [ "${{ matrix.os }}" = "windows" ]; then - BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }}.exe 2>/dev/null || ls *.exe | head -1) - else - BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }} 2>/dev/null || ls test-cgo* | grep -v '.exe' | head -1) - fi - - echo "Binary: $BINARY" - FILE_OUTPUT=$(file "$BINARY") - echo "File output: $FILE_OUTPUT" - - # Verify the binary format matches expected - if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected_file }}"; then - echo "✅ Binary format verified: ${{ matrix.os }}/${{ matrix.arch }}" - else - echo "❌ Binary format mismatch!" - echo "Expected pattern: ${{ matrix.expected_file }}" - echo "Got: $FILE_OUTPUT" - exit 1 - fi - - - name: Check library dependencies (Linux only) - if: matrix.os == 'linux' - run: | - cd test-project/bin - BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }} 2>/dev/null || ls test-cgo* | grep -v '.exe' | head -1) - - echo "## Library Dependencies for $BINARY" - echo "" - - # Use readelf to show dynamic dependencies - echo "### NEEDED libraries:" - readelf -d "$BINARY" | grep NEEDED || echo "No dynamic dependencies (statically linked)" - - # Verify expected libraries are linked - echo "" - echo "### Verifying required libraries..." - NEEDED=$(readelf -d "$BINARY" | grep NEEDED) - - MISSING="" - for lib in libwebkit2gtk-4.1.so libgtk-3.so libglib-2.0.so libc.so; do - if echo "$NEEDED" | grep -q "$lib"; then - echo "✅ $lib" - else - echo "❌ $lib MISSING" - MISSING="$MISSING $lib" - fi - done - - if [ -n "$MISSING" ]; then - echo "" - echo "ERROR: Missing required libraries:$MISSING" - exit 1 - fi - - # Test non-CGO builds (pure Go cross-compilation) - test-non-cgo: - needs: build - if: ${{ inputs.skip_tests != 'true' }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - os: darwin - arch: arm64 - expected_file: "Mach-O 64-bit.*arm64" - - os: darwin - arch: amd64 - expected_file: "Mach-O 64-bit.*x86_64" - - os: linux - arch: amd64 - expected_file: "ELF 64-bit LSB" - - os: linux - arch: arm64 - expected_file: "ELF 64-bit LSB.*ARM aarch64" - - os: windows - arch: amd64 - expected_file: "PE32\\+ executable.*x86-64" - - os: windows - arch: arm64 - expected_file: "PE32\\+ executable.*Aarch64" - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.branch || github.ref }} - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Create test non-CGO project - run: | - mkdir -p test-project - cd test-project - - # Create a pure Go test program (no CGO) - cat > main.go << 'EOF' - package main - - import "fmt" - - func main() { - fmt.Println("Pure Go cross-compilation test") - } - EOF - - cat > go.mod << 'EOF' - module test-pure-go - - go 1.21 - EOF - - - name: Build ${{ matrix.os }}/${{ matrix.arch }} (non-CGO) - run: | - cd test-project - - # For non-CGO, we can use any platform since Go handles cross-compilation - # We set CGO_ENABLED=0 to ensure pure Go build - docker run --rm \ - -v "$(pwd):/app" \ - -e APP_NAME="test-pure-go" \ - -e CGO_ENABLED=0 \ - --entrypoint /bin/sh \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }} \ - -c "GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o bin/test-pure-go-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.os == 'windows' && '.exe' || '' }} ." - - - name: Verify binary format - run: | - cd test-project/bin - ls -la - - # Find the built binary - if [ "${{ matrix.os }}" = "windows" ]; then - BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}.exe" - else - BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}" - fi - - echo "Binary: $BINARY" - FILE_OUTPUT=$(file "$BINARY") - echo "File output: $FILE_OUTPUT" - - # Verify the binary format matches expected - if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected_file }}"; then - echo "✅ Binary format verified: ${{ matrix.os }}/${{ matrix.arch }} (non-CGO)" - else - echo "❌ Binary format mismatch!" - echo "Expected pattern: ${{ matrix.expected_file }}" - echo "Got: $FILE_OUTPUT" - exit 1 - fi - - - name: Check library dependencies (Linux only) - if: matrix.os == 'linux' - run: | - cd test-project/bin - BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}" - - echo "## Library Dependencies for $BINARY (non-CGO)" - echo "" - - # Non-CGO builds should have minimal dependencies (just libc or statically linked) - echo "### NEEDED libraries:" - readelf -d "$BINARY" | grep NEEDED || echo "No dynamic dependencies (statically linked)" - - # Verify NO GTK/WebKit libraries (since CGO is disabled) - NEEDED=$(readelf -d "$BINARY" | grep NEEDED || true) - if echo "$NEEDED" | grep -q "libwebkit\|libgtk"; then - echo "❌ ERROR: Non-CGO binary should not link to GTK/WebKit!" - exit 1 - else - echo "✅ Confirmed: No GTK/WebKit dependencies (expected for non-CGO)" - fi - - # Summary job - test-summary: - needs: [build, test-cross-compile, test-non-cgo] - if: always() && inputs.skip_tests != 'true' - runs-on: ubuntu-latest - steps: - - name: Check test results - run: | - echo "## Cross-Compilation Test Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ needs.test-cross-compile.result }}" = "success" ]; then - echo "✅ **CGO Tests**: All passed" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **CGO Tests**: Failed" >> $GITHUB_STEP_SUMMARY - fi - - if [ "${{ needs.test-non-cgo.result }}" = "success" ]; then - echo "✅ **Non-CGO Tests**: All passed" >> $GITHUB_STEP_SUMMARY - else - echo "❌ **Non-CGO Tests**: Failed" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Tested Platforms" >> $GITHUB_STEP_SUMMARY - echo "| Platform | Architecture | CGO | Non-CGO |" >> $GITHUB_STEP_SUMMARY - echo "|----------|-------------|-----|---------|" >> $GITHUB_STEP_SUMMARY - echo "| Darwin | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Darwin | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Linux | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Linux | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Windows | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY - echo "| Windows | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY - - # Fail if any test failed - if [ "${{ needs.test-cross-compile.result }}" != "success" ] || [ "${{ needs.test-non-cgo.result }}" != "success" ]; then - echo "" - echo "❌ Some tests failed. Check the individual job logs for details." - exit 1 - fi diff --git a/.github/workflows/changelog-v3.yml b/.github/workflows/changelog-v3.yml deleted file mode 100644 index 688959b9e..000000000 --- a/.github/workflows/changelog-v3.yml +++ /dev/null @@ -1,216 +0,0 @@ -name: Changelog Validation (v3) - -on: - pull_request: - branches: [ v3-alpha ] - paths: - - 'docs/src/content/docs/changelog.mdx' - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to validate' - required: true - type: string - -jobs: - validate: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - actions: write - - steps: - - name: Checkout PR 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 || github.token }} - - - name: Get REAL validation script from v3-alpha - run: | - echo "Fetching the REAL validation script from v3-alpha branch..." - git fetch origin v3-alpha - git checkout origin/v3-alpha -- v3/scripts/validate-changelog.go - - echo "Validation script fetched successfully:" - ls -la v3/scripts/ - - - 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 changelog modifications - id: changelog_check - run: | - echo "Checking PR #${{ steps.pr_info.outputs.pr_number }} for changelog changes" - git fetch origin ${{ steps.pr_info.outputs.base_ref }} - - if git diff --name-only origin/${{ steps.pr_info.outputs.base_ref }}..HEAD | grep -q "docs/src/content/docs/changelog.mdx"; then - echo "changelog_modified=true" >> $GITHUB_OUTPUT - echo "✅ Changelog was modified in this PR" - else - echo "changelog_modified=false" >> $GITHUB_OUTPUT - echo "ℹ️ Changelog was not modified - skipping validation" - fi - - - name: Get changelog diff - id: get_diff - if: steps.changelog_check.outputs.changelog_modified == 'true' - run: | - echo "Getting diff for changelog changes..." - git diff origin/${{ steps.pr_info.outputs.base_ref }}..HEAD docs/src/content/docs/changelog.mdx | grep "^+" | grep -v "^+++" | sed 's/^+//' > /tmp/pr_added_lines.txt - - echo "Lines added in this PR:" - cat /tmp/pr_added_lines.txt - echo "Total lines added: $(wc -l < /tmp/pr_added_lines.txt)" - - - name: Validate changelog - id: validate - if: steps.changelog_check.outputs.changelog_modified == 'true' - run: | - echo "Running changelog validation..." - cd v3/scripts - OUTPUT=$(go run validate-changelog.go ../../docs/src/content/docs/changelog.mdx /tmp/pr_added_lines.txt 2>&1) - echo "$OUTPUT" - - RESULT=$(echo "$OUTPUT" | grep "VALIDATION_RESULT=" | cut -d'=' -f2) - echo "result=$RESULT" >> $GITHUB_OUTPUT - - - name: Commit fixes - id: commit_fixes - if: steps.validate.outputs.result == 'fixed' - run: | - echo "Committing automatic fixes..." - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # Check only the changelog file for changes - if git diff --quiet docs/src/content/docs/changelog.mdx; then - echo "No changes to commit" - echo "committed=false" >> $GITHUB_OUTPUT - else - # Ensure validation script doesn't get committed - echo "v3/scripts/validate-changelog.go" >> .git/info/exclude - # Get the correct branch name to push to - REPO_OWNER="wailsapp" # Always wailsapp for this repo - - if [ "${{ github.event_name }}" = "pull_request" ]; then - BRANCH_NAME="${{ github.event.pull_request.head.ref }}" - else - # For manual workflow dispatch, get PR info - PR_INFO=$(gh pr view ${{ steps.pr_info.outputs.pr_number }} --json headRefName,headRepository) - BRANCH_NAME=$(echo "$PR_INFO" | jq -r '.headRefName') - HEAD_REPO=$(echo "$PR_INFO" | jq -r '.headRepository.name') - - echo "🔍 PR source branch: $BRANCH_NAME" - echo "🔍 Head repository: $HEAD_REPO" - - # Don't push if this is from a fork or if branch is v3-alpha (main branch) - if [ "$HEAD_REPO" != "wails" ] || [ "$BRANCH_NAME" = "v3-alpha" ]; then - echo "⚠️ Cannot push - either fork or direct v3-alpha branch. Manual fix required." - echo "committed=false" >> $GITHUB_OUTPUT - exit 0 - fi - fi - - echo "Pushing to branch: $BRANCH_NAME in repo: $REPO_OWNER" - - # Only commit the changelog changes, not the validation script - git add docs/src/content/docs/changelog.mdx - git commit -m "🤖 Fix changelog: move entries to Unreleased section" - - # Only push if running on the main wailsapp repository - if [ "${{ github.repository }}" = "wailsapp/wails" ]; then - # Pull latest changes and rebase our commit - git fetch origin $BRANCH_NAME - git rebase origin/$BRANCH_NAME - git push origin HEAD:$BRANCH_NAME - else - echo "⚠️ Running on fork (${{ github.repository }}). Skipping push - manual fix required." - echo "committed=false" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "committed=true" >> $GITHUB_OUTPUT - echo "✅ Changes committed and pushed" - fi - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get PR author for tagging - id: pr_author - if: steps.validate.outputs.result && github.event.inputs.pr_number - run: | - PR_AUTHOR=$(gh pr view ${{ steps.pr_info.outputs.pr_number }} --json author --jq '.author.login') - echo "author=$PR_AUTHOR" >> $GITHUB_OUTPUT - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Comment on PR - if: steps.validate.outputs.result && github.event.inputs.pr_number - uses: actions/github-script@v7 - with: - script: | - const result = '${{ steps.validate.outputs.result }}'; - const committed = '${{ steps.commit_fixes.outputs.committed }}'; - const author = '${{ steps.pr_author.outputs.author }}'; - - let message; - if (result === 'success') { - message = '## ✅ Changelog Validation Passed\n\nNo misplaced changelog entries detected.'; - } else if (result === 'fixed' && committed === 'true') { - message = '## 🔧 Changelog Updated\n\nMisplaced entries were automatically moved to the `[Unreleased]` section. The changes have been committed to this PR.'; - } else if (result === 'fixed' || result === 'cannot_fix' || result === 'error') { - // Read the fixed changelog content - const fs = require('fs'); - let fixedContent = ''; - try { - fixedContent = fs.readFileSync('docs/src/content/docs/changelog.mdx', 'utf8'); - } catch (error) { - fixedContent = 'Error reading fixed changelog content'; - } - - message = '## ⚠️ Changelog Validation Issue\\n\\n' + - '@' + author + ' Your PR contains changelog entries that were added to already-released versions. These need to be moved to the `[Unreleased]` section.\\n\\n' + - (committed === 'true' ? - '✅ **Auto-fix applied**: The changes have been automatically committed to this PR.' : - '❌ **Manual fix required**: Please apply the changes shown below manually.') + '\\n\\n' + - '
\\n' + - '📝 Click to see the corrected changelog content\\n\\n' + - '```mdx\\n' + - fixedContent + - '\\n```\\n\\n' + - '
\\n\\n' + - '**What happened?** \\n' + - 'The validation script detected that you added changelog entries to a version section that has already been released (like `v3.0.0-alpha.10`). All new entries should go in the `[Unreleased]` section under the appropriate category (`### Added`, `### Fixed`, etc.).\\n\\n' + - (committed !== 'true' ? '**Action needed:** Please copy the corrected content from above and replace your changelog file.' : ''); - } - - if (message) { - await github.rest.issues.createComment({ - issue_number: ${{ steps.pr_info.outputs.pr_number }}, - owner: context.repo.owner, - repo: context.repo.repo, - body: message - }); - } - - - name: Fail if validation failed - if: steps.validate.outputs.result == 'cannot_fix' || steps.validate.outputs.result == 'error' - run: | - echo "❌ Changelog validation failed" - exit 1 \ No newline at end of file diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index b5e8cfd4d..000000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' - plugins: 'code-review@claude-code-plugins' - prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index d300267f1..000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' - diff --git a/.github/workflows/format-markdown-files.yml b/.github/workflows/format-markdown-files.yml new file mode 100644 index 000000000..d30546428 --- /dev/null +++ b/.github/workflows/format-markdown-files.yml @@ -0,0 +1,37 @@ +name: Format Markdown Files + +on: + workflow_dispatch: + push: + branches: [master] + +jobs: + format_markdown_files: + runs-on: ubuntu-latest + if: github.repository == 'wailsapp/wails' + steps: + - uses: actions/checkout@v3 + + - name: Setup Nodejs + uses: actions/setup-node@v2 + with: + node-version: 18.x + + - name: Install Task + uses: arduino/setup-task@v1 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Format All Markdown Files + run: task format-all-md + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4 + with: + commit-message: "docs: format document" + title: "docs: format document" + body: "- [x] Format all Markdown(x) documents" + branch: chore/format-markdown-files + delete-branch: true + draft: false diff --git a/.github/workflows/generate-sponsor-image.yml b/.github/workflows/generate-sponsor-image.yml index 56548ab43..5f3006d7e 100644 --- a/.github/workflows/generate-sponsor-image.yml +++ b/.github/workflows/generate-sponsor-image.yml @@ -16,7 +16,7 @@ jobs: - name: Set Node uses: actions/setup-node@v2 with: - node-version: 20.x + node-version: 16.x - name: Update Sponsors run: cd scripts/sponsors && chmod 755 ./generate-sponsor-image.sh && ./generate-sponsor-image.sh @@ -25,16 +25,11 @@ jobs: SPONSORKIT_GITHUB_LOGIN: wailsapp - name: Create Pull Request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v4 with: commit-message: "chore: update sponsors.svg" add-paths: "website/static/img/sponsors.svg" - title: "chore: update sponsors.svg" - body: | - Auto-generated by the sponsor image workflow - - [skip ci] [skip actions] + title: Update Sponsor Image + body: Generated new image 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 deleted file mode 100644 index 99159a2f5..000000000 --- a/.github/workflows/issue-triage-automation.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Issue Triage Automation - -on: - issues: - types: [opened] - -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 deleted file mode 100644 index ae56ba7bc..000000000 --- a/.github/workflows/nightly-release-v3.yml +++ /dev/null @@ -1,210 +0,0 @@ -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' - env: - WAILS_REPO_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }} - GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }} - run: | - cd v3/tasks/release - ARGS=() - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - ARGS+=(--dry-run) - fi - go run release.go "${ARGS[@]}" - - - name: Summary - if: always() - run: | - if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then - echo "## 🧪 DRY RUN Release Summary" >> $GITHUB_STEP_SUMMARY - else - echo "## 🚀 Nightly Release Summary" >> $GITHUB_STEP_SUMMARY - fi - echo "================================" >> $GITHUB_STEP_SUMMARY - - if [ -n "${{ steps.release.outputs.release_version }}" ]; then - echo "- **Version:** ${{ steps.release.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY - echo "- **Tag:** ${{ steps.release.outputs.release_tag }}" >> $GITHUB_STEP_SUMMARY - echo "- **Status:** ${{ steps.release.outcome == 'success' && '✅ Success' || '⚠️ Failed' }}" >> $GITHUB_STEP_SUMMARY - echo "- **Mode:** ${{ steps.release.outputs.release_dry_run == 'true' && '🧪 Dry Run' || '🚀 Live release' }}" >> $GITHUB_STEP_SUMMARY - if [ -n "${{ steps.release.outputs.release_url }}" ]; then - echo "- **Release URL:** ${{ steps.release.outputs.release_url }}" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Changelog" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then - echo "✅ Unreleased changelog processed and reset." >> $GITHUB_STEP_SUMMARY - else - echo "ℹ️ No unreleased changelog content detected." >> $GITHUB_STEP_SUMMARY - fi - else - echo "- Release script did not run (skipped or failed before execution)." >> $GITHUB_STEP_SUMMARY - fi - diff --git a/.github/workflows/pr-master.yml b/.github/workflows/pr-master.yml deleted file mode 100644 index c961b4434..000000000 --- a/.github/workflows/pr-master.yml +++ /dev/null @@ -1,104 +0,0 @@ -# Updated to ensure "Run Go Tests" runs for pull requests as expected. -# Key fix: the test_go job previously required github.event.review.state == 'approved' -# which only exists on pull_request_review events. That prevented the job from -# running for regular pull_request events (opened / synchronize / reopened). -# New logic: run tests for pull_request events, and also allow running when a -# pull_request_review is submitted with state == 'approved'. -on: - pull_request: - types: [opened, synchronize, reopened] - branches: - - master - pull_request_review: - types: [submitted] - branches: - - master - workflow_dispatch: {} - -name: PR Checks (master) - -jobs: - check_docs: - name: Check Docs - if: ${{ github.repository == 'wailsapp/wails' && github.base_ref == 'master' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Verify Changed files - uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 - id: verify-changed-files - with: - files: | - website/**/*.mdx - website/**/*.md - - name: Run step only when files change. - if: steps.verify-changed-files.outputs.files_changed != 'true' - run: | - echo "::warning::Feature branch does not contain any changes to the website." - - test_go: - name: Run Go Tests - runs-on: ${{ matrix.os }} - # Run when: - # - the event is a pull_request (opened/synchronize/reopened) OR - # - the event is a pull_request_review AND the review state is 'approved' - # plus other existing filters (not the update-sponsors branch, repo and base_ref) - if: > - github.repository == 'wailsapp/wails' && - github.base_ref == 'master' && - github.event.pull_request.head.ref != 'update-sponsors' && - ( - github.event_name == 'pull_request' || - (github.event_name == 'pull_request_review' && github.event.review.state == 'approved') - ) - strategy: - matrix: - os: [ubuntu-22.04, windows-latest, macos-latest, ubuntu-24.04] - go-version: ['1.23'] - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - 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) - 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 - - - name: Setup Go - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go-version }} - - - name: Run tests (mac) - if: matrix.os == 'macos-latest' - env: - CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 - working-directory: ./v2 - run: go test -v ./... - - - name: Run tests (!mac) - if: matrix.os != 'macos-latest' && matrix.os != 'ubuntu-24.04' - working-directory: ./v2 - run: go test -v ./... - - - name: Run tests (Ubuntu 24.04) - if: matrix.os == 'ubuntu-24.04' - working-directory: ./v2 - run: go test -v -tags webkit2_41 ./... - - # This job will run instead of test_go for the update-sponsors branch - skip_tests: - name: Skip Tests (Sponsor Update) - if: github.event.pull_request.head.ref == 'update-sponsors' - runs-on: ubuntu-latest - steps: - - name: Skip tests for sponsor updates - run: | - echo "Skipping tests for sponsor update branch" - echo "This is an automated update of the sponsors image." - continue-on-error: true diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 000000000..6db750b73 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,61 @@ +name: PR Checks + +on: + pull_request: + pull_request_review: + types: [submitted] + +jobs: + check_docs: + name: Check Docs + if: ${{github.repository == 'wailsapp/wails' && contains(github.head_ref,'feature/')}} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Verify Changed files + uses: tj-actions/verify-changed-files@v11.1 + id: verify-changed-files + with: + files: | + website/**/*.mdx + website/**/*.md + + - name: Run step only when files change. + if: steps.verify-changed-files.outputs.files_changed != 'true' + run: | + echo "::warning::Feature branch does not contain any changes to the website." + + test_go: + name: Run Go Tests + runs-on: ${{ matrix.os }} + if: github.event.review.state == 'approved' + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + go-version: [1.18, 1.19] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install linux dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - name: Run tests (mac) + if: matrix.os == 'macos-latest' + env: + CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 + working-directory: ./v2 + run: go test -v ./... + + - name: Run tests (!mac) + if: matrix.os != 'macos-latest' + working-directory: ./v2 + run: go test -v ./... diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml deleted file mode 100644 index a59818660..000000000 --- a/.github/workflows/semgrep.yml +++ /dev/null @@ -1,25 +0,0 @@ -on: - workflow_dispatch: {} - pull_request: {} - push: - branches: - - main - - master - - v3-alpha - paths: - - .github/workflows/semgrep.yml - schedule: - # random HH:MM to avoid a load spike on GitHub Actions at 00:00 - - cron: 14 16 * * * -name: Semgrep -jobs: - semgrep: - name: semgrep/ci - runs-on: ubuntu-24.04 - env: - SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - container: - image: returntocorp/semgrep - steps: - - uses: actions/checkout@v3 - - run: semgrep ci diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml deleted file mode 100644 index c4ffd25fe..000000000 --- a/.github/workflows/stale-issues.yml +++ /dev/null @@ -1,57 +0,0 @@ -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/sync-translated-documents.yml b/.github/workflows/sync-translated-documents.yml index 0aa06f11e..770b76124 100644 --- a/.github/workflows/sync-translated-documents.yml +++ b/.github/workflows/sync-translated-documents.yml @@ -15,7 +15,7 @@ jobs: - name: Setup Nodejs uses: actions/setup-node@v2 with: - node-version: 20.x + node-version: 18.x - name: Install Task uses: arduino/setup-task@v1 diff --git a/.github/workflows/test-nightly-releases.yml b/.github/workflows/test-nightly-releases.yml deleted file mode 100644 index 63df09935..000000000 --- a/.github/workflows/test-nightly-releases.yml +++ /dev/null @@ -1,216 +0,0 @@ -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/unreleased-changelog-trigger.yml b/.github/workflows/unreleased-changelog-trigger.yml deleted file mode 100644 index 8cfe85de0..000000000 --- a/.github/workflows/unreleased-changelog-trigger.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Auto Release on Changelog Update - -on: - push: - branches: - - v3-alpha - paths: - - 'v3/UNRELEASED_CHANGELOG.md' - workflow_dispatch: - inputs: - dry_run: - description: 'Run in dry-run mode (no actual release)' - required: false - default: false - type: boolean - -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 - - trigger-release: - name: Trigger v3-alpha Release - permissions: - contents: read - actions: write - runs-on: ubuntu-latest - needs: check-permissions - if: needs.check-permissions.outputs.authorized == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: v3-alpha - fetch-depth: 0 - token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} - - - name: Check for unreleased changelog content - id: changelog_check - run: | - echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..." - - cd v3 - # Check if UNRELEASED_CHANGELOG.md has actual content beyond the template - if [ -f "UNRELEASED_CHANGELOG.md" ]; then - # Use a simple check for actual content (bullet points starting with -) - CONTENT_LINES=$(grep -E "^\s*-\s+[^[:space:]]" UNRELEASED_CHANGELOG.md | wc -l) - if [ "$CONTENT_LINES" -gt 0 ]; then - echo "✅ Found $CONTENT_LINES content lines in UNRELEASED_CHANGELOG.md" - echo "has_content=true" >> $GITHUB_OUTPUT - else - echo "ℹ️ No actual content found in UNRELEASED_CHANGELOG.md" - echo "has_content=false" >> $GITHUB_OUTPUT - fi - else - echo "❌ UNRELEASED_CHANGELOG.md not found" - echo "has_content=false" >> $GITHUB_OUTPUT - fi - - - name: Trigger nightly release workflow - if: steps.changelog_check.outputs.has_content == 'true' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} - script: | - const response = await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'nightly-release-v3.yml', - ref: 'v3-alpha', - inputs: { - force_release: 'true', - dry_run: '${{ github.event.inputs.dry_run || "false" }}' - } - }); - - console.log('🚀 Successfully triggered nightly release workflow'); - console.log(`Workflow dispatch response status: ${response.status}`); - - // Create a summary - core.summary - .addHeading('🚀 Auto Release Triggered') - .addRaw('The v3-alpha release workflow has been automatically triggered due to changes in UNRELEASED_CHANGELOG.md') - .addTable([ - [{data: 'Trigger', header: true}, {data: 'Value', header: true}], - ['Repository', context.repo.repo], - ['Branch', 'v3-alpha'], - ['Actor', context.actor], - ['Dry Run', '${{ github.event.inputs.dry_run || "false" }}'], - ['Force Release', 'true'] - ]) - .addRaw('\n---\n*This release was automatically triggered by the unreleased-changelog-trigger workflow*') - .write(); - - - name: No content found - if: steps.changelog_check.outputs.has_content == 'false' - run: | - echo "ℹ️ No content found in UNRELEASED_CHANGELOG.md, skipping release trigger" - echo "## ℹ️ No Release Triggered" >> $GITHUB_STEP_SUMMARY - echo "**Reason:** UNRELEASED_CHANGELOG.md does not contain actual changelog content" >> $GITHUB_STEP_SUMMARY - echo "**Action:** No release workflow was triggered" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "To trigger a release, add actual changelog entries to the UNRELEASED_CHANGELOG.md file." >> $GITHUB_STEP_SUMMARY - - - name: Unauthorized user - if: needs.check-permissions.outputs.authorized == 'false' - run: | - echo "❌ User ${{ github.actor }} is not authorized to trigger releases" - echo "## ❌ Unauthorized Release Attempt" >> $GITHUB_STEP_SUMMARY - echo "**User:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY - echo "**Action:** Release trigger was blocked due to insufficient permissions" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Only authorized users can trigger automatic releases via changelog updates." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/upload-source-documents.yml b/.github/workflows/upload-source-documents.yml index 69d6c3e48..c94245ba5 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: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 + uses: tj-actions/changed-files@v35 with: files: | website/**/*.mdx @@ -25,7 +25,7 @@ jobs: - name: Setup Nodejs uses: actions/setup-node@v2 with: - node-version: 20.x + node-version: 18.x - name: Setup Task uses: arduino/setup-task@v1 diff --git a/.gitignore b/.gitignore index e7888b44a..cca554686 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,3 @@ v2/cmd/wails/internal/commands/initialise/templates/testtemplates/ /v3/examples/build/bin/testapp /websitev3/site/ /v3/examples/plugins/bin/testapp - -# Temporary called mkdocs, should be renamed to more standard -website or similar -/mkdocs-website/site diff --git a/.replit b/.replit deleted file mode 100644 index 619bd7227..000000000 --- a/.replit +++ /dev/null @@ -1,8 +0,0 @@ -modules = ["go-1.21", "web", "nodejs-20"] -run = "go run v2/cmd/wails/main.go" - -[nix] -channel = "stable-24_05" - -[deployment] -run = ["sh", "-c", "go run v2/cmd/wails/main.go"] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa53c412a..5ba956140 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1,2 @@ -The current Contribution Guidelines can be found at: https://wails.io/community-guide +The current Contribution Guidelines can be found at: +https://wails.io/community-guide diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a7b5a60ab..c1dd0db1c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,2 +1,2 @@ - -The latest contributors list may be found at: https://wails.io/credits#contributors \ No newline at end of file +The latest contributors list may be found at: +https://wails.io/credits#contributors diff --git a/README.de.md b/README.de.md deleted file mode 100644 index 5df35de5b..000000000 --- a/README.de.md +++ /dev/null @@ -1,160 +0,0 @@ -

-
-

- -

-Erschaffe Desktop Anwendungen mit Go & Web Technologien. -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) - - - -
- -## Inhaltsverzeichnis - -- [Inhaltsverzeichnis](#inhaltsverzeichnis) -- [Einführung](#einführung) -- [Funktionen](#funktionen) - - [Roadmap](#roadmap) -- [Loslegen](#loslegen) -- [Sponsoren](#sponsoren) -- [FAQ](#faq) -- [Sterne Überblick](#sterne-überblick) -- [Mitwirkende](#mitwirkende) -- [Lizenz](#lizenz) -- [Inspiration](#inspiration) - -## Einführung - -Die herkömmliche Methode zur Bereitstellung von Web-Interfaces für Go ist über einen eingebauten Webserver. -Wails nutzt einen anderen Weg. Es kann sowohl Go-Code als auch ein Web-Frontend in eine einzige Datei bauen. -Beigelieferte Werkzeuge übernehmen die Projekterstellung, den Kompilierungsprozess und das bauen. -Du musst nur kreativ werden. - -## Funktionen - -- Nutze Standard Go für das Backend -- Nutze eine Frontend Technologie mit der du dich bereits auskennst um dein UI zu bauen. -- Erschaffe schnell und einfach Frontends mit vorgefertigten Vorlagen für deine Go-Programme -- Nutze Javascript um Go Methoden aufzurufen -- Automatisch generierte Typescript Definitionen für deine Go Strukturen und Methoden -- Native Dialoge und Menüs -- Native Dark-/Lightmode Unterstützung -- Unterstützt moderne Transluzenz- und Milchglaseffekte -- Vereinheitlichtes Eventsystem zwischen Go und Javascript -- Leistungsstarkes CLI-Tool zum einfachen erstellen und bauen von Projekten -- Multiplattformen -- Nutze native Render-Engines - _keine eingebetteten Browser_! - -### Roadmap - -Die Projekt Roadmap kann [hier](https://github.com/wailsapp/wails/discussions/1484) gefunden werden. Bitte lies diese -durch bevor du eine Idee vorschlägst - -## Loslegen - -Die Installationsinstruktionen sind auf der [offiziellen Website](https://wails.io/docs/gettingstarted/installation). - -## Sponsoren - -Dieses Projekt wird von diesen freundlichen Leuten und Firmen unterstützt: - - -

- -

- -## FAQ - -- Ist das eine Alternative zu Electron? - - Hängt von deinen Anforderungen ab. Wails wurde entwickelt um das Go-Programmieren leicht zu machen und effiziente - Desktop-Anwendungen zu erstellen oder ein Frontend zu einer bestehenden Anwendung hinzuzufügen. - Wails bietet native Elemente wie Dialoge und Menüs und könnte somit als eine leichte effiziente Electron-Alternative - betrachtet werden. - -- Für wen ist dieses projekt geeignet? - - Go Entwickler, die ein HTML/CSS/JS-Frontend in ihre Anwendung integrieren möchten, ohne einen Webserver zu erstellen und - einen Browser öffnen zu müssen, um dieses zu sehen - -- Wie kam es zu diesem Namen? - - Als ich WebView sah dachte ich "Was ich wirklich will, ist ein Werkzeug für die Erstellung von WebView Anwendungen so wie Rails für Ruby". - Also war es zunächst ein Wortspiel (Webview on Rails). Zufälligerweise ist es auch ein Homophon des englischen Namens des [Landes](https://en.wikipedia.org/wiki/Wales), aus dem ich komme. - Also ist es dabei geblieben. - -## Sterne Überblick - - - - - - Star History Chart - - - -## Mitwirkende - -Die Liste der Mitwirkenden wird zu groß für diese Readme. All die fantastischen Menschen, die zu diesem -Projekt beigetragen haben, haben [hier](https://wails.io/credits#contributors) ihre eigene Seite. - -## Lizenz - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## Inspiration - -Dieses Projekt wurde hauptsächlich zu den folgenden Alben entwickelt - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.es.md b/README.es.md deleted file mode 100644 index 277d1c1fd..000000000 --- a/README.es.md +++ /dev/null @@ -1,169 +0,0 @@ -

-
-

- -

- Construye aplicaciones de escritorio usando Go y tecnologías web. -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) - - - -
- -## Tabla de Contenidos - -- [Tabla de Contenidos](#tabla-de-contenidos) -- [Introducción](#introducción) -- [Funcionalidades](#funcionalidades) - - [Plan de Trabajo](#plan-de-trabajo) -- [Empezando](#empezando) -- [Patrocinadores](#patrocinadores) -- [Preguntas Frecuentes](#preguntas-frecuentes) -- [Estrellas a lo Largo del Tiempo](#estrellas-a-lo-largo-del-tiempo) -- [Colaboradores](#colaboradores) -- [Licencia](#licencia) -- [Inspiración](#inspiración) - -## Introducción - -El método tradicional para proveer una interfaz web en programas hechos con Go -es a través del servidor web incorporado. Wails ofrece un enfoque diferente al -permitir combinar el código hecho en Go con un frontend web en un solo archivo -binario. Las herramientas que proporcionamos facilitan este trabajo para ti, al -crear, compilar y empaquetar tu proyecto. ¡Lo único que debes hacer es ponerte -creativo! - -## Funcionalidades - -- Utiliza Go estándar para el backend -- Utiliza cualquier tecnología frontend con la que ya estés familiarizado para - construir tu interfaz de usuario -- Crea rápidamente interfaces de usuario enriquecidas para tus programas en Go - utilizando plantillas predefinidas -- Invoca fácilmente métodos de Go desde Javascript -- Definiciones de Typescript generadas automáticamente para tus structs y - métodos de Go -- Diálogos y menús nativos -- Soporte nativo de modo oscuro / claro -- Soporte de translucidez y efectos de ventana esmerilada -- Sistema de eventos unificado entre Go y Javascript -- Herramienta CLI potente para generar y construir tus proyectos rápidamente -- Multiplataforma -- Usa motores de renderizado nativos - ¡_sin navegador integrado_! - -### Plan de Trabajo - -El plan de trabajo se puede encontrar -[aqui](https://github.com/wailsapp/wails/discussions/1484). Por favor, -consúltalo antes de abrir una solicitud de mejora. - -## Empezando - -Las instrucciones de instalacion se encuentran en nuestra -[pagina web oficial](https://wails.io/docs/gettingstarted/installation). - -## Patrocinadores - -Este Proyecto cuenta con el apoyo de estas amables personas/ compañías: - - -

- -

- -## Preguntas Frecuentes - -- ¿Es esta una alternativa a Electron? - - Depende de tus requisitos. Está diseñado para facilitar a los programadores de - Go la creación de aplicaciones de escritorio livianas o agregar una interfaz - gráfica a sus aplicaciones existentes. Wails ofrece elementos nativos como - menús y diálogos, por lo que podría considerarse una alternativa liviana a - Electron. - -- ¿A quien esta dirigido este proyecto? - - El proyecto esta dirigido a programadores de Go que desean integrar una - interfaz HMTL/JS/CSS en sus aplicaciones, sin tener que recurrir a la creación - de un servidor y abrir el navegador para visualizarla. - -- ¿Cual es el significado del nombre? - - Cuando vi WebView, pensé: "Lo que realmente quiero es una herramienta para - construir una aplicación WebView, algo similar a lo que Rails es para Ruby". - Así que inicialmente fue un juego de palabras (WebView en Rails). Además, por - casualidad, también es homófono del nombre en inglés del - [país](https://en.wikipedia.org/wiki/Wales) del que provengo. Así que se quedó - con ese nombre. - -## Estrellas a lo Largo del Tiempo - -[![Star History Chart](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) - -## Colaboradores - -¡La lista de colaboradores se está volviendo demasiado grande para el archivo -readme! Todas las personas increíbles que han contribuido a este proyecto tienen -su propia página [aqui](https://wails.io/credits#contributors). - -## Licencia - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## Inspiración - -Este proyecto fue construido mientras se escuchaban estos álbumes: - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) - [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.fr.md b/README.fr.md deleted file mode 100644 index 61230f353..000000000 --- a/README.fr.md +++ /dev/null @@ -1,144 +0,0 @@ -

-
-

- -

- Créer des applications de bureau avec Go et les technologies Web. -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) - - - -
- -## Sommaire - -- [Sommaire](#sommaire) -- [Introduction](#introduction) -- [Fonctionnalités](#fonctionnalités) - - [Feuille de route](#feuille-de-route) -- [Démarrage](#démarrage) -- [Les sponsors](#les-sponsors) -- [Foire aux questions](#foire-aux-questions) -- [Les étoiles au fil du temps](#les-étoiles-au-fil-du-temps) -- [Les contributeurs](#les-contributeurs) -- [License](#license) -- [Inspiration](#inspiration) - -## Introduction - -La méthode traditionnelle pour fournir des interfaces web aux programmes Go consiste à utiliser un serveur web intégré. Wails propose une approche différente : il offre la possibilité d'intégrer à la fois le code Go et une interface web dans un seul binaire. Des outils sont fournis pour vous faciliter la tâche en gérant la création, la compilation et le regroupement des projets. Il ne vous reste plus qu'à faire preuve de créativité! - -## Fonctionnalités - -- Utiliser Go pour le backend -- Utilisez n'importe quelle technologie frontend avec laquelle vous êtes déjà familier pour construire votre interface utilisateur. -- Créez rapidement des interfaces riches pour vos programmes Go à l'aide de modèles prédéfinis. -- Appeler facilement des méthodes Go à partir de Javascript -- Définitions Typescript auto-générées pour vos structures et méthodes Go -- Dialogues et menus natifs -- Prise en charge native des modes sombre et clair -- Prise en charge des effets modernes de translucidité et de "frosted window". -- Système d'événements unifié entre Go et Javascript -- Outil puissant pour générer et construire rapidement vos projets -- Multiplateforme -- Utilise des moteurs de rendu natifs - _pas de navigateur intégré_ ! - -### Feuille de route - -La feuille de route du projet peut être consultée [ici](https://github.com/wailsapp/wails/discussions/1484). Veuillez consulter avant d'ouvrir une demande d'amélioration. - -## Démarrage - -Les instructions d'installation se trouvent sur le site [site officiel](https://wails.io/docs/gettingstarted/installation). - -## Les sponsors - -Ce projet est soutenu par ces personnes aimables et entreprises: - - -

- -

- -## Foire aux questions - -- S'agit-il d'une alternative à Electron ? - - Cela dépend de vos besoins. Il est conçu pour permettre aux programmeurs Go de créer facilement des applications de bureau légères ou d'ajouter une interface à leurs applications existantes. Wails offre des éléments natifs tels que des menus et des boîtes de dialogue, il peut donc être considéré comme une alternative légère à electron. - -- À qui s'adresse ce projet ? - - Les programmeurs Go qui souhaitent intégrer une interface HTML/JS/CSS à leurs applications, sans avoir à créer un serveur et à ouvrir un navigateur pour l'afficher. - -- Pourquoi ce nom ?? - - Lorsque j'ai vu WebView, je me suis dit : "Ce que je veux vraiment, c'est un outil pour construire une application WebView, un peu comme Rails l'est pour Ruby". Au départ, il s'agissait donc d'un jeu de mots (Webview on Rails). Il se trouve que c'est aussi un homophone du nom anglais du [Pays](https://en.wikipedia.org/wiki/Wales) d'où je viens. Il s'est donc imposé. - -## Les étoiles au fil du temps - -[![Graphique de l'histoire des étoiles](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) - -## Les contributeurs - -La liste des contributeurs devient trop importante pour le readme ! Toutes les personnes extraordinaires qui ont contribué à ce projet ont leur propre page [ici](https://wails.io/credits#contributors). - -## License - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## Inspiration - -Ce projet a été principalement codé sur les albums suivants : - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.ja.md b/README.ja.md index ffd9f8103..88c629a74 100644 --- a/README.ja.md +++ b/README.ja.md @@ -27,7 +27,7 @@ Awesome - Discord + Discord
@@ -43,9 +43,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) +[한국어](README.ko.md) @@ -55,84 +53,118 @@ - [目次](#目次) - [はじめに](#はじめに) -- [特徴](#特徴) + - [公式サイト](#公式サイト) - [ロードマップ](#ロードマップ) -- [始め方](#始め方) +- [特徴](#特徴) - [スポンサー](#スポンサー) +- [始め方](#始め方) - [FAQ](#faq) - [スター数の推移](#スター数の推移) - [コントリビューター](#コントリビューター) +- [特記事項](#特記事項) +- [スペシャルサンクス](#スペシャルサンクス) - [ライセンス](#ライセンス) -- [インスピレーション](#インスピレーション) - ## はじめに -Go プログラムにウェブインタフェースを提供する従来の方法は内蔵のウェブサーバを経由するものですが、 Wails では異なるアプローチを提供します。 -Wails では Go のコードとウェブフロントエンドを単一のバイナリにまとめる機能を提供します。 -また、プロジェクトの作成、コンパイル、ビルドを行うためのツールが提供されています。あなたがすべきことは創造性を発揮することです! +Go プログラムにウェブインタフェースを提供する従来の方法は内蔵のウェブサーバを経 +由するものですが、 Wails では異なるアプローチを提供します。 Wails では Go のコー +ドとウェブフロントエンドを単一のバイナリにまとめる機能を提供します。また、プロジ +ェクトの作成、コンパイル、ビルドを行うためのツールが提供されています。あなたがす +べきことは創造性を発揮することです! + +### 公式サイト + +Version 2: + +Wails v2 が 3 つのプラットフォームでベータ版としてリリースされました。興味のある +方は[新しいウェブサイト](https://wails.io)をご覧ください。 + +レガシー版 v1: + +レガシー版 v1 のドキュメントは[https://wails.app](https://wails.app)で見ることが +できます。 + +### ロードマップ + +プロジェクトのロードマップ +は[こちら](https://github.com/wailsapp/wails/discussions/1484)になります。 +機能拡張のリクエストを出す前にご覧ください。 ## 特徴 - バックエンドには Go を利用しています - 使い慣れたフロントエンド技術を利用して UI を構築できます -- あらかじめ用意されたテンプレートを利用することで、リッチなフロントエンドを備えた Go プログラムを素早く作成できます +- あらかじめ用意されたテンプレートを利用することで、リッチなフロントエンドを備え + た Go プログラムを作成できます - JavaScript から Go のメソッドを簡単に呼び出すことができます -- あなたの書いた Go の構造体やメソットに応じた TypeScript の定義が自動生成されます +- あなたの書いた Go の構造体やメソットに応じた TypeScript の定義が自動生成されま + す - ネイティブのダイアログとメニューが利用できます -- ネイティブなダーク/ライトモードをサポートします - モダンな半透明や「frosted window」エフェクトをサポートしています - Go と JavaScript 間で統一されたイベント・システムを備えています - プロジェクトを素早く生成して構築する強力な cli ツールを用意しています - マルチプラットフォームに対応しています -- ネイティブなレンダリングエンジンを使用しています - _つまりブラウザを埋め込んでいるわけではありません!_ - -### ロードマップ - -プロジェクトのロードマップは[こちら](https://github.com/wailsapp/wails/discussions/1484)になります。 -機能拡張のリクエストを出す前にご覧ください。 - -## 始め方 - -インストール方法は[公式サイト](https://wails.io/docs/gettingstarted/installation)に掲載されています。 +- ネイティブなレンダリングエンジンを使用しています - _つまりブラウザを埋め込んで + いるわけではありません!_ ## スポンサー このプロジェクトは、以下の方々・企業によって支えられています。 +## 始め方 + +インストール方法 +は[公式サイト](https://wails.io/docs/gettingstarted/installation)に掲載されてい +ます。 + ## FAQ - Electron の代替品になりますか? - それはあなたの求める要件によります。Wails は Go プログラマーが簡単に軽量のデスクトップアプリケーションを作成したり、既存のアプリケーションにフロントエンドを追加できるように設計されています。 - Wails v2 ではメニューやダイアログといったネイティブな要素を提供するようになったため、軽量な Electron の代替となりつつあります。 + それはあなたの求める要件によります。Wails は Go プログラマーが簡単に軽量のデス + クトップアプリケーションを作成したり、既存のアプリケーションにフロントエンドを + 追加できるように設計されています。 Wails v2 ではメニューやダイアログといったネ + イティブな要素を提供するようになったため、軽量な Electron の代替となりつつあり + ます。 - このプロジェクトは誰に向けたものですか? - HTML/JS/CSS のフロントエンド技術をアプリケーションにバンドルさせることで、サーバーを作成してブラウザ経由で表示させることなくアプリケーションを利用したい Go プログラマにおすすめです。 + HTML/JS/CSS のフロントエンド技術をアプリケーションにバンドルさせることで、サー + バーを作成してブラウザ経由で表示させることなくアプリケーションを利用したい Go + プログラマにおすすめです。 - 名前の由来を教えて下さい WebView を見たとき、私はこう思いました。 - 「私が本当に欲しいのは、WebView アプリを構築するためのツールであり、Ruby に対する Rails のようなものである」と。 + 「私が本当に欲しいのは、WebView アプリを構築するためのツールであり、Ruby に対 + する Rails のようなものである」と。 そのため、最初は言葉遊びのつもりでした(Webview on Rails)。 - また、私の[出身国](https://en.wikipedia.org/wiki/Wales)の英語名と同音異義語でもあります。そしてこの名前が定着しました。 + また、私の[出身国](https://en.wikipedia.org/wiki/Wales)の英語名と同音異義語で + もあります。そしてこの名前が定着しました。 ## スター数の推移 -[![Star History Chart](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) +[![スター数の推移](https://starchart.cc/wailsapp/wails.svg)](https://starchart.cc/wailsapp/wails) ## コントリビューター 貢献してくれた方のリストが大きくなりすぎて、readme に入りきらなくなりました! -このプロジェクトに貢献してくれた素晴らしい方々のページは[こちら](https://wails.io/credits#contributors)です。 +このプロジェクトに貢献してくれた素晴らしい方々のページ +は[こちら](https://wails.io/credits#contributors)です。 -## ライセンス +## 特記事項 -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) +このプロジェクトは以下の方々の協力がなければ、実現しなかったと思います。 -## インスピレーション +- [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - 彼のサポートとフィード + バックはとても大きいものでした。 +- [Serge Zaitsev](https://github.com/zserge) - Wails のウィンドウで使用してい + る[Webview](https://github.com/zserge/webview)の作者です。 +- [Byron](https://github.com/bh90210) - 時には Byron が一人でこのプロジェクトを + 存続させてくれたこともありました。彼の素晴らしいインプットがなければ v1 に到達 + することはなかったでしょう。 プロジェクトを進める際に、以下のアルバムたちも支えてくれています。 @@ -150,3 +182,20 @@ Wails では Go のコードとウェブフロントエンドを単一のバイ - [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) - [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) +## スペシャルサンクス + +

+
+ このプロジェクトを後援し、WailsをApple Siliconに移植する取り組みを支援してくれた Paceとても感謝しています!

+ パワフルで素早く簡単に使えるプロジェクト管理ツールをお探しなら、ぜひチェックしてみてください!

+

+ +

+ ライセンスを提供していただいたJetBrains社に感謝します!

+ ロゴをクリックして、感謝の気持ちを伝えてください!

+ +

+ +## ライセンス + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) diff --git a/README.ko.md b/README.ko.md index 075e04229..82b923d2a 100644 --- a/README.ko.md +++ b/README.ko.md @@ -27,7 +27,7 @@ Awesome - Discord + Discord
@@ -43,9 +43,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) +[한국어](README.ko.md) @@ -67,16 +65,17 @@ ## 소개 -Go 프로그램에 웹 인터페이스를 제공하는 전통적인 방법은 내장 웹 서버를 이용하는 것입니다. -Wails는 다르게 접근합니다: Go 코드와 웹 프론트엔드를 단일 바이너리로 래핑하는 기능을 제공합니다. -프로젝트 생성, 컴파일 및 번들링을 처리하여 이를 쉽게 수행할 수 있도록 도구가 제공됩니다. -창의력을 발휘하기만 하면 됩니다! +Go 프로그램에 웹 인터페이스를 제공하는 전통적인 방법은 내장 웹 서버를 이용하는 +것입니다. Wails는 다르게 접근합니다: Go 코드와 웹 프론트엔드를 단일 바이너리로 +래핑하는 기능을 제공합니다. 프로젝트 생성, 컴파일 및 번들링을 처리하여 이를 쉽게 +수행할 수 있도록 도구가 제공됩니다. 창의력을 발휘하기만 하면 됩니다! ## 기능 - 백엔드에 표준 Go 사용 - 이미 익숙한 프론트엔드 기술을 사용하여 UI 구축 -- 사전 구축된 템플릿을 사용하여 Go 프로그램을 위한 풍부한 프론트엔드를 빠르게 생성 +- 사전 구축된 템플릿을 사용하여 Go 프로그램을 위한 풍부한 프론트엔드를 빠르게 생 + 성 - Javascript에서 Go 메서드를 쉽게 호출 - Go 구조체 및 메서드에 대한 자동 생성된 Typescript 정의 - 기본 대화 및 메뉴 @@ -89,13 +88,13 @@ Wails는 다르게 접근합니다: Go 코드와 웹 프론트엔드를 단일 ### 로드맵 -프로젝트 로드맵은 [여기](https://github.com/wailsapp/wails/discussions/1484)에서 +프로젝트 로드맵은 [여기](https://github.com/wailsapp/wails/discussions/1484)에서 확인할 수 있습니다. 개선 요청을 하기 전에 이것을 참조하십시오. ## 시작하기 -설치 지침은 -[공식 웹사이트](https://wails.io/docs/gettingstarted/installation)에 있습니다. +설치 지침은 [공식 웹사이트](https://wails.io/docs/gettingstarted/installation)에 +있습니다. ## 스폰서 @@ -106,22 +105,23 @@ Wails는 다르게 접근합니다: Go 코드와 웹 프론트엔드를 단일 - 이것은 Electron의 대안인가요? - 요구 사항에 따라 다릅니다. Go 프로그래머가 쉽게 가벼운 데스크톱 애플리케이션을 - 만들거나 기존 애플리케이션에 프론트엔드를 추가할 수 있도록 설계되었습니다. - Wails는 메뉴 및 대화 상자와 같은 기본 요소를 제공하므로 가벼운 Electron 대안으로 - 간주될 수 있습니다. + 요구 사항에 따라 다릅니다. Go 프로그래머가 쉽게 가벼운 데스크톱 애플리케이션을 + 만들거나 기존 애플리케이션에 프론트엔드를 추가할 수 있도록 설계되었습니다. + Wails는 메뉴 및 대화 상자와 같은 기본 요소를 제공하므로 가벼운 Electron 대안으 + 로 간주될 수 있습니다. - 이 프로젝트는 누구를 대상으로 하나요? - 서버를 생성하고 이를 보기 위해 브라우저를 열 필요 없이 HTML/JS/CSS 프런트엔드를 - 애플리케이션과 함께 묶고자 하는 프로그래머를 대상으로 합니다. + 서버를 생성하고 이를 보기 위해 브라우저를 열 필요 없이 HTML/JS/CSS 프런트엔드 + 를 애플리케이션과 함께 묶고자 하는 프로그래머를 대상으로 합니다. - Wails 이름의 의미는 무엇인가요? - WebView를 보았을 때 저는 "내가 정말로 원하는 것은 WebView 앱을 구축하기 위한 - 도구를 사용하는거야. 마치 Ruby on Rails 처럼 말이야."라고 생각했습니다. - 그래서 처음에는 말장난(Webview on Rails)이었습니다. - [국가](https://en.wikipedia.org/wiki/Wales)에 대한 영어 이름의 동음이의어이기도 하여 정했습니다. + WebView를 보았을 때 저는 "내가 정말로 원하는 것은 WebView 앱을 구축하기 위한 + 도구를 사용하는거야. 마치 Ruby on Rails 처럼 말이야."라고 생각했습니다. 그래서 + 처음에는 말장난(Webview on Rails)이었습니다. + [국가](https://en.wikipedia.org/wiki/Wales)에 대한 영어 이름의 동음이의어이기 + 도 하여 정했습니다. ## Stargazers 성장 추세 @@ -129,8 +129,9 @@ Wails는 다르게 접근합니다: Go 코드와 웹 프론트엔드를 단일 ## 기여자 -기여자 목록이 추가 정보에 비해 너무 커지고 있습니다! 이 프로젝트에 기여한 모든 놀라운 사람들은 -[여기](https://wails.io/credits#contributors)에 자신의 페이지를 가지고 있습니다. +기여자 목록이 추가 정보에 비해 너무 커지고 있습니다! 이 프로젝트에 기여한 모든 +놀라운 사람들은 [여기](https://wails.io/credits#contributors)에 자신의 페이지를 +가지고 있습니다. ## 라이센스 diff --git a/README.md b/README.md index 5ab9309b4..205d2e571 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Awesome - Discord + Discord
@@ -41,9 +41,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) +[한국어](README.ko.md) @@ -65,9 +63,11 @@ ## Introduction -The traditional method of providing web interfaces to Go programs is via a built-in web server. Wails offers a different -approach: it provides the ability to wrap both Go code and a web frontend into a single binary. Tools are provided to -make this easy for you by handling project creation, compilation and bundling. All you have to do is get creative! +The traditional method of providing web interfaces to Go programs is via a +built-in web server. Wails offers a different approach: it provides the ability +to wrap both Go code and a web frontend into a single binary. Tools are provided +to make this easy for you by handling project creation, compilation and +bundling. All you have to do is get creative! ## Features @@ -86,55 +86,56 @@ make this easy for you by handling project creation, compilation and bundling. A ### Roadmap -The project roadmap may be found [here](https://github.com/wailsapp/wails/discussions/1484). Please consult -it before creating an enhancement request. +The project roadmap may be found +[here](https://github.com/wailsapp/wails/discussions/1484). Please consult this +before open up an enhancement request. ## Getting Started -The installation instructions are on the [official website](https://wails.io/docs/gettingstarted/installation). +The installation instructions are on the +[official website](https://wails.io/docs/gettingstarted/installation). ## Sponsors This project is supported by these kind people / companies: -## Powered By - -[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSource) +

+ +

## FAQ - Is this an alternative to Electron? - Depends on your requirements. It's designed to make it easy for Go programmers to make lightweight desktop - applications or add a frontend to their existing applications. Wails does offer native elements such as menus - and dialogs, so it could be considered a lightweight electron alternative. + Depends on your requirements. It's designed to make it easy for Go programmers + to make lightweight desktop applications or add a frontend to their existing + applications. Wails does offer native elements such as menus and dialogs, so + it could be considered a lightweight electron alternative. - Who is this project aimed at? - Go programmers who want to bundle an HTML/JS/CSS frontend with their applications, without resorting to creating a - server and opening a browser to view it. + Go programmers who want to bundle an HTML/JS/CSS frontend with their + applications, without resorting to creating a server and opening a browser to + view it. - What's with the name? - When I saw WebView, I thought "What I really want is tooling around building a WebView app, a bit like Rails is to - Ruby". So initially it was a play on words (Webview on Rails). It just so happened to also be a homophone of the - English name for the [Country](https://en.wikipedia.org/wiki/Wales) I am from. So it stuck. + When I saw WebView, I thought "What I really want is tooling around building a + WebView app, a bit like Rails is to Ruby". So initially it was a play on words + (Webview on Rails). It just so happened to also be a homophone of the English + name for the [Country](https://en.wikipedia.org/wiki/Wales) I am from. So it + stuck. ## Stargazers over time -
- - - - Star History Chart - - +[![Star History Chart](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) ## Contributors -The contributors list is getting too big for the readme! All the amazing people who have contributed to this -project have their own page [here](https://wails.io/credits#contributors). +The contributors list is getting too big for the readme! All the amazing people +who have contributed to this project have their own page +[here](https://wails.io/credits#contributors). ## License diff --git a/README.pt-br.md b/README.pt-br.md deleted file mode 100644 index 0e3883352..000000000 --- a/README.pt-br.md +++ /dev/null @@ -1,151 +0,0 @@ -

-
-

- -

- Crie aplicativos de desktop usando Go e tecnologias Web. -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) - - - -
- -## Índice - -- [Índice](#índice) -- [Introdução](#introdução) -- [Recursos e funcionalidades](#recursos-e-funcionalidades) - - [Plano de trabalho](#plano-de-trabalho) -- [Iniciando](#iniciando) -- [Patrocinadores](#patrocinadores) -- [Perguntas frequentes](#perguntas-frequentes) -- [Estrelas ao longo do tempo](#estrelas-ao-longo-do-tempo) -- [Colaboradores](#colaboradores) -- [Licença](#licença) -- [Inspiração](#inspiração) - -## Introdução - -O método tradicional de fornecer interfaces da Web para programas Go é por meio de um servidor da Web integrado. Wails oferece uma -abordagem: fornece a capacidade de agrupar o código Go e um front-end da Web em um único binário. As ferramentas são fornecidas para -que torne isso mais fácil para você lidando com a criação, compilação e agrupamento de projetos. Tudo o que você precisa fazer é ser criativo! - -## Recursos e funcionalidades - -- Use Go padrão para o back-end -- Use qualquer tecnologia de front-end com a qual você já esteja familiarizado para criar sua interface do usuário -- Crie rapidamente um front-end avançado para seus programas Go usando modelos pré-construídos -- Chame facilmente métodos Go com JavaScript -- Definições TypeScript geradas automaticamente para suas estruturas e métodos Go -- Diálogos e menus nativos -- Suporte nativo ao modo escuro/claro -- Suporta translucidez moderna e efeitos de "janela fosca" -- Sistema de eventos unificado entre Go e JavaScript -- Poderosa ferramenta cli para gerar e construir rapidamente seus projetos -- Multiplataforma -- Usa mecanismos de renderização nativos - _sem navegador incorporado_! - -### Plano de trabalho - -O plano de trabalho do projeto pode ser encontrado [aqui](https://github.com/wailsapp/wails/discussions/1484). Por favor consulte -isso antes de abrir um pedido de melhoria. - -## Iniciando - -As instruções de instalação estão no [site oficial](https://wails.io/docs/gettingstarted/installation). - -## Patrocinadores - -Este projeto é apoiado por estas simpáticas pessoas/empresas: - - -

- -

- -## Perguntas frequentes - -- Esta é uma alternativa ao Electron? - - Depende de seus requisitos. Ele foi projetado para tornar mais fácil para os programadores Go criar aplicações desktop - e adicionar um front-end aos seus aplicativos existentes. O Wails oferece elementos nativos, como menus - e diálogos, por isso pode ser considerada uma alternativa leve, se comparado ao Electron. - -- A quem se destina este projeto? - - Programadores Go que desejam agrupar um front-end HTML/JS/CSS com seus aplicativos, sem recorrer à criação de um - servidor e abrir um navegador para visualizá-lo. - -- Qual é o significado do nome? - - Quando vi o WebView, pensei "O que eu realmente quero é ferramentas para construir um aplicativo WebView, algo semelhante ao que Rails é para Ruby". Portanto, inicialmente era um jogo de palavras (WebView on Rails). Por acaso, também era um homófono do - Nome em inglês para o [país](https://en.wikipedia.org/wiki/Wales) de onde eu sou. Então ficou com esse nome. - -## Estrelas ao longo do tempo - -[![Star History Chart](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) - -## Colaboradores - -A lista de colaboradores está ficando grande demais para o arquivo readme! Todas as pessoas incríveis que contribuíram para o -projeto tem sua própria página [aqui](https://wails.io/credits#contributors). - -## Licença - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## Inspiração - -Este projeto foi construído ouvindo esses álbuns: - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.ru.md b/README.ru.md deleted file mode 100644 index 76fa59d07..000000000 --- a/README.ru.md +++ /dev/null @@ -1,153 +0,0 @@ -

-
-

- -

- Собирайте Desktop приложения используя Go и Web технологии -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) - - - -
- -## Содержание - -- [Содержание](#содержание) -- [Вступление](#вступление) -- [Особенности](#особенности) - - [Roadmap](#roadmap) -- [Быстрый старт](#быстрый-старт) -- [Спонсоры](#спонсоры) -- [FAQ](#faq) -- [График звёздочек](#график-звёздочек-репозитория-относительно-времени) -- [Контребьюторы](#контребьюторы) -- [Лицензия](#лицензия) -- [Вдохновение](#вдохновение) - -## Вступление - -Обычно, веб-интерфейсы для программ Go - это встроенный веб-сервер и веб-браузер. -У Walls другой подход: он оборачивает как код Go, так и веб-интерфейс в один бинарник (EXE файл). -Облегчает вам создание вашего приложения, управляя созданием, компиляцией и объединением проектов. -Все ограничивается лишь вашей фантазией! - -## Особенности - -- Использование Go для backend -- Поддержка любой frontend технологии, с которой вы уже знакомы для создания вашего UI -- Быстрое создание frontend для ваших программ, используя готовые шаблоны -- Очень лёгкий вызов функций Go из JavaScript -- Автогенерация TypeScript типов для Go структур и функций -- Нативные диалоги и меню -- Нативная поддержка тёмной и светлой темы -- Поддержка современных эффектов прозрачности и "матового окна" -- Единая система эвентов для Go и JavaScript -- Мощный CLI для быстрого создания ваших проектов -- Мультиплатформенность -- Использование нативного движка рендеринга - нет встроенному браузеру! - -### Roadmap - -Roadmap проекта вы можете найти [здесь](https://github.com/wailsapp/wails/discussions/1484). -Пожалуйста, проконсультируйтесь перед предложением улучшения. - -## Быстрый старт - -Инструкции по установке находятся на [официальном сайте](https://wails.io/docs/gettingstarted/installation). - -## Спонсоры - -Проект поддерживается этими добрыми людьми / компаниями: - - -

- -

- -## FAQ - -- Это альтернатива Electron? - - Зависит от ваших требований. Wails разработан для легкого создания Desktop приложений или - расширения интерфейсной части существующих приложений для программистов на Go. Wails действительно - предлагает встроенные элементы, такие как меню и диалоги, так что его можно считать облегченной альтернативой Electron. - -- Для кого предназначен этот проект? - - Для Golang программистов, которые хотят создавать приложения, используя HTML, JS и CSS, - без создания веб-сервера и открытия браузера для их просмотра. - -- Что это за название? - - Когда я увидел WebView, я подумал: "Что мне действительно нужно, так это инструменты для создания приложения WebView, - немного похожие на Rails для Ruby". Изначально это была игра слов (Webview on Rails). Просто так получилось, что это - также омофон английского названия для [Страны](https://en.wikipedia.org/wiki/Wales) от куда я родом. Так что это прижилось. - -## График звёздочек репозитория по времени - -[![График звёзд](https://api.star-history.com/svg?repos=wailsapp/wails&type=Date)](https://star-history.com/#wailsapp/wails&Date) - -## Контрибьюторы - -Список участников слишком велик для README! У всех замечательных людей, которые внесли свой вклад в этот -проект, есть своя [страничка](https://wails.io/credits#contributors). - -## Лицензия - -[![Статус FOSSA](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## Вдохновение - -Этот проект был создан, в основном, под эти альбомы: - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.tr.md b/README.tr.md deleted file mode 100644 index e9b16ca76..000000000 --- a/README.tr.md +++ /dev/null @@ -1,156 +0,0 @@ -

-
-

- -

- Go ve Web Teknolojilerini kullanarak masaüstü uygulamaları oluşturun. -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · -[Türkçe](README.tr.md) - - - -
- -## İçerik - -- [İçerik](#içerik) -- [Giriş](#giriş) -- [Özellikler](#özellikler) - - [Yol Haritası](#yol-haritası) -- [Başlarken](#başlarken) -- [Sponsorlar](#sponsorlar) -- [Sıkça sorulan sorular](#sıkça-sorulan-sorular) -- [Zaman içinda yıldızlayanlar](#zaman-içinde-yıldızlayanlar) -- [Katkıda bulunanlar](#katkıda-bulunanlar) -- [Lisans](#lisans) -- [İlham](#ilham) - -## Giriş - -Go programlarına web arayüzleri sağlamak için geleneksel yöntem, yerleşik bir web sunucusu kullanmaktır. Wails, farklı bir yaklaşım sunar: Hem Go kodunu hem de bir web ön yüzünü tek bir ikili dosyada paketleme yeteneği sağlar. Proje oluşturma, derleme ve paketleme işlemlerini kolaylaştıran araçlar sunar. Tek yapmanız gereken yaratıcı olmaktır! - -## Özellikler - -- Backend için standart Go kullanın -- Kullanıcı arayüzünüzü oluşturmak için zaten aşina olduğunuz herhangi bir frontend teknolojisini kullanın -- Hazır şablonlar kullanarak Go programlarınız için hızlıca zengin ön yüzler oluşturun -- Javascript'ten Go metodlarını kolayca çağırın -- Go yapı ve metodlarınız için otomatik oluşturulan Typescript tanımları -- Yerel Diyaloglar ve Menüler -- Yerel Karanlık / Aydınlık mod desteği -- Modern saydamlık ve "buzlu cam" efektlerini destekler -- Go ve Javascript arasında birleşik olay sistemi -- Projelerinizi hızlıca oluşturmak ve derlemek için güçlü bir komut satırı aracı -- Çoklu platform desteği -- Yerel render motorlarını kullanır - _gömülü tarayıcı yok_! - - -### Yol Haritesı - -Proje yol haritasına [buradan](https://github.com/wailsapp/wails/discussions/1484) ulaşabilirsiniz. Lütfen bir iyileştirme talebi oluşturmadan önce danışın. - - -## Başlarken - -Kurulum talimatları [resmi web sitesinde](https://wails.io/docs/gettingstarted/installation) bulunmaktadır. - - -## Sponsorlar - -Bu proje, aşağıdaki nazik insanlar / şirketler tarafından desteklenmektedir: - - -

- -

- -## Sıkça Sorulan Sorular - -- Bu Electron'a alternatif mi? - - Gereksinimlerinize bağlıdır. Go programcılarının hafif masaüstü uygulamaları yapmasını veya mevcut uygulamalarına bir ön yüz eklemelerini kolaylaştırmak için tasarlanmıştır. Wails, menüler ve diyaloglar gibi yerel öğeler sunduğundan, hafif bir Electron alternatifi olarak kabul edilebilir. - -- Bu proje kimlere yöneliktir? - - HTML/JS/CSS ön yüzünü uygulamalarıyla birlikte paketlemek isteyen, ancak bir sunucu oluşturup bir tarayıcı açmaya başvurmadan bunu yapmak isteyen Go programcıları için. - -- İsmin anlamı nedir? - - WebView'i gördüğümde, "Aslında istediğim şey, WebView uygulaması oluşturmak için araçlar, biraz Rails'in Ruby için olduğu gibi" diye düşündüm. Bu nedenle başlangıçta kelime oyunu (Rails üzerinde Webview) olarak ortaya çıktı. Ayrıca, benim geldiğim [ülkenin](https://en.wikipedia.org/wiki/Wales) İngilizce adıyla homofon olması tesadüf oldu. Bu yüzden bu isim kaldı. - - -## Zaman içinda yıldızlayanlar - - - - - - Star History Chart - - - -## Katkıda Bulunanlar - -Katkıda bulunanların listesi, README için çok büyük hale geldi! Bu projeye katkıda bulunan tüm harika insanların kendi sayfaları [burada](https://wails.io/credits#contributors) bulunmaktadır. - - -## Lisans - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## İlham - -Bu proje esas olarak aşağıdaki albümler dinlenilerek kodlandı: - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) - diff --git a/README.uz.md b/README.uz.md deleted file mode 100644 index 807262405..000000000 --- a/README.uz.md +++ /dev/null @@ -1,159 +0,0 @@ -

-
-

- -

- Go va Web texnologiyalaridan foydalangan holda ish stoli ilovalarini yarating -
-
- - GitHub - - - - - - Go Reference - - - CodeFactor - - - - - - Awesome - - - Discord - -
- - Build - - - GitHub tag (latest SemVer pre-release) - -

- -
- - - -[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) - - - -
- -## Tarkib - -- [Tarkib](#tarkib) -- [Kirish](#kirish) -- [Xususiyatlari](#xususiyatlari) - - [Yo'l xaritasi](#yol-xaritasi) -- [Ishni boshlash](#ishni-boshlash) -- [Homiylar](#homiylar) -- [FAQ](#faq) -- [Vaqt o'tishi bilan yulduzlar](#vaqt-otishi-bilan-yulduzlar) -- [Ishtirokchilar](#homiylar) -- [Litsenziya](#litsenziya) -- [Ilhomlanish](#ilhomlanish) - -## Kirish - -Odatda, Go dasturlari uchun veb-interfeyslar o'rnatilgan veb-server va veb-brauzerdir. -Walls boshqacha yondashuvni qo'llaydi: u Go kodini ham, veb-interfeysni ham bitta ikkilik (e.g: EXE)fayliga o'raydi. -Loyihalarni yaratish, kompilyatsiya qilish va birlashtirishni boshqarish orqali ilovangizni yaratishni osonlashtiradi. -Hamma narsa faqat sizning tasavvuringiz bilan cheklangan! - -## Xususiyatlari - -- Backend uchun standart Go dan foydalaning -- UI yaratish uchun siz allaqachon tanish bo'lgan har qanday frontend texnologiyasidan foydalaning -- Oldindan tayyorlangan shablonlardan foydalanib, Go dasturlaringiz uchun tezda boy frontendlarni yarating -- Javascriptdan Go methodlarini osongina chaqiring -- Go struktura va methodlari uchun avtomatik yaratilgan Typescript ta'riflari -- Mahalliy Dialoglar va Menyular -- Mahalliy Dark / Light rejimini qo'llab-quvvatlash -- Zamonaviy shaffoflik va "muzli oyna" effektlarini qo'llab-quvvatlaydi -- Go va Javascript o'rtasidagi yagona hodisa tizimi -- Loyihalaringizni tezda yaratish va qurish uchun kuchli cli vositasi -- Ko'p platformali -- Mahalliy renderlash mexanizmlaridan foydalanadi - _o'rnatilgan brauzer yo'q_! - -### Yo'l xaritasi - -Loyihaning yoʻl xaritasini [bu yerdan](https://github.com/wailsapp/wails/discussions/1484) topish mumkin. Iltimos, maslahatlashing -Buni yaxshilash so'rovini ochishdan oldin. - -## Ishni boshlash - -O'rnatish bo'yicha ko'rsatmalar [Rasmiy veb saytda](https://wails.io/docs/gettingstarted/installation) mavjud. - -## Homiylar - -Ushbu loyiha quyidagi mehribon odamlar / kompaniyalar tomonidan qo'llab-quvvatlanadi: - - -

- -

- -## FAQ - -- Bu Elektronga muqobilmi? - - Sizning talablaringizga bog'liq. Bu Go dasturchilariga yengil ish stoli yaratishni osonlashtirish uchun yaratilgan - ilovalar yoki ularning mavjud ilovalariga frontend qo'shing. Wails menyular kabi mahalliy elementlarni taklif qiladi - va dialoglar, shuning uchun uni yengil elektron muqobili deb hisoblash mumkin. - -- Ushbu loyiha kimlar uchun? - - Server yaratmasdan va uni ko'rish uchun brauzerni ochmasdan, o'z ilovalari bilan HTML/JS/CSS orqali frontendini birlashtirmoqchi bo'lgan dasturchilar uchun. - -- Bu qanday nom? - - Men WebViewni ko'rganimda, men shunday deb o'yladim: "Menga WebView ilovasini yaratish uchun vositalar kerak. - biroz Rails for Rubyga o'xshaydi." Demak, dastlab bu so'zlar ustida o'yin edi (Railsda Webview). Shunday bo'ldi. - u men kelgan [Mamlakat](https://en.wikipedia.org/wiki/Wales)ning inglizcha nomining omofonidir. - -## Vaqt o'tishi bilan yulduzlar - - - - - - Yulduzlar tarixi jadvali - - - -## Ishtirokchilar - -Ishtirokchilar roʻyxati oʻqish uchun juda kattalashib bormoqda! Bunga hissa qo'shgan barcha ajoyib odamlarning -loyihada o'z sahifasi bor [bu yerga](https://wails.io/credits#contributors). - -## Litsenziya - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fwailsapp%2Fwails.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large) - -## Ilhomlanish - -Ushbu loyiha asosan quyidagi albomlar uchun kodlangan: - -- [Manic Street Preachers - Resistance Is Futile](https://open.spotify.com/album/1R2rsEUqXjIvAbzM0yHrxA) -- [Manic Street Preachers - This Is My Truth, Tell Me Yours](https://open.spotify.com/album/4VzCL9kjhgGQeKCiojK1YN) -- [The Midnight - Endless Summer](https://open.spotify.com/album/4Krg8zvprquh7TVn9OxZn8) -- [Gary Newman - Savage (Songs from a Broken World)](https://open.spotify.com/album/3kMfsD07Q32HRWKRrpcexr) -- [Steve Vai - Passion & Warfare](https://open.spotify.com/album/0oL0OhrE2rYVns4IGj8h2m) -- [Ben Howard - Every Kingdom](https://open.spotify.com/album/1nJsbWm3Yy2DW1KIc1OKle) -- [Ben Howard - Noonday Dream](https://open.spotify.com/album/6astw05cTiXEc2OvyByaPs) -- [Adwaith - Melyn](https://open.spotify.com/album/2vBE40Rp60tl7rNqIZjaXM) -- [Gwidaith Hen Fran - Cedors Hen Wrach](https://open.spotify.com/album/3v2hrfNGINPLuDP0YDTOjm) -- [Metallica - Metallica](https://open.spotify.com/album/2Kh43m04B1UkVcpcRa1Zug) -- [Bloc Party - Silent Alarm](https://open.spotify.com/album/6SsIdN05HQg2GwYLfXuzLB) -- [Maxthor - Another World](https://open.spotify.com/album/3tklE2Fgw1hCIUstIwPBJF) -- [Alun Tan Lan - Y Distawrwydd](https://open.spotify.com/album/0c32OywcLpdJCWWMC6vB8v) diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 4c09d0c45..1f67d21ff 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -27,7 +27,7 @@ Awesome - Discord + Discord
@@ -43,9 +43,7 @@ [English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md) · -[한국어](README.ko.md) · [Español](README.es.md) · [Português](README.pt-br.md) · -[Русский](README.ru.md) · [Francais](README.fr.md) · [Uzbek](README.uz.md) · [Deutsch](README.de.md) · -[Türkçe](README.tr.md) +[한국어](README.ko.md) @@ -67,8 +65,9 @@ ## 项目介绍 -为 Go 程序提供 Web 界面的传统方法是通过内置 Web 服务器。Wails 提供了一种不同的方法:它提供了将 Go 代码和 Web -前端一起打包成单个二进制文件的能力。通过提供的工具,可以很轻松的完成项目的创建、编译和打包。你所要做的就是发挥创造力! +为 Go 程序提供 Web 界面的传统方法是通过内置 Web 服务器。Wails 提供了一种不同的方 +法:它提供了将 Go 代码和 Web 前端一起打包成单个二进制文件的能力。通过提供的工具 +,可以很轻松的完成项目的创建、编译和打包。你所要做的就是发挥创造力! ## 功能 @@ -86,11 +85,12 @@ ### 路线图 -项目路线图可在 [此处](https://github.com/wailsapp/wails/discussions/1484) 找到。在提出增强请求之前请查阅此内容。 +项目路线图可在 [此处](https://github.com/wailsapp/wails/discussions/1484) 找到。 +在提出增强请求之前请查阅此内容。 ## 快速入门 -使用说明在 [官网](https://wails.io/zh-Hans/docs/gettingstarted/installation/)。 +使用说明在 [官网](https://wails.io/docs/gettingstarted/installation)。 ## 赞助商 @@ -102,16 +102,21 @@ - 它是 Electron 的替代品吗? - 取决于您的要求。它旨在使 Go 程序员可以轻松制作轻量级桌面应用程序或在其现有应用程序中添加前端。尽管 Wails 当前不提供对诸如菜单之类的原生元素的钩子,但将来可能会改变。 + 取决于您的要求。它旨在使 Go 程序员可以轻松制作轻量级桌面应用程序或在其现有应用 + 程序中添加前端。尽管 Wails 当前不提供对诸如菜单之类的原生元素的钩子,但将来可 + 能会改变。 - 这个项目针对的是哪些人? - 希望将 HTML / JS / CSS 前端与其应用程序捆绑在一起的程序员,而不是借助创建服务并打开浏览器进行查看的方式。 + 希望将 HTML / JS / CSS 前端与其应用程序捆绑在一起的程序员,而不是借助创建服务 + 并打开浏览器进行查看的方式。 - 名字怎么来的? - 当我看到 WebView 时,我想"我真正想要的是围绕构建 WebView 应用程序工作,有点像 Rails 对于 Ruby"。因此,最初它是一个文字游戏(Webview on - Rails)。碰巧也是我来自的 [国家](https://en.wikipedia.org/wiki/Wales) 的英文名字的同音。所以就是它了。 + 当我看到 WebView 时,我想"我真正想要的是围绕构建 WebView 应用程序工作,有点像 + Rails 对于 Ruby"。因此,最初它是一个文字游戏(Webview on Rails)。碰巧也是我来 + 自的 [国家](https://en.wikipedia.org/wiki/Wales) 的英文名字的同音。所以就是它 + 了。 ## 星星增长趋势 @@ -119,7 +124,8 @@ ## 贡献者 -贡献者列表对于 README 文件来说太大了!所有为这个项目做出贡献的了不起的人在[这里](https://wails.io/credits#contributors)都有自己的页面。 +贡献者列表对于 README 文件来说太大了!所有为这个项目做出贡献的了不起的人 +在[这里](https://wails.io/credits#contributors)都有自己的页面。 ## 许可证 diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index cb096f872..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,38 +0,0 @@ -# Security Policy - -## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| 2.x.x | :white_check_mark: | -| 3.0.x-alpha | :x: | - - -## Reporting a Vulnerability - -If you believe you have found a security vulnerability in our project, we encourage you to let us know right away. -We will investigate all legitimate reports and do our best to quickly fix the problem. - -Before reporting though, please review our security policy below. - -### How to Report - -To report a security vulnerability, please use GitHub's [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability) feature. If possible, please include as much information as possible. -This may include steps to reproduce, impact of the vulnerability, and anything else you believe would help us understand the problem. -**Please do not include any sensitive or personal information in your report**. - -### What to Expect - -When you report a vulnerability, here's what you can expect: - -- **Acknowledgement**: We will acknowledge your email within 48 hours, and you'll receive a more detailed response to your email within 72 hours indicating the next steps in handling your report. - -- **Updates**: After the initial reply to your report, our team will keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least once a week. - -- **Confidentiality**: We will maintain strict confidentiality of your report until the security issue is resolved. - -- **Issue Resolution**: If the issue is confirmed, we will release a patch as soon as possible depending on complexity of the fix. - -- **Recognition**: We recognize and appreciate every individual who helps us identify and fix vulnerabilities in our project. While we do not currently have a bounty program, we would be happy to publicly acknowledge your responsible disclosure. - -We strive to make Wails safe for everyone, and we greatly appreciate the assistance of security researchers and users in helping us identify and fix vulnerabilities. Thank you for your contribution to the security of this project. diff --git a/scripts/AUTOMATION-README.md b/scripts/AUTOMATION-README.md deleted file mode 100644 index 4096b1781..000000000 --- a/scripts/AUTOMATION-README.md +++ /dev/null @@ -1,123 +0,0 @@ -# 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 deleted file mode 100644 index 6f6edd3ad..000000000 --- a/scripts/issue-triage.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -# 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 deleted file mode 100644 index 5809b43a1..000000000 --- a/scripts/issue-triage.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/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 deleted file mode 100644 index 75fae4c3b..000000000 --- a/scripts/pr-review-helper.ps1 +++ /dev/null @@ -1,152 +0,0 @@ -# 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/scripts/sponsors/generate-sponsor-image.sh b/scripts/sponsors/generate-sponsor-image.sh index b034a0176..be90c8299 100755 --- a/scripts/sponsors/generate-sponsor-image.sh +++ b/scripts/sponsors/generate-sponsor-image.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash -npm install sponsorkit@16.4.2 +npm install sponsorkit@0.6.1 npx sponsorkit -o ../../website/static/img/ diff --git a/scripts/sponsors/package-lock.json b/scripts/sponsors/package-lock.json index 2bb15b685..cb6cc8b9e 100644 --- a/scripts/sponsors/package-lock.json +++ b/scripts/sponsors/package-lock.json @@ -9,469 +9,265 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "sponsorkit": "^16.5.0" - }, - "engines": { - "node": ">=22.0.0" + "sponsorkit": "^0.8.2" } }, - "node_modules/@emnapi/runtime": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", - "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", - "license": "MIT", - "optional": true, + "node_modules/@antfu/utils": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.4.tgz", + "integrity": "sha512-qe8Nmh9rYI/HIspLSTwtbMFPj6dISG6+dJnOguTlPNXtCvS2uezdxscVBb7/3DrmNbQK49TDqpkSQ1chbRGdpQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dependencies": { - "tslib": "^2.4.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", - "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", - "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.2.0" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", - "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", - "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.2.0" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", - "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", - "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", - "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", - "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", - "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", - "cpu": [ - "ppc64" - ], - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", - "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", - "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", - "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", - "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.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", - "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", - "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.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", - "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", - "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.2.0" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", - "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", - "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.2.0" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", - "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", - "cpu": [ - "ppc64" - ], - "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-ppc64": "1.2.0" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", - "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", - "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.2.0" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", - "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", - "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.2.0" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", - "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", - "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.2.0" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", - "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", - "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.2.0" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", - "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.4.4" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", - "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", - "cpu": [ - "arm64" - ], - "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-ia32": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", - "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", - "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.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", - "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", - "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/@quansync/fs": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.3.tgz", - "integrity": "sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==", - "license": "MIT", - "dependencies": { - "quansync": "^0.2.10" - }, - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/ansis": { + "node_modules/ansi-escape-sequences": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz", - "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==", - "license": "ISC", + "resolved": "https://registry.npmmirror.com/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", + "integrity": "sha512-dzW9kHxH011uBsidTXd14JXgzye/YLb2LzeKZ4bsgl/Knwx8AtbSFkkGxagdNOoh0DlqHCmfiEjWKBaqjOanVw==", + "dependencies": { + "array-back": "^3.0.1" + }, "engines": { - "node": ">=14" + "node": ">=8.0.0" } }, - "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==", - "license": "MIT", + "node_modules/ansi-escape-sequences/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "engines": { + "node": ">=4" + } + }, + "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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dependencies": { + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "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==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/cliss": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/cliss/-/cliss-0.0.2.tgz", + "integrity": "sha512-6rj9pgdukjT994Md13JCUAgTk91abAKrygL9sAvmHY4F6AKMOV8ccGaxhUUfcBuyg3sundWnn3JE0Mc9W6ZYqw==", + "dependencies": { + "command-line-usage": "^4.0.1", + "deepmerge": "^2.0.0", + "get-stdin": "^5.0.1", + "inspect-parameters-declaration": "0.0.9", + "object-to-arguments": "0.0.8", + "pipe-functions": "^1.3.0", + "strip-ansi": "^4.0.0", + "yargs-parser": "^7.0.0" + } + }, + "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==", + "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==", + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } @@ -480,7 +276,6 @@ "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" @@ -493,7 +288,6 @@ "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" }, @@ -504,114 +298,1160 @@ "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" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "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/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": ">= 0.8" + } + }, + "node_modules/command-line-usage": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/command-line-usage/-/command-line-usage-4.1.0.tgz", + "integrity": "sha512-MxS8Ad995KpdAC0Jopo/ovGIroV/m0KHwzKfXxKag6FHOkGsH8/lv5yjgablcRxCJJC0oJeUMuO/gmaq+Wq46g==", + "dependencies": { + "ansi-escape-sequences": "^4.0.0", + "array-back": "^2.0.0", + "table-layout": "^0.4.2", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmmirror.com/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "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==", + "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-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.2.tgz", + "integrity": "sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } }, "node_modules/destr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", - "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", - "license": "MIT" + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/destr/-/destr-1.2.1.tgz", + "integrity": "sha512-ud8w0qMLlci6iFG7CNgeRr8OcbUWMsbfjtWft1eJ5Luqrz/M8Ebqk/KCzne8rKUlIQWWfLv0wD6QHrqOf4GshA==" }, "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "engines": { "node": ">=8" } }, - "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" + "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==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { - "url": "https://dotenvx.com" + "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" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/for-each-property": { + "version": "0.0.4", + "resolved": "https://registry.npmmirror.com/for-each-property/-/for-each-property-0.0.4.tgz", + "integrity": "sha512-xYs28PM0CKXETFzuGC6ZooH0voZlsSDZwidJcy92flQJi3PK7i3gZx23xHXCPOaD4zmet3bDo+wS7E7SujrlCw==", + "dependencies": { + "get-prototype-chain": "^1.0.1" + } + }, + "node_modules/for-each-property-deep": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/for-each-property-deep/-/for-each-property-deep-0.0.3.tgz", + "integrity": "sha512-qzP8QkODWVVRPpWiBZacSbBl67cTTWoBfxMG0wE46AsS1yl7qv05sGN+dHvD4s4tnvl/goe6Sp4qBI+rlVBgNg==", + "dependencies": { + "for-each-property": "0.0.4" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-prototype-chain": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-prototype-chain/-/get-prototype-chain-1.0.1.tgz", + "integrity": "sha512-2m7WZ0jveIg/dAbCbpUxEToaJ8Dmti5EkgDP8YM3UpHUT6SAORjE2odP8XQGNVGXMHi8q8cCCoy3HTByTaTVTw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/image-data-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/image-data-uri/-/image-data-uri-2.0.1.tgz", + "integrity": "sha512-BZh721F2Q5TwBdwpiqrBrHEdj8daj8KuMZK/DOCyqQlz1CqFhhuZWbK5ZCUnAvFJr8LaKHTaWl9ja3/a3DC2Ew==", + "dependencies": { + "fs-extra": "^0.26.7", + "magicli": "0.0.8", + "mime-types": "^2.1.18", + "request": "^2.88.0" + }, + "bin": { + "image-data-uri": "bin/magicli.js" + } + }, + "node_modules/image-data-uri/node_modules/fs-extra": { + "version": "0.26.7", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-0.26.7.tgz", + "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "node_modules/image-data-uri/node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inspect-function": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/inspect-function/-/inspect-function-0.3.4.tgz", + "integrity": "sha512-s0RsbJqK/sNZ+U1mykGoTickog3ea1A9Qk4mXniogOBu4PgkkZ56elScO7QC/r8D94lhGmJ2NyDI1ipOA/uq/g==", + "dependencies": { + "inspect-parameters-declaration": "0.0.8", + "magicli": "0.0.8", + "split-skip": "0.0.1", + "stringify-parameters": "0.0.4", + "unpack-string": "0.0.2" + }, + "bin": { + "inspect-function": "bin/magicli.js" + } + }, + "node_modules/inspect-function/node_modules/inspect-function": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/inspect-function/-/inspect-function-0.2.2.tgz", + "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "dependencies": { + "split-skip": "0.0.1", + "unpack-string": "0.0.2" + } + }, + "node_modules/inspect-function/node_modules/inspect-parameters-declaration": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/inspect-parameters-declaration/-/inspect-parameters-declaration-0.0.8.tgz", + "integrity": "sha512-W4QzN1LgFmasKOM+NoLlDd2OAZM3enNZlVUOXoGQKmYBDFgxoPDOyebF55ALaf8avyM9TavNwibXxg347RrzCg==", + "dependencies": { + "magicli": "0.0.5", + "split-skip": "0.0.2", + "stringify-parameters": "0.0.4", + "unpack-string": "0.0.2" + }, + "bin": { + "inspect-parameters-declaration": "bin/cli.js" + } + }, + "node_modules/inspect-function/node_modules/inspect-parameters-declaration/node_modules/magicli": { + "version": "0.0.5", + "resolved": "https://registry.npmmirror.com/magicli/-/magicli-0.0.5.tgz", + "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "dependencies": { + "commander": "^2.9.0", + "get-stdin": "^5.0.1", + "inspect-function": "^0.2.1", + "pipe-functions": "^1.2.0" + } + }, + "node_modules/inspect-function/node_modules/inspect-parameters-declaration/node_modules/split-skip": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/split-skip/-/split-skip-0.0.2.tgz", + "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" + }, + "node_modules/inspect-function/node_modules/split-skip": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/split-skip/-/split-skip-0.0.1.tgz", + "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + }, + "node_modules/inspect-parameters-declaration": { + "version": "0.0.9", + "resolved": "https://registry.npmmirror.com/inspect-parameters-declaration/-/inspect-parameters-declaration-0.0.9.tgz", + "integrity": "sha512-c3jrKKA1rwwrsjdGMAo2hFWV0vNe3/RKHxpE/OBt41LP3ynOVI1qmgxpZYK5SQu3jtWCyaho8L7AZzCjJ4mEUw==", + "dependencies": { + "magicli": "0.0.5", + "split-skip": "0.0.2", + "stringify-parameters": "0.0.4", + "unpack-string": "0.0.2" + }, + "bin": { + "inspect-parameters-declaration": "bin/cli.js" + } + }, + "node_modules/inspect-parameters-declaration/node_modules/inspect-function": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/inspect-function/-/inspect-function-0.2.2.tgz", + "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "dependencies": { + "split-skip": "0.0.1", + "unpack-string": "0.0.2" + } + }, + "node_modules/inspect-parameters-declaration/node_modules/inspect-function/node_modules/split-skip": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/split-skip/-/split-skip-0.0.1.tgz", + "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + }, + "node_modules/inspect-parameters-declaration/node_modules/magicli": { + "version": "0.0.5", + "resolved": "https://registry.npmmirror.com/magicli/-/magicli-0.0.5.tgz", + "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "dependencies": { + "commander": "^2.9.0", + "get-stdin": "^5.0.1", + "inspect-function": "^0.2.1", + "pipe-functions": "^1.2.0" + } + }, + "node_modules/inspect-property": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/inspect-property/-/inspect-property-0.0.6.tgz", + "integrity": "sha512-LgjHkRl9W6bj2n+kWrAOgvCYPTYt+LanE4rtd/vKNq6yEb+SvVV7UTLzoSPpDX6/U1cAz7VfqPr+lPAIz7wHaQ==", + "dependencies": { + "for-each-property": "0.0.4", + "for-each-property-deep": "0.0.3", + "inspect-function": "^0.3.1" } }, "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" + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "license": "MIT", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", "bin": { - "jiti": "lib/jiti-cli.mjs" + "jiti": "bin/jiti.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "optionalDependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash.padend": { + "version": "4.6.1", + "resolved": "https://registry.npmmirror.com/lodash.padend/-/lodash.padend-4.6.1.tgz", + "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magicli": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/magicli/-/magicli-0.0.8.tgz", + "integrity": "sha512-x/eBenweAHF+DsYy172sK4doRxZl0yrJnfxhLJiN7H6hPM3Ya0PfI6uBZshZ3ScFFSQD7HXgBqMdbnXKEZsO1g==", + "dependencies": { + "cliss": "0.0.2", + "find-up": "^2.1.0", + "for-each-property": "0.0.4", + "inspect-property": "0.0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/node-abi": { + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.33.0.tgz", + "integrity": "sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-fetch-native": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", - "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", - "license": "MIT" + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-0.1.8.tgz", + "integrity": "sha512-ZNaury9r0NxaT2oL65GvdGDy+5PlSaHTovT6JV5tOW07k1TQmgC0olZETa4C9KZg0+6zBr99ctTYa3Utqj9P/Q==" }, - "node_modules/ofetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", - "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==", - "license": "MIT", + "node_modules/node-html-parser": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.5.tgz", + "integrity": "sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==", "dependencies": { - "destr": "^2.0.3", - "node-fetch-native": "^1.6.4", - "ufo": "^1.5.4" + "css-select": "^5.1.0", + "he": "1.2.0" } }, - "node_modules/quansync": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", - "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "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==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-to-arguments": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/object-to-arguments/-/object-to-arguments-0.0.8.tgz", + "integrity": "sha512-BfWfuAwuhdH1bhMG5EG90WE/eckkBhBvnke8eSEkCDXoLE9Jk5JwYGTbCx1ehGwV48HvBkn62VukPBdlMUOY9w==", + "dependencies": { + "inspect-parameters-declaration": "0.0.10", + "magicli": "0.0.5", + "split-skip": "0.0.2", + "stringify-parameters": "0.0.4", + "unpack-string": "0.0.2" + }, + "bin": { + "object-to-arguments": "bin/cli.js" + } + }, + "node_modules/object-to-arguments/node_modules/inspect-function": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/inspect-function/-/inspect-function-0.2.2.tgz", + "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "dependencies": { + "split-skip": "0.0.1", + "unpack-string": "0.0.2" + } + }, + "node_modules/object-to-arguments/node_modules/inspect-function/node_modules/split-skip": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/split-skip/-/split-skip-0.0.1.tgz", + "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + }, + "node_modules/object-to-arguments/node_modules/inspect-parameters-declaration": { + "version": "0.0.10", + "resolved": "https://registry.npmmirror.com/inspect-parameters-declaration/-/inspect-parameters-declaration-0.0.10.tgz", + "integrity": "sha512-L8/Bvt9iDXQTZ63xY5/MAyvzz+FagR/qGh1kIXvUpsno3AAE0Z95d6QO51zrcMGaEGpwh/57idfMxTxbvRmytg==", + "dependencies": { + "magicli": "0.0.5", + "split-skip": "0.0.2", + "stringify-parameters": "0.0.4", + "unpack-string": "0.0.2" + }, + "bin": { + "inspect-parameters-declaration": "bin/cli.js" + } + }, + "node_modules/object-to-arguments/node_modules/magicli": { + "version": "0.0.5", + "resolved": "https://registry.npmmirror.com/magicli/-/magicli-0.0.5.tgz", + "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "dependencies": { + "commander": "^2.9.0", + "get-stdin": "^5.0.1", + "inspect-function": "^0.2.1", + "pipe-functions": "^1.2.0" + } + }, + "node_modules/ohmyfetch": { + "version": "0.4.21", + "resolved": "https://registry.npmmirror.com/ohmyfetch/-/ohmyfetch-0.4.21.tgz", + "integrity": "sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==", + "dependencies": { + "destr": "^1.2.0", + "node-fetch-native": "^0.1.8", + "ufo": "^0.8.6", + "undici": "^5.12.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/pipe-functions": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/pipe-functions/-/pipe-functions-1.3.0.tgz", + "integrity": "sha512-6Rtbp7criZRwedlvWbUYxqlqJoAlMvYHo2UcRWq79xZ54vZcaNHpVBOcWkX3ErT2aUA69tv+uiv4zKJbhD/Wgg==" + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "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.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reduce-flatten": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/reduce-flatten/-/reduce-flatten-1.0.1.tgz", + "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -620,104 +1460,482 @@ } }, "node_modules/sharp": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", - "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "version": "0.31.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz", + "integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", - "detect-libc": "^2.0.4", - "semver": "^7.7.2" + "detect-libc": "^2.0.1", + "node-addon-api": "^5.0.0", + "prebuild-install": "^7.1.1", + "semver": "^7.3.8", + "simple-get": "^4.0.1", + "tar-fs": "^2.1.1", + "tunnel-agent": "^0.6.0" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=14.15.0" }, "funding": { "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.3", - "@img/sharp-darwin-x64": "0.34.3", - "@img/sharp-libvips-darwin-arm64": "1.2.0", - "@img/sharp-libvips-darwin-x64": "1.2.0", - "@img/sharp-libvips-linux-arm": "1.2.0", - "@img/sharp-libvips-linux-arm64": "1.2.0", - "@img/sharp-libvips-linux-ppc64": "1.2.0", - "@img/sharp-libvips-linux-s390x": "1.2.0", - "@img/sharp-libvips-linux-x64": "1.2.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", - "@img/sharp-libvips-linuxmusl-x64": "1.2.0", - "@img/sharp-linux-arm": "0.34.3", - "@img/sharp-linux-arm64": "0.34.3", - "@img/sharp-linux-ppc64": "0.34.3", - "@img/sharp-linux-s390x": "0.34.3", - "@img/sharp-linux-x64": "0.34.3", - "@img/sharp-linuxmusl-arm64": "0.34.3", - "@img/sharp-linuxmusl-x64": "0.34.3", - "@img/sharp-wasm32": "0.34.3", - "@img/sharp-win32-arm64": "0.34.3", - "@img/sharp-win32-ia32": "0.34.3", - "@img/sharp-win32-x64": "0.34.3" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "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/split-skip": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/split-skip/-/split-skip-0.0.2.tgz", + "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" + }, "node_modules/sponsorkit": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/sponsorkit/-/sponsorkit-16.5.0.tgz", - "integrity": "sha512-GvlLg88eAEbKzROwAspT+PQTMfHN9KQ+zgPqBBvV1W2jQmKxOtnv9vjgByXvXA2dvTjnksdvbTuwqhJZllyLQA==", - "license": "MIT", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/sponsorkit/-/sponsorkit-0.8.2.tgz", + "integrity": "sha512-Gxh7hkTUuUVj823+BnwC77Rl5ztFEY00qA2QVULOU0N5qgj1YEP3/0BB/EayfPrHeV7HbAOwSeAnqC8/yoRABA==", "dependencies": { - "ansis": "^4.1.0", - "cac": "^6.7.14", - "consola": "^3.4.2", - "dotenv": "^16.5.0", - "ofetch": "^1.4.1", - "sharp": "^0.34.2", - "unconfig": "^7.3.2" + "consola": "^2.15.3", + "dotenv": "^16.0.3", + "fs-extra": "^11.1.0", + "image-data-uri": "^2.0.1", + "node-html-parser": "^6.1.5", + "ohmyfetch": "^0.4.21", + "picocolors": "^1.0.0", + "sharp": "^0.31.3", + "unconfig": "^0.3.7", + "yargs": "^17.7.1" }, "bin": { - "sponsorkit": "bin/sponsorkit.mjs" + "sponsorkit": "bin/sponsorkit.js" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, - "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", - "optional": true + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "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/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-parameters": { + "version": "0.0.4", + "resolved": "https://registry.npmmirror.com/stringify-parameters/-/stringify-parameters-0.0.4.tgz", + "integrity": "sha512-H3L90ERn5UPtkpO8eugnKcLgpIVlvTyUTrcLGm607AV5JDH6z0GymtNLr3gjGlP6I6NB/mxNX9QpY6jEQGLPdQ==", + "dependencies": { + "magicli": "0.0.5", + "unpack-string": "0.0.2" + }, + "bin": { + "stringify-parameters": "bin/cli.js" + } + }, + "node_modules/stringify-parameters/node_modules/inspect-function": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/inspect-function/-/inspect-function-0.2.2.tgz", + "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "dependencies": { + "split-skip": "0.0.1", + "unpack-string": "0.0.2" + } + }, + "node_modules/stringify-parameters/node_modules/magicli": { + "version": "0.0.5", + "resolved": "https://registry.npmmirror.com/magicli/-/magicli-0.0.5.tgz", + "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "dependencies": { + "commander": "^2.9.0", + "get-stdin": "^5.0.1", + "inspect-function": "^0.2.1", + "pipe-functions": "^1.2.0" + } + }, + "node_modules/stringify-parameters/node_modules/split-skip": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/split-skip/-/split-skip-0.0.1.tgz", + "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/table-layout": { + "version": "0.4.5", + "resolved": "https://registry.npmmirror.com/table-layout/-/table-layout-0.4.5.tgz", + "integrity": "sha512-zTvf0mcggrGeTe/2jJ6ECkJHAQPIYEwDoqsiqBjI24mvRmQbInK5jq33fyypaCBxX08hMkfmdOqj6haT33EqWw==", + "dependencies": { + "array-back": "^2.0.0", + "deep-extend": "~0.6.0", + "lodash.padend": "^4.6.1", + "typical": "^2.6.1", + "wordwrapjs": "^3.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/typical": { + "version": "2.6.1", + "resolved": "https://registry.npmmirror.com/typical/-/typical-2.6.1.tgz", + "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==" }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "license": "MIT" + "version": "0.8.6", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-0.8.6.tgz", + "integrity": "sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==" }, "node_modules/unconfig": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.3.2.tgz", - "integrity": "sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==", - "license": "MIT", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.9.tgz", + "integrity": "sha512-8yhetFd48M641mxrkWA+C/lZU4N0rCOdlo3dFsyFPnBHBjMJfjT/3eAZBRT2RxCRqeBMAKBVgikejdS6yeBjMw==", "dependencies": { - "@quansync/fs": "^0.1.1", - "defu": "^6.1.4", - "jiti": "^2.4.2", - "quansync": "^0.2.8" + "@antfu/utils": "^0.7.2", + "defu": "^6.1.2", + "jiti": "^1.18.2" }, "funding": { "url": "https://github.com/sponsors/antfu" } + }, + "node_modules/undici": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.0.tgz", + "integrity": "sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.18" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpack-string": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/unpack-string/-/unpack-string-0.0.2.tgz", + "integrity": "sha512-2ZFjp5aY7QwHE6HAp47RnKYfvgAQ5+NwbKq/ZVtty85RDb3/UaTeCfizo5L/fXzM7UkMP/zDtbV+kGW/iJiK6w==" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.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==" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/wordwrapjs": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/wordwrapjs/-/wordwrapjs-3.0.0.tgz", + "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", + "dependencies": { + "reduce-flatten": "^1.0.1", + "typical": "^2.6.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "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==", + "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/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "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": "7.0.0", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha512-WhzC+xgstid9MbVUktco/bf+KJG+Uu6vMX0LN1sLJvwmbCQVxb4D8LzogobonKycNasCZLdOzTAk1SK7+K7swg==", + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/yargs/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==", + "engines": { + "node": ">=12" + } } } } diff --git a/scripts/sponsors/package.json b/scripts/sponsors/package.json index c9f000b90..70d6dc5ce 100644 --- a/scripts/sponsors/package.json +++ b/scripts/sponsors/package.json @@ -10,9 +10,6 @@ "author": "", "license": "ISC", "dependencies": { - "sponsorkit": "^16.5.0" - }, - "engines": { - "node": ">=22.0.0" + "sponsorkit": "^0.8.2" } } diff --git a/v2/.golangci.yml b/v2/.golangci.yml deleted file mode 100644 index 66b77ba7f..000000000 --- a/v2/.golangci.yml +++ /dev/null @@ -1,162 +0,0 @@ -# Options for analysis runner. -run: - # Custom concurrency value - concurrency: 4 - - # Execution timeout - timeout: 10m - - # Exit code when an issue is found. - issues-exit-code: 1 - - # Inclusion of test files - tests: false - - modules-download-mode: readonly - - allow-parallel-runners: false - - go: '1.21' - - -output: - # Runner output format - format: tab - - # Print line of issue code - print-issued-lines: false - - # Append linter to the output - print-linter-name: true - - # Separate issues by line - uniq-by-line: true - - # Output path prefixing - path-prefix: "" - - # Sort results - sort-results: true - - -# Specific linter configs -linters-settings: - errcheck: - check-type-assertions: false - check-blank: false - ignore: fmt:.* - disable-default-exclusions: false - - gofmt: - simplify: true - - gofumpt: - extra-rules: false - -linters: - fast: false - # Enable all available linters. - enable-all: true - # Disable specific linters - disable: - - asasalint - - asciicheck - - bidichk - - bodyclose - - containedctx - - contextcheck - - cyclop - - deadcode - - decorder - - depguard - - dogsled - - dupl - - dupword - - durationcheck - - errchkjson - - errorlint - - execinquery - - exhaustive - - exhaustivestruct - - exhaustruct - - exportloopref - - forbidigo - - forcetypeassert - - funlen - - gci - - ginkgolinter - - gocheckcompilerdirectives - - gochecknoglobals - - gochecknoinits - - gocognit - - goconst - - gocritic - - gocyclo - - godot - - godox - - goerr113 - - goheader - - goimports - - golint - - gomnd - - gomoddirectives - - gomodguard - - goprintffuncname - - gosec - - gosmopolitan - - govet - - grouper - - ifshort - - importas - - ineffassign - - interfacebloat - - interfacer - - ireturn - - lll - - loggercheck - - maintidx - - makezero - - maligned - - mirror - - musttag - - nakedret - - nestif - - nilerr - - nilnil - - nlreturn - - noctx - - nolintlint - - nonamedreturns - - nosnakecase - - nosprintfhostport - - paralleltest - - prealloc - - predeclared - - promlinter - - reassign - - revive - - rowserrcheck - - scopelint - - sqlclosecheck - - staticcheck - - structcheck - - stylecheck - - tagalign - - tagliatelle - - tenv - - testableexamples - - testpackage - - thelper - - tparallel - - typecheck - - unconvert - - unparam - - unused - - usestdlibvars - - varcheck - - varnamelen - - wastedassign - - whitespace - - wrapcheck - - wsl - - zerologlint \ No newline at end of file diff --git a/v2/README.md b/v2/README.md index c69808f58..66a82d6d3 100644 --- a/v2/README.md +++ b/v2/README.md @@ -62,9 +62,11 @@ ## Introduction -The traditional method of providing web interfaces to Go programs is via a built-in web server. Wails offers a different -approach: it provides the ability to wrap both Go code and a web frontend into a single binary. Tools are provided to -make this easy for you by handling project creation, compilation and bundling. All you have to do is get creative! +The traditional method of providing web interfaces to Go programs is via a +built-in web server. Wails offers a different approach: it provides the ability +to wrap both Go code and a web frontend into a single binary. Tools are provided +to make this easy for you by handling project creation, compilation and +bundling. All you have to do is get creative! ## Features @@ -83,8 +85,9 @@ make this easy for you by handling project creation, compilation and bundling. A ### Roadmap -The project roadmap may be found [here](https://github.com/wailsapp/wails/discussions/1484). Please consult -this before open up an enhancement request. +The project roadmap may be found +[here](https://github.com/wailsapp/wails/discussions/1484). Please consult this +before open up an enhancement request. ## Sponsors @@ -185,26 +188,31 @@ This project is supported by these kind people / companies: ## Getting Started -The installation instructions are on the [official website](https://wails.io/docs/gettingstarted/installation). +The installation instructions are on the +[official website](https://wails.io/docs/gettingstarted/installation). ## FAQ - Is this an alternative to Electron? - Depends on your requirements. It's designed to make it easy for Go programmers to make lightweight desktop - applications or add a frontend to their existing applications. Wails does offer native elements such as menus - and dialogs, so it could be considered a lightweight electron alternative. + Depends on your requirements. It's designed to make it easy for Go programmers + to make lightweight desktop applications or add a frontend to their existing + applications. Wails does offer native elements such as menus and dialogs, so + it could be considered a lightweight electron alternative. - Who is this project aimed at? - Go programmers who want to bundle an HTML/JS/CSS frontend with their applications, without resorting to creating a - server and opening a browser to view it. + Go programmers who want to bundle an HTML/JS/CSS frontend with their + applications, without resorting to creating a server and opening a browser to + view it. - What's with the name? - When I saw WebView, I thought "What I really want is tooling around building a WebView app, a bit like Rails is to - Ruby". So initially it was a play on words (Webview on Rails). It just so happened to also be a homophone of the - English name for the [Country](https://en.wikipedia.org/wiki/Wales) I am from. So it stuck. + When I saw WebView, I thought "What I really want is tooling around building a + WebView app, a bit like Rails is to Ruby". So initially it was a play on words + (Webview on Rails). It just so happened to also be a homophone of the English + name for the [Country](https://en.wikipedia.org/wiki/Wales) I am from. So it + stuck. ## Stargazers over time @@ -212,8 +220,9 @@ The installation instructions are on the [official website](https://wails.io/doc ## Contributors -The contributors list is getting too big for the readme! All the amazing people who have contributed to this -project have their own page [here](https://wails.io/credits#contributors). +The contributors list is getting too big for the readme! All the amazing people +who have contributed to this project have their own page +[here](https://wails.io/credits#contributors). ## License diff --git a/v2/Taskfile.yaml b/v2/Taskfile.yaml index d1893732b..32a32e0f8 100644 --- a/v2/Taskfile.yaml +++ b/v2/Taskfile.yaml @@ -3,18 +3,7 @@ version: "3" tasks: - download: - summary: Run go mod tidy - cmds: - - go mod tidy - - lint: - summary: Run golangci-lint - cmds: - - golangci-lint run ./... --timeout=3m -v - release: - summary: Release a new version of Task. Call with `task v2:release -- ` dir: tools/release cmds: - go run release.go {{.CLI_ARGS}} diff --git a/v2/cmd/wails/build.go b/v2/cmd/wails/build.go index 39ad00d2f..5d284e9a2 100644 --- a/v2/cmd/wails/build.go +++ b/v2/cmd/wails/build.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "github.com/wailsapp/wails/v2/pkg/commands/buildtags" "os" "runtime" "strings" @@ -19,6 +18,7 @@ import ( ) func buildApplication(f *flags.Build) error { + if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -50,23 +50,6 @@ func buildApplication(f *flags.Build) error { return err } - // Set obfuscation from project file - if projectOptions.Obfuscated { - f.Obfuscated = projectOptions.Obfuscated - } - - // Set garble args from project file - if projectOptions.GarbleArgs != "" { - f.GarbleArgs = projectOptions.GarbleArgs - } - - projectTags, err := buildtags.Parse(projectOptions.BuildTags) - if err != nil { - return err - } - userTags := f.GetTags() - compiledTags := append(projectTags, userTags...) - // Create BuildOptions buildOptions := &build.Options{ Logger: logger, @@ -74,7 +57,6 @@ func buildApplication(f *flags.Build) error { OutputFile: f.OutputFilename, CleanBinDirectory: f.Clean, Mode: f.GetBuildMode(), - Devtools: f.Debug || f.Devtools, Pack: !f.NoPackage, LDFlags: f.LdFlags, Compiler: f.Compiler, @@ -84,7 +66,7 @@ func buildApplication(f *flags.Build) error { IgnoreFrontend: f.SkipFrontend, Compress: f.Upx, CompressFlags: f.UpxFlags, - UserTags: compiledTags, + UserTags: f.GetTags(), WebView2Strategy: f.GetWebView2Strategy(), TrimPath: f.TrimPath, RaceDetector: f.RaceDetector, @@ -93,7 +75,6 @@ func buildApplication(f *flags.Build) error { GarbleArgs: f.GarbleArgs, SkipBindings: f.SkipBindings, ProjectData: projectOptions, - SkipEmbedCreate: f.SkipEmbedCreate, } tableData := pterm.TableData{ @@ -101,7 +82,6 @@ func buildApplication(f *flags.Build) error { {"Compiler", f.GetCompilerPath()}, {"Skip Bindings", bool2Str(f.SkipBindings)}, {"Build Mode", f.GetBuildModeAsString()}, - {"Devtools", bool2Str(buildOptions.Devtools)}, {"Frontend Directory", projectOptions.GetFrontendDir()}, {"Obfuscated", bool2Str(f.Obfuscated)}, } @@ -114,7 +94,7 @@ func buildApplication(f *flags.Build) error { {"Package", bool2Str(!f.NoPackage)}, {"Clean Bin Dir", bool2Str(f.Clean)}, {"LDFlags", f.LdFlags}, - {"Tags", "[" + strings.Join(compiledTags, ",") + "]"}, + {"Tags", "[" + strings.Join(f.GetTags(), ",") + "]"}, {"Race Detector", bool2Str(f.RaceDetector)}, }...) if len(buildOptions.OutputFile) > 0 && f.GetTargets().Length() == 1 { @@ -273,4 +253,5 @@ func buildApplication(f *flags.Build) error { } return nil + } diff --git a/v2/cmd/wails/dev.go b/v2/cmd/wails/dev.go index 30213a68e..dbb2cf5d8 100644 --- a/v2/cmd/wails/dev.go +++ b/v2/cmd/wails/dev.go @@ -1,16 +1,16 @@ package main import ( - "os" - "github.com/pterm/pterm" "github.com/wailsapp/wails/v2/cmd/wails/flags" "github.com/wailsapp/wails/v2/cmd/wails/internal/dev" "github.com/wailsapp/wails/v2/internal/colour" "github.com/wailsapp/wails/v2/pkg/clilogger" + "os" ) func devApplication(f *flags.Dev) error { + if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -34,4 +34,5 @@ func devApplication(f *flags.Dev) error { } return dev.Application(f, logger) + } diff --git a/v2/cmd/wails/doctor.go b/v2/cmd/wails/doctor.go index 7f453133d..75d62b246 100644 --- a/v2/cmd/wails/doctor.go +++ b/v2/cmd/wails/doctor.go @@ -1,17 +1,12 @@ package main import ( - "fmt" "runtime" "runtime/debug" - "strconv" "strings" - "github.com/wailsapp/wails/v2/internal/shell" - "github.com/pterm/pterm" - "github.com/jaypipes/ghw" "github.com/wailsapp/wails/v2/cmd/wails/flags" "github.com/wailsapp/wails/v2/internal/colour" "github.com/wailsapp/wails/v2/internal/system" @@ -79,92 +74,11 @@ func diagnoseEnvironment(f *flags.Doctor) error { {pterm.Bold.Sprint("OS"), info.OS.Name}, {pterm.Bold.Sprint("Version"), info.OS.Version}, {pterm.Bold.Sprint("ID"), info.OS.ID}, - {pterm.Bold.Sprint("Branding"), info.OS.Branding}, {pterm.Bold.Sprint("Go Version"), runtime.Version()}, {pterm.Bold.Sprint("Platform"), runtime.GOOS}, {pterm.Bold.Sprint("Architecture"), runtime.GOARCH}, } - // 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 { - cpuInfo := "Unknown" - if runtime.GOOS == "darwin" { - // Try to get CPU info from sysctl - if stdout, _, err := shell.RunCommand("", "sysctl", "-n", "machdep.cpu.brand_string"); err == nil { - cpuInfo = strings.TrimSpace(stdout) - } - } - systemTabledata = append(systemTabledata, []string{"CPU", cpuInfo}) - } - - // Probe GPU - gpu, _ := ghw.GPU(ghw.WithDisableWarnings()) - if gpu != nil { - prefix := "GPU" - for idx, card := range gpu.GraphicsCards { - if len(gpu.GraphicsCards) > 1 { - prefix = "GPU " + strconv.Itoa(idx+1) + " " - } - if card.DeviceInfo == nil { - systemTabledata = append(systemTabledata, []string{prefix, "Unknown"}) - continue - } - 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 { - gpuInfo := "Unknown" - if runtime.GOOS == "darwin" { - // Try to get GPU info from system_profiler - if stdout, _, err := shell.RunCommand("", "system_profiler", "SPDisplaysDataType"); err == nil { - var ( - startCapturing bool - gpuInfoDetails []string - ) - for _, line := range strings.Split(stdout, "\n") { - if strings.Contains(line, "Chipset Model") { - startCapturing = true - } - if startCapturing { - gpuInfoDetails = append(gpuInfoDetails, strings.TrimSpace(line)) - } - if strings.Contains(line, "Metal Support") { - break - } - } - if len(gpuInfoDetails) > 0 { - gpuInfo = strings.Join(gpuInfoDetails, " ") - } - } - } - systemTabledata = append(systemTabledata, []string{"GPU", gpuInfo}) - } - - memory, _ := ghw.Memory() - if memory != nil { - systemTabledata = append(systemTabledata, []string{"Memory", strconv.Itoa(int(memory.TotalPhysicalBytes/1024/1024/1024)) + "GB"}) - } else { - memInfo := "Unknown" - if runtime.GOOS == "darwin" { - // Try to get Memory info from sysctl - if stdout, _, err := shell.RunCommand("", "sysctl", "-n", "hw.memsize"); err == nil { - if memSize, err := strconv.Atoi(strings.TrimSpace(stdout)); err == nil { - memInfo = strconv.Itoa(memSize/1024/1024/1024) + "GB" - } - } - } - systemTabledata = append(systemTabledata, []string{"Memory", memInfo}) - } - err = pterm.DefaultTable.WithBoxed().WithData(systemTabledata).Render() if err != nil { return err @@ -175,8 +89,8 @@ func diagnoseEnvironment(f *flags.Doctor) error { // Output Dependencies Status var dependenciesMissing []string var externalPackages []*packagemanager.Dependency - dependenciesAvailableRequired := 0 - dependenciesAvailableOptional := 0 + var dependenciesAvailableRequired = 0 + var dependenciesAvailableOptional = 0 dependenciesTableData := pterm.TableData{ {"Dependency", "Package Name", "Status", "Version"}, @@ -255,6 +169,7 @@ func diagnoseEnvironment(f *flags.Doctor) error { if len(dependenciesMissing) != 0 { pterm.Println("Fatal:") pterm.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " ")) + pterm.Println("Please read this article on how to resolve this: https://wails.io/guides/resolving-missing-packages") } pterm.Println() // Spacer for sponsor message diff --git a/v2/cmd/wails/flags/build.go b/v2/cmd/wails/flags/build.go index db05c9035..d36ceef6a 100644 --- a/v2/cmd/wails/flags/build.go +++ b/v2/cmd/wails/flags/build.go @@ -24,7 +24,8 @@ type Build struct { Common BuildCommon - NoPackage bool `description:"Skips platform specific packaging"` + NoPackage bool `name:"noPackage" description:"Skips platform specific packaging"` + SkipModTidy bool `name:"m" description:"Skip mod tidy before compile"` Upx bool `description:"Compress final binary with UPX (if installed)"` UpxFlags string `description:"Flags to pass to upx"` Platform string `description:"Platform to target. Comma separate multiple platforms"` @@ -34,7 +35,6 @@ type Build struct { ForceBuild bool `name:"f" description:"Force build of application"` UpdateWailsVersionGoMod bool `name:"u" description:"Updates go.mod to use the same Wails version as the CLI"` Debug bool `description:"Builds the application in debug mode"` - Devtools bool `description:"Enable Devtools in productions, Already enabled in debug mode (-debug)"` NSIS bool `description:"Generate NSIS installer for Windows"` TrimPath bool `description:"Remove all file system paths from the resulting executable"` WindowsConsole bool `description:"Keep the console when building for Windows"` diff --git a/v2/cmd/wails/flags/buildcommon.go b/v2/cmd/wails/flags/buildcommon.go index a22f7a502..dcad33abf 100644 --- a/v2/cmd/wails/flags/buildcommon.go +++ b/v2/cmd/wails/flags/buildcommon.go @@ -1,16 +1,14 @@ package flags type BuildCommon struct { - LdFlags string `description:"Additional ldflags to pass to the compiler"` - Compiler string `description:"Use a different go compiler to build, eg go1.15beta1"` - SkipBindings bool `description:"Skips generation of bindings"` - RaceDetector bool `name:"race" description:"Build with Go's race detector"` - SkipFrontend bool `name:"s" description:"Skips building the frontend"` - Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"` - Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"` - NoSyncGoMod bool `description:"Don't sync go.mod"` - SkipModTidy bool `name:"m" description:"Skip mod tidy before compile"` - SkipEmbedCreate bool `description:"Skips creation of embed files"` + LdFlags string `description:"Additional ldflags to pass to the compiler"` + Compiler string `description:"Use a different go compiler to build, eg go1.15beta1"` + SkipBindings bool `description:"Skips generation of bindings"` + RaceDetector bool `name:"race" description:"Build with Go's race detector"` + SkipFrontend bool `name:"s" description:"Skips building the frontend"` + Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"` + Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"` + NoSyncGoMod bool `description:"Don't sync go.mod"` } func (c BuildCommon) Default() BuildCommon { diff --git a/v2/cmd/wails/flags/dev.go b/v2/cmd/wails/flags/dev.go index d31d8bc87..885e0cead 100644 --- a/v2/cmd/wails/flags/dev.go +++ b/v2/cmd/wails/flags/dev.go @@ -22,7 +22,6 @@ type Dev struct { Browser bool `flag:"browser" description:"Open the application in a browser"` NoReload bool `flag:"noreload" description:"Disable reload on asset change"` NoColour bool `flag:"nocolor" description:"Disable colour in output"` - NoGoRebuild bool `flag:"nogorebuild" description:"Disable automatic rebuilding on backend file changes/additions"` WailsJSDir string `flag:"wailsjsdir" description:"Directory to generate the Wails JS modules"` LogLevel string `flag:"loglevel" description:"LogLevel to use - Trace, Debug, Info, Warning, Error)"` ForceBuild bool `flag:"f" description:"Force build of application"` @@ -31,7 +30,6 @@ type Dev struct { AppArgs string `flag:"appargs" description:"arguments to pass to the underlying app (quoted and space separated)"` Save bool `flag:"save" description:"Save the given flags as defaults"` FrontendDevServerURL string `flag:"frontenddevserverurl" description:"The url of the external frontend dev server to use"` - ViteServerTimeout int `flag:"viteservertimeout" description:"The timeout in seconds for Vite server detection (default: 10)"` // Internal state devServerURL *url.URL @@ -42,13 +40,13 @@ func (*Dev) Default() *Dev { result := &Dev{ Extensions: "go", Debounce: 100, - LogLevel: "Info", } result.BuildCommon = result.BuildCommon.Default() return result } func (d *Dev) Process() error { + var err error err = d.loadAndMergeProjectConfig() if err != nil { @@ -106,13 +104,6 @@ func (d *Dev) loadAndMergeProjectConfig() error { d.AppArgs, _ = lo.Coalesce(d.AppArgs, d.projectConfig.AppArgs) - if d.ViteServerTimeout == 0 && d.projectConfig.ViteServerTimeout != 0 { - d.ViteServerTimeout = d.projectConfig.ViteServerTimeout - } else if d.ViteServerTimeout == 0 { - d.ViteServerTimeout = 10 // Default timeout - } - d.projectConfig.ViteServerTimeout = d.ViteServerTimeout - if d.Save { err = d.projectConfig.Save() if err != nil { @@ -121,28 +112,26 @@ func (d *Dev) loadAndMergeProjectConfig() error { } return nil + } // GenerateBuildOptions creates a build.Options using the flags func (d *Dev) GenerateBuildOptions() *build.Options { result := &build.Options{ - OutputType: "dev", - Mode: build.Dev, - Devtools: true, - Arch: runtime.GOARCH, - Pack: true, - Platform: runtime.GOOS, - LDFlags: d.LdFlags, - Compiler: d.Compiler, - ForceBuild: d.ForceBuild, - IgnoreFrontend: d.SkipFrontend, - SkipBindings: d.SkipBindings, - SkipModTidy: d.SkipModTidy, - Verbosity: d.Verbosity, - WailsJSDir: d.WailsJSDir, - RaceDetector: d.RaceDetector, - ProjectData: d.projectConfig, - SkipEmbedCreate: d.SkipEmbedCreate, + OutputType: "dev", + Mode: build.Dev, + Arch: runtime.GOARCH, + Pack: true, + Platform: runtime.GOOS, + LDFlags: d.LdFlags, + Compiler: d.Compiler, + ForceBuild: d.ForceBuild, + IgnoreFrontend: d.SkipFrontend, + SkipBindings: d.SkipBindings, + Verbosity: d.Verbosity, + WailsJSDir: d.WailsJSDir, + RaceDetector: d.RaceDetector, + ProjectData: d.projectConfig, } return result diff --git a/v2/cmd/wails/flags/generate.go b/v2/cmd/wails/flags/generate.go index b14d67017..9adf78aa4 100644 --- a/v2/cmd/wails/flags/generate.go +++ b/v2/cmd/wails/flags/generate.go @@ -2,7 +2,6 @@ package flags type GenerateModule struct { Common - Compiler string `description:"Use a different go compiler to build, eg go1.15beta1"` Tags string `description:"Build tags to pass to Go compiler. Must be quoted. Space or comma (but not both) separated"` Verbosity int `name:"v" description:"Verbosity level (0 = quiet, 1 = normal, 2 = verbose)"` } @@ -13,9 +12,3 @@ type GenerateTemplate struct { Frontend string `description:"Frontend to use for the template"` Quiet bool `description:"Suppress output"` } - -func (c *GenerateModule) Default() *GenerateModule { - return &GenerateModule{ - Compiler: "go", - } -} diff --git a/v2/cmd/wails/flags/init.go b/v2/cmd/wails/flags/init.go index 16d56a207..6e642ec9a 100644 --- a/v2/cmd/wails/flags/init.go +++ b/v2/cmd/wails/flags/init.go @@ -14,6 +14,7 @@ type Init struct { } func (i *Init) Default() *Init { + result := &Init{ TemplateName: "vanilla", } diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go index 15a6b33d8..a7b059ecf 100644 --- a/v2/cmd/wails/generate.go +++ b/v2/cmd/wails/generate.go @@ -2,9 +2,6 @@ package main import ( "fmt" - "os" - "path/filepath" - "github.com/leaanthony/debme" "github.com/leaanthony/gosod" "github.com/pterm/pterm" @@ -17,9 +14,12 @@ import ( "github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/pkg/commands/bindings" "github.com/wailsapp/wails/v2/pkg/commands/buildtags" + "os" + "path/filepath" ) func generateModule(f *flags.GenerateModule) error { + if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -43,16 +43,10 @@ func generateModule(f *flags.GenerateModule) error { return err } - if projectConfig.Bindings.TsGeneration.OutputType == "" { - projectConfig.Bindings.TsGeneration.OutputType = "classes" - } - _, err = bindings.GenerateBindings(bindings.Options{ - Compiler: f.Compiler, - Tags: buildTags, - TsPrefix: projectConfig.Bindings.TsGeneration.Prefix, - TsSuffix: projectConfig.Bindings.TsGeneration.Suffix, - TsOutputType: projectConfig.Bindings.TsGeneration.OutputType, + Tags: buildTags, + TsPrefix: projectConfig.Bindings.TsGeneration.Prefix, + TsSuffix: projectConfig.Bindings.TsGeneration.Suffix, }) if err != nil { return err @@ -61,6 +55,7 @@ func generateModule(f *flags.GenerateModule) error { } func generateTemplate(f *flags.GenerateTemplate) error { + if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -82,7 +77,7 @@ func generateTemplate(f *flags.GenerateTemplate) error { } templateDir := filepath.Join(cwd, f.Name) if !fs.DirExists(templateDir) { - err := os.MkdirAll(templateDir, 0o755) + err := os.MkdirAll(templateDir, 0755) if err != nil { return err } @@ -205,7 +200,7 @@ func processPackageJSON(frontendDir string) error { json, _ = sjson.SetBytes(json, "name", "{{.ProjectName}}") json, _ = sjson.SetBytes(json, "author", "{{.AuthorName}}") - err = os.WriteFile(packageJSON, json, 0o644) + err = os.WriteFile(packageJSON, json, 0644) if err != nil { return err } @@ -236,7 +231,7 @@ func processPackageLockJSON(frontendDir string) error { printBulletPoint("Updating package-lock.json data...") json, _ = sjson.Set(json, "name", "{{.ProjectName}}") - err = os.WriteFile(filename, []byte(json), 0o644) + err = os.WriteFile(filename, []byte(json), 0644) if err != nil { return err } diff --git a/v2/cmd/wails/init.go b/v2/cmd/wails/init.go index f79e37ffc..f9a9c6b3f 100644 --- a/v2/cmd/wails/init.go +++ b/v2/cmd/wails/init.go @@ -3,12 +3,6 @@ package main import ( "bufio" "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - "github.com/flytam/filenamify" "github.com/leaanthony/slicer" "github.com/pkg/errors" @@ -19,9 +13,15 @@ import ( "github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/pkg/git" "github.com/wailsapp/wails/v2/pkg/templates" + "os" + "os/exec" + "path/filepath" + "strings" + "time" ) func initProject(f *flags.Init) error { + if f.NoColour { pterm.DisableColor() colour.ColourEnabled = false @@ -125,12 +125,6 @@ func initProject(f *flags.Init) error { return err } - // Change the module name to project name - err = updateModuleNameToProjectName(options, quiet) - if err != nil { - return err - } - if !f.CIMode { // Run `go mod tidy` to ensure `go.sum` is up to date cmd := exec.Command("go", "mod", "tidy") @@ -221,7 +215,7 @@ func initGit(options *templates.Options) error { "frontend/dist", "frontend/node_modules", } - err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0o644) + err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0644) if err != nil { return errors.Wrap(err, "Unable to create gitignore") } @@ -277,19 +271,8 @@ func updateReplaceLine(targetPath string) { } } - err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0o644) + err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0644) if err != nil { fatal(err.Error()) } } - -func updateModuleNameToProjectName(options *templates.Options, quiet bool) error { - cmd := exec.Command("go", "mod", "edit", "-module", options.ProjectName) - cmd.Dir = options.TargetDir - cmd.Stderr = os.Stderr - if !quiet { - cmd.Stdout = os.Stdout - } - - return cmd.Run() -} diff --git a/v2/cmd/wails/internal/dev/dev.go b/v2/cmd/wails/internal/dev/dev.go index 9495b5bf2..8d11edbf7 100644 --- a/v2/cmd/wails/internal/dev/dev.go +++ b/v2/cmd/wails/internal/dev/dev.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "net/url" "os" @@ -52,6 +51,7 @@ func sliceToMap(input []string) map[string]struct{} { // Application runs the application in dev mode func Application(f *flags.Dev, logger *clilogger.CLILogger) error { + cwd := lo.Must(os.Getwd()) // Update go.mod to use current wails version @@ -60,12 +60,10 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error { return err } - if !f.SkipModTidy { - // Run go mod tidy to ensure we're up-to-date - err = runCommand(cwd, false, f.Compiler, "mod", "tidy") - if err != nil { - return err - } + // Run go mod tidy to ensure we're up-to-date + err = runCommand(cwd, false, "go", "mod", "tidy") + if err != nil { + return err } buildOptions := f.GenerateBuildOptions() @@ -76,18 +74,13 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error { return err } - projectConfig := f.ProjectConfig() + buildOptions.UserTags = userTags - projectTags, err := buildtags.Parse(projectConfig.BuildTags) - if err != nil { - return err - } - compiledTags := append(projectTags, userTags...) - buildOptions.UserTags = compiledTags + projectConfig := f.ProjectConfig() // Setup signal handler quitChannel := make(chan os.Signal, 1) - signal.Notify(quitChannel, os.Interrupt, syscall.SIGTERM) + signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM) exitCodeChannel := make(chan int, 1) // Build the frontend if requested, but ignore building the application itself. @@ -104,7 +97,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error { // frontend:dev:watcher command. frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery() if command := projectConfig.DevWatcherCommand; command != "" { - closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery, projectConfig.ViteServerTimeout) + closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery) if err != nil { return err } @@ -145,6 +138,20 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error { } } + // create the project files watcher + watcher, err := initialiseWatcher(cwd) + if err != nil { + return err + } + + defer func(watcher *fsnotify.Watcher) { + err := watcher.Close() + if err != nil { + logger.Fatal(err.Error()) + } + }(watcher) + + logutils.LogGreen("Watching (sub)/directory: %s", cwd) logutils.LogGreen("Using DevServer URL: %s", f.DevServerURL()) if f.FrontendDevServerURL != "" { logutils.LogGreen("Using Frontend DevServer URL: %s", f.FrontendDevServerURL) @@ -158,10 +165,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error { }() // Watch for changes and trigger restartApp() - debugBinaryProcess, err = doWatcherLoop(cwd, projectConfig.ReloadDirectories, buildOptions, debugBinaryProcess, f, exitCodeChannel, quitChannel, f.DevServerURL(), legacyUseDevServerInsteadofCustomScheme) - if err != nil { - return err - } + debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL(), legacyUseDevServerInsteadofCustomScheme) // Kill the current program if running and remove dev binary if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil { @@ -210,7 +214,7 @@ func runCommand(dir string, exitOnError bool, command string, args ...string) er } // runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev` -func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool, viteServerTimeout int) (func(), string, string, error) { +func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, string, error) { ctx, cancel := context.WithCancel(context.Background()) scanner := NewStdoutScanner() cmdSlice := strings.Split(devCommand, " ") @@ -230,9 +234,9 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d select { case serverURL := <-scanner.ViteServerURLChan: viteServerURL = serverURL - case <-time.After(time.Second * time.Duration(viteServerTimeout)): + case <-time.After(time.Second * 10): cancel() - return nil, "", "", fmt.Errorf("failed to find Vite server URL: Timed out waiting for Vite to output a URL after %d seconds", viteServerTimeout) + return nil, "", "", errors.New("failed to find Vite server URL") } } @@ -251,8 +255,8 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d const ( stateRunning int32 = 0 - stateCanceling int32 = 1 - stateStopped int32 = 2 + stateCanceling = 1 + stateStopped = 2 ) state := stateRunning go func() { @@ -277,6 +281,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d // restartApp does the actual rebuilding of the application when files change func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, string, error) { + appBinary, err := build.Build(buildOptions) println() if err != nil { @@ -303,6 +308,7 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process // parse appargs if any args, err := shlex.Split(f.AppArgs) + if err != nil { buildOptions.Logger.Fatal("Unable to parse appargs: %s", err.Error()) } @@ -331,25 +337,9 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process } // doWatcherLoop is the main watch loop that runs while dev is active -func doWatcherLoop(cwd string, reloadDirs string, buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, error) { - // create the project files watcher - watcher, err := initialiseWatcher(cwd, reloadDirs) - if err != nil { - logutils.LogRed("Unable to create filesystem watcher. Reloads will not occur.") - return nil, err - } - - defer func(watcher *fsnotify.Watcher) { - err := watcher.Close() - if err != nil { - log.Fatal(err.Error()) - } - }(watcher) - - logutils.LogGreen("Watching (sub)/directory: %s", cwd) - +func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL, legacyUseDevServerInsteadofCustomScheme bool) *process.Process { // Main Loop - extensionsThatTriggerARebuild := sliceToMap(strings.Split(f.Extensions, ",")) + var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ",")) var dirsThatTriggerAReload []string for _, dir := range strings.Split(f.ReloadDirs, ",") { if dir == "" { @@ -361,12 +351,6 @@ func doWatcherLoop(cwd string, reloadDirs string, buildOptions *build.Options, d continue } dirsThatTriggerAReload = append(dirsThatTriggerAReload, thePath) - err = watcher.Add(thePath) - if err != nil { - logutils.LogRed("Unable to watch path: %s due to error %v", thePath, err) - } else { - logutils.LogGreen("Watching (sub)/directory: %s", thePath) - } } quit := false @@ -382,7 +366,7 @@ func doWatcherLoop(cwd string, reloadDirs string, buildOptions *build.Options, d assetDirURL := joinPath(devServerURL, "/wails/assetdir") reloadURL := joinPath(devServerURL, "/wails/reload") - for !quit { + for quit == false { // reload := false select { case exitCode := <-exitCodeChannel: @@ -457,21 +441,16 @@ func doWatcherLoop(cwd string, reloadDirs string, buildOptions *build.Options, d case <-timer.C: if rebuild { rebuild = false - if f.NoGoRebuild { - logutils.LogGreen("[Rebuild triggered] skipping due to flag -nogorebuild") - } else { - logutils.LogGreen("[Rebuild triggered] files updated") - // Try and build the app - - newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme) - if err != nil { - logutils.LogRed("Error during build: %s", err.Error()) - continue - } - // If we have a new process, saveConfig it - if newBinaryProcess != nil { - debugBinaryProcess = newBinaryProcess - } + logutils.LogGreen("[Rebuild triggered] files updated") + // Try and build the app + newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme) + if err != nil { + logutils.LogRed("Error during build: %s", err.Error()) + continue + } + // If we have a new process, saveConfig it + if newBinaryProcess != nil { + debugBinaryProcess = newBinaryProcess } } @@ -515,7 +494,7 @@ func doWatcherLoop(cwd string, reloadDirs string, buildOptions *build.Options, d quit = true } } - return debugBinaryProcess, nil + return debugBinaryProcess } func joinPath(url *url.URL, subPath string) string { diff --git a/v2/cmd/wails/internal/dev/watcher.go b/v2/cmd/wails/internal/dev/watcher.go index e1161f87c..19caf0df8 100644 --- a/v2/cmd/wails/internal/dev/watcher.go +++ b/v2/cmd/wails/internal/dev/watcher.go @@ -4,7 +4,6 @@ import ( "bufio" "os" "path/filepath" - "strings" "github.com/wailsapp/wails/v2/internal/fs" @@ -18,7 +17,8 @@ type Watcher interface { } // initialiseWatcher creates the project directory watcher that will trigger recompile -func initialiseWatcher(cwd, reloadDirs string) (*fsnotify.Watcher, error) { +func initialiseWatcher(cwd string) (*fsnotify.Watcher, error) { + // Ignore dot files, node_modules and build directories by default ignoreDirs := getIgnoreDirs(cwd) @@ -28,42 +28,31 @@ func initialiseWatcher(cwd, reloadDirs string) (*fsnotify.Watcher, error) { return nil, err } - customDirs := dirs.AsSlice() - seperatedDirs := strings.Split(reloadDirs, ",") - for _, dir := range seperatedDirs { - customSub, err := fs.GetSubdirectories(filepath.Join(cwd, dir)) - if err != nil { - return nil, err - } - customDirs = append(customDirs, customSub.AsSlice()...) - } - watcher, err := fsnotify.NewWatcher() if err != nil { return nil, err } - for _, dir := range processDirectories(customDirs, ignoreDirs) { + for _, dir := range processDirectories(dirs.AsSlice(), ignoreDirs) { err := watcher.Add(dir) if err != nil { return nil, err } + println("watching: " + dir) } return watcher, nil } func getIgnoreDirs(cwd string) []string { ignoreDirs := []string{filepath.Join(cwd, "build/*"), ".*", "node_modules"} - baseDir := filepath.Base(cwd) + // Read .gitignore into ignoreDirs f, err := os.Open(filepath.Join(cwd, ".gitignore")) if err == nil { scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() - if line != baseDir { - ignoreDirs = append(ignoreDirs, line) - } + ignoreDirs = append(ignoreDirs, line) } } diff --git a/v2/cmd/wails/internal/gomod/gomod.go b/v2/cmd/wails/internal/gomod/gomod.go index 5da14a5ff..52e56344b 100644 --- a/v2/cmd/wails/internal/gomod/gomod.go +++ b/v2/cmd/wails/internal/gomod/gomod.go @@ -56,7 +56,7 @@ func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error { } if updated { - return os.WriteFile(gomodFilename, gomodData, 0o755) + return os.WriteFile(gomodFilename, gomodData, 0755) } return nil diff --git a/v2/cmd/wails/internal/template/base/README.md b/v2/cmd/wails/internal/template/base/README.md index ed259fcff..75ff857fd 100644 --- a/v2/cmd/wails/internal/template/base/README.md +++ b/v2/cmd/wails/internal/template/base/README.md @@ -6,8 +6,9 @@ About your template ## Live Development -To run in live development mode, run `wails dev` in the project directory. In another terminal, go into the `frontend` -directory and run `npm run dev`. The frontend dev server will run on http://localhost:34115. Connect to this in your +To run in live development mode, run `wails dev` in the project directory. In +another terminal, go into the `frontend` directory and run `npm run dev`. The +frontend dev server will run on http://localhost:34115. Connect to this in your browser and connect to your application. ## Building diff --git a/v2/cmd/wails/internal/version.txt b/v2/cmd/wails/internal/version.txt index 805579f30..8c99f59fb 100644 --- a/v2/cmd/wails/internal/version.txt +++ b/v2/cmd/wails/internal/version.txt @@ -1 +1 @@ -v2.11.0 \ No newline at end of file +v2.5.1 \ No newline at end of file diff --git a/v2/cmd/wails/main.go b/v2/cmd/wails/main.go index ccf1576e9..a0a7d4a31 100644 --- a/v2/cmd/wails/main.go +++ b/v2/cmd/wails/main.go @@ -66,6 +66,7 @@ func bool2Str(b bool) string { var app *clir.Cli func main() { + var err error app = clir.NewCli("Wails", "Go/HTML Appkit", internal.Version) diff --git a/v2/cmd/wails/update.go b/v2/cmd/wails/update.go index 9f8b6e604..ac0e7375a 100644 --- a/v2/cmd/wails/update.go +++ b/v2/cmd/wails/update.go @@ -15,6 +15,7 @@ import ( // AddSubcommand adds the `init` command for the Wails application func update(f *flags.Update) error { + if f.NoColour { colour.ColourEnabled = false pterm.DisableColor() @@ -72,7 +73,8 @@ func update(f *flags.Update) error { } func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentVersion string) error { - targetVersionString := "v" + targetVersion.String() + + var targetVersionString = "v" + targetVersion.String() if targetVersionString == currentVersion { pterm.Println("\nLooks like you're up to date!") diff --git a/v2/examples/customlayout/README.md b/v2/examples/customlayout/README.md index e4d79d4ec..b2f4fa311 100644 --- a/v2/examples/customlayout/README.md +++ b/v2/examples/customlayout/README.md @@ -1,4 +1,4 @@ # README -This is an example project that shows how to use a custom layout. -Run `wails build` in the `cmd/customlayout` directory to build the project. \ No newline at end of file +This is an example project that shows how to use a custom layout. Run +`wails build` in the `cmd/customlayout` directory to build the project. diff --git a/v2/examples/customlayout/build/README.md b/v2/examples/customlayout/build/README.md index 3018a06c4..fc547b771 100644 --- a/v2/examples/customlayout/build/README.md +++ b/v2/examples/customlayout/build/README.md @@ -1,35 +1,41 @@ # Build Directory -The build directory is used to house all the build files and assets for your application. +The build directory is used to house all the build files and assets for your +application. The structure is: -* bin - Output directory -* darwin - macOS specific files -* windows - Windows specific files +- bin - Output directory +- darwin - macOS specific files +- windows - Windows specific files ## Mac -The `darwin` directory holds files specific to Mac builds. -These may be customised and used as part of the build. To return these files to the default state, simply delete them -and -build with `wails build`. +The `darwin` directory holds files specific to Mac builds. These may be +customised and used as part of the build. To return these files to the default +state, simply delete them and build with `wails build`. The directory contains the following files: -- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. -- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. +- `Info.plist` - the main plist file used for Mac builds. It is used when + building using `wails build`. +- `Info.dev.plist` - same as the main plist file but used when building using + `wails dev`. ## Windows -The `windows` directory contains the manifest and rc files used when building with `wails build`. -These may be customised for your application. To return these files to the default state, simply delete them and -build with `wails build`. +The `windows` directory contains the manifest and rc files used when building +with `wails build`. These may be customised for your application. To return +these files to the default state, simply delete them and build with +`wails build`. -- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to - use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file - will be created using the `appicon.png` file in the build directory. -- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. -- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, - as well as the application itself (right click the exe -> properties -> details) -- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file +- `icon.ico` - The icon used for the application. This is used when building + using `wails build`. If you wish to use a different icon, simply replace this + file with your own. If it is missing, a new `icon.ico` file will be created + using the `appicon.png` file in the build directory. +- `installer/*` - The files used to create the Windows installer. These are used + when building using `wails build`. +- `info.json` - Application details used for Windows builds. The data here will + be used by the Windows installer, as well as the application itself (right + click the exe -> properties -> details) +- `wails.exe.manifest` - The main application manifest file. diff --git a/v2/examples/customlayout/go.mod b/v2/examples/customlayout/go.mod index e1a17304e..a25b94383 100644 --- a/v2/examples/customlayout/go.mod +++ b/v2/examples/customlayout/go.mod @@ -1,39 +1,33 @@ module changeme -go 1.22.0 - -toolchain go1.24.1 +go 1.18 require github.com/wailsapp/wails/v2 v2.1.0 require ( github.com/bep/debounce v1.2.1 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.1.2 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect - github.com/labstack/echo/v4 v4.13.3 // indirect - github.com/labstack/gommon v0.4.2 // indirect - github.com/leaanthony/go-ansi-parser v1.6.1 // indirect - github.com/leaanthony/gosod v1.0.4 // indirect - github.com/leaanthony/slicer v1.6.0 // indirect - github.com/leaanthony/u v1.1.1 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/labstack/echo/v4 v4.9.0 // indirect + github.com/labstack/gommon v0.3.1 // indirect + github.com/leaanthony/go-ansi-parser v1.0.1 // indirect + github.com/leaanthony/gosod v1.0.3 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // 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/tkrajina/go-reflector v0.5.8 // indirect + github.com/samber/lo v1.27.1 // indirect + github.com/tkrajina/go-reflector v0.5.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/wailsapp/go-webview2 v1.0.22 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // 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 + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.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 f1995affb..de073ade8 100644 --- a/v2/examples/customlayout/go.sum +++ b/v2/examples/customlayout/go.sum @@ -5,85 +5,55 @@ 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/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= -github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= +github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 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.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg= -github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= -github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4= +github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM= github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= -github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= -github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= -github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI= -github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= -github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20/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/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= 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.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +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/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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= -github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= -github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ= +github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.10 h1:PP5Hug6pnQEAhfRzLCoOh2jJaPdrqeRgJKZhyYyDV/w= -github.com/wailsapp/go-webview2 v1.0.10/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= -github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= -github.com/wailsapp/go-webview2 v1.0.22/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= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -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/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +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/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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= @@ -92,20 +62,14 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= 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= diff --git a/v2/examples/dragdrop-test/.gitignore b/v2/examples/dragdrop-test/.gitignore deleted file mode 100644 index a11bbf414..000000000 --- a/v2/examples/dragdrop-test/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -build/bin -node_modules -frontend/dist -frontend/wailsjs diff --git a/v2/examples/dragdrop-test/README.md b/v2/examples/dragdrop-test/README.md deleted file mode 100644 index 397b08b92..000000000 --- a/v2/examples/dragdrop-test/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# README - -## About - -This is the official Wails Vanilla template. - -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config - -## Live Development - -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. - -## Building - -To build a redistributable, production mode package, use `wails build`. diff --git a/v2/examples/dragdrop-test/app.go b/v2/examples/dragdrop-test/app.go deleted file mode 100644 index af53038a1..000000000 --- a/v2/examples/dragdrop-test/app.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "context" - "fmt" -) - -// App struct -type App struct { - ctx context.Context -} - -// NewApp creates a new App application struct -func NewApp() *App { - return &App{} -} - -// startup is called when the app starts. The context is saved -// so we can call the runtime methods -func (a *App) startup(ctx context.Context) { - a.ctx = ctx -} - -// Greet returns a greeting for the given name -func (a *App) Greet(name string) string { - return fmt.Sprintf("Hello %s, It's show time!", name) -} diff --git a/v2/examples/dragdrop-test/build/README.md b/v2/examples/dragdrop-test/build/README.md deleted file mode 100644 index 1ae2f677f..000000000 --- a/v2/examples/dragdrop-test/build/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Build Directory - -The build directory is used to house all the build files and assets for your application. - -The structure is: - -* bin - Output directory -* darwin - macOS specific files -* windows - Windows specific files - -## Mac - -The `darwin` directory holds files specific to Mac builds. -These may be customised and used as part of the build. To return these files to the default state, simply delete them -and -build with `wails build`. - -The directory contains the following files: - -- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. -- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. - -## Windows - -The `windows` directory contains the manifest and rc files used when building with `wails build`. -These may be customised for your application. To return these files to the default state, simply delete them and -build with `wails build`. - -- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to - use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file - will be created using the `appicon.png` file in the build directory. -- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. -- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, - as well as the application itself (right click the exe -> properties -> details) -- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file diff --git a/v2/examples/dragdrop-test/build/darwin/Info.dev.plist b/v2/examples/dragdrop-test/build/darwin/Info.dev.plist deleted file mode 100644 index 14121ef7c..000000000 --- a/v2/examples/dragdrop-test/build/darwin/Info.dev.plist +++ /dev/null @@ -1,68 +0,0 @@ - - - - CFBundlePackageType - APPL - CFBundleName - {{.Info.ProductName}} - CFBundleExecutable - {{.OutputFilename}} - CFBundleIdentifier - com.wails.{{.Name}} - CFBundleVersion - {{.Info.ProductVersion}} - CFBundleGetInfoString - {{.Info.Comments}} - CFBundleShortVersionString - {{.Info.ProductVersion}} - CFBundleIconFile - iconfile - LSMinimumSystemVersion - 10.13.0 - NSHighResolutionCapable - true - NSHumanReadableCopyright - {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} - NSAppTransportSecurity - - NSAllowsLocalNetworking - - - - diff --git a/v2/examples/dragdrop-test/build/darwin/Info.plist b/v2/examples/dragdrop-test/build/darwin/Info.plist deleted file mode 100644 index d17a7475c..000000000 --- a/v2/examples/dragdrop-test/build/darwin/Info.plist +++ /dev/null @@ -1,63 +0,0 @@ - - - - CFBundlePackageType - APPL - CFBundleName - {{.Info.ProductName}} - CFBundleExecutable - {{.OutputFilename}} - CFBundleIdentifier - com.wails.{{.Name}} - CFBundleVersion - {{.Info.ProductVersion}} - CFBundleGetInfoString - {{.Info.Comments}} - CFBundleShortVersionString - {{.Info.ProductVersion}} - CFBundleIconFile - iconfile - LSMinimumSystemVersion - 10.13.0 - NSHighResolutionCapable - true - NSHumanReadableCopyright - {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} - - diff --git a/v2/examples/dragdrop-test/build/windows/info.json b/v2/examples/dragdrop-test/build/windows/info.json deleted file mode 100644 index 9727946b7..000000000 --- a/v2/examples/dragdrop-test/build/windows/info.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "fixed": { - "file_version": "{{.Info.ProductVersion}}" - }, - "info": { - "0000": { - "ProductVersion": "{{.Info.ProductVersion}}", - "CompanyName": "{{.Info.CompanyName}}", - "FileDescription": "{{.Info.ProductName}}", - "LegalCopyright": "{{.Info.Copyright}}", - "ProductName": "{{.Info.ProductName}}", - "Comments": "{{.Info.Comments}}" - } - } -} \ No newline at end of file diff --git a/v2/examples/dragdrop-test/build/windows/installer/project.nsi b/v2/examples/dragdrop-test/build/windows/installer/project.nsi deleted file mode 100644 index 654ae2e49..000000000 --- a/v2/examples/dragdrop-test/build/windows/installer/project.nsi +++ /dev/null @@ -1,114 +0,0 @@ -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 with the values from ProjectInfo. -## If they are defined here, "wails_tools.nsh" will not touch them. This allows 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 ProjectInfo file, but they can be overwritten here. -#### -## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" -## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" -## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" -## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" -## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" -### -## !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 # Uinstalling page - -!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer - -## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 -#!uninstfinalize 'signtool --file "%1"' -#!finalize 'signtool --file "%1"' - -Name "${INFO_PRODUCTNAME}" -OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. -InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). -ShowInstDetails show # This will always show the installation details. - -Function .onInit - !insertmacro wails.checkArchitecture -FunctionEnd - -Section - !insertmacro wails.setShellContext - - !insertmacro wails.webview2runtime - - SetOutPath $INSTDIR - - !insertmacro wails.files - - CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" - CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" - - !insertmacro wails.associateFiles - !insertmacro wails.associateCustomProtocols - - !insertmacro wails.writeUninstaller -SectionEnd - -Section "uninstall" - !insertmacro wails.setShellContext - - RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath - - RMDir /r $INSTDIR - - Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" - Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" - - !insertmacro wails.unassociateFiles - !insertmacro wails.unassociateCustomProtocols - - !insertmacro wails.deleteUninstaller -SectionEnd diff --git a/v2/examples/dragdrop-test/build/windows/installer/wails_tools.nsh b/v2/examples/dragdrop-test/build/windows/installer/wails_tools.nsh deleted file mode 100644 index f9c0f8852..000000000 --- a/v2/examples/dragdrop-test/build/windows/installer/wails_tools.nsh +++ /dev/null @@ -1,249 +0,0 @@ -# DO NOT EDIT - Generated automatically by `wails build` - -!include "x64.nsh" -!include "WinVer.nsh" -!include "FileFunc.nsh" - -!ifndef INFO_PROJECTNAME - !define INFO_PROJECTNAME "{{.Name}}" -!endif -!ifndef INFO_COMPANYNAME - !define INFO_COMPANYNAME "{{.Info.CompanyName}}" -!endif -!ifndef INFO_PRODUCTNAME - !define INFO_PRODUCTNAME "{{.Info.ProductName}}" -!endif -!ifndef INFO_PRODUCTVERSION - !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" -!endif -!ifndef INFO_COPYRIGHT - !define INFO_COPYRIGHT "{{.Info.Copyright}}" -!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 "tmp\MicrosoftEdgeWebview2Setup.exe" - ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' - - SetDetailsPrint both - ok: -!macroend - -# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b -!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" - - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" - - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` -!macroend - -!macro APP_UNASSOCIATE EXT FILECLASS - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" - - DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` -!macroend - -!macro wails.associateFiles - ; Create file associations - {{range .Info.FileAssociations}} - !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - File "..\{{.IconName}}.ico" - {{end}} -!macroend - -!macro wails.unassociateFiles - ; Delete app associations - {{range .Info.FileAssociations}} - !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" - - Delete "$INSTDIR\{{.IconName}}.ico" - {{end}} -!macroend - -!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" -!macroend - -!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" -!macroend - -!macro wails.associateCustomProtocols - ; Create custom protocols associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - {{end}} -!macroend - -!macro wails.unassociateCustomProtocols - ; Delete app custom protocol associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" - {{end}} -!macroend diff --git a/v2/examples/dragdrop-test/build/windows/wails.exe.manifest b/v2/examples/dragdrop-test/build/windows/wails.exe.manifest deleted file mode 100644 index 17e1a2387..000000000 --- a/v2/examples/dragdrop-test/build/windows/wails.exe.manifest +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - true/pm - permonitorv2,permonitor - - - \ No newline at end of file diff --git a/v2/examples/dragdrop-test/frontend/index.html b/v2/examples/dragdrop-test/frontend/index.html deleted file mode 100644 index 4010f1be6..000000000 --- a/v2/examples/dragdrop-test/frontend/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - dragdrop-test - - -
- - - diff --git a/v2/examples/dragdrop-test/frontend/package-lock.json b/v2/examples/dragdrop-test/frontend/package-lock.json deleted file mode 100644 index 8eed5313c..000000000 --- a/v2/examples/dragdrop-test/frontend/package-lock.json +++ /dev/null @@ -1,653 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "frontend", - "version": "0.0.0", - "devDependencies": { - "vite": "^3.0.7" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-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/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/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "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/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/vite": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", - "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.15.9", - "postcss": "^8.4.18", - "resolve": "^1.22.1", - "rollup": "^2.79.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - } - } -} diff --git a/v2/examples/dragdrop-test/frontend/package.json b/v2/examples/dragdrop-test/frontend/package.json deleted file mode 100644 index a1b6f8e1a..000000000 --- a/v2/examples/dragdrop-test/frontend/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "frontend", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "devDependencies": { - "vite": "^3.0.7" - } -} \ No newline at end of file diff --git a/v2/examples/dragdrop-test/frontend/src/app.css b/v2/examples/dragdrop-test/frontend/src/app.css deleted file mode 100644 index 1d3b595bc..000000000 --- a/v2/examples/dragdrop-test/frontend/src/app.css +++ /dev/null @@ -1,229 +0,0 @@ -/* #app styles are in style.css to avoid conflicts */ - -.compact-container { - display: flex; - gap: 15px; - margin: 15px 0; - justify-content: center; - align-items: flex-start; -} - -.drag-source { - background: white; - border: 2px solid #5c6bc0; - padding: 12px; - min-width: 140px; - border-radius: 6px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.drag-source h4 { - color: #3949ab; - margin: 0 0 8px 0; - font-size: 14px; -} - -.draggable { - background: #f5f5f5; - color: #1a1a1a; - padding: 8px; - margin: 6px 0; - border-radius: 4px; - cursor: move; - text-align: center; - transition: all 0.3s ease; - font-weight: 600; - font-size: 14px; - border: 2px solid #c5cae9; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.draggable:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - background: #e8eaf6; - border-color: #7986cb; -} - -.draggable.dragging { - opacity: 0.5; - transform: scale(0.95); - background: #c5cae9; -} - -.drop-zone { - background: #f8f9fa; - border: 2px dashed #9e9e9e; - padding: 12px; - min-width: 180px; - min-height: 120px; - border-radius: 6px; - transition: all 0.3s ease; -} - -.drop-zone h4 { - color: #5c6bc0; - margin: 0 0 8px 0; - font-size: 14px; -} - -.drop-zone.drag-over { - background: #e3f2fd; - border-color: #2196F3; - transform: scale(1.02); - box-shadow: 0 4px 12px rgba(33, 150, 243, 0.2); -} - -.file-drop-zone { - background: #fff8e1; - border: 2px dashed #ffc107; - padding: 12px; - min-width: 180px; - min-height: 120px; - border-radius: 6px; - transition: all 0.3s ease; -} - -.file-drop-zone h4 { - color: #f57c00; - margin: 0 0 8px 0; - font-size: 14px; -} - -.file-drop-zone.drag-over { - background: #fff3cd; - border-color: #ff9800; - transform: scale(1.02); - box-shadow: 0 4px 12px rgba(255, 152, 0, 0.2); -} - -.dropped-item { - background: linear-gradient(135deg, #42a5f5 0%, #66bb6a 100%); - color: white; - padding: 6px 8px; - margin: 4px 2px; - border-radius: 4px; - text-align: center; - animation: slideIn 0.3s ease; - display: inline-block; - font-weight: 500; - font-size: 13px; -} - -.dropped-file { - background: #fff; - border: 2px solid #ff9800; - color: #333; - padding: 6px 8px; - margin: 4px 0; - border-radius: 4px; - text-align: left; - animation: slideIn 0.3s ease; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - font-size: 13px; -} - -#dropMessage, #fileDropMessage { - font-size: 12px; - color: #666; - margin: 4px 0; -} - -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.status { - margin: 15px auto; - max-width: 1000px; - padding: 12px; - background: #2c3e50; - border-radius: 6px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.status h4 { - color: white; - margin: 0 0 8px 0; - font-size: 14px; -} - -#eventLog { - background: #1a1a1a; - padding: 10px; - border-radius: 4px; - max-height: 200px; - overflow-y: auto; - font-family: 'Courier New', monospace; - text-align: left; - font-size: 12px; -} - -.log-entry { - padding: 4px 8px; - font-size: 13px; - margin: 2px 0; - border-radius: 3px; -} - -.log-entry.drag-start { - color: #81c784; - background: rgba(129, 199, 132, 0.1); -} - -.log-entry.drag-over { - color: #64b5f6; - background: rgba(100, 181, 246, 0.1); -} - -.log-entry.drag-enter { - color: #ffb74d; - background: rgba(255, 183, 77, 0.1); -} - -.log-entry.drag-leave { - color: #ba68c8; - background: rgba(186, 104, 200, 0.1); -} - -.log-entry.drop { - color: #e57373; - background: rgba(229, 115, 115, 0.1); - font-weight: bold; -} - -.log-entry.drag-end { - color: #90a4ae; - background: rgba(144, 164, 174, 0.1); -} - -.log-entry.file-drop { - color: #ffc107; - background: rgba(255, 193, 7, 0.1); - font-weight: bold; -} - -.log-entry.page-loaded { - color: #4caf50; - background: rgba(76, 175, 80, 0.1); -} - -.log-entry.wails-status { - color: #00bcd4; - background: rgba(0, 188, 212, 0.1); -} - -h1 { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - font-size: 1.8em; - margin: 10px 0 8px 0; -} \ No newline at end of file diff --git a/v2/examples/dragdrop-test/frontend/src/assets/fonts/OFL.txt b/v2/examples/dragdrop-test/frontend/src/assets/fonts/OFL.txt deleted file mode 100644 index 9cac04ce8..000000000 --- a/v2/examples/dragdrop-test/frontend/src/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), - -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/v2/examples/dragdrop-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/examples/dragdrop-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 deleted file mode 100644 index 2f9cc5964..000000000 Binary files a/v2/examples/dragdrop-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ diff --git a/v2/examples/dragdrop-test/frontend/src/assets/images/logo-universal.png b/v2/examples/dragdrop-test/frontend/src/assets/images/logo-universal.png deleted file mode 100644 index d63303bfa..000000000 Binary files a/v2/examples/dragdrop-test/frontend/src/assets/images/logo-universal.png and /dev/null differ diff --git a/v2/examples/dragdrop-test/frontend/src/main.js b/v2/examples/dragdrop-test/frontend/src/main.js deleted file mode 100644 index 60d76ac0f..000000000 --- a/v2/examples/dragdrop-test/frontend/src/main.js +++ /dev/null @@ -1,231 +0,0 @@ -import './style.css'; -import './app.css'; - -// CRITICAL: Register global handlers IMMEDIATELY to prevent file drops from opening new windows -// This must be done as early as possible, before any other code runs -(function() { - // Helper function to check if drag event contains files - function isFileDrop(e) { - return e.dataTransfer && e.dataTransfer.types && - (e.dataTransfer.types.includes('Files') || - Array.from(e.dataTransfer.types).includes('Files')); - } - - // Global dragover handler - MUST prevent default for file drops - window.addEventListener('dragover', function(e) { - if (isFileDrop(e)) { - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; - } - }, true); // Use capture phase to handle before any other handlers - - // Global drop handler - MUST prevent default for file drops - window.addEventListener('drop', function(e) { - if (isFileDrop(e)) { - e.preventDefault(); - console.log('Global handler prevented file drop navigation'); - } - }, true); // Use capture phase to handle before any other handlers - - // Global dragleave handler - window.addEventListener('dragleave', function(e) { - if (isFileDrop(e)) { - e.preventDefault(); - } - }, true); // Use capture phase - - console.log('Global file drop prevention handlers registered'); -})(); - -document.querySelector('#app').innerHTML = ` -

Wails Drag & Drop Test

- -
-
-

HTML5 Source

-
Item 1
-
Item 2
-
Item 3
-
- -
-

HTML5 Drop

-

Drop here

-
- -
-

File Drop

-

Drop files here

-
-
- -
-

Event Log

-
-
-`; - -// Get all draggable items and drop zones -const draggables = document.querySelectorAll('.draggable'); -const dropZone = document.getElementById('dropZone'); -const fileDropZone = document.getElementById('fileDropZone'); -const eventLog = document.getElementById('eventLog'); -const dropMessage = document.getElementById('dropMessage'); -const fileDropMessage = document.getElementById('fileDropMessage'); - -let draggedItem = null; -let eventCounter = 0; - -// Function to log events -function logEvent(eventName, details = '') { - eventCounter++; - const timestamp = new Date().toLocaleTimeString(); - const logEntry = document.createElement('div'); - logEntry.className = `log-entry ${eventName.replace(' ', '-').toLowerCase()}`; - logEntry.textContent = `[${timestamp}] ${eventCounter}. ${eventName} ${details}`; - eventLog.insertBefore(logEntry, eventLog.firstChild); - - // Keep only last 20 events - while (eventLog.children.length > 20) { - eventLog.removeChild(eventLog.lastChild); - } - - console.log(`Event: ${eventName} ${details}`); -} - -// Add event listeners to draggable items -draggables.forEach(item => { - // Drag start - item.addEventListener('dragstart', (e) => { - draggedItem = e.target; - e.target.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'copy'; - e.dataTransfer.setData('text/plain', e.target.dataset.item); - logEvent('drag-start', `- Started dragging: ${e.target.dataset.item}`); - }); - - // Drag end - item.addEventListener('dragend', (e) => { - e.target.classList.remove('dragging'); - logEvent('drag-end', `- Ended dragging: ${e.target.dataset.item}`); - }); -}); - -// Add event listeners to HTML drop zone -dropZone.addEventListener('dragenter', (e) => { - e.preventDefault(); - dropZone.classList.add('drag-over'); - logEvent('drag-enter', '- Entered HTML drop zone'); -}); - -dropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; - // Don't log every dragover to avoid spam -}); - -dropZone.addEventListener('dragleave', (e) => { - if (e.target === dropZone) { - dropZone.classList.remove('drag-over'); - logEvent('drag-leave', '- Left HTML drop zone'); - } -}); - -dropZone.addEventListener('drop', (e) => { - e.preventDefault(); - dropZone.classList.remove('drag-over'); - - const data = e.dataTransfer.getData('text/plain'); - logEvent('drop', `- Dropped in HTML zone: ${data}`); - - if (draggedItem) { - // Create a copy of the dragged item - const droppedElement = document.createElement('div'); - droppedElement.className = 'dropped-item'; - droppedElement.textContent = data; - - // Remove the placeholder message if it exists - if (dropMessage) { - dropMessage.style.display = 'none'; - } - - dropZone.appendChild(droppedElement); - } - - draggedItem = null; -}); - -// Add event listeners to file drop zone -fileDropZone.addEventListener('dragenter', (e) => { - e.preventDefault(); - fileDropZone.classList.add('drag-over'); - logEvent('drag-enter', '- Entered file drop zone'); -}); - -fileDropZone.addEventListener('dragover', (e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; -}); - -fileDropZone.addEventListener('dragleave', (e) => { - if (e.target === fileDropZone) { - fileDropZone.classList.remove('drag-over'); - logEvent('drag-leave', '- Left file drop zone'); - } -}); - -fileDropZone.addEventListener('drop', (e) => { - e.preventDefault(); - fileDropZone.classList.remove('drag-over'); - - const files = [...e.dataTransfer.files]; - if (files.length > 0) { - logEvent('file-drop', `- Dropped ${files.length} file(s)`); - - // Hide the placeholder message - if (fileDropMessage) { - fileDropMessage.style.display = 'none'; - } - - // Display dropped files - files.forEach(file => { - const fileElement = document.createElement('div'); - fileElement.className = 'dropped-file'; - - // Format file size - let size = file.size; - let unit = 'bytes'; - if (size > 1024 * 1024) { - size = (size / (1024 * 1024)).toFixed(2); - unit = 'MB'; - } else if (size > 1024) { - size = (size / 1024).toFixed(2); - unit = 'KB'; - } - - fileElement.textContent = `📄 ${file.name} (${size} ${unit})`; - fileDropZone.appendChild(fileElement); - }); - } -}); - -// Log when page loads -window.addEventListener('DOMContentLoaded', () => { - logEvent('page-loaded', '- Wails drag-and-drop test page ready'); - console.log('Wails Drag and Drop test application loaded'); - - // Check if Wails drag and drop is enabled - if (window.wails && window.wails.flags) { - logEvent('wails-status', `- Wails DnD enabled: ${window.wails.flags.enableWailsDragAndDrop}`); - } - - // IMPORTANT: Register Wails drag-and-drop handlers to prevent browser navigation - // This will ensure external files don't open in new windows when dropped anywhere - if (window.runtime && window.runtime.OnFileDrop) { - window.runtime.OnFileDrop((x, y, paths) => { - logEvent('wails-file-drop', `- Wails received ${paths.length} file(s) at (${x}, ${y})`); - console.log('Wails OnFileDrop:', paths); - }, false); // false = don't require drop target, handle all file drops - logEvent('wails-setup', '- Wails OnFileDrop handlers registered'); - } -}); \ No newline at end of file diff --git a/v2/examples/dragdrop-test/frontend/src/style.css b/v2/examples/dragdrop-test/frontend/src/style.css deleted file mode 100644 index f5d071597..000000000 --- a/v2/examples/dragdrop-test/frontend/src/style.css +++ /dev/null @@ -1,33 +0,0 @@ -html { - background-color: rgba(27, 38, 54, 1); - text-align: center; - color: white; - height: 100%; - overflow: hidden; -} - -body { - margin: 0; - color: white; - font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", - "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; - height: 100%; - overflow: hidden; -} - -@font-face { - font-family: "Nunito"; - font-style: normal; - font-weight: 400; - src: local(""), - url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); -} - -#app { - height: 100vh; - text-align: center; - overflow: hidden; - box-sizing: border-box; - padding: 10px; -} diff --git a/v2/examples/dragdrop-test/frontend/wailsjs/go/main/App.d.ts b/v2/examples/dragdrop-test/frontend/wailsjs/go/main/App.d.ts deleted file mode 100644 index 02a3bb988..000000000 --- a/v2/examples/dragdrop-test/frontend/wailsjs/go/main/App.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function Greet(arg1:string):Promise; diff --git a/v2/examples/dragdrop-test/frontend/wailsjs/go/main/App.js b/v2/examples/dragdrop-test/frontend/wailsjs/go/main/App.js deleted file mode 100644 index c71ae77cb..000000000 --- a/v2/examples/dragdrop-test/frontend/wailsjs/go/main/App.js +++ /dev/null @@ -1,7 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function Greet(arg1) { - return window['go']['main']['App']['Greet'](arg1); -} diff --git a/v2/examples/dragdrop-test/frontend/wailsjs/runtime/package.json b/v2/examples/dragdrop-test/frontend/wailsjs/runtime/package.json deleted file mode 100644 index 1e7c8a5d7..000000000 --- a/v2/examples/dragdrop-test/frontend/wailsjs/runtime/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@wailsapp/runtime", - "version": "2.0.0", - "description": "Wails Javascript runtime library", - "main": "runtime.js", - "types": "runtime.d.ts", - "scripts": { - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wailsapp/wails.git" - }, - "keywords": [ - "Wails", - "Javascript", - "Go" - ], - "author": "Lea Anthony ", - "license": "MIT", - "bugs": { - "url": "https://github.com/wailsapp/wails/issues" - }, - "homepage": "https://github.com/wailsapp/wails#readme" -} diff --git a/v2/examples/dragdrop-test/frontend/wailsjs/runtime/runtime.d.ts b/v2/examples/dragdrop-test/frontend/wailsjs/runtime/runtime.d.ts deleted file mode 100644 index 4445dac21..000000000 --- a/v2/examples/dragdrop-test/frontend/wailsjs/runtime/runtime.d.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export interface Position { - x: number; - y: number; -} - -export interface Size { - w: number; - h: number; -} - -export interface Screen { - isCurrent: boolean; - isPrimary: boolean; - width : number - height : number -} - -// Environment information such as platform, buildtype, ... -export interface EnvironmentInfo { - buildType: string; - platform: string; - arch: string; -} - -// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) -// emits the given event. Optional data may be passed with the event. -// This will trigger any event listeners. -export function EventsEmit(eventName: string, ...data: any): void; - -// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. -export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) -// sets up a listener for the given event name, but will only trigger a given number times. -export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; - -// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) -// sets up a listener for the given event name, but will only trigger once. -export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) -// unregisters the listener for the given event name. -export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; - -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all listeners. -export function EventsOffAll(): void; - -// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) -// logs the given message as a raw message -export function LogPrint(message: string): void; - -// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) -// logs the given message at the `trace` log level. -export function LogTrace(message: string): void; - -// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) -// logs the given message at the `debug` log level. -export function LogDebug(message: string): void; - -// [LogError](https://wails.io/docs/reference/runtime/log#logerror) -// logs the given message at the `error` log level. -export function LogError(message: string): void; - -// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) -// logs the given message at the `fatal` log level. -// The application will quit after calling this method. -export function LogFatal(message: string): void; - -// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) -// logs the given message at the `info` log level. -export function LogInfo(message: string): void; - -// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) -// logs the given message at the `warning` log level. -export function LogWarning(message: string): void; - -// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) -// Forces a reload by the main application as well as connected browsers. -export function WindowReload(): void; - -// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) -// Reloads the application frontend. -export function WindowReloadApp(): void; - -// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) -// Sets the window AlwaysOnTop or not on top. -export function WindowSetAlwaysOnTop(b: boolean): void; - -// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) -// *Windows only* -// Sets window theme to system default (dark/light). -export function WindowSetSystemDefaultTheme(): void; - -// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) -// *Windows only* -// Sets window to light theme. -export function WindowSetLightTheme(): void; - -// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) -// *Windows only* -// Sets window to dark theme. -export function WindowSetDarkTheme(): void; - -// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) -// Centers the window on the monitor the window is currently on. -export function WindowCenter(): void; - -// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) -// Sets the text in the window title bar. -export function WindowSetTitle(title: string): void; - -// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) -// Makes the window full screen. -export function WindowFullscreen(): void; - -// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) -// Restores the previous window dimensions and position prior to full screen. -export function WindowUnfullscreen(): void; - -// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) -// Returns the state of the window, i.e. whether the window is in full screen mode or not. -export function WindowIsFullscreen(): Promise; - -// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) -// Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; - -// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) -// Gets the width and height of the window. -export function WindowGetSize(): Promise; - -// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) -// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMaxSize(width: number, height: number): void; - -// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) -// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMinSize(width: number, height: number): void; - -// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) -// Sets the window position relative to the monitor the window is currently on. -export function WindowSetPosition(x: number, y: number): void; - -// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) -// Gets the window position relative to the monitor the window is currently on. -export function WindowGetPosition(): Promise; - -// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) -// Hides the window. -export function WindowHide(): void; - -// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) -// Shows the window, if it is currently hidden. -export function WindowShow(): void; - -// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) -// Maximises the window to fill the screen. -export function WindowMaximise(): void; - -// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) -// Toggles between Maximised and UnMaximised. -export function WindowToggleMaximise(): void; - -// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) -// Restores the window to the dimensions and position prior to maximising. -export function WindowUnmaximise(): void; - -// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) -// Returns the state of the window, i.e. whether the window is maximised or not. -export function WindowIsMaximised(): Promise; - -// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) -// Minimises the window. -export function WindowMinimise(): void; - -// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) -// Restores the window to the dimensions and position prior to minimising. -export function WindowUnminimise(): void; - -// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) -// Returns the state of the window, i.e. whether the window is minimised or not. -export function WindowIsMinimised(): Promise; - -// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) -// Returns the state of the window, i.e. whether the window is normal or not. -export function WindowIsNormal(): Promise; - -// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) -// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. -export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; - -// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) -// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. -export function ScreenGetAll(): Promise; - -// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) -// Opens the given URL in the system browser. -export function BrowserOpenURL(url: string): void; - -// [Environment](https://wails.io/docs/reference/runtime/intro#environment) -// Returns information about the environment -export function Environment(): Promise; - -// [Quit](https://wails.io/docs/reference/runtime/intro#quit) -// Quits the application. -export function Quit(): void; - -// [Hide](https://wails.io/docs/reference/runtime/intro#hide) -// Hides the application. -export function Hide(): void; - -// [Show](https://wails.io/docs/reference/runtime/intro#show) -// Shows the application. -export function Show(): void; - -// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) -// Returns the current text stored on clipboard -export function ClipboardGetText(): Promise; - -// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) -// Sets a text on the clipboard -export function ClipboardSetText(text: string): Promise; - -// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) -// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. -export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void - -// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) -// OnFileDropOff removes the drag and drop listeners and handlers. -export function OnFileDropOff() :void - -// Check if the file path resolver is available -export function CanResolveFilePaths(): boolean; - -// Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/v2/examples/dragdrop-test/frontend/wailsjs/runtime/runtime.js b/v2/examples/dragdrop-test/frontend/wailsjs/runtime/runtime.js deleted file mode 100644 index 7cb89d750..000000000 --- a/v2/examples/dragdrop-test/frontend/wailsjs/runtime/runtime.js +++ /dev/null @@ -1,242 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export function LogPrint(message) { - window.runtime.LogPrint(message); -} - -export function LogTrace(message) { - window.runtime.LogTrace(message); -} - -export function LogDebug(message) { - window.runtime.LogDebug(message); -} - -export function LogInfo(message) { - window.runtime.LogInfo(message); -} - -export function LogWarning(message) { - window.runtime.LogWarning(message); -} - -export function LogError(message) { - window.runtime.LogError(message); -} - -export function LogFatal(message) { - window.runtime.LogFatal(message); -} - -export function EventsOnMultiple(eventName, callback, maxCallbacks) { - return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); -} - -export function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); -} - -export function EventsOff(eventName, ...additionalEventNames) { - return window.runtime.EventsOff(eventName, ...additionalEventNames); -} - -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - -export function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); -} - -export function EventsEmit(eventName) { - let args = [eventName].slice.call(arguments); - return window.runtime.EventsEmit.apply(null, args); -} - -export function WindowReload() { - window.runtime.WindowReload(); -} - -export function WindowReloadApp() { - window.runtime.WindowReloadApp(); -} - -export function WindowSetAlwaysOnTop(b) { - window.runtime.WindowSetAlwaysOnTop(b); -} - -export function WindowSetSystemDefaultTheme() { - window.runtime.WindowSetSystemDefaultTheme(); -} - -export function WindowSetLightTheme() { - window.runtime.WindowSetLightTheme(); -} - -export function WindowSetDarkTheme() { - window.runtime.WindowSetDarkTheme(); -} - -export function WindowCenter() { - window.runtime.WindowCenter(); -} - -export function WindowSetTitle(title) { - window.runtime.WindowSetTitle(title); -} - -export function WindowFullscreen() { - window.runtime.WindowFullscreen(); -} - -export function WindowUnfullscreen() { - window.runtime.WindowUnfullscreen(); -} - -export function WindowIsFullscreen() { - return window.runtime.WindowIsFullscreen(); -} - -export function WindowGetSize() { - return window.runtime.WindowGetSize(); -} - -export function WindowSetSize(width, height) { - window.runtime.WindowSetSize(width, height); -} - -export function WindowSetMaxSize(width, height) { - window.runtime.WindowSetMaxSize(width, height); -} - -export function WindowSetMinSize(width, height) { - window.runtime.WindowSetMinSize(width, height); -} - -export function WindowSetPosition(x, y) { - window.runtime.WindowSetPosition(x, y); -} - -export function WindowGetPosition() { - return window.runtime.WindowGetPosition(); -} - -export function WindowHide() { - window.runtime.WindowHide(); -} - -export function WindowShow() { - window.runtime.WindowShow(); -} - -export function WindowMaximise() { - window.runtime.WindowMaximise(); -} - -export function WindowToggleMaximise() { - window.runtime.WindowToggleMaximise(); -} - -export function WindowUnmaximise() { - window.runtime.WindowUnmaximise(); -} - -export function WindowIsMaximised() { - return window.runtime.WindowIsMaximised(); -} - -export function WindowMinimise() { - window.runtime.WindowMinimise(); -} - -export function WindowUnminimise() { - window.runtime.WindowUnminimise(); -} - -export function WindowSetBackgroundColour(R, G, B, A) { - window.runtime.WindowSetBackgroundColour(R, G, B, A); -} - -export function ScreenGetAll() { - return window.runtime.ScreenGetAll(); -} - -export function WindowIsMinimised() { - return window.runtime.WindowIsMinimised(); -} - -export function WindowIsNormal() { - return window.runtime.WindowIsNormal(); -} - -export function BrowserOpenURL(url) { - window.runtime.BrowserOpenURL(url); -} - -export function Environment() { - return window.runtime.Environment(); -} - -export function Quit() { - window.runtime.Quit(); -} - -export function Hide() { - window.runtime.Hide(); -} - -export function Show() { - window.runtime.Show(); -} - -export function ClipboardGetText() { - return window.runtime.ClipboardGetText(); -} - -export function ClipboardSetText(text) { - return window.runtime.ClipboardSetText(text); -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - return window.runtime.OnFileDrop(callback, useDropTarget); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - return window.runtime.OnFileDropOff(); -} - -export function CanResolveFilePaths() { - return window.runtime.CanResolveFilePaths(); -} - -export function ResolveFilePaths(files) { - return window.runtime.ResolveFilePaths(files); -} \ No newline at end of file diff --git a/v2/examples/dragdrop-test/go.mod b/v2/examples/dragdrop-test/go.mod deleted file mode 100644 index be13aac19..000000000 --- a/v2/examples/dragdrop-test/go.mod +++ /dev/null @@ -1,37 +0,0 @@ -module dragdrop-test - -go 1.23 - -require github.com/wailsapp/wails/v2 v2.10.1 - -require ( - github.com/bep/debounce v1.2.1 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect - github.com/labstack/echo/v4 v4.13.3 // indirect - github.com/labstack/gommon v0.4.2 // indirect - github.com/leaanthony/go-ansi-parser v1.6.1 // indirect - github.com/leaanthony/gosod v1.0.4 // indirect - github.com/leaanthony/slicer v1.6.0 // indirect - github.com/leaanthony/u v1.1.1 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // 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/tkrajina/go-reflector v0.5.8 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/wailsapp/go-webview2 v1.0.22 // indirect - github.com/wailsapp/mimetype v1.4.1 // 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 -) - -replace github.com/wailsapp/wails/v2 => E:/releases/wails/v2 diff --git a/v2/examples/dragdrop-test/go.sum b/v2/examples/dragdrop-test/go.sum deleted file mode 100644 index 10d4a9b18..000000000 --- a/v2/examples/dragdrop-test/go.sum +++ /dev/null @@ -1,79 +0,0 @@ -github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= -github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= -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/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/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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -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/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -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/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= -github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= -github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= -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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -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/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/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= -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/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= -github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= -github.com/wailsapp/go-webview2 v1.0.22/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= -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/net v0.0.0-20210505024714-0287a6fb4125/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-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2/examples/dragdrop-test/main.go b/v2/examples/dragdrop-test/main.go deleted file mode 100644 index 64a0c2734..000000000 --- a/v2/examples/dragdrop-test/main.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "embed" - - "github.com/wailsapp/wails/v2" - "github.com/wailsapp/wails/v2/pkg/options" - "github.com/wailsapp/wails/v2/pkg/options/assetserver" -) - -//go:embed all:frontend/dist -var assets embed.FS - -func main() { - // Create an instance of the app structure - app := NewApp() - - // Create application with options - err := wails.Run(&options.App{ - Title: "Wails Drag & Drop Test", - Width: 800, - Height: 600, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - OnStartup: app.startup, - Bind: []interface{}{ - app, - }, - }) - - if err != nil { - println("Error:", err.Error()) - } -} diff --git a/v2/examples/dragdrop-test/wails.json b/v2/examples/dragdrop-test/wails.json deleted file mode 100644 index 7970ea4ca..000000000 --- a/v2/examples/dragdrop-test/wails.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://wails.io/schemas/config.v2.json", - "name": "dragdrop-test", - "outputfilename": "dragdrop-test", - "frontend:install": "npm install", - "frontend:build": "npm run build", - "frontend:dev:watcher": "npm run dev", - "frontend:dev:serverUrl": "auto", - "author": { - "name": "Lea Anthony", - "email": "lea.anthony@gmail.com" - } -} diff --git a/v2/examples/panic-recovery-test/README.md b/v2/examples/panic-recovery-test/README.md deleted file mode 100644 index c0a6a7e5a..000000000 --- a/v2/examples/panic-recovery-test/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Panic Recovery Test - -This example demonstrates the Linux signal handler issue (#3965) and verifies the fix using `runtime.ResetSignalHandlers()`. - -## The Problem - -On Linux, WebKit installs signal handlers without the `SA_ONSTACK` flag, which prevents Go from recovering panics caused by nil pointer dereferences (SIGSEGV). Without the fix, the application crashes with: - -``` -signal 11 received but handler not on signal stack -fatal error: non-Go code set up signal handler without SA_ONSTACK flag -``` - -## The Solution - -Call `runtime.ResetSignalHandlers()` immediately before code that might panic: - -```go -import "github.com/wailsapp/wails/v2/pkg/runtime" - -go func() { - defer func() { - if err := recover(); err != nil { - log.Printf("Recovered: %v", err) - } - }() - runtime.ResetSignalHandlers() - // Code that might panic... -}() -``` - -## How to Reproduce - -### Prerequisites - -- Linux with WebKit2GTK 4.1 installed -- Go 1.21+ -- Wails CLI - -### Steps - -1. Build the example: - ```bash - cd v2/examples/panic-recovery-test - wails build -tags webkit2_41 - ``` - -2. Run the application: - ```bash - ./build/bin/panic-recovery-test - ``` - -3. Wait ~10 seconds (the app auto-calls `Greet` after 5s, then waits another 5s before the nil pointer dereference) - -### Expected Result (with fix) - -The panic is recovered and you see: -``` -------------------------------"invalid memory address or nil pointer dereference" -``` - -The application continues running. - -### Without the fix - -Comment out the `runtime.ResetSignalHandlers()` call in `app.go` and rebuild. The application will crash with a fatal signal 11 error. - -## Files - -- `app.go` - Contains the `Greet` function that demonstrates panic recovery -- `frontend/src/main.js` - Auto-calls `Greet` after 5 seconds to trigger the test - -## Related - -- Issue: https://github.com/wailsapp/wails/issues/3965 -- Original fix PR: https://github.com/wailsapp/wails/pull/2152 diff --git a/v2/examples/panic-recovery-test/app.go b/v2/examples/panic-recovery-test/app.go deleted file mode 100644 index ceb46e8d5..000000000 --- a/v2/examples/panic-recovery-test/app.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "fmt" - "time" - - "github.com/wailsapp/wails/v2/pkg/runtime" -) - -// App struct -type App struct { - ctx context.Context -} - -// NewApp creates a new App application struct -func NewApp() *App { - return &App{} -} - -// startup is called when the app starts. The context is saved -// so we can call the runtime methods -func (a *App) startup(ctx context.Context) { - a.ctx = ctx -} - -// Greet returns a greeting for the given name -func (a *App) Greet(name string) string { - go func() { - defer func() { - if err := recover(); err != nil { - fmt.Printf("------------------------------%#v\n", err) - } - }() - time.Sleep(5 * time.Second) - // Fix signal handlers right before potential panic using the Wails runtime - runtime.ResetSignalHandlers() - // Nil pointer dereference - causes SIGSEGV - var t *time.Time - fmt.Println(t.Unix()) - }() - - return fmt.Sprintf("Hello %s, It's show time!", name) -} diff --git a/v2/examples/panic-recovery-test/frontend/index.html b/v2/examples/panic-recovery-test/frontend/index.html deleted file mode 100644 index d7aa4e942..000000000 --- a/v2/examples/panic-recovery-test/frontend/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - panic-test - - -
- - - diff --git a/v2/examples/panic-recovery-test/frontend/package.json b/v2/examples/panic-recovery-test/frontend/package.json deleted file mode 100644 index a1b6f8e1a..000000000 --- a/v2/examples/panic-recovery-test/frontend/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "frontend", - "private": true, - "version": "0.0.0", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "devDependencies": { - "vite": "^3.0.7" - } -} \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/frontend/src/app.css b/v2/examples/panic-recovery-test/frontend/src/app.css deleted file mode 100644 index 59d06f692..000000000 --- a/v2/examples/panic-recovery-test/frontend/src/app.css +++ /dev/null @@ -1,54 +0,0 @@ -#logo { - display: block; - width: 50%; - height: 50%; - margin: auto; - padding: 10% 0 0; - background-position: center; - background-repeat: no-repeat; - background-size: 100% 100%; - background-origin: content-box; -} - -.result { - height: 20px; - line-height: 20px; - margin: 1.5rem auto; -} - -.input-box .btn { - width: 60px; - height: 30px; - line-height: 30px; - border-radius: 3px; - border: none; - margin: 0 0 0 20px; - padding: 0 8px; - cursor: pointer; -} - -.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; - 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/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt deleted file mode 100644 index 9cac04ce8..000000000 --- a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), - -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/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 deleted file mode 100644 index 2f9cc5964..000000000 Binary files a/v2/examples/panic-recovery-test/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ diff --git a/v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png b/v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png deleted file mode 100644 index d63303bfa..000000000 Binary files a/v2/examples/panic-recovery-test/frontend/src/assets/images/logo-universal.png and /dev/null differ diff --git a/v2/examples/panic-recovery-test/frontend/src/main.js b/v2/examples/panic-recovery-test/frontend/src/main.js deleted file mode 100644 index ea5e74fc6..000000000 --- a/v2/examples/panic-recovery-test/frontend/src/main.js +++ /dev/null @@ -1,55 +0,0 @@ -import './style.css'; -import './app.css'; - -import logo from './assets/images/logo-universal.png'; -import {Greet} from '../wailsjs/go/main/App'; - -document.querySelector('#app').innerHTML = ` - -
Please enter your name below 👇
-
- - -
- -`; -document.getElementById('logo').src = logo; - -let nameElement = document.getElementById("name"); -nameElement.focus(); -let resultElement = document.getElementById("result"); - -// Setup the greet function -window.greet = function () { - // Get name - let name = nameElement.value; - - // Check if the input is empty - if (name === "") return; - - // Call App.Greet(name) - try { - Greet(name) - .then((result) => { - // Update result with data back from App.Greet() - resultElement.innerText = result; - }) - .catch((err) => { - console.error(err); - }); - } catch (err) { - console.error(err); - } -}; - -// Auto-call Greet after 5 seconds to trigger the panic test -setTimeout(() => { - console.log("Auto-calling Greet to trigger panic test..."); - Greet("PanicTest") - .then((result) => { - resultElement.innerText = result + " (auto-called - panic will occur in 5s)"; - }) - .catch((err) => { - console.error("Error:", err); - }); -}, 5000); diff --git a/v2/examples/panic-recovery-test/frontend/src/style.css b/v2/examples/panic-recovery-test/frontend/src/style.css deleted file mode 100644 index 3940d6c63..000000000 --- a/v2/examples/panic-recovery-test/frontend/src/style.css +++ /dev/null @@ -1,26 +0,0 @@ -html { - background-color: rgba(27, 38, 54, 1); - text-align: center; - color: white; -} - -body { - margin: 0; - color: white; - font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", - "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", - sans-serif; -} - -@font-face { - font-family: "Nunito"; - font-style: normal; - font-weight: 400; - src: local(""), - url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); -} - -#app { - height: 100vh; - text-align: center; -} diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts deleted file mode 100755 index 02a3bb988..000000000 --- a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function Greet(arg1:string):Promise; diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js b/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js deleted file mode 100755 index c71ae77cb..000000000 --- a/v2/examples/panic-recovery-test/frontend/wailsjs/go/main/App.js +++ /dev/null @@ -1,7 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function Greet(arg1) { - return window['go']['main']['App']['Greet'](arg1); -} diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json deleted file mode 100644 index 1e7c8a5d7..000000000 --- a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@wailsapp/runtime", - "version": "2.0.0", - "description": "Wails Javascript runtime library", - "main": "runtime.js", - "types": "runtime.d.ts", - "scripts": { - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wailsapp/wails.git" - }, - "keywords": [ - "Wails", - "Javascript", - "Go" - ], - "author": "Lea Anthony ", - "license": "MIT", - "bugs": { - "url": "https://github.com/wailsapp/wails/issues" - }, - "homepage": "https://github.com/wailsapp/wails#readme" -} diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts deleted file mode 100644 index 4445dac21..000000000 --- a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.d.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export interface Position { - x: number; - y: number; -} - -export interface Size { - w: number; - h: number; -} - -export interface Screen { - isCurrent: boolean; - isPrimary: boolean; - width : number - height : number -} - -// Environment information such as platform, buildtype, ... -export interface EnvironmentInfo { - buildType: string; - platform: string; - arch: string; -} - -// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) -// emits the given event. Optional data may be passed with the event. -// This will trigger any event listeners. -export function EventsEmit(eventName: string, ...data: any): void; - -// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. -export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) -// sets up a listener for the given event name, but will only trigger a given number times. -export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; - -// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) -// sets up a listener for the given event name, but will only trigger once. -export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) -// unregisters the listener for the given event name. -export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; - -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all listeners. -export function EventsOffAll(): void; - -// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) -// logs the given message as a raw message -export function LogPrint(message: string): void; - -// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) -// logs the given message at the `trace` log level. -export function LogTrace(message: string): void; - -// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) -// logs the given message at the `debug` log level. -export function LogDebug(message: string): void; - -// [LogError](https://wails.io/docs/reference/runtime/log#logerror) -// logs the given message at the `error` log level. -export function LogError(message: string): void; - -// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) -// logs the given message at the `fatal` log level. -// The application will quit after calling this method. -export function LogFatal(message: string): void; - -// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) -// logs the given message at the `info` log level. -export function LogInfo(message: string): void; - -// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) -// logs the given message at the `warning` log level. -export function LogWarning(message: string): void; - -// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) -// Forces a reload by the main application as well as connected browsers. -export function WindowReload(): void; - -// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) -// Reloads the application frontend. -export function WindowReloadApp(): void; - -// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) -// Sets the window AlwaysOnTop or not on top. -export function WindowSetAlwaysOnTop(b: boolean): void; - -// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) -// *Windows only* -// Sets window theme to system default (dark/light). -export function WindowSetSystemDefaultTheme(): void; - -// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) -// *Windows only* -// Sets window to light theme. -export function WindowSetLightTheme(): void; - -// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) -// *Windows only* -// Sets window to dark theme. -export function WindowSetDarkTheme(): void; - -// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) -// Centers the window on the monitor the window is currently on. -export function WindowCenter(): void; - -// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) -// Sets the text in the window title bar. -export function WindowSetTitle(title: string): void; - -// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) -// Makes the window full screen. -export function WindowFullscreen(): void; - -// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) -// Restores the previous window dimensions and position prior to full screen. -export function WindowUnfullscreen(): void; - -// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) -// Returns the state of the window, i.e. whether the window is in full screen mode or not. -export function WindowIsFullscreen(): Promise; - -// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) -// Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; - -// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) -// Gets the width and height of the window. -export function WindowGetSize(): Promise; - -// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) -// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMaxSize(width: number, height: number): void; - -// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) -// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMinSize(width: number, height: number): void; - -// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) -// Sets the window position relative to the monitor the window is currently on. -export function WindowSetPosition(x: number, y: number): void; - -// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) -// Gets the window position relative to the monitor the window is currently on. -export function WindowGetPosition(): Promise; - -// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) -// Hides the window. -export function WindowHide(): void; - -// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) -// Shows the window, if it is currently hidden. -export function WindowShow(): void; - -// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) -// Maximises the window to fill the screen. -export function WindowMaximise(): void; - -// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) -// Toggles between Maximised and UnMaximised. -export function WindowToggleMaximise(): void; - -// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) -// Restores the window to the dimensions and position prior to maximising. -export function WindowUnmaximise(): void; - -// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) -// Returns the state of the window, i.e. whether the window is maximised or not. -export function WindowIsMaximised(): Promise; - -// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) -// Minimises the window. -export function WindowMinimise(): void; - -// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) -// Restores the window to the dimensions and position prior to minimising. -export function WindowUnminimise(): void; - -// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) -// Returns the state of the window, i.e. whether the window is minimised or not. -export function WindowIsMinimised(): Promise; - -// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) -// Returns the state of the window, i.e. whether the window is normal or not. -export function WindowIsNormal(): Promise; - -// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) -// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. -export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; - -// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) -// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. -export function ScreenGetAll(): Promise; - -// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) -// Opens the given URL in the system browser. -export function BrowserOpenURL(url: string): void; - -// [Environment](https://wails.io/docs/reference/runtime/intro#environment) -// Returns information about the environment -export function Environment(): Promise; - -// [Quit](https://wails.io/docs/reference/runtime/intro#quit) -// Quits the application. -export function Quit(): void; - -// [Hide](https://wails.io/docs/reference/runtime/intro#hide) -// Hides the application. -export function Hide(): void; - -// [Show](https://wails.io/docs/reference/runtime/intro#show) -// Shows the application. -export function Show(): void; - -// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) -// Returns the current text stored on clipboard -export function ClipboardGetText(): Promise; - -// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) -// Sets a text on the clipboard -export function ClipboardSetText(text: string): Promise; - -// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) -// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. -export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void - -// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) -// OnFileDropOff removes the drag and drop listeners and handlers. -export function OnFileDropOff() :void - -// Check if the file path resolver is available -export function CanResolveFilePaths(): boolean; - -// Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js b/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js deleted file mode 100644 index 7cb89d750..000000000 --- a/v2/examples/panic-recovery-test/frontend/wailsjs/runtime/runtime.js +++ /dev/null @@ -1,242 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export function LogPrint(message) { - window.runtime.LogPrint(message); -} - -export function LogTrace(message) { - window.runtime.LogTrace(message); -} - -export function LogDebug(message) { - window.runtime.LogDebug(message); -} - -export function LogInfo(message) { - window.runtime.LogInfo(message); -} - -export function LogWarning(message) { - window.runtime.LogWarning(message); -} - -export function LogError(message) { - window.runtime.LogError(message); -} - -export function LogFatal(message) { - window.runtime.LogFatal(message); -} - -export function EventsOnMultiple(eventName, callback, maxCallbacks) { - return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); -} - -export function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); -} - -export function EventsOff(eventName, ...additionalEventNames) { - return window.runtime.EventsOff(eventName, ...additionalEventNames); -} - -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - -export function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); -} - -export function EventsEmit(eventName) { - let args = [eventName].slice.call(arguments); - return window.runtime.EventsEmit.apply(null, args); -} - -export function WindowReload() { - window.runtime.WindowReload(); -} - -export function WindowReloadApp() { - window.runtime.WindowReloadApp(); -} - -export function WindowSetAlwaysOnTop(b) { - window.runtime.WindowSetAlwaysOnTop(b); -} - -export function WindowSetSystemDefaultTheme() { - window.runtime.WindowSetSystemDefaultTheme(); -} - -export function WindowSetLightTheme() { - window.runtime.WindowSetLightTheme(); -} - -export function WindowSetDarkTheme() { - window.runtime.WindowSetDarkTheme(); -} - -export function WindowCenter() { - window.runtime.WindowCenter(); -} - -export function WindowSetTitle(title) { - window.runtime.WindowSetTitle(title); -} - -export function WindowFullscreen() { - window.runtime.WindowFullscreen(); -} - -export function WindowUnfullscreen() { - window.runtime.WindowUnfullscreen(); -} - -export function WindowIsFullscreen() { - return window.runtime.WindowIsFullscreen(); -} - -export function WindowGetSize() { - return window.runtime.WindowGetSize(); -} - -export function WindowSetSize(width, height) { - window.runtime.WindowSetSize(width, height); -} - -export function WindowSetMaxSize(width, height) { - window.runtime.WindowSetMaxSize(width, height); -} - -export function WindowSetMinSize(width, height) { - window.runtime.WindowSetMinSize(width, height); -} - -export function WindowSetPosition(x, y) { - window.runtime.WindowSetPosition(x, y); -} - -export function WindowGetPosition() { - return window.runtime.WindowGetPosition(); -} - -export function WindowHide() { - window.runtime.WindowHide(); -} - -export function WindowShow() { - window.runtime.WindowShow(); -} - -export function WindowMaximise() { - window.runtime.WindowMaximise(); -} - -export function WindowToggleMaximise() { - window.runtime.WindowToggleMaximise(); -} - -export function WindowUnmaximise() { - window.runtime.WindowUnmaximise(); -} - -export function WindowIsMaximised() { - return window.runtime.WindowIsMaximised(); -} - -export function WindowMinimise() { - window.runtime.WindowMinimise(); -} - -export function WindowUnminimise() { - window.runtime.WindowUnminimise(); -} - -export function WindowSetBackgroundColour(R, G, B, A) { - window.runtime.WindowSetBackgroundColour(R, G, B, A); -} - -export function ScreenGetAll() { - return window.runtime.ScreenGetAll(); -} - -export function WindowIsMinimised() { - return window.runtime.WindowIsMinimised(); -} - -export function WindowIsNormal() { - return window.runtime.WindowIsNormal(); -} - -export function BrowserOpenURL(url) { - window.runtime.BrowserOpenURL(url); -} - -export function Environment() { - return window.runtime.Environment(); -} - -export function Quit() { - window.runtime.Quit(); -} - -export function Hide() { - window.runtime.Hide(); -} - -export function Show() { - window.runtime.Show(); -} - -export function ClipboardGetText() { - return window.runtime.ClipboardGetText(); -} - -export function ClipboardSetText(text) { - return window.runtime.ClipboardSetText(text); -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - return window.runtime.OnFileDrop(callback, useDropTarget); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - return window.runtime.OnFileDropOff(); -} - -export function CanResolveFilePaths() { - return window.runtime.CanResolveFilePaths(); -} - -export function ResolveFilePaths(files) { - return window.runtime.ResolveFilePaths(files); -} \ No newline at end of file diff --git a/v2/examples/panic-recovery-test/go.mod b/v2/examples/panic-recovery-test/go.mod deleted file mode 100644 index 026042cbf..000000000 --- a/v2/examples/panic-recovery-test/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module panic-recovery-test - -go 1.21 - -require github.com/wailsapp/wails/v2 v2.11.0 diff --git a/v2/examples/panic-recovery-test/main.go b/v2/examples/panic-recovery-test/main.go deleted file mode 100644 index f6a38e86c..000000000 --- a/v2/examples/panic-recovery-test/main.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "embed" - - "github.com/wailsapp/wails/v2" - "github.com/wailsapp/wails/v2/pkg/options" - "github.com/wailsapp/wails/v2/pkg/options/assetserver" -) - -//go:embed all:frontend/dist -var assets embed.FS - -func main() { - // Create an instance of the app structure - app := NewApp() - - // Create application with options - err := wails.Run(&options.App{ - Title: "panic-test", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - OnStartup: app.startup, - Bind: []interface{}{ - app, - }, - }) - - if err != nil { - println("Error:", err.Error()) - } -} diff --git a/v2/examples/panic-recovery-test/wails.json b/v2/examples/panic-recovery-test/wails.json deleted file mode 100644 index 56770f091..000000000 --- a/v2/examples/panic-recovery-test/wails.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://wails.io/schemas/config.v2.json", - "name": "panic-recovery-test", - "outputfilename": "panic-recovery-test", - "frontend:install": "npm install", - "frontend:build": "npm run build", - "frontend:dev:watcher": "npm run dev", - "frontend:dev:serverUrl": "auto", - "author": { - "name": "Lea Anthony", - "email": "lea.anthony@gmail.com" - } -} diff --git a/v2/go.mod b/v2/go.mod index f1287bde7..c40a121f9 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -1,113 +1,95 @@ module github.com/wailsapp/wails/v2 -go 1.22.0 +go 1.18 require ( github.com/Masterminds/semver v1.5.0 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/bep/debounce v1.2.1 - github.com/bitfield/script v0.24.0 - github.com/charmbracelet/glamour v0.8.0 - github.com/flytam/filenamify v1.2.0 - github.com/fsnotify/fsnotify v1.9.0 - 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/bitfield/script v0.19.0 + github.com/charmbracelet/glamour v0.5.0 + github.com/flytam/filenamify v1.0.0 + github.com/fsnotify/fsnotify v1.4.9 + github.com/go-git/go-git/v5 v5.3.0 + github.com/go-ole/go-ole v1.2.6 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 + github.com/google/uuid v1.3.0 github.com/jackmordaunt/icns v1.0.0 - github.com/jaypipes/ghw v0.21.3 - github.com/labstack/echo/v4 v4.13.3 - github.com/labstack/gommon v0.4.2 + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e + github.com/labstack/echo/v4 v4.10.2 + github.com/labstack/gommon v0.4.0 github.com/leaanthony/clir v1.3.0 github.com/leaanthony/debme v1.2.1 - github.com/leaanthony/go-ansi-parser v1.6.1 - github.com/leaanthony/gosod v1.0.4 + github.com/leaanthony/go-ansi-parser v1.6.0 + github.com/leaanthony/gosod v1.0.3 github.com/leaanthony/slicer v1.6.0 - github.com/leaanthony/u v1.1.1 github.com/leaanthony/winicon v1.0.0 - github.com/matryer/is v1.4.1 - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + github.com/matryer/is v1.4.0 + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pkg/errors v0.9.1 - github.com/pterm/pterm v0.12.80 + github.com/pterm/pterm v0.12.49 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 - github.com/samber/lo v1.49.1 - github.com/stretchr/testify v1.10.0 - github.com/tc-hib/winres v0.3.1 - github.com/tidwall/sjson v1.2.5 - github.com/tkrajina/go-reflector v0.5.8 - github.com/wailsapp/go-webview2 v1.0.22 + github.com/samber/lo v1.38.1 + github.com/stretchr/testify v1.8.1 + github.com/tc-hib/winres v0.1.5 + github.com/tidwall/sjson v1.1.7 + github.com/tkrajina/go-reflector v0.5.6 github.com/wailsapp/mimetype v1.4.1 github.com/wzshiming/ctc v1.2.3 - golang.org/x/mod v0.23.0 - golang.org/x/net v0.35.0 - golang.org/x/sys v0.30.0 - golang.org/x/tools v0.30.0 + golang.org/x/mod v0.8.0 + golang.org/x/net v0.10.0 + golang.org/x/sys v0.8.0 + golang.org/x/tools v0.6.0 ) require ( - atomicgo.dev/cursor v0.2.0 // indirect - atomicgo.dev/keyboard v0.2.9 // indirect - atomicgo.dev/schedule v0.1.0 // indirect - dario.cat/mergo v1.0.0 // indirect - git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.5 // indirect - github.com/alecthomas/chroma/v2 v2.14.0 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + atomicgo.dev/cursor v0.1.1 // indirect + atomicgo.dev/keyboard v0.2.8 // indirect + bitbucket.org/creachadair/shell v0.0.7 // indirect + github.com/Microsoft/go-winio v0.4.16 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/charmbracelet/lipgloss v0.12.1 // indirect - github.com/charmbracelet/x/ansi v0.1.4 // indirect - github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/console v1.0.3 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dlclark/regexp2 v1.11.0 // 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/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/gookit/color v1.5.4 // indirect - github.com/gorilla/css v1.0.1 // indirect - github.com/itchyny/gojq v0.12.13 // indirect - github.com/itchyny/timefmt-go v0.1.5 // indirect - github.com/jaypipes/pcidb v1.1.1 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.2.0 // indirect + github.com/gookit/color v1.5.2 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/imdario/mergo v0.3.12 // 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/lithammer/fuzzysearch v1.1.8 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/lithammer/fuzzysearch v1.1.5 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/microcosm-cc/bluemonday v1.0.17 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect + github.com/muesli/termenv v0.9.0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect - github.com/tidwall/gjson v1.14.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/tidwall/gjson v1.9.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // 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.4 // indirect - github.com/yuin/goldmark-emoji v1.0.3 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/image v0.12.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + github.com/yuin/goldmark v1.4.13 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/image v0.5.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 // indirect - mvdan.cc/sh/v3 v3.7.0 // indirect ) diff --git a/v2/go.sum b/v2/go.sum index 2cfe9f7ab..415b4e42d 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -1,15 +1,9 @@ -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.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/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= +atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= +atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.8 h1:Di09BitwZgdTV1hPyX/b9Cqxi8HVuJQwWivnZUEqlj4= +atomicgo.dev/keyboard v0.2.8/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +bitbucket.org/creachadair/shell v0.0.7 h1:Z96pB6DkSb7F3Y3BBnJeOZH2gazyMTWlvecSD4vDqfk= +bitbucket.org/creachadair/shell v0.0.7/go.mod h1:oqtXSSvSYr4624lnnabXHaBsYW6RD80caLi2b3hJk0U= 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= @@ -17,179 +11,181 @@ github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSr 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/MarvinJWendt/testza v0.4.3 h1:u2XaM4IqGp9dsdUmML8/Z791fu4yjQYzOiufOtJwTII= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -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/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= -github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= -github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= -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/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 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/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo= +github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= 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/bitfield/script v0.24.0 h1:ic0Tbx+2AgRtkGGIcUyr+Un60vu4WXvqFrCSumf+T7M= -github.com/bitfield/script v0.24.0/go.mod h1:fv+6x4OzVsRs6qAlc7wiGq8fq1b5orhtQdtW0dwjUHI= -github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= -github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= -github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= -github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= -github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= -github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4 h1:6KzMkQeAF56rggw2NZu1L+TH7j9+DM1/2Kmh7KUxg1I= -github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/bitfield/script v0.19.0 h1:W24f+FQuPab9gXcW8bhcbo5qO8AtrXyu3XOnR4zhHN0= +github.com/bitfield/script v0.19.0/go.mod h1:ana6F8YOSZ3ImT8SauIzuYSqXgFVkSUJ6kgja+WMmIY= +github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g= +github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -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/flytam/filenamify v1.2.0 h1:7RiSqXYR4cJftDQ5NuvljKMfd/ubKnW/j9C6iekChgI= -github.com/flytam/filenamify v1.2.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8= -github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= -github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -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/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/flytam/filenamify v1.0.0 h1:ewx6BY2dj7U6h2zGPJmt33q/BjkSf/YsY/woQvnUNIs= +github.com/flytam/filenamify v1.0.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.2.0 h1:GcoouCP9J+5slw2uXAocL70z8ml4A8B/H8nEPt6CLPk= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/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-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -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/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 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/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.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/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= -github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= -github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= -github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= -github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= +github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= +github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo= -github.com/jaypipes/ghw v0.21.3 h1:v5mUHM+RN854Vqmk49Uh213jyUA4+8uqaRajlYESsh8= -github.com/jaypipes/ghw v0.21.3/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE= -github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro= -github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jchv/go-winloader v0.0.0-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/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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/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.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.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 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/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= +github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= +github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= github.com/leaanthony/clir v1.3.0 h1:L9nPDWrmc/qU9UWZZvRaFajWYuO0np9V5p+5gxyYno0= github.com/leaanthony/clir v1.3.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/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4= +github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM= +github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg= +github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= +github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= +github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -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/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= +github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= 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 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 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.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -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-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 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/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/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y= +github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= +github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= +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/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.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= +github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= +github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= @@ -201,153 +197,183 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej 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/pterm/pterm v0.12.49 h1:qeNm0wTWawy6WhKoY8ZKq6qTXFr0s2UtUyRW0yVztEg= +github.com/pterm/pterm v0.12.49/go.mod h1:D4OBoWNqAfXkm5QLTjIgjNiMXPHemLJHnIreGUsWzWg= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 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/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +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 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/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.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/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.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/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +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/tc-hib/winres v0.1.5 h1:2dA5yfjdoEA3UyRaOC92HNMt3jap66pLzoW4MjpC/0M= +github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= +github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= +github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= +github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= -github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/tidwall/sjson v1.1.7 h1:sgVPwu/yygHJ2m1pJDLgGM/h+1F5odx5Q9ljG3imRm8= +github.com/tidwall/sjson v1.1.7/go.mod h1:w/yG+ezBeTdUxiKs5NcPicO9diP38nk96QBAbIIGeFs= +github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ= +github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE= +github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= -github.com/wailsapp/go-webview2 v1.0.22/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/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c= github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28= github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY= github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20= -github.com/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/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= 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.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= -github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= -github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 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.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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +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 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= -golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= +golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 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-20210614182718-04defd469f4e/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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/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-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 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 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -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= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw= -howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk= -mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= -mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= diff --git a/v2/internal/app/app.go b/v2/internal/app/app.go index 0cd6bf614..f2821aaba 100644 --- a/v2/internal/app/app.go +++ b/v2/internal/app/app.go @@ -2,7 +2,6 @@ package app import ( "context" - "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/menumanager" @@ -21,9 +20,6 @@ type App struct { // Indicates if the app is in debug mode debug bool - // Indicates if the devtools is enabled - devtoolsEnabled bool - // OnStartup/OnShutdown startupCallback func(ctx context.Context) shutdownCallback func(ctx context.Context) diff --git a/v2/internal/app/app_bindings.go b/v2/internal/app/app_bindings.go index be031819c..d079790aa 100644 --- a/v2/internal/app/app_bindings.go +++ b/v2/internal/app/app_bindings.go @@ -31,7 +31,6 @@ func (a *App) Run() error { var tsPrefixFlag *string var tsPostfixFlag *string - var tsOutputTypeFlag *string tsPrefix := os.Getenv("tsprefix") if tsPrefix == "" { @@ -43,11 +42,6 @@ func (a *App) Run() error { tsPostfixFlag = bindingFlags.String("tssuffix", "", "Suffix for generated typescript entities") } - tsOutputType := os.Getenv("tsoutputtype") - if tsOutputType == "" { - tsOutputTypeFlag = bindingFlags.String("tsoutputtype", "", "Output type for generated typescript entities (classes|interfaces)") - } - _ = bindingFlags.Parse(os.Args[1:]) if tsPrefixFlag != nil { tsPrefix = *tsPrefixFlag @@ -55,15 +49,11 @@ func (a *App) Run() error { if tsPostfixFlag != nil { tsSuffix = *tsPostfixFlag } - if tsOutputTypeFlag != nil { - tsOutputType = *tsOutputTypeFlag - } - appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated(), a.options.EnumBind) + appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated()) appBindings.SetTsPrefix(tsPrefix) appBindings.SetTsSuffix(tsSuffix) - appBindings.SetOutputType(tsOutputType) err := generateBindings(appBindings) if err != nil { diff --git a/v2/internal/app/app_dev.go b/v2/internal/app/app_dev.go index 6de845f96..38aada698 100644 --- a/v2/internal/app/app_dev.go +++ b/v2/internal/app/app_dev.go @@ -42,16 +42,9 @@ func (a *App) Run() error { func CreateApp(appoptions *options.App) (*App, error) { var err error - ctx := context.Background() - ctx = context.WithValue(ctx, "debug", true) - ctx = context.WithValue(ctx, "devtoolsEnabled", true) - - // Set up logger if the appoptions.LogLevel is an invalid value, set it to the default log level - appoptions.LogLevel, err = pkglogger.StringToLogLevel(appoptions.LogLevel.String()) - if err != nil { - return nil, err - } + ctx := context.WithValue(context.Background(), "debug", true) + // Set up logger myLogger := logger.New(appoptions.Logger) myLogger.SetLogLevel(appoptions.LogLevel) @@ -79,11 +72,9 @@ func CreateApp(appoptions *options.App) (*App, error) { } loglevel := os.Getenv("loglevel") - appLogLevel := appoptions.LogLevel.String() - if loglevel != "" { - appLogLevel = loglevel + if loglevel == "" { + loglevelFlag = devFlags.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error") } - loglevelFlag = devFlags.String("loglevel", appLogLevel, "Loglevel to use - Trace, Debug, Info, Warning, Error") // If we weren't given the assetdir in the environment variables if assetdir == "" { @@ -181,10 +172,7 @@ func CreateApp(appoptions *options.App) (*App, error) { if err != nil { return nil, err } - // Only set the log level if it's different from the appoptions.LogLevel - if level != appoptions.LogLevel { - myLogger.SetLogLevel(level) - } + myLogger.SetLogLevel(level) } // Attach logger to context @@ -219,11 +207,11 @@ func CreateApp(appoptions *options.App) (*App, error) { appoptions.OnDomReady, appoptions.OnBeforeClose, } - appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false, appoptions.EnumBind) + appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false) eventHandler := runtime.NewEvents(myLogger) ctx = context.WithValue(ctx, "events", eventHandler) - messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery) + messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler) // Create the frontends and register to event handler desktopFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher) @@ -240,7 +228,6 @@ func CreateApp(appoptions *options.App) (*App, error) { startupCallback: appoptions.OnStartup, shutdownCallback: appoptions.OnShutdown, debug: true, - devtoolsEnabled: true, } result.options = appoptions diff --git a/v2/internal/app/app_devtools.go b/v2/internal/app/app_devtools.go deleted file mode 100644 index 60b221094..000000000 --- a/v2/internal/app/app_devtools.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build devtools - -package app - -// Note: devtools flag is also added in debug builds -func IsDevtoolsEnabled() bool { - return true -} diff --git a/v2/internal/app/app_devtools_not.go b/v2/internal/app/app_devtools_not.go deleted file mode 100644 index 912672048..000000000 --- a/v2/internal/app/app_devtools_not.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !devtools - -package app - -// IsDevtoolsEnabled returns true if devtools should be enabled -// Note: devtools flag is also added in debug builds -func IsDevtoolsEnabled() bool { - return false -} diff --git a/v2/internal/app/app_production.go b/v2/internal/app/app_production.go index 9eb0e5a66..afb67bdb3 100644 --- a/v2/internal/app/app_production.go +++ b/v2/internal/app/app_production.go @@ -34,9 +34,7 @@ func CreateApp(appoptions *options.App) (*App, error) { options.MergeDefaults(appoptions) debug := IsDebug() - devtoolsEnabled := IsDevtoolsEnabled() ctx = context.WithValue(ctx, "debug", debug) - ctx = context.WithValue(ctx, "devtoolsEnabled", devtoolsEnabled) // Set up logger myLogger := logger.New(appoptions.Logger) @@ -72,7 +70,7 @@ func CreateApp(appoptions *options.App) (*App, error) { appoptions.OnDomReady, appoptions.OnBeforeClose, } - appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated(), appoptions.EnumBind) + appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated()) eventHandler := runtime.NewEvents(myLogger) ctx = context.WithValue(ctx, "events", eventHandler) // Attach logger to context @@ -82,7 +80,7 @@ func CreateApp(appoptions *options.App) (*App, error) { ctx = context.WithValue(ctx, "buildtype", "production") } - messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery) + messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler) appFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher) eventHandler.AddFrontend(appFrontend) @@ -95,7 +93,6 @@ func CreateApp(appoptions *options.App) (*App, error) { startupCallback: appoptions.OnStartup, shutdownCallback: appoptions.OnShutdown, debug: debug, - devtoolsEnabled: devtoolsEnabled, options: appoptions, } diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go old mode 100644 new mode 100755 index b7bf07ae0..75b821f29 --- a/v2/internal/binding/binding.go +++ b/v2/internal/binding/binding.go @@ -14,7 +14,6 @@ import ( "github.com/wailsapp/wails/v2/internal/typescriptify" "github.com/leaanthony/slicer" - "github.com/wailsapp/wails/v2/internal/logger" ) @@ -24,20 +23,17 @@ type Bindings struct { exemptions slicer.StringSlicer structsToGenerateTS map[string]map[string]interface{} - enumsToGenerateTS map[string]map[string]interface{} tsPrefix string tsSuffix string - tsInterface bool obfuscate bool } // NewBindings returns a new Bindings object -func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool, enumsToBind []interface{}) *Bindings { +func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool) *Bindings { result := &Bindings{ db: newDB(), logger: logger.CustomLogger("Bindings"), structsToGenerateTS: make(map[string]map[string]interface{}), - enumsToGenerateTS: make(map[string]map[string]interface{}), obfuscate: obfuscate, } @@ -51,10 +47,6 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exem result.exemptions.Add(name) } - for _, enum := range enumsToBind { - result.AddEnumToGenerateTS(enum) - } - // Add the structs to bind for _, ptr := range structPointersToBind { err := result.Add(ptr) @@ -68,13 +60,20 @@ func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exem // Add the given struct methods to the Bindings func (b *Bindings) Add(structPtr interface{}) error { + methods, err := b.getMethods(structPtr) if err != nil { return fmt.Errorf("cannot bind value to app: %s", err.Error()) } for _, method := range methods { - b.db.AddMethod(method.Path.Package, method.Path.Struct, method.Path.Name, method) + splitName := strings.Split(method.Name, ".") + packageName := splitName[0] + structName := splitName[1] + methodName := splitName[2] + + // Add it as a regular method + b.db.AddMethod(packageName, structName, methodName, method) } return nil } @@ -90,21 +89,16 @@ func (b *Bindings) ToJSON() (string, error) { func (b *Bindings) GenerateModels() ([]byte, error) { models := map[string]string{} var seen slicer.StringSlicer - var seenEnumsPackages slicer.StringSlicer allStructNames := b.getAllStructNames() allStructNames.Sort() - allEnumNames := b.getAllEnumNames() - allEnumNames.Sort() for packageName, structsToGenerate := range b.structsToGenerateTS { thisPackageCode := "" w := typescriptify.New() w.WithPrefix(b.tsPrefix) w.WithSuffix(b.tsSuffix) - w.WithInterface(b.tsInterface) w.Namespace = packageName w.WithBackupDir("") w.KnownStructs = allStructNames - w.KnownEnums = allEnumNames // sort the structs var structNames []string for structName := range structsToGenerate { @@ -119,28 +113,6 @@ func (b *Bindings) GenerateModels() ([]byte, error) { structInterface := structsToGenerate[structName] w.Add(structInterface) } - - // if we have enums for this package, add them as well - var enums, enumsExist = b.enumsToGenerateTS[packageName] - if enumsExist { - // Sort the enum names first to make the output deterministic - sortedEnumNames := make([]string, 0, len(enums)) - for enumName := range enums { - sortedEnumNames = append(sortedEnumNames, enumName) - } - sort.Strings(sortedEnumNames) - - for _, enumName := range sortedEnumNames { - enum := enums[enumName] - fqemumname := packageName + "." + enumName - if seen.Contains(fqemumname) { - continue - } - w.AddEnum(enum) - } - seenEnumsPackages.Add(packageName) - } - str, err := w.Convert(nil) if err != nil { return nil, err @@ -150,37 +122,8 @@ func (b *Bindings) GenerateModels() ([]byte, error) { models[packageName] = thisPackageCode } - // Add outstanding enums to the models that were not in packages with structs - for packageName, enumsToGenerate := range b.enumsToGenerateTS { - if seenEnumsPackages.Contains(packageName) { - continue - } - - thisPackageCode := "" - w := typescriptify.New() - w.WithPrefix(b.tsPrefix) - w.WithSuffix(b.tsSuffix) - w.WithInterface(b.tsInterface) - w.Namespace = packageName - w.WithBackupDir("") - - for enumName, enum := range enumsToGenerate { - fqemumname := packageName + "." + enumName - if seen.Contains(fqemumname) { - continue - } - w.AddEnum(enum) - } - str, err := w.Convert(nil) - if err != nil { - return nil, err - } - thisPackageCode += str - models[packageName] = thisPackageCode - } - // Sort the package names first to make the output deterministic - sortedPackageNames := make([]string, 0, len(models)) + sortedPackageNames := make([]string, 0) for packageName := range models { sortedPackageNames = append(sortedPackageNames, packageName) } @@ -203,6 +146,7 @@ func (b *Bindings) GenerateModels() ([]byte, error) { } func (b *Bindings) WriteModels(modelsDir string) error { + modelsData, err := b.GenerateModels() if err != nil { return err @@ -213,7 +157,7 @@ func (b *Bindings) WriteModels(modelsDir string) error { } filename := filepath.Join(modelsDir, "models.ts") - err = os.WriteFile(filename, modelsData, 0o755) + err = os.WriteFile(filename, modelsData, 0755) if err != nil { return err } @@ -221,39 +165,6 @@ func (b *Bindings) WriteModels(modelsDir string) error { return nil } -func (b *Bindings) AddEnumToGenerateTS(e interface{}) { - enumType := reflect.TypeOf(e) - - var packageName string - var enumName string - // enums should be represented as array of all possible values - if hasElements(enumType) { - enum := enumType.Elem() - // simple enum represented by struct with Value/TSName fields - if enum.Kind() == reflect.Struct { - _, tsNamePresented := enum.FieldByName("TSName") - enumT, valuePresented := enum.FieldByName("Value") - if tsNamePresented && valuePresented { - packageName = getPackageName(enumT.Type.String()) - enumName = enumT.Type.Name() - } else { - return - } - // otherwise expecting implementation with TSName() https://github.com/tkrajina/typescriptify-golang-structs#enums-with-tsname - } else { - packageName = getPackageName(enumType.Elem().String()) - enumName = enumType.Elem().Name() - } - if b.enumsToGenerateTS[packageName] == nil { - b.enumsToGenerateTS[packageName] = make(map[string]interface{}) - } - if b.enumsToGenerateTS[packageName][enumName] != nil { - return - } - b.enumsToGenerateTS[packageName][enumName] = e - } -} - func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, s interface{}) { if b.structsToGenerateTS[packageName] == nil { b.structsToGenerateTS[packageName] = make(map[string]interface{}) @@ -265,19 +176,22 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, // Iterate this struct and add any struct field references structType := reflect.TypeOf(s) - for hasElements(structType) { + if hasElements(structType) { structType = structType.Elem() } for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) - if field.Anonymous || !field.IsExported() { + if field.Anonymous { continue } kind := field.Type.Kind() if kind == reflect.Struct { + if !field.IsExported() { + continue + } fqname := field.Type.String() - sNameSplit := strings.SplitN(fqname, ".", 2) + sNameSplit := strings.Split(fqname, ".") if len(sNameSplit) < 2 { continue } @@ -288,24 +202,22 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string, s := reflect.Indirect(a).Interface() b.AddStructToGenerateTS(pName, sName, s) } - } else { - fType := field.Type - for hasElements(fType) { - fType = fType.Elem() + } else if hasElements(field.Type) && field.Type.Elem().Kind() == reflect.Struct { + if !field.IsExported() { + continue } - if fType.Kind() == reflect.Struct { - fqname := fType.String() - sNameSplit := strings.SplitN(fqname, ".", 2) - if len(sNameSplit) < 2 { - continue - } - sName := sNameSplit[1] - pName := getPackageName(fqname) - a := reflect.New(fType) - if b.hasExportedJSONFields(fType) { - s := reflect.Indirect(a).Interface() - b.AddStructToGenerateTS(pName, sName, s) - } + fqname := field.Type.Elem().String() + sNameSplit := strings.Split(fqname, ".") + if len(sNameSplit) < 2 { + continue + } + sName := sNameSplit[1] + pName := getPackageName(fqname) + typ := field.Type.Elem() + a := reflect.New(typ) + if b.hasExportedJSONFields(typ) { + s := reflect.Indirect(a).Interface() + b.AddStructToGenerateTS(pName, sName, s) } } } @@ -321,13 +233,6 @@ func (b *Bindings) SetTsSuffix(postfix string) *Bindings { return b } -func (b *Bindings) SetOutputType(outputType string) *Bindings { - if outputType == "interfaces" { - b.tsInterface = true - } - return b -} - func (b *Bindings) getAllStructNames() *slicer.StringSlicer { var result slicer.StringSlicer for packageName, structsToGenerate := range b.structsToGenerateTS { @@ -338,32 +243,11 @@ func (b *Bindings) getAllStructNames() *slicer.StringSlicer { return &result } -func (b *Bindings) getAllEnumNames() *slicer.StringSlicer { - var result slicer.StringSlicer - for packageName, enumsToGenerate := range b.enumsToGenerateTS { - for enumName := range enumsToGenerate { - result.Add(packageName + "." + enumName) - } - } - return &result -} - func (b *Bindings) hasExportedJSONFields(typeOf reflect.Type) bool { for i := 0; i < typeOf.NumField(); i++ { jsonFieldName := "" f := typeOf.Field(i) - // function, complex, and channel types cannot be json-encoded - if f.Type.Kind() == reflect.Chan || - f.Type.Kind() == reflect.Func || - f.Type.Kind() == reflect.UnsafePointer || - f.Type.Kind() == reflect.Complex128 || - f.Type.Kind() == reflect.Complex64 { - continue - } - jsonTag, hasTag := f.Tag.Lookup("json") - if !hasTag && f.IsExported() { - return true - } + jsonTag := f.Tag.Get("json") if len(jsonTag) == 0 { continue } diff --git a/v2/internal/binding/binding_test/binding_anonymous_sub_struct_multi_level_test.go b/v2/internal/binding/binding_test/binding_anonymous_sub_struct_multi_level_test.go index 29777481b..3c888ab27 100644 --- a/v2/internal/binding/binding_test/binding_anonymous_sub_struct_multi_level_test.go +++ b/v2/internal/binding/binding_test/binding_anonymous_sub_struct_multi_level_test.go @@ -45,7 +45,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_anonymous_sub_struct_test.go b/v2/internal/binding/binding_test/binding_anonymous_sub_struct_test.go index 11afe4f0d..53617efac 100644 --- a/v2/internal/binding/binding_test/binding_anonymous_sub_struct_test.go +++ b/v2/internal/binding/binding_test/binding_anonymous_sub_struct_test.go @@ -39,7 +39,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go index b37334ec3..2309d6daf 100644 --- a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go +++ b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go @@ -42,7 +42,7 @@ func TestConflictingPackageName(t *testing.T) { // setup testLogger := &logger.Logger{} - b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false, []interface{}{}) + b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false) // then err := b.GenerateGoBindings(generationDir) diff --git a/v2/internal/binding/binding_test/binding_deepelements_test.go b/v2/internal/binding/binding_test/binding_deepelements_test.go deleted file mode 100644 index 034687474..000000000 --- a/v2/internal/binding/binding_test/binding_deepelements_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package binding_test - -// Issues 2303, 3442, 3709 - -type DeepMessage struct { - Msg string -} - -type DeepElements struct { - Single []int - Double [][]string - FourDouble [4][]float64 - DoubleFour [][4]int64 - Triple [][][]int - - SingleMap map[string]int - SliceMap map[string][]int - DoubleSliceMap map[string][][]int - - ArrayMap map[string][4]int - DoubleArrayMap1 map[string][4][]int - DoubleArrayMap2 map[string][][4]int - DoubleArrayMap3 map[string][4][4]int - - OneStructs []*DeepMessage - TwoStructs [3][]*DeepMessage - ThreeStructs [][][]DeepMessage - MapStructs map[string][]*DeepMessage - MapTwoStructs map[string][4][]DeepMessage - MapThreeStructs map[string][][7][]*DeepMessage -} - -func (x DeepElements) Get() DeepElements { - return x -} - -var DeepElementsTest = BindingTest{ - name: "DeepElements", - structs: []interface{}{ - &DeepElements{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - - export class DeepMessage { - Msg: string; - - static createFrom(source: any = {}) { - return new DeepMessage(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Msg = source["Msg"]; - } - } - export class DeepElements { - Single: number[]; - Double: string[][]; - FourDouble: number[][]; - DoubleFour: number[][]; - Triple: number[][][]; - SingleMap: Record; - SliceMap: Record>; - DoubleSliceMap: Record>>; - ArrayMap: Record>; - DoubleArrayMap1: Record>>; - DoubleArrayMap2: Record>>; - DoubleArrayMap3: Record>>; - OneStructs: DeepMessage[]; - TwoStructs: DeepMessage[][]; - ThreeStructs: DeepMessage[][][]; - MapStructs: Record>; - MapTwoStructs: Record>>; - MapThreeStructs: Record>>>; - - static createFrom(source: any = {}) { - return new DeepElements(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Single = source["Single"]; - this.Double = source["Double"]; - this.FourDouble = source["FourDouble"]; - this.DoubleFour = source["DoubleFour"]; - this.Triple = source["Triple"]; - this.SingleMap = source["SingleMap"]; - this.SliceMap = source["SliceMap"]; - this.DoubleSliceMap = source["DoubleSliceMap"]; - this.ArrayMap = source["ArrayMap"]; - this.DoubleArrayMap1 = source["DoubleArrayMap1"]; - this.DoubleArrayMap2 = source["DoubleArrayMap2"]; - this.DoubleArrayMap3 = source["DoubleArrayMap3"]; - this.OneStructs = this.convertValues(source["OneStructs"], DeepMessage); - this.TwoStructs = this.convertValues(source["TwoStructs"], DeepMessage); - this.ThreeStructs = this.convertValues(source["ThreeStructs"], DeepMessage); - this.MapStructs = this.convertValues(source["MapStructs"], Array, true); - this.MapTwoStructs = this.convertValues(source["MapTwoStructs"], Array>, true); - this.MapThreeStructs = this.convertValues(source["MapThreeStructs"], Array>>, true); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - } -`, -} diff --git a/v2/internal/binding/binding_test/binding_emptystruct_test.go b/v2/internal/binding/binding_test/binding_emptystruct_test.go index ffb85e865..c36603e64 100644 --- a/v2/internal/binding/binding_test/binding_emptystruct_test.go +++ b/v2/internal/binding/binding_test/binding_emptystruct_test.go @@ -34,7 +34,7 @@ export namespace binding_test { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_enum_ordering_test.go b/v2/internal/binding/binding_test/binding_enum_ordering_test.go deleted file mode 100644 index 0939535ec..000000000 --- a/v2/internal/binding/binding_test/binding_enum_ordering_test.go +++ /dev/null @@ -1,271 +0,0 @@ -package binding_test - -// Test for PR #4664: Fix generated enums ordering -// This test ensures that enum output is deterministic regardless of map iteration order - -// ZFirstEnum - named with Z prefix to test alphabetical sorting -type ZFirstEnum int - -const ( - ZFirstEnumValue1 ZFirstEnum = iota - ZFirstEnumValue2 -) - -var AllZFirstEnumValues = []struct { - Value ZFirstEnum - TSName string -}{ - {ZFirstEnumValue1, "ZValue1"}, - {ZFirstEnumValue2, "ZValue2"}, -} - -// ASecondEnum - named with A prefix to test alphabetical sorting -type ASecondEnum int - -const ( - ASecondEnumValue1 ASecondEnum = iota - ASecondEnumValue2 -) - -var AllASecondEnumValues = []struct { - Value ASecondEnum - TSName string -}{ - {ASecondEnumValue1, "AValue1"}, - {ASecondEnumValue2, "AValue2"}, -} - -// MMiddleEnum - named with M prefix to test alphabetical sorting -type MMiddleEnum int - -const ( - MMiddleEnumValue1 MMiddleEnum = iota - MMiddleEnumValue2 -) - -var AllMMiddleEnumValues = []struct { - Value MMiddleEnum - TSName string -}{ - {MMiddleEnumValue1, "MValue1"}, - {MMiddleEnumValue2, "MValue2"}, -} - -type EntityWithMultipleEnums struct { - Name string `json:"name"` - EnumZ ZFirstEnum `json:"enumZ"` - EnumA ASecondEnum `json:"enumA"` - EnumM MMiddleEnum `json:"enumM"` -} - -func (e EntityWithMultipleEnums) Get() EntityWithMultipleEnums { - return e -} - -// EnumOrderingTest tests that multiple enums in the same package are output -// in alphabetical order by enum name. Before PR #4664, the order was -// non-deterministic due to Go map iteration order. -var EnumOrderingTest = BindingTest{ - name: "EnumOrderingTest", - structs: []interface{}{ - &EntityWithMultipleEnums{}, - }, - enums: []interface{}{ - // Intentionally add enums in non-alphabetical order - AllZFirstEnumValues, - AllASecondEnumValues, - AllMMiddleEnumValues, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "", - TsSuffix: "", - }, - // Expected output should have enums in alphabetical order: ASecondEnum, MMiddleEnum, ZFirstEnum - want: `export namespace binding_test { - - export enum ASecondEnum { - AValue1 = 0, - AValue2 = 1, - } - export enum MMiddleEnum { - MValue1 = 0, - MValue2 = 1, - } - export enum ZFirstEnum { - ZValue1 = 0, - ZValue2 = 1, - } - export class EntityWithMultipleEnums { - name: string; - enumZ: ZFirstEnum; - enumA: ASecondEnum; - enumM: MMiddleEnum; - - static createFrom(source: any = {}) { - return new EntityWithMultipleEnums(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.enumZ = source["enumZ"]; - this.enumA = source["enumA"]; - this.enumM = source["enumM"]; - } - } - -} -`, -} - -// EnumElementOrderingEnum tests sorting of enum elements by TSName -type EnumElementOrderingEnum string - -const ( - EnumElementZ EnumElementOrderingEnum = "z_value" - EnumElementA EnumElementOrderingEnum = "a_value" - EnumElementM EnumElementOrderingEnum = "m_value" -) - -// AllEnumElementOrderingValues intentionally lists values out of alphabetical order -// to test that AddEnum sorts them -var AllEnumElementOrderingValues = []struct { - Value EnumElementOrderingEnum - TSName string -}{ - {EnumElementZ, "Zebra"}, - {EnumElementA, "Apple"}, - {EnumElementM, "Mango"}, -} - -type EntityWithUnorderedEnumElements struct { - Name string `json:"name"` - Enum EnumElementOrderingEnum `json:"enum"` -} - -func (e EntityWithUnorderedEnumElements) Get() EntityWithUnorderedEnumElements { - return e -} - -// EnumElementOrderingTest tests that enum elements are sorted alphabetically -// by their TSName within an enum. Before PR #4664, elements appeared in the -// order they were added, which could be arbitrary. -var EnumElementOrderingTest = BindingTest{ - name: "EnumElementOrderingTest", - structs: []interface{}{ - &EntityWithUnorderedEnumElements{}, - }, - enums: []interface{}{ - AllEnumElementOrderingValues, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "", - TsSuffix: "", - }, - // Expected output should have enum elements sorted: Apple, Mango, Zebra - want: `export namespace binding_test { - - export enum EnumElementOrderingEnum { - Apple = "a_value", - Mango = "m_value", - Zebra = "z_value", - } - export class EntityWithUnorderedEnumElements { - name: string; - enum: EnumElementOrderingEnum; - - static createFrom(source: any = {}) { - return new EntityWithUnorderedEnumElements(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.enum = source["enum"]; - } - } - -} -`, -} - -// TSNameEnumElementOrdering tests sorting with TSName() method enum -type TSNameEnumElementOrdering string - -const ( - TSNameEnumZ TSNameEnumElementOrdering = "z_value" - TSNameEnumA TSNameEnumElementOrdering = "a_value" - TSNameEnumM TSNameEnumElementOrdering = "m_value" -) - -func (v TSNameEnumElementOrdering) TSName() string { - switch v { - case TSNameEnumZ: - return "Zebra" - case TSNameEnumA: - return "Apple" - case TSNameEnumM: - return "Mango" - default: - return "Unknown" - } -} - -// AllTSNameEnumValues intentionally out of order -var AllTSNameEnumValues = []TSNameEnumElementOrdering{TSNameEnumZ, TSNameEnumA, TSNameEnumM} - -type EntityWithTSNameEnumOrdering struct { - Name string `json:"name"` - Enum TSNameEnumElementOrdering `json:"enum"` -} - -func (e EntityWithTSNameEnumOrdering) Get() EntityWithTSNameEnumOrdering { - return e -} - -// TSNameEnumElementOrderingTest tests that enums using TSName() method -// also have their elements sorted alphabetically by the TSName. -var TSNameEnumElementOrderingTest = BindingTest{ - name: "TSNameEnumElementOrderingTest", - structs: []interface{}{ - &EntityWithTSNameEnumOrdering{}, - }, - enums: []interface{}{ - AllTSNameEnumValues, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "", - TsSuffix: "", - }, - // Expected output should have enum elements sorted: Apple, Mango, Zebra - want: `export namespace binding_test { - - export enum TSNameEnumElementOrdering { - Apple = "a_value", - Mango = "m_value", - Zebra = "z_value", - } - export class EntityWithTSNameEnumOrdering { - name: string; - enum: TSNameEnumElementOrdering; - - static createFrom(source: any = {}) { - return new EntityWithTSNameEnumOrdering(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.enum = source["enum"]; - } - } - -} -`, -} diff --git a/v2/internal/binding/binding_test/binding_generics_test.go b/v2/internal/binding/binding_test/binding_generics_test.go deleted file mode 100644 index 920bd2a7a..000000000 --- a/v2/internal/binding/binding_test/binding_generics_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package binding_test - -import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/float_package" - -// Issues 3900, 3371, 2323 (no TS generics though) - -type ListData[T interface{}] struct { - Total int64 `json:"Total"` - TotalPage int64 `json:"TotalPage"` - PageNum int `json:"PageNum"` - List []T `json:"List,omitempty"` -} - -func (x ListData[T]) Get() ListData[T] { - return x -} - -var Generics1Test = BindingTest{ - name: "Generics1", - structs: []interface{}{ - &ListData[string]{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - - export class ListData_string_ { - Total: number; - TotalPage: number; - PageNum: number; - List?: string[]; - - static createFrom(source: any = {}) { - return new ListData_string_(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Total = source["Total"]; - this.TotalPage = source["TotalPage"]; - this.PageNum = source["PageNum"]; - this.List = source["List"]; - } - } - - } -`, -} - -var Generics2Test = BindingTest{ - name: "Generics2", - structs: []interface{}{ - &ListData[float_package.SomeStruct]{}, - &ListData[*float_package.SomeStruct]{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - - export class ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_ { - Total: number; - TotalPage: number; - PageNum: number; - List?: float_package.SomeStruct[]; - - static createFrom(source: any = {}) { - return new ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Total = source["Total"]; - this.TotalPage = source["TotalPage"]; - this.PageNum = source["PageNum"]; - this.List = this.convertValues(source["List"], float_package.SomeStruct); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_ { - Total: number; - TotalPage: number; - PageNum: number; - List?: float_package.SomeStruct[]; - - static createFrom(source: any = {}) { - return new ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Total = source["Total"]; - this.TotalPage = source["TotalPage"]; - this.PageNum = source["PageNum"]; - this.List = this.convertValues(source["List"], float_package.SomeStruct); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - } - - export namespace float_package { - - export class SomeStruct { - string: string; - - static createFrom(source: any = {}) { - return new SomeStruct(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.string = source["string"]; - } - } - - } -`, -} diff --git a/v2/internal/binding/binding_test/binding_ignored_test.go b/v2/internal/binding/binding_test/binding_ignored_test.go deleted file mode 100644 index aeb6a9c3f..000000000 --- a/v2/internal/binding/binding_test/binding_ignored_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package binding_test - -import ( - "unsafe" -) - -// Issues 3755, 3809 - -type Ignored struct { - Valid bool - Total func() int `json:"Total"` - UnsafeP unsafe.Pointer - Complex64 complex64 `json:"Complex"` - Complex128 complex128 - StringChan chan string -} - -func (x Ignored) Get() Ignored { - return x -} - -var IgnoredTest = BindingTest{ - name: "Ignored", - structs: []interface{}{ - &Ignored{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - - export class Ignored { - Valid: boolean; - - static createFrom(source: any = {}) { - return new Ignored(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Valid = source["Valid"]; - } - } - - } -`, -} diff --git a/v2/internal/binding/binding_test/binding_importedenum_test.go b/v2/internal/binding/binding_test/binding_importedenum_test.go deleted file mode 100644 index 5b5b4419e..000000000 --- a/v2/internal/binding/binding_test/binding_importedenum_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package binding_test - -import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import" - -type ImportedEnumStruct struct { - EnumValue binding_test_import.ImportedEnum `json:"EnumValue"` -} - -func (s ImportedEnumStruct) Get() ImportedEnumStruct { - return s -} - -var ImportedEnumTest = BindingTest{ - name: "ImportedEnum", - structs: []interface{}{ - &ImportedEnumStruct{}, - }, - enums: []interface{}{ - binding_test_import.AllImportedEnumValues, - }, - exemptions: nil, - shouldError: false, - want: `export namespace binding_test { - - export class ImportedEnumStruct { - EnumValue: binding_test_import.ImportedEnum; - - static createFrom(source: any = {}) { - return new ImportedEnumStruct(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.EnumValue = source["EnumValue"]; - } - } - - } - - export namespace binding_test_import { - - export enum ImportedEnum { - Value1 = "value1", - Value2 = "value2", - Value3 = "value3", - } - - } -`, -} diff --git a/v2/internal/binding/binding_test/binding_importedmap_test.go b/v2/internal/binding/binding_test/binding_importedmap_test.go index 4a4b2996c..54fb261a8 100644 --- a/v2/internal/binding/binding_test/binding_importedmap_test.go +++ b/v2/internal/binding/binding_test/binding_importedmap_test.go @@ -32,7 +32,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -50,7 +50,7 @@ export namespace binding_test { export namespace binding_test_import { export class AMapWrapper { - AMap: Record; + AMap: {[key: string]: binding_test_nestedimport.A}; static createFrom(source: any = {}) { return new AMapWrapper(source); } @@ -62,7 +62,7 @@ export namespace binding_test_import { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_importedslice_test.go b/v2/internal/binding/binding_test/binding_importedslice_test.go index 5abf55b43..b4a63689c 100644 --- a/v2/internal/binding/binding_test/binding_importedslice_test.go +++ b/v2/internal/binding/binding_test/binding_importedslice_test.go @@ -32,7 +32,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -62,7 +62,7 @@ export namespace binding_test_import { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_importedstruct_test.go b/v2/internal/binding/binding_test/binding_importedstruct_test.go index 1e94453c2..1629be9fa 100644 --- a/v2/internal/binding/binding_test/binding_importedstruct_test.go +++ b/v2/internal/binding/binding_test/binding_importedstruct_test.go @@ -33,7 +33,7 @@ export namespace binding_test { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -63,7 +63,7 @@ export namespace binding_test_import { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_nestedfield_test.go b/v2/internal/binding/binding_test/binding_nestedfield_test.go index 66dd11cbf..c2e4fcf9f 100644 --- a/v2/internal/binding/binding_test/binding_nestedfield_test.go +++ b/v2/internal/binding/binding_test/binding_nestedfield_test.go @@ -44,7 +44,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { diff --git a/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go b/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go index 9efee710f..37a61dd29 100644 --- a/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go +++ b/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go @@ -18,7 +18,7 @@ var NonStringMapKeyTest = BindingTest{ want: ` export namespace binding_test { export class NonStringMapKey { - numberMap: Record; + numberMap: {[key: number]: any}; static createFrom(source: any = {}) { return new NonStringMapKey(source); } diff --git a/v2/internal/binding/binding_test/binding_notags_test.go b/v2/internal/binding/binding_test/binding_notags_test.go deleted file mode 100644 index d4d9997e0..000000000 --- a/v2/internal/binding/binding_test/binding_notags_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package binding_test - -type NoFieldTags struct { - Name string - Address string - Zip *string - Spouse *NoFieldTags - NoFunc func() string -} - -func (n NoFieldTags) Get() NoFieldTags { - return n -} - -var NoFieldTagsTest = BindingTest{ - name: "NoFieldTags", - structs: []interface{}{ - &NoFieldTags{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - export class NoFieldTags { - Name: string; - Address: string; - Zip?: string; - Spouse?: NoFieldTags; - static createFrom(source: any = {}) { - return new NoFieldTags(source); - } - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.Name = source["Name"]; - this.Address = source["Address"]; - this.Zip = source["Zip"]; - this.Spouse = this.convertValues(source["Spouse"], NoFieldTags); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } -} -`, -} diff --git a/v2/internal/binding/binding_test/binding_returned_promises_test.go b/v2/internal/binding/binding_test/binding_returned_promises_test.go index 94941d0a3..837d5fad3 100644 --- a/v2/internal/binding/binding_test/binding_returned_promises_test.go +++ b/v2/internal/binding/binding_test/binding_returned_promises_test.go @@ -59,7 +59,7 @@ func TestPromises(t *testing.T) { // setup testLogger := &logger.Logger{} - b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false, []interface{}{}) + b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false) // then err := b.GenerateGoBindings(generationDir) diff --git a/v2/internal/binding/binding_test/binding_structwithoutfields_test.go b/v2/internal/binding/binding_test/binding_structwithoutfields_test.go deleted file mode 100644 index 4b2289b98..000000000 --- a/v2/internal/binding/binding_test/binding_structwithoutfields_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package binding_test - -type WithoutFields struct { -} - -func (s WithoutFields) Get() WithoutFields { - return s -} - -var WithoutFieldsTest = BindingTest{ - name: "StructWithoutFields", - structs: []interface{}{ - &WithoutFields{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - - export class WithoutFields { - - - static createFrom(source: any = {}) { - return new WithoutFields(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - - } - } - -}`, -} diff --git a/v2/internal/binding/binding_test/binding_test.go b/v2/internal/binding/binding_test/binding_test.go index 41f0618ce..c2e351915 100644 --- a/v2/internal/binding/binding_test/binding_test.go +++ b/v2/internal/binding/binding_test/binding_test.go @@ -13,7 +13,6 @@ import ( type BindingTest struct { name string structs []interface{} - enums []interface{} exemptions []interface{} want string shouldError bool @@ -21,9 +20,8 @@ type BindingTest struct { } type TsGenerationOptionsTest struct { - TsPrefix string - TsSuffix string - TsOutputType string + TsPrefix string + TsSuffix string } func TestBindings_GenerateModels(t *testing.T) { @@ -33,45 +31,28 @@ func TestBindings_GenerateModels(t *testing.T) { ImportedStructTest, ImportedSliceTest, ImportedMapTest, - ImportedEnumTest, NestedFieldTest, NonStringMapKeyTest, SingleFieldTest, MultistructTest, EmptyStructTest, GeneratedJsEntityTest, - GeneratedJsEntityWithIntEnumTest, - GeneratedJsEntityWithStringEnumTest, - GeneratedJsEntityWithEnumTsName, - GeneratedJsEntityWithNestedStructInterfacesTest, AnonymousSubStructTest, AnonymousSubStructMultiLevelTest, GeneratedJsEntityWithNestedStructTest, - EntityWithDiffNamespacesTest, - SpecialCharacterFieldTest, - WithoutFieldsTest, - NoFieldTagsTest, - Generics1Test, - Generics2Test, - IgnoredTest, - DeepElementsTest, - // PR #4664: Enum ordering tests - EnumOrderingTest, - EnumElementOrderingTest, - TSNameEnumElementOrderingTest, + EntityWithDiffNamespaces, } testLogger := &logger.Logger{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false, tt.enums) + b := binding.NewBindings(testLogger, tt.structs, tt.exemptions, false) for _, s := range tt.structs { err := b.Add(s) require.NoError(t, err) } b.SetTsPrefix(tt.TsPrefix) b.SetTsSuffix(tt.TsSuffix) - b.SetOutputType(tt.TsOutputType) got, err := b.GenerateModels() if (err != nil) != tt.shouldError { t.Errorf("GenerateModels() error = %v, shouldError %v", err, tt.shouldError) diff --git a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go b/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go index e7080c694..6b99d43be 100644 --- a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go +++ b/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go @@ -13,20 +13,3 @@ type ASliceWrapper struct { type AMapWrapper struct { AMap map[string]binding_test_nestedimport.A `json:"AMap"` } - -type ImportedEnum string - -const ( - ImportedEnumValue1 ImportedEnum = "value1" - ImportedEnumValue2 ImportedEnum = "value2" - ImportedEnumValue3 ImportedEnum = "value3" -) - -var AllImportedEnumValues = []struct { - Value ImportedEnum - TSName string -}{ - {ImportedEnumValue1, "Value1"}, - {ImportedEnumValue2, "Value2"}, - {ImportedEnumValue3, "Value3"}, -} diff --git a/v2/internal/binding/binding_test/binding_tsgeneration_test.go b/v2/internal/binding/binding_test/binding_tsgeneration_test.go index 850bc778a..d2c5349c5 100644 --- a/v2/internal/binding/binding_test/binding_tsgeneration_test.go +++ b/v2/internal/binding/binding_test/binding_tsgeneration_test.go @@ -107,7 +107,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -140,7 +140,7 @@ type ChildPackageEntity struct { ImportedPackage binding_test_import.AWrapper `json:"importedPackage"` } -var EntityWithDiffNamespacesTest = BindingTest{ +var EntityWithDiffNamespaces = BindingTest{ name: "EntityWithDiffNamespaces ", structs: []interface{}{ &ParentPackageEntity{}, @@ -172,7 +172,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -204,7 +204,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -239,7 +239,7 @@ export namespace binding_test { if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -275,235 +275,3 @@ export namespace binding_test { `, } - -type IntEnum int - -const ( - IntEnumValue1 IntEnum = iota - IntEnumValue2 - IntEnumValue3 -) - -var AllIntEnumValues = []struct { - Value IntEnum - TSName string -}{ - {IntEnumValue1, "Value1"}, - {IntEnumValue2, "Value2"}, - {IntEnumValue3, "Value3"}, -} - -type EntityWithIntEnum struct { - Name string `json:"name"` - Enum IntEnum `json:"enum"` -} - -func (e EntityWithIntEnum) Get() EntityWithIntEnum { - return e -} - -var GeneratedJsEntityWithIntEnumTest = BindingTest{ - name: "GeneratedJsEntityWithIntEnumTest", - structs: []interface{}{ - &EntityWithIntEnum{}, - }, - enums: []interface{}{ - AllIntEnumValues, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "MY_PREFIX_", - TsSuffix: "_MY_SUFFIX", - }, - want: `export namespace binding_test { - - export enum MY_PREFIX_IntEnum_MY_SUFFIX { - Value1 = 0, - Value2 = 1, - Value3 = 2, - } - export class MY_PREFIX_EntityWithIntEnum_MY_SUFFIX { - name: string; - enum: MY_PREFIX_IntEnum_MY_SUFFIX; - - static createFrom(source: any = {}) { - return new MY_PREFIX_EntityWithIntEnum_MY_SUFFIX(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.enum = source["enum"]; - } - } - - } -`, -} - -type StringEnum string - -const ( - StringEnumValue1 StringEnum = "value1" - StringEnumValue2 StringEnum = "value2" - StringEnumValue3 StringEnum = "value3" -) - -var AllStringEnumValues = []struct { - Value StringEnum - TSName string -}{ - {StringEnumValue1, "Value1"}, - {StringEnumValue2, "Value2"}, - {StringEnumValue3, "Value3"}, -} - -type EntityWithStringEnum struct { - Name string `json:"name"` - Enum StringEnum `json:"enum"` -} - -func (e EntityWithStringEnum) Get() EntityWithStringEnum { - return e -} - -var GeneratedJsEntityWithStringEnumTest = BindingTest{ - name: "GeneratedJsEntityWithStringEnumTest", - structs: []interface{}{ - &EntityWithStringEnum{}, - }, - enums: []interface{}{ - AllStringEnumValues, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "MY_PREFIX_", - TsSuffix: "_MY_SUFFIX", - }, - want: `export namespace binding_test { - - export enum MY_PREFIX_StringEnum_MY_SUFFIX { - Value1 = "value1", - Value2 = "value2", - Value3 = "value3", - } - export class MY_PREFIX_EntityWithStringEnum_MY_SUFFIX { - name: string; - enum: MY_PREFIX_StringEnum_MY_SUFFIX; - - static createFrom(source: any = {}) { - return new MY_PREFIX_EntityWithStringEnum_MY_SUFFIX(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.enum = source["enum"]; - } - } - - } -`, -} - -type EnumWithTsName string - -const ( - EnumWithTsName1 EnumWithTsName = "value1" - EnumWithTsName2 EnumWithTsName = "value2" - EnumWithTsName3 EnumWithTsName = "value3" -) - -var AllEnumWithTsNameValues = []EnumWithTsName{EnumWithTsName1, EnumWithTsName2, EnumWithTsName3} - -func (v EnumWithTsName) TSName() string { - switch v { - case EnumWithTsName1: - return "TsName1" - case EnumWithTsName2: - return "TsName2" - case EnumWithTsName3: - return "TsName3" - default: - return "???" - } -} - -type EntityWithEnumTsName struct { - Name string `json:"name"` - Enum EnumWithTsName `json:"enum"` -} - -func (e EntityWithEnumTsName) Get() EntityWithEnumTsName { - return e -} - -var GeneratedJsEntityWithEnumTsName = BindingTest{ - name: "GeneratedJsEntityWithEnumTsName", - structs: []interface{}{ - &EntityWithEnumTsName{}, - }, - enums: []interface{}{ - AllEnumWithTsNameValues, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "MY_PREFIX_", - TsSuffix: "_MY_SUFFIX", - }, - want: `export namespace binding_test { - - export enum MY_PREFIX_EnumWithTsName_MY_SUFFIX { - TsName1 = "value1", - TsName2 = "value2", - TsName3 = "value3", - } - export class MY_PREFIX_EntityWithEnumTsName_MY_SUFFIX { - name: string; - enum: MY_PREFIX_EnumWithTsName_MY_SUFFIX; - - static createFrom(source: any = {}) { - return new MY_PREFIX_EntityWithEnumTsName_MY_SUFFIX(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.enum = source["enum"]; - } - } - - } -`, -} - -var GeneratedJsEntityWithNestedStructInterfacesTest = BindingTest{ - name: "GeneratedJsEntityWithNestedStructInterfacesTest", - structs: []interface{}{ - &ParentEntity{}, - }, - exemptions: nil, - shouldError: false, - TsGenerationOptionsTest: TsGenerationOptionsTest{ - TsPrefix: "MY_PREFIX_", - TsSuffix: "_MY_SUFFIX", - TsOutputType: "interfaces", - }, - want: `export namespace binding_test { - - export interface MY_PREFIX_ChildEntity_MY_SUFFIX { - name: string; - childProp: number; - } - export interface MY_PREFIX_ParentEntity_MY_SUFFIX { - name: string; - ref: MY_PREFIX_ChildEntity_MY_SUFFIX; - parentProp: string; - } - - } -`, -} diff --git a/v2/internal/binding/binding_test/binding_type_alias_test.go b/v2/internal/binding/binding_test/binding_type_alias_test.go index 90b009c5f..8e7c7ca6d 100644 --- a/v2/internal/binding/binding_test/binding_type_alias_test.go +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -15,11 +15,11 @@ const expectedTypeAliasBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEID import {binding_test} from '../models'; import {int_package} from '../models'; -export function Map():Promise>; +export function Map():Promise<{[key: string]: string}>; export function MapAlias():Promise; -export function MapWithImportedStructValue():Promise>; +export function MapWithImportedStructValue():Promise<{[key: string]: int_package.SomeStruct}>; export function Slice():Promise>; @@ -41,7 +41,7 @@ func TestAliases(t *testing.T) { // setup testLogger := &logger.Logger{} - b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false, []interface{}{}) + b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false) // then err := b.GenerateGoBindings(generationDir) diff --git a/v2/internal/binding/binding_test/binding_variablespecialcharacter_test.go b/v2/internal/binding/binding_test/binding_variablespecialcharacter_test.go deleted file mode 100644 index 7dbe72350..000000000 --- a/v2/internal/binding/binding_test/binding_variablespecialcharacter_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package binding_test - -type SpecialCharacterField struct { - ID string `json:"@ID,omitempty"` -} - -func (s SpecialCharacterField) Get() SpecialCharacterField { - return s -} - -var SpecialCharacterFieldTest = BindingTest{ - name: "SpecialCharacterField", - structs: []interface{}{ - &SpecialCharacterField{}, - }, - exemptions: nil, - shouldError: false, - want: ` -export namespace binding_test { - export class SpecialCharacterField { - "@ID"?: string; - static createFrom(source: any = {}) { - return new SpecialCharacterField(source); - } - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this["@ID"] = source["@ID"]; - } - } -} -`, -} diff --git a/v2/internal/binding/boundMethod.go b/v2/internal/binding/boundMethod.go index e697041b0..f6ffdb600 100644 --- a/v2/internal/binding/boundMethod.go +++ b/v2/internal/binding/boundMethod.go @@ -6,24 +6,14 @@ import ( "reflect" ) -type BoundedMethodPath struct { - Package string - Struct string - Name string -} - -func (p *BoundedMethodPath) FullName() string { - return fmt.Sprintf("%s.%s.%s", p.Package, p.Struct, p.Name) -} - // BoundMethod defines all the data related to a Go method that is // bound to the Wails application type BoundMethod struct { - Path *BoundedMethodPath `json:"path"` - Inputs []*Parameter `json:"inputs,omitempty"` - Outputs []*Parameter `json:"outputs,omitempty"` - Comments string `json:"comments,omitempty"` - Method reflect.Value `json:"-"` + Name string `json:"name"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` + Comments string `json:"comments,omitempty"` + Method reflect.Value `json:"-"` } // InputCount returns the number of inputs this bound method has @@ -38,9 +28,10 @@ func (b *BoundMethod) OutputCount() int { // ParseArgs method converts the input json into the types expected by the method func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) { + result := make([]interface{}, b.InputCount()) if len(args) != b.InputCount() { - return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Path.FullName(), b.InputCount()) + return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount()) } for index, arg := range args { typ := b.Inputs[index].reflectType @@ -64,7 +55,7 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) { expectedInputLength := len(b.Inputs) actualInputLength := len(args) if expectedInputLength != actualInputLength { - return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Path.FullName(), expectedInputLength, actualInputLength) + return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength) } /** Convert inputs to reflect values **/ diff --git a/v2/internal/binding/db.go b/v2/internal/binding/db.go index f7b793839..f22798680 100644 --- a/v2/internal/binding/db.go +++ b/v2/internal/binding/db.go @@ -2,6 +2,7 @@ package binding import ( "encoding/json" + "sort" "sync" "unsafe" ) @@ -16,28 +17,24 @@ type DB struct { methodMap map[string]*BoundMethod // This uses ids to reference bound methods at runtime - obfuscatedMethodArray []*ObfuscatedMethod + obfuscatedMethodMap map[int]*BoundMethod // Lock to ensure sync access to the data lock sync.RWMutex } -type ObfuscatedMethod struct { - method *BoundMethod - methodName string -} - func newDB() *DB { return &DB{ - store: make(map[string]map[string]map[string]*BoundMethod), - methodMap: make(map[string]*BoundMethod), - obfuscatedMethodArray: []*ObfuscatedMethod{}, + store: make(map[string]map[string]map[string]*BoundMethod), + methodMap: make(map[string]*BoundMethod), + obfuscatedMethodMap: make(map[int]*BoundMethod), } } // GetMethodFromStore returns the method for the given package/struct/method names // nil is returned if any one of those does not exist func (d *DB) GetMethodFromStore(packageName string, structName string, methodName string) *BoundMethod { + // Lock the db whilst processing and unlock on return d.lock.RLock() defer d.lock.RUnlock() @@ -56,6 +53,7 @@ func (d *DB) GetMethodFromStore(packageName string, structName string, methodNam // GetMethod returns the method for the given qualified method name // qualifiedMethodName is "packagename.structname.methodname" func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod { + // Lock the db whilst processing and unlock on return d.lock.RLock() defer d.lock.RUnlock() @@ -69,15 +67,12 @@ func (d *DB) GetObfuscatedMethod(id int) *BoundMethod { d.lock.RLock() defer d.lock.RUnlock() - if len(d.obfuscatedMethodArray) <= id { - return nil - } - - return d.obfuscatedMethodArray[id].method + return d.obfuscatedMethodMap[id] } // AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) { + // Lock the db whilst processing and unlock on return d.lock.Lock() defer d.lock.Unlock() @@ -104,11 +99,12 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string, // Store in the methodMap key := packageName + "." + structName + "." + methodName d.methodMap[key] = methodDefinition - d.obfuscatedMethodArray = append(d.obfuscatedMethodArray, &ObfuscatedMethod{method: methodDefinition, methodName: key}) + } // ToJSON converts the method map to JSON func (d *DB) ToJSON() (string, error) { + // Lock the db whilst processing and unlock on return d.lock.RLock() defer d.lock.RUnlock() @@ -124,11 +120,20 @@ func (d *DB) ToJSON() (string, error) { // UpdateObfuscatedCallMap sets up the secure call mappings func (d *DB) UpdateObfuscatedCallMap() map[string]int { - mappings := make(map[string]int) - for id, k := range d.obfuscatedMethodArray { - mappings[k.methodName] = id + var mappings = make(map[string]int) + + // Iterate map keys and sort them + keys := make([]string, 0, len(d.methodMap)) + for k := range d.methodMap { + keys = append(keys, k) } + sort.Strings(keys) + // Iterate sorted keys and add to obfuscated method map + for id, k := range keys { + mappings[k] = id + d.obfuscatedMethodMap[id] = d.methodMap[k] + } return mappings } diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index 77edc983d..8416aade1 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -15,14 +15,12 @@ import ( "github.com/leaanthony/slicer" ) -var ( - mapRegex *regexp.Regexp - keyPackageIndex int - keyTypeIndex int - valueArrayIndex int - valuePackageIndex int - valueTypeIndex int -) +var mapRegex *regexp.Regexp +var keyPackageIndex int +var keyTypeIndex int +var valueArrayIndex int +var valuePackageIndex int +var valueTypeIndex int func init() { mapRegex = regexp.MustCompile(`(?:map\[(?:(?P\w+)\.)?(?P\w+)])?(?P\[])?(?:\*?(?P\w+)\.)?(?P.+)`) @@ -83,7 +81,9 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { } else { jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString)) } - jsoutput.WriteString("\n}\n") + jsoutput.WriteString("\n") + jsoutput.WriteString(fmt.Sprintf("}")) + jsoutput.WriteString("\n") // Generate TS tsBody.WriteString(fmt.Sprintf("\nexport function %s(", methodName)) @@ -127,12 +127,12 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error { tsContent.WriteString(tsBody.String()) jsfilename := filepath.Join(packageDir, structName+".js") - err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0o755) + err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0755) if err != nil { return err } tsfilename := filepath.Join(packageDir, structName+".d.ts") - err = os.WriteFile(tsfilename, tsContent.Bytes(), 0o755) + err = os.WriteFile(tsfilename, tsContent.Bytes(), 0755) if err != nil { return err } @@ -171,18 +171,7 @@ func fullyQualifiedName(packageName string, typeName string) string { } } -var ( - jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) -) - func arrayifyValue(valueArray string, valueType string) string { - valueType = strings.ReplaceAll(valueType, "*", "") - gidx := strings.IndexRune(valueType, '[') - if gidx > 0 { // its a generic type - rem := strings.SplitN(valueType, "[", 2) - valueType = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_") - } - if len(valueArray) == 0 { return valueType } @@ -197,7 +186,7 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri valueArray := matches[valueArrayIndex] valuePackage := matches[valuePackageIndex] valueType := matches[valueTypeIndex] - // fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n", + //fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n", // input, // keyPackage, // keyType, @@ -228,7 +217,7 @@ func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) stri } if len(key) > 0 { - return fmt.Sprintf("Record<%s, %s>", key, arrayifyValue(valueArray, value)) + return fmt.Sprintf("{[key: %s]: %s}", key, arrayifyValue(valueArray, value)) } return arrayifyValue(valueArray, value) diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go index 26d7c70df..565fba31c 100644 --- a/v2/internal/binding/generate_test.go +++ b/v2/internal/binding/generate_test.go @@ -25,7 +25,7 @@ type B struct { func TestNestedStruct(t *testing.T) { bind := &BindForTest{} - testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false, []interface{}{}) + testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false) namesStrSlicer := testBindings.getAllStructNames() names := []string{} @@ -116,28 +116,18 @@ func Test_goTypeToJSDocType(t *testing.T) { { name: "map", input: "map[string]float64", - want: "Record", + want: "{[key: string]: number}", }, { name: "map", input: "map[string]map[string]float64", - want: "Record>", + want: "{[key: string]: {[key: string]: number}}", }, { name: "types", input: "main.SomeType", want: "main.SomeType", }, - { - name: "primitive_generic", - input: "main.ListData[string]", - want: "main.ListData_string_", - }, - { - name: "stdlib_generic", - input: "main.ListData[*net/http.Request]", - want: "main.ListData_net_http_Request_", - }, } var importNamespaces slicer.StringSlicer for _, tt := range tests { diff --git a/v2/internal/binding/reflect.go b/v2/internal/binding/reflect.go old mode 100644 new mode 100755 index c254d0f0a..66a9cf7bd --- a/v2/internal/binding/reflect.go +++ b/v2/internal/binding/reflect.go @@ -19,32 +19,13 @@ func isFunction(value interface{}) bool { return reflect.ValueOf(value).Kind() == reflect.Func } -// isStruct returns true if the value given is a struct +// isStructPtr returns true if the value given is a struct func isStruct(value interface{}) bool { return reflect.ValueOf(value).Kind() == reflect.Struct } -func normalizeStructName(name string) string { - return strings.ReplaceAll( - strings.ReplaceAll( - strings.ReplaceAll( - strings.ReplaceAll( - name, - ",", - "-", - ), - "*", - "", - ), - "]", - "__", - ), - "[", - "__", - ) -} - func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { + // Create result placeholder var result []*BoundMethod @@ -67,14 +48,14 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { // Process Struct structType := reflect.TypeOf(value) structValue := reflect.ValueOf(value) - structName := structType.Elem().Name() - structNameNormalized := normalizeStructName(structName) - pkgPath := strings.TrimSuffix(structType.Elem().String(), fmt.Sprintf(".%s", structName)) + structTypeString := structType.String() + baseName := structTypeString[1:] // Process Methods for i := 0; i < structType.NumMethod(); i++ { methodDef := structType.Method(i) methodName := methodDef.Name + fullMethodName := baseName + "." + methodName method := structValue.MethodByName(methodName) methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name() @@ -84,11 +65,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { // Create new method boundMethod := &BoundMethod{ - Path: &BoundedMethodPath{ - Package: pkgPath, - Struct: structNameNormalized, - Name: methodName, - }, + Name: fullMethodName, Inputs: nil, Outputs: nil, Comments: "", @@ -190,8 +167,9 @@ func getPackageName(in string) string { } func getSplitReturn(in string) (string, string) { - result := strings.SplitN(in, ".", 2) + result := strings.Split(in, ".") return result[0], result[1] + } func hasElements(typ reflect.Type) bool { diff --git a/v2/internal/frontend/calls.go b/v2/internal/frontend/calls.go index 5401106bc..3983c24bf 100644 --- a/v2/internal/frontend/calls.go +++ b/v2/internal/frontend/calls.go @@ -1,5 +1,5 @@ package frontend type Calls interface { - Callback(message string) + Callback(string) } diff --git a/v2/internal/frontend/desktop/darwin/AppDelegate.h b/v2/internal/frontend/desktop/darwin/AppDelegate.h index a8d10f647..e2dd841c9 100644 --- a/v2/internal/frontend/desktop/darwin/AppDelegate.h +++ b/v2/internal/frontend/desktop/darwin/AppDelegate.h @@ -11,23 +11,13 @@ #import #import "WailsContext.h" -@interface AppDelegate : NSResponder +@interface AppDelegate : NSResponder @property bool alwaysOnTop; @property bool startHidden; -@property (retain) NSString* singleInstanceUniqueId; -@property bool singleInstanceLockEnabled; @property bool startFullscreen; @property (retain) WailsWindow* mainWindow; @end -extern void HandleOpenFile(char *); - -extern void HandleSecondInstanceData(char * message); - -void SendDataToFirstInstance(char * singleInstanceUniqueId, char * text); - -char* GetMacOsNativeTempDir(); - #endif /* AppDelegate_h */ diff --git a/v2/internal/frontend/desktop/darwin/AppDelegate.m b/v2/internal/frontend/desktop/darwin/AppDelegate.m index a73ec3ec3..6d46deae4 100644 --- a/v2/internal/frontend/desktop/darwin/AppDelegate.m +++ b/v2/internal/frontend/desktop/darwin/AppDelegate.m @@ -9,41 +9,15 @@ #import #import "AppDelegate.h" -#import "CustomProtocol.h" -#import "message.h" @implementation AppDelegate --(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename -{ - const char* utf8FileName = filename.UTF8String; - HandleOpenFile((char*)utf8FileName); - return YES; -} - -- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray> * _Nullable))restorationHandler { - if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSURL *url = userActivity.webpageURL; - if (url) { - HandleOpenURL((char*)[[url absoluteString] UTF8String]); - return YES; - } - } - return NO; -} - - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { - return NO; + return NO; } - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - processMessage("Q"); - return NSTerminateCancel; -} - - (void)applicationWillFinishLaunching:(NSNotification *)aNotification { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; if (self.alwaysOnTop) { - [self.mainWindow setLevel:NSFloatingWindowLevel]; + [self.mainWindow setLevel:NSStatusWindowLevel]; } if ( !self.startHidden ) { [self.mainWindow makeKeyAndOrderFront:self]; @@ -58,37 +32,6 @@ [self.mainWindow setCollectionBehavior:behaviour]; [self.mainWindow toggleFullScreen:nil]; } - - if ( self.singleInstanceLockEnabled ) { - [[NSDistributedNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleSecondInstanceNotification:) name:self.singleInstanceUniqueId object:nil]; - } -} - -void SendDataToFirstInstance(char * singleInstanceUniqueId, char * message) { - // we pass message in object because otherwise sandboxing will prevent us from sending it https://developer.apple.com/forums/thread/129437 - NSString * myString = [NSString stringWithUTF8String:message]; - [[NSDistributedNotificationCenter defaultCenter] - postNotificationName:[NSString stringWithUTF8String:singleInstanceUniqueId] - object:(__bridge const void *)(myString) - userInfo:nil - deliverImmediately:YES]; -} - -char* GetMacOsNativeTempDir() { - NSString *tempDir = NSTemporaryDirectory(); - char *copy = strdup([tempDir UTF8String]); - - return copy; -} - -- (void)handleSecondInstanceNotification:(NSNotification *)note; -{ - if (note.object != nil) { - NSString * message = (__bridge NSString *)note.object; - const char* utf8Message = message.UTF8String; - HandleSecondInstanceData((char*)utf8Message); - } } - (void)dealloc { diff --git a/v2/internal/frontend/desktop/darwin/Application.h b/v2/internal/frontend/desktop/darwin/Application.h index c3cd8075a..e418168e6 100644 --- a/v2/internal/frontend/desktop/darwin/Application.h +++ b/v2/internal/frontend/desktop/darwin/Application.h @@ -17,7 +17,7 @@ #define WindowStartsMinimised 2 #define WindowStartsFullscreen 3 -WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int contentProtection, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId, bool enableDragAndDrop, bool disableWebViewDragAndDrop); +WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled); void Run(void*, const char* url); void SetTitle(void* ctx, const char *title); @@ -41,7 +41,6 @@ void ShowApplication(void* ctx); void SetBackgroundColour(void* ctx, int r, int g, int b, int a); void ExecJS(void* ctx, const char*); void Quit(void*); -void WindowPrint(void* ctx); const char* GetSize(void *ctx); const char* GetPosition(void *ctx); @@ -69,21 +68,6 @@ void UpdateMenuItem(void* nsmenuitem, int checked); void RunMainLoop(void); void ReleaseContext(void *inctx); -/* Notifications */ -bool IsNotificationAvailable(void *inctx); -bool CheckBundleIdentifier(void *inctx); -bool EnsureDelegateInitialized(void *inctx); -void RequestNotificationAuthorization(void *inctx, int channelID); -void CheckNotificationAuthorization(void *inctx, int channelID); -void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json); -void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json); -void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle); -void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId); -void RemoveAllPendingNotifications(void *inctx); -void RemovePendingNotification(void *inctx, const char *identifier); -void RemoveAllDeliveredNotifications(void *inctx); -void RemoveDeliveredNotification(void *inctx, const char *identifier); - NSString* safeInit(const char* input); #endif /* Application_h */ diff --git a/v2/internal/frontend/desktop/darwin/Application.m b/v2/internal/frontend/desktop/darwin/Application.m index 38b2f35ef..ab951714d 100644 --- a/v2/internal/frontend/desktop/darwin/Application.m +++ b/v2/internal/frontend/desktop/darwin/Application.m @@ -10,32 +10,25 @@ #import "WailsContext.h" #import "Application.h" #import "AppDelegate.h" -#import "WindowDelegate.h" #import "WailsMenu.h" #import "WailsMenuItem.h" -WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int zoomable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int contentProtection, int devtoolsEnabled, int defaultContextMenuEnabled, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled, struct Preferences preferences, int singleInstanceLockEnabled, const char* singleInstanceUniqueId, bool enableDragAndDrop, bool disableWebViewDragAndDrop) { - +WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight, bool fraudulentWebsiteWarningEnabled) { + [NSApplication sharedApplication]; WailsContext *result = [WailsContext new]; - result.devtoolsEnabled = devtoolsEnabled; - result.defaultContextMenuEnabled = defaultContextMenuEnabled; - + result.debug = debug; + if ( windowStartState == WindowStartsFullscreen ) { fullscreen = 1; } - [result CreateWindow:width :height :frameless :resizable :zoomable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled :preferences :enableDragAndDrop :disableWebViewDragAndDrop]; + [result CreateWindow:width :height :frameless :resizable :fullscreen :fullSizeContent :hideTitleBar :titlebarAppearsTransparent :hideTitle :useToolbar :hideToolbarSeparator :webviewIsTransparent :hideWindowOnClose :safeInit(appearance) :windowIsTranslucent :minWidth :minHeight :maxWidth :maxHeight :fraudulentWebsiteWarningEnabled]; [result SetTitle:safeInit(title)]; [result Center]; - - if (contentProtection == 1 && - [result.mainWindow respondsToSelector:@selector(setSharingType:)]) { - [result.mainWindow setSharingType:NSWindowSharingNone]; - } - + switch( windowStartState ) { case WindowStartsMaximised: [result.mainWindow zoom:nil]; @@ -48,19 +41,14 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in if ( startsHidden == 1 ) { result.startHidden = true; } - + if ( fullscreen == 1 ) { result.startFullscreen = true; } - - if ( singleInstanceLockEnabled == 1 ) { - result.singleInstanceLockEnabled = true; - result.singleInstanceUniqueId = safeInit(singleInstanceUniqueId); - } - + result.alwaysOnTop = alwaysOnTop; result.hideOnClose = hideWindowOnClose; - + return result; } @@ -191,7 +179,7 @@ const char* GetPosition(void *inctx) { NSString *result = [NSString stringWithFormat:@"%d,%d",x,y]; return [result UTF8String]; } - + const bool IsFullScreen(void *inctx) { WailsContext *ctx = (__bridge WailsContext*) inctx; return [ctx IsFullScreen]; @@ -259,7 +247,7 @@ NSString* safeInit(const char* input) { void MessageDialog(void *inctx, const char* dialogType, const char* title, const char* message, const char* button1, const char* button2, const char* button3, const char* button4, const char* defaultButton, const char* cancelButton, void* iconData, int iconDataLength) { WailsContext *ctx = (__bridge WailsContext*) inctx; - + NSString *_dialogType = safeInit(dialogType); NSString *_title = safeInit(title); NSString *_message = safeInit(message); @@ -269,33 +257,33 @@ void MessageDialog(void *inctx, const char* dialogType, const char* title, const NSString *_button4 = safeInit(button4); NSString *_defaultButton = safeInit(defaultButton); NSString *_cancelButton = safeInit(cancelButton); - + ON_MAIN_THREAD( [ctx MessageDialog:_dialogType :_title :_message :_button1 :_button2 :_button3 :_button4 :_defaultButton :_cancelButton :iconData :iconDataLength]; ) } void OpenFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int allowDirectories, int allowFiles, int canCreateDirectories, int treatPackagesAsDirectories, int resolveAliases, int showHiddenFiles, int allowMultipleSelection, const char* filters) { - + WailsContext *ctx = (__bridge WailsContext*) inctx; NSString *_title = safeInit(title); NSString *_defaultFilename = safeInit(defaultFilename); NSString *_defaultDirectory = safeInit(defaultDirectory); NSString *_filters = safeInit(filters); - + ON_MAIN_THREAD( [ctx OpenFileDialog:_title :_defaultFilename :_defaultDirectory :allowDirectories :allowFiles :canCreateDirectories :treatPackagesAsDirectories :resolveAliases :showHiddenFiles :allowMultipleSelection :_filters]; ) } void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters) { - + WailsContext *ctx = (__bridge WailsContext*) inctx; NSString *_title = safeInit(title); NSString *_defaultFilename = safeInit(defaultFilename); NSString *_defaultDirectory = safeInit(defaultDirectory); NSString *_filters = safeInit(filters); - + ON_MAIN_THREAD( [ctx SaveFileDialog:_title :_defaultFilename :_defaultDirectory :canCreateDirectories :treatPackagesAsDirectories :showHiddenFiles :_filters]; ) @@ -367,74 +355,6 @@ void AppendSeparator(void* inMenu) { } -bool IsNotificationAvailable(void *inctx) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - return [ctx IsNotificationAvailable]; -} - -bool CheckBundleIdentifier(void *inctx) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - return [ctx CheckBundleIdentifier]; -} - -bool EnsureDelegateInitialized(void *inctx) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - return [ctx EnsureDelegateInitialized]; -} - -void RequestNotificationAuthorization(void *inctx, int channelID) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx RequestNotificationAuthorization:channelID]; -} - -void CheckNotificationAuthorization(void *inctx, int channelID) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx CheckNotificationAuthorization:channelID]; -} - -void SendNotification(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *data_json) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx SendNotification:channelID :identifier :title :subtitle :body :data_json]; -} - -void SendNotificationWithActions(void *inctx, int channelID, const char *identifier, const char *title, const char *subtitle, const char *body, const char *categoryId, const char *actions_json) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - - [ctx SendNotificationWithActions:channelID :identifier :title :subtitle :body :categoryId :actions_json]; -} - -void RegisterNotificationCategory(void *inctx, int channelID, const char *categoryId, const char *actions_json, bool hasReplyField, const char *replyPlaceholder, const char *replyButtonTitle) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - - [ctx RegisterNotificationCategory:channelID :categoryId :actions_json :hasReplyField :replyPlaceholder :replyButtonTitle]; -} - -void RemoveNotificationCategory(void *inctx, int channelID, const char *categoryId) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - - [ctx RemoveNotificationCategory:channelID :categoryId]; -} - -void RemoveAllPendingNotifications(void *inctx) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx RemoveAllPendingNotifications]; -} - -void RemovePendingNotification(void *inctx, const char *identifier) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx RemovePendingNotification:identifier]; -} - -void RemoveAllDeliveredNotifications(void *inctx) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx RemoveAllDeliveredNotifications]; -} - -void RemoveDeliveredNotification(void *inctx, const char *identifier) { - WailsContext *ctx = (__bridge WailsContext*)inctx; - [ctx RemoveDeliveredNotification:identifier]; -} - void Run(void *inctx, const char* url) { WailsContext *ctx = (__bridge WailsContext*) inctx; @@ -445,8 +365,6 @@ void Run(void *inctx, const char* url) { delegate.mainWindow = ctx.mainWindow; delegate.alwaysOnTop = ctx.alwaysOnTop; delegate.startHidden = ctx.startHidden; - delegate.singleInstanceLockEnabled = ctx.singleInstanceLockEnabled; - delegate.singleInstanceUniqueId = ctx.singleInstanceUniqueId; delegate.startFullscreen = ctx.startFullscreen; NSString *_url = safeInit(url); @@ -465,37 +383,3 @@ void ReleaseContext(void *inctx) { WailsContext *ctx = (__bridge WailsContext*) inctx; [ctx release]; } - -// Credit: https://stackoverflow.com/q/33319295 -void WindowPrint(void *inctx) { - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 - if (@available(macOS 11.0, *)) { - ON_MAIN_THREAD( - WailsContext *ctx = (__bridge WailsContext*) inctx; - WKWebView* webView = ctx.webview; - - // I think this should be exposed as a config - // It directly affects the printed output/PDF - NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo]; - pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic; - pInfo.verticalPagination = NSPrintingPaginationModeAutomatic; - pInfo.verticallyCentered = YES; - pInfo.horizontallyCentered = YES; - pInfo.orientation = NSPaperOrientationLandscape; - pInfo.leftMargin = 0; - pInfo.rightMargin = 0; - pInfo.topMargin = 0; - pInfo.bottomMargin = 0; - - NSPrintOperation *po = [webView printOperationWithPrintInfo:pInfo]; - po.showsPrintPanel = YES; - po.showsProgressPanel = YES; - - po.view.frame = webView.bounds; - - [po runOperationModalForWindow:ctx.mainWindow delegate:ctx.mainWindow.delegate didRunSelector:nil contextInfo:nil]; - ) - } -#endif -} diff --git a/v2/internal/frontend/desktop/darwin/CustomProtocol.h b/v2/internal/frontend/desktop/darwin/CustomProtocol.h deleted file mode 100644 index 0698a4d45..000000000 --- a/v2/internal/frontend/desktop/darwin/CustomProtocol.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef CustomProtocol_h -#define CustomProtocol_h - -#import - -extern void HandleOpenURL(char*); - -@interface CustomProtocolSchemeHandler : NSObject -+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; -@end - -void StartCustomProtocolHandler(void); - -#endif /* CustomProtocol_h */ diff --git a/v2/internal/frontend/desktop/darwin/CustomProtocol.m b/v2/internal/frontend/desktop/darwin/CustomProtocol.m deleted file mode 100644 index ebc61aa00..000000000 --- a/v2/internal/frontend/desktop/darwin/CustomProtocol.m +++ /dev/null @@ -1,20 +0,0 @@ -#include "CustomProtocol.h" - -@implementation CustomProtocolSchemeHandler -+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { - [event paramDescriptorForKeyword:keyDirectObject]; - - NSString *urlStr = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - - HandleOpenURL((char*)[[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]); -} -@end - -void StartCustomProtocolHandler(void) { - NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; - - [appleEventManager setEventHandler:[CustomProtocolSchemeHandler class] - andSelector:@selector(handleGetURLEvent:withReplyEvent:) - forEventClass:kInternetEventClass - andEventID: kAEGetURL]; -} diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.h b/v2/internal/frontend/desktop/darwin/WailsContext.h index aafc3a1d4..1e48b2182 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.h +++ b/v2/internal/frontend/desktop/darwin/WailsContext.h @@ -10,7 +10,6 @@ #import #import -#import "WailsWebView.h" #if __has_include() #define USE_NEW_FILTERS @@ -33,7 +32,7 @@ @interface WailsContext : NSObject @property (retain) WailsWindow* mainWindow; -@property (retain) WailsWebView* webview; +@property (retain) WKWebView* webview; @property (nonatomic, assign) id appdelegate; @property bool hideOnClose; @@ -41,15 +40,11 @@ @property bool startHidden; @property bool startFullscreen; -@property bool singleInstanceLockEnabled; -@property (retain) NSString* singleInstanceUniqueId; - @property (retain) NSEvent* mouseEvent; @property bool alwaysOnTop; -@property bool devtoolsEnabled; -@property bool defaultContextMenuEnabled; +@property bool debug; @property (retain) WKUserContentController* userContentController; @@ -59,13 +54,7 @@ @property (retain) NSString* aboutTitle; @property (retain) NSString* aboutDescription; -struct Preferences { - bool *tabFocusesLinks; - bool *textInteractionEnabled; - bool *fullscreenEnabled; -}; - -- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop; +- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString *)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled; - (void) SetSize:(int)width :(int)height; - (void) SetPosition:(int)x :(int) y; - (void) SetMinSize:(int)minWidth :(int)minHeight; @@ -92,24 +81,10 @@ struct Preferences { - (void) ShowApplication; - (void) Quit; -- (void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength; +-(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength; - (void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters; - (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters; -- (bool) IsNotificationAvailable; -- (bool) CheckBundleIdentifier; -- (bool) EnsureDelegateInitialized; -- (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 *)dataJSON; -- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)actionsJSON; -- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle; -- (void) RemoveNotificationCategory:(int)channelID :(const char *)categoryId; -- (void) RemoveAllPendingNotifications; -- (void) RemovePendingNotification:(const char *)identifier; -- (void) RemoveAllDeliveredNotifications; -- (void) RemoveDeliveredNotification:(const char *)identifier; - - (void) loadRequest:(NSString*)url; - (void) ExecJS:(NSString*)script; - (NSScreen*) getCurrentScreen; diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.m b/v2/internal/frontend/desktop/darwin/WailsContext.m index 51993eda2..29fa99317 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.m +++ b/v2/internal/frontend/desktop/darwin/WailsContext.m @@ -1,3 +1,4 @@ +//go:build darwin // // WailsContext.m // test @@ -5,13 +6,11 @@ // Created by Lea Anthony on 10/10/21. // -#include "Application.h" #import #import #import "WailsContext.h" #import "WailsAlert.h" #import "WailsMenu.h" -#import "WailsWebView.h" #import "WindowDelegate.h" #import "message.h" #import "Role.h" @@ -37,20 +36,12 @@ typedef void (^schemeTaskCaller)(id); @end -// Notifications -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 -#import -#endif - -extern void captureResult(int channelID, bool success, const char* error); -extern void didReceiveNotificationResponse(const char *jsonPayload, const char* error); - @implementation WailsContext - (void) SetSize:(int)width :(int)height { - + if (self.shuttingDown) return; - + NSRect frame = [self.mainWindow frame]; frame.origin.y += frame.size.height - height; frame.size.width = width; @@ -59,22 +50,22 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } - (void) SetPosition:(int)x :(int)y { - + if (self.shuttingDown) return; - + NSScreen* screen = [self getCurrentScreen]; NSRect windowFrame = [self.mainWindow frame]; - NSRect screenFrame = [screen visibleFrame]; + 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; - + [self.mainWindow setFrame:windowFrame display:TRUE animate:FALSE]; } - (void) SetMinSize:(int)minWidth :(int)minHeight { - + if (self.shuttingDown) return; - + NSSize size = { minWidth, minHeight }; self.mainWindow.userMinSize = size; [self.mainWindow setMinSize:size]; @@ -83,14 +74,14 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* - (void) SetMaxSize:(int)maxWidth :(int)maxHeight { - + if (self.shuttingDown) return; - + NSSize size = { FLT_MAX, FLT_MAX }; - + size.width = maxWidth > 0 ? maxWidth : FLT_MAX; size.height = maxHeight > 0 ? maxHeight : FLT_MAX; - + self.mainWindow.userMaxSize = size; [self.mainWindow setMaxSize:size]; [self adjustWindowSize]; @@ -98,18 +89,18 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* - (void) adjustWindowSize { - + if (self.shuttingDown) return; - + NSRect currentFrame = [self.mainWindow frame]; - + if ( currentFrame.size.width > self.mainWindow.userMaxSize.width ) currentFrame.size.width = self.mainWindow.userMaxSize.width; if ( currentFrame.size.width < self.mainWindow.userMinSize.width ) currentFrame.size.width = self.mainWindow.userMinSize.width; if ( currentFrame.size.height > self.mainWindow.userMaxSize.height ) currentFrame.size.height = self.mainWindow.userMaxSize.height; if ( currentFrame.size.height < self.mainWindow.userMinSize.height ) currentFrame.size.height = self.mainWindow.userMinSize.height; [self.mainWindow setFrame:currentFrame display:YES animate:FALSE]; - + } - (void) dealloc { @@ -145,16 +136,16 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* return NO; } -- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)zoomable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled :(struct Preferences)preferences :(bool)enableDragAndDrop :(bool)disableWebViewDragAndDrop { +- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight :(bool)fraudulentWebsiteWarningEnabled { NSWindowStyleMask styleMask = 0; - + if( !frameless ) { if (!hideTitleBar) { styleMask |= NSWindowStyleMaskTitled; } styleMask |= NSWindowStyleMaskClosable; } - + styleMask |= NSWindowStyleMaskMiniaturizable; if( fullSizeContent || frameless || titlebarAppearsTransparent ) { @@ -164,22 +155,23 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* if (resizable) { styleMask |= NSWindowStyleMaskResizable; } - + self.mainWindow = [[WailsWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; + if (!frameless && useToolbar) { id toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"]; [toolbar autorelease]; [toolbar setShowsBaselineSeparator:!hideToolbarSeparator]; [self.mainWindow setToolbar:toolbar]; - + } - + [self.mainWindow setTitleVisibility:hideTitle]; [self.mainWindow setTitlebarAppearsTransparent:titlebarAppearsTransparent]; - + // [self.mainWindow canBecomeKeyWindow]; - + id contentView = [self.mainWindow contentView]; if (windowIsTranslucent) { NSVisualEffectView *effectView = [NSVisualEffectView alloc]; @@ -190,17 +182,12 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* [effectView setState:NSVisualEffectStateActive]; [contentView addSubview:effectView positioned:NSWindowBelow relativeTo:nil]; } - + if (appearance != nil) { NSAppearance *nsAppearance = [NSAppearance appearanceNamed:appearance]; [self.mainWindow setAppearance:nsAppearance]; } - - if (!zoomable && resizable) { - NSButton *button = [self.mainWindow standardWindowButton:NSWindowZoomButton]; - [button setEnabled: NO]; - } - + NSSize minSize = { minWidth, minHeight }; NSSize maxSize = { maxWidth, maxHeight }; @@ -212,93 +199,62 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } self.mainWindow.userMaxSize = maxSize; self.mainWindow.userMinSize = minSize; - + if( !fullscreen ) { [self.mainWindow applyWindowConstraints]; } - + WindowDelegate *windowDelegate = [WindowDelegate new]; windowDelegate.hideOnClose = hideWindowOnClose; windowDelegate.ctx = self; [self.mainWindow setDelegate:windowDelegate]; - + // Webview stuff here! WKWebViewConfiguration *config = [WKWebViewConfiguration new]; - // Disable suppressesIncrementalRendering on macOS 26+ to prevent WebView crashes - // during rapid UI updates. See: https://github.com/wailsapp/wails/issues/4592 - if (@available(macOS 26.0, *)) { - config.suppressesIncrementalRendering = false; - } else { - config.suppressesIncrementalRendering = true; - } + config.suppressesIncrementalRendering = true; config.applicationNameForUserAgent = @"wails.io"; [config setURLSchemeHandler:self forURLScheme:@"wails"]; + +// [config.preferences setValue:[NSNumber numberWithBool:true] forKey:@"developerExtrasEnabled"]; - 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 - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 if (@available(macOS 10.15, *)) { config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; } -#endif WKUserContentController* userContentController = [WKUserContentController new]; [userContentController addScriptMessageHandler:self name:@"external"]; config.userContentController = userContentController; self.userContentController = userContentController; - - if (self.devtoolsEnabled) { + if (self.debug) { [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"]; - } - - if (!self.defaultContextMenuEnabled) { + } else { // Disable default context menus WKUserScript *initScript = [WKUserScript new]; - [initScript initWithSource:@"window.wails.flags.disableDefaultContextMenu = true;" + [initScript initWithSource:@"window.wails.flags.disableWailsDefaultContextMenu = true;" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:false]; [userContentController addUserScript:initScript]; + } - - self.webview = [WailsWebView alloc]; - self.webview.enableDragAndDrop = enableDragAndDrop; - self.webview.disableWebViewDragAndDrop = disableWebViewDragAndDrop; - + + self.webview = [WKWebView alloc]; CGRect init = { 0,0,0,0 }; [self.webview initWithFrame:init configuration:config]; [contentView addSubview:self.webview]; [self.webview setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable]; CGRect contentViewBounds = [contentView bounds]; [self.webview setFrame:contentViewBounds]; - + if (webviewIsTransparent) { [self.webview setValue:[NSNumber numberWithBool:!webviewIsTransparent] forKey:@"drawsBackground"]; } - + [self.webview setNavigationDelegate:self]; self.webview.UIDelegate = self; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setBool:FALSE forKey:@"NSAutomaticQuoteSubstitutionEnabled"]; - + // Mouse monitors [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { id window = [event window]; @@ -307,7 +263,7 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } return event; }]; - + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { id window = [event window]; if (window == self.mainWindow) { @@ -316,9 +272,9 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } return event; }]; - + self.applicationMenu = [NSMenu new]; - + } - (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags { @@ -354,9 +310,9 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* float green = g/255.0; float blue = b/255.0; float alpha = a/255.0; - + id colour = [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:alpha ]; - + [self.mainWindow setBackgroundColor:colour]; } @@ -438,7 +394,7 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* - (void) SetAlwaysOnTop:(int)onTop { if (onTop) { - [self.mainWindow setLevel:NSFloatingWindowLevel]; + [self.mainWindow setLevel:NSStatusWindowLevel]; } else { [self.mainWindow setLevel:NSNormalWindowLevel]; } @@ -452,17 +408,16 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* [self.webview evaluateJavaScript:script completionHandler:nil]; } -- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters +- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray * URLs))completionHandler { - + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 if (@available(macOS 10.14, *)) { openPanel.canChooseDirectories = parameters.allowsDirectories; } -#endif - [openPanel + + [openPanel beginSheetModalForWindow:webView.window completionHandler:^(NSInteger result) { if (result == NSModalResponseOK) @@ -492,17 +447,8 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } - (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message { - // Get the origin from the message's frame - NSString *origin = nil; - if (message.frameInfo && message.frameInfo.request && message.frameInfo.request.URL) { - NSURL *url = message.frameInfo.request.URL; - if (url.scheme && url.host) { - origin = [url absoluteString]; - } - } - NSString *m = message.body; - + // Check for drag if ( [m isEqualToString:@"drag"] ) { if( [self IsFullScreen] ) { @@ -513,18 +459,18 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } return; } - + const char *_m = [m UTF8String]; - const char *_origin = [origin UTF8String]; - - processBindingMessage(_m, _origin, message.frameInfo.isMainFrame); + + processMessage(_m); } + /***** Dialogs ******/ -(void) MessageDialog :(NSString*)dialogType :(NSString*)title :(NSString*)message :(NSString*)button1 :(NSString*)button2 :(NSString*)button3 :(NSString*)button4 :(NSString*)defaultButton :(NSString*)cancelButton :(void*)iconData :(int)iconDataLength { WailsAlert *alert = [WailsAlert new]; - + int style = NSAlertStyleInformational; if (dialogType != nil ) { if( [dialogType isEqualToString:@"warning"] ) { @@ -541,12 +487,12 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* if( message != nil ) { [alert setInformativeText:message]; } - + [alert addButton:button1 :defaultButton :cancelButton]; [alert addButton:button2 :defaultButton :cancelButton]; [alert addButton:button3 :defaultButton :cancelButton]; [alert addButton:button4 :defaultButton :cancelButton]; - + NSImage *icon = nil; if (iconData != nil) { NSData *imageData = [NSData dataWithBytes:iconData length:iconDataLength]; @@ -575,8 +521,8 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } -(void) OpenFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)allowDirectories :(bool)allowFiles :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)resolveAliases :(bool)showHiddenFiles :(bool)allowMultipleSelection :(NSString*)filters { - - + + // Create the dialog NSOpenPanel *dialog = [NSOpenPanel openPanel]; @@ -594,18 +540,14 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* #ifdef USE_NEW_FILTERS NSMutableArray *contentTypes = [[NSMutableArray new] autorelease]; for (NSString *filter in filterList) { -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithFilenameExtension:filter]; [contentTypes addObject:t]; } -#endif } -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { [dialog setAllowedContentTypes:contentTypes]; } -#endif #else [dialog setAllowedFileTypes:filterList]; #endif @@ -616,10 +558,11 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* if( defaultFilename != nil ) { [dialog setNameFieldStringValue:defaultFilename]; } - + [dialog setAllowsMultipleSelection: allowMultipleSelection]; + [dialog setShowsHiddenFiles: showHiddenFiles]; + } - [dialog setShowsHiddenFiles: showHiddenFiles]; // Default Directory if( defaultDirectory != nil ) { @@ -651,19 +594,19 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* [nsjson release]; [arr release]; }]; - + } -(void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters; { - - + + // Create the dialog NSSavePanel *dialog = [NSSavePanel savePanel]; // Do not hide extension [dialog setExtensionHidden:false]; - + // Valid but appears to do nothing.... :/ if( title != nil ) { [dialog setTitle:title]; @@ -677,21 +620,17 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* #ifdef USE_NEW_FILTERS NSMutableArray *contentTypes = [[NSMutableArray new] autorelease]; for (NSString *filter in filterList) { -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { UTType *t = [UTType typeWithFilenameExtension:filter]; [contentTypes addObject:t]; } -#endif } if( contentTypes.count == 0) { [dialog setAllowsOtherFileTypes:true]; } else { -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 110000 if (@available(macOS 11.0, *)) { [dialog setAllowedContentTypes:contentTypes]; } -#endif } #else @@ -704,7 +643,7 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* if( defaultFilename != nil ) { [dialog setNameFieldStringValue:defaultFilename]; } - + // Default Directory if( defaultDirectory != nil ) { NSURL *url = [NSURL fileURLWithPath:defaultDirectory]; @@ -729,370 +668,19 @@ extern void didReceiveNotificationResponse(const char *jsonPayload, const char* } processSaveFileDialogResponse(""); }]; - -} - -/***** Notifications ******/ -- (bool) IsNotificationAvailable { - if (@available(macOS 10.14, *)) { - return YES; - } else { - return NO; - } -} - -- (bool) CheckBundleIdentifier { - NSBundle *main = [NSBundle mainBundle]; - if (main.bundleIdentifier == nil) { - return NO; - } - return YES; -} - -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - willPresentNotification:(UNNotification *)notification - withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(macos(10.14)) { - UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound; - - 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 API_AVAILABLE(macos(10.14)) { - - 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:@"categoryId"]; - } - - 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(); -} - -- (bool) EnsureDelegateInitialized { - if (@available(macOS 10.14, *)) { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - center.delegate = (id)self; - return YES; - } - return NO; -} - -- (void) RequestNotificationAuthorization :(int)channelID { - if (@available(macOS 10.14, *)) { - if (![self 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); - } - }]; - } else { - captureResult(channelID, false, "Notifications not available on macOS versions prior to 10.14"); - } } -- (void) CheckNotificationAuthorization :(int) channelID { - if (@available(macOS 10.14, *)) { - if (![self 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); - }]; - } else { - captureResult(channelID, false, "Notifications not available on macOS versions prior to 10.14"); - } -} - -- (UNMutableNotificationContent *)createNotificationContent:(const char *)title subtitle:(const char *)subtitle body:(const char *)body dataJSON:(const char *)dataJSON error:(NSError **)contentError API_AVAILABLE(macos(10.14)) { - if (title == NULL) title = ""; - if (body == NULL) body = ""; - - NSString *nsTitle = [NSString stringWithUTF8String:title]; - NSString *nsSubtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : @""; - NSString *nsBody = [NSString stringWithUTF8String:body]; - - UNMutableNotificationContent *content = [[[UNMutableNotificationContent alloc] init] autorelease]; - content.title = nsTitle; - if (![nsSubtitle isEqualToString:@""]) { - content.subtitle = nsSubtitle; - } - content.body = nsBody; - content.sound = [UNNotificationSound defaultSound]; - - // Parse JSON data if provided - if (dataJSON) { - NSString *dataJsonStr = [NSString stringWithUTF8String:dataJSON]; - 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) { - if (contentError) *contentError = error; - } - } - - return content; -} - -- (void) sendNotificationWithRequest:(UNNotificationRequest *)request channelID:(int)channelID API_AVAILABLE(macos(10.14)) { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - [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) SendNotification:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)dataJSON API_AVAILABLE(macos(10.14)) { - if (![self EnsureDelegateInitialized]) { - NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; - captureResult(channelID, false, [errorMsg UTF8String]); - return; - } - - NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; - - NSError *contentError = nil; - UNMutableNotificationContent *content = [self createNotificationContent:title subtitle:subtitle body:body dataJSON:dataJSON error:&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]; - - [self sendNotificationWithRequest:request channelID:channelID]; -} - -- (void) SendNotificationWithActions:(int)channelID :(const char *)identifier :(const char *)title :(const char *)subtitle :(const char *)body :(const char *)categoryId :(const char *)dataJSON API_AVAILABLE(macos(10.14)) { - if (![self EnsureDelegateInitialized]) { - NSString *errorMsg = @"Notification delegate has been lost. Reinitialize the notification service."; - captureResult(channelID, false, [errorMsg UTF8String]); - return; - } - - NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; - NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; - - NSError *contentError = nil; - UNMutableNotificationContent *content = [self createNotificationContent:title subtitle:subtitle body:body dataJSON:dataJSON error:&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]; - - [self sendNotificationWithRequest:request channelID:channelID]; -} - -- (void) RegisterNotificationCategory:(int)channelID :(const char *)categoryId :(const char *)actionsJSON :(bool)hasReplyField :(const char *)replyPlaceholder :(const char *)replyButtonTitle API_AVAILABLE(macos(10.14)) { - if (![self 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 = actionsJSON ? [NSString stringWithUTF8String:actionsJSON] : @"[]"; - - 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) { - // Defensive NULL checks: if hasReplyField is true, both strings must be non-NULL - if (!replyPlaceholder || !replyButtonTitle) { - NSString *errorMsg = @"hasReplyField is true but replyPlaceholder or replyButtonTitle is NULL"; - captureResult(channelID, false, [errorMsg UTF8String]); - return; - } - 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 identifier if found - 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 API_AVAILABLE(macos(10.14)) { - NSString *nsCategoryId = [NSString stringWithUTF8String:categoryId]; - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - - [center getNotificationCategoriesWithCompletionHandler:^(NSSet *categories) { - NSMutableSet *updatedCategories = [NSMutableSet setWithSet:categories]; - - // Find and remove the matching category - 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 API_AVAILABLE(macos(10.14)) { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - [center removeAllPendingNotificationRequests]; -} - -- (void) RemovePendingNotification:(const char *)identifier API_AVAILABLE(macos(10.14)) { - NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - [center removePendingNotificationRequestsWithIdentifiers:@[nsIdentifier]]; -} - -- (void) RemoveAllDeliveredNotifications API_AVAILABLE(macos(10.14)) { - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - [center removeAllDeliveredNotifications]; -} - -- (void) RemoveDeliveredNotification:(const char *)identifier API_AVAILABLE(macos(10.14)) { - NSString *nsIdentifier = [NSString stringWithUTF8String:identifier]; - UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; - [center removeDeliveredNotificationsWithIdentifiers:@[nsIdentifier]]; -} - - - (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen { self.aboutTitle = title; self.aboutDescription = description; - + NSData *imageData = [NSData dataWithBytes:imagedata length:datalen]; self.aboutImage = [[NSImage alloc] initWithData:imageData]; } -- (void) About { - +-(void) About { + WailsAlert *alert = [WailsAlert new]; [alert setAlertStyle:NSAlertStyleInformational]; if( self.aboutTitle != nil ) { @@ -1101,8 +689,8 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response if( self.aboutDescription != nil ) { [alert setInformativeText:self.aboutDescription]; } - - + + [alert.window setLevel:NSFloatingWindowLevel]; if ( self.aboutImage != nil) { [alert setIcon:self.aboutImage]; diff --git a/v2/internal/frontend/desktop/darwin/WailsWebView.h b/v2/internal/frontend/desktop/darwin/WailsWebView.h deleted file mode 100644 index b6f746cf2..000000000 --- a/v2/internal/frontend/desktop/darwin/WailsWebView.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef WailsWebView_h -#define WailsWebView_h - -#import -#import - -// We will override WKWebView, so we can detect file drop in obj-c -// and grab their file path, to then inject into JS -@interface WailsWebView : WKWebView -@property bool disableWebViewDragAndDrop; -@property bool enableDragAndDrop; -@end - -#endif /* WailsWebView_h */ diff --git a/v2/internal/frontend/desktop/darwin/WailsWebView.m b/v2/internal/frontend/desktop/darwin/WailsWebView.m deleted file mode 100644 index de23ac794..000000000 --- a/v2/internal/frontend/desktop/darwin/WailsWebView.m +++ /dev/null @@ -1,122 +0,0 @@ -#import "WailsWebView.h" -#import "message.h" - - -@implementation WailsWebView -@synthesize disableWebViewDragAndDrop; -@synthesize enableDragAndDrop; - -- (BOOL)prepareForDragOperation:(id)sender -{ - if ( !enableDragAndDrop ) { - return [super prepareForDragOperation: sender]; - } - - if ( disableWebViewDragAndDrop ) { - return YES; - } - - return [super prepareForDragOperation: sender]; -} - -- (BOOL)performDragOperation:(id )sender -{ - if ( !enableDragAndDrop ) { - return [super performDragOperation: sender]; - } - - NSPasteboard *pboard = [sender draggingPasteboard]; - - // if no types, then we'll just let the WKWebView handle the drag-n-drop as normal - NSArray * types = [pboard types]; - if( !types ) - return [super performDragOperation: sender]; - - // getting all NSURL types - NSArray *url_class = @[[NSURL class]]; - NSDictionary *options = @{}; - NSArray *files = [pboard readObjectsForClasses:url_class options:options]; - - // collecting all file paths - NSMutableArray *files_strs = [[NSMutableArray alloc] init]; - for (NSURL *url in files) - { - const char *fs_path = [url fileSystemRepresentation]; //Will be UTF-8 encoded - NSString *fs_path_str = [[NSString alloc] initWithCString:fs_path encoding:NSUTF8StringEncoding]; - [files_strs addObject:fs_path_str]; -// NSLog( @"performDragOperation: file path: %s", fs_path ); - } - - NSString *joined=[files_strs componentsJoinedByString:@"\n"]; - - // Release the array of file paths - [files_strs release]; - - int dragXLocation = [sender draggingLocation].x - [self frame].origin.x; - int dragYLocation = [self frame].size.height - [sender draggingLocation].y; // Y coordinate is inverted, so we need to subtract from the height - -// NSLog( @"draggingUpdated: X coord: %d", dragXLocation ); -// NSLog( @"draggingUpdated: Y coord: %d", dragYLocation ); - - NSString *message = [NSString stringWithFormat:@"DD:%d:%d:%@", dragXLocation, dragYLocation, joined]; - - const char* res = message.UTF8String; - - processMessage(res); - - if ( disableWebViewDragAndDrop ) { - return YES; - } - - return [super performDragOperation: sender]; -} - -- (NSDragOperation)draggingUpdated:(id )sender { - if ( !enableDragAndDrop ) { - return [super draggingUpdated: sender]; - } - - NSPasteboard *pboard = [sender draggingPasteboard]; - - // if no types, then we'll just let the WKWebView handle the drag-n-drop as normal - NSArray * types = [pboard types]; - if( !types ) { - return [super draggingUpdated: sender]; - } - - if ( disableWebViewDragAndDrop ) { - // we should call supper as otherwise events will not pass - [super draggingUpdated: sender]; - - // pass NSDragOperationGeneric = 4 to show regular hover for drag and drop. As we want to ignore webkit behaviours that depends on webpage - return 4; - } - - return [super draggingUpdated: sender]; -} - -- (NSDragOperation)draggingEntered:(id )sender { - if ( !enableDragAndDrop ) { - return [super draggingEntered: sender]; - } - - NSPasteboard *pboard = [sender draggingPasteboard]; - - // if no types, then we'll just let the WKWebView handle the drag-n-drop as normal - NSArray * types = [pboard types]; - if( !types ) { - return [super draggingEntered: sender]; - } - - if ( disableWebViewDragAndDrop ) { - // we should call supper as otherwise events will not pass - [super draggingEntered: sender]; - - // pass NSDragOperationGeneric = 4 to show regular hover for drag and drop. As we want to ignore webkit behaviours that depends on webpage - return 4; - } - - return [super draggingEntered: sender]; -} - -@end diff --git a/v2/internal/frontend/desktop/darwin/browser.go b/v2/internal/frontend/desktop/darwin/browser.go index c865ab6d9..417501c8e 100644 --- a/v2/internal/frontend/desktop/darwin/browser.go +++ b/v2/internal/frontend/desktop/darwin/browser.go @@ -4,21 +4,11 @@ package darwin import ( - "fmt" "github.com/pkg/browser" - "github.com/wailsapp/wails/v2/internal/frontend/utils" ) // BrowserOpenURL Use the default browser to open the url -func (f *Frontend) BrowserOpenURL(rawURL string) { - url, err := utils.ValidateAndSanitizeURL(rawURL) - if err != nil { - f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error())) - return - } - +func (f *Frontend) BrowserOpenURL(url string) { // Specific method implementation - if err := browser.OpenURL(url); err != nil { - f.logger.Error("Unable to open default system browser") - } + _ = browser.OpenURL(url) } diff --git a/v2/internal/frontend/desktop/darwin/callbacks.go b/v2/internal/frontend/desktop/darwin/callbacks.go index ab0d18e47..7d930a2f9 100644 --- a/v2/internal/frontend/desktop/darwin/callbacks.go +++ b/v2/internal/frontend/desktop/darwin/callbacks.go @@ -12,7 +12,6 @@ package darwin #include */ import "C" - import ( "errors" "strconv" @@ -21,6 +20,7 @@ import ( ) func (f *Frontend) handleCallback(menuItemID uint) error { + menuItem := getMenuItemForID(menuItemID) if menuItem == nil { return errors.New("unknown menuItem ID: " + strconv.Itoa(int(menuItemID))) diff --git a/v2/internal/frontend/desktop/darwin/clipboard.go b/v2/internal/frontend/desktop/darwin/clipboard.go index c40ba8771..eea6c79ae 100644 --- a/v2/internal/frontend/desktop/darwin/clipboard.go +++ b/v2/internal/frontend/desktop/darwin/clipboard.go @@ -16,6 +16,7 @@ func (f *Frontend) ClipboardGetText() (string, error) { } func (f *Frontend) ClipboardSetText(text string) error { + copyCmd := exec.Command("pbcopy") in, err := copyCmd.StdinPipe() if err != nil { diff --git a/v2/internal/frontend/desktop/darwin/dialog.go b/v2/internal/frontend/desktop/darwin/dialog.go index 66bb2f13a..c6be559cb 100644 --- a/v2/internal/frontend/desktop/darwin/dialog.go +++ b/v2/internal/frontend/desktop/darwin/dialog.go @@ -11,7 +11,6 @@ package darwin #import "WailsContext.h" */ import "C" - import ( "encoding/json" "fmt" @@ -24,12 +23,10 @@ import ( ) // Obj-C dialog methods send the response to this channel -var ( - messageDialogResponse = make(chan int) - openFileDialogResponse = make(chan string) - saveFileDialogResponse = make(chan string) - dialogLock sync.Mutex -) +var messageDialogResponse = make(chan int) +var openFileDialogResponse = make(chan string) +var saveFileDialogResponse = make(chan string) +var dialogLock sync.Mutex // OpenDirectoryDialog prompts the user to select a directory func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) { @@ -77,7 +74,7 @@ func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool filters := filterStrings.Join(";") C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters)) - result := <-openFileDialogResponse + var result = <-openFileDialogResponse var parsedResults []string err := json.Unmarshal([]byte(result), &parsedResults) @@ -133,7 +130,7 @@ func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, e filters := filterStrings.Join(";") C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters)) - result := <-saveFileDialogResponse + var result = <-saveFileDialogResponse return result, nil } @@ -168,7 +165,7 @@ func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength) - result := <-messageDialogResponse + var result = <-messageDialogResponse selectedC := buttons[result] var selected string diff --git a/v2/internal/frontend/desktop/darwin/frontend.go b/v2/internal/frontend/desktop/darwin/frontend.go index 6566445d5..b714a2c3b 100644 --- a/v2/internal/frontend/desktop/darwin/frontend.go +++ b/v2/internal/frontend/desktop/darwin/frontend.go @@ -8,13 +8,11 @@ package darwin #cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit #import #import "Application.h" -#import "CustomProtocol.h" #import "WailsContext.h" #include */ import "C" - import ( "context" "encoding/json" @@ -23,7 +21,6 @@ import ( "log" "net" "net/url" - "os" "unsafe" "github.com/wailsapp/wails/v2/pkg/assetserver" @@ -31,7 +28,6 @@ import ( "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" - "github.com/wailsapp/wails/v2/internal/frontend/originvalidator" "github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/pkg/options" @@ -39,33 +35,18 @@ import ( const startURL = "wails://wails/" -type bindingsMessage struct { - message string - source string - isMainFrame bool -} - -var ( - messageBuffer = make(chan string, 100) - bindingsMessageBuffer = make(chan *bindingsMessage, 100) - requestBuffer = make(chan webview.Request, 100) - callbackBuffer = make(chan uint, 10) - openFilepathBuffer = make(chan string, 100) - openUrlBuffer = make(chan string, 100) - secondInstanceBuffer = make(chan options.SecondInstanceData, 1) -) +var messageBuffer = make(chan string, 100) +var requestBuffer = make(chan webview.Request, 100) +var callbackBuffer = make(chan uint, 10) type Frontend struct { + // Context ctx context.Context frontendOptions *options.App logger *logger.Logger debug bool - devtoolsEnabled bool - - // Keep single instance lock file, so that it will not be GC and lock will exist while app is running - singleInstanceLockFile *os.File // Assets assets *assetserver.AssetServer @@ -75,8 +56,6 @@ type Frontend struct { mainWindow *Window bindings *binding.Bindings dispatcher frontend.Dispatcher - - originValidator *originvalidator.OriginValidator } func (f *Frontend) RunMainLoop() { @@ -96,18 +75,12 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. ctx: ctx, } result.startURL, _ = url.Parse(startURL) - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) - - // this should be initialized as early as possible to handle first instance launch - C.StartCustomProtocolHandler() if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { result.startURL = _starturl - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) } else { if port, _ := ctx.Value("assetserverport").(string); port != "" { result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port) - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) } var bindings string @@ -132,72 +105,21 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } go result.startMessageProcessor() - go result.startBindingsMessageProcessor() go result.startCallbackProcessor() - go result.startFileOpenProcessor() - go result.startUrlOpenProcessor() - go result.startSecondInstanceProcessor() return result } -func (f *Frontend) startFileOpenProcessor() { - for filePath := range openFilepathBuffer { - f.ProcessOpenFileEvent(filePath) - } -} - -func (f *Frontend) startUrlOpenProcessor() { - for url := range openUrlBuffer { - f.ProcessOpenUrlEvent(url) - } -} - -func (f *Frontend) startSecondInstanceProcessor() { - for secondInstanceData := range secondInstanceBuffer { - if f.frontendOptions.SingleInstanceLock != nil && - f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { - f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) - } - } -} - func (f *Frontend) startMessageProcessor() { for message := range messageBuffer { f.processMessage(message) } } - -func (f *Frontend) startBindingsMessageProcessor() { - for msg := range bindingsMessageBuffer { - // Apple webkit doesn't provide origin of main frame. So we can't verify in case of iFrame that top level origin is allowed. - if !msg.isMainFrame { - f.logger.Error("Blocked request from not main frame") - continue - } - - origin, err := f.originValidator.GetOriginFromURL(msg.source) - if err != nil { - f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err)) - continue - } - - allowed := f.originValidator.IsOriginAllowed(origin) - if !allowed { - f.logger.Error("Blocked request from unauthorized origin: %s", origin) - continue - } - - f.processMessage(msg.message) - } -} - func (f *Frontend) startRequestProcessor() { for request := range requestBuffer { f.assets.ServeWebViewRequest(request) } } - func (f *Frontend) startCallbackProcessor() { for callback := range callbackBuffer { err := f.handleCallback(callback) @@ -216,32 +138,25 @@ func (f *Frontend) WindowReloadApp() { } func (f *Frontend) WindowSetSystemDefaultTheme() { + return } func (f *Frontend) WindowSetLightTheme() { + return } func (f *Frontend) WindowSetDarkTheme() { + return } func (f *Frontend) Run(ctx context.Context) error { f.ctx = ctx - - if f.frontendOptions.SingleInstanceLock != nil { - f.singleInstanceLockFile = SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) - } - - _debug := ctx.Value("debug") - _devtoolsEnabled := ctx.Value("devtoolsEnabled") - + var _debug = ctx.Value("debug") if _debug != nil { f.debug = _debug.(bool) } - if _devtoolsEnabled != nil { - f.devtoolsEnabled = _devtoolsEnabled.(bool) - } - mainWindow := NewWindow(f.frontendOptions, f.debug, f.devtoolsEnabled) + mainWindow := NewWindow(f.frontendOptions, f.debug) f.mainWindow = mainWindow f.mainWindow.Center() @@ -257,7 +172,6 @@ func (f *Frontend) Run(ctx context.Context) error { func (f *Frontend) WindowCenter() { f.mainWindow.Center() } - func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) { f.mainWindow.SetAlwaysOnTop(onTop) } @@ -265,7 +179,6 @@ func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) { func (f *Frontend) WindowSetPosition(x, y int) { f.mainWindow.SetPosition(x, y) } - func (f *Frontend) WindowGetPosition() (int, int) { return f.mainWindow.GetPosition() } @@ -297,7 +210,6 @@ func (f *Frontend) WindowShow() { func (f *Frontend) WindowHide() { f.mainWindow.Hide() } - func (f *Frontend) Show() { f.mainWindow.ShowApplication() } @@ -305,23 +217,18 @@ func (f *Frontend) Show() { func (f *Frontend) Hide() { f.mainWindow.HideApplication() } - func (f *Frontend) WindowMaximise() { f.mainWindow.Maximise() } - func (f *Frontend) WindowToggleMaximise() { f.mainWindow.ToggleMaximise() } - func (f *Frontend) WindowUnmaximise() { f.mainWindow.UnMaximise() } - func (f *Frontend) WindowMinimise() { f.mainWindow.Minimise() } - func (f *Frontend) WindowUnminimise() { f.mainWindow.UnMinimise() } @@ -329,7 +236,6 @@ func (f *Frontend) WindowUnminimise() { func (f *Frontend) WindowSetMinSize(width int, height int) { f.mainWindow.SetMinSize(width, height) } - func (f *Frontend) WindowSetMaxSize(width int, height int) { f.mainWindow.SetMaxSize(width, height) } @@ -373,10 +279,6 @@ func (f *Frontend) Quit() { f.mainWindow.Quit() } -func (f *Frontend) WindowPrint() { - f.mainWindow.Print() -} - type EventNotify struct { Name string `json:"name"` Data []interface{} `json:"data"` @@ -396,6 +298,7 @@ func (f *Frontend) Notify(name string, data ...interface{}) { } func (f *Frontend) processMessage(message string) { + if message == "DomReady" { if f.frontendOptions.OnDomReady != nil { f.frontendOptions.OnDomReady(f.ctx) @@ -406,16 +309,6 @@ func (f *Frontend) processMessage(message string) { if message == "runtime:ready" { cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue) f.ExecJS(cmd) - - if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.EnableFileDrop { - f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;") - } - - return - } - - if message == "wails:openInspector" { - showInspector(f.mainWindow.context) return } @@ -443,18 +336,7 @@ func (f *Frontend) processMessage(message string) { f.logger.Info("Unknown message returned from dispatcher: %+v", result) } }() -} -func (f *Frontend) ProcessOpenFileEvent(filePath string) { - if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnFileOpen != nil { - f.frontendOptions.Mac.OnFileOpen(filePath) - } -} - -func (f *Frontend) ProcessOpenUrlEvent(url string) { - if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnUrlOpen != nil { - f.frontendOptions.Mac.OnUrlOpen(url) - } } func (f *Frontend) Callback(message string) { @@ -491,35 +373,12 @@ func processMessage(message *C.char) { messageBuffer <- goMessage } -//export processBindingMessage -func processBindingMessage(message *C.char, source *C.char, fromMainFrame bool) { - goMessage := C.GoString(message) - goSource := C.GoString(source) - bindingsMessageBuffer <- &bindingsMessage{ - message: goMessage, - source: goSource, - isMainFrame: fromMainFrame, - } -} - //export processCallback func processCallback(callbackID uint) { callbackBuffer <- callbackID } //export processURLRequest -func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) { +func processURLRequest(ctx unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) { requestBuffer <- webview.NewRequest(wkURLSchemeTask) } - -//export HandleOpenFile -func HandleOpenFile(filePath *C.char) { - goFilepath := C.GoString(filePath) - openFilepathBuffer <- goFilepath -} - -//export HandleOpenURL -func HandleOpenURL(url *C.char) { - goUrl := C.GoString(url) - openUrlBuffer <- goUrl -} diff --git a/v2/internal/frontend/desktop/darwin/inspector.go b/v2/internal/frontend/desktop/darwin/inspector.go index dc3f08969..3021c5c16 100644 --- a/v2/internal/frontend/desktop/darwin/inspector.go +++ b/v2/internal/frontend/desktop/darwin/inspector.go @@ -1,4 +1,4 @@ -//go:build darwin && !(dev || debug || devtools) +//go:build darwin && !(dev || debug) package darwin @@ -6,5 +6,6 @@ import ( "unsafe" ) -func showInspector(_ unsafe.Pointer) { +func showInspector(context unsafe.Pointer) { + } diff --git a/v2/internal/frontend/desktop/darwin/inspector_dev.go b/v2/internal/frontend/desktop/darwin/inspector_dev.go index e79b9c3e7..e948435cb 100644 --- a/v2/internal/frontend/desktop/darwin/inspector_dev.go +++ b/v2/internal/frontend/desktop/darwin/inspector_dev.go @@ -1,4 +1,4 @@ -//go:build darwin && (dev || debug || devtools) +//go:build darwin && (dev || debug) package darwin @@ -11,8 +11,6 @@ package darwin #import #import "WailsContext.h" -extern void processMessage(const char *message); - @interface _WKInspector : NSObject - (void)show; - (void)detach; @@ -23,45 +21,28 @@ extern void processMessage(const char *message); @end void showInspector(void *inctx) { -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 - ON_MAIN_THREAD( - if (@available(macOS 12.0, *)) { - WailsContext *ctx = (__bridge WailsContext*) inctx; + if (@available(macOS 12.0, *)) { + WailsContext *ctx = (__bridge WailsContext*) inctx; + @try { + [ctx.webview._inspector show]; + } @catch (NSException *exception) { + NSLog(@"Opening the inspector failed: %@", exception.reason); + return; + } + + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + // Detach must be deferred a little bit and is ignored directly after a show. @try { - [ctx.webview._inspector show]; + [ctx.webview._inspector detach]; } @catch (NSException *exception) { - NSLog(@"Opening the inspector failed: %@", exception.reason); - return; + NSLog(@"Detaching the inspector failed: %@", exception.reason); } - - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - // Detach must be deferred a little bit and is ignored directly after a show. - @try { - [ctx.webview._inspector detach]; - } @catch (NSException *exception) { - NSLog(@"Detaching the inspector failed: %@", exception.reason); - } - }); - } else { - NSLog(@"Opening the inspector needs at least MacOS 12"); - } - ); -#endif -} - -void setupF12hotkey() { - [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { - if (event.keyCode == 111 && - event.modifierFlags & NSEventModifierFlagFunction && - event.modifierFlags & NSEventModifierFlagCommand && - event.modifierFlags & NSEventModifierFlagShift) { - processMessage("wails:openInspector"); - return nil; - } - return event; - }]; + }); + } else { + NSLog(@"Opening the inspector needs at least MacOS 12"); + } } */ import "C" @@ -69,10 +50,6 @@ import ( "unsafe" ) -func init() { - C.setupF12hotkey() -} - func showInspector(context unsafe.Pointer) { C.showInspector(context) } diff --git a/v2/internal/frontend/desktop/darwin/main.m b/v2/internal/frontend/desktop/darwin/main.m index 75a84dc76..d2f39bccb 100644 --- a/v2/internal/frontend/desktop/darwin/main.m +++ b/v2/internal/frontend/desktop/darwin/main.m @@ -203,7 +203,6 @@ int main(int argc, const char * argv[]) { // insert code here... int frameless = 0; int resizable = 1; - int zoomable = 0; int fullscreen = 1; int fullSizeContent = 1; int hideTitleBar = 0; @@ -216,11 +215,10 @@ int main(int argc, const char * argv[]) { int hideWindowOnClose = 0; const char* appearance = "NSAppearanceNameDarkAqua"; int windowIsTranslucent = 1; - int devtoolsEnabled = 1; - int defaultContextMenuEnabled = 1; + int debug = 1; int windowStartState = 0; int startsHidden = 0; - WailsContext *result = Create("OI OI!",400,400, frameless, resizable, zoomable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, devtoolsEnabled, defaultContextMenuEnabled, windowStartState, + WailsContext *result = Create("OI OI!",400,400, frameless, resizable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, debug, windowStartState, startsHidden, 400, 400, 600, 600, false); SetBackgroundColour(result, 255, 0, 0, 255); void *m = NewMenu(""); diff --git a/v2/internal/frontend/desktop/darwin/menu.go b/v2/internal/frontend/desktop/darwin/menu.go index 24dbe3201..08090f89a 100644 --- a/v2/internal/frontend/desktop/darwin/menu.go +++ b/v2/internal/frontend/desktop/darwin/menu.go @@ -13,7 +13,6 @@ package darwin #include */ import "C" - import ( "unsafe" @@ -123,6 +122,7 @@ func processMenuItem(parent *NSMenu, menuItem *menu.MenuItem) *MenuItem { } return parent.AddMenuItem(menuItem) + } func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) { diff --git a/v2/internal/frontend/desktop/darwin/menuitem.go b/v2/internal/frontend/desktop/darwin/menuitem.go index 64aab84a9..00ad57aa3 100644 --- a/v2/internal/frontend/desktop/darwin/menuitem.go +++ b/v2/internal/frontend/desktop/darwin/menuitem.go @@ -13,19 +13,16 @@ package darwin #include */ import "C" - import ( "log" "math" "sync" ) -var ( - menuItemToID = make(map[*MenuItem]uint) - idToMenuItem = make(map[uint]*MenuItem) - menuItemLock sync.Mutex - menuItemIDCounter uint = 0 -) +var menuItemToID = make(map[*MenuItem]uint) +var idToMenuItem = make(map[uint]*MenuItem) +var menuItemLock sync.Mutex +var menuItemIDCounter uint = 0 func createMenuItemID(item *MenuItem) uint { menuItemLock.Lock() diff --git a/v2/internal/frontend/desktop/darwin/message.h b/v2/internal/frontend/desktop/darwin/message.h index 86506f868..66110841d 100644 --- a/v2/internal/frontend/desktop/darwin/message.h +++ b/v2/internal/frontend/desktop/darwin/message.h @@ -15,7 +15,6 @@ extern "C" #endif void processMessage(const char *); -void processBindingMessage(const char *, const char *, bool); void processURLRequest(void *, void*); void processMessageDialogResponse(int); void processOpenFileDialogResponse(const char*); diff --git a/v2/internal/frontend/desktop/darwin/notifications.go b/v2/internal/frontend/desktop/darwin/notifications.go deleted file mode 100644 index b788841e0..000000000 --- a/v2/internal/frontend/desktop/darwin/notifications.go +++ /dev/null @@ -1,465 +0,0 @@ -//go:build darwin -// +build darwin - -package darwin - -/* -#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 "Application.h" -#import "WailsContext.h" -*/ -import "C" -import ( - "context" - "encoding/json" - "fmt" - "os" - "sync" - "time" - "unsafe" - - "github.com/wailsapp/wails/v2/internal/frontend" -) - -// Package-scoped variable only accessible within this file -var ( - currentFrontend *Frontend - frontendMutex sync.RWMutex - // Notification channels - channels map[int]chan notificationChannel - channelsLock sync.Mutex - nextChannelID int - - notificationResultCallback func(result frontend.NotificationResult) - callbackLock sync.RWMutex -) - -const DefaultActionIdentifier = "DEFAULT_ACTION" -const AppleDefaultActionIdentifier = "com.apple.UNNotificationDefaultActionIdentifier" - -// setCurrentFrontend sets the current frontend instance -// This is called when RequestNotificationAuthorization or CheckNotificationAuthorization is called -func setCurrentFrontend(f *Frontend) { - frontendMutex.Lock() - defer frontendMutex.Unlock() - currentFrontend = f -} - -// getCurrentFrontend gets the current frontend instance -func getCurrentFrontend() *Frontend { - frontendMutex.RLock() - defer frontendMutex.RUnlock() - return currentFrontend -} - -type notificationChannel struct { - Success bool - Error error -} - -func (f *Frontend) InitializeNotifications() error { - if !f.IsNotificationAvailable() { - return fmt.Errorf("notifications are not available on this system") - } - if !f.checkBundleIdentifier() { - return fmt.Errorf("notifications require a valid bundle identifier") - } - if !bool(C.EnsureDelegateInitialized(f.mainWindow.context)) { - return fmt.Errorf("failed to initialize notification center delegate") - } - - channels = make(map[int]chan notificationChannel) - nextChannelID = 0 - - setCurrentFrontend(f) - - return nil -} - -// CleanupNotifications is a macOS stub that does nothing. -// (Linux-specific cleanup) -func (f *Frontend) CleanupNotifications() { - // No cleanup needed on macOS -} - -func (f *Frontend) IsNotificationAvailable() bool { - return bool(C.IsNotificationAvailable(f.mainWindow.context)) -} - -func (f *Frontend) checkBundleIdentifier() bool { - return bool(C.CheckBundleIdentifier(f.mainWindow.context)) -} - -func (f *Frontend) RequestNotificationAuthorization() (bool, error) { - ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second) - defer cancel() - - id, resultCh := f.registerChannel() - - C.RequestNotificationAuthorization(f.mainWindow.context, C.int(id)) - - select { - case result := <-resultCh: - close(resultCh) - return result.Success, result.Error - case <-ctx.Done(): - f.cleanupChannel(id) - return false, fmt.Errorf("notification authorization timed out after 3 minutes: %w", ctx.Err()) - } -} - -func (f *Frontend) CheckNotificationAuthorization() (bool, error) { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - id, resultCh := f.registerChannel() - - C.CheckNotificationAuthorization(f.mainWindow.context, C.int(id)) - - select { - case result := <-resultCh: - close(resultCh) - return result.Success, result.Error - case <-ctx.Done(): - f.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 (f *Frontend) SendNotification(options frontend.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 := f.registerChannel() - C.SendNotification(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cDataJSON) - - select { - case result := <-resultCh: - close(resultCh) - if !result.Success { - if result.Error != nil { - return result.Error - } - return fmt.Errorf("sending notification failed") - } - return nil - case <-ctx.Done(): - f.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 (f *Frontend) SendNotificationWithActions(options frontend.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 := f.registerChannel() - C.SendNotificationWithActions(f.mainWindow.context, C.int(id), cIdentifier, cTitle, cSubtitle, cBody, cCategoryID, cDataJSON) - - select { - case result := <-resultCh: - close(resultCh) - if !result.Success { - if result.Error != nil { - return result.Error - } - return fmt.Errorf("sending notification failed") - } - return nil - case <-ctx.Done(): - f.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 (f *Frontend) RegisterNotificationCategory(category frontend.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 := f.registerChannel() - C.RegisterNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID, cActionsJSON, C.bool(category.HasReplyField), - cReplyPlaceholder, cReplyButtonTitle) - - select { - case result := <-resultCh: - close(resultCh) - if !result.Success { - if result.Error != nil { - return result.Error - } - return fmt.Errorf("category registration failed") - } - return nil - case <-ctx.Done(): - f.cleanupChannel(id) - return fmt.Errorf("category registration timed out: %w", ctx.Err()) - } -} - -// RemoveNotificationCategory remove a previously registered NotificationCategory. -func (f *Frontend) 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 := f.registerChannel() - C.RemoveNotificationCategory(f.mainWindow.context, C.int(id), cCategoryID) - - select { - case result := <-resultCh: - close(resultCh) - if !result.Success { - if result.Error != nil { - return result.Error - } - return fmt.Errorf("category removal failed") - } - return nil - case <-ctx.Done(): - f.cleanupChannel(id) - return fmt.Errorf("category removal timed out: %w", ctx.Err()) - } -} - -// RemoveAllPendingNotifications removes all pending notifications. -func (f *Frontend) RemoveAllPendingNotifications() error { - C.RemoveAllPendingNotifications(f.mainWindow.context) - return nil -} - -// RemovePendingNotification removes a pending notification matching the unique identifier. -func (f *Frontend) RemovePendingNotification(identifier string) error { - cIdentifier := C.CString(identifier) - defer C.free(unsafe.Pointer(cIdentifier)) - C.RemovePendingNotification(f.mainWindow.context, cIdentifier) - return nil -} - -// RemoveAllDeliveredNotifications removes all delivered notifications. -func (f *Frontend) RemoveAllDeliveredNotifications() error { - C.RemoveAllDeliveredNotifications(f.mainWindow.context) - return nil -} - -// RemoveDeliveredNotification removes a delivered notification matching the unique identifier. -func (f *Frontend) RemoveDeliveredNotification(identifier string) error { - cIdentifier := C.CString(identifier) - defer C.free(unsafe.Pointer(cIdentifier)) - C.RemoveDeliveredNotification(f.mainWindow.context, 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 (f *Frontend) RemoveNotification(identifier string) error { - return nil -} - -func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) { - callbackLock.Lock() - notificationResultCallback = callback - callbackLock.Unlock() -} - -//export captureResult -func captureResult(channelID C.int, success C.bool, errorMsg *C.char) { - f := getCurrentFrontend() - if f == nil { - return - } - - resultCh, exists := f.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, - } -} - -//export didReceiveNotificationResponse -func didReceiveNotificationResponse(jsonPayload *C.char, err *C.char) { - result := frontend.NotificationResult{} - - if err != nil { - errMsg := C.GoString(err) - result.Error = fmt.Errorf("notification response error: %s", errMsg) - handleNotificationResult(result) - - return - } - - if jsonPayload == nil { - result.Error = fmt.Errorf("received nil JSON payload in notification response") - handleNotificationResult(result) - return - } - - payload := C.GoString(jsonPayload) - - var response frontend.NotificationResponse - if err := json.Unmarshal([]byte(payload), &response); err != nil { - result.Error = fmt.Errorf("failed to unmarshal notification response: %w", err) - handleNotificationResult(result) - return - } - - if response.ActionIdentifier == AppleDefaultActionIdentifier { - response.ActionIdentifier = DefaultActionIdentifier - } - - result.Response = response - handleNotificationResult(result) -} - -func handleNotificationResult(result frontend.NotificationResult) { - callbackLock.Lock() - callback := notificationResultCallback - callbackLock.Unlock() - - if callback != nil { - go func() { - defer func() { - if r := recover(); r != nil { - // Log panic but don't crash the app - fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r) - } - }() - callback(result) - }() - } -} - -// Helper methods - -func (f *Frontend) registerChannel() (int, chan notificationChannel) { - channelsLock.Lock() - defer channelsLock.Unlock() - - // Initialize channels map if it's nil - if channels == nil { - channels = make(map[int]chan notificationChannel) - nextChannelID = 0 - } - - id := nextChannelID - nextChannelID++ - - resultCh := make(chan notificationChannel, 1) - - channels[id] = resultCh - return id, resultCh -} - -func (f *Frontend) GetChannel(id int) (chan notificationChannel, bool) { - channelsLock.Lock() - defer channelsLock.Unlock() - - if channels == nil { - return nil, false - } - - ch, exists := channels[id] - if exists { - delete(channels, id) - } - return ch, exists -} - -func (f *Frontend) cleanupChannel(id int) { - channelsLock.Lock() - defer channelsLock.Unlock() - - if channels == nil { - return - } - - if ch, exists := channels[id]; exists { - delete(channels, id) - close(ch) - } -} diff --git a/v2/internal/frontend/desktop/darwin/screen.go b/v2/internal/frontend/desktop/darwin/screen.go index bd64a31f9..7b552065a 100644 --- a/v2/internal/frontend/desktop/darwin/screen.go +++ b/v2/internal/frontend/desktop/darwin/screen.go @@ -18,8 +18,6 @@ typedef struct Screen { int isPrimary; int height; int width; - int pHeight; - int pWidth; } Screen; @@ -50,43 +48,14 @@ Screen GetNthScreen(int nth, void *inctx){ returnScreen.isPrimary = nth==0; returnScreen.height = (int) nthScreen.frame.size.height; returnScreen.width = (int) nthScreen.frame.size.width; - - returnScreen.pWidth = 0; - returnScreen.pHeight = 0; - - // https://stackoverflow.com/questions/13859109/how-to-programmatically-determine-native-pixel-resolution-of-retina-macbook-pro - CGDirectDisplayID sid = ((NSNumber *)[nthScreen.deviceDescription - objectForKey:@"NSScreenNumber"]).unsignedIntegerValue; - - CFArrayRef ms = CGDisplayCopyAllDisplayModes(sid, NULL); - CFIndex n = CFArrayGetCount(ms); - for (int i = 0; i < n; i++) { - CGDisplayModeRef m = (CGDisplayModeRef) CFArrayGetValueAtIndex(ms, i); - if (CGDisplayModeGetIOFlags(m) & kDisplayModeNativeFlag) { - // This corresponds with "System Settings" -> General -> About -> Displays - returnScreen.pWidth = CGDisplayModeGetPixelWidth(m); - returnScreen.pHeight = CGDisplayModeGetPixelHeight(m); - break; - } - } - CFRelease(ms); - - if (returnScreen.pWidth == 0 || returnScreen.pHeight == 0) { - // If there was no native resolution take a best fit approach and use the backing pixel size. - NSRect pSize = [nthScreen convertRectToBacking:nthScreen.frame]; - returnScreen.pHeight = (int) pSize.size.height; - returnScreen.pWidth = (int) pSize.size.width; - } return returnScreen; } */ import "C" - import ( - "unsafe" - "github.com/wailsapp/wails/v2/internal/frontend" + "unsafe" ) func GetAllScreens(wailsContext unsafe.Pointer) ([]frontend.Screen, error) { @@ -96,21 +65,11 @@ func GetAllScreens(wailsContext unsafe.Pointer) ([]frontend.Screen, error) { for screeNum := 0; screeNum < numScreens; screeNum++ { screenNumC := C.int(screeNum) cScreen := C.GetNthScreen(screenNumC, wailsContext) - screen := frontend.Screen{ Height: int(cScreen.height), Width: int(cScreen.width), IsCurrent: cScreen.isCurrent == C.int(1), IsPrimary: cScreen.isPrimary == C.int(1), - - Size: frontend.ScreenSize{ - Height: int(cScreen.height), - Width: int(cScreen.width), - }, - PhysicalSize: frontend.ScreenSize{ - Height: int(cScreen.pHeight), - Width: int(cScreen.pWidth), - }, } screens = append(screens, screen) } diff --git a/v2/internal/frontend/desktop/darwin/single_instance.go b/v2/internal/frontend/desktop/darwin/single_instance.go deleted file mode 100644 index 27b34045b..000000000 --- a/v2/internal/frontend/desktop/darwin/single_instance.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build darwin -// +build darwin - -package darwin - -/* -#cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Foundation -framework Cocoa -#import "AppDelegate.h" - -*/ -import "C" - -import ( - "encoding/json" - "fmt" - "os" - "strings" - "syscall" - "unsafe" - - "github.com/wailsapp/wails/v2/pkg/options" -) - -func SetupSingleInstance(uniqueID string) *os.File { - lockFilePath := getTempDir() - lockFileName := uniqueID + ".lock" - file, err := createLockFile(lockFilePath + "/" + lockFileName) - // if lockFile exist – send notification to second instance - if err != nil { - c := NewCalloc() - defer c.Free() - singleInstanceUniqueId := c.String(uniqueID) - - data, err := options.NewSecondInstanceData() - if err != nil { - return nil - } - - serialized, err := json.Marshal(data) - if err != nil { - return nil - } - - C.SendDataToFirstInstance(singleInstanceUniqueId, c.String(string(serialized))) - - os.Exit(0) - } - - return file -} - -//export HandleSecondInstanceData -func HandleSecondInstanceData(secondInstanceMessage *C.char) { - message := C.GoString(secondInstanceMessage) - - var secondInstanceData options.SecondInstanceData - - err := json.Unmarshal([]byte(message), &secondInstanceData) - if err == nil { - secondInstanceBuffer <- secondInstanceData - } -} - -// 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, 0o600) - if err != nil { - fmt.Printf("Failed to open lockfile %s: %s", filename, err) - return nil, err - } - - err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) - if err != nil { - // Flock failed for some other reason than other instance already lock it. Print it in logs for possible debugging. - if !strings.Contains(err.Error(), "resource temporarily unavailable") { - fmt.Printf("Failed to lock lockfile %s: %s", filename, err) - } - file.Close() - return nil, err - } - - return file, nil -} - -// If app is sandboxed, golang os.TempDir() will return path that will not be accessible. So use native macOS temp dir function. -func getTempDir() string { - cstring := C.GetMacOsNativeTempDir() - path := C.GoString(cstring) - C.free(unsafe.Pointer(cstring)) - - return path -} diff --git a/v2/internal/frontend/desktop/darwin/window.go b/v2/internal/frontend/desktop/darwin/window.go index 87d4213d9..8b4c77799 100644 --- a/v2/internal/frontend/desktop/darwin/window.go +++ b/v2/internal/frontend/desktop/darwin/window.go @@ -13,7 +13,6 @@ package darwin #include */ import "C" - import ( "log" "runtime" @@ -32,8 +31,6 @@ func init() { type Window struct { context unsafe.Pointer - - applicationMenu *menu.Menu } func bool2Cint(value bool) C.int { @@ -43,12 +40,8 @@ func bool2Cint(value bool) C.int { return C.int(0) } -func bool2CboolPtr(value bool) *C.bool { - v := C.bool(value) - return &v -} +func NewWindow(frontendOptions *options.App, debugMode bool) *Window { -func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window { c := NewCalloc() defer c.Free() @@ -58,14 +51,11 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window alwaysOnTop := bool2Cint(frontendOptions.AlwaysOnTop) hideWindowOnClose := bool2Cint(frontendOptions.HideWindowOnClose) startsHidden := bool2Cint(frontendOptions.StartHidden) - devtoolsEnabled := bool2Cint(devtools) - defaultContextMenuEnabled := bool2Cint(debug || frontendOptions.EnableDefaultContextMenu) - singleInstanceEnabled := bool2Cint(frontendOptions.SingleInstanceLock != nil) + debug := bool2Cint(debugMode) - var fullSizeContent, hideTitleBar, zoomable, hideTitle, useToolbar, webviewIsTransparent C.int - var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent, contentProtection C.int + var fullSizeContent, hideTitleBar, hideTitle, useToolbar, webviewIsTransparent C.int + var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent C.int var appearance, title *C.char - var preferences C.struct_Preferences width := C.int(frontendOptions.Width) height := C.int(frontendOptions.Height) @@ -77,17 +67,8 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window title = c.String(frontendOptions.Title) - singleInstanceUniqueIdStr := "" - if frontendOptions.SingleInstanceLock != nil { - singleInstanceUniqueIdStr = frontendOptions.SingleInstanceLock.UniqueId - } - singleInstanceUniqueId := c.String(singleInstanceUniqueIdStr) - enableFraudulentWebsiteWarnings := C.bool(frontendOptions.EnableFraudulentWebsiteDetection) - enableDragAndDrop := C.bool(frontendOptions.DragAndDrop != nil && frontendOptions.DragAndDrop.EnableFileDrop) - disableWebViewDragAndDrop := C.bool(frontendOptions.DragAndDrop != nil && frontendOptions.DragAndDrop.DisableWebViewDrop) - if frontendOptions.Mac != nil { mac := frontendOptions.Mac if mac.TitleBar != nil { @@ -98,35 +79,15 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window titlebarAppearsTransparent = bool2Cint(mac.TitleBar.TitlebarAppearsTransparent) hideToolbarSeparator = bool2Cint(mac.TitleBar.HideToolbarSeparator) } - - if mac.Preferences != nil { - if mac.Preferences.TabFocusesLinks.IsSet() { - preferences.tabFocusesLinks = bool2CboolPtr(mac.Preferences.TabFocusesLinks.Get()) - } - - if mac.Preferences.TextInteractionEnabled.IsSet() { - preferences.textInteractionEnabled = bool2CboolPtr(mac.Preferences.TextInteractionEnabled.Get()) - } - - if mac.Preferences.FullscreenEnabled.IsSet() { - preferences.fullscreenEnabled = bool2CboolPtr(mac.Preferences.FullscreenEnabled.Get()) - } - } - - zoomable = bool2Cint(!frontendOptions.Mac.DisableZoom) - windowIsTranslucent = bool2Cint(mac.WindowIsTranslucent) webviewIsTransparent = bool2Cint(mac.WebviewIsTransparent) - contentProtection = bool2Cint(mac.ContentProtection) appearance = c.String(string(mac.Appearance)) } - var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, zoomable, fullscreen, fullSizeContent, + var context *C.WailsContext = C.Create(title, width, height, frameless, resizable, fullscreen, fullSizeContent, hideTitleBar, titlebarAppearsTransparent, hideTitle, useToolbar, hideToolbarSeparator, webviewIsTransparent, - alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, contentProtection, devtoolsEnabled, defaultContextMenuEnabled, - windowStartState, startsHidden, minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings, - preferences, singleInstanceEnabled, singleInstanceUniqueId, enableDragAndDrop, disableWebViewDragAndDrop, - ) + alwaysOnTop, hideWindowOnClose, appearance, windowIsTranslucent, debug, windowStartState, startsHidden, + minWidth, minHeight, maxWidth, maxHeight, enableFraudulentWebsiteWarnings) // Create menu result := &Window{ @@ -153,7 +114,7 @@ func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window result.SetApplicationMenu(frontendOptions.Menu) } - if debug && frontendOptions.Debug.OpenInspectorOnStartup { + if debugMode && frontendOptions.Debug.OpenInspectorOnStartup { showInspector(result.context) } return result @@ -204,7 +165,6 @@ func (w *Window) SetTitle(title string) { func (w *Window) Maximise() { C.Maximise(w.context) } - func (w *Window) ToggleMaximise() { C.ToggleMaximise(w.context) } @@ -260,7 +220,6 @@ func (w *Window) Show() { func (w *Window) Hide() { C.Hide(w.context) } - func (w *Window) ShowApplication() { C.ShowApplication(w.context) } @@ -295,19 +254,11 @@ func (w *Window) Size() (int, int) { } func (w *Window) SetApplicationMenu(inMenu *menu.Menu) { - w.applicationMenu = inMenu - w.UpdateApplicationMenu() + mainMenu := NewNSMenu(w.context, "") + processMenu(mainMenu, inMenu) + C.SetAsApplicationMenu(w.context, mainMenu.nsmenu) } func (w *Window) UpdateApplicationMenu() { - mainMenu := NewNSMenu(w.context, "") - if w.applicationMenu != nil { - processMenu(mainMenu, w.applicationMenu) - } - C.SetAsApplicationMenu(w.context, mainMenu.nsmenu) C.UpdateApplicationMenu(w.context) } - -func (w Window) Print() { - C.WindowPrint(w.context) -} diff --git a/v2/internal/frontend/desktop/linux/browser.go b/v2/internal/frontend/desktop/linux/browser.go index 962e3b28a..47bf0ba5d 100644 --- a/v2/internal/frontend/desktop/linux/browser.go +++ b/v2/internal/frontend/desktop/linux/browser.go @@ -3,21 +3,10 @@ package linux -import ( - "fmt" - "github.com/pkg/browser" - "github.com/wailsapp/wails/v2/internal/frontend/utils" -) +import "github.com/pkg/browser" // BrowserOpenURL Use the default browser to open the url -func (f *Frontend) BrowserOpenURL(rawURL string) { - url, err := utils.ValidateAndSanitizeURL(rawURL) - if err != nil { - f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error())) - return - } +func (f *Frontend) BrowserOpenURL(url string) { // Specific method implementation - if err := browser.OpenURL(url); err != nil { - f.logger.Error("Unable to open default system browser") - } + _ = browser.OpenURL(url) } diff --git a/v2/internal/frontend/desktop/linux/clipboard.go b/v2/internal/frontend/desktop/linux/clipboard.go index a2a46dacc..88b8c713f 100644 --- a/v2/internal/frontend/desktop/linux/clipboard.go +++ b/v2/internal/frontend/desktop/linux/clipboard.go @@ -4,9 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #include "gtk/gtk.h" #include "webkit2/webkit2.h" diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index 2942a112e..58b8d746b 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -4,9 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #include "gtk/gtk.h" #include "webkit2/webkit2.h" @@ -73,22 +71,11 @@ static void install_signal_handlers() #endif } -static gboolean install_signal_handlers_idle(gpointer data) { - (void)data; - install_signal_handlers(); - return G_SOURCE_REMOVE; -} - -static void fix_signal_handlers_after_gtk_init() { - g_idle_add(install_signal_handlers_idle, NULL); -} - */ import "C" import ( "context" "encoding/json" - "errors" "fmt" "log" "net" @@ -96,7 +83,6 @@ import ( "os" "runtime" "strings" - "sync" "text/template" "unsafe" @@ -105,18 +91,13 @@ import ( "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" - "github.com/wailsapp/wails/v2/internal/frontend/originvalidator" wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/pkg/options" ) -var initOnce = sync.Once{} - const startURL = "wails://wails/" -var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) - type Frontend struct { // Context @@ -125,7 +106,6 @@ type Frontend struct { frontendOptions *options.App logger *logger.Logger debug bool - devtoolsEnabled bool // Assets assets *assetserver.AssetServer @@ -135,8 +115,6 @@ type Frontend struct { mainWindow *Window bindings *binding.Bindings dispatcher frontend.Dispatcher - - originValidator *originvalidator.OriginValidator } func (f *Frontend) RunMainLoop() { @@ -147,19 +125,18 @@ func (f *Frontend) WindowClose() { f.mainWindow.Destroy() } +func init() { + runtime.LockOSThread() + + // 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") + } + + C.gtk_init(nil, nil) +} + func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend { - initOnce.Do(func() { - runtime.LockOSThread() - - // 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") - } - - if ok := C.gtk_init_check(nil, nil); ok != 1 { - panic(errors.New("failed to init GTK")) - } - }) result := &Frontend{ frontendOptions: appoptions, @@ -169,15 +146,12 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. ctx: ctx, } result.startURL, _ = url.Parse(startURL) - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { result.startURL = _starturl - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) } else { if port, _ := ctx.Value("assetserverport").(string); port != "" { result.startURL.Host = net.JoinHostPort(result.startURL.Host+".localhost", port) - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) } var bindings string @@ -200,29 +174,14 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } go result.startMessageProcessor() - go result.startBindingsMessageProcessor() var _debug = ctx.Value("debug") - var _devtoolsEnabled = ctx.Value("devtoolsEnabled") - if _debug != nil { result.debug = _debug.(bool) } - if _devtoolsEnabled != nil { - result.devtoolsEnabled = _devtoolsEnabled.(bool) - } + result.mainWindow = NewWindow(appoptions, result.debug) - result.mainWindow = NewWindow(appoptions, result.debug, result.devtoolsEnabled) - - C.fix_signal_handlers_after_gtk_init() - - if appoptions.Linux != nil && appoptions.Linux.ProgramName != "" { - prgname := C.CString(appoptions.Linux.ProgramName) - C.g_set_prgname(prgname) - C.free(unsafe.Pointer(prgname)) - } - - go result.startSecondInstanceProcessor() + C.install_signal_handlers() return result } @@ -233,24 +192,6 @@ func (f *Frontend) startMessageProcessor() { } } -func (f *Frontend) startBindingsMessageProcessor() { - for msg := range bindingsMessageBuffer { - origin, err := f.originValidator.GetOriginFromURL(msg.source) - if err != nil { - f.logger.Error(fmt.Sprintf("failed to get origin for URL %q: %v", msg.source, err)) - continue - } - - allowed := f.originValidator.IsOriginAllowed(origin) - if !allowed { - f.logger.Error("Blocked request from unauthorized origin: %s", origin) - continue - } - - f.processMessage(msg.message) - } -} - func (f *Frontend) WindowReload() { f.ExecJS("runtime.WindowReload();") } @@ -276,10 +217,6 @@ func (f *Frontend) Run(ctx context.Context) error { } }() - if f.frontendOptions.SingleInstanceLock != nil { - SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) - } - f.mainWindow.Run(f.startURL.String()) return nil @@ -407,10 +344,6 @@ func (f *Frontend) Quit() { f.mainWindow.Quit() } -func (f *Frontend) WindowPrint() { - f.ExecJS("window.print();") -} - type EventNotify struct { Name string `json:"name"` Data []interface{} `json:"data"` @@ -455,11 +388,6 @@ func (f *Frontend) processMessage(message string) { return } - if message == "wails:showInspector" { - f.mainWindow.ShowInspector() - return - } - if strings.HasPrefix(message, "resize:") { if !f.mainWindow.IsFullScreen() { sl := strings.Split(message, ":") @@ -479,24 +407,12 @@ func (f *Frontend) processMessage(message string) { if message == "runtime:ready" { cmd := fmt.Sprintf( "window.wails.setCSSDragProperties('%s', '%s');\n"+ - "window.wails.setCSSDropProperties('%s', '%s');\n"+ - "window.wails.flags.deferDragToMouseMove = true;", - f.frontendOptions.CSSDragProperty, - f.frontendOptions.CSSDragValue, - f.frontendOptions.DragAndDrop.CSSDropProperty, - f.frontendOptions.DragAndDrop.CSSDropValue, - ) - + "window.wails.flags.deferDragToMouseMove = true;", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue) f.ExecJS(cmd) if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false { f.ExecJS("window.wails.flags.enableResize = true;") } - - if f.frontendOptions.DragAndDrop.EnableFileDrop { - f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;") - } - return } @@ -542,13 +458,7 @@ func (f *Frontend) ExecJS(js string) { f.mainWindow.ExecJS(js) } -type bindingsMessage struct { - message string - source string -} - var messageBuffer = make(chan string, 100) -var bindingsMessageBuffer = make(chan *bindingsMessage, 100) //export processMessage func processMessage(message *C.char) { @@ -556,16 +466,6 @@ func processMessage(message *C.char) { messageBuffer <- goMessage } -//export processBindingMessage -func processBindingMessage(message *C.char, source *C.char) { - goMessage := C.GoString(message) - goSource := C.GoString(source) - bindingsMessageBuffer <- &bindingsMessage{ - message: goMessage, - source: goSource, - } -} - var requestBuffer = make(chan webview.Request, 100) func (f *Frontend) startRequestProcessor() { @@ -578,12 +478,3 @@ func (f *Frontend) startRequestProcessor() { func processURLRequest(request unsafe.Pointer) { requestBuffer <- webview.NewRequest(request) } - -func (f *Frontend) startSecondInstanceProcessor() { - for secondInstanceData := range secondInstanceBuffer { - if f.frontendOptions.SingleInstanceLock != nil && - f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { - f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) - } - } -} diff --git a/v2/internal/frontend/desktop/linux/gtk.go b/v2/internal/frontend/desktop/linux/gtk.go index 67a38c7a0..f4bc531b3 100644 --- a/v2/internal/frontend/desktop/linux/gtk.go +++ b/v2/internal/frontend/desktop/linux/gtk.go @@ -4,9 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #include "gtk/gtk.h" diff --git a/v2/internal/frontend/desktop/linux/keys.go b/v2/internal/frontend/desktop/linux/keys.go index e5a127dbd..1c095fea9 100644 --- a/v2/internal/frontend/desktop/linux/keys.go +++ b/v2/internal/frontend/desktop/linux/keys.go @@ -4,10 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 - +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #include "gtk/gtk.h" diff --git a/v2/internal/frontend/desktop/linux/menu.go b/v2/internal/frontend/desktop/linux/menu.go index a61d190bd..bc3d2740b 100644 --- a/v2/internal/frontend/desktop/linux/menu.go +++ b/v2/internal/frontend/desktop/linux/menu.go @@ -4,9 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #include "gtk/gtk.h" @@ -34,11 +32,8 @@ void addAccelerator(GtkWidget* menuItem, GtkAccelGroup* group, guint key, GdkMod } */ import "C" -import ( - "unsafe" - - "github.com/wailsapp/wails/v2/pkg/menu" -) +import "github.com/wailsapp/wails/v2/pkg/menu" +import "unsafe" var menuIdCounter int var menuItemToId map[*menu.MenuItem]int @@ -84,10 +79,8 @@ func (w *Window) SetApplicationMenu(inmenu *menu.Menu) { func processMenu(window *Window, menu *menu.Menu) { for _, menuItem := range menu.Items { - if menuItem.SubMenu != nil { - submenu := processSubmenu(menuItem, window.accels) - C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(window.menubar)), submenu) - } + submenu := processSubmenu(menuItem, window.accels) + C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(window.menubar)), submenu) } } diff --git a/v2/internal/frontend/desktop/linux/notifications.go b/v2/internal/frontend/desktop/linux/notifications.go deleted file mode 100644 index 80f0ae569..000000000 --- a/v2/internal/frontend/desktop/linux/notifications.go +++ /dev/null @@ -1,594 +0,0 @@ -//go:build linux -// +build linux - -package linux - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sync" - - "github.com/godbus/dbus/v5" - "github.com/wailsapp/wails/v2/internal/frontend" -) - -var ( - conn *dbus.Conn - categories map[string]frontend.NotificationCategory = make(map[string]frontend.NotificationCategory) - categoriesLock sync.RWMutex - notifications map[uint32]*notificationData = make(map[uint32]*notificationData) - notificationsLock sync.RWMutex - notificationResultCallback func(result frontend.NotificationResult) - callbackLock 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" - DefaultActionIdentifier = "DEFAULT_ACTION" -) - -// Creates a new Notifications Service. -func (f *Frontend) InitializeNotifications() error { - // Clean up any previous initialization - f.CleanupNotifications() - - exe, err := os.Executable() - if err != nil { - return fmt.Errorf("failed to get executable: %w", err) - } - appName = filepath.Base(exe) - - _conn, err := dbus.ConnectSessionBus() - if err != nil { - return fmt.Errorf("failed to connect to session bus: %w", err) - } - conn = _conn - - if err := f.loadCategories(); err != nil { - f.logger.Warning("Failed to load notification categories: %v", err) - } - - var signalCtx context.Context - signalCtx, cancel = context.WithCancel(context.Background()) - - if err := f.setupSignalHandling(signalCtx); err != nil { - return fmt.Errorf("failed to set up notification signal handling: %w", err) - } - - return nil -} - -// CleanupNotifications cleans up notification resources -func (f *Frontend) CleanupNotifications() { - if cancel != nil { - cancel() - cancel = nil - } - - if conn != nil { - conn.Close() - conn = nil - } -} - -func (f *Frontend) IsNotificationAvailable() bool { - return true -} - -// RequestNotificationAuthorization is a Linux stub that always returns true, nil. -// (authorization is macOS-specific) -func (f *Frontend) RequestNotificationAuthorization() (bool, error) { - return true, nil -} - -// CheckNotificationAuthorization is a Linux stub that always returns true. -// (authorization is macOS-specific) -func (f *Frontend) CheckNotificationAuthorization() (bool, error) { - return true, nil -} - -// SendNotification sends a basic notification with a unique identifier, title, subtitle, and body. -func (f *Frontend) SendNotification(options frontend.NotificationOptions) error { - if conn == nil { - return fmt.Errorf("notifications not initialized") - } - - 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 := conn.Object(dbusNotificationInterface, dbusNotificationPath) - call := obj.Call( - dbusNotificationInterface+".Notify", - 0, - 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, - } - - notificationsLock.Lock() - notifications[dbusID] = notification - notificationsLock.Unlock() - - return nil -} - -// SendNotificationWithActions sends a notification with additional actions. -func (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error { - if conn == nil { - return fmt.Errorf("notifications not initialized") - } - - categoriesLock.RLock() - category, exists := categories[options.CategoryID] - categoriesLock.RUnlock() - - if options.CategoryID == "" || !exists { - // Fall back to basic notification - return f.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 := conn.Object(dbusNotificationInterface, dbusNotificationPath) - call := obj.Call( - dbusNotificationInterface+".Notify", - 0, - 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, - } - - notificationsLock.Lock() - notifications[dbusID] = notification - notificationsLock.Unlock() - - return nil -} - -// RegisterNotificationCategory registers a new NotificationCategory to be used with SendNotificationWithActions. -func (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error { - categoriesLock.Lock() - categories[category.ID] = category - categoriesLock.Unlock() - - if err := f.saveCategories(); err != nil { - f.logger.Warning("Failed to save notification categories: %v", err) - } - - return nil -} - -// RemoveNotificationCategory removes a previously registered NotificationCategory. -func (f *Frontend) RemoveNotificationCategory(categoryId string) error { - categoriesLock.Lock() - delete(categories, categoryId) - categoriesLock.Unlock() - - if err := f.saveCategories(); err != nil { - f.logger.Warning("Failed to save notification categories: %v", err) - } - - return nil -} - -// RemoveAllPendingNotifications attempts to remove all active notifications. -func (f *Frontend) RemoveAllPendingNotifications() error { - notificationsLock.Lock() - dbusIDs := make([]uint32, 0, len(notifications)) - for id := range notifications { - dbusIDs = append(dbusIDs, id) - } - notificationsLock.Unlock() - - for _, id := range dbusIDs { - f.closeNotification(id) - } - - return nil -} - -// RemovePendingNotification removes a pending notification. -func (f *Frontend) RemovePendingNotification(identifier string) error { - var dbusID uint32 - found := false - - notificationsLock.Lock() - for id, notif := range notifications { - if notif.ID == identifier { - dbusID = id - found = true - break - } - } - notificationsLock.Unlock() - - if !found { - return nil - } - - return f.closeNotification(dbusID) -} - -// RemoveAllDeliveredNotifications functionally equivalent to RemoveAllPendingNotification on Linux. -func (f *Frontend) RemoveAllDeliveredNotifications() error { - return f.RemoveAllPendingNotifications() -} - -// RemoveDeliveredNotification functionally equivalent RemovePendingNotification on Linux. -func (f *Frontend) RemoveDeliveredNotification(identifier string) error { - return f.RemovePendingNotification(identifier) -} - -// RemoveNotification removes a notification by identifier. -func (f *Frontend) RemoveNotification(identifier string) error { - return f.RemovePendingNotification(identifier) -} - -func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) { - callbackLock.Lock() - defer callbackLock.Unlock() - - notificationResultCallback = callback -} - -// Helper method to close a notification. -func (f *Frontend) closeNotification(id uint32) error { - if conn == nil { - return fmt.Errorf("notifications not initialized") - } - - obj := 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 (f *Frontend) 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, 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 (f *Frontend) saveCategories() error { - configDir, err := f.getConfigDir() - if err != nil { - return err - } - - categoriesFile := filepath.Join(configDir, "notification-categories.json") - - categoriesLock.RLock() - categoriesData, err := json.MarshalIndent(categories, "", " ") - 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 (f *Frontend) loadCategories() error { - configDir, err := f.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]frontend.NotificationCategory) - if err := json.Unmarshal(categoriesData, &_categories); err != nil { - return fmt.Errorf("failed to unmarshal notification categories: %w", err) - } - - categoriesLock.Lock() - categories = _categories - categoriesLock.Unlock() - - return nil -} - -// Setup signal handling for notification actions. -func (f *Frontend) setupSignalHandling(ctx context.Context) error { - if err := conn.AddMatchSignal( - dbus.WithMatchInterface(dbusNotificationInterface), - dbus.WithMatchMember("ActionInvoked"), - ); err != nil { - return err - } - - if err := conn.AddMatchSignal( - dbus.WithMatchInterface(dbusNotificationInterface), - dbus.WithMatchMember("NotificationClosed"), - ); err != nil { - return err - } - - c := make(chan *dbus.Signal, 10) - conn.Signal(c) - - go f.handleSignals(ctx, c) - - return nil -} - -// Handle incoming D-Bus signals. -func (f *Frontend) 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": - f.handleActionInvoked(signal) - case dbusNotificationInterface + ".NotificationClosed": - f.handleNotificationClosed(signal) - } - } - } -} - -// Handle ActionInvoked signal. -func (f *Frontend) 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 - } - - notificationsLock.Lock() - notification, exists := notifications[dbusID] - if exists { - delete(notifications, dbusID) - } - notificationsLock.Unlock() - - if !exists { - return - } - - appActionID, ok := notification.ActionMap[actionID] - if !ok { - appActionID = actionID - } - - response := frontend.NotificationResponse{ - ID: notification.ID, - ActionIdentifier: appActionID, - Title: notification.Title, - Subtitle: notification.Subtitle, - Body: notification.Body, - CategoryID: notification.CategoryID, - UserInfo: notification.Data, - } - - result := frontend.NotificationResult{ - Response: response, - } - - handleNotificationResult(result) -} - -func handleNotificationResult(result frontend.NotificationResult) { - callbackLock.Lock() - callback := notificationResultCallback - callbackLock.Unlock() - - if callback != nil { - go func() { - defer func() { - if r := recover(); r != nil { - // Log panic but don't crash the app - fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r) - } - }() - callback(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 (f *Frontend) 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 - } - - notificationsLock.Lock() - notification, exists := notifications[dbusID] - if exists { - delete(notifications, dbusID) - } - notificationsLock.Unlock() - - if !exists { - return - } - - if reason == 2 { - response := frontend.NotificationResponse{ - ID: notification.ID, - ActionIdentifier: DefaultActionIdentifier, - Title: notification.Title, - Subtitle: notification.Subtitle, - Body: notification.Body, - CategoryID: notification.CategoryID, - UserInfo: notification.Data, - } - - result := frontend.NotificationResult{ - Response: response, - } - - handleNotificationResult(result) - } -} diff --git a/v2/internal/frontend/desktop/linux/screen.go b/v2/internal/frontend/desktop/linux/screen.go index 0a0507425..bd186363b 100644 --- a/v2/internal/frontend/desktop/linux/screen.go +++ b/v2/internal/frontend/desktop/linux/screen.go @@ -4,10 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 - +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #cgo CFLAGS: -w #include #include "webkit2/webkit2.h" @@ -19,7 +16,6 @@ typedef struct Screen { int isPrimary; int height; int width; - int scale; } Screen; int GetNMonitors(GtkWindow *window){ @@ -40,16 +36,14 @@ Screen GetNThMonitor(int monitor_num, GtkWindow *window){ screen.isPrimary = gdk_monitor_is_primary(monitor); screen.height = geometry.height; screen.width = geometry.width; - screen.scale = gdk_monitor_get_scale_factor(monitor); return screen; } */ import "C" import ( - "sync" - "github.com/pkg/errors" "github.com/wailsapp/wails/v2/internal/frontend" + "sync" ) type Screen = frontend.Screen @@ -65,21 +59,11 @@ func GetAllScreens(window *C.GtkWindow) ([]Screen, error) { numMonitors := C.GetNMonitors(window) for i := 0; i < int(numMonitors); i++ { cMonitor := C.GetNThMonitor(C.int(i), window) - screen := Screen{ IsCurrent: cMonitor.isCurrent == 1, IsPrimary: cMonitor.isPrimary == 1, Width: int(cMonitor.width), Height: int(cMonitor.height), - - Size: frontend.ScreenSize{ - Width: int(cMonitor.width), - Height: int(cMonitor.height), - }, - PhysicalSize: frontend.ScreenSize{ - Width: int(cMonitor.width * cMonitor.scale), - Height: int(cMonitor.height * cMonitor.scale), - }, } screens = append(screens, screen) } diff --git a/v2/internal/frontend/desktop/linux/single_instance.go b/v2/internal/frontend/desktop/linux/single_instance.go deleted file mode 100644 index 0317dee49..000000000 --- a/v2/internal/frontend/desktop/linux/single_instance.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build linux -// +build linux - -package linux - -import ( - "encoding/json" - "github.com/godbus/dbus/v5" - "github.com/wailsapp/wails/v2/pkg/options" - "log" - "os" - "strings" -) - -type dbusHandler func(string) - -func (f dbusHandler) SendMessage(message string) *dbus.Error { - f(message) - return nil -} - -func SetupSingleInstance(uniqueID string) { - id := "wails_app_" + strings.ReplaceAll(strings.ReplaceAll(uniqueID, "-", "_"), ".", "_") - - dbusName := "org." + id + ".SingleInstance" - 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 - } - - f := dbusHandler(func(message string) { - var secondInstanceData options.SecondInstanceData - - err := json.Unmarshal([]byte(message), &secondInstanceData) - if err == nil { - secondInstanceBuffer <- secondInstanceData - } - }) - - err = conn.Export(f, dbus.ObjectPath(dbusPath), dbusName) - if err != nil { - return - } - - reply, err := conn.RequestName(dbusName, dbus.NameFlagDoNotQueue) - if err != nil { - return - } - - // if name already taken, try to send args to existing instance, if no success just launch new instance - if reply == dbus.RequestNameReplyExists { - data := options.SecondInstanceData{ - Args: os.Args[1:], - } - data.WorkingDirectory, err = os.Getwd() - if err != nil { - log.Printf("Failed to get working directory: %v", err) - return - } - - serialized, err := json.Marshal(data) - if err != nil { - log.Printf("Failed to marshal data: %v", err) - return - } - - err = conn.Object(dbusName, dbus.ObjectPath(dbusPath)).Call(dbusName+".SendMessage", 0, string(serialized)).Store() - if err != nil { - return - } - os.Exit(1) - } -} diff --git a/v2/internal/frontend/desktop/linux/webkit2.go b/v2/internal/frontend/desktop/linux/webkit2.go index 06e0c7824..843b72604 100644 --- a/v2/internal/frontend/desktop/linux/webkit2.go +++ b/v2/internal/frontend/desktop/linux/webkit2.go @@ -3,8 +3,7 @@ package linux /* -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: webkit2gtk-4.0 #include "webkit2/webkit2.h" */ import "C" diff --git a/v2/internal/frontend/desktop/linux/window.c b/v2/internal/frontend/desktop/linux/window.c index 5441db022..1e7d5aa73 100644 --- a/v2/internal/frontend/desktop/linux/window.c +++ b/v2/internal/frontend/desktop/linux/window.c @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include "window.h" // These are the x,y,time & button of the last mouse down event @@ -14,9 +12,6 @@ static float xroot = 0.0f; static float yroot = 0.0f; static int dragTime = -1; static uint mouseButton = 0; -static int wmIsWayland = -1; -static int decoratorWidth = -1; -static int decoratorHeight = -1; // casts void ExecuteOnMainThread(void *f, gpointer jscallback) @@ -45,17 +40,11 @@ GtkBox *GTKBOX(void *pointer) } extern void processMessage(char *); -extern void processBindingMessage(char *, char *); static void sendMessageToBackend(WebKitUserContentManager *contentManager, WebKitJavascriptResult *result, void *data) { - // Retrieve webview from content manager - WebKitWebView *webview = WEBKIT_WEB_VIEW(g_object_get_data(G_OBJECT(contentManager), "webview")); - const char *current_uri = webview ? webkit_web_view_get_uri(webview) : NULL; - char *uri = current_uri ? g_strdup(current_uri) : NULL; - #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 JSCValue *value = webkit_javascript_result_get_js_value(result); char *message = jsc_value_to_string(value); @@ -68,11 +57,8 @@ static void sendMessageToBackend(WebKitUserContentManager *contentManager, JSStringGetUTF8CString(js, message, messageSize); JSStringRelease(js); #endif - processBindingMessage(message, uri); + processMessage(message); g_free(message); - if (uri) { - g_free(uri); - } } static bool isNULLRectangle(GdkRectangle input) @@ -80,29 +66,6 @@ static bool isNULLRectangle(GdkRectangle input) return input.x == -1 && input.y == -1 && input.width == -1 && input.height == -1; } -static gboolean onWayland() -{ - switch (wmIsWayland) - { - case -1: - { - char *gdkBackend = getenv("XDG_SESSION_TYPE"); - if(gdkBackend != NULL && strcmp(gdkBackend, "wayland") == 0) - { - wmIsWayland = 1; - return TRUE; - } - - wmIsWayland = 0; - return FALSE; - } - case 1: - return TRUE; - default: - return FALSE; - } -} - static GdkMonitor *getCurrentMonitor(GtkWindow *window) { // Get the monitor that the window is currently on @@ -176,50 +139,11 @@ void SetWindowTransparency(GtkWidget *widget) } } -static GtkCssProvider *windowCssProvider = NULL; - void SetBackgroundColour(void *data) { - // set webview's background color RGBAOptions *options = (RGBAOptions *)data; - GdkRGBA colour = {options->r / 255.0, options->g / 255.0, options->b / 255.0, options->a / 255.0}; - if (options->windowIsTranslucent != NULL && options->windowIsTranslucent == TRUE) - { - colour.alpha = 0.0; - } webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(options->webview), &colour); - - // set window's background color - // Get the name of the current locale - char *old_locale, *saved_locale; - old_locale = setlocale(LC_ALL, NULL); - - // Copy the name so it won’t be clobbered by setlocale. - saved_locale = strdup(old_locale); - if (saved_locale == NULL) - return; - - //Now change the locale to english for so printf always converts floats with a dot decimal separator - setlocale(LC_ALL, "en_US.UTF-8"); - gchar *str = g_strdup_printf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", options->r, options->g, options->b, options->a / 255.0); - - //Restore the original locale. - setlocale(LC_ALL, saved_locale); - free(saved_locale); - - if (windowCssProvider == NULL) - { - windowCssProvider = gtk_css_provider_new(); - gtk_style_context_add_provider( - gtk_widget_get_style_context(GTK_WIDGET(options->webviewBox)), - GTK_STYLE_PROVIDER(windowCssProvider), - GTK_STYLE_PROVIDER_PRIORITY_USER); - g_object_unref(windowCssProvider); - } - - gtk_css_provider_load_from_data(windowCssProvider, str, -1, NULL); - g_free(str); } static gboolean setTitle(gpointer data) @@ -273,38 +197,15 @@ void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_wid { return; } - int flags = GDK_HINT_MAX_SIZE | GDK_HINT_MIN_SIZE; - size.max_height = (max_height == 0 ? monitorSize.height : max_height); size.max_width = (max_width == 0 ? monitorSize.width : max_width); size.min_height = min_height; size.min_width = min_width; - - // On Wayland window manager get the decorators and calculate the differences from the windows' size. - if(onWayland()) - { - if(decoratorWidth == -1 && decoratorHeight == -1) - { - int windowWidth, windowHeight; - gtk_window_get_size(window, &windowWidth, &windowHeight); - - GtkAllocation windowAllocation; - gtk_widget_get_allocation(GTK_WIDGET(window), &windowAllocation); - - decoratorWidth = (windowAllocation.width-windowWidth); - decoratorHeight = (windowAllocation.height-windowHeight); - } - - // Add the decorator difference to the window so fullscreen and maximise can fill the window. - size.max_height = decoratorHeight+size.max_height; - size.max_width = decoratorWidth+size.max_width; - } - gtk_window_set_geometry_hints(window, NULL, &size, flags); } -// function to disable the context menu but propagate the event +// function to disable the context menu but propogate the event static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data) { // return true to disable the context menu @@ -313,7 +214,7 @@ static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context void DisableContextMenu(void *webview) { - // Disable the context menu but propagate the event + // Disable the context menu but propogate the event g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL); } @@ -488,95 +389,14 @@ gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void *data) return TRUE; } -char *droppedFiles = NULL; - -static void onDragDataReceived(GtkWidget *self, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer data) -{ - if(selection_data == NULL || (gtk_selection_data_get_length(selection_data) <= 0) || target_type != 2) - { - return; - } - - if(droppedFiles != NULL) { - free(droppedFiles); - droppedFiles = NULL; - } - - gchar **filenames = NULL; - filenames = g_uri_list_extract_uris((const gchar *)gtk_selection_data_get_data(selection_data)); - if (filenames == NULL) // If unable to retrieve filenames: - { - g_strfreev(filenames); - return; - } - - droppedFiles = calloc((size_t)gtk_selection_data_get_length(selection_data), 1); - - int iter = 0; - while(filenames[iter] != NULL) // The last URI list element is NULL. - { - if(iter != 0) - { - strncat(droppedFiles, "\n", 1); - } - char *filename = g_filename_from_uri(filenames[iter], NULL, NULL); - if (filename == NULL) - { - break; - } - strncat(droppedFiles, filename, strlen(filename)); - - free(filename); - iter++; - } - - g_strfreev(filenames); -} - -static gboolean onDragDrop(GtkWidget* self, GdkDragContext* context, gint x, gint y, guint time, gpointer user_data) -{ - if(droppedFiles == NULL) - { - return FALSE; - } - - size_t resLen = strlen(droppedFiles)+(sizeof(gint)*2)+6; - char *res = calloc(resLen, 1); - - snprintf(res, resLen, "DD:%d:%d:%s", x, y, droppedFiles); - - if(droppedFiles != NULL) { - free(droppedFiles); - droppedFiles = NULL; - } - - processMessage(res); - return FALSE; -} - // WebView -GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop) +GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy) { GtkWidget *webview = webkit_web_view_new_with_user_content_manager((WebKitUserContentManager *)contentManager); - - // Store webview reference in the content manager - g_object_set_data(G_OBJECT((WebKitUserContentManager *)contentManager), "webview", webview); // gtk_container_add(GTK_CONTAINER(window), webview); WebKitWebContext *context = webkit_web_context_get_default(); webkit_web_context_register_uri_scheme(context, "wails", (WebKitURISchemeRequestCallback)processURLRequest, NULL, NULL); g_signal_connect(G_OBJECT(webview), "load-changed", G_CALLBACK(webviewLoadChanged), NULL); - - if(disableWebViewDragAndDrop) - { - gtk_drag_dest_unset(webview); - } - - if(enableDragAndDrop) - { - g_signal_connect(G_OBJECT(webview), "drag-data-received", G_CALLBACK(onDragDataReceived), NULL); - g_signal_connect(G_OBJECT(webview), "drag-drop", G_CALLBACK(onDragDrop), NULL); - } - if (hideWindowOnClose) { g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); @@ -614,7 +434,8 @@ void DevtoolsEnabled(void *webview, int enabled, bool showInspector) if (genabled && showInspector) { - ShowInspector(webview); + WebKitWebInspector *inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview)); + webkit_web_inspector_show(WEBKIT_WEB_INSPECTOR(inspector)); } } @@ -871,21 +692,3 @@ GtkFileFilter *newFileFilter() g_object_ref(result); return result; } - -void ShowInspector(void *webview) { - WebKitWebInspector *inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview)); - webkit_web_inspector_show(WEBKIT_WEB_INSPECTOR(inspector)); -} - -void sendShowInspectorMessage() { - processMessage("wails:showInspector"); -} - -void InstallF12Hotkey(void *window) -{ - // When the user presses Ctrl+Shift+F12, call ShowInspector - GtkAccelGroup *accel_group = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(window), accel_group); - GClosure *closure = g_cclosure_new(G_CALLBACK(sendShowInspectorMessage), window, NULL); - gtk_accel_group_connect(accel_group, GDK_KEY_F12, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, closure); -} diff --git a/v2/internal/frontend/desktop/linux/window.go b/v2/internal/frontend/desktop/linux/window.go index 0bf5ac51d..9cc86afbb 100644 --- a/v2/internal/frontend/desktop/linux/window.go +++ b/v2/internal/frontend/desktop/linux/window.go @@ -4,9 +4,7 @@ package linux /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #include #include @@ -27,7 +25,6 @@ import ( "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" - "github.com/wailsapp/wails/v2/pkg/options/linux" ) func gtkBool(input bool) C.gboolean { @@ -40,13 +37,11 @@ func gtkBool(input bool) C.gboolean { type Window struct { appoptions *options.App debug bool - devtoolsEnabled bool gtkWindow unsafe.Pointer contentManager unsafe.Pointer webview unsafe.Pointer applicationMenu *menu.Menu menubar *C.GtkWidget - webviewBox *C.GtkWidget vbox *C.GtkWidget accels *C.GtkAccelGroup minWidth, minHeight, maxWidth, maxHeight int @@ -59,28 +54,22 @@ func bool2Cint(value bool) C.int { return C.int(0) } -func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Window { +func NewWindow(appoptions *options.App, debug bool) *Window { validateWebKit2Version(appoptions) result := &Window{ - appoptions: appoptions, - debug: debug, - devtoolsEnabled: devtoolsEnabled, - minHeight: appoptions.MinHeight, - minWidth: appoptions.MinWidth, - maxHeight: appoptions.MaxHeight, - maxWidth: appoptions.MaxWidth, + appoptions: appoptions, + debug: debug, + minHeight: appoptions.MinHeight, + minWidth: appoptions.MinWidth, + maxHeight: appoptions.MaxHeight, + maxWidth: appoptions.MaxWidth, } gtkWindow := C.gtk_window_new(C.GTK_WINDOW_TOPLEVEL) C.g_object_ref_sink(C.gpointer(gtkWindow)) result.gtkWindow = unsafe.Pointer(gtkWindow) - webviewName := C.CString("webview-box") - defer C.free(unsafe.Pointer(webviewName)) - result.webviewBox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0) - C.gtk_widget_set_name(result.webviewBox, webviewName) - result.vbox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0) C.gtk_container_add(result.asGTKContainer(), result.vbox) @@ -93,9 +82,6 @@ func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Windo var webviewGpuPolicy int if appoptions.Linux != nil { webviewGpuPolicy = int(appoptions.Linux.WebviewGpuPolicy) - } else { - // workaround for https://github.com/wailsapp/wails/issues/2977 - webviewGpuPolicy = int(linux.WebviewGpuPolicyNever) } webview := C.SetupWebview( @@ -103,21 +89,15 @@ func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Windo result.asGTKWindow(), bool2Cint(appoptions.HideWindowOnClose), C.int(webviewGpuPolicy), - bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.DisableWebViewDrop), - bool2Cint(appoptions.DragAndDrop != nil && appoptions.DragAndDrop.EnableFileDrop), ) result.webview = unsafe.Pointer(webview) buttonPressedName := C.CString("button-press-event") defer C.free(unsafe.Pointer(buttonPressedName)) C.ConnectButtons(unsafe.Pointer(webview)) - if devtoolsEnabled { - C.DevtoolsEnabled(unsafe.Pointer(webview), C.int(1), C.bool(debug && appoptions.Debug.OpenInspectorOnStartup)) - // Install Ctrl-Shift-F12 hotkey to call ShowInspector - C.InstallF12Hotkey(unsafe.Pointer(gtkWindow)) - } - - if !(debug || appoptions.EnableDefaultContextMenu) { + if debug { + C.DevtoolsEnabled(unsafe.Pointer(webview), C.int(1), C.bool(appoptions.Debug.OpenInspectorOnStartup)) + } else { C.DisableContextMenu(unsafe.Pointer(webview)) } @@ -128,7 +108,7 @@ func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Windo // Setup window result.SetKeepAbove(appoptions.AlwaysOnTop) result.SetResizable(!appoptions.DisableResize) - result.SetDefaultSize(appoptions.Width, appoptions.Height) + result.SetSize(appoptions.Width, appoptions.Height) result.SetDecorated(!appoptions.Frameless) result.SetTitle(appoptions.Title) result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight) @@ -191,9 +171,7 @@ func (w *Window) Center() { } func (w *Window) SetPosition(x int, y int) { - invokeOnMainThread(func() { - C.SetPosition(unsafe.Pointer(w.asGTKWindow()), C.int(x), C.int(y)) - }) + C.SetPosition(unsafe.Pointer(w.asGTKWindow()), C.int(x), C.int(y)) } func (w *Window) Size() (int, int) { @@ -283,18 +261,12 @@ func (w *Window) IsNormal() bool { } func (w *Window) SetBackgroundColour(r uint8, g uint8, b uint8, a uint8) { - windowIsTranslucent := false - if w.appoptions.Linux != nil && w.appoptions.Linux.WindowIsTranslucent { - windowIsTranslucent = true - } data := C.RGBAOptions{ - r: C.uchar(r), - g: C.uchar(g), - b: C.uchar(b), - a: C.uchar(a), - webview: w.webview, - webviewBox: unsafe.Pointer(w.webviewBox), - windowIsTranslucent: gtkBool(windowIsTranslucent), + r: C.uchar(r), + g: C.uchar(g), + b: C.uchar(b), + a: C.uchar(a), + webview: w.webview, } invokeOnMainThread(func() { C.SetBackgroundColour(unsafe.Pointer(&data)) }) @@ -311,9 +283,7 @@ func (w *Window) Run(url string) { if w.menubar != nil { C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.menubar, 0, 0, 0) } - - C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.webviewBox)), C.GTKWIDGET(w.webview), 1, 1, 0) - C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.webviewBox, 1, 1, 0) + C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), C.GTKWIDGET(w.webview), 1, 1, 0) _url := C.CString(url) C.LoadIndex(w.webview, _url) defer C.free(unsafe.Pointer(_url)) @@ -340,10 +310,6 @@ func (w *Window) SetResizable(resizable bool) { C.gtk_window_set_resizable(w.asGTKWindow(), gtkBool(resizable)) } -func (w *Window) SetDefaultSize(width int, height int) { - C.gtk_window_set_default_size(w.asGTKWindow(), C.int(width), C.int(height)) -} - func (w *Window) SetSize(width int, height int) { C.gtk_window_resize(w.asGTKWindow(), C.gint(width), C.gint(height)) } @@ -458,10 +424,6 @@ func (w *Window) ToggleMaximise() { } } -func (w *Window) ShowInspector() { - invokeOnMainThread(func() { C.ShowInspector(w.webview) }) -} - // showModalDialogAndExit shows a modal dialog and exits the app. func showModalDialogAndExit(title, message string) { go func() { diff --git a/v2/internal/frontend/desktop/linux/window.h b/v2/internal/frontend/desktop/linux/window.h index 04410959a..f02542feb 100644 --- a/v2/internal/frontend/desktop/linux/window.h +++ b/v2/internal/frontend/desktop/linux/window.h @@ -55,8 +55,6 @@ typedef struct RGBAOptions uint8_t b; uint8_t a; void *webview; - void *webviewBox; - gboolean windowIsTranslucent; } RGBAOptions; typedef struct SetTitleArgs @@ -106,7 +104,7 @@ gboolean Fullscreen(gpointer data); gboolean UnFullscreen(gpointer data); // WebView -GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop); +GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy); void LoadIndex(void *webview, char *url); void DevtoolsEnabled(void *webview, int enabled, bool showInspector); void ExecuteJS(void *data); @@ -120,9 +118,4 @@ void MessageDialog(void *data); GtkFileFilter **AllocFileFilterArray(size_t ln); void Opendialog(void *data); -// Inspector -void sendShowInspectorMessage(); -void ShowInspector(void *webview); -void InstallF12Hotkey(void *window); - #endif /* window_h */ diff --git a/v2/internal/frontend/desktop/windows/browser.go b/v2/internal/frontend/desktop/windows/browser.go index 13d037b14..f23b04dbe 100644 --- a/v2/internal/frontend/desktop/windows/browser.go +++ b/v2/internal/frontend/desktop/windows/browser.go @@ -4,40 +4,11 @@ package windows import ( - "fmt" "github.com/pkg/browser" - "github.com/wailsapp/wails/v2/internal/frontend/utils" - "golang.org/x/sys/windows" ) -var fallbackBrowserPaths = []string{ - `\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`, - `\Program Files\Google\Chrome\Application\chrome.exe`, - `\Program Files (x86)\Google\Chrome\Application\chrome.exe`, - `\Program Files\Mozilla Firefox\firefox.exe`, -} - // BrowserOpenURL Use the default browser to open the url -func (f *Frontend) BrowserOpenURL(rawURL string) { - url, err := utils.ValidateAndSanitizeURL(rawURL) - if err != nil { - f.logger.Error(fmt.Sprintf("Invalid URL %s", err.Error())) - return - } - +func (f *Frontend) BrowserOpenURL(url string) { // Specific method implementation - err = browser.OpenURL(url) - if err == nil { - return - } - for _, fallback := range fallbackBrowserPaths { - if err := openBrowser(fallback, url); err == nil { - return - } - } - f.logger.Error("Unable to open default system browser") -} - -func openBrowser(path, url string) error { - return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(path), windows.StringToUTF16Ptr(url), nil, windows.SW_SHOWNORMAL) + _ = browser.OpenURL(url) } diff --git a/v2/internal/frontend/desktop/windows/dialog.go b/v2/internal/frontend/desktop/windows/dialog.go index 573325886..1ca422b71 100644 --- a/v2/internal/frontend/desktop/windows/dialog.go +++ b/v2/internal/frontend/desktop/windows/dialog.go @@ -47,7 +47,7 @@ func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (stri return cfd.NewSelectFolderDialog(config) }, false) - if err != nil && err != cfd.ErrCancelled { + if err != nil && err != cfd.ErrorCancelled { return "", err } return result.(string), nil @@ -72,7 +72,7 @@ func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, e return cfd.NewOpenFileDialog(config) }, false) - if err != nil && err != cfd.ErrCancelled { + if err != nil && err != cfd.ErrorCancelled { return "", err } return result.(string), nil @@ -99,7 +99,7 @@ func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ( return cfd.NewOpenMultipleFilesDialog(config) }, true) - if err != nil && err != cfd.ErrCancelled { + if err != nil && err != cfd.ErrorCancelled { return nil, err } return result.([]string), nil @@ -121,16 +121,12 @@ func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, e Folder: defaultFolder, } - if len(options.Filters) > 0 { - config.DefaultExtension = strings.TrimPrefix(strings.Split(options.Filters[0].Pattern, ";")[0], "*") - } - result, err := f.showCfdDialog( func() (cfd.Dialog, error) { return cfd.NewSaveFileDialog(config) }, false) - if err != nil && err != cfd.ErrCancelled { + if err != nil && err != cfd.ErrorCancelled { return "", err } return result.(string), nil diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index 5df13ed98..82289e054 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -7,8 +7,11 @@ import ( "context" "encoding/json" "fmt" + "io" "log" "net" + "net/http" + "net/http/httptest" "net/url" "os" "runtime" @@ -16,31 +19,24 @@ import ( "sync" "text/template" "time" - "unsafe" "github.com/bep/debounce" - "github.com/wailsapp/go-webview2/pkg/edge" "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" - "github.com/wailsapp/wails/v2/internal/frontend/originvalidator" wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime" "github.com/wailsapp/wails/v2/internal/logger" - w32consts "github.com/wailsapp/wails/v2/internal/platform/win32" "github.com/wailsapp/wails/v2/internal/system/operatingsystem" "github.com/wailsapp/wails/v2/pkg/assetserver" - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/windows" - w "golang.org/x/sys/windows" ) const startURL = "http://wails.localhost/" -var secondInstanceBuffer = make(chan options.SecondInstanceData, 1) - type Screen = frontend.Screen type Frontend struct { @@ -52,7 +48,6 @@ type Frontend struct { logger *logger.Logger chromium *edge.Chromium debug bool - devtoolsEnabled bool // Assets assets *assetserver.AssetServer @@ -65,8 +60,6 @@ type Frontend struct { hasStarted bool - originValidator *originvalidator.OriginValidator - // Windows build number versionInfo *operatingsystem.WindowsVersionInfo resizeDebouncer func(f func()) @@ -77,13 +70,6 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. // Get Windows build number versionInfo, _ := operatingsystem.GetWindowsVersionInfo() - // Apply DLL search path settings if specified - if appoptions.Windows != nil && appoptions.Windows.DLLSearchPaths != 0 { - w.SetDefaultDllDirectories(appoptions.Windows.DLLSearchPaths) - } - // Now initialize packages that load DLLs - w32.Init() - w32consts.Init() result := &Frontend{ frontendOptions: appoptions, logger: myLogger, @@ -101,17 +87,14 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. // We currently can't use wails://wails/ as other platforms do, therefore we map the assets sever onto the following url. result.startURL, _ = url.Parse(startURL) - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil { result.startURL = _starturl - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) return result } if port, _ := ctx.Value("assetserverport").(string); port != "" { result.startURL.Host = net.JoinHostPort(result.startURL.Host, port) - result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins) } var bindings string @@ -131,8 +114,6 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. } result.assets = assets - go result.startSecondInstanceProcessor() - return result } @@ -157,22 +138,13 @@ func (f *Frontend) Run(ctx context.Context) error { f.chromium = edge.NewChromium() - if f.frontendOptions.SingleInstanceLock != nil { - SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId) - } - mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo, f.chromium) f.mainWindow = mainWindow var _debug = ctx.Value("debug") - var _devtoolsEnabled = ctx.Value("devtoolsEnabled") - if _debug != nil { f.debug = _debug.(bool) } - if _devtoolsEnabled != nil { - f.devtoolsEnabled = _devtoolsEnabled.(bool) - } f.WindowCenter() f.setupChromium() @@ -185,21 +157,10 @@ func (f *Frontend) Run(ctx context.Context) error { // depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319 event, _ := arg.Data.(*winc.SizeEventData) if event != nil && event.Type == w32.SIZE_MINIMIZED { - // Set minimizing flag to prevent unnecessary redraws during minimize/restore for frameless windows - // 设置最小化标志以防止无边框窗口在最小化/恢复过程中的不必要重绘 - // This fixes window flickering when minimizing/restoring frameless windows - // 这修复了无边框窗口在最小化/恢复时的闪烁问题 - // Reference: https://github.com/wailsapp/wails/issues/3951 - f.mainWindow.isMinimizing = true return } } - // Clear minimizing flag for all non-minimize size events - // 对于所有非最小化的尺寸变化事件,清除最小化标志 - // Reference: https://github.com/wailsapp/wails/issues/3951 - f.mainWindow.isMinimizing = false - if f.resizeDebouncer != nil { f.resizeDebouncer(func() { f.mainWindow.Invoke(func() { @@ -246,7 +207,6 @@ func (f *Frontend) WindowCenter() { func (f *Frontend) WindowSetAlwaysOnTop(b bool) { runtime.LockOSThread() - defer runtime.UnlockOSThread() f.mainWindow.SetAlwaysOnTop(b) } @@ -459,10 +419,6 @@ func (f *Frontend) Quit() { f.mainWindow.Invoke(winc.Exit) } -func (f *Frontend) WindowPrint() { - f.ExecJS("window.print();") -} - func (f *Frontend) setupChromium() { chromium := f.chromium @@ -488,29 +444,10 @@ func (f *Frontend) setupChromium() { chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg) } - if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.DisableWebViewDrop { - if err := chromium.AllowExternalDrag(false); err != nil { - f.logger.Warning("WebView failed to set AllowExternalDrag to false!") - } - } - chromium.MessageCallback = f.processMessage - chromium.MessageWithAdditionalObjectsCallback = f.processMessageWithAdditionalObjects chromium.WebResourceRequestedCallback = f.processRequest chromium.NavigationCompletedCallback = f.navigationCompleted chromium.AcceleratorKeyCallback = func(vkey uint) bool { - if vkey == w32.VK_F12 && f.devtoolsEnabled { - var keyState [256]byte - if w32.GetKeyboardState(keyState[:]) { - // Check if CTRL is pressed - if keyState[w32.VK_CONTROL]&0x80 != 0 && keyState[w32.VK_SHIFT]&0x80 != 0 { - chromium.OpenDevToolsWindow() - return true - } - } else { - f.logger.Error("Call to GetKeyboardState failed") - } - } w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0) return false } @@ -547,24 +484,16 @@ func (f *Frontend) setupChromium() { } chromium.Embed(f.mainWindow.Handle()) - - if chromium.HasCapability(edge.SwipeNavigation) { - swipeGesturesEnabled := f.frontendOptions.Windows != nil && f.frontendOptions.Windows.EnableSwipeGestures - err := chromium.PutIsSwipeNavigationEnabled(swipeGesturesEnabled) - if err != nil { - log.Fatal(err) - } - } chromium.Resize() settings, err := chromium.GetSettings() if err != nil { log.Fatal(err) } - err = settings.PutAreDefaultContextMenusEnabled(f.debug || f.frontendOptions.EnableDefaultContextMenu) + err = settings.PutAreDefaultContextMenusEnabled(f.debug) if err != nil { log.Fatal(err) } - err = settings.PutAreDevToolsEnabled(f.devtoolsEnabled) + err = settings.PutAreDevToolsEnabled(f.debug) if err != nil { log.Fatal(err) } @@ -577,10 +506,6 @@ func (f *Frontend) setupChromium() { if err != nil { log.Fatal(err) } - err = settings.PutIsPinchZoomEnabled(!opts.DisablePinchZoom) - if err != nil { - log.Fatal(err) - } } err = settings.PutIsStatusBarEnabled(false) @@ -591,6 +516,10 @@ func (f *Frontend) setupChromium() { if err != nil { log.Fatal(err) } + err = settings.PutIsSwipeNavigationEnabled(false) + if err != nil { + log.Fatal(err) + } if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup { chromium.OpenDevToolsWindow() @@ -657,31 +586,36 @@ func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, arg return } - webviewRequest, err := webview.NewRequest( - f.chromium.Environment(), - args, - func(fn func()) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - if f.mainWindow.InvokeRequired() { - var wg sync.WaitGroup - wg.Add(1) - f.mainWindow.Invoke(func() { - fn() - wg.Done() - }) - wg.Wait() - } else { - fn() - } - }) + rw := httptest.NewRecorder() + f.assets.ProcessHTTPRequestLegacy(rw, coreWebview2RequestToHttpRequest(req)) - if err != nil { - f.logger.Error("%s: NewRequest failed: %s", uri, err) - return + headers := []string{} + for k, v := range rw.Header() { + headers = append(headers, fmt.Sprintf("%s: %s", k, strings.Join(v, ","))) } - f.assets.ServeWebViewRequest(webviewRequest) + 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. + f.logger.Error("%s: AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError", uri) + code = http.StatusInternalServerError + } + + env := f.chromium.Environment() + response, err := env.CreateWebResourceResponse(rw.Body.Bytes(), code, http.StatusText(code), strings.Join(headers, "\n")) + if err != nil { + f.logger.Error("CreateWebResourceResponse Error: %s", err) + return + } + defer response.Release() + + // Send response back + err = args.PutResponse(response) + if err != nil { + f.logger.Error("PutResponse Error: %s", err) + return + } } var edgeMap = map[string]uintptr{ @@ -695,24 +629,7 @@ var edgeMap = map[string]uintptr{ "nw-resize": w32.HTTOPLEFT, } -func (f *Frontend) processMessage(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) { - topSource, err := sender.GetSource() - if err != nil { - f.logger.Error(fmt.Sprintf("Unable to get source from sender: %s", err.Error())) - return - } - - senderSource, err := args.GetSource() - if err != nil { - f.logger.Error(fmt.Sprintf("Unable to get source from args: %s", err.Error())) - return - } - - // verify both topSource and sender are allowed origins - if !f.validBindingOrigin(topSource) || !f.validBindingOrigin(senderSource) { - return - } - +func (f *Frontend) processMessage(message string) { if message == "drag" { if !f.mainWindow.IsFullScreen() { err := f.startDrag() @@ -724,15 +641,7 @@ func (f *Frontend) processMessage(message string, sender *edge.ICoreWebView2, ar } if message == "runtime:ready" { - cmd := fmt.Sprintf( - "window.wails.setCSSDragProperties('%s', '%s');\n"+ - "window.wails.setCSSDropProperties('%s', '%s');", - f.frontendOptions.CSSDragProperty, - f.frontendOptions.CSSDragValue, - f.frontendOptions.DragAndDrop.CSSDropProperty, - f.frontendOptions.DragAndDrop.CSSDropValue, - ) - + cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue) f.ExecJS(cmd) return } @@ -753,117 +662,25 @@ func (f *Frontend) processMessage(message string, sender *edge.ICoreWebView2, ar return } - go f.dispatchMessage(message) -} - -func (f *Frontend) processMessageWithAdditionalObjects(message string, sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebMessageReceivedEventArgs) { - topSource, err := sender.GetSource() - if err != nil { - f.logger.Error(fmt.Sprintf("Unable to get source from sender: %s", err.Error())) - return - } - - senderSource, err := args.GetSource() - if err != nil { - f.logger.Error(fmt.Sprintf("Unable to get source from args: %s", err.Error())) - return - } - - // verify both topSource and sender are allowed origins - if !f.validBindingOrigin(topSource) || !f.validBindingOrigin(senderSource) { - return - } - - if strings.HasPrefix(message, "file:drop") { - if !f.frontendOptions.DragAndDrop.EnableFileDrop { - return - } - objs, err := args.GetAdditionalObjects() + go func() { + result, err := f.dispatcher.ProcessMessage(message, f) if err != nil { f.logger.Error(err.Error()) + f.Callback(result) + return + } + if result == "" { return } - defer objs.Release() - - count, err := objs.GetCount() - if err != nil { - f.logger.Error(err.Error()) - return + switch result[0] { + case 'c': + // Callback from a method call + f.Callback(result[1:]) + default: + f.logger.Info("Unknown message returned from dispatcher: %+v", result) } - - files := make([]string, count) - for i := uint32(0); i < count; i++ { - _file, err := objs.GetValueAtIndex(i) - if err != nil { - f.logger.Error("cannot get value at %d : %s", i, err.Error()) - return - } - - if _file == nil { - f.logger.Warning("object at %d is not a file", i) - continue - } - - file := (*edge.ICoreWebView2File)(unsafe.Pointer(_file)) - defer file.Release() - - filepath, err := file.GetPath() - if err != nil { - f.logger.Error("cannot get path for object at %d : %s", i, err.Error()) - return - } - - files[i] = filepath - } - - var ( - x = "0" - y = "0" - ) - coords := strings.SplitN(message[10:], ":", 2) - if len(coords) == 2 { - x = coords[0] - y = coords[1] - } - - go f.dispatchMessage(fmt.Sprintf("DD:%s:%s:%s", x, y, strings.Join(files, "\n"))) - return - } -} - -func (f *Frontend) validBindingOrigin(source string) bool { - origin, err := f.originValidator.GetOriginFromURL(source) - if err != nil { - f.logger.Error(fmt.Sprintf("Error parsing source URL %s: %v", source, err.Error())) - return false - } - allowed := f.originValidator.IsOriginAllowed(origin) - if !allowed { - f.logger.Error("Blocked request from unauthorized origin: %s", origin) - return false - } - return true -} - -func (f *Frontend) dispatchMessage(message string) { - result, err := f.dispatcher.ProcessMessage(message, f) - if err != nil { - f.logger.Error(err.Error()) - f.Callback(result) - return - } - if result == "" { - return - } - - switch result[0] { - case 'c': - // Callback from a method call - f.Callback(result[1:]) - default: - f.logger.Info("Unknown message returned from dispatcher: %+v", result) - } + }() } func (f *Frontend) Callback(message string) { @@ -909,10 +726,6 @@ func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.IC f.ExecJS("window.wails.flags.enableResize = true;") } - if f.frontendOptions.DragAndDrop != nil && f.frontendOptions.DragAndDrop.EnableFileDrop { - f.ExecJS("window.wails.flags.enableWailsDragAndDrop = true;") - } - if f.hasStarted { return } @@ -994,11 +807,92 @@ func (f *Frontend) onFocus(arg *winc.Event) { f.chromium.Focus() } -func (f *Frontend) startSecondInstanceProcessor() { - for secondInstanceData := range secondInstanceBuffer { - if f.frontendOptions.SingleInstanceLock != nil && - f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch != nil { - f.frontendOptions.SingleInstanceLock.OnSecondInstanceLaunch(secondInstanceData) +func coreWebview2RequestToHttpRequest(coreReq *edge.ICoreWebView2WebResourceRequest) func() (*http.Request, error) { + return func() (r *http.Request, err error) { + header := http.Header{} + headers, err := coreReq.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") + + method, err := coreReq.GetMethod() + if err != nil { + return nil, fmt.Errorf("GetMethod Error: %s", err) + } + + uri, err := coreReq.GetUri() + if err != nil { + return nil, fmt.Errorf("GetUri Error: %s", err) + } + + var body io.ReadCloser + if content, err := coreReq.GetContent(); err != nil { + return nil, fmt.Errorf("GetContent Error: %s", err) + } else if content != nil { + body = &iStreamReleaseCloser{stream: content} + } + + req, err := http.NewRequest(method, uri, body) + if err != nil { + if body != nil { + body.Close() + } + return nil, err + } + req.Header = header + return req, nil } } + +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() +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/LICENSE b/v2/internal/frontend/desktop/windows/go-webview2/LICENSE new file mode 100644 index 000000000..ef2a0f485 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 John Chadwick +Some portions Copyright (c) 2017 Serge Zaitsev + +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/v2/internal/frontend/desktop/windows/go-webview2/README.md b/v2/internal/frontend/desktop/windows/go-webview2/README.md new file mode 100644 index 000000000..d88a9d1d1 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/README.md @@ -0,0 +1,32 @@ +# go-webview2 + +This is a proof of concept for embedding Webview2 into Go without CGo. It is +based on [webview/webview](https://github.com/webview/webview) and provides a +compatible API. + +## Notice + +Because this version doesn't currently have an EdgeHTML fallback, it will not +work unless you have a Webview2 runtime installed. In addition, it requires the +Webview2Loader DLL in order to function. Adding an EdgeHTML fallback should be +technically possible but will likely require much worse hacks since the API is +not strictly COM to my knowledge. + +## Demo + +For now, you'll need to install the Webview2 runtime, as it does not ship with +Windows. + +[WebView2 runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) + +After that, you should be able to run go-webview2 directly: + +``` +go run go-webview2/cmd/demo +``` + +This will use go-winloader to load an embedded copy of WebView2Loader.dll. + +If this does not work, please try running from a directory that has an +appropriate copy of `WebView2Loader.dll` for your GOARCH. If _that_ worked, +_please_ file a bug so we can figure out what's wrong with go-winloader :) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/internal/w32/w32.go b/v2/internal/frontend/desktop/windows/go-webview2/internal/w32/w32.go new file mode 100644 index 000000000..2b564173f --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/internal/w32/w32.go @@ -0,0 +1,157 @@ +//go:build windows + +package w32 + +import ( + "syscall" + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + ole32 = windows.NewLazySystemDLL("ole32") + Ole32CoInitializeEx = ole32.NewProc("CoInitializeEx") + + kernel32 = windows.NewLazySystemDLL("kernel32") + Kernel32GetCurrentThreadID = kernel32.NewProc("GetCurrentThreadId") + + shlwapi = windows.NewLazySystemDLL("shlwapi") + shlwapiSHCreateMemStream = shlwapi.NewProc("SHCreateMemStream") + + user32 = windows.NewLazySystemDLL("user32") + User32LoadImageW = user32.NewProc("LoadImageW") + User32GetSystemMetrics = user32.NewProc("GetSystemMetrics") + User32RegisterClassExW = user32.NewProc("RegisterClassExW") + User32CreateWindowExW = user32.NewProc("CreateWindowExW") + User32DestroyWindow = user32.NewProc("DestroyWindow") + User32ShowWindow = user32.NewProc("ShowWindow") + User32UpdateWindow = user32.NewProc("UpdateWindow") + User32SetFocus = user32.NewProc("SetFocus") + User32GetMessageW = user32.NewProc("GetMessageW") + User32TranslateMessage = user32.NewProc("TranslateMessage") + User32DispatchMessageW = user32.NewProc("DispatchMessageW") + User32DefWindowProcW = user32.NewProc("DefWindowProcW") + User32GetClientRect = user32.NewProc("GetClientRect") + User32PostQuitMessage = user32.NewProc("PostQuitMessage") + User32SetWindowTextW = user32.NewProc("SetWindowTextW") + User32PostThreadMessageW = user32.NewProc("PostThreadMessageW") + User32GetWindowLongPtrW = user32.NewProc("GetWindowLongPtrW") + User32SetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") + User32AdjustWindowRect = user32.NewProc("AdjustWindowRect") + User32SetWindowPos = user32.NewProc("SetWindowPos") +) + +const ( + SystemMetricsCxIcon = 11 + SystemMetricsCyIcon = 12 +) + +const ( + SWShow = 5 +) + +const ( + SWPNoZOrder = 0x0004 + SWPNoActivate = 0x0010 + SWPNoMove = 0x0002 + SWPFrameChanged = 0x0020 +) + +const ( + WMDestroy = 0x0002 + WMMove = 0x0003 + WMSize = 0x0005 + WMClose = 0x0010 + WMQuit = 0x0012 + WMGetMinMaxInfo = 0x0024 + WMNCLButtonDown = 0x00A1 + WMMoving = 0x0216 + WMApp = 0x8000 +) + +const ( + GWLStyle = -16 +) + +const ( + WSOverlapped = 0x00000000 + WSMaximizeBox = 0x00020000 + WSThickFrame = 0x00040000 + WSCaption = 0x00C00000 + WSSysMenu = 0x00080000 + WSMinimizeBox = 0x00020000 + WSOverlappedWindow = (WSOverlapped | WSCaption | WSSysMenu | WSThickFrame | WSMinimizeBox | WSMaximizeBox) +) + +type WndClassExW struct { + CbSize uint32 + Style uint32 + LpfnWndProc uintptr + CnClsExtra int32 + CbWndExtra int32 + HInstance windows.Handle + HIcon windows.Handle + HCursor windows.Handle + HbrBackground windows.Handle + LpszMenuName *uint16 + LpszClassName *uint16 + HIconSm windows.Handle +} + +type Rect struct { + Left int32 + Top int32 + Right int32 + Bottom int32 +} + +type MinMaxInfo struct { + PtReserved Point + PtMaxSize Point + PtMaxPosition Point + PtMinTrackSize Point + PtMaxTrackSize Point +} + +type Point struct { + X, Y int32 +} + +type Msg struct { + Hwnd syscall.Handle + Message uint32 + WParam uintptr + LParam uintptr + Time uint32 + Pt Point + LPrivate uint32 +} + +func Utf16PtrToString(p *uint16) string { + if p == nil { + return "" + } + // Find NUL terminator. + end := unsafe.Pointer(p) + n := 0 + for *(*uint16)(end) != 0 { + end = unsafe.Pointer(uintptr(end) + unsafe.Sizeof(*p)) + n++ + } + s := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(p))[:n:n] + return string(utf16.Decode(s)) +} + +func SHCreateMemStream(data []byte) (uintptr, error) { + ret, _, err := shlwapiSHCreateMemStream.Call( + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + ) + if ret == 0 { + return 0, err + } + + return ret, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/bridge.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/bridge.go new file mode 100644 index 000000000..ccf04243f --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/bridge.go @@ -0,0 +1,239 @@ +//go:build windows + +package combridge + +import ( + "fmt" + "runtime" + "sync" + "sync/atomic" +) + +var ( + comIfcePointersL sync.RWMutex + comIfcePointers = map[uintptr]*comObject{} // Map from ComInterfacePointer to the Go ComObject +) + +// Resolve the GoInterface of the specified ComInterfacePointer +func Resolve[T IUnknown](ifceP uintptr) T { + comIfcePointersL.RLock() + comObj := comIfcePointers[ifceP] + comIfcePointersL.RUnlock() + + var n T + if comObj != nil { + t := comObj.resolve(ifceP) + if t != nil { + n = t.(T) + } + } + + return n +} + +// New returns a new ComObject which implements the specified Com Interface, com calls will be redirected +// to the specified go interface. +func New[T IUnknown](obj T) *ComObject[T] { + cObj := new( + ifceDef[T]{obj}, + ) + return newComObject[T](cObj) +} + +// New2 returns a new ComObject which implements the two specified Com Interfaces, com calls will be redirected +// to those interfaces accordingly. +// This is needed if a ComObject should implement two interfaces that are not descendants of each other, +// then you get multiple inheritance. +func New2[T IUnknown, T2 IUnknown](obj T, obj2 T2) *ComObject[T] { + cObj := new( + ifceDef[T]{obj}, + ifceDef[T2]{obj2}, + ) + return newComObject[T](cObj) +} + +// new returns a new ComObject which implements multiple specified Com Interfaces, com calls will be redirected +// to the specified go interfaces accordingly. +// This is needed if a ComObject should implement multiple interfaces that are not descendants of each other, +// then you get multiple inheritance. +func new(impls ...ifceImpl) *comObject { + impls = append([]ifceImpl{ifceDef[IUnknown]{}}, impls...) + + cObj := &comObject{ + refCount: 1, + ifces: map[string]int{}, + ifcesImpl: make([]comInterfaceDesc, len(impls)), + } + + for i, ifceDef := range impls { + vtable, err := ifceDef.ifce() + if err != nil { + panic(err) + } + + needsImplement := false + for table := vtable; table != nil; table = table.Parent { + guid := table.ComGUID + if i, found := cObj.ifces[guid]; found { + // This Interface is already implemented + if guid == iUnknownGUID { + // IUnknown is a special interface and never has an user specific implementation + } else if cObj.ifcesImpl[i].impl != ifceDef.impl() { + panic(fmt.Sprintf("Interface '%s' is already implemented by another object", table.Name)) + } + + break + } + + needsImplement = true + cObj.ifces[guid] = i + } + + if !needsImplement { + continue + } + + ifceP, ifcePSlice := allocUintptrObject(1) + ifcePSlice[0] = vtable.ComVTable + cObj.ifcesImpl[i] = comInterfaceDesc{ifceP, ifceDef.impl()} + } + + comIfcePointersL.Lock() + for _, ifceImpl := range cObj.ifcesImpl { + comIfcePointers[ifceImpl.ref] = cObj + } + comIfcePointersL.Unlock() + + return cObj +} + +func newComObject[T IUnknown](comObj *comObject) *ComObject[T] { + c := &ComObject[T]{obj: comObj} + // Make sure to async release since release needs locks and might block the finalizer goroutine for a longer period + runtime.SetFinalizer(c, func(obj *ComObject[T]) { obj.close(true) }) + return c +} + +// ComObject describes an exported go instance to be used as a ComObject which implements +// the specified Interface. +type ComObject[T IUnknown] struct { + obj *comObject + closed int32 +} + +// Ref returns the native uintptr that points to the ComObject that is an interface pointer to T. +// This can be used in native calls. If the object has been closed this function will panic. +func (o *ComObject[T]) Ref() uintptr { + if atomic.LoadInt32(&o.closed) != 0 { + panic("ComObject has been released") + } + return o.obj.queryInterface(guidOf[T](), false) +} + +// Close releases the native com object from the go side. It will only be destroyed if the ref counter +// reaches zero. +// After closing `Ref()` will panic. +func (o *ComObject[T]) Close() error { + o.close(false) + return nil +} + +// close releases the native com object from the go side. It will only be destroyed if the ref counter +// reaches zero. +// After closing `Ref()` will panic. +func (o *ComObject[T]) close(asyncRelease bool) { + if atomic.CompareAndSwapInt32(&o.closed, 0, 1) { + runtime.SetFinalizer(o, nil) + if asyncRelease { + go o.obj.release() + } else { + o.obj.release() + } + } +} + +type comInterfaceDesc struct { + ref uintptr // The native Com InterfacePointer + impl any // The golang target object +} + +type comObject struct { + l sync.Mutex + + refCount int32 + ifces map[string]int // Map of ComInterfaceGUID to Interface Slots + ifcesImpl []comInterfaceDesc // Slots with InterfaceDescriptors +} + +func (c *comObject) queryInterface(ifceGUID string, withAddRef bool) uintptr { + c.l.Lock() + defer c.l.Unlock() + if c.refCount <= 0 { + panic("call on released com object") + } + + i, found := c.ifces[ifceGUID] + if !found { + return 0 + } + + if withAddRef { + c.refCount++ + } + return c.ifcesImpl[i].ref +} + +func (c *comObject) resolve(ifceP uintptr) any { + c.l.Lock() + defer c.l.Unlock() + if c.refCount <= 0 { + panic("call on destroyed com object") + } + + for _, ifce := range c.ifcesImpl { + if ifce.ref != ifceP { + continue + } + + return ifce.impl + } + return nil +} + +func (c *comObject) addRef() int32 { + c.l.Lock() + defer c.l.Unlock() + if c.refCount <= 0 { + panic("call on destroyed com object") + } + + c.refCount++ + return c.refCount +} + +func (c *comObject) release() int32 { + c.l.Lock() + defer c.l.Unlock() + if c.refCount <= 0 { + panic("call on destroyed com object") + } + + if c.refCount--; c.refCount == 0 { + comIfcePointersL.Lock() + for _, ref := range c.ifcesImpl { + delete(comIfcePointers, ref.ref) + } + comIfcePointersL.Unlock() + + for _, impl := range c.ifcesImpl { + ref := impl.ref + if ref == 0 { + continue + } + + globalFree(ref) + } + } + + return c.refCount +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/iunknown.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/iunknown.go new file mode 100644 index 000000000..90d7247fe --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/iunknown.go @@ -0,0 +1,56 @@ +//go:build windows + +package combridge + +import ( + "golang.org/x/sys/windows" +) + +const iUnknownGUID = "{00000000-0000-0000-C000-000000000046}" + +func init() { + registerVTableInternal[IUnknown, IUnknown]( + iUnknownGUID, + true, + iUnknownQueryInterface, + iUnknownAddRef, + iUnknownRelease, + ) +} + +type IUnknown interface{} + +func iUnknownQueryInterface(this uintptr, refiid *windows.GUID, ppvObject *uintptr) uintptr { + if refiid == nil || ppvObject == nil { + return uintptr(windows.E_INVALIDARG) + } + + comIfcePointersL.RLock() + obj := comIfcePointers[this] + comIfcePointersL.RUnlock() + + ref := obj.queryInterface(refiid.String(), true) + if ref != 0 { + *ppvObject = ref + return windows.NO_ERROR + } + + *ppvObject = 0 + return uintptr(windows.E_NOINTERFACE) +} + +func iUnknownAddRef(this uintptr) uintptr { + comIfcePointersL.RLock() + obj := comIfcePointers[this] + comIfcePointersL.RUnlock() + + return uintptr(obj.addRef()) +} + +func iUnknownRelease(this uintptr) uintptr { + comIfcePointersL.RLock() + obj := comIfcePointers[this] + comIfcePointersL.RUnlock() + + return uintptr(obj.release()) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/iunknown_impl.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/iunknown_impl.go new file mode 100644 index 000000000..4c748d461 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/iunknown_impl.go @@ -0,0 +1,74 @@ +//go:build windows + +package combridge + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +// IUnknownFromPointer cast a generic pointer into a IUnknownImpl pointer +func IUnknownFromPointer(ref unsafe.Pointer) *IUnknownImpl { + return (*IUnknownImpl)(ref) +} + +// IUnknownFromPointer cast native pointer into a IUnknownImpl pointer +func IUnknownFromUintptr(ref uintptr) *IUnknownImpl { + return IUnknownFromPointer(unsafe.Pointer(ref)) +} + +type IUnknownVtbl struct { + queryInterface uintptr + addRef uintptr + release uintptr +} + +func (i *IUnknownVtbl) QueryInterface(this unsafe.Pointer, refiid *windows.GUID, ppvObject **IUnknownImpl) error { + r, _, _ := syscall.SyscallN( + i.queryInterface, + uintptr(this), + uintptr(unsafe.Pointer(refiid)), + uintptr(unsafe.Pointer(ppvObject)), + ) + + if r != uintptr(windows.S_OK) { + return syscall.Errno(r) + } + + return nil +} + +func (i *IUnknownVtbl) AddRef(this unsafe.Pointer) uint32 { + r, _, _ := syscall.SyscallN( + i.addRef, + uintptr(this), + ) + return uint32(r) +} + +func (i *IUnknownVtbl) Release(this unsafe.Pointer) uint32 { + r, _, _ := syscall.SyscallN( + i.release, + uintptr(this), + ) + + return uint32(r) +} + +type IUnknownImpl struct { + vtbl *IUnknownVtbl +} + +func (i *IUnknownImpl) QueryInterface(refiid *windows.GUID, ppvObject **IUnknownImpl) error { + return i.vtbl.QueryInterface(unsafe.Pointer(i), refiid, ppvObject) +} + +func (i *IUnknownImpl) AddRef() uint32 { + return i.vtbl.AddRef(unsafe.Pointer(i)) +} + +func (i *IUnknownImpl) Release() uint32 { + return i.vtbl.Release(unsafe.Pointer(i)) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/syscall.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/syscall.go new file mode 100644 index 000000000..17b7f500e --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/syscall.go @@ -0,0 +1,39 @@ +//go:build windows + +package combridge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + procGlobalAlloc = modkernel32.NewProc("GlobalAlloc") + procGlobalFree = modkernel32.NewProc("GlobalFree") + + uintptrSize = unsafe.Sizeof(uintptr(0)) +) + +func allocUintptrObject(size int) (uintptr, []uintptr) { + v := globalAlloc(uintptr(size) * uintptrSize) + slice := unsafe.Slice((*uintptr)(unsafe.Pointer(v)), size) + return v, slice +} + +func globalAlloc(dwBytes uintptr) uintptr { + ret, _, _ := procGlobalAlloc.Call(uintptr(0), dwBytes) + if ret == 0 { + panic("globalAlloc failed") + } + + return ret +} + +func globalFree(data uintptr) { + ret, _, _ := procGlobalFree.Call(data) + if ret != 0 { + panic("globalFree failed") + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/vtables.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/vtables.go new file mode 100644 index 000000000..b099a7848 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge/vtables.go @@ -0,0 +1,147 @@ +//go:build windows + +package combridge + +import ( + "fmt" + "reflect" + "sync" + + "golang.org/x/sys/windows" +) + +var ( + vTablesL sync.Mutex + vTables = make(map[string]*vTable) +) + +// RegisterVTable registers the vtable trampoline methods for the specified ComInterface +// TBase is the base interface of T, and must be another ComInterface which roots in IUnknown or IUnknown itself. +// The first paramter of the fn is always the uintptr of the ComObject and the GoObject can be resolved with Resolve(). +// After having resolved the GoObject the call must be redirected to the GoObject. +// Typically a trampoline FN looks like this. +// +// func _ICoreWebView2NavigationCompletedEventHandlerInvoke(this uintptr, sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr { +// return combridge.Resolve[_ICoreWebView2NavigationCompletedEventHandler](this).NavigationCompleted(sender, args) +// } +// +// The order of registration must be in the correct order as specified in the IDL of the interface. +func RegisterVTable[TParent, T IUnknown](guid string, fns ...interface{}) { + registerVTableInternal[TParent, T](guid, false, fns...) +} + +type vTable struct { + Parent *vTable + + Name string + ComGUID string + ComVTable uintptr + ComProcs []uintptr +} + +func registerVTableInternal[TParent, T IUnknown](guid string, isInternal bool, fns ...interface{}) { + vTablesL.Lock() + defer vTablesL.Unlock() + + t, tName := typeInterfaceToString[T]() + tParent, tParentName := typeInterfaceToString[TParent]() + if !t.Implements(tParent) { + panic(fmt.Errorf("RegisterVTable '%s': '%s' must implement '%s'", tName, tName, tParentName)) + } + + if !isInternal { + if t == reflect.TypeOf((*IUnknown)(nil)).Elem() { + panic(fmt.Errorf("RegisterVTable '%s' IUnknown can't be registered", tName)) + } + + if t == tParent { + panic(fmt.Errorf("RegisterVTable '%s': T and TParent can't be the same type", tName)) + } + } + + var parent *vTable + var parentProcs []uintptr + var parentProcsCount int + if t != tParent { + parent = vTables[tParentName] + if parent == nil { + panic(fmt.Errorf("RegisterVTable '%s': Parent VTable '%s' not registered", tName, tParentName)) + } + + parentProcs = parent.ComProcs + parentProcsCount = len(parentProcs) + } + + comGuid, err := windows.GUIDFromString(guid) + if err != nil { + panic(fmt.Errorf("RegisterVTable '%s': invalid guid: %s", tName, err)) + } + + vTable := &vTable{ + Parent: parent, + Name: tName, + ComGUID: comGuid.String(), + } + vTable.ComVTable, vTable.ComProcs = allocUintptrObject(parentProcsCount + len(fns)) + + for i, proc := range parentProcs { + vTable.ComProcs[i] = proc + } + + for i, fn := range fns { + vTable.ComProcs[parentProcsCount+i] = windows.NewCallback(fn) + } + + vTables[tName] = vTable +} + +func typeInterfaceToString[T any]() (reflect.Type, string) { + t := reflect.TypeOf((*T)(nil)) + if t.Kind() != reflect.Pointer { + panic("must be a (*yourInterfaceType)(nil)") + } + t = t.Elem() + return t, t.PkgPath() + "/" + t.Name() +} + +func typeInterfaceToStringOnly[T any]() string { + _, nane := typeInterfaceToString[T]() + return nane +} + +func guidOf[T any]() string { + vtable := vTableOf[T]() + if vtable == nil { + return "" + } + return vtable.ComGUID +} + +func vTableOf[T any]() *vTable { + name := typeInterfaceToStringOnly[T]() + vTablesL.Lock() + defer vTablesL.Unlock() + + return vTables[name] +} + +type ifceImpl interface { + impl() any + ifce() (*vTable, error) +} + +type ifceDef[T any] struct { + objImpl any +} + +func (i ifceDef[T]) impl() any { + return i.objImpl +} + +func (i ifceDef[T]) ifce() (*vTable, error) { + vtable := vTableOf[T]() + if vtable == nil { + return nil, fmt.Errorf("Unable to find vTable for %s", typeInterfaceToStringOnly[T]()) + } + return vtable, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_COLOR.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_COLOR.go new file mode 100644 index 000000000..429ecef24 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_COLOR.go @@ -0,0 +1,10 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_COLOR struct { + A uint8 + R uint8 + G uint8 + B uint8 +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND.go new file mode 100644 index 000000000..ed106ed44 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND.go @@ -0,0 +1,11 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND uint32 + +const ( + COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY = iota + COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_ALLOW + COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS +) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_KEY_EVENT_KIND.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_KEY_EVENT_KIND.go new file mode 100644 index 000000000..607147535 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_KEY_EVENT_KIND.go @@ -0,0 +1,12 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_KEY_EVENT_KIND uint32 + +const ( + COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN = 0 + COREWEBVIEW2_KEY_EVENT_KIND_KEY_UP = 1 + COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN = 2 + COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_UP = 3 +) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_MOVE_FOCUS_REASON.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_MOVE_FOCUS_REASON.go new file mode 100644 index 000000000..c1679cc37 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_MOVE_FOCUS_REASON.go @@ -0,0 +1,11 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_MOVE_FOCUS_REASON uint32 + +const ( + COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC = 0 + COREWEBVIEW2_MOVE_FOCUS_REASON_NEXT = 1 + COREWEBVIEW2_MOVE_FOCUS_REASON_PREVIOUS = 2 +) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PHYSICAL_KEY_STATUS.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PHYSICAL_KEY_STATUS.go new file mode 100644 index 000000000..dd8834255 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PHYSICAL_KEY_STATUS.go @@ -0,0 +1,12 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_PHYSICAL_KEY_STATUS struct { + RepeatCount uint32 + ScanCode uint32 + IsExtendedKey bool + IsMenuKeyDown bool + WasKeyDown bool + IsKeyReleased bool +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PROCESS_FAILED_KIND.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PROCESS_FAILED_KIND.go new file mode 100644 index 000000000..a7e9aa339 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_PROCESS_FAILED_KIND.go @@ -0,0 +1,49 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_PROCESS_FAILED_KIND uint32 + +const ( + // Indicates that the browser process ended unexpectedly. The WebView + // automatically moves to the Closed state. The app has to recreate a new + // WebView to recover from this failure. + COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED = 0 + + // Indicates that the main frame's render process ended unexpectedly. A new + // render process is created automatically and navigated to an error page. + // You can use the `Reload` method to try to reload the page that failed. + COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED = 1 + + // Indicates that the main frame's render process is unresponsive. + // + // Note that this does not seem to work right now. + // Does not fire for simple long running script case, the only related test + // SitePerProcessBrowserTest::NoCommitTimeoutForInvisibleWebContents is + // disabled. + COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_UNRESPONSIVE = 2 + + // Indicates that a frame-only render process ended unexpectedly. The process + // exit does not affect the top-level document, only a subset of the + // subframes within it. The content in these frames is replaced with an error + // page in the frame. + COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED = 3 + + // Indicates that a utility process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_UTILITY_PROCESS_EXITED = 4 + + // Indicates that a sandbox helper process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_SANDBOX_HELPER_PROCESS_EXITED = 5 + + // Indicates that the GPU process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_GPU_PROCESS_EXITED = 6 + + // Indicates that a PPAPI plugin process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_PLUGIN_PROCESS_EXITED = 7 + + // Indicates that a PPAPI plugin broker process ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_PPAPI_BROKER_PROCESS_EXITED = 8 + + // Indicates that a process of unspecified kind ended unexpectedly. + COREWEBVIEW2_PROCESS_FAILED_KIND_UNKNOWN_PROCESS_EXITED = 9 +) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_WEB_RESOURCE_CONTEXT.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_WEB_RESOURCE_CONTEXT.go new file mode 100644 index 000000000..2e9261d0e --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/COREWEBVIEW2_WEB_RESOURCE_CONTEXT.go @@ -0,0 +1,25 @@ +//go:build windows + +package edge + +type COREWEBVIEW2_WEB_RESOURCE_CONTEXT uint32 + +const ( + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL = 0 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT = 1 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_STYLESHEET = 2 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_IMAGE = 3 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_MEDIA = 4 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_FONT = 5 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_SCRIPT = 6 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_XML_HTTP_REQUEST = 7 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_FETCH = 8 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_TEXT_TRACK = 9 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_EVENT_SOURCE = 10 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_WEBSOCKET = 11 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_MANIFEST = 12 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_SIGNED_EXCHANGE = 13 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_PING = 14 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_CSP_VIOLATION_REPORT = 15 + COREWEBVIEW2_WEB_RESOURCE_CONTEXT_OTHER = 16 +) diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventArgs.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventArgs.go new file mode 100644 index 000000000..2a3a9c823 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventArgs.go @@ -0,0 +1,79 @@ +//go:build windows + +package edge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2AcceleratorKeyPressedEventArgsVtbl struct { + _IUnknownVtbl + GetKeyEventKind ComProc + GetVirtualKey ComProc + GetKeyEventLParam ComProc + GetPhysicalKeyStatus ComProc + GetHandled ComProc + PutHandled ComProc +} + +type ICoreWebView2AcceleratorKeyPressedEventArgs struct { + vtbl *_ICoreWebView2AcceleratorKeyPressedEventArgsVtbl +} + +func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) GetKeyEventKind() (COREWEBVIEW2_KEY_EVENT_KIND, error) { + var err error + var keyEventKind COREWEBVIEW2_KEY_EVENT_KIND + _, _, err = i.vtbl.GetKeyEventKind.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&keyEventKind)), + ) + if err != windows.ERROR_SUCCESS { + return 0, err + } + return keyEventKind, nil +} + +func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) GetVirtualKey() (uint, error) { + var err error + var virtualKey uint + _, _, err = i.vtbl.GetVirtualKey.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&virtualKey)), + ) + if err != windows.ERROR_SUCCESS { + return 0, err + } + return virtualKey, nil +} + +func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) GetPhysicalKeyStatus() (COREWEBVIEW2_PHYSICAL_KEY_STATUS, error) { + var err error + var physicalKeyStatus COREWEBVIEW2_PHYSICAL_KEY_STATUS + _, _, err = i.vtbl.GetPhysicalKeyStatus.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&physicalKeyStatus)), + ) + if err != windows.ERROR_SUCCESS { + return COREWEBVIEW2_PHYSICAL_KEY_STATUS{}, err + } + return physicalKeyStatus, nil +} + +func (i *ICoreWebView2AcceleratorKeyPressedEventArgs) PutHandled(handled bool) error { + var err error + + _, _, err = i.vtbl.PutHandled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(handled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventHandler.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventHandler.go new file mode 100644 index 000000000..2c276560b --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2AcceleratorKeyPressedEventHandler.go @@ -0,0 +1,53 @@ +//go:build windows + +package edge + +type _ICoreWebView2AcceleratorKeyPressedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type ICoreWebView2AcceleratorKeyPressedEventHandler struct { + vtbl *_ICoreWebView2AcceleratorKeyPressedEventHandlerVtbl + impl _ICoreWebView2AcceleratorKeyPressedEventHandlerImpl +} + +func (i *ICoreWebView2AcceleratorKeyPressedEventHandler) AddRef() uintptr { + return i.AddRef() +} +func _ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownQueryInterface(this *ICoreWebView2AcceleratorKeyPressedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownAddRef(this *ICoreWebView2AcceleratorKeyPressedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownRelease(this *ICoreWebView2AcceleratorKeyPressedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2AcceleratorKeyPressedEventHandlerInvoke(this *ICoreWebView2AcceleratorKeyPressedEventHandler, sender *ICoreWebView2Controller, args *ICoreWebView2AcceleratorKeyPressedEventArgs) uintptr { + return this.impl.AcceleratorKeyPressed(sender, args) +} + +type _ICoreWebView2AcceleratorKeyPressedEventHandlerImpl interface { + _IUnknownImpl + AcceleratorKeyPressed(sender *ICoreWebView2Controller, args *ICoreWebView2AcceleratorKeyPressedEventArgs) uintptr +} + +var _ICoreWebView2AcceleratorKeyPressedEventHandlerFn = _ICoreWebView2AcceleratorKeyPressedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2AcceleratorKeyPressedEventHandlerInvoke), +} + +func newICoreWebView2AcceleratorKeyPressedEventHandler(impl _ICoreWebView2AcceleratorKeyPressedEventHandlerImpl) *ICoreWebView2AcceleratorKeyPressedEventHandler { + return &ICoreWebView2AcceleratorKeyPressedEventHandler{ + vtbl: &_ICoreWebView2AcceleratorKeyPressedEventHandlerFn, + impl: impl, + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Controller.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Controller.go new file mode 100644 index 000000000..c95a00ade --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Controller.go @@ -0,0 +1,160 @@ +//go:build windows + +package edge + +import ( + "math" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/internal/w32" + "golang.org/x/sys/windows" +) + +type _ICoreWebView2ControllerVtbl struct { + _IUnknownVtbl + GetIsVisible ComProc + PutIsVisible ComProc + GetBounds ComProc + PutBounds ComProc + GetZoomFactor ComProc + PutZoomFactor ComProc + AddZoomFactorChanged ComProc + RemoveZoomFactorChanged ComProc + SetBoundsAndZoomFactor ComProc + MoveFocus ComProc + AddMoveFocusRequested ComProc + RemoveMoveFocusRequested ComProc + AddGotFocus ComProc + RemoveGotFocus ComProc + AddLostFocus ComProc + RemoveLostFocus ComProc + AddAcceleratorKeyPressed ComProc + RemoveAcceleratorKeyPressed ComProc + GetParentWindow ComProc + PutParentWindow ComProc + NotifyParentWindowPositionChanged ComProc + Close ComProc + GetCoreWebView2 ComProc +} + +type ICoreWebView2Controller struct { + vtbl *_ICoreWebView2ControllerVtbl +} + +func (i *ICoreWebView2Controller) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2Controller) GetBounds() (*w32.Rect, error) { + var err error + var bounds w32.Rect + _, _, err = i.vtbl.GetBounds.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&bounds)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + return &bounds, nil +} + +func (i *ICoreWebView2Controller) PutBounds(bounds w32.Rect) error { + var err error + + _, _, err = i.vtbl.PutBounds.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&bounds)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Controller) MoveFocus(reason COREWEBVIEW2_MOVE_FOCUS_REASON) error { + var err error + + _, _, err = i.vtbl.MoveFocus.Call( + uintptr(unsafe.Pointer(i)), + uintptr(reason), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Controller) AddAcceleratorKeyPressed(eventHandler *ICoreWebView2AcceleratorKeyPressedEventHandler, token *_EventRegistrationToken) error { + var err error + _, _, err = i.vtbl.AddAcceleratorKeyPressed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(eventHandler)), + uintptr(unsafe.Pointer(&token)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Controller) PutIsVisible(isVisible bool) error { + var err error + + _, _, err = i.vtbl.PutIsVisible.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isVisible)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Controller) GetICoreWebView2Controller2() *ICoreWebView2Controller2 { + + var result *ICoreWebView2Controller2 + + iidICoreWebView2Controller2 := NewGUID("{c979903e-d4ca-4228-92eb-47ee3fa96eab}") + i.vtbl.QueryInterface.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(iidICoreWebView2Controller2)), + uintptr(unsafe.Pointer(&result))) + + return result +} + +func (i *ICoreWebView2Controller) NotifyParentWindowPositionChanged() error { + var err error + _, _, err = i.vtbl.NotifyParentWindowPositionChanged.Call( + uintptr(unsafe.Pointer(i)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Controller) PutZoomFactor(zoomFactor float64) error { + var err error + _, _, err = i.vtbl.PutZoomFactor.Call( + uintptr(unsafe.Pointer(i)), + uintptr(math.Float64bits(zoomFactor)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Controller) GetZoomFactor() (float64, error) { + var err error + var zoomFactorUint64 uint64 + _, _, err = i.vtbl.GetZoomFactor.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&zoomFactorUint64)), + ) + if err != windows.ERROR_SUCCESS { + return 0.0, err + } + return math.Float64frombits(zoomFactorUint64), nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Controller2.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Controller2.go new file mode 100644 index 000000000..eff315a91 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Controller2.go @@ -0,0 +1,75 @@ +//go:build windows + +package edge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2Controller2Vtbl struct { + _IUnknownVtbl + GetIsVisible ComProc + PutIsVisible ComProc + GetBounds ComProc + PutBounds ComProc + GetZoomFactor ComProc + PutZoomFactor ComProc + AddZoomFactorChanged ComProc + RemoveZoomFactorChanged ComProc + SetBoundsAndZoomFactor ComProc + MoveFocus ComProc + AddMoveFocusRequested ComProc + RemoveMoveFocusRequested ComProc + AddGotFocus ComProc + RemoveGotFocus ComProc + AddLostFocus ComProc + RemoveLostFocus ComProc + AddAcceleratorKeyPressed ComProc + RemoveAcceleratorKeyPressed ComProc + GetParentWindow ComProc + PutParentWindow ComProc + NotifyParentWindowPositionChanged ComProc + Close ComProc + GetCoreWebView2 ComProc + GetDefaultBackgroundColor ComProc + PutDefaultBackgroundColor ComProc +} + +type ICoreWebView2Controller2 struct { + vtbl *_ICoreWebView2Controller2Vtbl +} + +func (i *ICoreWebView2Controller2) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2Controller2) GetDefaultBackgroundColor() (*COREWEBVIEW2_COLOR, error) { + var err error + var backgroundColor *COREWEBVIEW2_COLOR + _, _, err = i.vtbl.GetDefaultBackgroundColor.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&backgroundColor)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + return backgroundColor, nil +} + +func (i *ICoreWebView2Controller2) PutDefaultBackgroundColor(backgroundColor COREWEBVIEW2_COLOR) error { + var err error + + // Cast to a uint32 as that's what the call is expecting + col := *(*uint32)(unsafe.Pointer(&backgroundColor)) + + _, _, err = i.vtbl.PutDefaultBackgroundColor.Call( + uintptr(unsafe.Pointer(i)), + uintptr(col), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2CreateCoreWebView2ControllerCompletedHandler.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2CreateCoreWebView2ControllerCompletedHandler.go new file mode 100644 index 000000000..c0e4d13b7 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2CreateCoreWebView2ControllerCompletedHandler.go @@ -0,0 +1,53 @@ +//go:build windows + +package edge + +type _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type iCoreWebView2CreateCoreWebView2ControllerCompletedHandler struct { + vtbl *_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl + impl _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerImpl +} + +func (i *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler) AddRef() uintptr { + return i.AddRef() +} +func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownQueryInterface(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownAddRef(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownRelease(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerInvoke(this *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler, errorCode uintptr, createdController *ICoreWebView2Controller) uintptr { + return this.impl.CreateCoreWebView2ControllerCompleted(errorCode, createdController) +} + +type _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerImpl interface { + _IUnknownImpl + CreateCoreWebView2ControllerCompleted(errorCode uintptr, createdController *ICoreWebView2Controller) uintptr +} + +var _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerFn = _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerInvoke), +} + +func newICoreWebView2CreateCoreWebView2ControllerCompletedHandler(impl _ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerImpl) *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler { + return &iCoreWebView2CreateCoreWebView2ControllerCompletedHandler{ + vtbl: &_ICoreWebView2CreateCoreWebView2ControllerCompletedHandlerFn, + impl: impl, + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2HttpHeadersCollectionIterator.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2HttpHeadersCollectionIterator.go new file mode 100644 index 000000000..0c9eacb46 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2HttpHeadersCollectionIterator.go @@ -0,0 +1,78 @@ +//go:build windows + +package edge + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2HttpHeadersCollectionIteratorVtbl struct { + _IUnknownVtbl + GetCurrentHeader ComProc + GetHasCurrentHeader ComProc + MoveNext ComProc +} + +type ICoreWebView2HttpHeadersCollectionIterator struct { + vtbl *_ICoreWebView2HttpHeadersCollectionIteratorVtbl +} + +func (i *ICoreWebView2HttpHeadersCollectionIterator) Release() error { + return i.vtbl.CallRelease(unsafe.Pointer(i)) +} + +func (i *ICoreWebView2HttpHeadersCollectionIterator) HasCurrentHeader() (bool, error) { + var hasHeader int32 + res, _, err := i.vtbl.GetHasCurrentHeader.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&hasHeader)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + if windows.Handle(res) != windows.S_OK { + return false, syscall.Errno(res) + } + return hasHeader != 0, nil +} + +func (i *ICoreWebView2HttpHeadersCollectionIterator) GetCurrentHeader() (string, string, error) { + // Create *uint16 to hold result + var _name *uint16 + var _value *uint16 + res, _, err := i.vtbl.GetCurrentHeader.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&_name)), + uintptr(unsafe.Pointer(&_value)), + ) + if err != windows.ERROR_SUCCESS { + return "", "", err + } + if windows.Handle(res) != windows.S_OK { + return "", "", syscall.Errno(res) + } + // Get result and cleanup + name := windows.UTF16PtrToString(_name) + windows.CoTaskMemFree(unsafe.Pointer(_name)) + value := windows.UTF16PtrToString(_value) + windows.CoTaskMemFree(unsafe.Pointer(_value)) + return name, value, nil +} + +func (i *ICoreWebView2HttpHeadersCollectionIterator) MoveNext() (bool, error) { + var next int32 + res, _, err := i.vtbl.MoveNext.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&next)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + if windows.Handle(res) != windows.S_OK { + return false, syscall.Errno(res) + } + return next != 0, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2HttpRequestHeaders.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2HttpRequestHeaders.go new file mode 100644 index 000000000..5a147b299 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2HttpRequestHeaders.go @@ -0,0 +1,101 @@ +//go:build windows + +package edge + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + ERROR_ELEMENT_NOT_FOUND syscall.Errno = 0x80070490 +) + +type _ICoreWebView2HttpRequestHeadersVtbl struct { + _IUnknownVtbl + GetHeader ComProc + GetHeaders ComProc + Contains ComProc + SetHeader ComProc + RemoveHeader ComProc + GetIterator ComProc +} + +type ICoreWebView2HttpRequestHeaders struct { + vtbl *_ICoreWebView2HttpRequestHeadersVtbl +} + +func (i *ICoreWebView2HttpRequestHeaders) Release() error { + return i.vtbl.CallRelease(unsafe.Pointer(i)) +} + +// GetHeader returns the value of the specified header. If the header is not found +// ERROR_ELEMENT_NOT_FOUND is returned as error. +func (i *ICoreWebView2HttpRequestHeaders) GetHeader(name string) (string, error) { + _name, err := windows.UTF16PtrFromString(name) + if err != nil { + return "", nil + } + + var _value *uint16 + res, _, err := i.vtbl.GetHeader.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(_name)), + uintptr(unsafe.Pointer(&_value)), + ) + if err != windows.ERROR_SUCCESS { + return "", err + } + if windows.Handle(res) != windows.S_OK { + return "", syscall.Errno(res) + } + + value := windows.UTF16PtrToString(_value) + windows.CoTaskMemFree(unsafe.Pointer(_value)) + return value, nil +} + +// SetHeader sets the specified header to the value. +func (i *ICoreWebView2HttpRequestHeaders) SetHeader(name, value string) error { + _name, err := windows.UTF16PtrFromString(name) + if err != nil { + return nil + } + + _value, err := windows.UTF16PtrFromString(value) + if err != nil { + return nil + } + + res, _, err := i.vtbl.SetHeader.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(_name)), + uintptr(unsafe.Pointer(_value)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + if windows.Handle(res) != windows.S_OK { + return syscall.Errno(res) + } + return nil +} + +// GetIterator returns an iterator over the collection of request headers. Make sure to call +// Release on the returned Object after finished using it. +func (i *ICoreWebView2HttpRequestHeaders) GetIterator() (*ICoreWebView2HttpHeadersCollectionIterator, error) { + var headers *ICoreWebView2HttpHeadersCollectionIterator + res, _, err := i.vtbl.GetIterator.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&headers)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + if windows.Handle(res) != windows.S_OK { + return nil, syscall.Errno(res) + } + return headers, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go new file mode 100644 index 000000000..c3998e0a2 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventArgs.go @@ -0,0 +1,18 @@ +//go:build windows + +package edge + +type _ICoreWebView2NavigationCompletedEventArgsVtbl struct { + _IUnknownVtbl + GetIsSuccess ComProc + GetWebErrorStatus ComProc + GetNavigationId ComProc +} + +type ICoreWebView2NavigationCompletedEventArgs struct { + vtbl *_ICoreWebView2NavigationCompletedEventArgsVtbl +} + +func (i *ICoreWebView2NavigationCompletedEventArgs) AddRef() uintptr { + return i.AddRef() +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventHandler.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventHandler.go new file mode 100644 index 000000000..456da5074 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2NavigationCompletedEventHandler.go @@ -0,0 +1,53 @@ +//go:build windows + +package edge + +type _ICoreWebView2NavigationCompletedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type ICoreWebView2NavigationCompletedEventHandler struct { + vtbl *_ICoreWebView2NavigationCompletedEventHandlerVtbl + impl _ICoreWebView2NavigationCompletedEventHandlerImpl +} + +func (i *ICoreWebView2NavigationCompletedEventHandler) AddRef() uintptr { + return i.AddRef() +} +func _ICoreWebView2NavigationCompletedEventHandlerIUnknownQueryInterface(this *ICoreWebView2NavigationCompletedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2NavigationCompletedEventHandlerIUnknownAddRef(this *ICoreWebView2NavigationCompletedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2NavigationCompletedEventHandlerIUnknownRelease(this *ICoreWebView2NavigationCompletedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2NavigationCompletedEventHandlerInvoke(this *ICoreWebView2NavigationCompletedEventHandler, sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr { + return this.impl.NavigationCompleted(sender, args) +} + +type _ICoreWebView2NavigationCompletedEventHandlerImpl interface { + _IUnknownImpl + NavigationCompleted(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr +} + +var _ICoreWebView2NavigationCompletedEventHandlerFn = _ICoreWebView2NavigationCompletedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2NavigationCompletedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2NavigationCompletedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2NavigationCompletedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2NavigationCompletedEventHandlerInvoke), +} + +func newICoreWebView2NavigationCompletedEventHandler(impl _ICoreWebView2NavigationCompletedEventHandlerImpl) *ICoreWebView2NavigationCompletedEventHandler { + return &ICoreWebView2NavigationCompletedEventHandler{ + vtbl: &_ICoreWebView2NavigationCompletedEventHandlerFn, + impl: impl, + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventArgs.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventArgs.go new file mode 100644 index 000000000..b6d3cda1b --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventArgs.go @@ -0,0 +1,41 @@ +//go:build windows + +package edge + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2ProcessFailedEventArgsVtbl struct { + _IUnknownVtbl + GetProcessFailedKind ComProc +} + +type ICoreWebView2ProcessFailedEventArgs struct { + vtbl *_ICoreWebView2ProcessFailedEventArgsVtbl +} + +func (i *ICoreWebView2ProcessFailedEventArgs) GetProcessFailedKind() (COREWEBVIEW2_PROCESS_FAILED_KIND, error) { + kind := COREWEBVIEW2_PROCESS_FAILED_KIND(0xffffffff) + hr, _, err := i.vtbl.GetProcessFailedKind.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&kind)), + ) + + if windows.Handle(hr) != windows.S_OK { + return 0, syscall.Errno(hr) + } + + if kind == 0xffffffff { + if err == nil { + err = fmt.Errorf("unknown error") + } + return 0, err + } + + return kind, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventHandler.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventHandler.go new file mode 100644 index 000000000..fc8c7369c --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2ProcessFailedEventHandler.go @@ -0,0 +1,53 @@ +//go:build windows + +package edge + +type _ICoreWebView2ProcessFailedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type ICoreWebView2ProcessFailedEventHandler struct { + vtbl *_ICoreWebView2ProcessFailedEventHandlerVtbl + impl _ICoreWebView2ProcessFailedEventHandlerImpl +} + +func (i *ICoreWebView2ProcessFailedEventHandler) AddRef() uintptr { + return i.AddRef() +} +func _ICoreWebView2ProcessFailedEventHandlerIUnknownQueryInterface(this *ICoreWebView2ProcessFailedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2ProcessFailedEventHandlerIUnknownAddRef(this *ICoreWebView2ProcessFailedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2ProcessFailedEventHandlerIUnknownRelease(this *ICoreWebView2ProcessFailedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2ProcessFailedEventHandlerInvoke(this *ICoreWebView2ProcessFailedEventHandler, sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) uintptr { + return this.impl.ProcessFailed(sender, args) +} + +type _ICoreWebView2ProcessFailedEventHandlerImpl interface { + _IUnknownImpl + ProcessFailed(sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) uintptr +} + +var _ICoreWebView2ProcessFailedEventHandlerFn = _ICoreWebView2ProcessFailedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2ProcessFailedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2ProcessFailedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2ProcessFailedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2ProcessFailedEventHandlerInvoke), +} + +func newICoreWebView2ProcessFailedEventHandler(impl _ICoreWebView2ProcessFailedEventHandlerImpl) *ICoreWebView2ProcessFailedEventHandler { + return &ICoreWebView2ProcessFailedEventHandler{ + vtbl: &_ICoreWebView2ProcessFailedEventHandlerFn, + impl: impl, + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Settings.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Settings.go new file mode 100644 index 000000000..a4ba613d2 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2Settings.go @@ -0,0 +1,271 @@ +//go:build windows + +package edge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2SettingsVtbl struct { + _IUnknownVtbl + GetIsScriptEnabled ComProc + PutIsScriptEnabled ComProc + GetIsWebMessageEnabled ComProc + PutIsWebMessageEnabled ComProc + GetAreDefaultScriptDialogsEnabled ComProc + PutAreDefaultScriptDialogsEnabled ComProc + GetIsStatusBarEnabled ComProc + PutIsStatusBarEnabled ComProc + GetAreDevToolsEnabled ComProc + PutAreDevToolsEnabled ComProc + GetAreDefaultContextMenusEnabled ComProc + PutAreDefaultContextMenusEnabled ComProc + GetAreHostObjectsAllowed ComProc + PutAreHostObjectsAllowed ComProc + GetIsZoomControlEnabled ComProc + PutIsZoomControlEnabled ComProc + GetIsBuiltInErrorPageEnabled ComProc + PutIsBuiltInErrorPageEnabled ComProc +} + +type ICoreWebView2Settings struct { + vtbl *_ICoreWebView2SettingsVtbl +} + +func (i *ICoreWebView2Settings) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2Settings) GetIsScriptEnabled() (bool, error) { + var err error + var isScriptEnabled bool + _, _, err = i.vtbl.GetIsScriptEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&isScriptEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return isScriptEnabled, nil +} + +func (i *ICoreWebView2Settings) PutIsScriptEnabled(isScriptEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsScriptEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isScriptEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetIsWebMessageEnabled() (bool, error) { + var err error + var isWebMessageEnabled bool + _, _, err = i.vtbl.GetIsWebMessageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&isWebMessageEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return isWebMessageEnabled, nil +} + +func (i *ICoreWebView2Settings) PutIsWebMessageEnabled(isWebMessageEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsWebMessageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isWebMessageEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetAreDefaultScriptDialogsEnabled() (bool, error) { + var err error + var areDefaultScriptDialogsEnabled bool + _, _, err = i.vtbl.GetAreDefaultScriptDialogsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&areDefaultScriptDialogsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return areDefaultScriptDialogsEnabled, nil +} + +func (i *ICoreWebView2Settings) PutAreDefaultScriptDialogsEnabled(areDefaultScriptDialogsEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutAreDefaultScriptDialogsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(areDefaultScriptDialogsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetIsStatusBarEnabled() (bool, error) { + var err error + var isStatusBarEnabled bool + _, _, err = i.vtbl.GetIsStatusBarEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&isStatusBarEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return isStatusBarEnabled, nil +} + +func (i *ICoreWebView2Settings) PutIsStatusBarEnabled(isStatusBarEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsStatusBarEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isStatusBarEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetAreDevToolsEnabled() (bool, error) { + var err error + var areDevToolsEnabled bool + _, _, err = i.vtbl.GetAreDevToolsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&areDevToolsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return areDevToolsEnabled, nil +} + +func (i *ICoreWebView2Settings) PutAreDevToolsEnabled(areDevToolsEnabled bool) error { + var err error + _, _, err = i.vtbl.PutAreDevToolsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(areDevToolsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetAreDefaultContextMenusEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetAreDefaultContextMenusEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebView2Settings) PutAreDefaultContextMenusEnabled(enabled bool) error { + var err error + _, _, err = i.vtbl.PutAreDefaultContextMenusEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetAreHostObjectsAllowed() (bool, error) { + var err error + var allowed bool + _, _, err = i.vtbl.GetAreHostObjectsAllowed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&allowed)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return allowed, nil +} + +func (i *ICoreWebView2Settings) PutAreHostObjectsAllowed(allowed bool) error { + var err error + + _, _, err = i.vtbl.PutAreHostObjectsAllowed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(allowed)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetIsZoomControlEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetIsZoomControlEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebView2Settings) PutIsZoomControlEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsZoomControlEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2Settings) GetIsBuiltInErrorPageEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetIsBuiltInErrorPageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebView2Settings) PutIsBuiltInErrorPageEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsBuiltInErrorPageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequest.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequest.go new file mode 100644 index 000000000..fe7f2cfa2 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequest.go @@ -0,0 +1,102 @@ +//go:build windows + +package edge + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2WebResourceRequestVtbl struct { + _IUnknownVtbl + GetUri ComProc + PutUri ComProc + GetMethod ComProc + PutMethod ComProc + GetContent ComProc + PutContent ComProc + GetHeaders ComProc +} + +type ICoreWebView2WebResourceRequest struct { + vtbl *_ICoreWebView2WebResourceRequestVtbl +} + +func (i *ICoreWebView2WebResourceRequest) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2WebResourceRequest) GetMethod() (string, error) { + // Create *uint16 to hold result + var _method *uint16 + res, _, err := i.vtbl.GetMethod.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&_method)), + ) + if err != windows.ERROR_SUCCESS { + return "", err + } + if windows.Handle(res) != windows.S_OK { + return "", syscall.Errno(res) + } + // Get result and cleanup + uri := windows.UTF16PtrToString(_method) + windows.CoTaskMemFree(unsafe.Pointer(_method)) + return uri, nil +} + +func (i *ICoreWebView2WebResourceRequest) GetUri() (string, error) { + var err error + // Create *uint16 to hold result + var _uri *uint16 + _, _, err = i.vtbl.GetUri.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&_uri)), + ) + if err != windows.ERROR_SUCCESS { + return "", err + } // Get result and cleanup + uri := windows.UTF16PtrToString(_uri) + windows.CoTaskMemFree(unsafe.Pointer(_uri)) + return uri, nil +} + +// GetContent returns the body of the request. Returns nil if there's no body. Make sure to call +// Release on the returned IStream after finished using it. +func (i *ICoreWebView2WebResourceRequest) GetContent() (*IStream, error) { + var stream *IStream + res, _, err := i.vtbl.GetContent.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&stream)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + if windows.Handle(res) != windows.S_OK { + return nil, syscall.Errno(res) + } + return stream, nil +} + +// GetHeaders returns the mutable HTTP request headers. Make sure to call +// Release on the returned Object after finished using it. +func (i *ICoreWebView2WebResourceRequest) GetHeaders() (*ICoreWebView2HttpRequestHeaders, error) { + var headers *ICoreWebView2HttpRequestHeaders + res, _, err := i.vtbl.GetHeaders.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&headers)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + if windows.Handle(res) != windows.S_OK { + return nil, syscall.Errno(res) + } + return headers, nil +} + +func (i *ICoreWebView2WebResourceRequest) Release() error { + return i.vtbl.CallRelease(unsafe.Pointer(i)) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventArgs.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventArgs.go new file mode 100644 index 000000000..614594e87 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventArgs.go @@ -0,0 +1,52 @@ +//go:build windows + +package edge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +type _ICoreWebView2WebResourceRequestedEventArgsVtbl struct { + _IUnknownVtbl + GetRequest ComProc + GetResponse ComProc + PutResponse ComProc + GetDeferral ComProc + GetResourceContext ComProc +} + +type ICoreWebView2WebResourceRequestedEventArgs struct { + vtbl *_ICoreWebView2WebResourceRequestedEventArgsVtbl +} + +func (i *ICoreWebView2WebResourceRequestedEventArgs) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2WebResourceRequestedEventArgs) PutResponse(response *ICoreWebView2WebResourceResponse) error { + var err error + + _, _, err = i.vtbl.PutResponse.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(response)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2WebResourceRequestedEventArgs) GetRequest() (*ICoreWebView2WebResourceRequest, error) { + var err error + var request *ICoreWebView2WebResourceRequest + _, _, err = i.vtbl.GetRequest.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&request)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + return request, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventHandler.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventHandler.go new file mode 100644 index 000000000..d0860c3be --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceRequestedEventHandler.go @@ -0,0 +1,50 @@ +//go:build windows + +package edge + +type _ICoreWebView2WebResourceRequestedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type iCoreWebView2WebResourceRequestedEventHandler struct { + vtbl *_ICoreWebView2WebResourceRequestedEventHandlerVtbl + impl _ICoreWebView2WebResourceRequestedEventHandlerImpl +} + +func _ICoreWebView2WebResourceRequestedEventHandlerIUnknownQueryInterface(this *iCoreWebView2WebResourceRequestedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2WebResourceRequestedEventHandlerIUnknownAddRef(this *iCoreWebView2WebResourceRequestedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2WebResourceRequestedEventHandlerIUnknownRelease(this *iCoreWebView2WebResourceRequestedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2WebResourceRequestedEventHandlerInvoke(this *iCoreWebView2WebResourceRequestedEventHandler, sender *ICoreWebView2, args *ICoreWebView2WebResourceRequestedEventArgs) uintptr { + return this.impl.WebResourceRequested(sender, args) +} + +type _ICoreWebView2WebResourceRequestedEventHandlerImpl interface { + _IUnknownImpl + WebResourceRequested(sender *ICoreWebView2, args *ICoreWebView2WebResourceRequestedEventArgs) uintptr +} + +var _ICoreWebView2WebResourceRequestedEventHandlerFn = _ICoreWebView2WebResourceRequestedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2WebResourceRequestedEventHandlerInvoke), +} + +func newICoreWebView2WebResourceRequestedEventHandler(impl _ICoreWebView2WebResourceRequestedEventHandlerImpl) *iCoreWebView2WebResourceRequestedEventHandler { + return &iCoreWebView2WebResourceRequestedEventHandler{ + vtbl: &_ICoreWebView2WebResourceRequestedEventHandlerFn, + impl: impl, + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceResponse.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceResponse.go new file mode 100644 index 000000000..dd02e6089 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2WebResourceResponse.go @@ -0,0 +1,28 @@ +//go:build windows + +package edge + +import "unsafe" + +type _ICoreWebView2WebResourceResponseVtbl struct { + _IUnknownVtbl + GetContent ComProc + PutContent ComProc + GetHeaders ComProc + GetStatusCode ComProc + PutStatusCode ComProc + GetReasonPhrase ComProc + PutReasonPhrase ComProc +} + +type ICoreWebView2WebResourceResponse struct { + vtbl *_ICoreWebView2WebResourceResponseVtbl +} + +func (i *ICoreWebView2WebResourceResponse) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebView2WebResourceResponse) Release() error { + return i.vtbl.CallRelease(unsafe.Pointer(i)) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2_2.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2_2.go new file mode 100644 index 000000000..85a4f71fa --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2_2.go @@ -0,0 +1,18 @@ +//go:build windows + +package edge + +type iCoreWebView2_2Vtbl struct { + iCoreWebView2Vtbl + AddWebResourceResponseReceived ComProc + RemoveWebResourceResponseReceived ComProc + NavigateWithWebResourceRequest ComProc + AddDomContentLoaded ComProc + RemoveDomContentLoaded ComProc + GetCookieManager ComProc + GetEnvironment ComProc +} + +type ICoreWebView2_2 struct { + vtbl *iCoreWebView2_2Vtbl +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2_3.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2_3.go new file mode 100644 index 000000000..58424bd6a --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebView2_3.go @@ -0,0 +1,62 @@ +//go:build windows + +package edge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +type iCoreWebView2_3Vtbl struct { + iCoreWebView2_2Vtbl + TrySuspend ComProc + Resume ComProc + GetIsSuspended ComProc + SetVirtualHostNameToFolderMapping ComProc + ClearVirtualHostNameToFolderMapping ComProc +} + +type ICoreWebView2_3 struct { + vtbl *iCoreWebView2_3Vtbl +} + +func (i *ICoreWebView2_3) SetVirtualHostNameToFolderMapping(hostName, folderPath string, accessKind COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND) error { + _hostName, err := windows.UTF16PtrFromString(hostName) + if err != nil { + return err + } + + _folderPath, err := windows.UTF16PtrFromString(folderPath) + if err != nil { + return err + } + + _, _, err = i.vtbl.SetVirtualHostNameToFolderMapping.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(_hostName)), + uintptr(unsafe.Pointer(_folderPath)), + uintptr(accessKind), + ) + if err != windows.ERROR_SUCCESS { + return err + } + + return nil +} + +func (i *ICoreWebView2) GetICoreWebView2_3() *ICoreWebView2_3 { + var result *ICoreWebView2_3 + + iidICoreWebView2_3 := NewGUID("{A0D6DF20-3B92-416D-AA0C-437A9C727857}") + _, _, _ = i.vtbl.QueryInterface.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(iidICoreWebView2_3)), + uintptr(unsafe.Pointer(&result))) + + return result +} + +func (e *Chromium) GetICoreWebView2_3() *ICoreWebView2_3 { + return e.webview.GetICoreWebView2_3() +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebViewSettings.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebViewSettings.go new file mode 100644 index 000000000..6c6b16d74 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/ICoreWebViewSettings.go @@ -0,0 +1,397 @@ +//go:build windows + +package edge + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +// ICoreWebviewSettings is the merged settings class + +type _ICoreWebViewSettingsVtbl struct { + _IUnknownVtbl + GetIsScriptEnabled ComProc + PutIsScriptEnabled ComProc + GetIsWebMessageEnabled ComProc + PutIsWebMessageEnabled ComProc + GetAreDefaultScriptDialogsEnabled ComProc + PutAreDefaultScriptDialogsEnabled ComProc + GetIsStatusBarEnabled ComProc + PutIsStatusBarEnabled ComProc + GetAreDevToolsEnabled ComProc + PutAreDevToolsEnabled ComProc + GetAreDefaultContextMenusEnabled ComProc + PutAreDefaultContextMenusEnabled ComProc + GetAreHostObjectsAllowed ComProc + PutAreHostObjectsAllowed ComProc + GetIsZoomControlEnabled ComProc + PutIsZoomControlEnabled ComProc + GetIsBuiltInErrorPageEnabled ComProc + PutIsBuiltInErrorPageEnabled ComProc + GetUserAgent ComProc // ICoreWebView2Settings2: SDK 1.0.864.35 + PutUserAgent ComProc + GetAreBrowserAcceleratorKeysEnabled ComProc // ICoreWebView2Settings3: SDK 1.0.864.35 + PutAreBrowserAcceleratorKeysEnabled ComProc + GetIsPasswordAutosaveEnabled ComProc // ICoreWebView2Settings4: SDK 1.0.902.49 + PutIsPasswordAutosaveEnabled ComProc + GetIsGeneralAutofillEnabled ComProc + PutIsGeneralAutofillEnabled ComProc + GetIsPinchZoomEnabled ComProc // ICoreWebView2Settings5: SDK 1.0.902.49 + PutIsPinchZoomEnabled ComProc + GetIsSwipeNavigationEnabled ComProc // ICoreWebView2Settings6: SDK 1.0.992.28 + PutIsSwipeNavigationEnabled ComProc +} + +type ICoreWebViewSettings struct { + vtbl *_ICoreWebViewSettingsVtbl +} + +func (i *ICoreWebViewSettings) AddRef() uintptr { + return i.AddRef() +} + +func (i *ICoreWebViewSettings) GetIsScriptEnabled() (bool, error) { + var err error + var isScriptEnabled bool + _, _, err = i.vtbl.GetIsScriptEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&isScriptEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return isScriptEnabled, nil +} + +func (i *ICoreWebViewSettings) PutIsScriptEnabled(isScriptEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsScriptEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isScriptEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetIsWebMessageEnabled() (bool, error) { + var err error + var isWebMessageEnabled bool + _, _, err = i.vtbl.GetIsWebMessageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&isWebMessageEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return isWebMessageEnabled, nil +} + +func (i *ICoreWebViewSettings) PutIsWebMessageEnabled(isWebMessageEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsWebMessageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isWebMessageEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetAreDefaultScriptDialogsEnabled() (bool, error) { + var err error + var areDefaultScriptDialogsEnabled bool + _, _, err = i.vtbl.GetAreDefaultScriptDialogsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&areDefaultScriptDialogsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return areDefaultScriptDialogsEnabled, nil +} + +func (i *ICoreWebViewSettings) PutAreDefaultScriptDialogsEnabled(areDefaultScriptDialogsEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutAreDefaultScriptDialogsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(areDefaultScriptDialogsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetIsStatusBarEnabled() (bool, error) { + var err error + var isStatusBarEnabled bool + _, _, err = i.vtbl.GetIsStatusBarEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&isStatusBarEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return isStatusBarEnabled, nil +} + +func (i *ICoreWebViewSettings) PutIsStatusBarEnabled(isStatusBarEnabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsStatusBarEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(isStatusBarEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetAreDevToolsEnabled() (bool, error) { + var err error + var areDevToolsEnabled bool + _, _, err = i.vtbl.GetAreDevToolsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&areDevToolsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return areDevToolsEnabled, nil +} + +func (i *ICoreWebViewSettings) PutAreDevToolsEnabled(areDevToolsEnabled bool) error { + var err error + _, _, err = i.vtbl.PutAreDevToolsEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(areDevToolsEnabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetAreDefaultContextMenusEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetAreDefaultContextMenusEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebViewSettings) PutAreDefaultContextMenusEnabled(enabled bool) error { + var err error + _, _, err = i.vtbl.PutAreDefaultContextMenusEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetAreHostObjectsAllowed() (bool, error) { + var err error + var allowed bool + _, _, err = i.vtbl.GetAreHostObjectsAllowed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&allowed)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return allowed, nil +} + +func (i *ICoreWebViewSettings) PutAreHostObjectsAllowed(allowed bool) error { + var err error + + _, _, err = i.vtbl.PutAreHostObjectsAllowed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(allowed)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetIsZoomControlEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetIsZoomControlEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebViewSettings) PutIsZoomControlEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsZoomControlEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetIsBuiltInErrorPageEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetIsBuiltInErrorPageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebViewSettings) PutIsBuiltInErrorPageEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsBuiltInErrorPageEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetUserAgent() (string, error) { + var err error + // Create *uint16 to hold result + var _userAgent *uint16 + _, _, err = i.vtbl.GetUserAgent.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(_userAgent)), + ) + if err != windows.ERROR_SUCCESS { + return "", err + } // Get result and cleanup + userAgent := windows.UTF16PtrToString(_userAgent) + windows.CoTaskMemFree(unsafe.Pointer(_userAgent)) + return userAgent, nil +} + +func (i *ICoreWebViewSettings) PutUserAgent(userAgent string) error { + var err error + // Convert string 'userAgent' to *uint16 + _userAgent, err := windows.UTF16PtrFromString(userAgent) + if err != nil { + return err + } + + _, _, err = i.vtbl.PutUserAgent.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(_userAgent)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetAreBrowserAcceleratorKeysEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetAreBrowserAcceleratorKeysEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebViewSettings) PutAreBrowserAcceleratorKeysEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutAreBrowserAcceleratorKeysEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetIsPinchZoomEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetIsPinchZoomEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebViewSettings) PutIsPinchZoomEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsPinchZoomEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebViewSettings) GetIsSwipeNavigationEnabled() (bool, error) { + var err error + var enabled bool + _, _, err = i.vtbl.GetIsSwipeNavigationEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&enabled)), + ) + if err != windows.ERROR_SUCCESS { + return false, err + } + return enabled, nil +} + +func (i *ICoreWebViewSettings) PutIsSwipeNavigationEnabled(enabled bool) error { + var err error + + _, _, err = i.vtbl.PutIsSwipeNavigationEnabled.Call( + uintptr(unsafe.Pointer(i)), + uintptr(boolToInt(enabled)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/IStream.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/IStream.go new file mode 100644 index 000000000..9e29ca4f0 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/IStream.go @@ -0,0 +1,54 @@ +//go:build windows + +package edge + +import ( + "io" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type _IStreamVtbl struct { + _IUnknownVtbl + Read ComProc + Write ComProc +} + +type IStream struct { + vtbl *_IStreamVtbl +} + +func (i *IStream) Release() error { + return i.vtbl.CallRelease(unsafe.Pointer(i)) +} + +func (i *IStream) Read(p []byte) (int, error) { + bufLen := len(p) + if bufLen == 0 { + return 0, nil + } + + var n int + res, _, err := i.vtbl.Read.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&p[0])), + uintptr(bufLen), + uintptr(unsafe.Pointer(&n)), + ) + if err != windows.ERROR_SUCCESS { + return 0, err + } + + switch windows.Handle(res) { + case windows.S_OK: + // The buffer has been completely filled + return n, nil + case windows.S_FALSE: + // The buffer has been filled with less than len data and the stream is EOF + return n, io.EOF + default: + return 0, syscall.Errno(res) + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go new file mode 100644 index 000000000..376891bb1 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium.go @@ -0,0 +1,419 @@ +//go:build windows +// +build windows + +package edge + +import ( + "errors" + "log" + "os" + "path/filepath" + "strings" + "sync/atomic" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/internal/w32" + "golang.org/x/sys/windows" +) + +type Rect = w32.Rect + +type Chromium struct { + hwnd uintptr + controller *ICoreWebView2Controller + webview *ICoreWebView2 + inited uintptr + envCompleted *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler + controllerCompleted *iCoreWebView2CreateCoreWebView2ControllerCompletedHandler + webMessageReceived *iCoreWebView2WebMessageReceivedEventHandler + permissionRequested *iCoreWebView2PermissionRequestedEventHandler + webResourceRequested *iCoreWebView2WebResourceRequestedEventHandler + acceleratorKeyPressed *ICoreWebView2AcceleratorKeyPressedEventHandler + navigationCompleted *ICoreWebView2NavigationCompletedEventHandler + processFailed *ICoreWebView2ProcessFailedEventHandler + + environment *ICoreWebView2Environment + + padding Rect + + // Settings + Debug bool + DataPath string + BrowserPath string + AdditionalBrowserArgs []string + + // permissions + permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState + globalPermission *CoreWebView2PermissionState + + // Callbacks + MessageCallback func(string) + WebResourceRequestedCallback func(request *ICoreWebView2WebResourceRequest, args *ICoreWebView2WebResourceRequestedEventArgs) + NavigationCompletedCallback func(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) + ProcessFailedCallback func(sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) + AcceleratorKeyCallback func(uint) bool +} + +func NewChromium() *Chromium { + e := &Chromium{} + /* + All these handlers are passed to native code through syscalls with 'uintptr(unsafe.Pointer(handler))' and we know + that a pointer to those will be kept in the native code. Furthermore these handlers als contain pointer to other Go + structs like the vtable. + This violates the unsafe.Pointer rule '(4) Conversion of a Pointer to a uintptr when calling syscall.Syscall.' because + theres no guarantee that Go doesn't move these objects. + AFAIK currently the Go runtime doesn't move HEAP objects, so we should be safe with these handlers. But they don't + guarantee it, because in the future Go might use a compacting GC. + There's a proposal to add a runtime.Pin function, to prevent moving pinned objects, which would allow to easily fix + this issue by just pinning the handlers. The https://go-review.googlesource.com/c/go/+/367296/ should land in Go 1.19. + */ + e.envCompleted = newICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler(e) + e.controllerCompleted = newICoreWebView2CreateCoreWebView2ControllerCompletedHandler(e) + e.webMessageReceived = newICoreWebView2WebMessageReceivedEventHandler(e) + e.permissionRequested = newICoreWebView2PermissionRequestedEventHandler(e) + e.webResourceRequested = newICoreWebView2WebResourceRequestedEventHandler(e) + e.acceleratorKeyPressed = newICoreWebView2AcceleratorKeyPressedEventHandler(e) + e.navigationCompleted = newICoreWebView2NavigationCompletedEventHandler(e) + e.processFailed = newICoreWebView2ProcessFailedEventHandler(e) + e.permissions = make(map[CoreWebView2PermissionKind]CoreWebView2PermissionState) + + return e +} + +func (e *Chromium) Embed(hwnd uintptr) bool { + e.hwnd = hwnd + + dataPath := e.DataPath + if dataPath == "" { + currentExePath := make([]uint16, windows.MAX_PATH) + _, err := windows.GetModuleFileName(windows.Handle(0), ¤tExePath[0], windows.MAX_PATH) + if err != nil { + // What to do here? + return false + } + currentExeName := filepath.Base(windows.UTF16ToString(currentExePath)) + dataPath = filepath.Join(os.Getenv("AppData"), currentExeName) + } + + if e.BrowserPath != "" { + if _, err := os.Stat(e.BrowserPath); errors.Is(err, os.ErrNotExist) { + log.Printf("Browser path %s does not exist", e.BrowserPath) + return false + } + } + + browserArgs := strings.Join(e.AdditionalBrowserArgs, " ") + if err := createCoreWebView2EnvironmentWithOptions(e.BrowserPath, dataPath, e.envCompleted, browserArgs); err != nil { + log.Printf("Error calling Webview2Loader: %v", err) + return false + } + + var msg w32.Msg + for { + if atomic.LoadUintptr(&e.inited) != 0 { + break + } + r, _, _ := w32.User32GetMessageW.Call( + uintptr(unsafe.Pointer(&msg)), + 0, + 0, + 0, + ) + if r == 0 { + break + } + w32.User32TranslateMessage.Call(uintptr(unsafe.Pointer(&msg))) + w32.User32DispatchMessageW.Call(uintptr(unsafe.Pointer(&msg))) + } + e.Init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}") + return true +} + +func (e *Chromium) SetPadding(padding Rect) { + if e.padding.Top == padding.Top && e.padding.Bottom == padding.Bottom && + e.padding.Left == padding.Left && e.padding.Right == padding.Right { + + return + } + + e.padding = padding + e.Resize() +} + +func (e *Chromium) Resize() { + if e.hwnd == 0 { + return + } + + var bounds w32.Rect + w32.User32GetClientRect.Call(e.hwnd, uintptr(unsafe.Pointer(&bounds))) + + bounds.Top += e.padding.Top + bounds.Bottom -= e.padding.Bottom + bounds.Left += e.padding.Left + bounds.Right -= e.padding.Right + + e.SetSize(bounds) +} + +func (e *Chromium) Navigate(url string) { + e.webview.vtbl.Navigate.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(url))), + ) +} + +func (e *Chromium) Init(script string) { + e.webview.vtbl.AddScriptToExecuteOnDocumentCreated.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(script))), + 0, + ) +} + +func (e *Chromium) Eval(script string) { + + _script, err := windows.UTF16PtrFromString(script) + if err != nil { + log.Fatal(err) + } + + e.webview.vtbl.ExecuteScript.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(_script)), + 0, + ) +} + +func (e *Chromium) Show() error { + return e.controller.PutIsVisible(true) +} + +func (e *Chromium) Hide() error { + return e.controller.PutIsVisible(false) +} + +func (e *Chromium) QueryInterface(_, _ uintptr) uintptr { + return 0 +} + +func (e *Chromium) AddRef() uintptr { + return 1 +} + +func (e *Chromium) Release() uintptr { + return 1 +} + +func (e *Chromium) EnvironmentCompleted(res uintptr, env *ICoreWebView2Environment) uintptr { + if int32(res) < 0 { + log.Fatalf("Creating environment failed with %08x: %s", res, syscall.Errno(res)) + } + env.vtbl.AddRef.Call(uintptr(unsafe.Pointer(env))) + e.environment = env + + env.vtbl.CreateCoreWebView2Controller.Call( + uintptr(unsafe.Pointer(env)), + e.hwnd, + uintptr(unsafe.Pointer(e.controllerCompleted)), + ) + return 0 +} + +func (e *Chromium) CreateCoreWebView2ControllerCompleted(res uintptr, controller *ICoreWebView2Controller) uintptr { + if int32(res) < 0 { + log.Fatalf("Creating controller failed with %08x: %s", res, syscall.Errno(res)) + } + controller.vtbl.AddRef.Call(uintptr(unsafe.Pointer(controller))) + e.controller = controller + + var token _EventRegistrationToken + controller.vtbl.GetCoreWebView2.Call( + uintptr(unsafe.Pointer(controller)), + uintptr(unsafe.Pointer(&e.webview)), + ) + e.webview.vtbl.AddRef.Call( + uintptr(unsafe.Pointer(e.webview)), + ) + e.webview.vtbl.AddWebMessageReceived.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.webMessageReceived)), + uintptr(unsafe.Pointer(&token)), + ) + e.webview.vtbl.AddPermissionRequested.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.permissionRequested)), + uintptr(unsafe.Pointer(&token)), + ) + e.webview.vtbl.AddWebResourceRequested.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.webResourceRequested)), + uintptr(unsafe.Pointer(&token)), + ) + e.webview.vtbl.AddNavigationCompleted.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.navigationCompleted)), + uintptr(unsafe.Pointer(&token)), + ) + e.webview.vtbl.AddProcessFailed.Call( + uintptr(unsafe.Pointer(e.webview)), + uintptr(unsafe.Pointer(e.processFailed)), + uintptr(unsafe.Pointer(&token)), + ) + + e.controller.AddAcceleratorKeyPressed(e.acceleratorKeyPressed, &token) + + atomic.StoreUintptr(&e.inited, 1) + + return 0 +} + +func (e *Chromium) MessageReceived(sender *ICoreWebView2, args *iCoreWebView2WebMessageReceivedEventArgs) uintptr { + var message *uint16 + args.vtbl.TryGetWebMessageAsString.Call( + uintptr(unsafe.Pointer(args)), + uintptr(unsafe.Pointer(&message)), + ) + if e.MessageCallback != nil { + e.MessageCallback(w32.Utf16PtrToString(message)) + } + sender.vtbl.PostWebMessageAsString.Call( + uintptr(unsafe.Pointer(sender)), + uintptr(unsafe.Pointer(message)), + ) + windows.CoTaskMemFree(unsafe.Pointer(message)) + return 0 +} + +func (e *Chromium) SetPermission(kind CoreWebView2PermissionKind, state CoreWebView2PermissionState) { + e.permissions[kind] = state +} + +func (e *Chromium) SetGlobalPermission(state CoreWebView2PermissionState) { + e.globalPermission = &state +} + +func (e *Chromium) PermissionRequested(_ *ICoreWebView2, args *iCoreWebView2PermissionRequestedEventArgs) uintptr { + var kind CoreWebView2PermissionKind + args.vtbl.GetPermissionKind.Call( + uintptr(unsafe.Pointer(args)), + uintptr(kind), + ) + var result CoreWebView2PermissionState + if e.globalPermission != nil { + result = *e.globalPermission + } else { + var ok bool + result, ok = e.permissions[kind] + if !ok { + result = CoreWebView2PermissionStateDefault + } + } + args.vtbl.PutState.Call( + uintptr(unsafe.Pointer(args)), + uintptr(result), + ) + return 0 +} + +func (e *Chromium) WebResourceRequested(sender *ICoreWebView2, args *ICoreWebView2WebResourceRequestedEventArgs) uintptr { + req, err := args.GetRequest() + if err != nil { + log.Fatal(err) + } + defer req.Release() + + if e.WebResourceRequestedCallback != nil { + e.WebResourceRequestedCallback(req, args) + } + return 0 +} + +func (e *Chromium) AddWebResourceRequestedFilter(filter string, ctx COREWEBVIEW2_WEB_RESOURCE_CONTEXT) { + err := e.webview.AddWebResourceRequestedFilter(filter, ctx) + if err != nil { + log.Fatal(err) + } +} + +func (e *Chromium) Environment() *ICoreWebView2Environment { + return e.environment +} + +// AcceleratorKeyPressed is called when an accelerator key is pressed. +// If the AcceleratorKeyCallback method has been set, it will defer handling of the keypress +// to the callback. That callback returns a bool indicating if the event was handled. +func (e *Chromium) AcceleratorKeyPressed(sender *ICoreWebView2Controller, args *ICoreWebView2AcceleratorKeyPressedEventArgs) uintptr { + if e.AcceleratorKeyCallback == nil { + return 0 + } + eventKind, _ := args.GetKeyEventKind() + if eventKind == COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN || + eventKind == COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN { + virtualKey, _ := args.GetVirtualKey() + status, _ := args.GetPhysicalKeyStatus() + if !status.WasKeyDown { + args.PutHandled(e.AcceleratorKeyCallback(virtualKey)) + return 0 + } + } + args.PutHandled(false) + return 0 +} + +func (e *Chromium) GetSettings() (*ICoreWebViewSettings, error) { + return e.webview.GetSettings() +} + +func (e *Chromium) GetController() *ICoreWebView2Controller { + return e.controller +} + +func boolToInt(input bool) int { + if input { + return 1 + } + return 0 +} + +func (e *Chromium) NavigationCompleted(sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr { + if e.NavigationCompletedCallback != nil { + e.NavigationCompletedCallback(sender, args) + } + return 0 +} + +func (e *Chromium) ProcessFailed(sender *ICoreWebView2, args *ICoreWebView2ProcessFailedEventArgs) uintptr { + if e.ProcessFailedCallback != nil { + e.ProcessFailedCallback(sender, args) + } + return 0 +} + +func (e *Chromium) NotifyParentWindowPositionChanged() error { + //It looks like the wndproc function is called before the controller initialization is complete. + //Because of this the controller is nil + if e.controller == nil { + return nil + } + return e.controller.NotifyParentWindowPositionChanged() +} + +func (e *Chromium) Focus() { + err := e.controller.MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC) + if err != nil { + log.Fatal(err) + } +} + +func (e *Chromium) PutZoomFactor(zoomFactor float64) { + err := e.controller.PutZoomFactor(zoomFactor) + if err != nil { + log.Fatal(err) + } +} + +func (e *Chromium) OpenDevToolsWindow() { + e.webview.OpenDevToolsWindow() +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_386.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_386.go new file mode 100644 index 000000000..00f6f42fb --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_386.go @@ -0,0 +1,23 @@ +//go:build windows +// +build windows + +package edge + +import ( + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/internal/w32" + "unsafe" +) + +func (e *Chromium) SetSize(bounds w32.Rect) { + if e.controller == nil { + return + } + + e.controller.vtbl.PutBounds.Call( + uintptr(unsafe.Pointer(e.controller)), + uintptr(bounds.Left), + uintptr(bounds.Top), + uintptr(bounds.Right), + uintptr(bounds.Bottom), + ) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_amd64.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_amd64.go new file mode 100644 index 000000000..858b93f17 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_amd64.go @@ -0,0 +1,20 @@ +//go:build windows +// +build windows + +package edge + +import ( + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/internal/w32" + "unsafe" +) + +func (e *Chromium) SetSize(bounds w32.Rect) { + if e.controller == nil { + return + } + + e.controller.vtbl.PutBounds.Call( + uintptr(unsafe.Pointer(e.controller)), + uintptr(unsafe.Pointer(&bounds)), + ) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_arm64.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_arm64.go new file mode 100644 index 000000000..b237792e4 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/chromium_arm64.go @@ -0,0 +1,23 @@ +//go:build windows +// +build windows + +package edge + +import ( + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/internal/w32" +) + +func (e *Chromium) SetSize(bounds w32.Rect) { + if e.controller == nil { + return + } + + words := (*[2]uintptr)(unsafe.Pointer(&bounds)) + e.controller.vtbl.PutBounds.Call( + uintptr(unsafe.Pointer(e.controller)), + words[0], + words[1], + ) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go new file mode 100644 index 000000000..6f1afbd87 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/corewebview2.go @@ -0,0 +1,503 @@ +//go:build windows +// +build windows + +package edge + +import ( + "fmt" + "log" + "runtime" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/internal/w32" + + "golang.org/x/sys/windows" +) + +func init() { + runtime.LockOSThread() + + r, _, _ := w32.Ole32CoInitializeEx.Call(0, 2) + if int(r) < 0 { + log.Printf("Warning: CoInitializeEx call failed: E=%08x", r) + } +} + +type _EventRegistrationToken struct { + value int64 +} + +type CoreWebView2PermissionKind uint32 + +const ( + CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota + CoreWebView2PermissionKindMicrophone + CoreWebView2PermissionKindCamera + CoreWebView2PermissionKindGeolocation + CoreWebView2PermissionKindNotifications + CoreWebView2PermissionKindOtherSensors + CoreWebView2PermissionKindClipboardRead +) + +type CoreWebView2PermissionState uint32 + +const ( + CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota + CoreWebView2PermissionStateAllow + CoreWebView2PermissionStateDeny +) + +// 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)) +} + +// Call calls a COM procedure. +// +//go:uintptrescapes +func (p ComProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { + // The magic uintptrescapes comment is needed to prevent moving uintptr(unsafe.Pointer(p)) so calls to .Call() also + // satisfy the unsafe.Pointer rule "(4) Conversion of a Pointer to a uintptr when calling syscall.Syscall." + // Otherwise it might be that pointers get moved, especially pointer onto the Go stack which might grow dynamically. + // See https://pkg.go.dev/unsafe#Pointer and https://github.com/golang/go/issues/34474 + switch len(a) { + case 0: + return syscall.Syscall(uintptr(p), 0, 0, 0, 0) + case 1: + return syscall.Syscall(uintptr(p), 1, a[0], 0, 0) + case 2: + return syscall.Syscall(uintptr(p), 2, a[0], a[1], 0) + case 3: + return syscall.Syscall(uintptr(p), 3, a[0], a[1], a[2]) + case 4: + return syscall.Syscall6(uintptr(p), 4, a[0], a[1], a[2], a[3], 0, 0) + case 5: + return syscall.Syscall6(uintptr(p), 5, a[0], a[1], a[2], a[3], a[4], 0) + case 6: + return syscall.Syscall6(uintptr(p), 6, a[0], a[1], a[2], a[3], a[4], a[5]) + case 7: + return syscall.Syscall9(uintptr(p), 7, a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0) + case 8: + return syscall.Syscall9(uintptr(p), 8, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0) + case 9: + return syscall.Syscall9(uintptr(p), 9, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]) + case 10: + return syscall.Syscall12(uintptr(p), 10, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0) + case 11: + return syscall.Syscall12(uintptr(p), 11, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0) + case 12: + return syscall.Syscall12(uintptr(p), 12, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11]) + case 13: + return syscall.Syscall15(uintptr(p), 13, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0) + case 14: + return syscall.Syscall15(uintptr(p), 14, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0) + case 15: + return syscall.Syscall15(uintptr(p), 15, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14]) + default: + panic("too many arguments") + } +} + +// IUnknown + +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 +} + +// ICoreWebView2 + +type iCoreWebView2Vtbl struct { + _IUnknownVtbl + GetSettings ComProc + GetSource ComProc + Navigate ComProc + NavigateToString ComProc + AddNavigationStarting ComProc + RemoveNavigationStarting ComProc + AddContentLoading ComProc + RemoveContentLoading ComProc + AddSourceChanged ComProc + RemoveSourceChanged ComProc + AddHistoryChanged ComProc + RemoveHistoryChanged ComProc + AddNavigationCompleted ComProc + RemoveNavigationCompleted ComProc + AddFrameNavigationStarting ComProc + RemoveFrameNavigationStarting ComProc + AddFrameNavigationCompleted ComProc + RemoveFrameNavigationCompleted ComProc + AddScriptDialogOpening ComProc + RemoveScriptDialogOpening ComProc + AddPermissionRequested ComProc + RemovePermissionRequested ComProc + AddProcessFailed ComProc + RemoveProcessFailed ComProc + AddScriptToExecuteOnDocumentCreated ComProc + RemoveScriptToExecuteOnDocumentCreated ComProc + ExecuteScript ComProc + CapturePreview ComProc + Reload ComProc + PostWebMessageAsJSON ComProc + PostWebMessageAsString ComProc + AddWebMessageReceived ComProc + RemoveWebMessageReceived ComProc + CallDevToolsProtocolMethod ComProc + GetBrowserProcessID ComProc + GetCanGoBack ComProc + GetCanGoForward ComProc + GoBack ComProc + GoForward ComProc + GetDevToolsProtocolEventReceiver ComProc + Stop ComProc + AddNewWindowRequested ComProc + RemoveNewWindowRequested ComProc + AddDocumentTitleChanged ComProc + RemoveDocumentTitleChanged ComProc + GetDocumentTitle ComProc + AddHostObjectToScript ComProc + RemoveHostObjectFromScript ComProc + OpenDevToolsWindow ComProc + AddContainsFullScreenElementChanged ComProc + RemoveContainsFullScreenElementChanged ComProc + GetContainsFullScreenElement ComProc + AddWebResourceRequested ComProc + RemoveWebResourceRequested ComProc + AddWebResourceRequestedFilter ComProc + RemoveWebResourceRequestedFilter ComProc + AddWindowCloseRequested ComProc + RemoveWindowCloseRequested ComProc +} + +type ICoreWebView2 struct { + vtbl *iCoreWebView2Vtbl +} + +func (i *ICoreWebView2) GetSettings() (*ICoreWebViewSettings, error) { + var err error + var settings *ICoreWebViewSettings + _, _, err = i.vtbl.GetSettings.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(&settings)), + ) + if err != windows.ERROR_SUCCESS { + return nil, err + } + return settings, nil +} + +// ICoreWebView2Environment + +type iCoreWebView2EnvironmentVtbl struct { + _IUnknownVtbl + CreateCoreWebView2Controller ComProc + CreateWebResourceResponse ComProc + GetBrowserVersionString ComProc + AddNewBrowserVersionAvailable ComProc + RemoveNewBrowserVersionAvailable ComProc +} + +type ICoreWebView2Environment struct { + vtbl *iCoreWebView2EnvironmentVtbl +} + +// CreateWebResourceResponse creates a new ICoreWebView2WebResourceResponse, it must be released after finishing using it. +func (e *ICoreWebView2Environment) CreateWebResourceResponse(content []byte, statusCode int, reasonPhrase string, headers string) (*ICoreWebView2WebResourceResponse, error) { + var err error + var stream uintptr + + if len(content) > 0 { + // Create stream for response + stream, err = w32.SHCreateMemStream(content) + if err != nil { + return nil, err + } + + // Release the IStream after we are finished, CreateWebResourceResponse Call will increase the reference + // count on IStream and therefore it won't be freed until the reference count of the response is 0. + defer (*IStream)(unsafe.Pointer(stream)).Release() + } + + // Convert string 'uri' to *uint16 + _reason, err := windows.UTF16PtrFromString(reasonPhrase) + if err != nil { + return nil, err + } + // Convert string 'uri' to *uint16 + _headers, err := windows.UTF16PtrFromString(headers) + if err != nil { + return nil, err + } + var response *ICoreWebView2WebResourceResponse + hr, _, err := e.vtbl.CreateWebResourceResponse.Call( + uintptr(unsafe.Pointer(e)), + stream, + uintptr(statusCode), + uintptr(unsafe.Pointer(_reason)), + uintptr(unsafe.Pointer(_headers)), + uintptr(unsafe.Pointer(&response)), + ) + if windows.Handle(hr) != windows.S_OK { + return nil, syscall.Errno(hr) + } + + if response == nil { + if err == nil { + err = fmt.Errorf("unknown error") + } + return nil, err + } + return response, nil + +} + +// ICoreWebView2WebMessageReceivedEventArgs + +type iCoreWebView2WebMessageReceivedEventArgsVtbl struct { + _IUnknownVtbl + GetSource ComProc + GetWebMessageAsJSON ComProc + TryGetWebMessageAsString ComProc +} + +type iCoreWebView2WebMessageReceivedEventArgs struct { + vtbl *iCoreWebView2WebMessageReceivedEventArgsVtbl +} + +// ICoreWebView2PermissionRequestedEventArgs + +type iCoreWebView2PermissionRequestedEventArgsVtbl struct { + _IUnknownVtbl + GetURI ComProc + GetPermissionKind ComProc + GetIsUserInitiated ComProc + GetState ComProc + PutState ComProc + GetDeferral ComProc +} + +type iCoreWebView2PermissionRequestedEventArgs struct { + vtbl *iCoreWebView2PermissionRequestedEventArgsVtbl +} + +// ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler + +type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerImpl interface { + _IUnknownImpl + EnvironmentCompleted(res uintptr, env *ICoreWebView2Environment) uintptr +} + +type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler struct { + vtbl *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl + impl iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerImpl +} + +func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownQueryInterface(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownAddRef(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownRelease(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerInvoke(this *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, res uintptr, env *ICoreWebView2Environment) uintptr { + return this.impl.EnvironmentCompleted(res, env) +} + +var iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerFn = iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerInvoke), +} + +func newICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler(impl iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerImpl) *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler { + return &iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler{ + vtbl: &iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerFn, + impl: impl, + } +} + +// ICoreWebView2WebMessageReceivedEventHandler + +type iCoreWebView2WebMessageReceivedEventHandlerImpl interface { + _IUnknownImpl + MessageReceived(sender *ICoreWebView2, args *iCoreWebView2WebMessageReceivedEventArgs) uintptr +} + +type iCoreWebView2WebMessageReceivedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type iCoreWebView2WebMessageReceivedEventHandler struct { + vtbl *iCoreWebView2WebMessageReceivedEventHandlerVtbl + impl iCoreWebView2WebMessageReceivedEventHandlerImpl +} + +func _ICoreWebView2WebMessageReceivedEventHandlerIUnknownQueryInterface(this *iCoreWebView2WebMessageReceivedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2WebMessageReceivedEventHandlerIUnknownAddRef(this *iCoreWebView2WebMessageReceivedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2WebMessageReceivedEventHandlerIUnknownRelease(this *iCoreWebView2WebMessageReceivedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2WebMessageReceivedEventHandlerInvoke(this *iCoreWebView2WebMessageReceivedEventHandler, sender *ICoreWebView2, args *iCoreWebView2WebMessageReceivedEventArgs) uintptr { + return this.impl.MessageReceived(sender, args) +} + +var iCoreWebView2WebMessageReceivedEventHandlerFn = iCoreWebView2WebMessageReceivedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2WebMessageReceivedEventHandlerInvoke), +} + +func newICoreWebView2WebMessageReceivedEventHandler(impl iCoreWebView2WebMessageReceivedEventHandlerImpl) *iCoreWebView2WebMessageReceivedEventHandler { + return &iCoreWebView2WebMessageReceivedEventHandler{ + vtbl: &iCoreWebView2WebMessageReceivedEventHandlerFn, + impl: impl, + } +} + +// ICoreWebView2PermissionRequestedEventHandler + +type iCoreWebView2PermissionRequestedEventHandlerImpl interface { + _IUnknownImpl + PermissionRequested(sender *ICoreWebView2, args *iCoreWebView2PermissionRequestedEventArgs) uintptr +} + +type iCoreWebView2PermissionRequestedEventHandlerVtbl struct { + _IUnknownVtbl + Invoke ComProc +} + +type iCoreWebView2PermissionRequestedEventHandler struct { + vtbl *iCoreWebView2PermissionRequestedEventHandlerVtbl + impl iCoreWebView2PermissionRequestedEventHandlerImpl +} + +func _ICoreWebView2PermissionRequestedEventHandlerIUnknownQueryInterface(this *iCoreWebView2PermissionRequestedEventHandler, refiid, object uintptr) uintptr { + return this.impl.QueryInterface(refiid, object) +} + +func _ICoreWebView2PermissionRequestedEventHandlerIUnknownAddRef(this *iCoreWebView2PermissionRequestedEventHandler) uintptr { + return this.impl.AddRef() +} + +func _ICoreWebView2PermissionRequestedEventHandlerIUnknownRelease(this *iCoreWebView2PermissionRequestedEventHandler) uintptr { + return this.impl.Release() +} + +func _ICoreWebView2PermissionRequestedEventHandlerInvoke(this *iCoreWebView2PermissionRequestedEventHandler, sender *ICoreWebView2, args *iCoreWebView2PermissionRequestedEventArgs) uintptr { + return this.impl.PermissionRequested(sender, args) +} + +var iCoreWebView2PermissionRequestedEventHandlerFn = iCoreWebView2PermissionRequestedEventHandlerVtbl{ + _IUnknownVtbl{ + NewComProc(_ICoreWebView2PermissionRequestedEventHandlerIUnknownQueryInterface), + NewComProc(_ICoreWebView2PermissionRequestedEventHandlerIUnknownAddRef), + NewComProc(_ICoreWebView2PermissionRequestedEventHandlerIUnknownRelease), + }, + NewComProc(_ICoreWebView2PermissionRequestedEventHandlerInvoke), +} + +func newICoreWebView2PermissionRequestedEventHandler(impl iCoreWebView2PermissionRequestedEventHandlerImpl) *iCoreWebView2PermissionRequestedEventHandler { + return &iCoreWebView2PermissionRequestedEventHandler{ + vtbl: &iCoreWebView2PermissionRequestedEventHandlerFn, + impl: impl, + } +} + +func (i *ICoreWebView2) AddWebResourceRequestedFilter(uri string, resourceContext COREWEBVIEW2_WEB_RESOURCE_CONTEXT) error { + var err error + // Convert string 'uri' to *uint16 + _uri, err := windows.UTF16PtrFromString(uri) + if err != nil { + return err + } + _, _, err = i.vtbl.AddWebResourceRequestedFilter.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(_uri)), + uintptr(resourceContext), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} +func (i *ICoreWebView2) AddNavigationCompleted(eventHandler *ICoreWebView2NavigationCompletedEventHandler, token *_EventRegistrationToken) error { + var err error + _, _, err = i.vtbl.AddNavigationCompleted.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(eventHandler)), + uintptr(unsafe.Pointer(&token)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2) AddProcessFailed(eventHandler *ICoreWebView2ProcessFailedEventHandler, token *_EventRegistrationToken) error { + var err error + _, _, err = i.vtbl.AddProcessFailed.Call( + uintptr(unsafe.Pointer(i)), + uintptr(unsafe.Pointer(eventHandler)), + uintptr(unsafe.Pointer(&token)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} + +func (i *ICoreWebView2) OpenDevToolsWindow() error { + var err error + _, _, err = i.vtbl.OpenDevToolsWindow.Call( + uintptr(unsafe.Pointer(i)), + ) + if err != windows.ERROR_SUCCESS { + return err + } + return nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/create_env_go.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/create_env_go.go new file mode 100644 index 000000000..a3b7374ca --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/create_env_go.go @@ -0,0 +1,29 @@ +//go:build windows && !native_webview2loader + +package edge + +import ( + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/webviewloader" +) + +func createCoreWebView2EnvironmentWithOptions(browserExecutableFolder, userDataFolder string, environmentCompletedHandle *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, additionalBrowserArgs string) error { + e := &environmentCreatedHandler{environmentCompletedHandle} + return webviewloader.CreateCoreWebView2EnvironmentWithOptions( + e, + webviewloader.WithBrowserExecutableFolder(browserExecutableFolder), + webviewloader.WithUserDataFolder(userDataFolder), + webviewloader.WithAdditionalBrowserArguments(additionalBrowserArgs), + ) +} + +type environmentCreatedHandler struct { + originalHandler *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler +} + +func (r *environmentCreatedHandler) EnvironmentCompleted(errorCode webviewloader.HRESULT, createdEnvironment *webviewloader.ICoreWebView2Environment) webviewloader.HRESULT { + env := (*ICoreWebView2Environment)(unsafe.Pointer(createdEnvironment)) + res := r.originalHandler.impl.EnvironmentCompleted(uintptr(errorCode), env) + return webviewloader.HRESULT(res) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/create_env_native.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/create_env_native.go new file mode 100644 index 000000000..9b4f02bfd --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/create_env_native.go @@ -0,0 +1,41 @@ +//go:build windows && native_webview2loader + +package edge + +import ( + "fmt" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/webviewloader" + + "golang.org/x/sys/windows" +) + +func createCoreWebView2EnvironmentWithOptions(browserExecutableFolder, userDataFolder string, environmentCompletedHandle *iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, additionalBrowserArgs string) error { + browserPathPtr, err := windows.UTF16PtrFromString(browserExecutableFolder) + if err != nil { + return fmt.Errorf("Error calling UTF16PtrFromString for %s: %v", browserExecutableFolder, err) + } + + userPathPtr, err := windows.UTF16PtrFromString(userDataFolder) + if err != nil { + return fmt.Errorf("Error calling UTF16PtrFromString for %s: %v", userDataFolder, err) + } + + hr, err := webviewloader.CreateCoreWebView2EnvironmentWithOptions( + browserPathPtr, + userPathPtr, + uintptr(unsafe.Pointer(environmentCompletedHandle)), + additionalBrowserArgs, + ) + + if hr != 0 { + if err == nil || err == windows.ERROR_SUCCESS { + err = syscall.Errno(hr) + } + return err + } + + return nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/guid.go b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/guid.go new file mode 100644 index 000000000..007e60586 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge/guid.go @@ -0,0 +1,225 @@ +//go:build windows + +package edge + +// 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/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/LICENSE b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/LICENSE new file mode 100644 index 000000000..af1894c87 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/LICENSE @@ -0,0 +1,16 @@ +ISC License (ISC) + +Copyright (c) 2020 John Chadwick +Copyright (c) 2022 Wails Project Developers + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/README.md b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/README.md new file mode 100644 index 000000000..dd265a601 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/README.md @@ -0,0 +1,22 @@ +# Webviewloader + +Webviewloader is a port of +[OpenWebView2Loader](https://github.com/jchv/OpenWebView2Loader) to Go. + +It is intended to be feature-complete with the original WebView2Loader +distributed with the WebView2 NuGet package, but some features are intentionally +not implemented. + +## Status + +- [x] CompareBrowserVersions +- [x] CreateCoreWebView2Environment +- [x] CreateCoreWebView2EnvironmentWithOptions +- [x] GetAvailableCoreWebView2BrowserVersionString + +## Not implemented features + +- Registry Overrides of Parameters +- Env Variable Overrides of Parameters +- Does not incorporate `GetCurrentPackageInfo` to search for an installed + runtime diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/arm64/WebView2Loader.dll b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/arm64/WebView2Loader.dll new file mode 100644 index 000000000..cd1c694b8 Binary files /dev/null and b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/arm64/WebView2Loader.dll differ diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create.go new file mode 100644 index 000000000..892189a4a --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create.go @@ -0,0 +1,176 @@ +//go:build windows && !native_webview2loader + +package webviewloader + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge" + "golang.org/x/sys/windows" +) + +func init() { + fmt.Println("DEB | Using go webview2loader") + preventEnvAndRegistryOverrides() +} + +type webView2RunTimeType int32 + +const ( + webView2RunTimeTypeInstalled webView2RunTimeType = 0x00 + webView2RunTimeTypeRedistributable webView2RunTimeType = 0x01 +) + +// CreateCoreWebView2Environment creates an evergreen WebView2 Environment using the installed WebView2 Runtime version. +// +// This is equivalent to running CreateCoreWebView2EnvironmentWithOptions without any options. +// For more information, see CreateCoreWebView2EnvironmentWithOptions. +func CreateCoreWebView2Environment(environmentCompletedHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) error { + return CreateCoreWebView2EnvironmentWithOptions(environmentCompletedHandler) +} + +// CreateCoreWebView2EnvironmentWithOptions creates an environment with a custom version of WebView2 Runtime, +// user data folder, and with or without additional options. +// +// See https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?#createcorewebview2environmentwithoptions +func CreateCoreWebView2EnvironmentWithOptions(environmentCompletedHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, opts ...option) error { + var params environmentOptions + for _, opt := range opts { + opt(¶ms) + } + + var err error + var dllPath string + var runtimeType webView2RunTimeType + if browserExecutableFolder := params.browserExecutableFolder; browserExecutableFolder != "" { + runtimeType = webView2RunTimeTypeRedistributable + dllPath, err = findEmbeddedClientDll(browserExecutableFolder) + } else { + runtimeType = webView2RunTimeTypeInstalled + dllPath, _, err = findInstalledClientDll(params.preferCanary) + } + + if err != nil { + return err + } + + return createWebViewEnvironmentWithClientDll(dllPath, runtimeType, params.userDataFolder, + ¶ms, environmentCompletedHandler) +} + +func createWebViewEnvironmentWithClientDll(lpLibFileName string, runtimeType webView2RunTimeType, userDataFolder string, + envOptions *environmentOptions, envCompletedHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler) error { + + if !filepath.IsAbs(lpLibFileName) { + return fmt.Errorf("lpLibFileName must be absolute") + } + + dll, err := windows.LoadDLL(lpLibFileName) + if err != nil { + return fmt.Errorf("Loading DLL failed: %w", err) + } + + defer func() { + canUnloadProc, err := dll.FindProc("DllCanUnloadNow") + if err != nil { + return + } + + if r1, _, _ := canUnloadProc.Call(); r1 != windows.NO_ERROR { + return + } + + dll.Release() + }() + + createProc, err := dll.FindProc("CreateWebViewEnvironmentWithOptionsInternal") + if err != nil { + return fmt.Errorf("Unable to find CreateWebViewEnvironmentWithOptionsInternal entrypoint: %w", err) + } + + userDataPtr, err := windows.UTF16PtrFromString(userDataFolder) + if err != nil { + return err + } + + envOptionsCom := combridge.New2[iCoreWebView2EnvironmentOptions, iCoreWebView2EnvironmentOptions2]( + envOptions, envOptions) + + defer envOptionsCom.Close() + + envCompletedHandler = &environmentCreatedHandler{envCompletedHandler} + envCompletedCom := combridge.New[iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler](envCompletedHandler) + defer envCompletedCom.Close() + + preventEnvAndRegistryOverrides() + + const unknown = 1 + hr, _, err := createProc.Call( + uintptr(unknown), + uintptr(runtimeType), + uintptr(unsafe.Pointer(userDataPtr)), + uintptr(envOptionsCom.Ref()), + uintptr(envCompletedCom.Ref())) + + if hr != 0 { + if err == nil || err == windows.ERROR_SUCCESS { + err = syscall.Errno(hr) + } + return err + } + + return nil +} + +type environmentCreatedHandler struct { + originalHandler ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler +} + +func (r *environmentCreatedHandler) EnvironmentCompleted(errorCode HRESULT, createdEnvironment *ICoreWebView2Environment) HRESULT { + // The OpenWebview2Loader has some retry logic and retries once, didn't encounter any case when this would have been + // needed during the development: https://github.com/jchv/OpenWebView2Loader/blob/master/Source/WebView2Loader.cpp#L202 + + if createdEnvironment != nil { + // May or may not be necessary, but the official WebView2Loader seems to do it. + iidICoreWebView2Environment := windows.GUID{ + Data1: 0xb96d755e, + Data2: 0x0319, + Data3: 0x4e92, + Data4: [8]byte{0xa2, 0x96, 0x23, 0x43, 0x6f, 0x46, 0xa1, 0xfc}, + } + + if err := createdEnvironment.QueryInterface(&iidICoreWebView2Environment, &createdEnvironment); err != nil { + createdEnvironment = nil + errNo, ok := err.(syscall.Errno) + if !ok { + errNo = syscall.Errno(windows.E_FAIL) + } + errorCode = HRESULT(errNo) + } + } + + r.originalHandler.EnvironmentCompleted(errorCode, createdEnvironment) + + if createdEnvironment != nil { + createdEnvironment.Release() + } + + return HRESULT(windows.S_OK) +} + +func preventEnvAndRegistryOverrides() { + // Setting these env variables to empty string also prevents registry overrides because webview2 + // checks for existence and not for empty value + os.Setenv("WEBVIEW2_PIPE_FOR_SCRIPT_DEBUGGER", "") + os.Setenv("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "") + os.Setenv("WEBVIEW2_RELEASE_CHANNEL_PREFERENCE", "0") + + // The following seems not be be required because those are only used by the webview2loader which + // in this case is implemented on our own. But nevertheless set them to empty to be consistent. + os.Setenv("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", "") + os.Setenv("WEBVIEW2_USER_DATA_FOLDER", "") +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create_completed.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create_completed.go new file mode 100644 index 000000000..223c77e85 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create_completed.go @@ -0,0 +1,42 @@ +//go:build windows && !native_webview2loader + +package webviewloader + +import ( + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge" +) + +// HRESULT +// +// See https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values +type HRESULT int32 + +// ICoreWebView2Environment Represents the WebView2 Environment +// +// See https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2environment +type ICoreWebView2Environment = combridge.IUnknownImpl + +// ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler receives the WebView2Environment created using CreateCoreWebView2Environment. +type ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler interface { + // EnvironmentCompleted is invoked to receive the created WebView2Environment + // + // See https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2createcorewebview2environmentcompletedhandler?#invoke + EnvironmentCompleted(errorCode HRESULT, createdEnvironment *ICoreWebView2Environment) HRESULT +} + +type iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler interface { + combridge.IUnknown + ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler +} + +func init() { + combridge.RegisterVTable[combridge.IUnknown, iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler]( + "{4e8a3389-c9d8-4bd2-b6b5-124fee6cc14d}", + _iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerInvoke, + ) +} + +func _iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandlerInvoke(this uintptr, errorCode HRESULT, env *combridge.IUnknownImpl) uintptr { + res := combridge.Resolve[iCoreWebView2CreateCoreWebView2EnvironmentCompletedHandler](this).EnvironmentCompleted(errorCode, env) + return uintptr(res) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create_options.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create_options.go new file mode 100644 index 000000000..4bae6064a --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/env_create_options.go @@ -0,0 +1,276 @@ +//go:build windows && !native_webview2loader + +package webviewloader + +import ( + "unicode/utf16" + "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/combridge" + "golang.org/x/sys/windows" +) + +// WithBrowserExecutableFolder to specify whether WebView2 controls use a fixed or installed version +// of the WebView2 Runtime that exists on a user machine. +// +// To use a fixed version of the WebView2 Runtime, +// pass the folder path that contains the fixed version of the WebView2 Runtime. +// BrowserExecutableFolder supports both relative (to the application's executable) and absolute files paths. +// To create WebView2 controls that use the installed version of the WebView2 Runtime that exists on user +// machines, pass a empty string to WithBrowserExecutableFolder. In this scenario, the API tries to find a +// compatible version of the WebView2 Runtime that is installed on the user machine (first at the machine level, +// and then per user) using the selected channel preference. The path of fixed version of the WebView2 Runtime +// should not contain \Edge\Application\. When such a path is used, the API fails with HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED). +func WithBrowserExecutableFolder(folder string) option { + return func(wvep *environmentOptions) { + wvep.browserExecutableFolder = folder + } +} + +// WithUserDataFolder specifies to user data folder location for WebView2 +// +// You may specify the userDataFolder to change the default user data folder location for WebView2. +// The path is either an absolute file path or a relative file path that is interpreted as relative +// to the compiled code for the current process. +// Dhe default user data ({Executable File Name}.WebView2) folder is created in the same directory +// next to the compiled code for the app. WebView2 creation fails if the compiled code is running +// in a directory in which the process does not have permission to create a new directory. +// The app is responsible to clean up the associated user data folder when it is done. +func WithUserDataFolder(folder string) option { + return func(wvep *environmentOptions) { + wvep.userDataFolder = folder + } +} + +// WithAdditionalBrowserArguments changes the behavior of the WebView. +// +// The arguments are passed to the +// browser process as part of the command. For more information about +// using command-line switches with Chromium browser processes, navigate to +// [Run Chromium with Flags][ChromiumDevelopersHowTosRunWithFlags]. +// The value appended to a switch is appended to the browser process, for +// example, in `--edge-webview-switches=xxx` the value is `xxx`. If you +// specify a switch that is important to WebView functionality, it is +// ignored, for example, `--user-data-dir`. Specific features are disabled +// internally and blocked from being enabled. If a switch is specified +// multiple times, only the last instance is used. +// +// \> [!NOTE]\n\> A merge of the different values of the same switch is not attempted, +// except for disabled and enabled features. The features specified by +// `--enable-features` and `--disable-features` are merged with simple +// logic.\n\> * The features is the union of the specified features +// and built-in features. If a feature is disabled, it is removed from the +// enabled features list. +// +// If you specify command-line switches and use the +// `additionalBrowserArguments` parameter, the `--edge-webview-switches` +// value takes precedence and is processed last. If a switch fails to +// parse, the switch is ignored. The default state for the operation is +// to run the browser process with no extra flags. +// +// [ChromiumDevelopersHowTosRunWithFlags]: https://www.chromium.org/developers/how-tos/run-chromium-with-flags "Run Chromium with flags | The Chromium Projects" +func WithAdditionalBrowserArguments(args string) option { + return func(wvep *environmentOptions) { + wvep.additionalBrowserArguments = args + } +} + +// WithLanguage sets the default display language for WebView. +// +// It applies to browser UI such as +// context menu and dialogs. It also applies to the `accept-languages` HTTP +// header that WebView sends to websites. It is in the format of +// +// `language[-country]` where `language` is the 2-letter code from +// [ISO 639][ISO639LanguageCodesHtml] +// and `country` is the +// 2-letter code from +// [ISO 3166][ISOStandard72482Html]. +// +// [ISO639LanguageCodesHtml]: https://www.iso.org/iso-639-language-codes.html "ISO 639 | ISO" +// [ISOStandard72482Html]: https://www.iso.org/standard/72482.html "ISO 3166-1:2020 | ISO" +func WithLanguage(lang string) option { + return func(wvep *environmentOptions) { + wvep.language = lang + } +} + +// WithTargetCompatibleBrowserVersion secifies the version of the WebView2 Runtime binaries required to be +// compatible with your app. +// +// This defaults to the WebView2 Runtime version +// that corresponds with the version of the SDK the app is using. The +// format of this value is the same as the format of the +// `BrowserVersionString` property and other `BrowserVersion` values. Only +// the version part of the `BrowserVersion` value is respected. The channel +// suffix, if it exists, is ignored. The version of the WebView2 Runtime +// binaries actually used may be different from the specified +// `TargetCompatibleBrowserVersion`. The binaries are only guaranteed to be +// compatible. Verify the actual version on the `BrowserVersionString` +// property on the `ICoreWebView2Environment`. +func WithTargetCompatibleBrowserVersion(version string) option { + return func(wvep *environmentOptions) { + wvep.targetCompatibleBrowserVersion = version + } +} + +// WithAllowSingleSignOnUsingOSPrimaryAccount is used to enable +// single sign on with Azure Active Directory (AAD) and personal Microsoft +// Account (MSA) resources inside WebView. All AAD accounts, connected to +// Windows and shared for all apps, are supported. For MSA, SSO is only enabled +// for the account associated for Windows account login, if any. +// Default is disabled. Universal Windows Platform apps must also declare +// `enterpriseCloudSSO` +// [Restricted capabilities][WindowsUwpPackagingAppCapabilityDeclarationsRestrictedCapabilities] +// for the single sign on (SSO) to work. +// +// [WindowsUwpPackagingAppCapabilityDeclarationsRestrictedCapabilities]: /windows/uwp/packaging/app-capability-declarations\#restricted-capabilities "Restricted capabilities - App capability declarations | Microsoft Docs" +func WithAllowSingleSignOnUsingOSPrimaryAccount(allow bool) option { + return func(wvep *environmentOptions) { + wvep.allowSingleSignOnUsingOSPrimaryAccount = allow + } +} + +// WithExclusiveUserDataFolderAccess specifies that the WebView environment +// obtains exclusive access to the user data folder. +// +// If the user data folder is already being used by another WebView environment with a +// different value for `ExclusiveUserDataFolderAccess` property, the creation of a WebView2Controller +// using the environment object will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. +// When set as TRUE, no other WebView can be created from other processes using WebView2Environment +// objects with the same UserDataFolder. This prevents other processes from creating WebViews +// which share the same browser process instance, since sharing is performed among +// WebViews that have the same UserDataFolder. When another process tries to create a +// WebView2Controller from an WebView2Environment object created with the same user data folder, +// it will fail with `HRESULT_FROM_WIN32(ERROR_INVALID_STATE)`. +func WithExclusiveUserDataFolderAccess(exclusive bool) option { + return func(wvep *environmentOptions) { + wvep.exclusiveUserDataFolderAccess = exclusive + } +} + +type option func(*environmentOptions) + +var _ iCoreWebView2EnvironmentOptions = &environmentOptions{} +var _ iCoreWebView2EnvironmentOptions2 = &environmentOptions{} + +type environmentOptions struct { + browserExecutableFolder string + userDataFolder string + preferCanary bool + + additionalBrowserArguments string + language string + targetCompatibleBrowserVersion string + allowSingleSignOnUsingOSPrimaryAccount bool + exclusiveUserDataFolderAccess bool +} + +func (o *environmentOptions) AdditionalBrowserArguments() string { + return o.additionalBrowserArguments +} + +func (o *environmentOptions) Language() string { + return o.language +} + +func (o *environmentOptions) TargetCompatibleBrowserVersion() string { + v := o.targetCompatibleBrowserVersion + if v == "" { + v = kMinimumCompatibleVersion + } + return v +} + +func (o *environmentOptions) AllowSingleSignOnUsingOSPrimaryAccount() bool { + return o.allowSingleSignOnUsingOSPrimaryAccount +} + +func (o *environmentOptions) ExclusiveUserDataFolderAccess() bool { + return o.exclusiveUserDataFolderAccess +} + +type iCoreWebView2EnvironmentOptions interface { + combridge.IUnknown + + AdditionalBrowserArguments() string + Language() string + TargetCompatibleBrowserVersion() string + AllowSingleSignOnUsingOSPrimaryAccount() bool +} + +type iCoreWebView2EnvironmentOptions2 interface { + combridge.IUnknown + + ExclusiveUserDataFolderAccess() bool +} + +func init() { + combridge.RegisterVTable[combridge.IUnknown, iCoreWebView2EnvironmentOptions]( + "{2fde08a8-1e9a-4766-8c05-95a9ceb9d1c5}", + _iCoreWebView2EnvironmentOptionsAdditionalBrowserArguments, + _iCoreWebView2EnvironmentOptionsNOP, + _iCoreWebView2EnvironmentOptionsLanguage, + _iCoreWebView2EnvironmentOptionsNOP, + _iCoreWebView2EnvironmentTargetCompatibleBrowserVersion, + _iCoreWebView2EnvironmentOptionsNOP, + _iCoreWebView2EnvironmentOptionsAllowSingleSignOnUsingOSPrimaryAccount, + _iCoreWebView2EnvironmentOptionsNOP, + ) + + combridge.RegisterVTable[combridge.IUnknown, iCoreWebView2EnvironmentOptions2]( + "{ff85c98a-1ba7-4a6b-90c8-2b752c89e9e2}", + _iCoreWebView2EnvironmentOptions2ExclusiveUserDataFolderAccess, + _iCoreWebView2EnvironmentOptionsNOP, + ) +} +func _iCoreWebView2EnvironmentOptionsNOP(this uintptr) uintptr { + return uintptr(windows.S_FALSE) +} + +func _iCoreWebView2EnvironmentOptionsAdditionalBrowserArguments(this uintptr, value **uint16) uintptr { + v := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).AdditionalBrowserArguments() + *value = stringToOleString(v) + return uintptr(windows.S_OK) +} + +func _iCoreWebView2EnvironmentOptionsLanguage(this uintptr, value **uint16) uintptr { + args := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).Language() + *value = stringToOleString(args) + return uintptr(windows.S_OK) +} + +func _iCoreWebView2EnvironmentTargetCompatibleBrowserVersion(this uintptr, value **uint16) uintptr { + args := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).TargetCompatibleBrowserVersion() + *value = stringToOleString(args) + return uintptr(windows.S_OK) +} + +func _iCoreWebView2EnvironmentOptionsAllowSingleSignOnUsingOSPrimaryAccount(this uintptr, value *int32) uintptr { + v := combridge.Resolve[iCoreWebView2EnvironmentOptions](this).AllowSingleSignOnUsingOSPrimaryAccount() + *value = boolToInt(v) + return uintptr(windows.S_OK) +} + +func _iCoreWebView2EnvironmentOptions2ExclusiveUserDataFolderAccess(this uintptr, value *int32) uintptr { + v := combridge.Resolve[iCoreWebView2EnvironmentOptions2](this).ExclusiveUserDataFolderAccess() + *value = boolToInt(v) + return uintptr(windows.S_OK) +} + +func stringToOleString(v string) *uint16 { + wstr := utf16.Encode([]rune(v + "\x00")) + lwstr := len(wstr) + ptr := (*uint16)(coTaskMemAlloc(2 * lwstr)) + + copy(unsafe.Slice(ptr, lwstr), wstr) + + return ptr +} + +func boolToInt(v bool) int32 { + if v { + return 1 + } + return 0 +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/find_dll.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/find_dll.go new file mode 100644 index 000000000..da9472ff1 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/find_dll.go @@ -0,0 +1,74 @@ +//go:build windows + +package webviewloader + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + + "golang.org/x/sys/windows/registry" +) + +var ( + errNoClientDLLFound = errors.New("no webview2 found") +) + +func findEmbeddedBrowserVersion(filename string) (string, error) { + block, err := getFileVersionInfo(filename) + if err != nil { + return "", err + } + + info, err := verQueryValueString(block, "\\StringFileInfo\\040904B0\\ProductVersion") + if err != nil { + return "", err + } + + return info, nil +} + +func findEmbeddedClientDll(embeddedEdgeSubFolder string) (outClientPath string, err error) { + if !filepath.IsAbs(embeddedEdgeSubFolder) { + exe, err := os.Executable() + if err != nil { + return "", err + } + + embeddedEdgeSubFolder = filepath.Join(filepath.Dir(exe), embeddedEdgeSubFolder) + } + + return findClientDllInFolder(embeddedEdgeSubFolder) +} + +func findClientDllInFolder(folder string) (string, error) { + arch := "" + switch runtime.GOARCH { + case "arm64": + arch = "arm64" + case "amd64": + arch = "x64" + case "386": + arch = "x86" + default: + return "", fmt.Errorf("Unsupported architecture") + } + + dllPath := filepath.Join(folder, "EBWebView", arch, "EmbeddedBrowserWebView.dll") + if _, err := os.Stat(dllPath); err != nil { + return "", mapFindErr(err) + } + return dllPath, nil +} + +func mapFindErr(err error) error { + if errors.Is(err, registry.ErrNotExist) { + return errNoClientDLLFound + } + if errors.Is(err, os.ErrNotExist) { + return errNoClientDLLFound + } + return err +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/find_dll_installed.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/find_dll_installed.go new file mode 100644 index 000000000..7ee171b2a --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/find_dll_installed.go @@ -0,0 +1,94 @@ +//go:build windows && !native_webview2loader + +package webviewloader + +import ( + "path/filepath" + + "golang.org/x/sys/windows/registry" +) + +const ( + kNumChannels = 4 + kInstallKeyPath = "Software\\Microsoft\\EdgeUpdate\\ClientState\\" + kMinimumCompatibleVersion = "86.0.616.0" +) + +var ( + kChannelName = [kNumChannels]string{ + "", "beta", "dev", "canary", // "internal" + } + + kChannelUuid = [kNumChannels]string{ + "{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}", + "{2CD8A007-E189-409D-A2C8-9AF4EF3C72AA}", + "{0D50BFEC-CD6A-4F9A-964C-C7416E3ACB10}", + "{65C35B14-6C1D-4122-AC46-7148CC9D6497}", + //"{BE59E8FD-089A-411B-A3B0-051D9E417818}", + } + + minimumCompatibleVersion, _ = parseVersion(kMinimumCompatibleVersion) +) + +func findInstalledClientDll(preferCanary bool) (clientPath string, version *version, err error) { + for i := 0; i < kNumChannels; i++ { + channel := i + if preferCanary { + channel = (kNumChannels - 1) - i + } + + key := kInstallKeyPath + kChannelUuid[channel] + for _, checkSystem := range []bool{true, false} { + clientPath, version, err := findInstalledClientDllForChannel(key, checkSystem) + if err == errNoClientDLLFound { + continue + } + if err != nil { + return "", nil, err + } + + version.channel = kChannelName[channel] + return clientPath, version, nil + } + } + return "", nil, errNoClientDLLFound +} + +func findInstalledClientDllForChannel(subKey string, system bool) (clientPath string, clientVersion *version, err error) { + key := registry.LOCAL_MACHINE + if !system { + key = registry.CURRENT_USER + } + + regKey, err := registry.OpenKey(key, subKey, registry.READ|registry.WOW64_32KEY) + if err != nil { + return "", nil, mapFindErr(err) + } + defer regKey.Close() + + embeddedEdgeSubFolder, _, err := regKey.GetStringValue("EBWebView") + if err != nil { + return "", nil, mapFindErr(err) + } + + if embeddedEdgeSubFolder == "" { + return "", nil, errNoClientDLLFound + } + + versionString := filepath.Base(embeddedEdgeSubFolder) + version, err := parseVersion(versionString) + if err != nil { + return "", nil, errNoClientDLLFound + } + + if version.compare(minimumCompatibleVersion) < 0 { + return "", nil, errNoClientDLLFound + } + + dllPath, err := findEmbeddedClientDll(embeddedEdgeSubFolder) + if err != nil { + return "", nil, mapFindErr(err) + } + + return dllPath, &version, nil +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module.go new file mode 100644 index 000000000..3e02fe985 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module.go @@ -0,0 +1,173 @@ +//go:build windows && native_webview2loader + +package webviewloader + +import ( + "errors" + "fmt" + "os" + "sync" + "unsafe" + + "github.com/jchv/go-winloader" + + "golang.org/x/sys/windows" +) + +func init() { + preventEnvAndRegistryOverrides(nil, nil, "") +} + +var ( + memOnce sync.Once + memModule winloader.Module + memCreate winloader.Proc + memCompareBrowserVersions winloader.Proc + memGetAvailableCoreWebView2BrowserVersionString winloader.Proc + memErr error +) + +const ( + // https://referencesource.microsoft.com/#system.web/Util/hresults.cs,20 + E_FILENOTFOUND = 0x80070002 +) + +// CompareBrowserVersions will compare the 2 given versions and return: +// +// Less than zero: v1 < v2 +// zero: v1 == v2 +// Greater than zero: v1 > v2 +func CompareBrowserVersions(v1 string, v2 string) (int, error) { + _v1, err := windows.UTF16PtrFromString(v1) + if err != nil { + return 0, err + } + _v2, err := windows.UTF16PtrFromString(v2) + if err != nil { + return 0, err + } + + err = loadFromMemory() + if err != nil { + return 0, err + } + + var result int32 + _, _, err = memCompareBrowserVersions.Call( + uint64(uintptr(unsafe.Pointer(_v1))), + uint64(uintptr(unsafe.Pointer(_v2))), + uint64(uintptr(unsafe.Pointer(&result)))) + + if err != windows.ERROR_SUCCESS { + return 0, err + } + return int(result), nil +} + +// GetAvailableCoreWebView2BrowserVersionString returns version of the webview2 runtime. +// If path is empty, it will try to find installed webview2 is the system. +// If there is no version installed, a blank string is returned. +func GetAvailableCoreWebView2BrowserVersionString(path string) (string, error) { + if path != "" { + // The default implementation fails if CGO and a fixed browser path is used. It's caused by the go-winloader + // which loads the native DLL from memory. + // Use the new GoWebView2Loader in this case, in the future we will make GoWebView2Loader + // feature-complete and remove the use of the native DLL and go-winloader. + version, err := goGetAvailableCoreWebView2BrowserVersionString(path) + if errors.Is(err, errNoClientDLLFound) { + // WebView2 is not found + return "", nil + } else if err != nil { + return "", err + } + + return version, nil + } + + err := loadFromMemory() + if err != nil { + return "", err + } + + var browserPath *uint16 = nil + if path != "" { + browserPath, err = windows.UTF16PtrFromString(path) + if err != nil { + return "", fmt.Errorf("error calling UTF16PtrFromString for %s: %v", path, err) + } + } + + preventEnvAndRegistryOverrides(browserPath, nil, "") + var result *uint16 + res, _, err := memGetAvailableCoreWebView2BrowserVersionString.Call( + uint64(uintptr(unsafe.Pointer(browserPath))), + uint64(uintptr(unsafe.Pointer(&result)))) + + if res != 0 { + if res == E_FILENOTFOUND { + // WebView2 is not installed + return "", nil + } + + return "", fmt.Errorf("Unable to call GetAvailableCoreWebView2BrowserVersionString (%x): %w", res, err) + } + + version := windows.UTF16PtrToString(result) + windows.CoTaskMemFree(unsafe.Pointer(result)) + return version, nil +} + +// CreateCoreWebView2EnvironmentWithOptions tries to load WebviewLoader2 and +// call the CreateCoreWebView2EnvironmentWithOptions routine. +func CreateCoreWebView2EnvironmentWithOptions(browserExecutableFolder, userDataFolder *uint16, environmentCompletedHandle uintptr, additionalBrowserArgs string) (uintptr, error) { + err := loadFromMemory() + if err != nil { + return 0, err + } + + preventEnvAndRegistryOverrides(browserExecutableFolder, userDataFolder, additionalBrowserArgs) + res, _, _ := memCreate.Call( + uint64(uintptr(unsafe.Pointer(browserExecutableFolder))), + uint64(uintptr(unsafe.Pointer(userDataFolder))), + 0, + uint64(environmentCompletedHandle), + ) + return uintptr(res), nil +} + +func loadFromMemory() error { + var err error + // DLL is not available natively. Try loading embedded copy. + memOnce.Do(func() { + memModule, memErr = winloader.LoadFromMemory(WebView2Loader) + if memErr != nil { + err = fmt.Errorf("Unable to load WebView2Loader.dll from memory: %w", memErr) + return + } + memCreate = memModule.Proc("CreateCoreWebView2EnvironmentWithOptions") + memCompareBrowserVersions = memModule.Proc("CompareBrowserVersions") + memGetAvailableCoreWebView2BrowserVersionString = memModule.Proc("GetAvailableCoreWebView2BrowserVersionString") + }) + return err +} + +func preventEnvAndRegistryOverrides(browserFolder, userDataFolder *uint16, additionalBrowserArgs string) { + // Setting these env variables to empty string also prevents registry overrides because webview2loader + // checks for existence and not for empty value + os.Setenv("WEBVIEW2_PIPE_FOR_SCRIPT_DEBUGGER", "") + + // Set these overrides to the values or empty to prevent registry and external env overrides + os.Setenv("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", additionalBrowserArgs) + os.Setenv("WEBVIEW2_RELEASE_CHANNEL_PREFERENCE", "0") + os.Setenv("WEBVIEW2_BROWSER_EXECUTABLE_FOLDER", windows.UTF16PtrToString(browserFolder)) + os.Setenv("WEBVIEW2_USER_DATA_FOLDER", windows.UTF16PtrToString(userDataFolder)) +} + +func goGetAvailableCoreWebView2BrowserVersionString(browserExecutableFolder string) (string, error) { + clientPath, err := findEmbeddedClientDll(browserExecutableFolder) + if err != nil { + return "", err + } + + return findEmbeddedBrowserVersion(clientPath) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_386.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_386.go new file mode 100644 index 000000000..e4ff44ff3 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_386.go @@ -0,0 +1,8 @@ +//go:build windows && native_webview2loader + +package webviewloader + +import _ "embed" + +//go:embed x86/WebView2Loader.dll +var WebView2Loader []byte diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_amd64.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_amd64.go new file mode 100644 index 000000000..27423ae8a --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_amd64.go @@ -0,0 +1,8 @@ +//go:build windows && native_webview2loader + +package webviewloader + +import _ "embed" + +//go:embed x64/WebView2Loader.dll +var WebView2Loader []byte diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_arm64.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_arm64.go new file mode 100644 index 000000000..bba6a88cb --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/native_module_arm64.go @@ -0,0 +1,8 @@ +//go:build windows && native_webview2loader + +package webviewloader + +import _ "embed" + +//go:embed arm64/WebView2Loader.dll +var WebView2Loader []byte diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/syscall.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/syscall.go new file mode 100644 index 000000000..24d0856a5 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/syscall.go @@ -0,0 +1,143 @@ +//go:build windows + +package webviewloader + +import ( + "fmt" + "syscall" + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + procGlobalAlloc = modkernel32.NewProc("GlobalAlloc") + procGlobalFree = modkernel32.NewProc("GlobalFree") + + modversion = windows.NewLazySystemDLL("version.dll") + procGetFileVersionInfoSize = modversion.NewProc("GetFileVersionInfoSizeW") + procGetFileVersionInfo = modversion.NewProc("GetFileVersionInfoW") + procVerQueryValue = modversion.NewProc("VerQueryValueW") + + modole32 = windows.NewLazySystemDLL("ole32.dll") + procCoTaskMemAlloc = modole32.NewProc("CoTaskMemAlloc") +) + +func getFileVersionInfo(path string) ([]byte, error) { + lptstrFilename, err := syscall.UTF16PtrFromString(path) + if err != nil { + return nil, err + } + + size, _, err := procGetFileVersionInfoSize.Call( + uintptr(unsafe.Pointer(lptstrFilename)), + 0, + ) + + err = maskErrorSuccess(err) + if size == 0 && err == nil { + err = fmt.Errorf("GetFileVersionInfoSize failed") + } + + if err != nil { + return nil, err + } + + data := make([]byte, size) + ret, _, err := procGetFileVersionInfo.Call( + uintptr(unsafe.Pointer(lptstrFilename)), + 0, + uintptr(size), + uintptr(unsafe.Pointer(&data[0])), + ) + + err = maskErrorSuccess(err) + if ret == 0 && err == nil { + err = fmt.Errorf("GetFileVersionInfo failed") + } + + if err != nil { + return nil, err + } + return data, nil +} + +func verQueryValueString(block []byte, subBlock string) (string, error) { + // Allocate memory from native side to make sure the block doesn't get moved + // because we get a pointer into that memory block from the native verQueryValue + // call back. + pBlock := globalAlloc(0, uint32(len(block))) + defer globalFree(unsafe.Pointer(pBlock)) + + // Copy the memory region into native side memory + copy(unsafe.Slice((*byte)(pBlock), len(block)), block) + + lpSubBlock, err := syscall.UTF16PtrFromString(subBlock) + if err != nil { + return "", err + } + + var lplpBuffer unsafe.Pointer + var puLen uint + ret, _, err := procVerQueryValue.Call( + uintptr(pBlock), + uintptr(unsafe.Pointer(lpSubBlock)), + uintptr(unsafe.Pointer(&lplpBuffer)), + uintptr(unsafe.Pointer(&puLen)), + ) + + err = maskErrorSuccess(err) + if ret == 0 && err == nil { + err = fmt.Errorf("VerQueryValue failed") + } + + if err != nil { + return "", err + } + + if puLen <= 1 { + return "", nil + } + puLen -= 1 // Remove Null-Terminator + + wchar := unsafe.Slice((*uint16)(lplpBuffer), puLen) + return string(utf16.Decode(wchar)), nil +} + +func globalAlloc(uFlags uint, dwBytes uint32) unsafe.Pointer { + ret, _, _ := procGlobalAlloc.Call( + uintptr(uFlags), + uintptr(dwBytes)) + + if ret == 0 { + panic("globalAlloc failed") + } + + return unsafe.Pointer(ret) +} + +func globalFree(data unsafe.Pointer) { + ret, _, _ := procGlobalFree.Call(uintptr(data)) + if ret != 0 { + panic("globalFree failed") + } +} + +func maskErrorSuccess(err error) error { + if err == windows.ERROR_SUCCESS { + return nil + } + return err +} + +func coTaskMemAlloc(size int) unsafe.Pointer { + ret, _, _ := procCoTaskMemAlloc.Call( + uintptr(size)) + + if ret == 0 { + panic("coTaskMemAlloc failed") + } + return unsafe.Pointer(ret) +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/version.go b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/version.go new file mode 100644 index 000000000..cf278d950 --- /dev/null +++ b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/version.go @@ -0,0 +1,147 @@ +//go:build windows && !native_webview2loader + +package webviewloader + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// CompareBrowserVersions will compare the 2 given versions and return: +// +// -1 = v1 < v2 +// 0 = v1 == v2 +// 1 = v1 > v2 +func CompareBrowserVersions(v1 string, v2 string) (int, error) { + v, err := parseVersion(v1) + if err != nil { + return 0, fmt.Errorf("v1 invalid: %w", err) + } + + w, err := parseVersion(v2) + if err != nil { + return 0, fmt.Errorf("v2 invalid: %w", err) + } + + return v.compare(w), nil +} + +// GetAvailableCoreWebView2BrowserVersionString get the browser version info including channel name +// if it is the WebView2 Runtime. +// Channel names are Beta, Dev, and Canary. +func GetAvailableCoreWebView2BrowserVersionString(browserExecutableFolder string) (string, error) { + if browserExecutableFolder != "" { + clientPath, err := findEmbeddedClientDll(browserExecutableFolder) + if errors.Is(err, errNoClientDLLFound) { + // WebView2 is not found + return "", nil + } else if err != nil { + return "", err + } + + return findEmbeddedBrowserVersion(clientPath) + } + + _, version, err := findInstalledClientDll(false) + if errors.Is(err, errNoClientDLLFound) { + return "", nil + } else if err != nil { + return "", err + } + + return version.String(), nil +} + +type version struct { + major int + minor int + patch int + build int + + channel string +} + +func (v version) String() string { + vv := fmt.Sprintf("%d.%d.%d.%d", v.major, v.minor, v.patch, v.build) + if v.channel != "" { + vv += " " + v.channel + } + + return vv +} + +func (v version) compare(o version) int { + if c := compareInt(v.major, o.major); c != 0 { + return c + } + if c := compareInt(v.minor, o.minor); c != 0 { + return c + } + if c := compareInt(v.patch, o.patch); c != 0 { + return c + } + return compareInt(v.build, o.build) +} + +func parseVersion(v string) (version, error) { + var p version + + // Split away channel information... + if i := strings.Index(v, " "); i > 0 { + p.channel = v[i+1:] + v = v[:i] + } + + vv := strings.Split(v, ".") + if len(vv) > 4 { + return p, fmt.Errorf("too many version parts") + } + + var err error + vv, p.major, err = parseInt(vv) + if err != nil { + return p, fmt.Errorf("bad major version: %w", err) + } + + vv, p.minor, err = parseInt(vv) + if err != nil { + return p, fmt.Errorf("bad minor version: %w", err) + } + + vv, p.patch, err = parseInt(vv) + if err != nil { + return p, fmt.Errorf("bad patch version: %w", err) + } + + _, p.build, err = parseInt(vv) + if err != nil { + return p, fmt.Errorf("bad build version: %w", err) + } + + return p, nil +} + +func parseInt(v []string) ([]string, int, error) { + if len(v) == 0 { + return nil, 0, nil + } + + p, err := strconv.ParseInt(v[0], 10, 32) + if err != nil { + return nil, 0, err + } + return v[1:], int(p), nil +} + +func compareInt(v1, v2 int) int { + if v1 == v2 { + return 0 + } + if v1 < v2 { + return -1 + } else { + return +1 + } +} diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/x64/WebView2Loader.dll b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/x64/WebView2Loader.dll new file mode 100644 index 000000000..ab15cffb4 Binary files /dev/null and b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/x64/WebView2Loader.dll differ diff --git a/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/x86/WebView2Loader.dll b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/x86/WebView2Loader.dll new file mode 100644 index 000000000..8609d58ee Binary files /dev/null and b/v2/internal/frontend/desktop/windows/go-webview2/webviewloader/x86/WebView2Loader.dll differ diff --git a/v2/internal/frontend/desktop/windows/notifications.go b/v2/internal/frontend/desktop/windows/notifications.go deleted file mode 100644 index 0176b7077..000000000 --- a/v2/internal/frontend/desktop/windows/notifications.go +++ /dev/null @@ -1,489 +0,0 @@ -//go:build windows -// +build windows - -package windows - -import ( - "encoding/base64" - "encoding/json" - "log" - "sync" - - wintoast "git.sr.ht/~jackmordaunt/go-toast/v2/wintoast" - "github.com/google/uuid" - "github.com/wailsapp/wails/v2/internal/frontend" - "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc" - "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" - - "fmt" - "os" - "path/filepath" - _ "unsafe" // for go:linkname - - "git.sr.ht/~jackmordaunt/go-toast/v2" - "golang.org/x/sys/windows/registry" -) - -var ( - categories map[string]frontend.NotificationCategory - categoriesLock sync.RWMutex - appName string - appGUID string - iconPath string = "" - exePath string - iconOnce sync.Once - iconErr error - - notificationResultCallback func(result frontend.NotificationResult) - callbackLock sync.RWMutex -) - -const DefaultActionIdentifier = "DEFAULT_ACTION" - -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 frontend.NotificationOptions `json:"payload,omitempty"` -} - -func (f *Frontend) InitializeNotifications() error { - categoriesLock.Lock() - defer categoriesLock.Unlock() - categories = make(map[string]frontend.NotificationCategory) - - exe, err := os.Executable() - if err != nil { - return fmt.Errorf("failed to get executable: %w", err) - } - exePath = exe - appName = filepath.Base(exePath) - - appGUID, err = getGUID() - if err != nil { - return err - } - - iconPath = filepath.Join(os.TempDir(), appName+appGUID+".png") - - // Create the registry key for the toast activator - key, _, err := registry.CreateKey(registry.CURRENT_USER, - `Software\Classes\CLSID\`+appGUID+`\LocalServer32`, registry.ALL_ACCESS) - if err != nil { - return fmt.Errorf("failed to create CLSID key: %w", err) - } - defer key.Close() - - if err := key.SetStringValue("", fmt.Sprintf("\"%s\" %%1", exePath)); err != nil { - return fmt.Errorf("failed to set CLSID server path: %w", err) - } - - toast.SetAppData(toast.AppData{ - AppID: appName, - GUID: appGUID, - IconPath: iconPath, - ActivationExe: exePath, - }) - - toast.SetActivationCallback(func(args string, data []toast.UserData) { - result := frontend.NotificationResult{} - - actionIdentifier, options, err := parseNotificationResponse(args) - - if err != nil { - result.Error = err - } else { - // Subtitle is retained but was not shown with the notification - response := frontend.NotificationResponse{ - ID: options.ID, - ActionIdentifier: actionIdentifier, - Title: options.Title, - Subtitle: options.Subtitle, - Body: options.Body, - CategoryID: options.CategoryID, - UserInfo: options.Data, - } - - if userText, found := getUserText(data); found { - response.UserText = userText - } - - result.Response = response - } - - handleNotificationResult(result) - }) - - // Register the COM class factory for toast activation. - // This is required for Windows to activate the app when users interact with notifications. - // The go-toast library's SetAppData and SetActivationCallback handle the callback setup, - // but the COM class factory registration is not exposed via public APIs, so we use - // go:linkname to access the internal registerClassFactory function. - if err := registerToastClassFactory(wintoast.ClassFactory); err != nil { - return fmt.Errorf("CoRegisterClassObject failed: %w", err) - } - - return loadCategoriesFromRegistry() -} - -// registerToastClassFactory registers the COM class factory required for Windows toast notification activation. -// This function uses go:linkname to access the unexported registerClassFactory function from go-toast. -// The class factory is necessary for Windows COM activation when users click notification actions. -// Without this registration, notification actions will not activate the application. -// -// This is a workaround until go-toast exports this functionality via a public API. -// See: https://git.sr.ht/~jackmordaunt/go-toast -// -//go:linkname registerToastClassFactory git.sr.ht/~jackmordaunt/go-toast/v2/wintoast.registerClassFactory -func registerToastClassFactory(factory *wintoast.IClassFactory) error - -// CleanupNotifications is a Windows stub that does nothing. -// (Linux-specific cleanup) -func (f *Frontend) CleanupNotifications() { - // No cleanup needed on Windows -} - -func (f *Frontend) IsNotificationAvailable() bool { - return true -} - -func (f *Frontend) RequestNotificationAuthorization() (bool, error) { - return true, nil -} - -func (f *Frontend) 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 (f *Frontend) SendNotification(options frontend.NotificationOptions) error { - if err := f.saveIconToDir(); err != nil { - f.logger.Warning("Error saving icon: %v", err) - } - - n := toast.Notification{ - Title: options.Title, - Body: options.Body, - ActivationType: toast.Foreground, - ActivationArguments: DefaultActionIdentifier, - } - - encodedPayload, err := 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 (f *Frontend) SendNotificationWithActions(options frontend.NotificationOptions) error { - if err := f.saveIconToDir(); err != nil { - f.logger.Warning("Error saving icon: %v", err) - } - - categoriesLock.RLock() - nCategory, categoryExists := categories[options.CategoryID] - categoriesLock.RUnlock() - - if options.CategoryID == "" || !categoryExists { - f.logger.Warning("Category '%s' not found, sending basic notification without actions", options.CategoryID) - return f.SendNotification(options) - } - - 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 := 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 := 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 (f *Frontend) RegisterNotificationCategory(category frontend.NotificationCategory) error { - categoriesLock.Lock() - defer categoriesLock.Unlock() - - categories[category.ID] = frontend.NotificationCategory{ - ID: category.ID, - Actions: category.Actions, - HasReplyField: category.HasReplyField, - ReplyPlaceholder: category.ReplyPlaceholder, - ReplyButtonTitle: category.ReplyButtonTitle, - } - - return saveCategoriesToRegistry() -} - -// RemoveNotificationCategory removes a previously registered NotificationCategory. -func (f *Frontend) RemoveNotificationCategory(categoryId string) error { - categoriesLock.Lock() - defer categoriesLock.Unlock() - - delete(categories, categoryId) - - return saveCategoriesToRegistry() -} - -// RemoveAllPendingNotifications is a Windows stub that always returns nil. -// (macOS and Linux only) -func (f *Frontend) RemoveAllPendingNotifications() error { - return nil -} - -// RemovePendingNotification is a Windows stub that always returns nil. -// (macOS and Linux only) -func (f *Frontend) RemovePendingNotification(_ string) error { - return nil -} - -// RemoveAllDeliveredNotifications is a Windows stub that always returns nil. -// (macOS and Linux only) -func (f *Frontend) RemoveAllDeliveredNotifications() error { - return nil -} - -// RemoveDeliveredNotification is a Windows stub that always returns nil. -// (macOS and Linux only) -func (f *Frontend) RemoveDeliveredNotification(_ string) error { - return nil -} - -// RemoveNotification is a Windows stub that always returns nil. -// (Linux-specific) -func (f *Frontend) RemoveNotification(identifier string) error { - return nil -} - -func (f *Frontend) OnNotificationResponse(callback func(result frontend.NotificationResult)) { - callbackLock.Lock() - defer callbackLock.Unlock() - - notificationResultCallback = callback -} - -func (f *Frontend) saveIconToDir() error { - iconOnce.Do(func() { - hIcon := w32.ExtractIcon(exePath, 0) - if hIcon == 0 { - iconErr = fmt.Errorf("ExtractIcon failed for %s", exePath) - return - } - defer w32.DestroyIcon(hIcon) - iconErr = winc.SaveHIconAsPNG(hIcon, iconPath) - }) - return iconErr -} - -func saveCategoriesToRegistry() error { - // We assume lock is held by caller - - registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, appName) - - key, _, err := registry.CreateKey( - registry.CURRENT_USER, - registryPath, - registry.ALL_ACCESS, - ) - if err != nil { - return err - } - defer key.Close() - - data, err := json.Marshal(categories) - if err != nil { - return err - } - - return key.SetStringValue(NotificationCategoriesRegistryKey, string(data)) -} - -func loadCategoriesFromRegistry() error { - // We assume lock is held by caller - - registryPath := fmt.Sprintf(NotificationCategoriesRegistryPath, 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]frontend.NotificationCategory) - if err := json.Unmarshal([]byte(data), &_categories); err != nil { - return fmt.Errorf("failed to parse notification categories from registry: %w", err) - } - - categories = _categories - - return nil -} - -func getUserText(data []toast.UserData) (string, bool) { - for _, d := range data { - if d.Key == "userText" { - return d.Value, true - } - } - return "", false -} - -// encodePayload combines an action ID and user data into a single encoded string -func encodePayload(actionID string, options frontend.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, frontend.NotificationOptions, error) { - jsonData, err := base64.StdEncoding.DecodeString(encodedString) - if err != nil { - return encodedString, frontend.NotificationOptions{}, fmt.Errorf("failed to decode base64 payload: %w", err) - } - - var payload NotificationPayload - if err := json.Unmarshal(jsonData, &payload); err != nil { - return encodedString, frontend.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 frontend.NotificationOptions, err error) { - actionID, options, err := decodePayload(response) - - if err != nil { - log.Printf("Warning: Failed to decode notification response: %v", err) - return response, frontend.NotificationOptions{}, err - } - - return actionID, options, nil -} - -func handleNotificationResult(result frontend.NotificationResult) { - callbackLock.RLock() - callback := notificationResultCallback - callbackLock.RUnlock() - - if callback != nil { - go func() { - defer func() { - if r := recover(); r != nil { - // Log panic but don't crash the app - fmt.Fprintf(os.Stderr, "panic in notification callback: %v\n", r) - } - }() - callback(result) - }() - } -} - -// Helper functions - -func getGUID() (string, error) { - keyPath := ToastRegistryPath + 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 := 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 generateGUID() string { - guid := uuid.New() - return fmt.Sprintf("{%s}", guid.String()) -} diff --git a/v2/internal/frontend/desktop/windows/screen.go b/v2/internal/frontend/desktop/windows/screen.go index f6e12bcce..e9e9cd603 100644 --- a/v2/internal/frontend/desktop/windows/screen.go +++ b/v2/internal/frontend/desktop/windows/screen.go @@ -5,12 +5,10 @@ package windows import ( "fmt" + "github.com/pkg/errors" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" "syscall" "unsafe" - - "github.com/pkg/errors" - "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc" - "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" ) func MonitorsEqual(first w32.MONITORINFO, second w32.MONITORINFO) bool { @@ -68,26 +66,13 @@ func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, return w32.TRUE } - width := lprcMonitor.Right - lprcMonitor.Left - height := lprcMonitor.Bottom - lprcMonitor.Top + height := lprcMonitor.Right - lprcMonitor.Left + width := lprcMonitor.Bottom - lprcMonitor.Top ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1 - ourMonitorData.Height = int(height) - ourMonitorData.Width = int(width) + ourMonitorData.Height = int(width) + ourMonitorData.Width = int(height) ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo) - ourMonitorData.PhysicalSize.Width = int(width) - ourMonitorData.PhysicalSize.Height = int(height) - - var dpiX, dpiY uint - w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) - if dpiX == 0 || dpiY == 0 { - screenContainer.errors = append(screenContainer.errors, fmt.Errorf("unable to get DPI for screen")) - screenContainer.monitors = append(screenContainer.monitors, Screen{}) - return w32.TRUE - } - ourMonitorData.Size.Width = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Width, dpiX) - ourMonitorData.Size.Height = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Height, dpiY) - // the reason we need a container is that we have don't know how many times this function will be called // this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors // and retrieve the values after all EnumProc calls diff --git a/v2/internal/frontend/desktop/windows/single_instance.go b/v2/internal/frontend/desktop/windows/single_instance.go deleted file mode 100644 index a02b7edb9..000000000 --- a/v2/internal/frontend/desktop/windows/single_instance.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build windows - -package windows - -import ( - "encoding/json" - "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" - "github.com/wailsapp/wails/v2/pkg/options" - "golang.org/x/sys/windows" - "log" - "os" - "syscall" - "unsafe" -) - -type COPYDATASTRUCT struct { - dwData uintptr - cbData uint32 - lpData uintptr -} - -// WMCOPYDATA_SINGLE_INSTANCE_DATA we define our own type for WM_COPYDATA message -const WMCOPYDATA_SINGLE_INSTANCE_DATA = 1542 - -func SendMessage(hwnd w32.HWND, data string) { - arrUtf16, _ := syscall.UTF16FromString(data) - - pCopyData := new(COPYDATASTRUCT) - pCopyData.dwData = WMCOPYDATA_SINGLE_INSTANCE_DATA - pCopyData.cbData = uint32(len(arrUtf16)*2 + 1) - pCopyData.lpData = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(data))) - - w32.SendMessage(hwnd, w32.WM_COPYDATA, 0, uintptr(unsafe.Pointer(pCopyData))) -} - -// SetupSingleInstance single instance Windows app -func SetupSingleInstance(uniqueId string) { - id := "wails-app-" + uniqueId - - className := id + "-sic" - windowName := id + "-siw" - mutexName := id + "sim" - - _, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(mutexName)) - - if err != nil { - if err == windows.ERROR_ALREADY_EXISTS { - // app is already running - hwnd := w32.FindWindowW(windows.StringToUTF16Ptr(className), windows.StringToUTF16Ptr(windowName)) - - if hwnd != 0 { - data := options.SecondInstanceData{ - Args: os.Args[1:], - } - data.WorkingDirectory, err = os.Getwd() - if err != nil { - log.Printf("Failed to get working directory: %v", err) - return - } - serialized, err := json.Marshal(data) - if err != nil { - log.Printf("Failed to marshal data: %v", err) - return - } - - SendMessage(hwnd, string(serialized)) - // exit second instance of app after sending message - os.Exit(0) - } - // if we got any other unknown error we will just start new application instance - } - } else { - createEventTargetWindow(className, windowName) - } -} - -func createEventTargetWindow(className string, windowName string) w32.HWND { - // callback handler in the event target window - wndProc := func( - hwnd w32.HWND, msg uint32, wparam w32.WPARAM, lparam w32.LPARAM, - ) w32.LRESULT { - if msg == w32.WM_COPYDATA { - ldata := (*COPYDATASTRUCT)(unsafe.Pointer(lparam)) - - if ldata.dwData == WMCOPYDATA_SINGLE_INSTANCE_DATA { - serialized := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ldata.lpData))) - - var secondInstanceData options.SecondInstanceData - - err := json.Unmarshal([]byte(serialized), &secondInstanceData) - - if err == nil { - secondInstanceBuffer <- secondInstanceData - } - } - - return w32.LRESULT(0) - } - - return w32.DefWindowProc(hwnd, msg, wparam, lparam) - } - - 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 = windows.StringToUTF16Ptr(className) - class.IconSm = 0 - - w32.RegisterClassEx(&class) - - // create event window that will not be visible for user - hwnd := w32.CreateWindowEx( - 0, - windows.StringToUTF16Ptr(className), - windows.StringToUTF16Ptr(windowName), - 0, - 0, - 0, - 0, - 0, - w32.HWND_MESSAGE, - 0, - w32.GetModuleHandle(""), - nil, - ) - - return hwnd -} diff --git a/v2/internal/frontend/desktop/windows/winc/README.md b/v2/internal/frontend/desktop/windows/winc/README.md index 4d4d467bd..7cd01fee2 100644 --- a/v2/internal/frontend/desktop/windows/winc/README.md +++ b/v2/internal/frontend/desktop/windows/winc/README.md @@ -1,10 +1,11 @@ # winc -** This is a fork of [tadvi/winc](https://github.com/tadvi/winc) for the sole purpose of integration -with [Wails](https://github.com/wailsapp/wails). This repository comes with ***no support*** ** +** This is a fork of [tadvi/winc](https://github.com/tadvi/winc) for the sole +purpose of integration with [Wails](https://github.com/wailsapp/wails). This +repository comes with \***no support**\* ** -Common library for Go GUI apps on Windows. It is for Windows OS only. This makes library smaller than some other UI -libraries for Go. +Common library for Go GUI apps on Windows. It is for Windows OS only. This makes +library smaller than some other UI libraries for Go. Design goals: minimalism and simplicity. @@ -14,9 +15,10 @@ No other dependencies except Go standard library. ## Building -If you want to package icon files and other resources into binary **rsrc** tool is recommended: +If you want to package icon files and other resources into binary **rsrc** tool +is recommended: - rsrc -manifest app.manifest -ico=app.ico,application_edit.ico,application_error.ico -o rsrc.syso + rsrc -manifest app.manifest -ico=app.ico,application_edit.ico,application_error.ico -o rsrc.syso Here app.manifest is XML file in format: @@ -32,19 +34,20 @@ Here app.manifest is XML file in format: ``` -Most Windows applications do not display command prompt. Build your Go project with flag to indicate that it is Windows -GUI binary: +Most Windows applications do not display command prompt. Build your Go project +with flag to indicate that it is Windows GUI binary: - go build -ldflags="-H windowsgui" + go build -ldflags="-H windowsgui" ## Samples -Best way to learn how to use the library is to look at the included **examples** projects. +Best way to learn how to use the library is to look at the included **examples** +projects. ## Setup -1. Make sure you have a working Go installation and build environment, see more for details on page below. - http://golang.org/doc/install +1. Make sure you have a working Go installation and build environment, see more + for details on page below. http://golang.org/doc/install 2. go get github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc @@ -69,38 +72,46 @@ const myIcon = 13 btn.SetResIcon(myIcon) // Set icon on the button. ``` -Included source **examples** use basic building via `release.bat` files. Note that icon IDs are order dependent. So if -you change they order in -ico flag then icon IDs will be different. If you want to keep order the same, just add new +Included source **examples** use basic building via `release.bat` files. Note +that icon IDs are order dependent. So if you change they order in -ico flag then +icon IDs will be different. If you want to keep order the same, just add new icons to the end of -ico comma separated list. ## Layout Manager SimpleDock is default layout manager. -Current design of docking and split views allows building simple apps but if you need to have multiple split views in -few different directions you might need to create your own layout manager. +Current design of docking and split views allows building simple apps but if you +need to have multiple split views in few different directions you might need to +create your own layout manager. -Important point is to have **one** control inside SimpleDock set to dock as **Fill**. Controls that are not set to any -docking get placed using SetPos() function. So you can have Panel set to dock at the Top and then have another dock to -arrange controls inside that Panel or have controls placed using SetPos() at fixed positions. +Important point is to have **one** control inside SimpleDock set to dock as +**Fill**. Controls that are not set to any docking get placed using SetPos() +function. So you can have Panel set to dock at the Top and then have another +dock to arrange controls inside that Panel or have controls placed using +SetPos() at fixed positions. ![Example layout with two toolbars and status bar](dock_topbottom.png) -This is basic layout. Instead of toolbars and status bar you can have Panel or any other control that can resize. Panel -can have its own internal Dock that will arrange other controls inside of it. +This is basic layout. Instead of toolbars and status bar you can have Panel or +any other control that can resize. Panel can have its own internal Dock that +will arrange other controls inside of it. ![Example layout with two toolbars and navigation on the left](dock_topleft.png) -This is layout with extra control(s) on the left. Left side is usually treeview or listview. +This is layout with extra control(s) on the left. Left side is usually treeview +or listview. -The rule is simple: you either dock controls using SimpleDock OR use SetPos() to set them at fixed positions. That's it. +The rule is simple: you either dock controls using SimpleDock OR use SetPos() to +set them at fixed positions. That's it. At some point **winc** may get more sophisticated layout manager. ## Dialog Screens -Dialog screens are not based on Windows resource files (.rc). They are just windows with controls placed at fixed -coordinates. This works fine for dialog screens up to 10-14 controls. +Dialog screens are not based on Windows resource files (.rc). They are just +windows with controls placed at fixed coordinates. This works fine for dialog +screens up to 10-14 controls. # Minimal Demo @@ -151,16 +162,18 @@ Result of running sample_minimal. ## Create Your Own -It is good practice to create your own controls based on existing structures and event model. Library contains some of -the controls built that way: IconButton (button.go), ErrorPanel (panel.go), MultiEdit (edit.go), etc. Please look at +It is good practice to create your own controls based on existing structures and +event model. Library contains some of the controls built that way: IconButton +(button.go), ErrorPanel (panel.go), MultiEdit (edit.go), etc. Please look at existing controls as examples before building your own. -When designing your own controls keep in mind that types have to be converted from Go into Win32 API and back. This is -usually due to string UTF8 and UTF16 conversions. But there are other types of conversions too. +When designing your own controls keep in mind that types have to be converted +from Go into Win32 API and back. This is usually due to string UTF8 and UTF16 +conversions. But there are other types of conversions too. When developing your own controls you might also need to: - import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" + import "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" w32 has Win32 API low level constants and functions. @@ -176,6 +189,5 @@ This library is built on [AllenDang/gform Windows GUI framework for Go](https://github.com/AllenDang/gform) -**winc** takes most design decisions from **gform** and adds many more controls and code samples to it. - - +**winc** takes most design decisions from **gform** and adds many more controls +and code samples to it. diff --git a/v2/internal/frontend/desktop/windows/winc/app.go b/v2/internal/frontend/desktop/windows/winc/app.go index 973880444..52b30f591 100644 --- a/v2/internal/frontend/desktop/windows/winc/app.go +++ b/v2/internal/frontend/desktop/windows/winc/app.go @@ -37,7 +37,7 @@ func init() { w32.InitCommonControlsEx(&initCtrls) } -// SetAppIcon sets resource icon ID for the apps windows. +// SetAppIconID sets recource icon ID for the apps windows. func SetAppIcon(appIconID int) { AppIconID = appIconID } diff --git a/v2/internal/frontend/desktop/windows/winc/combobox.go b/v2/internal/frontend/desktop/windows/winc/combobox.go index 380ea88d8..3b4348acb 100644 --- a/v2/internal/frontend/desktop/windows/winc/combobox.go +++ b/v2/internal/frontend/desktop/windows/winc/combobox.go @@ -54,7 +54,7 @@ func (cb *ComboBox) OnSelectedChange() *EventManager { return &cb.onSelectedChange } -// Message processor +// Message processer func (cb *ComboBox) WndProc(msg uint32, wparam, lparam uintptr) uintptr { switch msg { case w32.WM_COMMAND: diff --git a/v2/internal/frontend/desktop/windows/winc/controlbase.go b/v2/internal/frontend/desktop/windows/winc/controlbase.go index cdb29518c..b745cb1b0 100644 --- a/v2/internal/frontend/desktop/windows/winc/controlbase.go +++ b/v2/internal/frontend/desktop/windows/winc/controlbase.go @@ -65,7 +65,7 @@ type ControlBase struct { dispatchq []func() } -// InitControl is called by controls: edit, button, treeview, listview, and so on. +// initControl is called by controls: edit, button, treeview, listview, and so on. func (cba *ControlBase) InitControl(className string, parent Controller, exstyle, style uint) { cba.hwnd = CreateWindow(className, parent, exstyle, style) if cba.hwnd == 0 { @@ -170,14 +170,6 @@ func (cba *ControlBase) SetTranslucentBackground() { w32.SetWindowCompositionAttribute(cba.hwnd, &data) } -func (cba *ControlBase) SetContentProtection(enable bool) { - if enable { - w32.SetWindowDisplayAffinity(uintptr(cba.hwnd), w32.WDA_EXCLUDEFROMCAPTURE) - } else { - w32.SetWindowDisplayAffinity(uintptr(cba.hwnd), w32.WDA_NONE) - } -} - func min(a, b int) int { if a < b { return a @@ -342,23 +334,7 @@ func (cba *ControlBase) ClientHeight() int { } func (cba *ControlBase) Show() { - // WindowPos is used with HWND_TOPMOST to guarantee bring our app on top - // force set our main window on top - w32.SetWindowPos( - cba.hwnd, - w32.HWND_TOPMOST, - 0, 0, 0, 0, - w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE, - ) - // remove topmost to allow normal windows manipulations - w32.SetWindowPos( - cba.hwnd, - w32.HWND_NOTOPMOST, - 0, 0, 0, 0, - w32.SWP_SHOWWINDOW|w32.SWP_NOSIZE|w32.SWP_NOMOVE, - ) - // put main window on tops foreground - w32.SetForegroundWindow(cba.hwnd) + w32.ShowWindow(cba.hwnd, w32.SW_SHOWDEFAULT) } func (cba *ControlBase) Hide() { diff --git a/v2/internal/frontend/desktop/windows/winc/form.go b/v2/internal/frontend/desktop/windows/winc/form.go index c9acf7278..9b9cadb2c 100644 --- a/v2/internal/frontend/desktop/windows/winc/form.go +++ b/v2/internal/frontend/desktop/windows/winc/form.go @@ -136,16 +136,7 @@ func (fm *Form) Minimise() { } func (fm *Form) Restore() { - // SC_RESTORE param for WM_SYSCOMMAND to restore app if it is minimized - const SC_RESTORE = 0xF120 - // restore the minimized window, if it is - w32.SendMessage( - fm.hwnd, - w32.WM_SYSCOMMAND, - SC_RESTORE, - 0, - ) - w32.ShowWindow(fm.hwnd, w32.SW_SHOW) + w32.ShowWindow(fm.hwnd, w32.SW_RESTORE) } // Public methods diff --git a/v2/internal/frontend/desktop/windows/winc/icon.go b/v2/internal/frontend/desktop/windows/winc/icon.go index 94e9198d6..6a3e1a391 100644 --- a/v2/internal/frontend/desktop/windows/winc/icon.go +++ b/v2/internal/frontend/desktop/windows/winc/icon.go @@ -10,86 +10,11 @@ package winc import ( "errors" "fmt" - "image" - "image/png" - "os" "syscall" - "unsafe" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" ) -var ( - user32 = syscall.NewLazyDLL("user32.dll") - gdi32 = syscall.NewLazyDLL("gdi32.dll") - procGetIconInfo = user32.NewProc("GetIconInfo") - procDeleteObject = gdi32.NewProc("DeleteObject") - procGetObject = gdi32.NewProc("GetObjectW") - procGetDIBits = gdi32.NewProc("GetDIBits") - procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC") - procSelectObject = gdi32.NewProc("SelectObject") - procDeleteDC = gdi32.NewProc("DeleteDC") -) - -func init() { - // Validate DLL loads at initialization time to surface missing APIs early - if err := user32.Load(); err != nil { - panic(fmt.Sprintf("failed to load user32.dll: %v", err)) - } - if err := gdi32.Load(); err != nil { - panic(fmt.Sprintf("failed to load gdi32.dll: %v", err)) - } -} - -// ICONINFO mirrors the Win32 ICONINFO struct -type ICONINFO struct { - FIcon int32 - XHotspot uint32 - YHotspot uint32 - HbmMask uintptr - HbmColor uintptr -} - -// 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 Icon struct { handle w32.HICON } @@ -121,95 +46,6 @@ func ExtractIcon(fileName string, index int) (*Icon, error) { return ico, err } -func SaveHIconAsPNG(hIcon w32.HICON, filePath string) error { - // Get icon info - var iconInfo ICONINFO - ret, _, err := procGetIconInfo.Call( - uintptr(hIcon), - uintptr(unsafe.Pointer(&iconInfo)), - ) - if ret == 0 { - return err - } - defer procDeleteObject.Call(uintptr(iconInfo.HbmMask)) - defer procDeleteObject.Call(uintptr(iconInfo.HbmColor)) - - // Get bitmap info - var bmp BITMAP - ret, _, err = procGetObject.Call( - uintptr(iconInfo.HbmColor), - unsafe.Sizeof(bmp), - uintptr(unsafe.Pointer(&bmp)), - ) - if ret == 0 { - return err - } - - // Get screen DC for GetDIBits (bitmap must not be selected into a DC) - screenDC := w32.GetDC(0) - if screenDC == 0 { - return fmt.Errorf("failed to get screen DC") - } - defer w32.ReleaseDC(0, screenDC) - - // 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 = w32.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 using screen DC (bitmap must not be selected into any DC) - ret, _, err = procGetDIBits.Call( - uintptr(screenDC), - uintptr(iconInfo.HbmColor), - 0, - uintptr(bmp.BmHeight), - uintptr(unsafe.Pointer(&bits[0])), - uintptr(unsafe.Pointer(&bi)), - w32.DIB_RGB_COLORS, - ) - if ret == 0 { - return fmt.Errorf("failed to get bitmap bits: %w", 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 - // RGBA image is top-down - imgIndex := (y*width + x) * 4 - - // BGRA to RGBA - img.Pix[imgIndex] = bits[dibIndex+2] // R - img.Pix[imgIndex+1] = bits[dibIndex+1] // G - img.Pix[imgIndex+2] = bits[dibIndex] // B - img.Pix[imgIndex+3] = bits[dibIndex+3] // 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 (ic *Icon) Destroy() bool { return w32.DestroyIcon(ic.handle) } diff --git a/v2/internal/frontend/desktop/windows/winc/layout.go b/v2/internal/frontend/desktop/windows/winc/layout.go index 7962dc726..da9895205 100644 --- a/v2/internal/frontend/desktop/windows/winc/layout.go +++ b/v2/internal/frontend/desktop/windows/winc/layout.go @@ -65,7 +65,7 @@ type SimpleDock struct { loadedState bool } -// CtlState gets saved and loaded from json +// DockState gets saved and loaded from json type CtlState struct { X, Y, Width, Height int } diff --git a/v2/internal/frontend/desktop/windows/winc/listview.go b/v2/internal/frontend/desktop/windows/winc/listview.go index 8edfd1c11..c98fc4c62 100644 --- a/v2/internal/frontend/desktop/windows/winc/listview.go +++ b/v2/internal/frontend/desktop/windows/winc/listview.go @@ -438,7 +438,7 @@ func (lv *ListView) OnEndScroll() *EventManager { return &lv.onEndScroll } -// Message processor +// Message processer func (lv *ListView) WndProc(msg uint32, wparam, lparam uintptr) uintptr { switch msg { /*case w32.WM_ERASEBKGND: diff --git a/v2/internal/frontend/desktop/windows/winc/treeview.go b/v2/internal/frontend/desktop/windows/winc/treeview.go index 2cdc0e936..9118f3d05 100644 --- a/v2/internal/frontend/desktop/windows/winc/treeview.go +++ b/v2/internal/frontend/desktop/windows/winc/treeview.go @@ -248,7 +248,7 @@ func (tv *TreeView) OnViewChange() *EventManager { return &tv.onViewChange } -// Message processor +// Message processer func (tv *TreeView) WndProc(msg uint32, wparam, lparam uintptr) uintptr { switch msg { case w32.WM_NOTIFY: diff --git a/v2/internal/frontend/desktop/windows/winc/w32/user32.go b/v2/internal/frontend/desktop/windows/winc/w32/user32.go index 707701f5e..17e0d9991 100644 --- a/v2/internal/frontend/desktop/windows/winc/w32/user32.go +++ b/v2/internal/frontend/desktop/windows/winc/w32/user32.go @@ -24,7 +24,6 @@ var ( procShowWindowAsync = moduser32.NewProc("ShowWindowAsync") procUpdateWindow = moduser32.NewProc("UpdateWindow") procCreateWindowEx = moduser32.NewProc("CreateWindowExW") - procFindWindowW = moduser32.NewProc("FindWindowW") procAdjustWindowRect = moduser32.NewProc("AdjustWindowRect") procAdjustWindowRectEx = moduser32.NewProc("AdjustWindowRectEx") procDestroyWindow = moduser32.NewProc("DestroyWindow") @@ -264,14 +263,6 @@ func CreateWindowEx(exStyle uint, className, windowName *uint16, return HWND(ret) } -func FindWindowW(className, windowName *uint16) HWND { - ret, _, _ := procFindWindowW.Call( - uintptr(unsafe.Pointer(className)), - uintptr(unsafe.Pointer(windowName))) - - return HWND(ret) -} - func AdjustWindowRectEx(rect *RECT, style uint, menu bool, exStyle uint) bool { ret, _, _ := procAdjustWindowRectEx.Call( uintptr(unsafe.Pointer(rect)), @@ -639,7 +630,7 @@ func GetSysColorBrush(nIndex int) HBRUSH { return HBRUSH(ret) */ - ret, _, _ := syscall.SyscallN(getSysColorBrush, + ret, _, _ := syscall.Syscall(getSysColorBrush, 1, uintptr(nIndex), 0, 0) @@ -792,9 +783,11 @@ func CreateMenu() HMENU { } func SetMenu(hWnd HWND, hMenu HMENU) bool { - ret, _, _ := syscall.SyscallN(setMenu, + ret, _, _ := syscall.Syscall(setMenu, 2, uintptr(hWnd), - uintptr(hMenu)) + uintptr(hMenu), + 0) + return ret != 0 } @@ -832,7 +825,11 @@ func TrackPopupMenuEx(hMenu HMENU, fuFlags uint32, x, y int32, hWnd HWND, lptpm } func DrawMenuBar(hWnd HWND) bool { - ret, _, _ := syscall.SyscallN(drawMenuBar, hWnd) + ret, _, _ := syscall.Syscall(drawMenuBar, 1, + uintptr(hWnd), + 0, + 0) + return ret != 0 } @@ -1026,11 +1023,9 @@ func EndPaint(hwnd HWND, paint *PAINTSTRUCT) { 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]))) +func GetKeyboardState(lpKeyState *[]byte) bool { + ret, _, _ := procGetKeyboardState.Call( + uintptr(unsafe.Pointer(&(*lpKeyState)[0]))) return ret != 0 } @@ -1225,8 +1220,11 @@ func CallNextHookEx(hhk HHOOK, nCode int, wParam WPARAM, lParam LPARAM) LRESULT } func GetKeyState(nVirtKey int32) int16 { - ret, _, _ := syscall.SyscallN(getKeyState, - uintptr(nVirtKey)) + ret, _, _ := syscall.Syscall(getKeyState, 1, + uintptr(nVirtKey), + 0, + 0) + return int16(ret) } @@ -1240,15 +1238,17 @@ func DestroyMenu(hMenu HMENU) bool { } func GetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool { - ret, _, _ := syscall.SyscallN(getWindowPlacement, - hWnd, - uintptr(unsafe.Pointer(lpwndpl))) + ret, _, _ := syscall.Syscall(getWindowPlacement, 2, + uintptr(hWnd), + uintptr(unsafe.Pointer(lpwndpl)), + 0) + return ret != 0 } func SetWindowPlacement(hWnd HWND, lpwndpl *WINDOWPLACEMENT) bool { - ret, _, _ := syscall.SyscallN(setWindowPlacement, - hWnd, + ret, _, _ := syscall.Syscall(setWindowPlacement, 2, + uintptr(hWnd), uintptr(unsafe.Pointer(lpwndpl)), 0) @@ -1268,7 +1268,7 @@ func SetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO, fRedraw bool) int32 } func GetScrollInfo(hwnd HWND, fnBar int32, lpsi *SCROLLINFO) bool { - ret, _, _ := syscall.SyscallN(getScrollInfo, + ret, _, _ := syscall.Syscall(getScrollInfo, 3, hwnd, uintptr(fnBar), uintptr(unsafe.Pointer(lpsi))) diff --git a/v2/internal/frontend/desktop/windows/winc/w32/utils.go b/v2/internal/frontend/desktop/windows/winc/w32/utils.go index 4568b4849..8a72d4846 100644 --- a/v2/internal/frontend/desktop/windows/winc/w32/utils.go +++ b/v2/internal/frontend/desktop/windows/winc/w32/utils.go @@ -75,7 +75,7 @@ func UTF16PtrToString(cstr *uint16) string { } func ComAddRef(unknown *IUnknown) int32 { - ret, _, _ := syscall.SyscallN(unknown.lpVtbl.pAddRef, + ret, _, _ := syscall.Syscall(unknown.lpVtbl.pAddRef, 1, uintptr(unsafe.Pointer(unknown)), 0, 0) @@ -83,7 +83,7 @@ func ComAddRef(unknown *IUnknown) int32 { } func ComRelease(unknown *IUnknown) int32 { - ret, _, _ := syscall.SyscallN(unknown.lpVtbl.pRelease, + ret, _, _ := syscall.Syscall(unknown.lpVtbl.pRelease, 1, uintptr(unsafe.Pointer(unknown)), 0, 0) @@ -92,7 +92,7 @@ func ComRelease(unknown *IUnknown) int32 { func ComQueryInterface(unknown *IUnknown, id *GUID) *IDispatch { var disp *IDispatch - hr, _, _ := syscall.SyscallN(unknown.lpVtbl.pQueryInterface, + hr, _, _ := syscall.Syscall(unknown.lpVtbl.pQueryInterface, 3, uintptr(unsafe.Pointer(unknown)), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(&disp))) diff --git a/v2/internal/frontend/desktop/windows/winc/w32/uxtheme.go b/v2/internal/frontend/desktop/windows/winc/w32/uxtheme.go index 8a14f0cb7..51ec0035f 100644 --- a/v2/internal/frontend/desktop/windows/winc/w32/uxtheme.go +++ b/v2/internal/frontend/desktop/windows/winc/w32/uxtheme.go @@ -69,7 +69,7 @@ var ( setWindowTheme uintptr ) -func Init() { +func init() { // Library libuxtheme = MustLoadLibrary("uxtheme.dll") @@ -83,7 +83,7 @@ func Init() { } func CloseThemeData(hTheme HTHEME) HRESULT { - ret, _, _ := syscall.SyscallN(closeThemeData, + ret, _, _ := syscall.Syscall(closeThemeData, 1, uintptr(hTheme), 0, 0) @@ -134,7 +134,7 @@ func GetThemeTextExtent(hTheme HTHEME, hdc HDC, iPartId, iStateId int32, pszText } func OpenThemeData(hwnd HWND, pszClassList *uint16) HTHEME { - ret, _, _ := syscall.SyscallN(openThemeData, + ret, _, _ := syscall.Syscall(openThemeData, 2, uintptr(hwnd), uintptr(unsafe.Pointer(pszClassList)), 0) @@ -143,7 +143,7 @@ func OpenThemeData(hwnd HWND, pszClassList *uint16) HTHEME { } func SetWindowTheme(hwnd HWND, pszSubAppName, pszSubIdList *uint16) HRESULT { - ret, _, _ := syscall.SyscallN(setWindowTheme, + ret, _, _ := syscall.Syscall(setWindowTheme, 3, uintptr(hwnd), uintptr(unsafe.Pointer(pszSubAppName)), uintptr(unsafe.Pointer(pszSubIdList))) diff --git a/v2/internal/frontend/desktop/windows/winc/w32/wda.go b/v2/internal/frontend/desktop/windows/winc/w32/wda.go deleted file mode 100644 index 3925f2805..000000000 --- a/v2/internal/frontend/desktop/windows/winc/w32/wda.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build windows - -package w32 - -import ( - "syscall" - - "github.com/wailsapp/wails/v2/internal/system/operatingsystem" -) - -var user32 = syscall.NewLazyDLL("user32.dll") -var procSetWindowDisplayAffinity = user32.NewProc("SetWindowDisplayAffinity") -var windowsVersion, _ = operatingsystem.GetWindowsVersionInfo() - -const ( - WDA_NONE = 0x00000000 - WDA_MONITOR = 0x00000001 - WDA_EXCLUDEFROMCAPTURE = 0x00000011 // windows 10 2004+ -) - -func isWindowsVersionAtLeast(major, minor, build int) bool { - if windowsVersion.Major > major { - return true - } - if windowsVersion.Major < major { - return false - } - if windowsVersion.Minor > minor { - return true - } - if windowsVersion.Minor < minor { - return false - } - return windowsVersion.Build >= build -} - -func SetWindowDisplayAffinity(hwnd uintptr, affinity uint32) bool { - if affinity == WDA_EXCLUDEFROMCAPTURE && !isWindowsVersionAtLeast(10, 0, 19041) { - // for older windows versions, use WDA_MONITOR - affinity = WDA_MONITOR - } - ret, _, _ := procSetWindowDisplayAffinity.Call( - hwnd, - uintptr(affinity), - ) - return ret != 0 -} diff --git a/v2/internal/frontend/desktop/windows/window.go b/v2/internal/frontend/desktop/windows/window.go index b04d61814..43ec57c72 100644 --- a/v2/internal/frontend/desktop/windows/window.go +++ b/v2/internal/frontend/desktop/windows/window.go @@ -6,7 +6,7 @@ import ( "sync" "unsafe" - "github.com/wailsapp/go-webview2/pkg/edge" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32" "github.com/wailsapp/wails/v2/internal/system/operatingsystem" @@ -38,13 +38,6 @@ type Window struct { OnResume func() chromium *edge.Chromium - - // isMinimizing indicates whether the window is currently being minimized - // 标识窗口是否处于最小化状态,用于解决最小化/恢复时的闪屏问题 - // This flag is used to prevent unnecessary redraws during minimize/restore transitions for frameless windows - // 此标志用于防止无边框窗口在最小化/恢复过程中的不必要重绘 - // Reference: https://github.com/wailsapp/wails/issues/3951 - isMinimizing bool } func NewWindow(parent winc.Controller, appoptions *options.App, versionInfo *operatingsystem.WindowsVersionInfo, chromium *edge.Chromium) *Window { @@ -78,13 +71,8 @@ func NewWindow(parent winc.Controller, appoptions *options.App, versionInfo *ope var dwStyle = w32.WS_OVERLAPPEDWINDOW - windowClassName := "wailsWindow" - if windowsOptions != nil && windowsOptions.WindowClassName != "" { - windowClassName = windowsOptions.WindowClassName - } - - winc.RegClassOnlyOnce(windowClassName) - handle := winc.CreateWindow(windowClassName, parent, uint(exStyle), uint(dwStyle)) + winc.RegClassOnlyOnce("wailsWindow") + handle := winc.CreateWindow("wailsWindow", parent, uint(exStyle), uint(dwStyle)) result.SetHandle(handle) winc.RegMsgHandler(result) result.SetParent(parent) @@ -131,10 +119,6 @@ func NewWindow(parent winc.Controller, appoptions *options.App, versionInfo *ope } } - if windowsOptions.ContentProtection { - w32.SetWindowDisplayAffinity(result.Handle(), w32.WDA_EXCLUDEFROMCAPTURE) - } - if windowsOptions.DisableWindowIcon { result.DisableIcon() } @@ -268,7 +252,7 @@ func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr { rgrc := (*w32.RECT)(unsafe.Pointer(lparam)) if w.Form.IsFullScreen() { // In Full-Screen mode we don't need to adjust anything - w.SetPadding(edge.Rect{}) + w.chromium.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. @@ -299,7 +283,7 @@ func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr { } } } - w.SetPadding(edge.Rect{}) + w.chromium.SetPadding(edge.Rect{}) } else { // This is needed to workaround the resize flickering in frameless mode with WindowDecorations // See: https://stackoverflow.com/a/6558508 @@ -308,7 +292,7 @@ func (w *Window) WndProc(msg uint32, wparam, lparam uintptr) uintptr { // Increasing the bottom also worksaround the flickering but we would loose 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}) + w.chromium.SetPadding(edge.Rect{Bottom: 1}) } return 0 } @@ -351,17 +335,3 @@ func invokeSync[T any](cba *Window, fn func() (T, error)) (res T, err error) { wg.Wait() return res, err } - -// SetPadding is a filter that wraps chromium.SetPadding to prevent unnecessary redraws during minimize/restore -// 包装了chromium.SetPadding的过滤器,用于防止窗口最小化/恢复过程中的不必要重绘 -// This fixes window flickering when minimizing/restoring frameless windows -// 这修复了无边框窗口在最小化/恢复时的闪烁问题 -// Reference: https://github.com/wailsapp/wails/issues/3951 -func (w *Window) SetPadding(padding edge.Rect) { - // Skip SetPadding if window is being minimized to prevent flickering - // 如果窗口正在最小化,跳过设置padding以防止闪烁 - if w.isMinimizing { - return - } - w.chromium.SetPadding(padding) -} diff --git a/v2/internal/frontend/devserver/devserver.go b/v2/internal/frontend/devserver/devserver.go index 8a130890d..3d623ed6d 100644 --- a/v2/internal/frontend/devserver/devserver.go +++ b/v2/internal/frontend/devserver/devserver.go @@ -20,23 +20,17 @@ import ( "github.com/wailsapp/wails/v2/internal/frontend/runtime" - "github.com/gorilla/websocket" "github.com/labstack/echo/v4" "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/menumanager" "github.com/wailsapp/wails/v2/pkg/options" + "golang.org/x/net/websocket" ) type Screen = frontend.Screen -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { return true }, -} - type DevWebServer struct { server *echo.Echo ctx context.Context @@ -161,64 +155,51 @@ func (d *DevWebServer) handleReloadApp(c echo.Context) error { } func (d *DevWebServer) handleIPCWebSocket(c echo.Context) error { - conn, err := upgrader.Upgrade(c.Response(), c.Request(), nil) - if err != nil { - d.logger.Error("WebSocket upgrade failed %v", err) - return err - } - d.LogDebug(fmt.Sprintf("WebSocket client %p connected", conn)) - - d.socketMutex.Lock() - d.websocketClients[conn] = &sync.Mutex{} - locker := d.websocketClients[conn] - d.socketMutex.Unlock() - - var wg sync.WaitGroup - - defer func() { - wg.Wait() + websocket.Handler(func(c *websocket.Conn) { + d.LogDebug(fmt.Sprintf("Websocket client %p connected", c)) d.socketMutex.Lock() - delete(d.websocketClients, conn) + d.websocketClients[c] = &sync.Mutex{} + locker := d.websocketClients[c] d.socketMutex.Unlock() - d.LogDebug(fmt.Sprintf("WebSocket client %p disconnected", conn)) - conn.Close() - }() - for { - _, msgBytes, err := conn.ReadMessage() - if err != nil { - break - } + defer func() { + d.socketMutex.Lock() + delete(d.websocketClients, c) + d.socketMutex.Unlock() + d.LogDebug(fmt.Sprintf("Websocket client %p disconnected", c)) + }() - msg := string(msgBytes) - wg.Add(1) - - go func(m string) { - defer wg.Done() - - if m == "drag" { - return + var msg string + defer c.Close() + for { + if err := websocket.Message.Receive(c, &msg); err != nil { + break + } + // We do not support drag in browsers + if msg == "drag" { + continue } - if len(m) > 2 && strings.HasPrefix(m, "EE") { - d.notifyExcludingSender([]byte(m), conn) + // Notify the other browsers of "EventEmit" + if len(msg) > 2 && strings.HasPrefix(string(msg), "EE") { + d.notifyExcludingSender([]byte(msg), c) } - result, err := d.dispatcher.ProcessMessage(m, d) + // Send the message to dispatch to the frontend + result, err := d.dispatcher.ProcessMessage(string(msg), d) if err != nil { d.logger.Error(err.Error()) } - if result != "" { locker.Lock() - defer locker.Unlock() - if err := conn.WriteMessage(websocket.TextMessage, []byte(result)); err != nil { - d.logger.Error("Websocket write message failed %v", err) + if err = websocket.Message.Send(c, result); err != nil { + locker.Unlock() + break } + locker.Unlock() } - }(msg) - } - + } + }).ServeHTTP(c.Response(), c.Request()) return nil } @@ -241,7 +222,7 @@ func (d *DevWebServer) broadcast(message string) { return } locker.Lock() - err := client.WriteMessage(websocket.TextMessage, []byte(message)) + err := websocket.Message.Send(client, message) if err != nil { locker.Unlock() d.logger.Error(err.Error()) @@ -275,7 +256,7 @@ func (d *DevWebServer) broadcastExcludingSender(message string, sender *websocke return } locker.Lock() - err := client.WriteMessage(websocket.TextMessage, []byte(message)) + err := websocket.Message.Send(client, message) if err != nil { locker.Unlock() d.logger.Error(err.Error()) diff --git a/v2/internal/frontend/dispatcher/calls.go b/v2/internal/frontend/dispatcher/calls.go index ba1062913..ef19c6030 100644 --- a/v2/internal/frontend/dispatcher/calls.go +++ b/v2/internal/frontend/dispatcher/calls.go @@ -3,9 +3,8 @@ package dispatcher import ( "encoding/json" "fmt" - "strings" - "github.com/wailsapp/wails/v2/internal/frontend" + "strings" ) type callMessage struct { @@ -15,6 +14,7 @@ type callMessage struct { } func (d *Dispatcher) processCallMessage(message string, sender frontend.Frontend) (string, error) { + var payload callMessage err := json.Unmarshal([]byte(message[1:]), &payload) if err != nil { @@ -49,12 +49,7 @@ func (d *Dispatcher) processCallMessage(message string, sender frontend.Frontend CallbackID: payload.CallbackID, } if err != nil { - // Use the error formatter if one was provided - if d.errfmt != nil { - callbackMessage.Err = d.errfmt(err) - } else { - callbackMessage.Err = err.Error() - } + callbackMessage.Err = err.Error() } else { callbackMessage.Result = result } @@ -71,7 +66,7 @@ func (d *Dispatcher) processCallMessage(message string, sender frontend.Frontend // CallbackMessage defines a message that contains the result of a call type CallbackMessage struct { Result interface{} `json:"result"` - Err any `json:"error"` + Err string `json:"error"` CallbackID string `json:"callbackid"` } diff --git a/v2/internal/frontend/dispatcher/dispatcher.go b/v2/internal/frontend/dispatcher/dispatcher.go index 24a43cfef..44a8886e5 100644 --- a/v2/internal/frontend/dispatcher/dispatcher.go +++ b/v2/internal/frontend/dispatcher/dispatcher.go @@ -2,52 +2,32 @@ package dispatcher import ( "context" - "fmt" + "github.com/pkg/errors" "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/logger" - "github.com/wailsapp/wails/v2/pkg/options" ) type Dispatcher struct { - log *logger.Logger - bindings *binding.Bindings - events frontend.Events - bindingsDB *binding.DB - ctx context.Context - errfmt options.ErrorFormatter - disablePanicRecovery bool + log *logger.Logger + bindings *binding.Bindings + events frontend.Events + bindingsDB *binding.DB + ctx context.Context } -func NewDispatcher(ctx context.Context, log *logger.Logger, bindings *binding.Bindings, events frontend.Events, errfmt options.ErrorFormatter, disablePanicRecovery bool) *Dispatcher { +func NewDispatcher(ctx context.Context, log *logger.Logger, bindings *binding.Bindings, events frontend.Events) *Dispatcher { return &Dispatcher{ - log: log, - bindings: bindings, - events: events, - bindingsDB: bindings.DB(), - ctx: ctx, - errfmt: errfmt, - disablePanicRecovery: disablePanicRecovery, + log: log, + bindings: bindings, + events: events, + bindingsDB: bindings.DB(), + ctx: ctx, } } -func (d *Dispatcher) ProcessMessage(message string, sender frontend.Frontend) (_ string, err error) { - if !d.disablePanicRecovery { - defer func() { - if e := recover(); e != nil { - if errPanic, ok := e.(error); ok { - err = errPanic - } else { - err = fmt.Errorf("%v", e) - } - } - if err != nil { - d.log.Error("process message error: %s -> %s", message, err) - } - }() - } - +func (d *Dispatcher) ProcessMessage(message string, sender frontend.Frontend) (string, error) { if message == "" { return "", errors.New("No message to process") } @@ -64,8 +44,6 @@ func (d *Dispatcher) ProcessMessage(message string, sender frontend.Frontend) (_ return d.processWindowMessage(message, sender) case 'B': return d.processBrowserMessage(message, sender) - case 'D': - return d.processDragAndDropMessage(message) case 'Q': sender.Quit() return "", nil diff --git a/v2/internal/frontend/dispatcher/draganddrop.go b/v2/internal/frontend/dispatcher/draganddrop.go deleted file mode 100644 index 8266ec712..000000000 --- a/v2/internal/frontend/dispatcher/draganddrop.go +++ /dev/null @@ -1,38 +0,0 @@ -package dispatcher - -import ( - "errors" - "strconv" - "strings" -) - -func (d *Dispatcher) processDragAndDropMessage(message string) (string, error) { - switch message[1] { - case 'D': - msg := strings.SplitN(message[3:], ":", 3) - if len(msg) != 3 { - return "", errors.New("Invalid drag and drop Message: " + message) - } - - x, err := strconv.Atoi(msg[0]) - if err != nil { - return "", errors.New("Invalid x coordinate in drag and drop Message: " + message) - } - - y, err := strconv.Atoi(msg[1]) - if err != nil { - return "", errors.New("Invalid y coordinate in drag and drop Message: " + message) - } - - paths := strings.Split(msg[2], "\n") - if len(paths) < 1 { - return "", errors.New("Invalid drag and drop Message: " + message) - } - - d.events.Emit("wails:file-drop", x, y, paths) - default: - return "", errors.New("Invalid drag and drop Message: " + message) - } - - return "", nil -} diff --git a/v2/internal/frontend/dispatcher/events.go b/v2/internal/frontend/dispatcher/events.go index 12fe7b89e..11680651b 100644 --- a/v2/internal/frontend/dispatcher/events.go +++ b/v2/internal/frontend/dispatcher/events.go @@ -3,7 +3,6 @@ package dispatcher import ( "encoding/json" "errors" - "github.com/wailsapp/wails/v2/internal/frontend" ) diff --git a/v2/internal/frontend/dispatcher/securecalls.go b/v2/internal/frontend/dispatcher/securecalls.go index 8cdcdfb85..40accaa26 100644 --- a/v2/internal/frontend/dispatcher/securecalls.go +++ b/v2/internal/frontend/dispatcher/securecalls.go @@ -3,7 +3,6 @@ package dispatcher import ( "encoding/json" "fmt" - "github.com/wailsapp/wails/v2/internal/frontend" ) @@ -14,6 +13,7 @@ type secureCallMessage struct { } func (d *Dispatcher) processSecureCallMessage(message string, sender frontend.Frontend) (string, error) { + var payload secureCallMessage err := json.Unmarshal([]byte(message[1:]), &payload) if err != nil { diff --git a/v2/internal/frontend/dispatcher/systemcalls.go b/v2/internal/frontend/dispatcher/systemcalls.go index a13eb03b9..b810d9bea 100644 --- a/v2/internal/frontend/dispatcher/systemcalls.go +++ b/v2/internal/frontend/dispatcher/systemcalls.go @@ -4,9 +4,8 @@ import ( "encoding/json" "errors" "fmt" - "strings" - "github.com/wailsapp/wails/v2/pkg/runtime" + "strings" "github.com/wailsapp/wails/v2/internal/frontend" ) @@ -24,6 +23,7 @@ type size struct { } func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Frontend) (interface{}, error) { + // Strip prefix name := strings.TrimPrefix(payload.Name, systemCallPrefix) @@ -61,103 +61,8 @@ func (d *Dispatcher) processSystemCall(payload callMessage, sender frontend.Fron return false, err } return true, nil - case "InitializeNotifications": - err := sender.InitializeNotifications() - return nil, err - case "CleanupNotifications": - sender.CleanupNotifications() - return nil, nil - case "IsNotificationAvailable": - return sender.IsNotificationAvailable(), nil - case "RequestNotificationAuthorization": - authorized, err := sender.RequestNotificationAuthorization() - if err != nil { - return nil, err - } - return authorized, nil - case "CheckNotificationAuthorization": - authorized, err := sender.CheckNotificationAuthorization() - if err != nil { - return nil, err - } - return authorized, nil - case "SendNotification": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot send notification") - } - var options frontend.NotificationOptions - if err := json.Unmarshal(payload.Args[0], &options); err != nil { - return nil, err - } - err := sender.SendNotification(options) - return nil, err - case "SendNotificationWithActions": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot send notification") - } - var options frontend.NotificationOptions - if err := json.Unmarshal(payload.Args[0], &options); err != nil { - return nil, err - } - err := sender.SendNotificationWithActions(options) - return nil, err - case "RegisterNotificationCategory": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot register category") - } - var category frontend.NotificationCategory - if err := json.Unmarshal(payload.Args[0], &category); err != nil { - return nil, err - } - err := sender.RegisterNotificationCategory(category) - return nil, err - case "RemoveNotificationCategory": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot remove category") - } - var categoryId string - if err := json.Unmarshal(payload.Args[0], &categoryId); err != nil { - return nil, err - } - err := sender.RemoveNotificationCategory(categoryId) - return nil, err - case "RemoveAllPendingNotifications": - err := sender.RemoveAllPendingNotifications() - return nil, err - case "RemovePendingNotification": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot remove notification") - } - var identifier string - if err := json.Unmarshal(payload.Args[0], &identifier); err != nil { - return nil, err - } - err := sender.RemovePendingNotification(identifier) - return nil, err - case "RemoveAllDeliveredNotifications": - err := sender.RemoveAllDeliveredNotifications() - return nil, err - case "RemoveDeliveredNotification": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot remove notification") - } - var identifier string - if err := json.Unmarshal(payload.Args[0], &identifier); err != nil { - return nil, err - } - err := sender.RemoveDeliveredNotification(identifier) - return nil, err - case "RemoveNotification": - if len(payload.Args) < 1 { - return nil, errors.New("empty argument, cannot remove notification") - } - var identifier string - if err := json.Unmarshal(payload.Args[0], &identifier); err != nil { - return nil, err - } - err := sender.RemoveNotification(identifier) - return nil, err default: return nil, fmt.Errorf("unknown systemcall message: %s", payload.Name) } + } diff --git a/v2/internal/frontend/frontend.go b/v2/internal/frontend/frontend.go index 873b61dc7..79ee254c2 100644 --- a/v2/internal/frontend/frontend.go +++ b/v2/internal/frontend/frontend.go @@ -48,21 +48,8 @@ const ( type Screen struct { IsCurrent bool `json:"isCurrent"` IsPrimary bool `json:"isPrimary"` - - // Deprecated: Please use Size and PhysicalSize - Width int `json:"width"` - // Deprecated: Please use Size and PhysicalSize - Height int `json:"height"` - - // Size is the size of the screen in logical pixel space, used when setting sizes in Wails - Size ScreenSize `json:"size"` - // PhysicalSize is the physical size of the screen in pixels - PhysicalSize ScreenSize `json:"physicalSize"` -} - -type ScreenSize struct { - Width int `json:"width"` - Height int `json:"height"` + Width int `json:"width"` + Height int `json:"height"` } // MessageDialogOptions contains the options for the Message dialogs, EG Info, Warning, etc runtime methods @@ -76,53 +63,8 @@ type MessageDialogOptions struct { Icon []byte } -// 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"` -} - -// 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"` -} - -// 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:"categoryId,omitempty"` // Consistent with NotificationOptions - 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 -} - type Frontend interface { - Run(ctx context.Context) error + Run(context.Context) error RunMainLoop() ExecJS(js string) Hide() @@ -166,9 +108,8 @@ type Frontend interface { WindowIsNormal() bool WindowIsFullscreen() bool WindowClose() - WindowPrint() - // Screen + //Screen ScreenGetAll() ([]Screen, error) // Menus @@ -184,21 +125,4 @@ type Frontend interface { // Clipboard ClipboardGetText() (string, error) ClipboardSetText(text string) error - - // Notifications - InitializeNotifications() error - CleanupNotifications() - IsNotificationAvailable() bool - RequestNotificationAuthorization() (bool, error) - CheckNotificationAuthorization() (bool, error) - OnNotificationResponse(callback func(result NotificationResult)) - SendNotification(options NotificationOptions) error - SendNotificationWithActions(options NotificationOptions) error - RegisterNotificationCategory(category NotificationCategory) error - RemoveNotificationCategory(categoryId string) error - RemoveAllPendingNotifications() error - RemovePendingNotification(identifier string) error - RemoveAllDeliveredNotifications() error - RemoveDeliveredNotification(identifier string) error - RemoveNotification(identifier string) error } diff --git a/v2/internal/frontend/originvalidator/originValidator.go b/v2/internal/frontend/originvalidator/originValidator.go deleted file mode 100644 index fd416f945..000000000 --- a/v2/internal/frontend/originvalidator/originValidator.go +++ /dev/null @@ -1,116 +0,0 @@ -package originvalidator - -import ( - "fmt" - "net/url" - "regexp" - "strings" -) - -type OriginValidator struct { - allowedOrigins []string -} - -// NewOriginValidator creates a new validator from a comma-separated string of allowed origins -func NewOriginValidator(startUrl *url.URL, allowedOriginsString string) *OriginValidator { - allowedOrigins := startUrl.Scheme + "://" + startUrl.Host - if allowedOriginsString != "" { - allowedOrigins += "," + allowedOriginsString - } - validator := &OriginValidator{} - validator.parseAllowedOrigins(allowedOrigins) - return validator -} - -// parseAllowedOrigins parses the comma-separated origins string -func (v *OriginValidator) parseAllowedOrigins(originsString string) { - if originsString == "" { - v.allowedOrigins = []string{} - return - } - - origins := strings.Split(originsString, ",") - var trimmedOrigins []string - - for _, origin := range origins { - trimmed := strings.TrimSuffix(strings.TrimSpace(origin), "/") - if trimmed != "" { - trimmedOrigins = append(trimmedOrigins, trimmed) - } - } - - v.allowedOrigins = trimmedOrigins -} - -// IsOriginAllowed checks if the given origin is allowed -func (v *OriginValidator) IsOriginAllowed(origin string) bool { - if origin == "" { - return false - } - - for _, allowedOrigin := range v.allowedOrigins { - if v.matchesOriginPattern(allowedOrigin, origin) { - return true - } - } - - return false -} - -// matchesOriginPattern checks if origin matches the pattern (supports wildcards) -func (v *OriginValidator) matchesOriginPattern(pattern, origin string) bool { - // Exact match - if pattern == origin { - return true - } - - // Wildcard pattern matching - if strings.Contains(pattern, "*") { - regexPattern := v.wildcardPatternToRegex(pattern) - matched, err := regexp.MatchString(regexPattern, origin) - if err != nil { - return false - } - return matched - } - - return false -} - -// wildcardPatternToRegex converts wildcard pattern to regex -func (v *OriginValidator) wildcardPatternToRegex(wildcardPattern string) string { - // Escape special regex characters except * - specialChars := []string{"\\", ".", "+", "?", "^", "$", "{", "}", "(", ")", "|", "[", "]"} - - escaped := wildcardPattern - for _, specialChar := range specialChars { - escaped = strings.ReplaceAll(escaped, specialChar, "\\"+specialChar) - } - - // Replace * with .* (matches any characters) - escaped = strings.ReplaceAll(escaped, "*", ".*") - - // Anchor the pattern to match the entire string - return "^" + escaped + "$" -} - -// GetOriginFromURL extracts origin from URL string -func (v *OriginValidator) GetOriginFromURL(urlString string) (string, error) { - if urlString == "" { - return "", fmt.Errorf("empty URL") - } - - parsedURL, err := url.Parse(urlString) - if err != nil { - return "", fmt.Errorf("invalid URL: %v", err) - } - - if parsedURL.Scheme == "" || parsedURL.Host == "" { - return "", fmt.Errorf("URL missing scheme or host") - } - - // Build origin (scheme + host) - origin := parsedURL.Scheme + "://" + parsedURL.Host - - return origin, nil -} diff --git a/v2/internal/frontend/runtime/desktop/contextmenu.js b/v2/internal/frontend/runtime/desktop/contextmenu.js deleted file mode 100644 index b9c397546..000000000 --- a/v2/internal/frontend/runtime/desktop/contextmenu.js +++ /dev/null @@ -1,50 +0,0 @@ -/* ---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 -*/ -export function processDefaultContextMenu(event) { - // Process default context menu - const element = event.target; - const computedStyle = window.getComputedStyle(element); - const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim(); - switch (defaultContextMenuAction) { - case "show": - return; - case "hide": - event.preventDefault(); - return; - default: - // Check if contentEditable is true - if (element.isContentEditable) { - return; - } - - // Check if text has been selected and action is on the selected elements - const selection = window.getSelection(); - const hasSelection = (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) === element) { - return; - } - } - } - } - // Check if tagname is input or textarea - if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") { - if (hasSelection || (!element.readOnly && !element.disabled)) { - return; - } - } - - // hide default context menu - event.preventDefault(); - } -} diff --git a/v2/internal/frontend/runtime/desktop/draganddrop.js b/v2/internal/frontend/runtime/desktop/draganddrop.js deleted file mode 100644 index e470e4823..000000000 --- a/v2/internal/frontend/runtime/desktop/draganddrop.js +++ /dev/null @@ -1,276 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -/* jshint esversion: 9 */ - -import {EventsOn, EventsOff} from "./events"; - -const flags = { - registered: false, - defaultUseDropTarget: true, - useDropTarget: true, - nextDeactivate: null, - nextDeactivateTimeout: null, -}; - -const DROP_TARGET_ACTIVE = "wails-drop-target-active"; - -/** - * checkStyleDropTarget checks if the style has the drop target attribute - * - * @param {CSSStyleDeclaration} style - * @returns - */ -function checkStyleDropTarget(style) { - const cssDropValue = style.getPropertyValue(window.wails.flags.cssDropProperty).trim(); - if (cssDropValue) { - if (cssDropValue === window.wails.flags.cssDropValue) { - return true; - } - // if the element has the drop target attribute, but - // the value is not correct, terminate finding process. - // This can be useful to block some child elements from being drop targets. - return false; - } - return false; -} - -/** - * onDragOver is called when the dragover event is emitted. - * @param {DragEvent} e - * @returns - */ -function onDragOver(e) { - // Check if this is an external file drop or internal HTML drag - // External file drops will have "Files" in the types array - // Internal HTML drags typically have "text/plain", "text/html" or custom types - const isFileDrop = e.dataTransfer.types.includes("Files"); - - // Only handle external file drops, let internal HTML5 drag-and-drop work normally - if (!isFileDrop) { - return; - } - - // ALWAYS prevent default for file drops to stop browser navigation - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; - - if (!window.wails.flags.enableWailsDragAndDrop) { - return; - } - - if (!flags.useDropTarget) { - return; - } - - const element = e.target; - - // Trigger debounce function to deactivate drop targets - if(flags.nextDeactivate) flags.nextDeactivate(); - - // if the element is null or element is not child of drop target element - if (!element || !checkStyleDropTarget(getComputedStyle(element))) { - return; - } - - let currentElement = element; - while (currentElement) { - // check if currentElement is drop target element - if (checkStyleDropTarget(getComputedStyle(currentElement))) { - currentElement.classList.add(DROP_TARGET_ACTIVE); - } - currentElement = currentElement.parentElement; - } -} - -/** - * onDragLeave is called when the dragleave event is emitted. - * @param {DragEvent} e - * @returns - */ -function onDragLeave(e) { - // Check if this is an external file drop or internal HTML drag - const isFileDrop = e.dataTransfer.types.includes("Files"); - - // Only handle external file drops, let internal HTML5 drag-and-drop work normally - if (!isFileDrop) { - return; - } - - // ALWAYS prevent default for file drops to stop browser navigation - e.preventDefault(); - - if (!window.wails.flags.enableWailsDragAndDrop) { - return; - } - - if (!flags.useDropTarget) { - return; - } - - // Find the close drop target element - if (!e.target || !checkStyleDropTarget(getComputedStyle(e.target))) { - return null; - } - - // Trigger debounce function to deactivate drop targets - if(flags.nextDeactivate) flags.nextDeactivate(); - - // Use debounce technique to tacle dragleave events on overlapping elements and drop target elements - flags.nextDeactivate = () => { - // Deactivate all drop targets, new drop target will be activated on next dragover event - Array.from(document.getElementsByClassName(DROP_TARGET_ACTIVE)).forEach(el => el.classList.remove(DROP_TARGET_ACTIVE)); - // Reset nextDeactivate - flags.nextDeactivate = null; - // Clear timeout - if (flags.nextDeactivateTimeout) { - clearTimeout(flags.nextDeactivateTimeout); - flags.nextDeactivateTimeout = null; - } - } - - // Set timeout to deactivate drop targets if not triggered by next drag event - flags.nextDeactivateTimeout = setTimeout(() => { - if(flags.nextDeactivate) flags.nextDeactivate(); - }, 50); -} - -/** - * onDrop is called when the drop event is emitted. - * @param {DragEvent} e - * @returns - */ -function onDrop(e) { - // Check if this is an external file drop or internal HTML drag - const isFileDrop = e.dataTransfer.types.includes("Files"); - - // Only handle external file drops, let internal HTML5 drag-and-drop work normally - if (!isFileDrop) { - return; - } - - // ALWAYS prevent default for file drops to stop browser navigation - e.preventDefault(); - - if (!window.wails.flags.enableWailsDragAndDrop) { - return; - } - - if (CanResolveFilePaths()) { - // process files - let files = []; - if (e.dataTransfer.items) { - files = [...e.dataTransfer.items].map((item, i) => { - if (item.kind === 'file') { - return item.getAsFile(); - } - }); - } else { - files = [...e.dataTransfer.files]; - } - window.runtime.ResolveFilePaths(e.x, e.y, files); - } - - if (!flags.useDropTarget) { - return; - } - - // Trigger debounce function to deactivate drop targets - if(flags.nextDeactivate) flags.nextDeactivate(); - - // Deactivate all drop targets - Array.from(document.getElementsByClassName(DROP_TARGET_ACTIVE)).forEach(el => el.classList.remove(DROP_TARGET_ACTIVE)); -} - -/** - * postMessageWithAdditionalObjects checks the browser's capability of sending postMessageWithAdditionalObjects - * - * @returns {boolean} - * @constructor - */ -export function CanResolveFilePaths() { - return window.chrome?.webview?.postMessageWithAdditionalObjects != null; -} - -/** - * ResolveFilePaths sends drop events to the GO side to resolve file paths on windows. - * - * @param {number} x - * @param {number} y - * @param {any[]} files - * @constructor - */ -export function ResolveFilePaths(x, y, files) { - // Only for windows webview2 >= 1.0.1774.30 - // https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2webmessagereceivedeventargs2?view=webview2-1.0.1823.32#applies-to - if (window.chrome?.webview?.postMessageWithAdditionalObjects) { - chrome.webview.postMessageWithAdditionalObjects(`file:drop:${x}:${y}`, files); - } -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - if (typeof callback !== "function") { - console.error("DragAndDropCallback is not a function"); - return; - } - - if (flags.registered) { - return; - } - flags.registered = true; - - const uDTPT = typeof useDropTarget; - flags.useDropTarget = uDTPT === "undefined" || uDTPT !== "boolean" ? flags.defaultUseDropTarget : useDropTarget; - window.addEventListener('dragover', onDragOver); - window.addEventListener('dragleave', onDragLeave); - window.addEventListener('drop', onDrop); - - let cb = callback; - if (flags.useDropTarget) { - cb = function (x, y, paths) { - const element = document.elementFromPoint(x, y) - // if the element is null or element is not child of drop target element, return null - if (!element || !checkStyleDropTarget(getComputedStyle(element))) { - return null; - } - callback(x, y, paths); - } - } - - EventsOn("wails:file-drop", cb); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - window.removeEventListener('dragover', onDragOver); - window.removeEventListener('dragleave', onDragLeave); - window.removeEventListener('drop', onDrop); - EventsOff("wails:file-drop"); - flags.registered = false; -} diff --git a/v2/internal/frontend/runtime/desktop/events.js b/v2/internal/frontend/runtime/desktop/events.js index e665a8aff..9548cbc34 100644 --- a/v2/internal/frontend/runtime/desktop/events.js +++ b/v2/internal/frontend/runtime/desktop/events.js @@ -90,17 +90,17 @@ function notifyListeners(eventData) { // Get the event name let eventName = eventData.name; - // Keep a list of listener indexes to destroy - const newEventListenerList = eventListeners[eventName]?.slice() || []; - // Check if we have any listeners for this event - if (newEventListenerList.length) { + if (eventListeners[eventName]) { + + // Keep a list of listener indexes to destroy + const newEventListenerList = eventListeners[eventName].slice(); // Iterate listeners - for (let count = newEventListenerList.length - 1; count >= 0; count -= 1) { + for (let count = eventListeners[eventName].length - 1; count >= 0; count -= 1) { // Get next listener - const listener = newEventListenerList[count]; + const listener = eventListeners[eventName][count]; let data = eventData.data; @@ -190,9 +190,9 @@ export function EventsOff(eventName, ...additionalEventNames) { */ export function EventsOffAll() { const eventNames = Object.keys(eventListeners); - eventNames.forEach(eventName => { - removeListener(eventName) - }) + for (let i = 0; i !== eventNames.length; i++) { + removeListener(eventNames[i]); + } } /** @@ -202,8 +202,6 @@ export function EventsOff(eventName, ...additionalEventNames) { */ function listenerOff(listener) { const eventName = listener.eventName; - if (eventListeners[eventName] === undefined) return; - // Remove local listener eventListeners[eventName] = eventListeners[eventName].filter(l => l !== listener); diff --git a/v2/internal/frontend/runtime/desktop/main.js b/v2/internal/frontend/runtime/desktop/main.js index 405d5f60d..9ec68acd6 100644 --- a/v2/internal/frontend/runtime/desktop/main.js +++ b/v2/internal/frontend/runtime/desktop/main.js @@ -9,25 +9,14 @@ The electron alternative for Go */ /* jshint esversion: 9 */ import * as Log from './log'; -import { - eventListeners, - EventsEmit, - EventsNotify, - EventsOff, - EventsOffAll, - EventsOn, - EventsOnce, - EventsOnMultiple, -} from "./events"; -import { Call, Callback, callbacks } from './calls'; -import { SetBindings } from "./bindings"; +import {eventListeners, EventsEmit, EventsNotify, EventsOff, EventsOn, EventsOnce, EventsOnMultiple} from './events'; +import {Call, Callback, callbacks} from './calls'; +import {SetBindings} from "./bindings"; import * as Window from "./window"; import * as Screen from "./screen"; import * as Browser from "./browser"; import * as Clipboard from "./clipboard"; -import * as DragAndDrop from "./draganddrop"; -import * as ContextMenu from "./contextmenu"; -import * as Notifications from "./notifications"; + export function Quit() { window.WailsInvoke('Q'); @@ -52,14 +41,11 @@ window.runtime = { ...Browser, ...Screen, ...Clipboard, - ...DragAndDrop, - ...Notifications, EventsOn, EventsOnce, EventsOnMultiple, EventsEmit, EventsOff, - EventsOffAll, Environment, Show, Hide, @@ -75,7 +61,7 @@ window.wails = { callbacks, flags: { disableScrollbarDrag: false, - disableDefaultContextMenu: false, + disableWailsDefaultContextMenu: false, enableResize: false, defaultCursor: null, borderThickness: 6, @@ -83,9 +69,6 @@ window.wails = { deferDragToMouseMove: true, cssDragProperty: "--wails-draggable", cssDragValue: "drag", - cssDropProperty: "--wails-drop-target", - cssDropValue: "drop", - enableWailsDragAndDrop: false, } }; @@ -95,17 +78,19 @@ if (window.wailsbindings) { delete window.wails.SetBindings; } -// (bool) This is evaluated at build time in package.json -if (!DEBUG) { +// This is evaluated at build time in package.json +// const dev = 0; +// const production = 1; +if (ENV === 1) { delete window.wailsbindings; } -let dragTest = function(e) { +let dragTest = function (e) { var val = window.getComputedStyle(e.target).getPropertyValue(window.wails.flags.cssDragProperty); if (val) { - val = val.trim(); + val = val.trim(); } - + if (val !== window.wails.flags.cssDragValue) { return false; } @@ -123,17 +108,13 @@ let dragTest = function(e) { return true; }; -window.wails.setCSSDragProperties = function(property, value) { +window.wails.setCSSDragProperties = function (property, value) { window.wails.flags.cssDragProperty = property; window.wails.flags.cssDragValue = value; } -window.wails.setCSSDropProperties = function(property, value) { - window.wails.flags.cssDropProperty = property; - window.wails.flags.cssDropValue = value; -} - window.addEventListener('mousedown', (e) => { + // Check for resizing if (window.wails.flags.resizeEdge) { window.WailsInvoke("resize:" + window.wails.flags.resizeEdge); @@ -169,7 +150,7 @@ function setResize(cursor) { window.wails.flags.resizeEdge = cursor; } -window.addEventListener('mousemove', function(e) { +window.addEventListener('mousemove', function (e) { if (window.wails.flags.shouldDrag) { window.wails.flags.shouldDrag = false; let mousePressed = e.buttons !== undefined ? e.buttons : e.which; @@ -207,14 +188,9 @@ window.addEventListener('mousemove', function(e) { }); // Setup context menu hook -window.addEventListener('contextmenu', function(e) { - // always show the contextmenu in debug & dev - if (DEBUG) return; - - if (window.wails.flags.disableDefaultContextMenu) { +window.addEventListener('contextmenu', function (e) { + if (window.wails.flags.disableWailsDefaultContextMenu) { e.preventDefault(); - } else { - ContextMenu.processDefaultContextMenu(e); } }); diff --git a/v2/internal/frontend/runtime/desktop/notifications.js b/v2/internal/frontend/runtime/desktop/notifications.js deleted file mode 100644 index 25c01bb34..000000000 --- a/v2/internal/frontend/runtime/desktop/notifications.js +++ /dev/null @@ -1,200 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ -/* jshint esversion: 9 */ - -import {Call} from "./calls"; - -/** - * Initialize the notification service for the application. - * This must be called before sending any notifications. - * On macOS, this also ensures the notification delegate is properly initialized. - * - * @export - * @return {Promise} - */ -export function InitializeNotifications() { - return Call(":wails:InitializeNotifications"); -} - -/** - * Clean up notification resources and release any held connections. - * This should be called when shutting down the application to properly release resources - * (primarily needed on Linux to close D-Bus connections). - * - * @export - * @return {Promise} - */ -export function CleanupNotifications() { - return Call(":wails:CleanupNotifications"); -} - -/** - * Check if notifications are available on the current platform. - * - * @export - * @return {Promise} True if notifications are available, false otherwise - */ -export function IsNotificationAvailable() { - return Call(":wails:IsNotificationAvailable"); -} - -/** - * Request notification authorization from the user. - * On macOS, this prompts the user to allow notifications. - * On other platforms, this always returns true. - * - * @export - * @return {Promise} True if authorization was granted, false otherwise - */ -export function RequestNotificationAuthorization() { - return Call(":wails:RequestNotificationAuthorization"); -} - -/** - * Check the current notification authorization status. - * On macOS, this checks if the app has notification permissions. - * On other platforms, this always returns true. - * - * @export - * @return {Promise} True if authorized, false otherwise - */ -export function CheckNotificationAuthorization() { - return Call(":wails:CheckNotificationAuthorization"); -} - -/** - * Send a basic notification with the given options. - * The notification will display with the provided title, subtitle (if supported), and body text. - * - * @export - * @param {Object} options - Notification options - * @param {string} options.id - Unique identifier for the notification - * @param {string} options.title - Notification title - * @param {string} [options.subtitle] - Notification subtitle (macOS and Linux only) - * @param {string} [options.body] - Notification body text - * @param {string} [options.categoryId] - Category ID for action buttons (requires SendNotificationWithActions) - * @param {Object} [options.data] - Additional user data to attach to the notification - * @return {Promise} - */ -export function SendNotification(options) { - return Call(":wails:SendNotification", [options]); -} - -/** - * Send a notification with action buttons. - * A NotificationCategory must be registered first using RegisterNotificationCategory. - * The options.categoryId must match a previously registered category ID. - * If the category is not found, a basic notification will be sent instead. - * - * @export - * @param {Object} options - Notification options - * @param {string} options.id - Unique identifier for the notification - * @param {string} options.title - Notification title - * @param {string} [options.subtitle] - Notification subtitle (macOS and Linux only) - * @param {string} [options.body] - Notification body text - * @param {string} options.categoryId - Category ID that matches a registered category - * @param {Object} [options.data] - Additional user data to attach to the notification - * @return {Promise} - */ -export function SendNotificationWithActions(options) { - return Call(":wails:SendNotificationWithActions", [options]); -} - -/** - * Register a notification category that can be used with SendNotificationWithActions. - * Categories define the action buttons and optional reply fields that will appear on notifications. - * Registering a category with the same ID as a previously registered category will override it. - * - * @export - * @param {Object} category - Notification category definition - * @param {string} category.id - Unique identifier for the category - * @param {Array} [category.actions] - Array of action buttons - * @param {string} category.actions[].id - Unique identifier for the action - * @param {string} category.actions[].title - Display title for the action button - * @param {boolean} [category.actions[].destructive] - Whether the action is destructive (macOS-specific) - * @param {boolean} [category.hasReplyField] - Whether to include a text input field for replies - * @param {string} [category.replyPlaceholder] - Placeholder text for the reply field (required if hasReplyField is true) - * @param {string} [category.replyButtonTitle] - Title for the reply button (required if hasReplyField is true) - * @return {Promise} - */ -export function RegisterNotificationCategory(category) { - return Call(":wails:RegisterNotificationCategory", [category]); -} - -/** - * Remove a previously registered notification category. - * - * @export - * @param {string} categoryId - The ID of the category to remove - * @return {Promise} - */ -export function RemoveNotificationCategory(categoryId) { - return Call(":wails:RemoveNotificationCategory", [categoryId]); -} - -/** - * Remove all pending notifications from the notification center. - * On Windows, this is a no-op as the platform manages notification lifecycle automatically. - * - * @export - * @return {Promise} - */ -export function RemoveAllPendingNotifications() { - return Call(":wails:RemoveAllPendingNotifications"); -} - -/** - * Remove a specific pending notification by its identifier. - * On Windows, this is a no-op as the platform manages notification lifecycle automatically. - * - * @export - * @param {string} identifier - The ID of the notification to remove - * @return {Promise} - */ -export function RemovePendingNotification(identifier) { - return Call(":wails:RemovePendingNotification", [identifier]); -} - -/** - * Remove all delivered notifications from the notification center. - * On Windows, this is a no-op as the platform manages notification lifecycle automatically. - * - * @export - * @return {Promise} - */ -export function RemoveAllDeliveredNotifications() { - return Call(":wails:RemoveAllDeliveredNotifications"); -} - -/** - * Remove a specific delivered notification by its identifier. - * On Windows, this is a no-op as the platform manages notification lifecycle automatically. - * - * @export - * @param {string} identifier - The ID of the notification to remove - * @return {Promise} - */ -export function RemoveDeliveredNotification(identifier) { - return Call(":wails:RemoveDeliveredNotification", [identifier]); -} - -/** - * Remove a notification by its identifier. - * This is a convenience function that works across platforms. - * On macOS, use the more specific RemovePendingNotification or RemoveDeliveredNotification functions. - * - * @export - * @param {string} identifier - The ID of the notification to remove - * @return {Promise} - */ -export function RemoveNotification(identifier) { - return Call(":wails:RemoveNotification", [identifier]); -} - diff --git a/v2/internal/frontend/runtime/dev/main.js b/v2/internal/frontend/runtime/dev/main.js index c7f31b0f2..e6e05be54 100644 --- a/v2/internal/frontend/runtime/dev/main.js +++ b/v2/internal/frontend/runtime/dev/main.js @@ -80,7 +80,7 @@ function handleDisconnect() { function _connect() { if (websocket == null) { - websocket = new WebSocket((window.location.protocol.startsWith("https") ? "wss://" : "ws://") + window.location.host + "/wails/ipc"); + websocket = new WebSocket('ws://' + window.location.host + '/wails/ipc'); websocket.onopen = handleConnect; websocket.onerror = function (e) { e.stopImmediatePropagation(); diff --git a/v2/internal/frontend/runtime/events.go b/v2/internal/frontend/runtime/events.go index 1f2e0a6e4..ac9d6299c 100644 --- a/v2/internal/frontend/runtime/events.go +++ b/v2/internal/frontend/runtime/events.go @@ -143,7 +143,7 @@ func (e *Events) notifyBackend(eventName string, data ...interface{}) { } // Do we have items to delete? - if itemsToDelete { + if itemsToDelete == true { // Create a new Listeners slice var newListeners []*eventListener diff --git a/v2/internal/frontend/runtime/ipc_websocket.js b/v2/internal/frontend/runtime/ipc_websocket.js index a0d6b4a70..d5dca66af 100644 --- a/v2/internal/frontend/runtime/ipc_websocket.js +++ b/v2/internal/frontend/runtime/ipc_websocket.js @@ -1,10 +1,10 @@ -(()=>{function D(t){console.log("%c wails dev %c "+t+" ","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 _(){}var A=t=>t;function N(t){return t()}function it(){return Object.create(null)}function b(t){t.forEach(N)}function w(t){return typeof t=="function"}function L(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function ot(t){return Object.keys(t).length===0}function rt(t,...e){if(t==null)return _;let n=t.subscribe(...e);return n.unsubscribe?()=>n.unsubscribe():n}function st(t,e,n){t.$$.on_destroy.push(rt(e,n))}var ct=typeof window!="undefined",Ot=ct?()=>window.performance.now():()=>Date.now(),P=ct?t=>requestAnimationFrame(t):_;var x=new Set;function lt(t){x.forEach(e=>{e.c(t)||(x.delete(e),e.f())}),x.size!==0&&P(lt)}function Dt(t){let e;return x.size===0&&P(lt),{promise:new Promise(n=>{x.add(e={c:t,f:n})}),abort(){x.delete(e)}}}var ut=!1;function At(){ut=!0}function Lt(){ut=!1}function Bt(t,e){t.appendChild(e)}function at(t,e,n){let i=R(t);if(!i.getElementById(e)){let o=B("style");o.id=e,o.textContent=n,ft(i,o)}}function R(t){if(!t)return document;let e=t.getRootNode?t.getRootNode():t.ownerDocument;return e&&e.host?e:t.ownerDocument}function Tt(t){let e=B("style");return ft(R(t),e),e.sheet}function ft(t,e){return Bt(t.head||t,e),e.sheet}function W(t,e,n){t.insertBefore(e,n||null)}function S(t){t.parentNode.removeChild(t)}function B(t){return document.createElement(t)}function Jt(t){return document.createTextNode(t)}function dt(){return Jt("")}function ht(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function zt(t){return Array.from(t.childNodes)}function Ht(t,e,{bubbles:n=!1,cancelable:i=!1}={}){let o=document.createEvent("CustomEvent");return o.initCustomEvent(t,n,i,e),o}var T=new Map,J=0;function Gt(t){let e=5381,n=t.length;for(;n--;)e=(e<<5)-e^t.charCodeAt(n);return e>>>0}function qt(t,e){let n={stylesheet:Tt(e),rules:{}};return T.set(t,n),n}function pt(t,e,n,i,o,c,s,l=0){let f=16.666/i,r=`{ +(()=>{function D(t){console.log("%c wails dev %c "+t+" ","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 p(){}var A=t=>t;function N(t){return t()}function it(){return Object.create(null)}function b(t){t.forEach(N)}function w(t){return typeof t=="function"}function L(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function ot(t){return Object.keys(t).length===0}function rt(t,...e){if(t==null)return p;let n=t.subscribe(...e);return n.unsubscribe?()=>n.unsubscribe():n}function st(t,e,n){t.$$.on_destroy.push(rt(e,n))}var ct=typeof window!="undefined",Ot=ct?()=>window.performance.now():()=>Date.now(),P=ct?t=>requestAnimationFrame(t):p;var x=new Set;function lt(t){x.forEach(e=>{e.c(t)||(x.delete(e),e.f())}),x.size!==0&&P(lt)}function Dt(t){let e;return x.size===0&&P(lt),{promise:new Promise(n=>{x.add(e={c:t,f:n})}),abort(){x.delete(e)}}}var ut=!1;function At(){ut=!0}function Lt(){ut=!1}function Bt(t,e){t.appendChild(e)}function at(t,e,n){let i=R(t);if(!i.getElementById(e)){let o=B("style");o.id=e,o.textContent=n,ft(i,o)}}function R(t){if(!t)return document;let e=t.getRootNode?t.getRootNode():t.ownerDocument;return e&&e.host?e:t.ownerDocument}function Tt(t){let e=B("style");return ft(R(t),e),e.sheet}function ft(t,e){return Bt(t.head||t,e),e.sheet}function W(t,e,n){t.insertBefore(e,n||null)}function S(t){t.parentNode.removeChild(t)}function B(t){return document.createElement(t)}function Jt(t){return document.createTextNode(t)}function dt(){return Jt("")}function ht(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function zt(t){return Array.from(t.childNodes)}function Ht(t,e,{bubbles:n=!1,cancelable:i=!1}={}){let o=document.createEvent("CustomEvent");return o.initCustomEvent(t,n,i,e),o}var T=new Map,J=0;function Gt(t){let e=5381,n=t.length;for(;n--;)e=(e<<5)-e^t.charCodeAt(n);return e>>>0}function qt(t,e){let n={stylesheet:Tt(e),rules:{}};return T.set(t,n),n}function _t(t,e,n,i,o,c,s,l=0){let f=16.666/i,r=`{ `;for(let g=0;g<=1;g+=f){let F=e+(n-e)*c(g);r+=g*100+`%{${s(F,1-F)}} `}let y=r+`100% {${s(n,1-n)}} -}`,a=`__svelte_${Gt(y)}_${l}`,u=R(t),{stylesheet:h,rules:p}=T.get(u)||qt(u,t);p[a]||(p[a]=!0,h.insertRule(`@keyframes ${a} ${y}`,h.cssRules.length));let v=t.style.animation||"";return t.style.animation=`${v?`${v}, `:""}${a} ${i}ms linear ${o}ms 1 both`,J+=1,a}function Kt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),o=n.length-i.length;o&&(t.style.animation=i.join(", "),J-=o,J||Nt())}function Nt(){P(()=>{J||(T.forEach(t=>{let{ownerNode:e}=t.stylesheet;e&&S(e)}),T.clear())})}var V;function C(t){V=t}var k=[];var _t=[],z=[],mt=[],Pt=Promise.resolve(),U=!1;function Rt(){U||(U=!0,Pt.then(yt))}function $(t){z.push(t)}var X=new Set,H=0;function yt(){let t=V;do{for(;H{E=null})),E}function Z(t,e,n){t.dispatchEvent(Ht(`${e?"intro":"outro"}${n}`))}var G=new Set,m;function gt(){m={r:0,c:[],p:m}}function bt(){m.r||b(m.c),m=m.p}function I(t,e){t&&t.i&&(G.delete(t),t.i(e))}function Q(t,e,n,i){if(t&&t.o){if(G.has(t))return;G.add(t),m.c.push(()=>{G.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}else i&&i()}var Ut={duration:0};function Y(t,e,n,i){let o=e(t,n),c=i?0:1,s=null,l=null,f=null;function r(){f&&Kt(t,f)}function y(u,h){let p=u.b-c;return h*=Math.abs(p),{a:c,b:u.b,d:p,duration:h,start:u.start,end:u.start+h,group:u.group}}function a(u){let{delay:h=0,duration:p=300,easing:v=A,tick:g=_,css:F}=o||Ut,K={start:Ot()+h,b:u};u||(K.group=m,m.r+=1),s||l?l=K:(F&&(r(),f=pt(t,c,u,p,h,v,F)),u&&g(0,1),s=y(K,p),$(()=>Z(t,u,"start")),Dt(O=>{if(l&&O>l.start&&(s=y(l,p),l=null,Z(t,s.b,"start"),F&&(r(),f=pt(t,c,s.b,s.duration,0,v,o.css))),s){if(O>=s.end)g(c=s.b,1-c),Z(t,s.b,"end"),l||(s.b?r():--s.group.r||b(s.group.c)),s=null;else if(O>=s.start){let jt=O-s.start;c=s.a+s.d*v(jt/s.duration),g(c,1-c)}}return!!(s||l)}))}return{run(u){w(o)?Vt().then(()=>{o=o(),a(u)}):a(u)},end(){r(),s=l=null}}}var le=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var ue=new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);function Xt(t,e,n,i){let{fragment:o,after_update:c}=t.$$;o&&o.m(e,n),i||$(()=>{let s=t.$$.on_mount.map(N).filter(w);t.$$.on_destroy?t.$$.on_destroy.push(...s):b(s),t.$$.on_mount=[]}),c.forEach($)}function wt(t,e){let n=t.$$;n.fragment!==null&&(b(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function Zt(t,e){t.$$.dirty[0]===-1&&(k.push(t),Rt(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let p=h.length?h[0]:u;return r.ctx&&o(r.ctx[a],r.ctx[a]=p)&&(!r.skip_bound&&r.bound[a]&&r.bound[a](p),y&&Zt(t,a)),u}):[],r.update(),y=!0,b(r.before_update),r.fragment=i?i(r.ctx):!1,e.target){if(e.hydrate){At();let a=zt(e.target);r.fragment&&r.fragment.l(a),a.forEach(S)}else r.fragment&&r.fragment.c();e.intro&&I(t.$$.fragment),Xt(t,e.target,e.anchor,e.customElement),Lt(),yt()}C(f)}var Qt;typeof HTMLElement=="function"&&(Qt=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(N).filter(w);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){b(this.$$.on_disconnect)}$destroy(){wt(this,1),this.$destroy=_}$on(t,e){if(!w(e))return _;let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!ot(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var tt=class{$destroy(){wt(this,1),this.$destroy=_}$on(e,n){if(!w(n))return _;let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let o=i.indexOf(n);o!==-1&&i.splice(o,1)}}$set(e){this.$$set&&!ot(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var M=[];function Ft(t,e=_){let n,i=new Set;function o(l){if(L(t,l)&&(t=l,n)){let f=!M.length;for(let r of i)r[1](),M.push(r,t);if(f){for(let r=0;r{i.delete(r),i.size===0&&(n(),n=null)}}return{set:o,update:c,subscribe:s}}var q=Ft(!1);function xt(){q.set(!0)}function $t(){q.set(!1)}function et(t,{delay:e=0,duration:n=400,easing:i=A}={}){let o=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*o}`}}function Yt(t){at(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999 +}`,a=`__svelte_${Gt(y)}_${l}`,u=R(t),{stylesheet:h,rules:_}=T.get(u)||qt(u,t);_[a]||(_[a]=!0,h.insertRule(`@keyframes ${a} ${y}`,h.cssRules.length));let v=t.style.animation||"";return t.style.animation=`${v?`${v}, `:""}${a} ${i}ms linear ${o}ms 1 both`,J+=1,a}function Kt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),o=n.length-i.length;o&&(t.style.animation=i.join(", "),J-=o,J||Nt())}function Nt(){P(()=>{J||(T.forEach(t=>{let{ownerNode:e}=t.stylesheet;e&&S(e)}),T.clear())})}var V;function C(t){V=t}var k=[];var pt=[],z=[],mt=[],Pt=Promise.resolve(),U=!1;function Rt(){U||(U=!0,Pt.then(yt))}function $(t){z.push(t)}var X=new Set,H=0;function yt(){let t=V;do{for(;H{E=null})),E}function Z(t,e,n){t.dispatchEvent(Ht(`${e?"intro":"outro"}${n}`))}var G=new Set,m;function gt(){m={r:0,c:[],p:m}}function bt(){m.r||b(m.c),m=m.p}function I(t,e){t&&t.i&&(G.delete(t),t.i(e))}function Q(t,e,n,i){if(t&&t.o){if(G.has(t))return;G.add(t),m.c.push(()=>{G.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}else i&&i()}var Ut={duration:0};function Y(t,e,n,i){let o=e(t,n),c=i?0:1,s=null,l=null,f=null;function r(){f&&Kt(t,f)}function y(u,h){let _=u.b-c;return h*=Math.abs(_),{a:c,b:u.b,d:_,duration:h,start:u.start,end:u.start+h,group:u.group}}function a(u){let{delay:h=0,duration:_=300,easing:v=A,tick:g=p,css:F}=o||Ut,K={start:Ot()+h,b:u};u||(K.group=m,m.r+=1),s||l?l=K:(F&&(r(),f=_t(t,c,u,_,h,v,F)),u&&g(0,1),s=y(K,_),$(()=>Z(t,u,"start")),Dt(O=>{if(l&&O>l.start&&(s=y(l,_),l=null,Z(t,s.b,"start"),F&&(r(),f=_t(t,c,s.b,s.duration,0,v,o.css))),s){if(O>=s.end)g(c=s.b,1-c),Z(t,s.b,"end"),l||(s.b?r():--s.group.r||b(s.group.c)),s=null;else if(O>=s.start){let jt=O-s.start;c=s.a+s.d*v(jt/s.duration),g(c,1-c)}}return!!(s||l)}))}return{run(u){w(o)?Vt().then(()=>{o=o(),a(u)}):a(u)},end(){r(),s=l=null}}}var le=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var ue=new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);function Xt(t,e,n,i){let{fragment:o,after_update:c}=t.$$;o&&o.m(e,n),i||$(()=>{let s=t.$$.on_mount.map(N).filter(w);t.$$.on_destroy?t.$$.on_destroy.push(...s):b(s),t.$$.on_mount=[]}),c.forEach($)}function wt(t,e){let n=t.$$;n.fragment!==null&&(b(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function Zt(t,e){t.$$.dirty[0]===-1&&(k.push(t),Rt(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let _=h.length?h[0]:u;return r.ctx&&o(r.ctx[a],r.ctx[a]=_)&&(!r.skip_bound&&r.bound[a]&&r.bound[a](_),y&&Zt(t,a)),u}):[],r.update(),y=!0,b(r.before_update),r.fragment=i?i(r.ctx):!1,e.target){if(e.hydrate){At();let a=zt(e.target);r.fragment&&r.fragment.l(a),a.forEach(S)}else r.fragment&&r.fragment.c();e.intro&&I(t.$$.fragment),Xt(t,e.target,e.anchor,e.customElement),Lt(),yt()}C(f)}var Qt;typeof HTMLElement=="function"&&(Qt=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(N).filter(w);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){b(this.$$.on_disconnect)}$destroy(){wt(this,1),this.$destroy=p}$on(t,e){if(!w(e))return p;let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!ot(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var tt=class{$destroy(){wt(this,1),this.$destroy=p}$on(e,n){if(!w(n))return p;let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let o=i.indexOf(n);o!==-1&&i.splice(o,1)}}$set(e){this.$$set&&!ot(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var M=[];function Ft(t,e=p){let n,i=new Set;function o(l){if(L(t,l)&&(t=l,n)){let f=!M.length;for(let r of i)r[1](),M.push(r,t);if(f){for(let r=0;r{i.delete(r),i.size===0&&(n(),n=null)}}return{set:o,update:c,subscribe:s}}var q=Ft(!1);function xt(){q.set(!0)}function $t(){q.set(!1)}function et(t,{delay:e=0,duration:n=400,easing:i=A}={}){let o=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*o}`}}function Yt(t){at(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999 }.wails-reconnect-overlay-content.svelte-181h7z{position:relative;top:50%;transform:translateY(-50%);margin:0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAA7CAMAAAAEsocZAAAC91BMVEUAAACzQ0PjMjLkMjLZLS7XLS+vJCjkMjKlEx6uGyHjMDGiFx7GJyrAISjUKy3mMzPlMjLjMzOsGyDKJirkMjK6HyXmMjLgMDC6IiLcMjLULC3MJyrRKSy+IibmMzPmMjK7ISXlMjLIJimzHSLkMjKtGiHZLC7BIifgMDCpGSDFIivcLy+yHSKoGR+eFBzNKCvlMjKxHSPkMTKxHSLmMjLKJyq5ICXDJCe6ISXdLzDkMjLmMzPFJSm2HyTlMTLhMDGyHSKUEBmhFx24HyTCJCjHJijjMzOiFh7mMjJ6BhDaLDCuGyOKABjnMzPGJinJJiquHCGEChSmGB/pMzOiFh7VKy3OKCu1HiSvHCLjMTLMKCrBIyeICxWxHCLDIyjSKizBIyh+CBO9ISa6ISWDChS9Iie1HyXVLC7FJSrLKCrlMjLiMTGPDhicFRywGyKXFBuhFx1/BxO7IiXkMTGeFBx8BxLkMTGnGR/GJCi4ICWsGyGJDxXSLS2yGiHSKi3CJCfnMzPQKiyECRTKJiq6ISWUERq/Iye0HiPDJCjGJSm6ICaPDxiTEBrdLy+3HyXSKiy0HyOQEBi4ICWhFh1+CBO9IieODhfSKyzWLC2LDhh8BxHKKCq7ISWaFBzkMzPqNDTTLC3EJSiHDBacExyvGyO1HyTPKCy+IieoGSC7ISaVEhrMKCvQKyusGyG0HiKACBPIJSq/JCaABxR5BRLEJCnkMzPJJinEJimPDRZ2BRKqHx/jMjLnMzPgMDHULC3NKSvQKSzsNDTWLS7SKyy3HyTKJyrDJSjbLzDYLC6mGB/GJSnVLC61HiPLKCrHJSm/Iye8Iia6ICWzHSKxHCLaLi/PKSupGR+7ICXpMzPbLi/IJinJJSmsGyGrGiCkFx6PDheJCxaFChXBIyfAIieSDxmBCBPlMjLeLzDdLzC5HySMDRe+ISWvGyGcFBzSKSzPJyvMJyrEJCjDIyefFRyWERriMDHUKiy/ISaZExv0NjbwNTXuNDTrMzMI0c+yAAAAu3RSTlMAA8HR/gwGgAj+MEpGCsC+hGpjQjYnIxgWBfzx7urizMrFqqB1bF83KhsR/fz8+/r5+fXv7unZ1tC+t6mmopqKdW1nYVpVRjUeHhIQBPr59/b28/Hx8ODg3NvUw8O/vKeim5aNioiDgn1vZWNjX1xUU1JPTUVFPT08Mi4qJyIh/Pv7+/n4+Pf39fT08/Du7efn5uXj4uHa19XNwsG/vrq2tbSuramlnpyYkpGNiIZ+enRraGVjVVBKOzghdjzRsAAABJVJREFUWMPtllVQG1EYhTc0ASpoobS0FCulUHd3oUjd3d3d3d3d3d2b7CYhnkBCCHGDEIK7Vh56d0NpOgwkYfLQzvA9ZrLfnPvfc+8uVEst/yheBJup3Nya2MjU6pa/jWLZtxjXpZFtVB4uVNI6m5gIruNkVFebqIb5Ug2ym4TIEM/gtUOGbg613oBzjAzZFrZ+lXu/3TIiMXXS5M6HTvrNHeLpZLEh6suGNW9fzZ9zd/qVi2eOHygqi5cDE5GUrJocONgzyqo0UXNSUlKSEhMztFqtXq9vNxImAmS3g7Y6QlbjdBWVGW36jt4wDGTUXjUsafh5zJWRkdFuZGtWGnCRmg+HasiGMUClTTzW0ZuVgLlGDIPM4Lhi0IrVq+tv2hS21fNrSONQgpM9DsJ4t3fM9PkvJuKj2ZjrZwvILKvaSTgciUSirjt6dOfOpyd169bDb9rMOwF9Hj4OD100gY0YXYb299bjzMrqj9doNByJWlVXFB9DT5dmJuvy+cq83JyuS6ayEYSHulKL8dmFnBkrCeZlHKMrC5XRhXGCZB2Ty1fkleRQaMCFT2DBsEafzRFJu7/2MicbKynPhQUDLiZwMWLJZKNLzoLbJBYVcurSmbmn+rcyJ8vCMgmlmaW6gnwun/+3C96VpAUuET1ZgRR36r2xWlnYSnf3oKABA14uXDDvydxHs6cpTV1p3hlJ2rJCiUjIZCByItXg8sHJijuvT64CuMTABUYvb6NN1Jdp1PH7D7f3bo2eS5KvW4RJr7atWT5w4MBBg9zdBw9+37BS7QIoFS5WnIaj12dr1DEXFgdvr4fh4eFl+u/wz8uf3jjHic8s4DL2Dal0IANyUBeCRCcwOBJV26JsjSpGwHVuSai69jvqD+jr56OgtKy0zAAK5mLTVBKVKL5tNthGAR9JneJQ/bFsHNzy+U7IlCYROxtMpIjR0ceoQVnowracLLpAQWETqV361bPoFo3cEbz2zYLZM7t3HWXcxmiBOgttS1ycWkTXMWh4mGigdug9DFdttqCFgTN6nD0q1XEVSoCxEjyFCi2eNC6Z69MRVIImJ6JQSf5gcFVCuF+aDhCa1F6MJFDaiNBQAh2TMfWBjhmLsAxUjG/fmjs0qjJck8D0GPBcuUuZW1LS/tIsPzqmQt17PvZQknlwnf4tHDBc+7t5VV3QQCkdc+Ur8/hdrz0but0RCumWiYbiKmLJ7EVbRomj4Q7+y5wsaXvfTGFpQcHB7n2WbG4MGdniw2Tm8xl5Yhr7MrSYHQ3uampz10aWyHyuzxvqaW/6W4MjXAUD3QV2aw97ZxhGjxCohYf5TpTHMXU1BbsAuoFnkRygVieIGAbqiF7rrH4rfWpKJouBCtyHJF8ctEyGubBa+C6NsMYEUonJFITHZqWBxXUA12Dv76Tf/PgOBmeNiiLG1pcKo1HAq8jLpY4JU1yWEixVNaOgoRJAKBSZHTZTU+wJOMtUDZvlVITC6FTlksyrEBoPHXpxxbzdaqzigUtVDkJVIOtVQ9UEOR4VGUh/kHWq0edJ6CxnZ+eePXva2bnY/cF/I1RLLf8vvwDANdMSMegxcAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center }.wails-reconnect-overlay-loadingspinner.svelte-181h7z{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#f00 #eee0 #f00 #eee0;border-radius:50%;animation:svelte-181h7z-loadingspin 1s linear infinite;margin:auto;padding:2.5em - }@keyframes svelte-181h7z-loadingspin{100%{transform:rotate(360deg)}}`)}function Mt(t){let e,n,i;return{c(){e=B("div"),e.innerHTML='
',ht(e,"class","wails-reconnect-overlay svelte-181h7z")},m(o,c){W(o,e,c),i=!0},i(o){i||($(()=>{n||(n=Y(e,et,{duration:300},!0)),n.run(1)}),i=!0)},o(o){n||(n=Y(e,et,{duration:300},!1)),n.run(0),i=!1},d(o){o&&S(e),o&&n&&n.end()}}}function te(t){let e,n,i=t[0]&&Mt(t);return{c(){i&&i.c(),e=dt()},m(o,c){i&&i.m(o,c),W(o,e,c),n=!0},p(o,[c]){o[0]?i?c&1&&I(i,1):(i=Mt(o),i.c(),I(i,1),i.m(e.parentNode,e)):i&&(gt(),Q(i,1,1,()=>{i=null}),bt())},i(o){n||(I(i),n=!0)},o(o){Q(i),n=!1},d(o){i&&i.d(o),o&&S(e)}}}function ee(t,e,n){let i;return st(t,q,o=>n(0,i=o)),[i]}var St=class extends tt{constructor(e){super();vt(this,e,ee,te,L,{},Yt)}},Ct=St;var ne={},nt=null,j=[];window.WailsInvoke=t=>{if(!nt){console.log("Queueing: "+t),j.push(t);return}nt(t)};window.addEventListener("DOMContentLoaded",()=>{ne.overlay=new Ct({target:document.body,anchor:document.querySelector("#wails-spinner")})});var d=null,kt;window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};It();function ie(){nt=t=>{d.send(t)};for(let t=0;t
',ht(e,"class","wails-reconnect-overlay svelte-181h7z")},m(o,c){W(o,e,c),i=!0},i(o){i||($(()=>{n||(n=Y(e,et,{duration:300},!0)),n.run(1)}),i=!0)},o(o){n||(n=Y(e,et,{duration:300},!1)),n.run(0),i=!1},d(o){o&&S(e),o&&n&&n.end()}}}function te(t){let e,n,i=t[0]&&Mt(t);return{c(){i&&i.c(),e=dt()},m(o,c){i&&i.m(o,c),W(o,e,c),n=!0},p(o,[c]){o[0]?i?c&1&&I(i,1):(i=Mt(o),i.c(),I(i,1),i.m(e.parentNode,e)):i&&(gt(),Q(i,1,1,()=>{i=null}),bt())},i(o){n||(I(i),n=!0)},o(o){Q(i),n=!1},d(o){i&&i.d(o),o&&S(e)}}}function ee(t,e,n){let i;return st(t,q,o=>n(0,i=o)),[i]}var St=class extends tt{constructor(e){super();vt(this,e,ee,te,L,{},Yt)}},Ct=St;var ne={},nt=null,j=[];window.WailsInvoke=t=>{if(!nt){console.log("Queueing: "+t),j.push(t);return}nt(t)};window.addEventListener("DOMContentLoaded",()=>{ne.overlay=new Ct({target:document.body,anchor:document.querySelector("#wails-spinner")})});var d=null,kt;window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};It();function ie(){nt=t=>{d.send(t)};for(let t=0;t= 14", "less": "*", "sass": "*", "stylus": "*", - "sugarss": "*", "terser": "^5.4.0" }, "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, "less": { "optional": true }, @@ -1905,9 +1890,6 @@ "stylus": { "optional": true }, - "sugarss": { - "optional": true - }, "terser": { "optional": true } @@ -2536,9 +2518,9 @@ } }, "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, @@ -2847,9 +2829,9 @@ "dev": true }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true }, "nice-try": { @@ -2982,12 +2964,12 @@ "dev": true }, "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", "dev": true, "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -3054,9 +3036,9 @@ } }, "rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -3075,9 +3057,9 @@ "dev": true }, "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "shebang-command": { @@ -3338,16 +3320,16 @@ } }, "vite": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.10.tgz", - "integrity": "sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz", + "integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==", "dev": true, "requires": { "esbuild": "^0.15.9", "fsevents": "~2.3.2", - "postcss": "^8.4.18", + "postcss": "^8.4.16", "resolve": "^1.22.1", - "rollup": "^2.79.1" + "rollup": "~2.78.0" } }, "vitest": { diff --git a/v2/internal/frontend/runtime/package.json b/v2/internal/frontend/runtime/package.json index 09ff4d50f..aa6c3aad5 100644 --- a/v2/internal/frontend/runtime/package.json +++ b/v2/internal/frontend/runtime/package.json @@ -7,8 +7,8 @@ "build": "run-p build:*", "build:ipc-desktop": "npx esbuild desktop/ipc.js --bundle --minify --outfile=ipc.js", "build:ipc-dev": "cd dev && npm install && npm run build", - "build:runtime-desktop-prod": "npx esbuild desktop/main.js --bundle --minify --outfile=runtime_prod_desktop.js --define:DEBUG=false", - "build:runtime-desktop-debug": "npx esbuild desktop/main.js --bundle --sourcemap=inline --outfile=runtime_debug_desktop.js --define:DEBUG=true", + "build:runtime-desktop-prod": "npx esbuild desktop/main.js --bundle --minify --outfile=runtime_prod_desktop.js --define:ENV=1", + "build:runtime-desktop-dev": "npx esbuild desktop/main.js --bundle --sourcemap=inline --outfile=runtime_dev_desktop.js --define:ENV=0", "test": "vitest" }, "author": "Lea Anthony ", diff --git a/v2/internal/frontend/runtime/runtime_debug_desktop.go b/v2/internal/frontend/runtime/runtime_debug_desktop.go deleted file mode 100644 index 8dff343c0..000000000 --- a/v2/internal/frontend/runtime/runtime_debug_desktop.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build debug || !production - -package runtime - -import _ "embed" - -//go:embed runtime_debug_desktop.js -var RuntimeDesktopJS []byte diff --git a/v2/internal/frontend/runtime/runtime_debug_desktop.js b/v2/internal/frontend/runtime/runtime_debug_desktop.js deleted file mode 100644 index e646ed532..000000000 --- a/v2/internal/frontend/runtime/runtime_debug_desktop.js +++ /dev/null @@ -1,854 +0,0 @@ -(() => { - var __defProp = Object.defineProperty; - var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); - }; - - // desktop/log.js - var log_exports = {}; - __export(log_exports, { - LogDebug: () => LogDebug, - LogError: () => LogError, - LogFatal: () => LogFatal, - LogInfo: () => LogInfo, - LogLevel: () => LogLevel, - LogPrint: () => LogPrint, - LogTrace: () => LogTrace, - LogWarning: () => LogWarning, - SetLogLevel: () => SetLogLevel - }); - function sendLogMessage(level, message) { - window.WailsInvoke("L" + level + message); - } - function LogTrace(message) { - sendLogMessage("T", message); - } - function LogPrint(message) { - sendLogMessage("P", message); - } - function LogDebug(message) { - sendLogMessage("D", message); - } - function LogInfo(message) { - sendLogMessage("I", message); - } - function LogWarning(message) { - sendLogMessage("W", message); - } - function LogError(message) { - sendLogMessage("E", message); - } - function LogFatal(message) { - sendLogMessage("F", message); - } - function SetLogLevel(loglevel) { - sendLogMessage("S", loglevel); - } - var LogLevel = { - TRACE: 1, - DEBUG: 2, - INFO: 3, - WARNING: 4, - ERROR: 5 - }; - - // desktop/events.js - var Listener = class { - constructor(eventName, callback, maxCallbacks) { - this.eventName = eventName; - this.maxCallbacks = maxCallbacks || -1; - this.Callback = (data) => { - callback.apply(null, data); - if (this.maxCallbacks === -1) { - return false; - } - this.maxCallbacks -= 1; - return this.maxCallbacks === 0; - }; - } - }; - var eventListeners = {}; - function EventsOnMultiple(eventName, callback, maxCallbacks) { - eventListeners[eventName] = eventListeners[eventName] || []; - const thisListener = new Listener(eventName, callback, maxCallbacks); - eventListeners[eventName].push(thisListener); - return () => listenerOff(thisListener); - } - function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); - } - function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); - } - function notifyListeners(eventData) { - let eventName = eventData.name; - const newEventListenerList = eventListeners[eventName]?.slice() || []; - if (newEventListenerList.length) { - for (let count = newEventListenerList.length - 1; count >= 0; count -= 1) { - const listener = newEventListenerList[count]; - let data = eventData.data; - const destroy = listener.Callback(data); - if (destroy) { - newEventListenerList.splice(count, 1); - } - } - if (newEventListenerList.length === 0) { - removeListener(eventName); - } else { - eventListeners[eventName] = newEventListenerList; - } - } - } - function EventsNotify(notifyMessage) { - let message; - try { - message = JSON.parse(notifyMessage); - } catch (e) { - const error = "Invalid JSON passed to Notify: " + notifyMessage; - throw new Error(error); - } - notifyListeners(message); - } - function EventsEmit(eventName) { - const payload = { - name: eventName, - data: [].slice.apply(arguments).slice(1) - }; - notifyListeners(payload); - window.WailsInvoke("EE" + JSON.stringify(payload)); - } - function removeListener(eventName) { - delete eventListeners[eventName]; - window.WailsInvoke("EX" + eventName); - } - function EventsOff(eventName, ...additionalEventNames) { - removeListener(eventName); - if (additionalEventNames.length > 0) { - additionalEventNames.forEach((eventName2) => { - removeListener(eventName2); - }); - } - } - function EventsOffAll() { - const eventNames = Object.keys(eventListeners); - eventNames.forEach((eventName) => { - removeListener(eventName); - }); - } - function listenerOff(listener) { - const eventName = listener.eventName; - if (eventListeners[eventName] === void 0) - return; - eventListeners[eventName] = eventListeners[eventName].filter((l) => l !== listener); - if (eventListeners[eventName].length === 0) { - removeListener(eventName); - } - } - - // desktop/calls.js - var callbacks = {}; - function cryptoRandom() { - var array = new Uint32Array(1); - return window.crypto.getRandomValues(array)[0]; - } - function basicRandom() { - return Math.random() * 9007199254740991; - } - var randomFunc; - if (window.crypto) { - randomFunc = cryptoRandom; - } else { - randomFunc = basicRandom; - } - function Call(name, args, timeout) { - if (timeout == null) { - timeout = 0; - } - return new Promise(function(resolve, reject) { - var callbackID; - do { - callbackID = name + "-" + randomFunc(); - } while (callbacks[callbackID]); - var timeoutHandle; - if (timeout > 0) { - timeoutHandle = setTimeout(function() { - reject(Error("Call to " + name + " timed out. Request ID: " + callbackID)); - }, timeout); - } - callbacks[callbackID] = { - timeoutHandle, - reject, - resolve - }; - try { - const payload = { - name, - args, - callbackID - }; - window.WailsInvoke("C" + JSON.stringify(payload)); - } catch (e) { - console.error(e); - } - }); - } - window.ObfuscatedCall = (id, args, timeout) => { - if (timeout == null) { - timeout = 0; - } - return new Promise(function(resolve, reject) { - var callbackID; - do { - callbackID = id + "-" + randomFunc(); - } while (callbacks[callbackID]); - var timeoutHandle; - if (timeout > 0) { - timeoutHandle = setTimeout(function() { - reject(Error("Call to method " + id + " timed out. Request ID: " + callbackID)); - }, timeout); - } - callbacks[callbackID] = { - timeoutHandle, - reject, - resolve - }; - try { - const payload = { - id, - args, - callbackID - }; - window.WailsInvoke("c" + JSON.stringify(payload)); - } catch (e) { - console.error(e); - } - }); - }; - function Callback(incomingMessage) { - let message; - try { - message = JSON.parse(incomingMessage); - } catch (e) { - const error = `Invalid JSON passed to callback: ${e.message}. Message: ${incomingMessage}`; - runtime.LogDebug(error); - throw new Error(error); - } - let callbackID = message.callbackid; - let callbackData = callbacks[callbackID]; - if (!callbackData) { - const error = `Callback '${callbackID}' not registered!!!`; - console.error(error); - throw new Error(error); - } - clearTimeout(callbackData.timeoutHandle); - delete callbacks[callbackID]; - if (message.error) { - callbackData.reject(message.error); - } else { - callbackData.resolve(message.result); - } - } - - // desktop/bindings.js - window.go = {}; - function SetBindings(bindingsMap) { - try { - bindingsMap = JSON.parse(bindingsMap); - } catch (e) { - console.error(e); - } - window.go = window.go || {}; - Object.keys(bindingsMap).forEach((packageName) => { - window.go[packageName] = window.go[packageName] || {}; - Object.keys(bindingsMap[packageName]).forEach((structName) => { - window.go[packageName][structName] = window.go[packageName][structName] || {}; - Object.keys(bindingsMap[packageName][structName]).forEach((methodName) => { - window.go[packageName][structName][methodName] = function() { - let timeout = 0; - function dynamic() { - const args = [].slice.call(arguments); - return Call([packageName, structName, methodName].join("."), args, timeout); - } - dynamic.setTimeout = function(newTimeout) { - timeout = newTimeout; - }; - dynamic.getTimeout = function() { - return timeout; - }; - return dynamic; - }(); - }); - }); - }); - } - - // desktop/window.js - var window_exports = {}; - __export(window_exports, { - WindowCenter: () => WindowCenter, - WindowFullscreen: () => WindowFullscreen, - WindowGetPosition: () => WindowGetPosition, - WindowGetSize: () => WindowGetSize, - WindowHide: () => WindowHide, - WindowIsFullscreen: () => WindowIsFullscreen, - WindowIsMaximised: () => WindowIsMaximised, - WindowIsMinimised: () => WindowIsMinimised, - WindowIsNormal: () => WindowIsNormal, - WindowMaximise: () => WindowMaximise, - WindowMinimise: () => WindowMinimise, - WindowReload: () => WindowReload, - WindowReloadApp: () => WindowReloadApp, - WindowSetAlwaysOnTop: () => WindowSetAlwaysOnTop, - WindowSetBackgroundColour: () => WindowSetBackgroundColour, - WindowSetDarkTheme: () => WindowSetDarkTheme, - WindowSetLightTheme: () => WindowSetLightTheme, - WindowSetMaxSize: () => WindowSetMaxSize, - WindowSetMinSize: () => WindowSetMinSize, - WindowSetPosition: () => WindowSetPosition, - WindowSetSize: () => WindowSetSize, - WindowSetSystemDefaultTheme: () => WindowSetSystemDefaultTheme, - WindowSetTitle: () => WindowSetTitle, - WindowShow: () => WindowShow, - WindowToggleMaximise: () => WindowToggleMaximise, - WindowUnfullscreen: () => WindowUnfullscreen, - WindowUnmaximise: () => WindowUnmaximise, - WindowUnminimise: () => WindowUnminimise - }); - function WindowReload() { - window.location.reload(); - } - function WindowReloadApp() { - window.WailsInvoke("WR"); - } - function WindowSetSystemDefaultTheme() { - window.WailsInvoke("WASDT"); - } - function WindowSetLightTheme() { - window.WailsInvoke("WALT"); - } - function WindowSetDarkTheme() { - window.WailsInvoke("WADT"); - } - function WindowCenter() { - window.WailsInvoke("Wc"); - } - function WindowSetTitle(title) { - window.WailsInvoke("WT" + title); - } - function WindowFullscreen() { - window.WailsInvoke("WF"); - } - function WindowUnfullscreen() { - window.WailsInvoke("Wf"); - } - function WindowIsFullscreen() { - return Call(":wails:WindowIsFullscreen"); - } - function WindowSetSize(width, height) { - window.WailsInvoke("Ws:" + width + ":" + height); - } - function WindowGetSize() { - return Call(":wails:WindowGetSize"); - } - function WindowSetMaxSize(width, height) { - window.WailsInvoke("WZ:" + width + ":" + height); - } - function WindowSetMinSize(width, height) { - window.WailsInvoke("Wz:" + width + ":" + height); - } - function WindowSetAlwaysOnTop(b) { - window.WailsInvoke("WATP:" + (b ? "1" : "0")); - } - function WindowSetPosition(x, y) { - window.WailsInvoke("Wp:" + x + ":" + y); - } - function WindowGetPosition() { - return Call(":wails:WindowGetPos"); - } - function WindowHide() { - window.WailsInvoke("WH"); - } - function WindowShow() { - window.WailsInvoke("WS"); - } - function WindowMaximise() { - window.WailsInvoke("WM"); - } - function WindowToggleMaximise() { - window.WailsInvoke("Wt"); - } - function WindowUnmaximise() { - window.WailsInvoke("WU"); - } - function WindowIsMaximised() { - return Call(":wails:WindowIsMaximised"); - } - function WindowMinimise() { - window.WailsInvoke("Wm"); - } - function WindowUnminimise() { - window.WailsInvoke("Wu"); - } - function WindowIsMinimised() { - return Call(":wails:WindowIsMinimised"); - } - function WindowIsNormal() { - return Call(":wails:WindowIsNormal"); - } - function WindowSetBackgroundColour(R, G, B, A) { - let rgba = JSON.stringify({ r: R || 0, g: G || 0, b: B || 0, a: A || 255 }); - window.WailsInvoke("Wr:" + rgba); - } - - // desktop/screen.js - var screen_exports = {}; - __export(screen_exports, { - ScreenGetAll: () => ScreenGetAll - }); - function ScreenGetAll() { - return Call(":wails:ScreenGetAll"); - } - - // desktop/browser.js - var browser_exports = {}; - __export(browser_exports, { - BrowserOpenURL: () => BrowserOpenURL - }); - function BrowserOpenURL(url) { - window.WailsInvoke("BO:" + url); - } - - // desktop/clipboard.js - var clipboard_exports = {}; - __export(clipboard_exports, { - ClipboardGetText: () => ClipboardGetText, - ClipboardSetText: () => ClipboardSetText - }); - function ClipboardSetText(text) { - return Call(":wails:ClipboardSetText", [text]); - } - function ClipboardGetText() { - return Call(":wails:ClipboardGetText"); - } - - // desktop/draganddrop.js - var draganddrop_exports = {}; - __export(draganddrop_exports, { - CanResolveFilePaths: () => CanResolveFilePaths, - OnFileDrop: () => OnFileDrop, - OnFileDropOff: () => OnFileDropOff, - ResolveFilePaths: () => ResolveFilePaths - }); - var flags = { - registered: false, - defaultUseDropTarget: true, - useDropTarget: true, - nextDeactivate: null, - nextDeactivateTimeout: null - }; - var DROP_TARGET_ACTIVE = "wails-drop-target-active"; - function checkStyleDropTarget(style) { - const cssDropValue = style.getPropertyValue(window.wails.flags.cssDropProperty).trim(); - if (cssDropValue) { - if (cssDropValue === window.wails.flags.cssDropValue) { - return true; - } - return false; - } - return false; - } - function onDragOver(e) { - const isFileDrop = e.dataTransfer.types.includes("Files"); - if (!isFileDrop) { - return; - } - e.preventDefault(); - e.dataTransfer.dropEffect = "copy"; - if (!window.wails.flags.enableWailsDragAndDrop) { - return; - } - if (!flags.useDropTarget) { - return; - } - const element = e.target; - if (flags.nextDeactivate) - flags.nextDeactivate(); - if (!element || !checkStyleDropTarget(getComputedStyle(element))) { - return; - } - let currentElement = element; - while (currentElement) { - if (checkStyleDropTarget(getComputedStyle(currentElement))) { - currentElement.classList.add(DROP_TARGET_ACTIVE); - } - currentElement = currentElement.parentElement; - } - } - function onDragLeave(e) { - const isFileDrop = e.dataTransfer.types.includes("Files"); - if (!isFileDrop) { - return; - } - e.preventDefault(); - if (!window.wails.flags.enableWailsDragAndDrop) { - return; - } - if (!flags.useDropTarget) { - return; - } - if (!e.target || !checkStyleDropTarget(getComputedStyle(e.target))) { - return null; - } - if (flags.nextDeactivate) - flags.nextDeactivate(); - flags.nextDeactivate = () => { - Array.from(document.getElementsByClassName(DROP_TARGET_ACTIVE)).forEach((el) => el.classList.remove(DROP_TARGET_ACTIVE)); - flags.nextDeactivate = null; - if (flags.nextDeactivateTimeout) { - clearTimeout(flags.nextDeactivateTimeout); - flags.nextDeactivateTimeout = null; - } - }; - flags.nextDeactivateTimeout = setTimeout(() => { - if (flags.nextDeactivate) - flags.nextDeactivate(); - }, 50); - } - function onDrop(e) { - const isFileDrop = e.dataTransfer.types.includes("Files"); - if (!isFileDrop) { - return; - } - e.preventDefault(); - if (!window.wails.flags.enableWailsDragAndDrop) { - return; - } - if (CanResolveFilePaths()) { - let files = []; - if (e.dataTransfer.items) { - files = [...e.dataTransfer.items].map((item, i) => { - if (item.kind === "file") { - return item.getAsFile(); - } - }); - } else { - files = [...e.dataTransfer.files]; - } - window.runtime.ResolveFilePaths(e.x, e.y, files); - } - if (!flags.useDropTarget) { - return; - } - if (flags.nextDeactivate) - flags.nextDeactivate(); - Array.from(document.getElementsByClassName(DROP_TARGET_ACTIVE)).forEach((el) => el.classList.remove(DROP_TARGET_ACTIVE)); - } - function CanResolveFilePaths() { - return window.chrome?.webview?.postMessageWithAdditionalObjects != null; - } - function ResolveFilePaths(x, y, files) { - if (window.chrome?.webview?.postMessageWithAdditionalObjects) { - chrome.webview.postMessageWithAdditionalObjects(`file:drop:${x}:${y}`, files); - } - } - function OnFileDrop(callback, useDropTarget) { - if (typeof callback !== "function") { - console.error("DragAndDropCallback is not a function"); - return; - } - if (flags.registered) { - return; - } - flags.registered = true; - const uDTPT = typeof useDropTarget; - flags.useDropTarget = uDTPT === "undefined" || uDTPT !== "boolean" ? flags.defaultUseDropTarget : useDropTarget; - window.addEventListener("dragover", onDragOver); - window.addEventListener("dragleave", onDragLeave); - window.addEventListener("drop", onDrop); - let cb = callback; - if (flags.useDropTarget) { - cb = function(x, y, paths) { - const element = document.elementFromPoint(x, y); - if (!element || !checkStyleDropTarget(getComputedStyle(element))) { - return null; - } - callback(x, y, paths); - }; - } - EventsOn("wails:file-drop", cb); - } - function OnFileDropOff() { - window.removeEventListener("dragover", onDragOver); - window.removeEventListener("dragleave", onDragLeave); - window.removeEventListener("drop", onDrop); - EventsOff("wails:file-drop"); - flags.registered = false; - } - - // desktop/contextmenu.js - function processDefaultContextMenu(event) { - const element = event.target; - const computedStyle = window.getComputedStyle(element); - const defaultContextMenuAction = computedStyle.getPropertyValue("--default-contextmenu").trim(); - switch (defaultContextMenuAction) { - case "show": - return; - case "hide": - event.preventDefault(); - return; - default: - if (element.isContentEditable) { - return; - } - const selection = window.getSelection(); - const hasSelection = 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) === element) { - return; - } - } - } - } - if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") { - if (hasSelection || !element.readOnly && !element.disabled) { - return; - } - } - event.preventDefault(); - } - } - - // desktop/notifications.js - var notifications_exports = {}; - __export(notifications_exports, { - CheckNotificationAuthorization: () => CheckNotificationAuthorization, - CleanupNotifications: () => CleanupNotifications, - InitializeNotifications: () => InitializeNotifications, - IsNotificationAvailable: () => IsNotificationAvailable, - RegisterNotificationCategory: () => RegisterNotificationCategory, - RemoveAllDeliveredNotifications: () => RemoveAllDeliveredNotifications, - RemoveAllPendingNotifications: () => RemoveAllPendingNotifications, - RemoveDeliveredNotification: () => RemoveDeliveredNotification, - RemoveNotification: () => RemoveNotification, - RemoveNotificationCategory: () => RemoveNotificationCategory, - RemovePendingNotification: () => RemovePendingNotification, - RequestNotificationAuthorization: () => RequestNotificationAuthorization, - SendNotification: () => SendNotification, - SendNotificationWithActions: () => SendNotificationWithActions - }); - function InitializeNotifications() { - return Call(":wails:InitializeNotifications"); - } - function CleanupNotifications() { - return Call(":wails:CleanupNotifications"); - } - function IsNotificationAvailable() { - return Call(":wails:IsNotificationAvailable"); - } - function RequestNotificationAuthorization() { - return Call(":wails:RequestNotificationAuthorization"); - } - function CheckNotificationAuthorization() { - return Call(":wails:CheckNotificationAuthorization"); - } - function SendNotification(options) { - return Call(":wails:SendNotification", [options]); - } - function SendNotificationWithActions(options) { - return Call(":wails:SendNotificationWithActions", [options]); - } - function RegisterNotificationCategory(category) { - return Call(":wails:RegisterNotificationCategory", [category]); - } - function RemoveNotificationCategory(categoryId) { - return Call(":wails:RemoveNotificationCategory", [categoryId]); - } - function RemoveAllPendingNotifications() { - return Call(":wails:RemoveAllPendingNotifications"); - } - function RemovePendingNotification(identifier) { - return Call(":wails:RemovePendingNotification", [identifier]); - } - function RemoveAllDeliveredNotifications() { - return Call(":wails:RemoveAllDeliveredNotifications"); - } - function RemoveDeliveredNotification(identifier) { - return Call(":wails:RemoveDeliveredNotification", [identifier]); - } - function RemoveNotification(identifier) { - return Call(":wails:RemoveNotification", [identifier]); - } - - // desktop/main.js - function Quit() { - window.WailsInvoke("Q"); - } - function Show() { - window.WailsInvoke("S"); - } - function Hide() { - window.WailsInvoke("H"); - } - function Environment() { - return Call(":wails:Environment"); - } - window.runtime = { - ...log_exports, - ...window_exports, - ...browser_exports, - ...screen_exports, - ...clipboard_exports, - ...draganddrop_exports, - ...notifications_exports, - EventsOn, - EventsOnce, - EventsOnMultiple, - EventsEmit, - EventsOff, - EventsOffAll, - Environment, - Show, - Hide, - Quit - }; - window.wails = { - Callback, - EventsNotify, - SetBindings, - eventListeners, - callbacks, - flags: { - disableScrollbarDrag: false, - disableDefaultContextMenu: false, - enableResize: false, - defaultCursor: null, - borderThickness: 6, - shouldDrag: false, - deferDragToMouseMove: true, - cssDragProperty: "--wails-draggable", - cssDragValue: "drag", - cssDropProperty: "--wails-drop-target", - cssDropValue: "drop", - enableWailsDragAndDrop: false - } - }; - if (window.wailsbindings) { - window.wails.SetBindings(window.wailsbindings); - delete window.wails.SetBindings; - } - if (false) { - delete window.wailsbindings; - } - var dragTest = function(e) { - var val = window.getComputedStyle(e.target).getPropertyValue(window.wails.flags.cssDragProperty); - if (val) { - val = val.trim(); - } - if (val !== window.wails.flags.cssDragValue) { - return false; - } - if (e.buttons !== 1) { - return false; - } - if (e.detail !== 1) { - return false; - } - return true; - }; - window.wails.setCSSDragProperties = function(property, value) { - window.wails.flags.cssDragProperty = property; - window.wails.flags.cssDragValue = value; - }; - window.wails.setCSSDropProperties = function(property, value) { - window.wails.flags.cssDropProperty = property; - window.wails.flags.cssDropValue = value; - }; - window.addEventListener("mousedown", (e) => { - if (window.wails.flags.resizeEdge) { - window.WailsInvoke("resize:" + window.wails.flags.resizeEdge); - e.preventDefault(); - return; - } - if (dragTest(e)) { - if (window.wails.flags.disableScrollbarDrag) { - if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) { - return; - } - } - if (window.wails.flags.deferDragToMouseMove) { - window.wails.flags.shouldDrag = true; - } else { - e.preventDefault(); - window.WailsInvoke("drag"); - } - return; - } else { - window.wails.flags.shouldDrag = false; - } - }); - window.addEventListener("mouseup", () => { - window.wails.flags.shouldDrag = false; - }); - function setResize(cursor) { - document.documentElement.style.cursor = cursor || window.wails.flags.defaultCursor; - window.wails.flags.resizeEdge = cursor; - } - window.addEventListener("mousemove", function(e) { - if (window.wails.flags.shouldDrag) { - window.wails.flags.shouldDrag = false; - let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; - if (mousePressed > 0) { - window.WailsInvoke("drag"); - return; - } - } - if (!window.wails.flags.enableResize) { - return; - } - if (window.wails.flags.defaultCursor == null) { - window.wails.flags.defaultCursor = document.documentElement.style.cursor; - } - if (window.outerWidth - e.clientX < window.wails.flags.borderThickness && window.outerHeight - e.clientY < window.wails.flags.borderThickness) { - document.documentElement.style.cursor = "se-resize"; - } - let rightBorder = window.outerWidth - e.clientX < window.wails.flags.borderThickness; - let leftBorder = e.clientX < window.wails.flags.borderThickness; - let topBorder = e.clientY < window.wails.flags.borderThickness; - let bottomBorder = window.outerHeight - e.clientY < window.wails.flags.borderThickness; - if (!leftBorder && !rightBorder && !topBorder && !bottomBorder && window.wails.flags.resizeEdge !== void 0) { - setResize(); - } else if (rightBorder && bottomBorder) - setResize("se-resize"); - else if (leftBorder && bottomBorder) - setResize("sw-resize"); - else if (leftBorder && topBorder) - setResize("nw-resize"); - else if (topBorder && rightBorder) - 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"); - }); - window.addEventListener("contextmenu", function(e) { - if (true) - return; - if (window.wails.flags.disableDefaultContextMenu) { - e.preventDefault(); - } else { - processDefaultContextMenu(e); - } - }); - window.WailsInvoke("runtime:ready"); -})(); -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9ldmVudHMuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL2JpbmRpbmdzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3Avc2NyZWVuLmpzIiwgImRlc2t0b3AvYnJvd3Nlci5qcyIsICJkZXNrdG9wL2NsaXBib2FyZC5qcyIsICJkZXNrdG9wL2RyYWdhbmRkcm9wLmpzIiwgImRlc2t0b3AvY29udGV4dG1lbnUuanMiLCAiZGVza3RvcC9ub3RpZmljYXRpb25zLmpzIiwgImRlc2t0b3AvbWFpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfICAgICAgIF9fICAgICAgXyBfX1xufCB8ICAgICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuLyoqXG4gKiBTZW5kcyBhIGxvZyBtZXNzYWdlIHRvIHRoZSBiYWNrZW5kIHdpdGggdGhlIGdpdmVuIGxldmVsICsgbWVzc2FnZVxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBsZXZlbFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZnVuY3Rpb24gc2VuZExvZ01lc3NhZ2UobGV2ZWwsIG1lc3NhZ2UpIHtcblxuXHQvLyBMb2cgTWVzc2FnZSBmb3JtYXQ6XG5cdC8vIGxbdHlwZV1bbWVzc2FnZV1cblx0d2luZG93LldhaWxzSW52b2tlKCdMJyArIGxldmVsICsgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiB0cmFjZSBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gTG9nVHJhY2UobWVzc2FnZSkge1xuXHRzZW5kTG9nTWVzc2FnZSgnVCcsIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIExvZyB0aGUgZ2l2ZW4gbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ1ByaW50KG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ1AnLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIGRlYnVnIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dEZWJ1ZyhtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdEJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiBpbmZvIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dJbmZvKG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ0knLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIHdhcm5pbmcgbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ1dhcm5pbmcobWVzc2FnZSkge1xuXHRzZW5kTG9nTWVzc2FnZSgnVycsIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIExvZyB0aGUgZ2l2ZW4gZXJyb3IgbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ0Vycm9yKG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ0UnLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIGZhdGFsIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dGYXRhbChtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdGJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogU2V0cyB0aGUgTG9nIGxldmVsIHRvIHRoZSBnaXZlbiBsb2cgbGV2ZWxcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge251bWJlcn0gbG9nbGV2ZWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNldExvZ0xldmVsKGxvZ2xldmVsKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdTJywgbG9nbGV2ZWwpO1xufVxuXG4vLyBMb2cgbGV2ZWxzXG5leHBvcnQgY29uc3QgTG9nTGV2ZWwgPSB7XG5cdFRSQUNFOiAxLFxuXHRERUJVRzogMixcblx0SU5GTzogMyxcblx0V0FSTklORzogNCxcblx0RVJST1I6IDUsXG59O1xuIiwgIi8qXG4gXyAgICAgICBfXyAgICAgIF8gX19cbnwgfCAgICAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xuXG4vLyBEZWZpbmVzIGEgc2luZ2xlIGxpc3RlbmVyIHdpdGggYSBtYXhpbXVtIG51bWJlciBvZiB0aW1lcyB0byBjYWxsYmFja1xuXG4vKipcbiAqIFRoZSBMaXN0ZW5lciBjbGFzcyBkZWZpbmVzIGEgbGlzdGVuZXIhIDotKVxuICpcbiAqIEBjbGFzcyBMaXN0ZW5lclxuICovXG5jbGFzcyBMaXN0ZW5lciB7XG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhbiBpbnN0YW5jZSBvZiBMaXN0ZW5lci5cbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gICAgICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcbiAgICAgKiBAcGFyYW0ge251bWJlcn0gbWF4Q2FsbGJhY2tzXG4gICAgICogQG1lbWJlcm9mIExpc3RlbmVyXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgICAgIHRoaXMuZXZlbnROYW1lID0gZXZlbnROYW1lO1xuICAgICAgICAvLyBEZWZhdWx0IG9mIC0xIG1lYW5zIGluZmluaXRlXG4gICAgICAgIHRoaXMubWF4Q2FsbGJhY2tzID0gbWF4Q2FsbGJhY2tzIHx8IC0xO1xuICAgICAgICAvLyBDYWxsYmFjayBpbnZva2VzIHRoZSBjYWxsYmFjayB3aXRoIHRoZSBnaXZlbiBkYXRhXG4gICAgICAgIC8vIFJldHVybnMgdHJ1ZSBpZiB0aGlzIGxpc3RlbmVyIHNob3VsZCBiZSBkZXN0cm95ZWRcbiAgICAgICAgdGhpcy5DYWxsYmFjayA9IChkYXRhKSA9PiB7XG4gICAgICAgICAgICBjYWxsYmFjay5hcHBseShudWxsLCBkYXRhKTtcbiAgICAgICAgICAgIC8vIElmIG1heENhbGxiYWNrcyBpcyBpbmZpbml0ZSwgcmV0dXJuIGZhbHNlIChkbyBub3QgZGVzdHJveSlcbiAgICAgICAgICAgIGlmICh0aGlzLm1heENhbGxiYWNrcyA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBEZWNyZW1lbnQgbWF4Q2FsbGJhY2tzLiBSZXR1cm4gdHJ1ZSBpZiBub3cgMCwgb3RoZXJ3aXNlIGZhbHNlXG4gICAgICAgICAgICB0aGlzLm1heENhbGxiYWNrcyAtPSAxO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMubWF4Q2FsbGJhY2tzID09PSAwO1xuICAgICAgICB9O1xuICAgIH1cbn1cblxuZXhwb3J0IGNvbnN0IGV2ZW50TGlzdGVuZXJzID0ge307XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGBtYXhDYWxsYmFja3NgIHRpbWVzIGJlZm9yZSBiZWluZyBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXSA9IGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gfHwgW107XG4gICAgY29uc3QgdGhpc0xpc3RlbmVyID0gbmV3IExpc3RlbmVyKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcyk7XG4gICAgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXS5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgcmV0dXJuICgpID0+IGxpc3RlbmVyT2ZmKHRoaXNMaXN0ZW5lcik7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGV2ZXJ5IHRpbWUgdGhlIGV2ZW50IGlzIGVtaXR0ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09uKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcbiAgICByZXR1cm4gRXZlbnRzT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAtMSk7XG59XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIG9uY2UgdGhlbiBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9ufSBjYWxsYmFja1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09uY2UoZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBFdmVudHNPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG5mdW5jdGlvbiBub3RpZnlMaXN0ZW5lcnMoZXZlbnREYXRhKSB7XG5cbiAgICAvLyBHZXQgdGhlIGV2ZW50IG5hbWVcbiAgICBsZXQgZXZlbnROYW1lID0gZXZlbnREYXRhLm5hbWU7XG5cbiAgICAvLyBLZWVwIGEgbGlzdCBvZiBsaXN0ZW5lciBpbmRleGVzIHRvIGRlc3Ryb3lcbiAgICBjb25zdCBuZXdFdmVudExpc3RlbmVyTGlzdCA9IGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0/LnNsaWNlKCkgfHwgW107XG5cbiAgICAvLyBDaGVjayBpZiB3ZSBoYXZlIGFueSBsaXN0ZW5lcnMgZm9yIHRoaXMgZXZlbnRcbiAgICBpZiAobmV3RXZlbnRMaXN0ZW5lckxpc3QubGVuZ3RoKSB7XG5cbiAgICAgICAgLy8gSXRlcmF0ZSBsaXN0ZW5lcnNcbiAgICAgICAgZm9yIChsZXQgY291bnQgPSBuZXdFdmVudExpc3RlbmVyTGlzdC5sZW5ndGggLSAxOyBjb3VudCA+PSAwOyBjb3VudCAtPSAxKSB7XG5cbiAgICAgICAgICAgIC8vIEdldCBuZXh0IGxpc3RlbmVyXG4gICAgICAgICAgICBjb25zdCBsaXN0ZW5lciA9IG5ld0V2ZW50TGlzdGVuZXJMaXN0W2NvdW50XTtcblxuICAgICAgICAgICAgbGV0IGRhdGEgPSBldmVudERhdGEuZGF0YTtcblxuICAgICAgICAgICAgLy8gRG8gdGhlIGNhbGxiYWNrXG4gICAgICAgICAgICBjb25zdCBkZXN0cm95ID0gbGlzdGVuZXIuQ2FsbGJhY2soZGF0YSk7XG4gICAgICAgICAgICBpZiAoZGVzdHJveSkge1xuICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBsaXN0ZW5lciBpbmRpY2F0ZWQgdG8gZGVzdHJveSBpdHNlbGYsIGFkZCBpdCB0byB0aGUgZGVzdHJveSBsaXN0XG4gICAgICAgICAgICAgICAgbmV3RXZlbnRMaXN0ZW5lckxpc3Quc3BsaWNlKGNvdW50LCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFVwZGF0ZSBjYWxsYmFja3Mgd2l0aCBuZXcgbGlzdCBvZiBsaXN0ZW5lcnNcbiAgICAgICAgaWYgKG5ld0V2ZW50TGlzdGVuZXJMaXN0Lmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gPSBuZXdFdmVudExpc3RlbmVyTGlzdDtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLyoqXG4gKiBOb3RpZnkgaW5mb3JtcyBmcm9udGVuZCBsaXN0ZW5lcnMgdGhhdCBhbiBldmVudCB3YXMgZW1pdHRlZCB3aXRoIHRoZSBnaXZlbiBkYXRhXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG5vdGlmeU1lc3NhZ2UgLSBlbmNvZGVkIG5vdGlmaWNhdGlvbiBtZXNzYWdlXG5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c05vdGlmeShub3RpZnlNZXNzYWdlKSB7XG4gICAgLy8gUGFyc2UgdGhlIG1lc3NhZ2VcbiAgICBsZXQgbWVzc2FnZTtcbiAgICB0cnkge1xuICAgICAgICBtZXNzYWdlID0gSlNPTi5wYXJzZShub3RpZnlNZXNzYWdlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnN0IGVycm9yID0gJ0ludmFsaWQgSlNPTiBwYXNzZWQgdG8gTm90aWZ5OiAnICsgbm90aWZ5TWVzc2FnZTtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVycm9yKTtcbiAgICB9XG4gICAgbm90aWZ5TGlzdGVuZXJzKG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIEVtaXQgYW4gZXZlbnQgd2l0aCB0aGUgZ2l2ZW4gbmFtZSBhbmQgZGF0YVxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c0VtaXQoZXZlbnROYW1lKSB7XG5cbiAgICBjb25zdCBwYXlsb2FkID0ge1xuICAgICAgICBuYW1lOiBldmVudE5hbWUsXG4gICAgICAgIGRhdGE6IFtdLnNsaWNlLmFwcGx5KGFyZ3VtZW50cykuc2xpY2UoMSksXG4gICAgfTtcblxuICAgIC8vIE5vdGlmeSBKUyBsaXN0ZW5lcnNcbiAgICBub3RpZnlMaXN0ZW5lcnMocGF5bG9hZCk7XG5cbiAgICAvLyBOb3RpZnkgR28gbGlzdGVuZXJzXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdFRScgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG59XG5cbmZ1bmN0aW9uIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSkge1xuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lcnNcbiAgICBkZWxldGUgZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXTtcblxuICAgIC8vIE5vdGlmeSBHbyBsaXN0ZW5lcnNcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ0VYJyArIGV2ZW50TmFtZSk7XG59XG5cbi8qKlxuICogT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT24sXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVyZXMgY2FuIGJlIHVucmVnaXN0ZXJlZCB2aWEgYGFkZGl0aW9uYWxFdmVudE5hbWVzYFxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSAgey4uLnN0cmluZ30gYWRkaXRpb25hbEV2ZW50TmFtZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09mZihldmVudE5hbWUsIC4uLmFkZGl0aW9uYWxFdmVudE5hbWVzKSB7XG4gICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKVxuXG4gICAgaWYgKGFkZGl0aW9uYWxFdmVudE5hbWVzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgYWRkaXRpb25hbEV2ZW50TmFtZXMuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKVxuICAgICAgICB9KVxuICAgIH1cbn1cblxuLyoqXG4gKiBPZmYgdW5yZWdpc3RlcnMgYWxsIGV2ZW50IGxpc3RlbmVycyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgd2l0aCBPblxuICovXG4gZXhwb3J0IGZ1bmN0aW9uIEV2ZW50c09mZkFsbCgpIHtcbiAgICBjb25zdCBldmVudE5hbWVzID0gT2JqZWN0LmtleXMoZXZlbnRMaXN0ZW5lcnMpO1xuICAgIGV2ZW50TmFtZXMuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudE5hbWUpXG4gICAgfSlcbn1cblxuLyoqXG4gKiBsaXN0ZW5lck9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIEV2ZW50c09uXG4gKlxuICogQHBhcmFtIHtMaXN0ZW5lcn0gbGlzdGVuZXJcbiAqL1xuIGZ1bmN0aW9uIGxpc3RlbmVyT2ZmKGxpc3RlbmVyKSB7XG4gICAgY29uc3QgZXZlbnROYW1lID0gbGlzdGVuZXIuZXZlbnROYW1lO1xuICAgIGlmIChldmVudExpc3RlbmVyc1tldmVudE5hbWVdID09PSB1bmRlZmluZWQpIHJldHVybjtcblxuICAgIC8vIFJlbW92ZSBsb2NhbCBsaXN0ZW5lclxuICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gPSBldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmZpbHRlcihsID0+IGwgIT09IGxpc3RlbmVyKTtcblxuICAgIC8vIENsZWFuIHVwIGlmIHRoZXJlIGFyZSBubyBldmVudCBsaXN0ZW5lcnMgbGVmdFxuICAgIGlmIChldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudE5hbWUpO1xuICAgIH1cbn1cbiIsICIvKlxuIF8gICAgICAgX18gICAgICBfIF9fXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuZXhwb3J0IGNvbnN0IGNhbGxiYWNrcyA9IHt9O1xuXG4vKipcbiAqIFJldHVybnMgYSBudW1iZXIgZnJvbSB0aGUgbmF0aXZlIGJyb3dzZXIgcmFuZG9tIGZ1bmN0aW9uXG4gKlxuICogQHJldHVybnMgbnVtYmVyXG4gKi9cbmZ1bmN0aW9uIGNyeXB0b1JhbmRvbSgpIHtcblx0dmFyIGFycmF5ID0gbmV3IFVpbnQzMkFycmF5KDEpO1xuXHRyZXR1cm4gd2luZG93LmNyeXB0by5nZXRSYW5kb21WYWx1ZXMoYXJyYXkpWzBdO1xufVxuXG4vKipcbiAqIFJldHVybnMgYSBudW1iZXIgdXNpbmcgZGEgb2xkLXNrb29sIE1hdGguUmFuZG9tXG4gKiBJIGxpa2VzIHRvIGNhbGwgaXQgTE9MUmFuZG9tXG4gKlxuICogQHJldHVybnMgbnVtYmVyXG4gKi9cbmZ1bmN0aW9uIGJhc2ljUmFuZG9tKCkge1xuXHRyZXR1cm4gTWF0aC5yYW5kb20oKSAqIDkwMDcxOTkyNTQ3NDA5OTE7XG59XG5cbi8vIFBpY2sgYSByYW5kb20gbnVtYmVyIGZ1bmN0aW9uIGJhc2VkIG9uIGJyb3dzZXIgY2FwYWJpbGl0eVxudmFyIHJhbmRvbUZ1bmM7XG5pZiAod2luZG93LmNyeXB0bykge1xuXHRyYW5kb21GdW5jID0gY3J5cHRvUmFuZG9tO1xufSBlbHNlIHtcblx0cmFuZG9tRnVuYyA9IGJhc2ljUmFuZG9tO1xufVxuXG5cbi8qKlxuICogQ2FsbCBzZW5kcyBhIG1lc3NhZ2UgdG8gdGhlIGJhY2tlbmQgdG8gY2FsbCB0aGUgYmluZGluZyB3aXRoIHRoZVxuICogZ2l2ZW4gZGF0YS4gQSBwcm9taXNlIGlzIHJldHVybmVkIGFuZCB3aWxsIGJlIGNvbXBsZXRlZCB3aGVuIHRoZVxuICogYmFja2VuZCByZXNwb25kcy4gVGhpcyB3aWxsIGJlIHJlc29sdmVkIHdoZW4gdGhlIGNhbGwgd2FzIHN1Y2Nlc3NmdWxcbiAqIG9yIHJlamVjdGVkIGlmIGFuIGVycm9yIGlzIHBhc3NlZCBiYWNrLlxuICogVGhlcmUgaXMgYSB0aW1lb3V0IG1lY2hhbmlzbS4gSWYgdGhlIGNhbGwgZG9lc24ndCByZXNwb25kIGluIHRoZSBnaXZlblxuICogdGltZSAoaW4gbWlsbGlzZWNvbmRzKSB0aGVuIHRoZSBwcm9taXNlIGlzIHJlamVjdGVkLlxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lXG4gKiBAcGFyYW0ge2FueT19IGFyZ3NcbiAqIEBwYXJhbSB7bnVtYmVyPX0gdGltZW91dFxuICogQHJldHVybnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENhbGwobmFtZSwgYXJncywgdGltZW91dCkge1xuXG5cdC8vIFRpbWVvdXQgaW5maW5pdGUgYnkgZGVmYXVsdFxuXHRpZiAodGltZW91dCA9PSBudWxsKSB7XG5cdFx0dGltZW91dCA9IDA7XG5cdH1cblxuXHQvLyBDcmVhdGUgYSBwcm9taXNlXG5cdHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XG5cblx0XHQvLyBDcmVhdGUgYSB1bmlxdWUgY2FsbGJhY2tJRFxuXHRcdHZhciBjYWxsYmFja0lEO1xuXHRcdGRvIHtcblx0XHRcdGNhbGxiYWNrSUQgPSBuYW1lICsgJy0nICsgcmFuZG9tRnVuYygpO1xuXHRcdH0gd2hpbGUgKGNhbGxiYWNrc1tjYWxsYmFja0lEXSk7XG5cblx0XHR2YXIgdGltZW91dEhhbmRsZTtcblx0XHQvLyBTZXQgdGltZW91dFxuXHRcdGlmICh0aW1lb3V0ID4gMCkge1xuXHRcdFx0dGltZW91dEhhbmRsZSA9IHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuXHRcdFx0XHRyZWplY3QoRXJyb3IoJ0NhbGwgdG8gJyArIG5hbWUgKyAnIHRpbWVkIG91dC4gUmVxdWVzdCBJRDogJyArIGNhbGxiYWNrSUQpKTtcblx0XHRcdH0sIHRpbWVvdXQpO1xuXHRcdH1cblxuXHRcdC8vIFN0b3JlIGNhbGxiYWNrXG5cdFx0Y2FsbGJhY2tzW2NhbGxiYWNrSURdID0ge1xuXHRcdFx0dGltZW91dEhhbmRsZTogdGltZW91dEhhbmRsZSxcblx0XHRcdHJlamVjdDogcmVqZWN0LFxuXHRcdFx0cmVzb2x2ZTogcmVzb2x2ZVxuXHRcdH07XG5cblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgcGF5bG9hZCA9IHtcblx0XHRcdFx0bmFtZSxcblx0XHRcdFx0YXJncyxcblx0XHRcdFx0Y2FsbGJhY2tJRCxcblx0XHRcdH07XG5cbiAgICAgICAgICAgIC8vIE1ha2UgdGhlIGNhbGxcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnQycgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZVxuICAgICAgICAgICAgY29uc29sZS5lcnJvcihlKTtcbiAgICAgICAgfVxuICAgIH0pO1xufVxuXG53aW5kb3cuT2JmdXNjYXRlZENhbGwgPSAoaWQsIGFyZ3MsIHRpbWVvdXQpID0+IHtcblxuICAgIC8vIFRpbWVvdXQgaW5maW5pdGUgYnkgZGVmYXVsdFxuICAgIGlmICh0aW1lb3V0ID09IG51bGwpIHtcbiAgICAgICAgdGltZW91dCA9IDA7XG4gICAgfVxuXG4gICAgLy8gQ3JlYXRlIGEgcHJvbWlzZVxuICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XG5cbiAgICAgICAgLy8gQ3JlYXRlIGEgdW5pcXVlIGNhbGxiYWNrSURcbiAgICAgICAgdmFyIGNhbGxiYWNrSUQ7XG4gICAgICAgIGRvIHtcbiAgICAgICAgICAgIGNhbGxiYWNrSUQgPSBpZCArICctJyArIHJhbmRvbUZ1bmMoKTtcbiAgICAgICAgfSB3aGlsZSAoY2FsbGJhY2tzW2NhbGxiYWNrSURdKTtcblxuICAgICAgICB2YXIgdGltZW91dEhhbmRsZTtcbiAgICAgICAgLy8gU2V0IHRpbWVvdXRcbiAgICAgICAgaWYgKHRpbWVvdXQgPiAwKSB7XG4gICAgICAgICAgICB0aW1lb3V0SGFuZGxlID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKCdDYWxsIHRvIG1ldGhvZCAnICsgaWQgKyAnIHRpbWVkIG91dC4gUmVxdWVzdCBJRDogJyArIGNhbGxiYWNrSUQpKTtcbiAgICAgICAgICAgIH0sIHRpbWVvdXQpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gU3RvcmUgY2FsbGJhY2tcbiAgICAgICAgY2FsbGJhY2tzW2NhbGxiYWNrSURdID0ge1xuICAgICAgICAgICAgdGltZW91dEhhbmRsZTogdGltZW91dEhhbmRsZSxcbiAgICAgICAgICAgIHJlamVjdDogcmVqZWN0LFxuICAgICAgICAgICAgcmVzb2x2ZTogcmVzb2x2ZVxuICAgICAgICB9O1xuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBwYXlsb2FkID0ge1xuXHRcdFx0XHRpZCxcblx0XHRcdFx0YXJncyxcblx0XHRcdFx0Y2FsbGJhY2tJRCxcblx0XHRcdH07XG5cbiAgICAgICAgICAgIC8vIE1ha2UgdGhlIGNhbGxcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnYycgKyBKU09OLnN0cmluZ2lmeShwYXlsb2FkKSk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZVxuICAgICAgICAgICAgY29uc29sZS5lcnJvcihlKTtcbiAgICAgICAgfVxuICAgIH0pO1xufTtcblxuXG4vKipcbiAqIENhbGxlZCBieSB0aGUgYmFja2VuZCB0byByZXR1cm4gZGF0YSB0byBhIHByZXZpb3VzbHkgY2FsbGVkXG4gKiBiaW5kaW5nIGludm9jYXRpb25cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gaW5jb21pbmdNZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYWxsYmFjayhpbmNvbWluZ01lc3NhZ2UpIHtcblx0Ly8gUGFyc2UgdGhlIG1lc3NhZ2Vcblx0bGV0IG1lc3NhZ2U7XG5cdHRyeSB7XG5cdFx0bWVzc2FnZSA9IEpTT04ucGFyc2UoaW5jb21pbmdNZXNzYWdlKTtcblx0fSBjYXRjaCAoZSkge1xuXHRcdGNvbnN0IGVycm9yID0gYEludmFsaWQgSlNPTiBwYXNzZWQgdG8gY2FsbGJhY2s6ICR7ZS5tZXNzYWdlfS4gTWVzc2FnZTogJHtpbmNvbWluZ01lc3NhZ2V9YDtcblx0XHRydW50aW1lLkxvZ0RlYnVnKGVycm9yKTtcblx0XHR0aHJvdyBuZXcgRXJyb3IoZXJyb3IpO1xuXHR9XG5cdGxldCBjYWxsYmFja0lEID0gbWVzc2FnZS5jYWxsYmFja2lkO1xuXHRsZXQgY2FsbGJhY2tEYXRhID0gY2FsbGJhY2tzW2NhbGxiYWNrSURdO1xuXHRpZiAoIWNhbGxiYWNrRGF0YSkge1xuXHRcdGNvbnN0IGVycm9yID0gYENhbGxiYWNrICcke2NhbGxiYWNrSUR9JyBub3QgcmVnaXN0ZXJlZCEhIWA7XG5cdFx0Y29uc29sZS5lcnJvcihlcnJvcik7IC8vIGVzbGludC1kaXNhYmxlLWxpbmVcblx0XHR0aHJvdyBuZXcgRXJyb3IoZXJyb3IpO1xuXHR9XG5cdGNsZWFyVGltZW91dChjYWxsYmFja0RhdGEudGltZW91dEhhbmRsZSk7XG5cblx0ZGVsZXRlIGNhbGxiYWNrc1tjYWxsYmFja0lEXTtcblxuXHRpZiAobWVzc2FnZS5lcnJvcikge1xuXHRcdGNhbGxiYWNrRGF0YS5yZWplY3QobWVzc2FnZS5lcnJvcik7XG5cdH0gZWxzZSB7XG5cdFx0Y2FsbGJhY2tEYXRhLnJlc29sdmUobWVzc2FnZS5yZXN1bHQpO1xuXHR9XG59XG4iLCAiLypcbiBfICAgICAgIF9fICAgICAgXyBfXyAgICBcbnwgfCAgICAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKSBcbnxfXy98X18vXFxfXyxfL18vXy9fX19fLyAgXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuLyoganNoaW50IGVzdmVyc2lvbjogNiAqL1xuXG5pbXBvcnQge0NhbGx9IGZyb20gJy4vY2FsbHMnO1xuXG4vLyBUaGlzIGlzIHdoZXJlIHdlIGJpbmQgZ28gbWV0aG9kIHdyYXBwZXJzXG53aW5kb3cuZ28gPSB7fTtcblxuZXhwb3J0IGZ1bmN0aW9uIFNldEJpbmRpbmdzKGJpbmRpbmdzTWFwKSB7XG5cdHRyeSB7XG5cdFx0YmluZGluZ3NNYXAgPSBKU09OLnBhcnNlKGJpbmRpbmdzTWFwKTtcblx0fSBjYXRjaCAoZSkge1xuXHRcdGNvbnNvbGUuZXJyb3IoZSk7XG5cdH1cblxuXHQvLyBJbml0aWFsaXNlIHRoZSBiaW5kaW5ncyBtYXBcblx0d2luZG93LmdvID0gd2luZG93LmdvIHx8IHt9O1xuXG5cdC8vIEl0ZXJhdGUgcGFja2FnZSBuYW1lc1xuXHRPYmplY3Qua2V5cyhiaW5kaW5nc01hcCkuZm9yRWFjaCgocGFja2FnZU5hbWUpID0+IHtcblxuXHRcdC8vIENyZWF0ZSBpbm5lciBtYXAgaWYgaXQgZG9lc24ndCBleGlzdFxuXHRcdHdpbmRvdy5nb1twYWNrYWdlTmFtZV0gPSB3aW5kb3cuZ29bcGFja2FnZU5hbWVdIHx8IHt9O1xuXG5cdFx0Ly8gSXRlcmF0ZSBzdHJ1Y3QgbmFtZXNcblx0XHRPYmplY3Qua2V5cyhiaW5kaW5nc01hcFtwYWNrYWdlTmFtZV0pLmZvckVhY2goKHN0cnVjdE5hbWUpID0+IHtcblxuXHRcdFx0Ly8gQ3JlYXRlIGlubmVyIG1hcCBpZiBpdCBkb2Vzbid0IGV4aXN0XG5cdFx0XHR3aW5kb3cuZ29bcGFja2FnZU5hbWVdW3N0cnVjdE5hbWVdID0gd2luZG93LmdvW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXSB8fCB7fTtcblxuXHRcdFx0T2JqZWN0LmtleXMoYmluZGluZ3NNYXBbcGFja2FnZU5hbWVdW3N0cnVjdE5hbWVdKS5mb3JFYWNoKChtZXRob2ROYW1lKSA9PiB7XG5cblx0XHRcdFx0d2luZG93LmdvW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXVttZXRob2ROYW1lXSA9IGZ1bmN0aW9uICgpIHtcblxuXHRcdFx0XHRcdC8vIE5vIHRpbWVvdXQgYnkgZGVmYXVsdFxuXHRcdFx0XHRcdGxldCB0aW1lb3V0ID0gMDtcblxuXHRcdFx0XHRcdC8vIEFjdHVhbCBmdW5jdGlvblxuXHRcdFx0XHRcdGZ1bmN0aW9uIGR5bmFtaWMoKSB7XG5cdFx0XHRcdFx0XHRjb25zdCBhcmdzID0gW10uc2xpY2UuY2FsbChhcmd1bWVudHMpO1xuXHRcdFx0XHRcdFx0cmV0dXJuIENhbGwoW3BhY2thZ2VOYW1lLCBzdHJ1Y3ROYW1lLCBtZXRob2ROYW1lXS5qb2luKCcuJyksIGFyZ3MsIHRpbWVvdXQpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdC8vIEFsbG93IHNldHRpbmcgdGltZW91dCB0byBmdW5jdGlvblxuXHRcdFx0XHRcdGR5bmFtaWMuc2V0VGltZW91dCA9IGZ1bmN0aW9uIChuZXdUaW1lb3V0KSB7XG5cdFx0XHRcdFx0XHR0aW1lb3V0ID0gbmV3VGltZW91dDtcblx0XHRcdFx0XHR9O1xuXG5cdFx0XHRcdFx0Ly8gQWxsb3cgZ2V0dGluZyB0aW1lb3V0IHRvIGZ1bmN0aW9uXG5cdFx0XHRcdFx0ZHluYW1pYy5nZXRUaW1lb3V0ID0gZnVuY3Rpb24gKCkge1xuXHRcdFx0XHRcdFx0cmV0dXJuIHRpbWVvdXQ7XG5cdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdHJldHVybiBkeW5hbWljO1xuXHRcdFx0XHR9KCk7XG5cdFx0XHR9KTtcblx0XHR9KTtcblx0fSk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuXG5pbXBvcnQge0NhbGx9IGZyb20gXCIuL2NhbGxzXCI7XG5cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dSZWxvYWQoKSB7XG4gICAgd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gV2luZG93UmVsb2FkQXBwKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1InKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFN5c3RlbURlZmF1bHRUaGVtZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBU0RUJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRMaWdodFRoZW1lKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FMVCcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0RGFya1RoZW1lKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV0FEVCcpO1xufVxuXG4vKipcbiAqIFBsYWNlIHRoZSB3aW5kb3cgaW4gdGhlIGNlbnRlciBvZiB0aGUgc2NyZWVuXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93Q2VudGVyKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV2MnKTtcbn1cblxuLyoqXG4gKiBTZXRzIHRoZSB3aW5kb3cgdGl0bGVcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gdGl0bGVcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFRpdGxlKHRpdGxlKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXVCcgKyB0aXRsZSk7XG59XG5cbi8qKlxuICogTWFrZXMgdGhlIHdpbmRvdyBnbyBmdWxsc2NyZWVuXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93RnVsbHNjcmVlbigpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dGJyk7XG59XG5cbi8qKlxuICogUmV2ZXJ0cyB0aGUgd2luZG93IGZyb20gZnVsbHNjcmVlblxuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1VuZnVsbHNjcmVlbigpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dmJyk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgc3RhdGUgb2YgdGhlIHdpbmRvdywgaS5lLiB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgaW4gZnVsbCBzY3JlZW4gbW9kZSBvciBub3QuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVGhlIHN0YXRlIG9mIHRoZSB3aW5kb3dcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0lzRnVsbHNjcmVlbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc0Z1bGxzY3JlZW5cIik7XG59XG5cbi8qKlxuICogU2V0IHRoZSBTaXplIG9mIHRoZSB3aW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge251bWJlcn0gd2lkdGhcbiAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldFNpemUod2lkdGgsIGhlaWdodCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3M6JyArIHdpZHRoICsgJzonICsgaGVpZ2h0KTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIFNpemUgb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8e3c6IG51bWJlciwgaDogbnVtYmVyfT59IFRoZSBzaXplIG9mIHRoZSB3aW5kb3dcblxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93R2V0U2l6ZSgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dHZXRTaXplXCIpO1xufVxuXG4vKipcbiAqIFNldCB0aGUgbWF4aW11bSBzaXplIG9mIHRoZSB3aW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge251bWJlcn0gd2lkdGhcbiAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldE1heFNpemUod2lkdGgsIGhlaWdodCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1o6JyArIHdpZHRoICsgJzonICsgaGVpZ2h0KTtcbn1cblxuLyoqXG4gKiBTZXQgdGhlIG1pbmltdW0gc2l6ZSBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRNaW5TaXplKHdpZHRoLCBoZWlnaHQpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1d6OicgKyB3aWR0aCArICc6JyArIGhlaWdodCk7XG59XG5cblxuXG4vKipcbiAqIFNldCB0aGUgd2luZG93IEFsd2F5c09uVG9wIG9yIG5vdCBvbiB0b3BcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRBbHdheXNPblRvcChiKSB7XG5cbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBVFA6JyArIChiID8gJzEnIDogJzAnKSk7XG59XG5cblxuXG5cbi8qKlxuICogU2V0IHRoZSBQb3NpdGlvbiBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IHhcbiAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRQb3NpdGlvbih4LCB5KSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXcDonICsgeCArICc6JyArIHkpO1xufVxuXG4vKipcbiAqIEdldCB0aGUgUG9zaXRpb24gb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8e3g6IG51bWJlciwgeTogbnVtYmVyfT59IFRoZSBwb3NpdGlvbiBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dHZXRQb3NpdGlvbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dHZXRQb3NcIik7XG59XG5cbi8qKlxuICogSGlkZSB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SGlkZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dIJyk7XG59XG5cbi8qKlxuICogU2hvdyB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2hvdygpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dTJyk7XG59XG5cbi8qKlxuICogTWF4aW1pc2UgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd01heGltaXNlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV00nKTtcbn1cblxuLyoqXG4gKiBUb2dnbGUgdGhlIE1heGltaXNlIG9mIHRoZSBXaW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dUb2dnbGVNYXhpbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1d0Jyk7XG59XG5cbi8qKlxuICogVW5tYXhpbWlzZSB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93VW5tYXhpbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dVJyk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgc3RhdGUgb2YgdGhlIHdpbmRvdywgaS5lLiB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgbWF4aW1pc2VkIG9yIG5vdC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPGJvb2xlYW4+fSBUaGUgc3RhdGUgb2YgdGhlIHdpbmRvd1xuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SXNNYXhpbWlzZWQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93SXNNYXhpbWlzZWRcIik7XG59XG5cbi8qKlxuICogTWluaW1pc2UgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd01pbmltaXNlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV20nKTtcbn1cblxuLyoqXG4gKiBVbm1pbmltaXNlIHRoZSBXaW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dVbm1pbmltaXNlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3UnKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBzdGF0ZSBvZiB0aGUgd2luZG93LCBpLmUuIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyBtaW5pbWlzZWQgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc01pbmltaXNlZCgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc01pbmltaXNlZFwiKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBzdGF0ZSBvZiB0aGUgd2luZG93LCBpLmUuIHdoZXRoZXIgdGhlIHdpbmRvdyBpcyBub3JtYWwgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc05vcm1hbCgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpXaW5kb3dJc05vcm1hbFwiKTtcbn1cblxuLyoqXG4gKiBTZXRzIHRoZSBiYWNrZ3JvdW5kIGNvbG91ciBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IFIgUmVkXG4gKiBAcGFyYW0ge251bWJlcn0gRyBHcmVlblxuICogQHBhcmFtIHtudW1iZXJ9IEIgQmx1ZVxuICogQHBhcmFtIHtudW1iZXJ9IEEgQWxwaGFcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldEJhY2tncm91bmRDb2xvdXIoUiwgRywgQiwgQSkge1xuICAgIGxldCByZ2JhID0gSlNPTi5zdHJpbmdpZnkoe3I6IFIgfHwgMCwgZzogRyB8fCAwLCBiOiBCIHx8IDAsIGE6IEEgfHwgMjU1fSk7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXcjonICsgcmdiYSk7XG59XG5cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5cbmltcG9ydCB7Q2FsbH0gZnJvbSBcIi4vY2FsbHNcIjtcblxuXG4vKipcbiAqIEdldHMgdGhlIGFsbCBzY3JlZW5zLiBDYWxsIHRoaXMgYW5ldyBlYWNoIHRpbWUgeW91IHdhbnQgdG8gcmVmcmVzaCBkYXRhIGZyb20gdGhlIHVuZGVybHlpbmcgd2luZG93aW5nIHN5c3RlbS5cbiAqIEBleHBvcnRcbiAqIEB0eXBlZGVmIHtpbXBvcnQoJy4uL3dyYXBwZXIvcnVudGltZScpLlNjcmVlbn0gU2NyZWVuXG4gKiBAcmV0dXJuIHtQcm9taXNlPHtTY3JlZW5bXX0+fSBUaGUgc2NyZWVuc1xuICovXG5leHBvcnQgZnVuY3Rpb24gU2NyZWVuR2V0QWxsKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlNjcmVlbkdldEFsbFwiKTtcbn1cbiIsICIvKipcbiAqIEBkZXNjcmlwdGlvbjogVXNlIHRoZSBzeXN0ZW0gZGVmYXVsdCBicm93c2VyIHRvIG9wZW4gdGhlIHVybFxuICogQHBhcmFtIHtzdHJpbmd9IHVybCBcbiAqIEByZXR1cm4ge3ZvaWR9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBCcm93c2VyT3BlblVSTCh1cmwpIHtcbiAgd2luZG93LldhaWxzSW52b2tlKCdCTzonICsgdXJsKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtDYWxsfSBmcm9tIFwiLi9jYWxsc1wiO1xuXG4vKipcbiAqIFNldCB0aGUgU2l6ZSBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IHRleHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENsaXBib2FyZFNldFRleHQodGV4dCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOkNsaXBib2FyZFNldFRleHRcIiwgW3RleHRdKTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIHRleHQgY29udGVudCBvZiB0aGUgY2xpcGJvYXJkXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTx7c3RyaW5nfT59IFRleHQgY29udGVudCBvZiB0aGUgY2xpcGJvYXJkXG5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENsaXBib2FyZEdldFRleHQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6Q2xpcGJvYXJkR2V0VGV4dFwiKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtFdmVudHNPbiwgRXZlbnRzT2ZmfSBmcm9tIFwiLi9ldmVudHNcIjtcblxuY29uc3QgZmxhZ3MgPSB7XG4gICAgcmVnaXN0ZXJlZDogZmFsc2UsXG4gICAgZGVmYXVsdFVzZURyb3BUYXJnZXQ6IHRydWUsXG4gICAgdXNlRHJvcFRhcmdldDogdHJ1ZSxcbiAgICBuZXh0RGVhY3RpdmF0ZTogbnVsbCxcbiAgICBuZXh0RGVhY3RpdmF0ZVRpbWVvdXQ6IG51bGwsXG59O1xuXG5jb25zdCBEUk9QX1RBUkdFVF9BQ1RJVkUgPSBcIndhaWxzLWRyb3AtdGFyZ2V0LWFjdGl2ZVwiO1xuXG4vKipcbiAqIGNoZWNrU3R5bGVEcm9wVGFyZ2V0IGNoZWNrcyBpZiB0aGUgc3R5bGUgaGFzIHRoZSBkcm9wIHRhcmdldCBhdHRyaWJ1dGVcbiAqIFxuICogQHBhcmFtIHtDU1NTdHlsZURlY2xhcmF0aW9ufSBzdHlsZSBcbiAqIEByZXR1cm5zIFxuICovXG5mdW5jdGlvbiBjaGVja1N0eWxlRHJvcFRhcmdldChzdHlsZSkge1xuICAgIGNvbnN0IGNzc0Ryb3BWYWx1ZSA9IHN0eWxlLmdldFByb3BlcnR5VmFsdWUod2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BQcm9wZXJ0eSkudHJpbSgpO1xuICAgIGlmIChjc3NEcm9wVmFsdWUpIHtcbiAgICAgICAgaWYgKGNzc0Ryb3BWYWx1ZSA9PT0gd2luZG93LndhaWxzLmZsYWdzLmNzc0Ryb3BWYWx1ZSkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gaWYgdGhlIGVsZW1lbnQgaGFzIHRoZSBkcm9wIHRhcmdldCBhdHRyaWJ1dGUsIGJ1dCBcbiAgICAgICAgLy8gdGhlIHZhbHVlIGlzIG5vdCBjb3JyZWN0LCB0ZXJtaW5hdGUgZmluZGluZyBwcm9jZXNzLlxuICAgICAgICAvLyBUaGlzIGNhbiBiZSB1c2VmdWwgdG8gYmxvY2sgc29tZSBjaGlsZCBlbGVtZW50cyBmcm9tIGJlaW5nIGRyb3AgdGFyZ2V0cy5cbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG59XG5cbi8qKlxuICogb25EcmFnT3ZlciBpcyBjYWxsZWQgd2hlbiB0aGUgZHJhZ292ZXIgZXZlbnQgaXMgZW1pdHRlZC5cbiAqIEBwYXJhbSB7RHJhZ0V2ZW50fSBlXG4gKiBAcmV0dXJuc1xuICovXG5mdW5jdGlvbiBvbkRyYWdPdmVyKGUpIHtcbiAgICAvLyBDaGVjayBpZiB0aGlzIGlzIGFuIGV4dGVybmFsIGZpbGUgZHJvcCBvciBpbnRlcm5hbCBIVE1MIGRyYWdcbiAgICAvLyBFeHRlcm5hbCBmaWxlIGRyb3BzIHdpbGwgaGF2ZSBcIkZpbGVzXCIgaW4gdGhlIHR5cGVzIGFycmF5XG4gICAgLy8gSW50ZXJuYWwgSFRNTCBkcmFncyB0eXBpY2FsbHkgaGF2ZSBcInRleHQvcGxhaW5cIiwgXCJ0ZXh0L2h0bWxcIiBvciBjdXN0b20gdHlwZXNcbiAgICBjb25zdCBpc0ZpbGVEcm9wID0gZS5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoXCJGaWxlc1wiKTtcblxuICAgIC8vIE9ubHkgaGFuZGxlIGV4dGVybmFsIGZpbGUgZHJvcHMsIGxldCBpbnRlcm5hbCBIVE1MNSBkcmFnLWFuZC1kcm9wIHdvcmsgbm9ybWFsbHlcbiAgICBpZiAoIWlzRmlsZURyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIEFMV0FZUyBwcmV2ZW50IGRlZmF1bHQgZm9yIGZpbGUgZHJvcHMgdG8gc3RvcCBicm93c2VyIG5hdmlnYXRpb25cbiAgICBlLnByZXZlbnREZWZhdWx0KCk7XG4gICAgZS5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdjb3B5JztcblxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmICghZmxhZ3MudXNlRHJvcFRhcmdldCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgZWxlbWVudCA9IGUudGFyZ2V0O1xuXG4gICAgLy8gVHJpZ2dlciBkZWJvdW5jZSBmdW5jdGlvbiB0byBkZWFjdGl2YXRlIGRyb3AgdGFyZ2V0c1xuICAgIGlmKGZsYWdzLm5leHREZWFjdGl2YXRlKSBmbGFncy5uZXh0RGVhY3RpdmF0ZSgpO1xuXG4gICAgLy8gaWYgdGhlIGVsZW1lbnQgaXMgbnVsbCBvciBlbGVtZW50IGlzIG5vdCBjaGlsZCBvZiBkcm9wIHRhcmdldCBlbGVtZW50XG4gICAgaWYgKCFlbGVtZW50IHx8ICFjaGVja1N0eWxlRHJvcFRhcmdldChnZXRDb21wdXRlZFN0eWxlKGVsZW1lbnQpKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgbGV0IGN1cnJlbnRFbGVtZW50ID0gZWxlbWVudDtcbiAgICB3aGlsZSAoY3VycmVudEVsZW1lbnQpIHtcbiAgICAgICAgLy8gY2hlY2sgaWYgY3VycmVudEVsZW1lbnQgaXMgZHJvcCB0YXJnZXQgZWxlbWVudFxuICAgICAgICBpZiAoY2hlY2tTdHlsZURyb3BUYXJnZXQoZ2V0Q29tcHV0ZWRTdHlsZShjdXJyZW50RWxlbWVudCkpKSB7XG4gICAgICAgICAgICBjdXJyZW50RWxlbWVudC5jbGFzc0xpc3QuYWRkKERST1BfVEFSR0VUX0FDVElWRSk7XG4gICAgICAgIH1cbiAgICAgICAgY3VycmVudEVsZW1lbnQgPSBjdXJyZW50RWxlbWVudC5wYXJlbnRFbGVtZW50O1xuICAgIH1cbn1cblxuLyoqXG4gKiBvbkRyYWdMZWF2ZSBpcyBjYWxsZWQgd2hlbiB0aGUgZHJhZ2xlYXZlIGV2ZW50IGlzIGVtaXR0ZWQuXG4gKiBAcGFyYW0ge0RyYWdFdmVudH0gZVxuICogQHJldHVybnNcbiAqL1xuZnVuY3Rpb24gb25EcmFnTGVhdmUoZSkge1xuICAgIC8vIENoZWNrIGlmIHRoaXMgaXMgYW4gZXh0ZXJuYWwgZmlsZSBkcm9wIG9yIGludGVybmFsIEhUTUwgZHJhZ1xuICAgIGNvbnN0IGlzRmlsZURyb3AgPSBlLmRhdGFUcmFuc2Zlci50eXBlcy5pbmNsdWRlcyhcIkZpbGVzXCIpO1xuXG4gICAgLy8gT25seSBoYW5kbGUgZXh0ZXJuYWwgZmlsZSBkcm9wcywgbGV0IGludGVybmFsIEhUTUw1IGRyYWctYW5kLWRyb3Agd29yayBub3JtYWxseVxuICAgIGlmICghaXNGaWxlRHJvcCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQUxXQVlTIHByZXZlbnQgZGVmYXVsdCBmb3IgZmlsZSBkcm9wcyB0byBzdG9wIGJyb3dzZXIgbmF2aWdhdGlvblxuICAgIGUucHJldmVudERlZmF1bHQoKTtcblxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVdhaWxzRHJhZ0FuZERyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmICghZmxhZ3MudXNlRHJvcFRhcmdldCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gRmluZCB0aGUgY2xvc2UgZHJvcCB0YXJnZXQgZWxlbWVudFxuICAgIGlmICghZS50YXJnZXQgfHwgIWNoZWNrU3R5bGVEcm9wVGFyZ2V0KGdldENvbXB1dGVkU3R5bGUoZS50YXJnZXQpKSkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBUcmlnZ2VyIGRlYm91bmNlIGZ1bmN0aW9uIHRvIGRlYWN0aXZhdGUgZHJvcCB0YXJnZXRzXG4gICAgaWYoZmxhZ3MubmV4dERlYWN0aXZhdGUpIGZsYWdzLm5leHREZWFjdGl2YXRlKCk7XG4gICAgXG4gICAgLy8gVXNlIGRlYm91bmNlIHRlY2huaXF1ZSB0byB0YWNsZSBkcmFnbGVhdmUgZXZlbnRzIG9uIG92ZXJsYXBwaW5nIGVsZW1lbnRzIGFuZCBkcm9wIHRhcmdldCBlbGVtZW50c1xuICAgIGZsYWdzLm5leHREZWFjdGl2YXRlID0gKCkgPT4ge1xuICAgICAgICAvLyBEZWFjdGl2YXRlIGFsbCBkcm9wIHRhcmdldHMsIG5ldyBkcm9wIHRhcmdldCB3aWxsIGJlIGFjdGl2YXRlZCBvbiBuZXh0IGRyYWdvdmVyIGV2ZW50XG4gICAgICAgIEFycmF5LmZyb20oZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZShEUk9QX1RBUkdFVF9BQ1RJVkUpKS5mb3JFYWNoKGVsID0+IGVsLmNsYXNzTGlzdC5yZW1vdmUoRFJPUF9UQVJHRVRfQUNUSVZFKSk7XG4gICAgICAgIC8vIFJlc2V0IG5leHREZWFjdGl2YXRlXG4gICAgICAgIGZsYWdzLm5leHREZWFjdGl2YXRlID0gbnVsbDtcbiAgICAgICAgLy8gQ2xlYXIgdGltZW91dFxuICAgICAgICBpZiAoZmxhZ3MubmV4dERlYWN0aXZhdGVUaW1lb3V0KSB7XG4gICAgICAgICAgICBjbGVhclRpbWVvdXQoZmxhZ3MubmV4dERlYWN0aXZhdGVUaW1lb3V0KTtcbiAgICAgICAgICAgIGZsYWdzLm5leHREZWFjdGl2YXRlVGltZW91dCA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBTZXQgdGltZW91dCB0byBkZWFjdGl2YXRlIGRyb3AgdGFyZ2V0cyBpZiBub3QgdHJpZ2dlcmVkIGJ5IG5leHQgZHJhZyBldmVudFxuICAgIGZsYWdzLm5leHREZWFjdGl2YXRlVGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICBpZihmbGFncy5uZXh0RGVhY3RpdmF0ZSkgZmxhZ3MubmV4dERlYWN0aXZhdGUoKTtcbiAgICB9LCA1MCk7XG59XG5cbi8qKlxuICogb25Ecm9wIGlzIGNhbGxlZCB3aGVuIHRoZSBkcm9wIGV2ZW50IGlzIGVtaXR0ZWQuXG4gKiBAcGFyYW0ge0RyYWdFdmVudH0gZVxuICogQHJldHVybnNcbiAqL1xuZnVuY3Rpb24gb25Ecm9wKGUpIHtcbiAgICAvLyBDaGVjayBpZiB0aGlzIGlzIGFuIGV4dGVybmFsIGZpbGUgZHJvcCBvciBpbnRlcm5hbCBIVE1MIGRyYWdcbiAgICBjb25zdCBpc0ZpbGVEcm9wID0gZS5kYXRhVHJhbnNmZXIudHlwZXMuaW5jbHVkZXMoXCJGaWxlc1wiKTtcblxuICAgIC8vIE9ubHkgaGFuZGxlIGV4dGVybmFsIGZpbGUgZHJvcHMsIGxldCBpbnRlcm5hbCBIVE1MNSBkcmFnLWFuZC1kcm9wIHdvcmsgbm9ybWFsbHlcbiAgICBpZiAoIWlzRmlsZURyb3ApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIEFMV0FZUyBwcmV2ZW50IGRlZmF1bHQgZm9yIGZpbGUgZHJvcHMgdG8gc3RvcCBicm93c2VyIG5hdmlnYXRpb25cbiAgICBlLnByZXZlbnREZWZhdWx0KCk7XG5cbiAgICBpZiAoIXdpbmRvdy53YWlscy5mbGFncy5lbmFibGVXYWlsc0RyYWdBbmREcm9wKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAoQ2FuUmVzb2x2ZUZpbGVQYXRocygpKSB7XG4gICAgICAgIC8vIHByb2Nlc3MgZmlsZXNcbiAgICAgICAgbGV0IGZpbGVzID0gW107XG4gICAgICAgIGlmIChlLmRhdGFUcmFuc2Zlci5pdGVtcykge1xuICAgICAgICAgICAgZmlsZXMgPSBbLi4uZS5kYXRhVHJhbnNmZXIuaXRlbXNdLm1hcCgoaXRlbSwgaSkgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChpdGVtLmtpbmQgPT09ICdmaWxlJykge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gaXRlbS5nZXRBc0ZpbGUoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGZpbGVzID0gWy4uLmUuZGF0YVRyYW5zZmVyLmZpbGVzXTtcbiAgICAgICAgfVxuICAgICAgICB3aW5kb3cucnVudGltZS5SZXNvbHZlRmlsZVBhdGhzKGUueCwgZS55LCBmaWxlcyk7XG4gICAgfVxuXG4gICAgaWYgKCFmbGFncy51c2VEcm9wVGFyZ2V0KSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBUcmlnZ2VyIGRlYm91bmNlIGZ1bmN0aW9uIHRvIGRlYWN0aXZhdGUgZHJvcCB0YXJnZXRzXG4gICAgaWYoZmxhZ3MubmV4dERlYWN0aXZhdGUpIGZsYWdzLm5leHREZWFjdGl2YXRlKCk7XG5cbiAgICAvLyBEZWFjdGl2YXRlIGFsbCBkcm9wIHRhcmdldHNcbiAgICBBcnJheS5mcm9tKGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoRFJPUF9UQVJHRVRfQUNUSVZFKSkuZm9yRWFjaChlbCA9PiBlbC5jbGFzc0xpc3QucmVtb3ZlKERST1BfVEFSR0VUX0FDVElWRSkpO1xufVxuXG4vKipcbiAqIHBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzIGNoZWNrcyB0aGUgYnJvd3NlcidzIGNhcGFiaWxpdHkgb2Ygc2VuZGluZyBwb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0c1xuICpcbiAqIEByZXR1cm5zIHtib29sZWFufVxuICogQGNvbnN0cnVjdG9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYW5SZXNvbHZlRmlsZVBhdGhzKCkge1xuICAgIHJldHVybiB3aW5kb3cuY2hyb21lPy53ZWJ2aWV3Py5wb3N0TWVzc2FnZVdpdGhBZGRpdGlvbmFsT2JqZWN0cyAhPSBudWxsO1xufVxuXG4vKipcbiAqIFJlc29sdmVGaWxlUGF0aHMgc2VuZHMgZHJvcCBldmVudHMgdG8gdGhlIEdPIHNpZGUgdG8gcmVzb2x2ZSBmaWxlIHBhdGhzIG9uIHdpbmRvd3MuXG4gKlxuICogQHBhcmFtIHtudW1iZXJ9IHhcbiAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gKiBAcGFyYW0ge2FueVtdfSBmaWxlc1xuICogQGNvbnN0cnVjdG9yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZXNvbHZlRmlsZVBhdGhzKHgsIHksIGZpbGVzKSB7XG4gICAgLy8gT25seSBmb3Igd2luZG93cyB3ZWJ2aWV3MiA+PSAxLjAuMTc3NC4zMFxuICAgIC8vIGh0dHBzOi8vbGVhcm4ubWljcm9zb2Z0LmNvbS9lbi11cy9taWNyb3NvZnQtZWRnZS93ZWJ2aWV3Mi9yZWZlcmVuY2Uvd2luMzIvaWNvcmV3ZWJ2aWV3MndlYm1lc3NhZ2VyZWNlaXZlZGV2ZW50YXJnczI/dmlldz13ZWJ2aWV3Mi0xLjAuMTgyMy4zMiNhcHBsaWVzLXRvXG4gICAgaWYgKHdpbmRvdy5jaHJvbWU/LndlYnZpZXc/LnBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzKSB7XG4gICAgICAgIGNocm9tZS53ZWJ2aWV3LnBvc3RNZXNzYWdlV2l0aEFkZGl0aW9uYWxPYmplY3RzKGBmaWxlOmRyb3A6JHt4fToke3l9YCwgZmlsZXMpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBDYWxsYmFjayBmb3IgT25GaWxlRHJvcCByZXR1cm5zIGEgc2xpY2Ugb2YgZmlsZSBwYXRoIHN0cmluZ3Mgd2hlbiBhIGRyb3AgaXMgZmluaXNoZWQuXG4gKlxuICogQGV4cG9ydFxuICogQGNhbGxiYWNrIE9uRmlsZURyb3BDYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IHggLSB4IGNvb3JkaW5hdGUgb2YgdGhlIGRyb3BcbiAqIEBwYXJhbSB7bnVtYmVyfSB5IC0geSBjb29yZGluYXRlIG9mIHRoZSBkcm9wXG4gKiBAcGFyYW0ge3N0cmluZ1tdfSBwYXRocyAtIEEgbGlzdCBvZiBmaWxlIHBhdGhzLlxuICovXG5cbi8qKlxuICogT25GaWxlRHJvcCBsaXN0ZW5zIHRvIGRyYWcgYW5kIGRyb3AgZXZlbnRzIGFuZCBjYWxscyB0aGUgY2FsbGJhY2sgd2l0aCB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIGRyb3AgYW5kIGFuIGFycmF5IG9mIHBhdGggc3RyaW5ncy5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge09uRmlsZURyb3BDYWxsYmFja30gY2FsbGJhY2sgLSBDYWxsYmFjayBmb3IgT25GaWxlRHJvcCByZXR1cm5zIGEgc2xpY2Ugb2YgZmlsZSBwYXRoIHN0cmluZ3Mgd2hlbiBhIGRyb3AgaXMgZmluaXNoZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFt1c2VEcm9wVGFyZ2V0PXRydWVdIC0gT25seSBjYWxsIHRoZSBjYWxsYmFjayB3aGVuIHRoZSBkcm9wIGZpbmlzaGVkIG9uIGFuIGVsZW1lbnQgdGhhdCBoYXMgdGhlIGRyb3AgdGFyZ2V0IHN0eWxlLiAoLS13YWlscy1kcm9wLXRhcmdldClcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uRmlsZURyb3AoY2FsbGJhY2ssIHVzZURyb3BUYXJnZXQpIHtcbiAgICBpZiAodHlwZW9mIGNhbGxiYWNrICE9PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihcIkRyYWdBbmREcm9wQ2FsbGJhY2sgaXMgbm90IGEgZnVuY3Rpb25cIik7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAoZmxhZ3MucmVnaXN0ZXJlZCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGZsYWdzLnJlZ2lzdGVyZWQgPSB0cnVlO1xuXG4gICAgY29uc3QgdURUUFQgPSB0eXBlb2YgdXNlRHJvcFRhcmdldDtcbiAgICBmbGFncy51c2VEcm9wVGFyZ2V0ID0gdURUUFQgPT09IFwidW5kZWZpbmVkXCIgfHwgdURUUFQgIT09IFwiYm9vbGVhblwiID8gZmxhZ3MuZGVmYXVsdFVzZURyb3BUYXJnZXQgOiB1c2VEcm9wVGFyZ2V0O1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdkcmFnb3ZlcicsIG9uRHJhZ092ZXIpO1xuICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCBvbkRyYWdMZWF2ZSk7XG4gICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2Ryb3AnLCBvbkRyb3ApO1xuXG4gICAgbGV0IGNiID0gY2FsbGJhY2s7XG4gICAgaWYgKGZsYWdzLnVzZURyb3BUYXJnZXQpIHtcbiAgICAgICAgY2IgPSBmdW5jdGlvbiAoeCwgeSwgcGF0aHMpIHtcbiAgICAgICAgICAgIGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5lbGVtZW50RnJvbVBvaW50KHgsIHkpXG4gICAgICAgICAgICAvLyBpZiB0aGUgZWxlbWVudCBpcyBudWxsIG9yIGVsZW1lbnQgaXMgbm90IGNoaWxkIG9mIGRyb3AgdGFyZ2V0IGVsZW1lbnQsIHJldHVybiBudWxsXG4gICAgICAgICAgICBpZiAoIWVsZW1lbnQgfHwgIWNoZWNrU3R5bGVEcm9wVGFyZ2V0KGdldENvbXB1dGVkU3R5bGUoZWxlbWVudCkpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjYWxsYmFjayh4LCB5LCBwYXRocyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBFdmVudHNPbihcIndhaWxzOmZpbGUtZHJvcFwiLCBjYik7XG59XG5cbi8qKlxuICogT25GaWxlRHJvcE9mZiByZW1vdmVzIHRoZSBkcmFnIGFuZCBkcm9wIGxpc3RlbmVycyBhbmQgaGFuZGxlcnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPbkZpbGVEcm9wT2ZmKCkge1xuICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdkcmFnb3ZlcicsIG9uRHJhZ092ZXIpO1xuICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdkcmFnbGVhdmUnLCBvbkRyYWdMZWF2ZSk7XG4gICAgd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ2Ryb3AnLCBvbkRyb3ApO1xuICAgIEV2ZW50c09mZihcIndhaWxzOmZpbGUtZHJvcFwiKTtcbiAgICBmbGFncy5yZWdpc3RlcmVkID0gZmFsc2U7XG59XG4iLCAiLypcbi0tZGVmYXVsdC1jb250ZXh0bWVudTogYXV0bzsgKGRlZmF1bHQpIHdpbGwgc2hvdyB0aGUgZGVmYXVsdCBjb250ZXh0IG1lbnUgaWYgY29udGVudEVkaXRhYmxlIGlzIHRydWUgT1IgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZCBPUiBlbGVtZW50IGlzIGlucHV0IG9yIHRleHRhcmVhXG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IHNob3c7IHdpbGwgYWx3YXlzIHNob3cgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XG4tLWRlZmF1bHQtY29udGV4dG1lbnU6IGhpZGU7IHdpbGwgYWx3YXlzIGhpZGUgdGhlIGRlZmF1bHQgY29udGV4dCBtZW51XG5cblRoaXMgcnVsZSBpcyBpbmhlcml0ZWQgbGlrZSBub3JtYWwgQ1NTIHJ1bGVzLCBzbyBuZXN0aW5nIHdvcmtzIGFzIGV4cGVjdGVkXG4qL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZXZlbnQpIHtcbiAgICAvLyBQcm9jZXNzIGRlZmF1bHQgY29udGV4dCBtZW51XG4gICAgY29uc3QgZWxlbWVudCA9IGV2ZW50LnRhcmdldDtcbiAgICBjb25zdCBjb21wdXRlZFN0eWxlID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZWxlbWVudCk7XG4gICAgY29uc3QgZGVmYXVsdENvbnRleHRNZW51QWN0aW9uID0gY29tcHV0ZWRTdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKFwiLS1kZWZhdWx0LWNvbnRleHRtZW51XCIpLnRyaW0oKTtcbiAgICBzd2l0Y2ggKGRlZmF1bHRDb250ZXh0TWVudUFjdGlvbikge1xuICAgICAgICBjYXNlIFwic2hvd1wiOlxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICBjYXNlIFwiaGlkZVwiOlxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIGNvbnRlbnRFZGl0YWJsZSBpcyB0cnVlXG4gICAgICAgICAgICBpZiAoZWxlbWVudC5pc0NvbnRlbnRFZGl0YWJsZSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gQ2hlY2sgaWYgdGV4dCBoYXMgYmVlbiBzZWxlY3RlZCBhbmQgYWN0aW9uIGlzIG9uIHRoZSBzZWxlY3RlZCBlbGVtZW50c1xuICAgICAgICAgICAgY29uc3Qgc2VsZWN0aW9uID0gd2luZG93LmdldFNlbGVjdGlvbigpO1xuICAgICAgICAgICAgY29uc3QgaGFzU2VsZWN0aW9uID0gKHNlbGVjdGlvbi50b1N0cmluZygpLmxlbmd0aCA+IDApXG4gICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uKSB7XG4gICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzZWxlY3Rpb24ucmFuZ2VDb3VudDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoaSk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHJlY3RzID0gcmFuZ2UuZ2V0Q2xpZW50UmVjdHMoKTtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCByZWN0cy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcmVjdCA9IHJlY3RzW2pdO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGRvY3VtZW50LmVsZW1lbnRGcm9tUG9pbnQocmVjdC5sZWZ0LCByZWN0LnRvcCkgPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDaGVjayBpZiB0YWduYW1lIGlzIGlucHV0IG9yIHRleHRhcmVhXG4gICAgICAgICAgICBpZiAoZWxlbWVudC50YWdOYW1lID09PSBcIklOUFVUXCIgfHwgZWxlbWVudC50YWdOYW1lID09PSBcIlRFWFRBUkVBXCIpIHtcbiAgICAgICAgICAgICAgICBpZiAoaGFzU2VsZWN0aW9uIHx8ICghZWxlbWVudC5yZWFkT25seSAmJiAhZWxlbWVudC5kaXNhYmxlZCkpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gaGlkZSBkZWZhdWx0IGNvbnRleHQgbWVudVxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICB9XG59XG4iLCAiLypcbiBfICAgICAgIF9fICAgICAgXyBfX1xufCB8ICAgICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cbmltcG9ydCB7Q2FsbH0gZnJvbSBcIi4vY2FsbHNcIjtcblxuLyoqXG4gKiBJbml0aWFsaXplIHRoZSBub3RpZmljYXRpb24gc2VydmljZSBmb3IgdGhlIGFwcGxpY2F0aW9uLlxuICogVGhpcyBtdXN0IGJlIGNhbGxlZCBiZWZvcmUgc2VuZGluZyBhbnkgbm90aWZpY2F0aW9ucy5cbiAqIE9uIG1hY09TLCB0aGlzIGFsc28gZW5zdXJlcyB0aGUgbm90aWZpY2F0aW9uIGRlbGVnYXRlIGlzIHByb3Blcmx5IGluaXRpYWxpemVkLlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJbml0aWFsaXplTm90aWZpY2F0aW9ucygpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpJbml0aWFsaXplTm90aWZpY2F0aW9uc1wiKTtcbn1cblxuLyoqXG4gKiBDbGVhbiB1cCBub3RpZmljYXRpb24gcmVzb3VyY2VzIGFuZCByZWxlYXNlIGFueSBoZWxkIGNvbm5lY3Rpb25zLlxuICogVGhpcyBzaG91bGQgYmUgY2FsbGVkIHdoZW4gc2h1dHRpbmcgZG93biB0aGUgYXBwbGljYXRpb24gdG8gcHJvcGVybHkgcmVsZWFzZSByZXNvdXJjZXNcbiAqIChwcmltYXJpbHkgbmVlZGVkIG9uIExpbnV4IHRvIGNsb3NlIEQtQnVzIGNvbm5lY3Rpb25zKS5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gQ2xlYW51cE5vdGlmaWNhdGlvbnMoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6Q2xlYW51cE5vdGlmaWNhdGlvbnNcIik7XG59XG5cbi8qKlxuICogQ2hlY2sgaWYgbm90aWZpY2F0aW9ucyBhcmUgYXZhaWxhYmxlIG9uIHRoZSBjdXJyZW50IHBsYXRmb3JtLlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRydWUgaWYgbm90aWZpY2F0aW9ucyBhcmUgYXZhaWxhYmxlLCBmYWxzZSBvdGhlcndpc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIElzTm90aWZpY2F0aW9uQXZhaWxhYmxlKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOklzTm90aWZpY2F0aW9uQXZhaWxhYmxlXCIpO1xufVxuXG4vKipcbiAqIFJlcXVlc3Qgbm90aWZpY2F0aW9uIGF1dGhvcml6YXRpb24gZnJvbSB0aGUgdXNlci5cbiAqIE9uIG1hY09TLCB0aGlzIHByb21wdHMgdGhlIHVzZXIgdG8gYWxsb3cgbm90aWZpY2F0aW9ucy5cbiAqIE9uIG90aGVyIHBsYXRmb3JtcywgdGhpcyBhbHdheXMgcmV0dXJucyB0cnVlLlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRydWUgaWYgYXV0aG9yaXphdGlvbiB3YXMgZ3JhbnRlZCwgZmFsc2Ugb3RoZXJ3aXNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZXF1ZXN0Tm90aWZpY2F0aW9uQXV0aG9yaXphdGlvbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpSZXF1ZXN0Tm90aWZpY2F0aW9uQXV0aG9yaXphdGlvblwiKTtcbn1cblxuLyoqXG4gKiBDaGVjayB0aGUgY3VycmVudCBub3RpZmljYXRpb24gYXV0aG9yaXphdGlvbiBzdGF0dXMuXG4gKiBPbiBtYWNPUywgdGhpcyBjaGVja3MgaWYgdGhlIGFwcCBoYXMgbm90aWZpY2F0aW9uIHBlcm1pc3Npb25zLlxuICogT24gb3RoZXIgcGxhdGZvcm1zLCB0aGlzIGFsd2F5cyByZXR1cm5zIHRydWUuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVHJ1ZSBpZiBhdXRob3JpemVkLCBmYWxzZSBvdGhlcndpc2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIENoZWNrTm90aWZpY2F0aW9uQXV0aG9yaXphdGlvbigpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpDaGVja05vdGlmaWNhdGlvbkF1dGhvcml6YXRpb25cIik7XG59XG5cbi8qKlxuICogU2VuZCBhIGJhc2ljIG5vdGlmaWNhdGlvbiB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxuICogVGhlIG5vdGlmaWNhdGlvbiB3aWxsIGRpc3BsYXkgd2l0aCB0aGUgcHJvdmlkZWQgdGl0bGUsIHN1YnRpdGxlIChpZiBzdXBwb3J0ZWQpLCBhbmQgYm9keSB0ZXh0LlxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7T2JqZWN0fSBvcHRpb25zIC0gTm90aWZpY2F0aW9uIG9wdGlvbnNcbiAqIEBwYXJhbSB7c3RyaW5nfSBvcHRpb25zLmlkIC0gVW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSBub3RpZmljYXRpb25cbiAqIEBwYXJhbSB7c3RyaW5nfSBvcHRpb25zLnRpdGxlIC0gTm90aWZpY2F0aW9uIHRpdGxlXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMuc3VidGl0bGVdIC0gTm90aWZpY2F0aW9uIHN1YnRpdGxlIChtYWNPUyBhbmQgTGludXggb25seSlcbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5ib2R5XSAtIE5vdGlmaWNhdGlvbiBib2R5IHRleHRcbiAqIEBwYXJhbSB7c3RyaW5nfSBbb3B0aW9ucy5jYXRlZ29yeUlkXSAtIENhdGVnb3J5IElEIGZvciBhY3Rpb24gYnV0dG9ucyAocmVxdWlyZXMgU2VuZE5vdGlmaWNhdGlvbldpdGhBY3Rpb25zKVxuICogQHBhcmFtIHtPYmplY3Q8c3RyaW5nLCBhbnk+fSBbb3B0aW9ucy5kYXRhXSAtIEFkZGl0aW9uYWwgdXNlciBkYXRhIHRvIGF0dGFjaCB0byB0aGUgbm90aWZpY2F0aW9uXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gU2VuZE5vdGlmaWNhdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6U2VuZE5vdGlmaWNhdGlvblwiLCBbb3B0aW9uc10pO1xufVxuXG4vKipcbiAqIFNlbmQgYSBub3RpZmljYXRpb24gd2l0aCBhY3Rpb24gYnV0dG9ucy5cbiAqIEEgTm90aWZpY2F0aW9uQ2F0ZWdvcnkgbXVzdCBiZSByZWdpc3RlcmVkIGZpcnN0IHVzaW5nIFJlZ2lzdGVyTm90aWZpY2F0aW9uQ2F0ZWdvcnkuXG4gKiBUaGUgb3B0aW9ucy5jYXRlZ29yeUlkIG11c3QgbWF0Y2ggYSBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgY2F0ZWdvcnkgSUQuXG4gKiBJZiB0aGUgY2F0ZWdvcnkgaXMgbm90IGZvdW5kLCBhIGJhc2ljIG5vdGlmaWNhdGlvbiB3aWxsIGJlIHNlbnQgaW5zdGVhZC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge09iamVjdH0gb3B0aW9ucyAtIE5vdGlmaWNhdGlvbiBvcHRpb25zXG4gKiBAcGFyYW0ge3N0cmluZ30gb3B0aW9ucy5pZCAtIFVuaXF1ZSBpZGVudGlmaWVyIGZvciB0aGUgbm90aWZpY2F0aW9uXG4gKiBAcGFyYW0ge3N0cmluZ30gb3B0aW9ucy50aXRsZSAtIE5vdGlmaWNhdGlvbiB0aXRsZVxuICogQHBhcmFtIHtzdHJpbmd9IFtvcHRpb25zLnN1YnRpdGxlXSAtIE5vdGlmaWNhdGlvbiBzdWJ0aXRsZSAobWFjT1MgYW5kIExpbnV4IG9ubHkpXG4gKiBAcGFyYW0ge3N0cmluZ30gW29wdGlvbnMuYm9keV0gLSBOb3RpZmljYXRpb24gYm9keSB0ZXh0XG4gKiBAcGFyYW0ge3N0cmluZ30gb3B0aW9ucy5jYXRlZ29yeUlkIC0gQ2F0ZWdvcnkgSUQgdGhhdCBtYXRjaGVzIGEgcmVnaXN0ZXJlZCBjYXRlZ29yeVxuICogQHBhcmFtIHtPYmplY3Q8c3RyaW5nLCBhbnk+fSBbb3B0aW9ucy5kYXRhXSAtIEFkZGl0aW9uYWwgdXNlciBkYXRhIHRvIGF0dGFjaCB0byB0aGUgbm90aWZpY2F0aW9uXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gU2VuZE5vdGlmaWNhdGlvbldpdGhBY3Rpb25zKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpTZW5kTm90aWZpY2F0aW9uV2l0aEFjdGlvbnNcIiwgW29wdGlvbnNdKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlciBhIG5vdGlmaWNhdGlvbiBjYXRlZ29yeSB0aGF0IGNhbiBiZSB1c2VkIHdpdGggU2VuZE5vdGlmaWNhdGlvbldpdGhBY3Rpb25zLlxuICogQ2F0ZWdvcmllcyBkZWZpbmUgdGhlIGFjdGlvbiBidXR0b25zIGFuZCBvcHRpb25hbCByZXBseSBmaWVsZHMgdGhhdCB3aWxsIGFwcGVhciBvbiBub3RpZmljYXRpb25zLlxuICogUmVnaXN0ZXJpbmcgYSBjYXRlZ29yeSB3aXRoIHRoZSBzYW1lIElEIGFzIGEgcHJldmlvdXNseSByZWdpc3RlcmVkIGNhdGVnb3J5IHdpbGwgb3ZlcnJpZGUgaXQuXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtPYmplY3R9IGNhdGVnb3J5IC0gTm90aWZpY2F0aW9uIGNhdGVnb3J5IGRlZmluaXRpb25cbiAqIEBwYXJhbSB7c3RyaW5nfSBjYXRlZ29yeS5pZCAtIFVuaXF1ZSBpZGVudGlmaWVyIGZvciB0aGUgY2F0ZWdvcnlcbiAqIEBwYXJhbSB7QXJyYXk8T2JqZWN0Pn0gW2NhdGVnb3J5LmFjdGlvbnNdIC0gQXJyYXkgb2YgYWN0aW9uIGJ1dHRvbnNcbiAqIEBwYXJhbSB7c3RyaW5nfSBjYXRlZ29yeS5hY3Rpb25zW10uaWQgLSBVbmlxdWUgaWRlbnRpZmllciBmb3IgdGhlIGFjdGlvblxuICogQHBhcmFtIHtzdHJpbmd9IGNhdGVnb3J5LmFjdGlvbnNbXS50aXRsZSAtIERpc3BsYXkgdGl0bGUgZm9yIHRoZSBhY3Rpb24gYnV0dG9uXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtjYXRlZ29yeS5hY3Rpb25zW10uZGVzdHJ1Y3RpdmVdIC0gV2hldGhlciB0aGUgYWN0aW9uIGlzIGRlc3RydWN0aXZlIChtYWNPUy1zcGVjaWZpYylcbiAqIEBwYXJhbSB7Ym9vbGVhbn0gW2NhdGVnb3J5Lmhhc1JlcGx5RmllbGRdIC0gV2hldGhlciB0byBpbmNsdWRlIGEgdGV4dCBpbnB1dCBmaWVsZCBmb3IgcmVwbGllc1xuICogQHBhcmFtIHtzdHJpbmd9IFtjYXRlZ29yeS5yZXBseVBsYWNlaG9sZGVyXSAtIFBsYWNlaG9sZGVyIHRleHQgZm9yIHRoZSByZXBseSBmaWVsZCAocmVxdWlyZWQgaWYgaGFzUmVwbHlGaWVsZCBpcyB0cnVlKVxuICogQHBhcmFtIHtzdHJpbmd9IFtjYXRlZ29yeS5yZXBseUJ1dHRvblRpdGxlXSAtIFRpdGxlIGZvciB0aGUgcmVwbHkgYnV0dG9uIChyZXF1aXJlZCBpZiBoYXNSZXBseUZpZWxkIGlzIHRydWUpXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gUmVnaXN0ZXJOb3RpZmljYXRpb25DYXRlZ29yeShjYXRlZ29yeSkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlZ2lzdGVyTm90aWZpY2F0aW9uQ2F0ZWdvcnlcIiwgW2NhdGVnb3J5XSk7XG59XG5cbi8qKlxuICogUmVtb3ZlIGEgcHJldmlvdXNseSByZWdpc3RlcmVkIG5vdGlmaWNhdGlvbiBjYXRlZ29yeS5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gY2F0ZWdvcnlJZCAtIFRoZSBJRCBvZiB0aGUgY2F0ZWdvcnkgdG8gcmVtb3ZlXG4gKiBAcmV0dXJuIHtQcm9taXNlPHZvaWQ+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gUmVtb3ZlTm90aWZpY2F0aW9uQ2F0ZWdvcnkoY2F0ZWdvcnlJZCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZU5vdGlmaWNhdGlvbkNhdGVnb3J5XCIsIFtjYXRlZ29yeUlkXSk7XG59XG5cbi8qKlxuICogUmVtb3ZlIGFsbCBwZW5kaW5nIG5vdGlmaWNhdGlvbnMgZnJvbSB0aGUgbm90aWZpY2F0aW9uIGNlbnRlci5cbiAqIE9uIFdpbmRvd3MsIHRoaXMgaXMgYSBuby1vcCBhcyB0aGUgcGxhdGZvcm0gbWFuYWdlcyBub3RpZmljYXRpb24gbGlmZWN5Y2xlIGF1dG9tYXRpY2FsbHkuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbW92ZUFsbFBlbmRpbmdOb3RpZmljYXRpb25zKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZUFsbFBlbmRpbmdOb3RpZmljYXRpb25zXCIpO1xufVxuXG4vKipcbiAqIFJlbW92ZSBhIHNwZWNpZmljIHBlbmRpbmcgbm90aWZpY2F0aW9uIGJ5IGl0cyBpZGVudGlmaWVyLlxuICogT24gV2luZG93cywgdGhpcyBpcyBhIG5vLW9wIGFzIHRoZSBwbGF0Zm9ybSBtYW5hZ2VzIG5vdGlmaWNhdGlvbiBsaWZlY3ljbGUgYXV0b21hdGljYWxseS5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gaWRlbnRpZmllciAtIFRoZSBJRCBvZiB0aGUgbm90aWZpY2F0aW9uIHRvIHJlbW92ZVxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbW92ZVBlbmRpbmdOb3RpZmljYXRpb24oaWRlbnRpZmllcikge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZVBlbmRpbmdOb3RpZmljYXRpb25cIiwgW2lkZW50aWZpZXJdKTtcbn1cblxuLyoqXG4gKiBSZW1vdmUgYWxsIGRlbGl2ZXJlZCBub3RpZmljYXRpb25zIGZyb20gdGhlIG5vdGlmaWNhdGlvbiBjZW50ZXIuXG4gKiBPbiBXaW5kb3dzLCB0aGlzIGlzIGEgbm8tb3AgYXMgdGhlIHBsYXRmb3JtIG1hbmFnZXMgbm90aWZpY2F0aW9uIGxpZmVjeWNsZSBhdXRvbWF0aWNhbGx5LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZW1vdmVBbGxEZWxpdmVyZWROb3RpZmljYXRpb25zKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZUFsbERlbGl2ZXJlZE5vdGlmaWNhdGlvbnNcIik7XG59XG5cbi8qKlxuICogUmVtb3ZlIGEgc3BlY2lmaWMgZGVsaXZlcmVkIG5vdGlmaWNhdGlvbiBieSBpdHMgaWRlbnRpZmllci5cbiAqIE9uIFdpbmRvd3MsIHRoaXMgaXMgYSBuby1vcCBhcyB0aGUgcGxhdGZvcm0gbWFuYWdlcyBub3RpZmljYXRpb24gbGlmZWN5Y2xlIGF1dG9tYXRpY2FsbHkuXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IGlkZW50aWZpZXIgLSBUaGUgSUQgb2YgdGhlIG5vdGlmaWNhdGlvbiB0byByZW1vdmVcbiAqIEByZXR1cm4ge1Byb21pc2U8dm9pZD59XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBSZW1vdmVEZWxpdmVyZWROb3RpZmljYXRpb24oaWRlbnRpZmllcikge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOlJlbW92ZURlbGl2ZXJlZE5vdGlmaWNhdGlvblwiLCBbaWRlbnRpZmllcl0pO1xufVxuXG4vKipcbiAqIFJlbW92ZSBhIG5vdGlmaWNhdGlvbiBieSBpdHMgaWRlbnRpZmllci5cbiAqIFRoaXMgaXMgYSBjb252ZW5pZW5jZSBmdW5jdGlvbiB0aGF0IHdvcmtzIGFjcm9zcyBwbGF0Zm9ybXMuXG4gKiBPbiBtYWNPUywgdXNlIHRoZSBtb3JlIHNwZWNpZmljIFJlbW92ZVBlbmRpbmdOb3RpZmljYXRpb24gb3IgUmVtb3ZlRGVsaXZlcmVkTm90aWZpY2F0aW9uIGZ1bmN0aW9ucy5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gaWRlbnRpZmllciAtIFRoZSBJRCBvZiB0aGUgbm90aWZpY2F0aW9uIHRvIHJlbW92ZVxuICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFJlbW92ZU5vdGlmaWNhdGlvbihpZGVudGlmaWVyKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6UmVtb3ZlTm90aWZpY2F0aW9uXCIsIFtpZGVudGlmaWVyXSk7XG59XG5cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cbmltcG9ydCAqIGFzIExvZyBmcm9tICcuL2xvZyc7XG5pbXBvcnQge1xuICBldmVudExpc3RlbmVycyxcbiAgRXZlbnRzRW1pdCxcbiAgRXZlbnRzTm90aWZ5LFxuICBFdmVudHNPZmYsXG4gIEV2ZW50c09mZkFsbCxcbiAgRXZlbnRzT24sXG4gIEV2ZW50c09uY2UsXG4gIEV2ZW50c09uTXVsdGlwbGUsXG59IGZyb20gXCIuL2V2ZW50c1wiO1xuaW1wb3J0IHsgQ2FsbCwgQ2FsbGJhY2ssIGNhbGxiYWNrcyB9IGZyb20gJy4vY2FsbHMnO1xuaW1wb3J0IHsgU2V0QmluZGluZ3MgfSBmcm9tIFwiLi9iaW5kaW5nc1wiO1xuaW1wb3J0ICogYXMgV2luZG93IGZyb20gXCIuL3dpbmRvd1wiO1xuaW1wb3J0ICogYXMgU2NyZWVuIGZyb20gXCIuL3NjcmVlblwiO1xuaW1wb3J0ICogYXMgQnJvd3NlciBmcm9tIFwiLi9icm93c2VyXCI7XG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSBcIi4vY2xpcGJvYXJkXCI7XG5pbXBvcnQgKiBhcyBEcmFnQW5kRHJvcCBmcm9tIFwiLi9kcmFnYW5kZHJvcFwiO1xuaW1wb3J0ICogYXMgQ29udGV4dE1lbnUgZnJvbSBcIi4vY29udGV4dG1lbnVcIjtcbmltcG9ydCAqIGFzIE5vdGlmaWNhdGlvbnMgZnJvbSBcIi4vbm90aWZpY2F0aW9uc1wiO1xuXG5leHBvcnQgZnVuY3Rpb24gUXVpdCgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1EnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFNob3coKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdTJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBIaWRlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnSCcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gRW52aXJvbm1lbnQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6RW52aXJvbm1lbnRcIik7XG59XG5cbi8vIFRoZSBKUyBydW50aW1lXG53aW5kb3cucnVudGltZSA9IHtcbiAgICAuLi5Mb2csXG4gICAgLi4uV2luZG93LFxuICAgIC4uLkJyb3dzZXIsXG4gICAgLi4uU2NyZWVuLFxuICAgIC4uLkNsaXBib2FyZCxcbiAgICAuLi5EcmFnQW5kRHJvcCxcbiAgICAuLi5Ob3RpZmljYXRpb25zLFxuICAgIEV2ZW50c09uLFxuICAgIEV2ZW50c09uY2UsXG4gICAgRXZlbnRzT25NdWx0aXBsZSxcbiAgICBFdmVudHNFbWl0LFxuICAgIEV2ZW50c09mZixcbiAgICBFdmVudHNPZmZBbGwsXG4gICAgRW52aXJvbm1lbnQsXG4gICAgU2hvdyxcbiAgICBIaWRlLFxuICAgIFF1aXRcbn07XG5cbi8vIEludGVybmFsIHdhaWxzIGVuZHBvaW50c1xud2luZG93LndhaWxzID0ge1xuICAgIENhbGxiYWNrLFxuICAgIEV2ZW50c05vdGlmeSxcbiAgICBTZXRCaW5kaW5ncyxcbiAgICBldmVudExpc3RlbmVycyxcbiAgICBjYWxsYmFja3MsXG4gICAgZmxhZ3M6IHtcbiAgICAgICAgZGlzYWJsZVNjcm9sbGJhckRyYWc6IGZhbHNlLFxuICAgICAgICBkaXNhYmxlRGVmYXVsdENvbnRleHRNZW51OiBmYWxzZSxcbiAgICAgICAgZW5hYmxlUmVzaXplOiBmYWxzZSxcbiAgICAgICAgZGVmYXVsdEN1cnNvcjogbnVsbCxcbiAgICAgICAgYm9yZGVyVGhpY2tuZXNzOiA2LFxuICAgICAgICBzaG91bGREcmFnOiBmYWxzZSxcbiAgICAgICAgZGVmZXJEcmFnVG9Nb3VzZU1vdmU6IHRydWUsXG4gICAgICAgIGNzc0RyYWdQcm9wZXJ0eTogXCItLXdhaWxzLWRyYWdnYWJsZVwiLFxuICAgICAgICBjc3NEcmFnVmFsdWU6IFwiZHJhZ1wiLFxuICAgICAgICBjc3NEcm9wUHJvcGVydHk6IFwiLS13YWlscy1kcm9wLXRhcmdldFwiLFxuICAgICAgICBjc3NEcm9wVmFsdWU6IFwiZHJvcFwiLFxuICAgICAgICBlbmFibGVXYWlsc0RyYWdBbmREcm9wOiBmYWxzZSxcbiAgICB9XG59O1xuXG4vLyBTZXQgdGhlIGJpbmRpbmdzXG5pZiAod2luZG93LndhaWxzYmluZGluZ3MpIHtcbiAgICB3aW5kb3cud2FpbHMuU2V0QmluZGluZ3Mod2luZG93LndhaWxzYmluZGluZ3MpO1xuICAgIGRlbGV0ZSB3aW5kb3cud2FpbHMuU2V0QmluZGluZ3M7XG59XG5cbi8vIChib29sKSBUaGlzIGlzIGV2YWx1YXRlZCBhdCBidWlsZCB0aW1lIGluIHBhY2thZ2UuanNvblxuaWYgKCFERUJVRykge1xuICAgIGRlbGV0ZSB3aW5kb3cud2FpbHNiaW5kaW5ncztcbn1cblxubGV0IGRyYWdUZXN0ID0gZnVuY3Rpb24oZSkge1xuICAgIHZhciB2YWwgPSB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShlLnRhcmdldCkuZ2V0UHJvcGVydHlWYWx1ZSh3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1Byb3BlcnR5KTtcbiAgICBpZiAodmFsKSB7XG4gICAgICAgIHZhbCA9IHZhbC50cmltKCk7XG4gICAgfVxuXG4gICAgaWYgKHZhbCAhPT0gd2luZG93LndhaWxzLmZsYWdzLmNzc0RyYWdWYWx1ZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgaWYgKGUuYnV0dG9ucyAhPT0gMSkge1xuICAgICAgICAvLyBEbyBub3Qgc3RhcnQgZHJhZ2dpbmcgaWYgbm90IHRoZSBwcmltYXJ5IGJ1dHRvbiBoYXMgYmVlbiBjbGlja2VkLlxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgaWYgKGUuZGV0YWlsICE9PSAxKSB7XG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnZ2luZyBpZiBtb3JlIHRoYW4gb25jZSBoYXMgYmVlbiBjbGlja2VkLCBlLmcuIHdoZW4gZG91YmxlIGNsaWNraW5nXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbn07XG5cbndpbmRvdy53YWlscy5zZXRDU1NEcmFnUHJvcGVydGllcyA9IGZ1bmN0aW9uKHByb3BlcnR5LCB2YWx1ZSkge1xuICAgIHdpbmRvdy53YWlscy5mbGFncy5jc3NEcmFnUHJvcGVydHkgPSBwcm9wZXJ0eTtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1ZhbHVlID0gdmFsdWU7XG59XG5cbndpbmRvdy53YWlscy5zZXRDU1NEcm9wUHJvcGVydGllcyA9IGZ1bmN0aW9uKHByb3BlcnR5LCB2YWx1ZSkge1xuICAgIHdpbmRvdy53YWlscy5mbGFncy5jc3NEcm9wUHJvcGVydHkgPSBwcm9wZXJ0eTtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJvcFZhbHVlID0gdmFsdWU7XG59XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtb3VzZWRvd24nLCAoZSkgPT4ge1xuICAgIC8vIENoZWNrIGZvciByZXNpemluZ1xuICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSkge1xuICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoXCJyZXNpemU6XCIgKyB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSk7XG4gICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChkcmFnVGVzdChlKSkge1xuICAgICAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRpc2FibGVTY3JvbGxiYXJEcmFnKSB7XG4gICAgICAgICAgICAvLyBUaGlzIGNoZWNrcyBmb3IgY2xpY2tzIG9uIHRoZSBzY3JvbGwgYmFyXG4gICAgICAgICAgICBpZiAoZS5vZmZzZXRYID4gZS50YXJnZXQuY2xpZW50V2lkdGggfHwgZS5vZmZzZXRZID4gZS50YXJnZXQuY2xpZW50SGVpZ2h0KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MuZGVmZXJEcmFnVG9Nb3VzZU1vdmUpIHtcbiAgICAgICAgICAgIHdpbmRvdy53YWlscy5mbGFncy5zaG91bGREcmFnID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgICAgICAgICAgd2luZG93LldhaWxzSW52b2tlKFwiZHJhZ1wiKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgfSBlbHNlIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbiAgICB9XG59KTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNldXAnLCAoKSA9PiB7XG4gICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbn0pO1xuXG5mdW5jdGlvbiBzZXRSZXNpemUoY3Vyc29yKSB7XG4gICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IGN1cnNvciB8fCB3aW5kb3cud2FpbHMuZmxhZ3MuZGVmYXVsdEN1cnNvcjtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSA9IGN1cnNvcjtcbn1cblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlbW92ZScsIGZ1bmN0aW9uKGUpIHtcbiAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcpIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbiAgICAgICAgbGV0IG1vdXNlUHJlc3NlZCA9IGUuYnV0dG9ucyAhPT0gdW5kZWZpbmVkID8gZS5idXR0b25zIDogZS53aGljaDtcbiAgICAgICAgaWYgKG1vdXNlUHJlc3NlZCA+IDApIHtcbiAgICAgICAgICAgIHdpbmRvdy5XYWlsc0ludm9rZShcImRyYWdcIik7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKCF3aW5kb3cud2FpbHMuZmxhZ3MuZW5hYmxlUmVzaXplKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHdpbmRvdy53YWlscy5mbGFncy5kZWZhdWx0Q3Vyc29yID09IG51bGwpIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLmRlZmF1bHRDdXJzb3IgPSBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yO1xuICAgIH1cbiAgICBpZiAod2luZG93Lm91dGVyV2lkdGggLSBlLmNsaWVudFggPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzICYmIHdpbmRvdy5vdXRlckhlaWdodCAtIGUuY2xpZW50WSA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3MpIHtcbiAgICAgICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IFwic2UtcmVzaXplXCI7XG4gICAgfVxuICAgIGxldCByaWdodEJvcmRlciA9IHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgd2luZG93LndhaWxzLmZsYWdzLmJvcmRlclRoaWNrbmVzcztcbiAgICBsZXQgbGVmdEJvcmRlciA9IGUuY2xpZW50WCA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG4gICAgbGV0IHRvcEJvcmRlciA9IGUuY2xpZW50WSA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG4gICAgbGV0IGJvdHRvbUJvcmRlciA9IHdpbmRvdy5vdXRlckhlaWdodCAtIGUuY2xpZW50WSA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG5cbiAgICAvLyBJZiB3ZSBhcmVuJ3Qgb24gYW4gZWRnZSwgYnV0IHdlcmUsIHJlc2V0IHRoZSBjdXJzb3IgdG8gZGVmYXVsdFxuICAgIGlmICghbGVmdEJvcmRlciAmJiAhcmlnaHRCb3JkZXIgJiYgIXRvcEJvcmRlciAmJiAhYm90dG9tQm9yZGVyICYmIHdpbmRvdy53YWlscy5mbGFncy5yZXNpemVFZGdlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgc2V0UmVzaXplKCk7XG4gICAgfSBlbHNlIGlmIChyaWdodEJvcmRlciAmJiBib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInNlLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Qm9yZGVyICYmIGJvdHRvbUJvcmRlcikgc2V0UmVzaXplKFwic3ctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKGxlZnRCb3JkZXIgJiYgdG9wQm9yZGVyKSBzZXRSZXNpemUoXCJudy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyICYmIHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJuZS1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAobGVmdEJvcmRlcikgc2V0UmVzaXplKFwidy1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAodG9wQm9yZGVyKSBzZXRSZXNpemUoXCJuLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInMtcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHJpZ2h0Qm9yZGVyKSBzZXRSZXNpemUoXCJlLXJlc2l6ZVwiKTtcblxufSk7XG5cbi8vIFNldHVwIGNvbnRleHQgbWVudSBob29rXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBmdW5jdGlvbihlKSB7XG4gICAgLy8gYWx3YXlzIHNob3cgdGhlIGNvbnRleHRtZW51IGluIGRlYnVnICYgZGV2XG4gICAgaWYgKERFQlVHKSByZXR1cm47XG5cbiAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRpc2FibGVEZWZhdWx0Q29udGV4dE1lbnUpIHtcbiAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIENvbnRleHRNZW51LnByb2Nlc3NEZWZhdWx0Q29udGV4dE1lbnUoZSk7XG4gICAgfVxufSk7XG5cbndpbmRvdy5XYWlsc0ludm9rZShcInJ1bnRpbWU6cmVhZHlcIik7Il0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsV0FBUyxlQUFlLE9BQU8sU0FBUztBQUl2QyxXQUFPLFlBQVksTUFBTSxRQUFRLE9BQU87QUFBQSxFQUN6QztBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxTQUFTLFNBQVM7QUFDakMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsUUFBUSxTQUFTO0FBQ2hDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxXQUFXLFNBQVM7QUFDbkMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxZQUFZLFVBQVU7QUFDckMsbUJBQWUsS0FBSyxRQUFRO0FBQUEsRUFDN0I7QUFHTyxNQUFNLFdBQVc7QUFBQSxJQUN2QixPQUFPO0FBQUEsSUFDUCxPQUFPO0FBQUEsSUFDUCxNQUFNO0FBQUEsSUFDTixTQUFTO0FBQUEsSUFDVCxPQUFPO0FBQUEsRUFDUjs7O0FDOUZBLE1BQU0sV0FBTixNQUFlO0FBQUEsSUFRWCxZQUFZLFdBQVcsVUFBVSxjQUFjO0FBQzNDLFdBQUssWUFBWTtBQUVqQixXQUFLLGVBQWUsZ0JBQWdCO0FBR3BDLFdBQUssV0FBVyxDQUFDLFNBQVM7QUFDdEIsaUJBQVMsTUFBTSxNQUFNLElBQUk7QUFFekIsWUFBSSxLQUFLLGlCQUFpQixJQUFJO0FBQzFCLGlCQUFPO0FBQUEsUUFDWDtBQUVBLGFBQUssZ0JBQWdCO0FBQ3JCLGVBQU8sS0FBSyxpQkFBaUI7QUFBQSxNQUNqQztBQUFBLElBQ0o7QUFBQSxFQUNKO0FBRU8sTUFBTSxpQkFBaUIsQ0FBQztBQVd4QixXQUFTLGlCQUFpQixXQUFXLFVBQVUsY0FBYztBQUNoRSxtQkFBZSxhQUFhLGVBQWUsY0FBYyxDQUFDO0FBQzFELFVBQU0sZUFBZSxJQUFJLFNBQVMsV0FBVyxVQUFVLFlBQVk7QUFDbkUsbUJBQWUsV0FBVyxLQUFLLFlBQVk7QUFDM0MsV0FBTyxNQUFNLFlBQVksWUFBWTtBQUFBLEVBQ3pDO0FBVU8sV0FBUyxTQUFTLFdBQVcsVUFBVTtBQUMxQyxXQUFPLGlCQUFpQixXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQ25EO0FBVU8sV0FBUyxXQUFXLFdBQVcsVUFBVTtBQUM1QyxXQUFPLGlCQUFpQixXQUFXLFVBQVUsQ0FBQztBQUFBLEVBQ2xEO0FBRUEsV0FBUyxnQkFBZ0IsV0FBVztBQUdoQyxRQUFJLFlBQVksVUFBVTtBQUcxQixVQUFNLHVCQUF1QixlQUFlLFlBQVksTUFBTSxLQUFLLENBQUM7QUFHcEUsUUFBSSxxQkFBcUIsUUFBUTtBQUc3QixlQUFTLFFBQVEscUJBQXFCLFNBQVMsR0FBRyxTQUFTLEdBQUcsU0FBUyxHQUFHO0FBR3RFLGNBQU0sV0FBVyxxQkFBcUI7QUFFdEMsWUFBSSxPQUFPLFVBQVU7QUFHckIsY0FBTSxVQUFVLFNBQVMsU0FBUyxJQUFJO0FBQ3RDLFlBQUksU0FBUztBQUVULCtCQUFxQixPQUFPLE9BQU8sQ0FBQztBQUFBLFFBQ3hDO0FBQUEsTUFDSjtBQUdBLFVBQUkscUJBQXFCLFdBQVcsR0FBRztBQUNuQyx1QkFBZSxTQUFTO0FBQUEsTUFDNUIsT0FBTztBQUNILHVCQUFlLGFBQWE7QUFBQSxNQUNoQztBQUFBLElBQ0o7QUFBQSxFQUNKO0FBU08sV0FBUyxhQUFhLGVBQWU7QUFFeEMsUUFBSTtBQUNKLFFBQUk7QUFDQSxnQkFBVSxLQUFLLE1BQU0sYUFBYTtBQUFBLElBQ3RDLFNBQVMsR0FBUDtBQUNFLFlBQU0sUUFBUSxvQ0FBb0M7QUFDbEQsWUFBTSxJQUFJLE1BQU0sS0FBSztBQUFBLElBQ3pCO0FBQ0Esb0JBQWdCLE9BQU87QUFBQSxFQUMzQjtBQVFPLFdBQVMsV0FBVyxXQUFXO0FBRWxDLFVBQU0sVUFBVTtBQUFBLE1BQ1osTUFBTTtBQUFBLE1BQ04sTUFBTSxDQUFDLEVBQUUsTUFBTSxNQUFNLFNBQVMsRUFBRSxNQUFNLENBQUM7QUFBQSxJQUMzQztBQUdBLG9CQUFnQixPQUFPO0FBR3ZCLFdBQU8sWUFBWSxPQUFPLEtBQUssVUFBVSxPQUFPLENBQUM7QUFBQSxFQUNyRDtBQUVBLFdBQVMsZUFBZSxXQUFXO0FBRS9CLFdBQU8sZUFBZTtBQUd0QixXQUFPLFlBQVksT0FBTyxTQUFTO0FBQUEsRUFDdkM7QUFTTyxXQUFTLFVBQVUsY0FBYyxzQkFBc0I7QUFDMUQsbUJBQWUsU0FBUztBQUV4QixRQUFJLHFCQUFxQixTQUFTLEdBQUc7QUFDakMsMkJBQXFCLFFBQVEsQ0FBQUEsZUFBYTtBQUN0Qyx1QkFBZUEsVUFBUztBQUFBLE1BQzVCLENBQUM7QUFBQSxJQUNMO0FBQUEsRUFDSjtBQUtRLFdBQVMsZUFBZTtBQUM1QixVQUFNLGFBQWEsT0FBTyxLQUFLLGNBQWM7QUFDN0MsZUFBVyxRQUFRLGVBQWE7QUFDNUIscUJBQWUsU0FBUztBQUFBLElBQzVCLENBQUM7QUFBQSxFQUNMO0FBT0MsV0FBUyxZQUFZLFVBQVU7QUFDNUIsVUFBTSxZQUFZLFNBQVM7QUFDM0IsUUFBSSxlQUFlLGVBQWU7QUFBVztBQUc3QyxtQkFBZSxhQUFhLGVBQWUsV0FBVyxPQUFPLE9BQUssTUFBTSxRQUFRO0FBR2hGLFFBQUksZUFBZSxXQUFXLFdBQVcsR0FBRztBQUN4QyxxQkFBZSxTQUFTO0FBQUEsSUFDNUI7QUFBQSxFQUNKOzs7QUMxTU8sTUFBTSxZQUFZLENBQUM7QUFPMUIsV0FBUyxlQUFlO0FBQ3ZCLFFBQUksUUFBUSxJQUFJLFlBQVksQ0FBQztBQUM3QixXQUFPLE9BQU8sT0FBTyxnQkFBZ0IsS0FBSyxFQUFFO0FBQUEsRUFDN0M7QUFRQSxXQUFTLGNBQWM7QUFDdEIsV0FBTyxLQUFLLE9BQU8sSUFBSTtBQUFBLEVBQ3hCO0FBR0EsTUFBSTtBQUNKLE1BQUksT0FBTyxRQUFRO0FBQ2xCLGlCQUFhO0FBQUEsRUFDZCxPQUFPO0FBQ04saUJBQWE7QUFBQSxFQUNkO0FBaUJPLFdBQVMsS0FBSyxNQUFNLE1BQU0sU0FBUztBQUd6QyxRQUFJLFdBQVcsTUFBTTtBQUNwQixnQkFBVTtBQUFBLElBQ1g7QUFHQSxXQUFPLElBQUksUUFBUSxTQUFVLFNBQVMsUUFBUTtBQUc3QyxVQUFJO0FBQ0osU0FBRztBQUNGLHFCQUFhLE9BQU8sTUFBTSxXQUFXO0FBQUEsTUFDdEMsU0FBUyxVQUFVO0FBRW5CLFVBQUk7QUFFSixVQUFJLFVBQVUsR0FBRztBQUNoQix3QkFBZ0IsV0FBVyxXQUFZO0FBQ3RDLGlCQUFPLE1BQU0sYUFBYSxPQUFPLDZCQUE2QixVQUFVLENBQUM7QUFBQSxRQUMxRSxHQUFHLE9BQU87QUFBQSxNQUNYO0FBR0EsZ0JBQVUsY0FBYztBQUFBLFFBQ3ZCO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNEO0FBRUEsVUFBSTtBQUNILGNBQU0sVUFBVTtBQUFBLFVBQ2Y7QUFBQSxVQUNBO0FBQUEsVUFDQTtBQUFBLFFBQ0Q7QUFHUyxlQUFPLFlBQVksTUFBTSxLQUFLLFVBQVUsT0FBTyxDQUFDO0FBQUEsTUFDcEQsU0FBUyxHQUFQO0FBRUUsZ0JBQVEsTUFBTSxDQUFDO0FBQUEsTUFDbkI7QUFBQSxJQUNKLENBQUM7QUFBQSxFQUNMO0FBRUEsU0FBTyxpQkFBaUIsQ0FBQyxJQUFJLE1BQU0sWUFBWTtBQUczQyxRQUFJLFdBQVcsTUFBTTtBQUNqQixnQkFBVTtBQUFBLElBQ2Q7QUFHQSxXQUFPLElBQUksUUFBUSxTQUFVLFNBQVMsUUFBUTtBQUcxQyxVQUFJO0FBQ0osU0FBRztBQUNDLHFCQUFhLEtBQUssTUFBTSxXQUFXO0FBQUEsTUFDdkMsU0FBUyxVQUFVO0FBRW5CLFVBQUk7QUFFSixVQUFJLFVBQVUsR0FBRztBQUNiLHdCQUFnQixXQUFXLFdBQVk7QUFDbkMsaUJBQU8sTUFBTSxvQkFBb0IsS0FBSyw2QkFBNkIsVUFBVSxDQUFDO0FBQUEsUUFDbEYsR0FBRyxPQUFPO0FBQUEsTUFDZDtBQUdBLGdCQUFVLGNBQWM7QUFBQSxRQUNwQjtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsTUFDSjtBQUVBLFVBQUk7QUFDQSxjQUFNLFVBQVU7QUFBQSxVQUN4QjtBQUFBLFVBQ0E7QUFBQSxVQUNBO0FBQUEsUUFDRDtBQUdTLGVBQU8sWUFBWSxNQUFNLEtBQUssVUFBVSxPQUFPLENBQUM7QUFBQSxNQUNwRCxTQUFTLEdBQVA7QUFFRSxnQkFBUSxNQUFNLENBQUM7QUFBQSxNQUNuQjtBQUFBLElBQ0osQ0FBQztBQUFBLEVBQ0w7QUFVTyxXQUFTLFNBQVMsaUJBQWlCO0FBRXpDLFFBQUk7QUFDSixRQUFJO0FBQ0gsZ0JBQVUsS0FBSyxNQUFNLGVBQWU7QUFBQSxJQUNyQyxTQUFTLEdBQVA7QUFDRCxZQUFNLFFBQVEsb0NBQW9DLEVBQUUscUJBQXFCO0FBQ3pFLGNBQVEsU0FBUyxLQUFLO0FBQ3RCLFlBQU0sSUFBSSxNQUFNLEtBQUs7QUFBQSxJQUN0QjtBQUNBLFFBQUksYUFBYSxRQUFRO0FBQ3pCLFFBQUksZUFBZSxVQUFVO0FBQzdCLFFBQUksQ0FBQyxjQUFjO0FBQ2xCLFlBQU0sUUFBUSxhQUFhO0FBQzNCLGNBQVEsTUFBTSxLQUFLO0FBQ25CLFlBQU0sSUFBSSxNQUFNLEtBQUs7QUFBQSxJQUN0QjtBQUNBLGlCQUFhLGFBQWEsYUFBYTtBQUV2QyxXQUFPLFVBQVU7QUFFakIsUUFBSSxRQUFRLE9BQU87QUFDbEIsbUJBQWEsT0FBTyxRQUFRLEtBQUs7QUFBQSxJQUNsQyxPQUFPO0FBQ04sbUJBQWEsUUFBUSxRQUFRLE1BQU07QUFBQSxJQUNwQztBQUFBLEVBQ0Q7OztBQzFLQSxTQUFPLEtBQUssQ0FBQztBQUVOLFdBQVMsWUFBWSxhQUFhO0FBQ3hDLFFBQUk7QUFDSCxvQkFBYyxLQUFLLE1BQU0sV0FBVztBQUFBLElBQ3JDLFNBQVMsR0FBUDtBQUNELGNBQVEsTUFBTSxDQUFDO0FBQUEsSUFDaEI7QUFHQSxXQUFPLEtBQUssT0FBTyxNQUFNLENBQUM7QUFHMUIsV0FBTyxLQUFLLFdBQVcsRUFBRSxRQUFRLENBQUMsZ0JBQWdCO0FBR2pELGFBQU8sR0FBRyxlQUFlLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztBQUdwRCxhQUFPLEtBQUssWUFBWSxZQUFZLEVBQUUsUUFBUSxDQUFDLGVBQWU7QUFHN0QsZUFBTyxHQUFHLGFBQWEsY0FBYyxPQUFPLEdBQUcsYUFBYSxlQUFlLENBQUM7QUFFNUUsZUFBTyxLQUFLLFlBQVksYUFBYSxXQUFXLEVBQUUsUUFBUSxDQUFDLGVBQWU7QUFFekUsaUJBQU8sR0FBRyxhQUFhLFlBQVksY0FBYyxXQUFZO0FBRzVELGdCQUFJLFVBQVU7QUFHZCxxQkFBUyxVQUFVO0FBQ2xCLG9CQUFNLE9BQU8sQ0FBQyxFQUFFLE1BQU0sS0FBSyxTQUFTO0FBQ3BDLHFCQUFPLEtBQUssQ0FBQyxhQUFhLFlBQVksVUFBVSxFQUFFLEtBQUssR0FBRyxHQUFHLE1BQU0sT0FBTztBQUFBLFlBQzNFO0FBR0Esb0JBQVEsYUFBYSxTQUFVLFlBQVk7QUFDMUMsd0JBQVU7QUFBQSxZQUNYO0FBR0Esb0JBQVEsYUFBYSxXQUFZO0FBQ2hDLHFCQUFPO0FBQUEsWUFDUjtBQUVBLG1CQUFPO0FBQUEsVUFDUixFQUFFO0FBQUEsUUFDSCxDQUFDO0FBQUEsTUFDRixDQUFDO0FBQUEsSUFDRixDQUFDO0FBQUEsRUFDRjs7O0FDbEVBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBZU8sV0FBUyxlQUFlO0FBQzNCLFdBQU8sU0FBUyxPQUFPO0FBQUEsRUFDM0I7QUFFTyxXQUFTLGtCQUFrQjtBQUM5QixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBRU8sV0FBUyw4QkFBOEI7QUFDMUMsV0FBTyxZQUFZLE9BQU87QUFBQSxFQUM5QjtBQUVPLFdBQVMsc0JBQXNCO0FBQ2xDLFdBQU8sWUFBWSxNQUFNO0FBQUEsRUFDN0I7QUFFTyxXQUFTLHFCQUFxQjtBQUNqQyxXQUFPLFlBQVksTUFBTTtBQUFBLEVBQzdCO0FBT08sV0FBUyxlQUFlO0FBQzNCLFdBQU8sWUFBWSxJQUFJO0FBQUEsRUFDM0I7QUFRTyxXQUFTLGVBQWUsT0FBTztBQUNsQyxXQUFPLFlBQVksT0FBTyxLQUFLO0FBQUEsRUFDbkM7QUFPTyxXQUFTLG1CQUFtQjtBQUMvQixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBT08sV0FBUyxxQkFBcUI7QUFDakMsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQVFPLFdBQVMscUJBQXFCO0FBQ2pDLFdBQU8sS0FBSywyQkFBMkI7QUFBQSxFQUMzQztBQVNPLFdBQVMsY0FBYyxPQUFPLFFBQVE7QUFDekMsV0FBTyxZQUFZLFFBQVEsUUFBUSxNQUFNLE1BQU07QUFBQSxFQUNuRDtBQVNPLFdBQVMsZ0JBQWdCO0FBQzVCLFdBQU8sS0FBSyxzQkFBc0I7QUFBQSxFQUN0QztBQVNPLFdBQVMsaUJBQWlCLE9BQU8sUUFBUTtBQUM1QyxXQUFPLFlBQVksUUFBUSxRQUFRLE1BQU0sTUFBTTtBQUFBLEVBQ25EO0FBU08sV0FBUyxpQkFBaUIsT0FBTyxRQUFRO0FBQzVDLFdBQU8sWUFBWSxRQUFRLFFBQVEsTUFBTSxNQUFNO0FBQUEsRUFDbkQ7QUFTTyxXQUFTLHFCQUFxQixHQUFHO0FBRXBDLFdBQU8sWUFBWSxXQUFXLElBQUksTUFBTSxJQUFJO0FBQUEsRUFDaEQ7QUFZTyxXQUFTLGtCQUFrQixHQUFHLEdBQUc7QUFDcEMsV0FBTyxZQUFZLFFBQVEsSUFBSSxNQUFNLENBQUM7QUFBQSxFQUMxQztBQVFPLFdBQVMsb0JBQW9CO0FBQ2hDLFdBQU8sS0FBSyxxQkFBcUI7QUFBQSxFQUNyQztBQU9PLFdBQVMsYUFBYTtBQUN6QixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBT08sV0FBUyxhQUFhO0FBQ3pCLFdBQU8sWUFBWSxJQUFJO0FBQUEsRUFDM0I7QUFPTyxXQUFTLGlCQUFpQjtBQUM3QixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBT08sV0FBUyx1QkFBdUI7QUFDbkMsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQU9PLFdBQVMsbUJBQW1CO0FBQy9CLFdBQU8sWUFBWSxJQUFJO0FBQUEsRUFDM0I7QUFRTyxXQUFTLG9CQUFvQjtBQUNoQyxXQUFPLEtBQUssMEJBQTBCO0FBQUEsRUFDMUM7QUFPTyxXQUFTLGlCQUFpQjtBQUM3QixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBT08sV0FBUyxtQkFBbUI7QUFDL0IsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQVFPLFdBQVMsb0JBQW9CO0FBQ2hDLFdBQU8sS0FBSywwQkFBMEI7QUFBQSxFQUMxQztBQVFPLFdBQVMsaUJBQWlCO0FBQzdCLFdBQU8sS0FBSyx1QkFBdUI7QUFBQSxFQUN2QztBQVdPLFdBQVMsMEJBQTBCLEdBQUcsR0FBRyxHQUFHLEdBQUc7QUFDbEQsUUFBSSxPQUFPLEtBQUssVUFBVSxFQUFDLEdBQUcsS0FBSyxHQUFHLEdBQUcsS0FBSyxHQUFHLEdBQUcsS0FBSyxHQUFHLEdBQUcsS0FBSyxJQUFHLENBQUM7QUFDeEUsV0FBTyxZQUFZLFFBQVEsSUFBSTtBQUFBLEVBQ25DOzs7QUMzUUE7QUFBQTtBQUFBO0FBQUE7QUFzQk8sV0FBUyxlQUFlO0FBQzNCLFdBQU8sS0FBSyxxQkFBcUI7QUFBQSxFQUNyQzs7O0FDeEJBO0FBQUE7QUFBQTtBQUFBO0FBS08sV0FBUyxlQUFlLEtBQUs7QUFDbEMsV0FBTyxZQUFZLFFBQVEsR0FBRztBQUFBLEVBQ2hDOzs7QUNQQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBb0JPLFdBQVMsaUJBQWlCLE1BQU07QUFDbkMsV0FBTyxLQUFLLDJCQUEyQixDQUFDLElBQUksQ0FBQztBQUFBLEVBQ2pEO0FBU08sV0FBUyxtQkFBbUI7QUFDL0IsV0FBTyxLQUFLLHlCQUF5QjtBQUFBLEVBQ3pDOzs7QUNqQ0E7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFNLFFBQVE7QUFBQSxJQUNWLFlBQVk7QUFBQSxJQUNaLHNCQUFzQjtBQUFBLElBQ3RCLGVBQWU7QUFBQSxJQUNmLGdCQUFnQjtBQUFBLElBQ2hCLHVCQUF1QjtBQUFBLEVBQzNCO0FBRUEsTUFBTSxxQkFBcUI7QUFRM0IsV0FBUyxxQkFBcUIsT0FBTztBQUNqQyxVQUFNLGVBQWUsTUFBTSxpQkFBaUIsT0FBTyxNQUFNLE1BQU0sZUFBZSxFQUFFLEtBQUs7QUFDckYsUUFBSSxjQUFjO0FBQ2QsVUFBSSxpQkFBaUIsT0FBTyxNQUFNLE1BQU0sY0FBYztBQUNsRCxlQUFPO0FBQUEsTUFDWDtBQUlBLGFBQU87QUFBQSxJQUNYO0FBQ0EsV0FBTztBQUFBLEVBQ1g7QUFPQSxXQUFTLFdBQVcsR0FBRztBQUluQixVQUFNLGFBQWEsRUFBRSxhQUFhLE1BQU0sU0FBUyxPQUFPO0FBR3hELFFBQUksQ0FBQyxZQUFZO0FBQ2I7QUFBQSxJQUNKO0FBR0EsTUFBRSxlQUFlO0FBQ2pCLE1BQUUsYUFBYSxhQUFhO0FBRTVCLFFBQUksQ0FBQyxPQUFPLE1BQU0sTUFBTSx3QkFBd0I7QUFDNUM7QUFBQSxJQUNKO0FBRUEsUUFBSSxDQUFDLE1BQU0sZUFBZTtBQUN0QjtBQUFBLElBQ0o7QUFFQSxVQUFNLFVBQVUsRUFBRTtBQUdsQixRQUFHLE1BQU07QUFBZ0IsWUFBTSxlQUFlO0FBRzlDLFFBQUksQ0FBQyxXQUFXLENBQUMscUJBQXFCLGlCQUFpQixPQUFPLENBQUMsR0FBRztBQUM5RDtBQUFBLElBQ0o7QUFFQSxRQUFJLGlCQUFpQjtBQUNyQixXQUFPLGdCQUFnQjtBQUVuQixVQUFJLHFCQUFxQixpQkFBaUIsY0FBYyxDQUFDLEdBQUc7QUFDeEQsdUJBQWUsVUFBVSxJQUFJLGtCQUFrQjtBQUFBLE1BQ25EO0FBQ0EsdUJBQWlCLGVBQWU7QUFBQSxJQUNwQztBQUFBLEVBQ0o7QUFPQSxXQUFTLFlBQVksR0FBRztBQUVwQixVQUFNLGFBQWEsRUFBRSxhQUFhLE1BQU0sU0FBUyxPQUFPO0FBR3hELFFBQUksQ0FBQyxZQUFZO0FBQ2I7QUFBQSxJQUNKO0FBR0EsTUFBRSxlQUFlO0FBRWpCLFFBQUksQ0FBQyxPQUFPLE1BQU0sTUFBTSx3QkFBd0I7QUFDNUM7QUFBQSxJQUNKO0FBRUEsUUFBSSxDQUFDLE1BQU0sZUFBZTtBQUN0QjtBQUFBLElBQ0o7QUFHQSxRQUFJLENBQUMsRUFBRSxVQUFVLENBQUMscUJBQXFCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQyxHQUFHO0FBQ2hFLGFBQU87QUFBQSxJQUNYO0FBR0EsUUFBRyxNQUFNO0FBQWdCLFlBQU0sZUFBZTtBQUc5QyxVQUFNLGlCQUFpQixNQUFNO0FBRXpCLFlBQU0sS0FBSyxTQUFTLHVCQUF1QixrQkFBa0IsQ0FBQyxFQUFFLFFBQVEsUUFBTSxHQUFHLFVBQVUsT0FBTyxrQkFBa0IsQ0FBQztBQUVySCxZQUFNLGlCQUFpQjtBQUV2QixVQUFJLE1BQU0sdUJBQXVCO0FBQzdCLHFCQUFhLE1BQU0scUJBQXFCO0FBQ3hDLGNBQU0sd0JBQXdCO0FBQUEsTUFDbEM7QUFBQSxJQUNKO0FBR0EsVUFBTSx3QkFBd0IsV0FBVyxNQUFNO0FBQzNDLFVBQUcsTUFBTTtBQUFnQixjQUFNLGVBQWU7QUFBQSxJQUNsRCxHQUFHLEVBQUU7QUFBQSxFQUNUO0FBT0EsV0FBUyxPQUFPLEdBQUc7QUFFZixVQUFNLGFBQWEsRUFBRSxhQUFhLE1BQU0sU0FBUyxPQUFPO0FBR3hELFFBQUksQ0FBQyxZQUFZO0FBQ2I7QUFBQSxJQUNKO0FBR0EsTUFBRSxlQUFlO0FBRWpCLFFBQUksQ0FBQyxPQUFPLE1BQU0sTUFBTSx3QkFBd0I7QUFDNUM7QUFBQSxJQUNKO0FBRUEsUUFBSSxvQkFBb0IsR0FBRztBQUV2QixVQUFJLFFBQVEsQ0FBQztBQUNiLFVBQUksRUFBRSxhQUFhLE9BQU87QUFDdEIsZ0JBQVEsQ0FBQyxHQUFHLEVBQUUsYUFBYSxLQUFLLEVBQUUsSUFBSSxDQUFDLE1BQU0sTUFBTTtBQUMvQyxjQUFJLEtBQUssU0FBUyxRQUFRO0FBQ3RCLG1CQUFPLEtBQUssVUFBVTtBQUFBLFVBQzFCO0FBQUEsUUFDSixDQUFDO0FBQUEsTUFDTCxPQUFPO0FBQ0gsZ0JBQVEsQ0FBQyxHQUFHLEVBQUUsYUFBYSxLQUFLO0FBQUEsTUFDcEM7QUFDQSxhQUFPLFFBQVEsaUJBQWlCLEVBQUUsR0FBRyxFQUFFLEdBQUcsS0FBSztBQUFBLElBQ25EO0FBRUEsUUFBSSxDQUFDLE1BQU0sZUFBZTtBQUN0QjtBQUFBLElBQ0o7QUFHQSxRQUFHLE1BQU07QUFBZ0IsWUFBTSxlQUFlO0FBRzlDLFVBQU0sS0FBSyxTQUFTLHVCQUF1QixrQkFBa0IsQ0FBQyxFQUFFLFFBQVEsUUFBTSxHQUFHLFVBQVUsT0FBTyxrQkFBa0IsQ0FBQztBQUFBLEVBQ3pIO0FBUU8sV0FBUyxzQkFBc0I7QUFDbEMsV0FBTyxPQUFPLFFBQVEsU0FBUyxvQ0FBb0M7QUFBQSxFQUN2RTtBQVVPLFdBQVMsaUJBQWlCLEdBQUcsR0FBRyxPQUFPO0FBRzFDLFFBQUksT0FBTyxRQUFRLFNBQVMsa0NBQWtDO0FBQzFELGFBQU8sUUFBUSxpQ0FBaUMsYUFBYSxLQUFLLEtBQUssS0FBSztBQUFBLElBQ2hGO0FBQUEsRUFDSjtBQW1CTyxXQUFTLFdBQVcsVUFBVSxlQUFlO0FBQ2hELFFBQUksT0FBTyxhQUFhLFlBQVk7QUFDaEMsY0FBUSxNQUFNLHVDQUF1QztBQUNyRDtBQUFBLElBQ0o7QUFFQSxRQUFJLE1BQU0sWUFBWTtBQUNsQjtBQUFBLElBQ0o7QUFDQSxVQUFNLGFBQWE7QUFFbkIsVUFBTSxRQUFRLE9BQU87QUFDckIsVUFBTSxnQkFBZ0IsVUFBVSxlQUFlLFVBQVUsWUFBWSxNQUFNLHVCQUF1QjtBQUNsRyxXQUFPLGlCQUFpQixZQUFZLFVBQVU7QUFDOUMsV0FBTyxpQkFBaUIsYUFBYSxXQUFXO0FBQ2hELFdBQU8saUJBQWlCLFFBQVEsTUFBTTtBQUV0QyxRQUFJLEtBQUs7QUFDVCxRQUFJLE1BQU0sZUFBZTtBQUNyQixXQUFLLFNBQVUsR0FBRyxHQUFHLE9BQU87QUFDeEIsY0FBTSxVQUFVLFNBQVMsaUJBQWlCLEdBQUcsQ0FBQztBQUU5QyxZQUFJLENBQUMsV0FBVyxDQUFDLHFCQUFxQixpQkFBaUIsT0FBTyxDQUFDLEdBQUc7QUFDOUQsaUJBQU87QUFBQSxRQUNYO0FBQ0EsaUJBQVMsR0FBRyxHQUFHLEtBQUs7QUFBQSxNQUN4QjtBQUFBLElBQ0o7QUFFQSxhQUFTLG1CQUFtQixFQUFFO0FBQUEsRUFDbEM7QUFLTyxXQUFTLGdCQUFnQjtBQUM1QixXQUFPLG9CQUFvQixZQUFZLFVBQVU7QUFDakQsV0FBTyxvQkFBb0IsYUFBYSxXQUFXO0FBQ25ELFdBQU8sb0JBQW9CLFFBQVEsTUFBTTtBQUN6QyxjQUFVLGlCQUFpQjtBQUMzQixVQUFNLGFBQWE7QUFBQSxFQUN2Qjs7O0FDNVFPLFdBQVMsMEJBQTBCLE9BQU87QUFFN0MsVUFBTSxVQUFVLE1BQU07QUFDdEIsVUFBTSxnQkFBZ0IsT0FBTyxpQkFBaUIsT0FBTztBQUNyRCxVQUFNLDJCQUEyQixjQUFjLGlCQUFpQix1QkFBdUIsRUFBRSxLQUFLO0FBQzlGLFlBQVEsMEJBQTBCO0FBQUEsTUFDOUIsS0FBSztBQUNEO0FBQUEsTUFDSixLQUFLO0FBQ0QsY0FBTSxlQUFlO0FBQ3JCO0FBQUEsTUFDSjtBQUVJLFlBQUksUUFBUSxtQkFBbUI7QUFDM0I7QUFBQSxRQUNKO0FBR0EsY0FBTSxZQUFZLE9BQU8sYUFBYTtBQUN0QyxjQUFNLGVBQWdCLFVBQVUsU0FBUyxFQUFFLFNBQVM7QUFDcEQsWUFBSSxjQUFjO0FBQ2QsbUJBQVMsSUFBSSxHQUFHLElBQUksVUFBVSxZQUFZLEtBQUs7QUFDM0Msa0JBQU0sUUFBUSxVQUFVLFdBQVcsQ0FBQztBQUNwQyxrQkFBTSxRQUFRLE1BQU0sZUFBZTtBQUNuQyxxQkFBUyxJQUFJLEdBQUcsSUFBSSxNQUFNLFFBQVEsS0FBSztBQUNuQyxvQkFBTSxPQUFPLE1BQU07QUFDbkIsa0JBQUksU0FBUyxpQkFBaUIsS0FBSyxNQUFNLEtBQUssR0FBRyxNQUFNLFNBQVM7QUFDNUQ7QUFBQSxjQUNKO0FBQUEsWUFDSjtBQUFBLFVBQ0o7QUFBQSxRQUNKO0FBRUEsWUFBSSxRQUFRLFlBQVksV0FBVyxRQUFRLFlBQVksWUFBWTtBQUMvRCxjQUFJLGdCQUFpQixDQUFDLFFBQVEsWUFBWSxDQUFDLFFBQVEsVUFBVztBQUMxRDtBQUFBLFVBQ0o7QUFBQSxRQUNKO0FBR0EsY0FBTSxlQUFlO0FBQUEsSUFDN0I7QUFBQSxFQUNKOzs7QUNqREE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQXFCTyxXQUFTLDBCQUEwQjtBQUN0QyxXQUFPLEtBQUssZ0NBQWdDO0FBQUEsRUFDaEQ7QUFVTyxXQUFTLHVCQUF1QjtBQUNuQyxXQUFPLEtBQUssNkJBQTZCO0FBQUEsRUFDN0M7QUFRTyxXQUFTLDBCQUEwQjtBQUN0QyxXQUFPLEtBQUssZ0NBQWdDO0FBQUEsRUFDaEQ7QUFVTyxXQUFTLG1DQUFtQztBQUMvQyxXQUFPLEtBQUsseUNBQXlDO0FBQUEsRUFDekQ7QUFVTyxXQUFTLGlDQUFpQztBQUM3QyxXQUFPLEtBQUssdUNBQXVDO0FBQUEsRUFDdkQ7QUFnQk8sV0FBUyxpQkFBaUIsU0FBUztBQUN0QyxXQUFPLEtBQUssMkJBQTJCLENBQUMsT0FBTyxDQUFDO0FBQUEsRUFDcEQ7QUFrQk8sV0FBUyw0QkFBNEIsU0FBUztBQUNqRCxXQUFPLEtBQUssc0NBQXNDLENBQUMsT0FBTyxDQUFDO0FBQUEsRUFDL0Q7QUFtQk8sV0FBUyw2QkFBNkIsVUFBVTtBQUNuRCxXQUFPLEtBQUssdUNBQXVDLENBQUMsUUFBUSxDQUFDO0FBQUEsRUFDakU7QUFTTyxXQUFTLDJCQUEyQixZQUFZO0FBQ25ELFdBQU8sS0FBSyxxQ0FBcUMsQ0FBQyxVQUFVLENBQUM7QUFBQSxFQUNqRTtBQVNPLFdBQVMsZ0NBQWdDO0FBQzVDLFdBQU8sS0FBSyxzQ0FBc0M7QUFBQSxFQUN0RDtBQVVPLFdBQVMsMEJBQTBCLFlBQVk7QUFDbEQsV0FBTyxLQUFLLG9DQUFvQyxDQUFDLFVBQVUsQ0FBQztBQUFBLEVBQ2hFO0FBU08sV0FBUyxrQ0FBa0M7QUFDOUMsV0FBTyxLQUFLLHdDQUF3QztBQUFBLEVBQ3hEO0FBVU8sV0FBUyw0QkFBNEIsWUFBWTtBQUNwRCxXQUFPLEtBQUssc0NBQXNDLENBQUMsVUFBVSxDQUFDO0FBQUEsRUFDbEU7QUFXTyxXQUFTLG1CQUFtQixZQUFZO0FBQzNDLFdBQU8sS0FBSyw2QkFBNkIsQ0FBQyxVQUFVLENBQUM7QUFBQSxFQUN6RDs7O0FDdktPLFdBQVMsT0FBTztBQUNuQixXQUFPLFlBQVksR0FBRztBQUFBLEVBQzFCO0FBRU8sV0FBUyxPQUFPO0FBQ25CLFdBQU8sWUFBWSxHQUFHO0FBQUEsRUFDMUI7QUFFTyxXQUFTLE9BQU87QUFDbkIsV0FBTyxZQUFZLEdBQUc7QUFBQSxFQUMxQjtBQUVPLFdBQVMsY0FBYztBQUMxQixXQUFPLEtBQUssb0JBQW9CO0FBQUEsRUFDcEM7QUFHQSxTQUFPLFVBQVU7QUFBQSxJQUNiLEdBQUc7QUFBQSxJQUNILEdBQUc7QUFBQSxJQUNILEdBQUc7QUFBQSxJQUNILEdBQUc7QUFBQSxJQUNILEdBQUc7QUFBQSxJQUNILEdBQUc7QUFBQSxJQUNILEdBQUc7QUFBQSxJQUNIO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsRUFDSjtBQUdBLFNBQU8sUUFBUTtBQUFBLElBQ1g7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQSxPQUFPO0FBQUEsTUFDSCxzQkFBc0I7QUFBQSxNQUN0QiwyQkFBMkI7QUFBQSxNQUMzQixjQUFjO0FBQUEsTUFDZCxlQUFlO0FBQUEsTUFDZixpQkFBaUI7QUFBQSxNQUNqQixZQUFZO0FBQUEsTUFDWixzQkFBc0I7QUFBQSxNQUN0QixpQkFBaUI7QUFBQSxNQUNqQixjQUFjO0FBQUEsTUFDZCxpQkFBaUI7QUFBQSxNQUNqQixjQUFjO0FBQUEsTUFDZCx3QkFBd0I7QUFBQSxJQUM1QjtBQUFBLEVBQ0o7QUFHQSxNQUFJLE9BQU8sZUFBZTtBQUN0QixXQUFPLE1BQU0sWUFBWSxPQUFPLGFBQWE7QUFDN0MsV0FBTyxPQUFPLE1BQU07QUFBQSxFQUN4QjtBQUdBLE1BQUksT0FBUTtBQUNSLFdBQU8sT0FBTztBQUFBLEVBQ2xCO0FBRUEsTUFBSSxXQUFXLFNBQVMsR0FBRztBQUN2QixRQUFJLE1BQU0sT0FBTyxpQkFBaUIsRUFBRSxNQUFNLEVBQUUsaUJBQWlCLE9BQU8sTUFBTSxNQUFNLGVBQWU7QUFDL0YsUUFBSSxLQUFLO0FBQ0wsWUFBTSxJQUFJLEtBQUs7QUFBQSxJQUNuQjtBQUVBLFFBQUksUUFBUSxPQUFPLE1BQU0sTUFBTSxjQUFjO0FBQ3pDLGFBQU87QUFBQSxJQUNYO0FBRUEsUUFBSSxFQUFFLFlBQVksR0FBRztBQUVqQixhQUFPO0FBQUEsSUFDWDtBQUVBLFFBQUksRUFBRSxXQUFXLEdBQUc7QUFFaEIsYUFBTztBQUFBLElBQ1g7QUFFQSxXQUFPO0FBQUEsRUFDWDtBQUVBLFNBQU8sTUFBTSx1QkFBdUIsU0FBUyxVQUFVLE9BQU87QUFDMUQsV0FBTyxNQUFNLE1BQU0sa0JBQWtCO0FBQ3JDLFdBQU8sTUFBTSxNQUFNLGVBQWU7QUFBQSxFQUN0QztBQUVBLFNBQU8sTUFBTSx1QkFBdUIsU0FBUyxVQUFVLE9BQU87QUFDMUQsV0FBTyxNQUFNLE1BQU0sa0JBQWtCO0FBQ3JDLFdBQU8sTUFBTSxNQUFNLGVBQWU7QUFBQSxFQUN0QztBQUVBLFNBQU8saUJBQWlCLGFBQWEsQ0FBQyxNQUFNO0FBRXhDLFFBQUksT0FBTyxNQUFNLE1BQU0sWUFBWTtBQUMvQixhQUFPLFlBQVksWUFBWSxPQUFPLE1BQU0sTUFBTSxVQUFVO0FBQzVELFFBQUUsZUFBZTtBQUNqQjtBQUFBLElBQ0o7QUFFQSxRQUFJLFNBQVMsQ0FBQyxHQUFHO0FBQ2IsVUFBSSxPQUFPLE1BQU0sTUFBTSxzQkFBc0I7QUFFekMsWUFBSSxFQUFFLFVBQVUsRUFBRSxPQUFPLGVBQWUsRUFBRSxVQUFVLEVBQUUsT0FBTyxjQUFjO0FBQ3ZFO0FBQUEsUUFDSjtBQUFBLE1BQ0o7QUFDQSxVQUFJLE9BQU8sTUFBTSxNQUFNLHNCQUFzQjtBQUN6QyxlQUFPLE1BQU0sTUFBTSxhQUFhO0FBQUEsTUFDcEMsT0FBTztBQUNILFVBQUUsZUFBZTtBQUNqQixlQUFPLFlBQVksTUFBTTtBQUFBLE1BQzdCO0FBQ0E7QUFBQSxJQUNKLE9BQU87QUFDSCxhQUFPLE1BQU0sTUFBTSxhQUFhO0FBQUEsSUFDcEM7QUFBQSxFQUNKLENBQUM7QUFFRCxTQUFPLGlCQUFpQixXQUFXLE1BQU07QUFDckMsV0FBTyxNQUFNLE1BQU0sYUFBYTtBQUFBLEVBQ3BDLENBQUM7QUFFRCxXQUFTLFVBQVUsUUFBUTtBQUN2QixhQUFTLGdCQUFnQixNQUFNLFNBQVMsVUFBVSxPQUFPLE1BQU0sTUFBTTtBQUNyRSxXQUFPLE1BQU0sTUFBTSxhQUFhO0FBQUEsRUFDcEM7QUFFQSxTQUFPLGlCQUFpQixhQUFhLFNBQVMsR0FBRztBQUM3QyxRQUFJLE9BQU8sTUFBTSxNQUFNLFlBQVk7QUFDL0IsYUFBTyxNQUFNLE1BQU0sYUFBYTtBQUNoQyxVQUFJLGVBQWUsRUFBRSxZQUFZLFNBQVksRUFBRSxVQUFVLEVBQUU7QUFDM0QsVUFBSSxlQUFlLEdBQUc7QUFDbEIsZUFBTyxZQUFZLE1BQU07QUFDekI7QUFBQSxNQUNKO0FBQUEsSUFDSjtBQUNBLFFBQUksQ0FBQyxPQUFPLE1BQU0sTUFBTSxjQUFjO0FBQ2xDO0FBQUEsSUFDSjtBQUNBLFFBQUksT0FBTyxNQUFNLE1BQU0saUJBQWlCLE1BQU07QUFDMUMsYUFBTyxNQUFNLE1BQU0sZ0JBQWdCLFNBQVMsZ0JBQWdCLE1BQU07QUFBQSxJQUN0RTtBQUNBLFFBQUksT0FBTyxhQUFhLEVBQUUsVUFBVSxPQUFPLE1BQU0sTUFBTSxtQkFBbUIsT0FBTyxjQUFjLEVBQUUsVUFBVSxPQUFPLE1BQU0sTUFBTSxpQkFBaUI7QUFDM0ksZUFBUyxnQkFBZ0IsTUFBTSxTQUFTO0FBQUEsSUFDNUM7QUFDQSxRQUFJLGNBQWMsT0FBTyxhQUFhLEVBQUUsVUFBVSxPQUFPLE1BQU0sTUFBTTtBQUNyRSxRQUFJLGFBQWEsRUFBRSxVQUFVLE9BQU8sTUFBTSxNQUFNO0FBQ2hELFFBQUksWUFBWSxFQUFFLFVBQVUsT0FBTyxNQUFNLE1BQU07QUFDL0MsUUFBSSxlQUFlLE9BQU8sY0FBYyxFQUFFLFVBQVUsT0FBTyxNQUFNLE1BQU07QUFHdkUsUUFBSSxDQUFDLGNBQWMsQ0FBQyxlQUFlLENBQUMsYUFBYSxDQUFDLGdCQUFnQixPQUFPLE1BQU0sTUFBTSxlQUFlLFFBQVc7QUFDM0csZ0JBQVU7QUFBQSxJQUNkLFdBQVcsZUFBZTtBQUFjLGdCQUFVLFdBQVc7QUFBQSxhQUNwRCxjQUFjO0FBQWMsZ0JBQVUsV0FBVztBQUFBLGFBQ2pELGNBQWM7QUFBVyxnQkFBVSxXQUFXO0FBQUEsYUFDOUMsYUFBYTtBQUFhLGdCQUFVLFdBQVc7QUFBQSxhQUMvQztBQUFZLGdCQUFVLFVBQVU7QUFBQSxhQUNoQztBQUFXLGdCQUFVLFVBQVU7QUFBQSxhQUMvQjtBQUFjLGdCQUFVLFVBQVU7QUFBQSxhQUNsQztBQUFhLGdCQUFVLFVBQVU7QUFBQSxFQUU5QyxDQUFDO0FBR0QsU0FBTyxpQkFBaUIsZUFBZSxTQUFTLEdBQUc7QUFFL0MsUUFBSTtBQUFPO0FBRVgsUUFBSSxPQUFPLE1BQU0sTUFBTSwyQkFBMkI7QUFDOUMsUUFBRSxlQUFlO0FBQUEsSUFDckIsT0FBTztBQUNILE1BQVksMEJBQTBCLENBQUM7QUFBQSxJQUMzQztBQUFBLEVBQ0osQ0FBQztBQUVELFNBQU8sWUFBWSxlQUFlOyIsCiAgIm5hbWVzIjogWyJldmVudE5hbWUiXQp9Cg== diff --git a/v2/internal/frontend/runtime/runtime_dev_desktop.go b/v2/internal/frontend/runtime/runtime_dev_desktop.go new file mode 100644 index 000000000..c0dcb1fc5 --- /dev/null +++ b/v2/internal/frontend/runtime/runtime_dev_desktop.go @@ -0,0 +1,8 @@ +//go:build dev || bindings || (!dev && !production && !bindings) + +package runtime + +import _ "embed" + +//go:embed runtime_dev_desktop.js +var RuntimeDesktopJS []byte diff --git a/v2/internal/frontend/runtime/runtime_dev_desktop.js b/v2/internal/frontend/runtime/runtime_dev_desktop.js new file mode 100644 index 000000000..6bfce9f5b --- /dev/null +++ b/v2/internal/frontend/runtime/runtime_dev_desktop.js @@ -0,0 +1,586 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/log.js + var log_exports = {}; + __export(log_exports, { + LogDebug: () => LogDebug, + LogError: () => LogError, + LogFatal: () => LogFatal, + LogInfo: () => LogInfo, + LogLevel: () => LogLevel, + LogPrint: () => LogPrint, + LogTrace: () => LogTrace, + LogWarning: () => LogWarning, + SetLogLevel: () => SetLogLevel + }); + function sendLogMessage(level, message) { + window.WailsInvoke("L" + level + message); + } + function LogTrace(message) { + sendLogMessage("T", message); + } + function LogPrint(message) { + sendLogMessage("P", message); + } + function LogDebug(message) { + sendLogMessage("D", message); + } + function LogInfo(message) { + sendLogMessage("I", message); + } + function LogWarning(message) { + sendLogMessage("W", message); + } + function LogError(message) { + sendLogMessage("E", message); + } + function LogFatal(message) { + sendLogMessage("F", message); + } + function SetLogLevel(loglevel) { + sendLogMessage("S", loglevel); + } + var LogLevel = { + TRACE: 1, + DEBUG: 2, + INFO: 3, + WARNING: 4, + ERROR: 5 + }; + + // desktop/events.js + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback.apply(null, data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var eventListeners = {}; + function EventsOnMultiple(eventName, callback, maxCallbacks) { + eventListeners[eventName] = eventListeners[eventName] || []; + const thisListener = new Listener(eventName, callback, maxCallbacks); + eventListeners[eventName].push(thisListener); + return () => listenerOff(thisListener); + } + function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1); + } + function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1); + } + function notifyListeners(eventData) { + let eventName = eventData.name; + if (eventListeners[eventName]) { + const newEventListenerList = eventListeners[eventName].slice(); + for (let count = eventListeners[eventName].length - 1; count >= 0; count -= 1) { + const listener = eventListeners[eventName][count]; + let data = eventData.data; + const destroy = listener.Callback(data); + if (destroy) { + newEventListenerList.splice(count, 1); + } + } + if (newEventListenerList.length === 0) { + removeListener(eventName); + } else { + eventListeners[eventName] = newEventListenerList; + } + } + } + function EventsNotify(notifyMessage) { + let message; + try { + message = JSON.parse(notifyMessage); + } catch (e) { + const error = "Invalid JSON passed to Notify: " + notifyMessage; + throw new Error(error); + } + notifyListeners(message); + } + function EventsEmit(eventName) { + const payload = { + name: eventName, + data: [].slice.apply(arguments).slice(1) + }; + notifyListeners(payload); + window.WailsInvoke("EE" + JSON.stringify(payload)); + } + function removeListener(eventName) { + delete eventListeners[eventName]; + window.WailsInvoke("EX" + eventName); + } + function EventsOff(eventName, ...additionalEventNames) { + removeListener(eventName); + if (additionalEventNames.length > 0) { + additionalEventNames.forEach((eventName2) => { + removeListener(eventName2); + }); + } + } + function listenerOff(listener) { + const eventName = listener.eventName; + eventListeners[eventName] = eventListeners[eventName].filter((l) => l !== listener); + if (eventListeners[eventName].length === 0) { + removeListener(eventName); + } + } + + // desktop/calls.js + var callbacks = {}; + function cryptoRandom() { + var array = new Uint32Array(1); + return window.crypto.getRandomValues(array)[0]; + } + function basicRandom() { + return Math.random() * 9007199254740991; + } + var randomFunc; + if (window.crypto) { + randomFunc = cryptoRandom; + } else { + randomFunc = basicRandom; + } + function Call(name, args, timeout) { + if (timeout == null) { + timeout = 0; + } + return new Promise(function(resolve, reject) { + var callbackID; + do { + callbackID = name + "-" + randomFunc(); + } while (callbacks[callbackID]); + var timeoutHandle; + if (timeout > 0) { + timeoutHandle = setTimeout(function() { + reject(Error("Call to " + name + " timed out. Request ID: " + callbackID)); + }, timeout); + } + callbacks[callbackID] = { + timeoutHandle, + reject, + resolve + }; + try { + const payload = { + name, + args, + callbackID + }; + window.WailsInvoke("C" + JSON.stringify(payload)); + } catch (e) { + console.error(e); + } + }); + } + window.ObfuscatedCall = (id, args, timeout) => { + if (timeout == null) { + timeout = 0; + } + return new Promise(function(resolve, reject) { + var callbackID; + do { + callbackID = id + "-" + randomFunc(); + } while (callbacks[callbackID]); + var timeoutHandle; + if (timeout > 0) { + timeoutHandle = setTimeout(function() { + reject(Error("Call to method " + id + " timed out. Request ID: " + callbackID)); + }, timeout); + } + callbacks[callbackID] = { + timeoutHandle, + reject, + resolve + }; + try { + const payload = { + id, + args, + callbackID + }; + window.WailsInvoke("c" + JSON.stringify(payload)); + } catch (e) { + console.error(e); + } + }); + }; + function Callback(incomingMessage) { + let message; + try { + message = JSON.parse(incomingMessage); + } catch (e) { + const error = `Invalid JSON passed to callback: ${e.message}. Message: ${incomingMessage}`; + runtime.LogDebug(error); + throw new Error(error); + } + let callbackID = message.callbackid; + let callbackData = callbacks[callbackID]; + if (!callbackData) { + const error = `Callback '${callbackID}' not registered!!!`; + console.error(error); + throw new Error(error); + } + clearTimeout(callbackData.timeoutHandle); + delete callbacks[callbackID]; + if (message.error) { + callbackData.reject(message.error); + } else { + callbackData.resolve(message.result); + } + } + + // desktop/bindings.js + window.go = {}; + function SetBindings(bindingsMap) { + try { + bindingsMap = JSON.parse(bindingsMap); + } catch (e) { + console.error(e); + } + window.go = window.go || {}; + Object.keys(bindingsMap).forEach((packageName) => { + window.go[packageName] = window.go[packageName] || {}; + Object.keys(bindingsMap[packageName]).forEach((structName) => { + window.go[packageName][structName] = window.go[packageName][structName] || {}; + Object.keys(bindingsMap[packageName][structName]).forEach((methodName) => { + window.go[packageName][structName][methodName] = function() { + let timeout = 0; + function dynamic() { + const args = [].slice.call(arguments); + return Call([packageName, structName, methodName].join("."), args, timeout); + } + dynamic.setTimeout = function(newTimeout) { + timeout = newTimeout; + }; + dynamic.getTimeout = function() { + return timeout; + }; + return dynamic; + }(); + }); + }); + }); + } + + // desktop/window.js + var window_exports = {}; + __export(window_exports, { + WindowCenter: () => WindowCenter, + WindowFullscreen: () => WindowFullscreen, + WindowGetPosition: () => WindowGetPosition, + WindowGetSize: () => WindowGetSize, + WindowHide: () => WindowHide, + WindowIsFullscreen: () => WindowIsFullscreen, + WindowIsMaximised: () => WindowIsMaximised, + WindowIsMinimised: () => WindowIsMinimised, + WindowIsNormal: () => WindowIsNormal, + WindowMaximise: () => WindowMaximise, + WindowMinimise: () => WindowMinimise, + WindowReload: () => WindowReload, + WindowReloadApp: () => WindowReloadApp, + WindowSetAlwaysOnTop: () => WindowSetAlwaysOnTop, + WindowSetBackgroundColour: () => WindowSetBackgroundColour, + WindowSetDarkTheme: () => WindowSetDarkTheme, + WindowSetLightTheme: () => WindowSetLightTheme, + WindowSetMaxSize: () => WindowSetMaxSize, + WindowSetMinSize: () => WindowSetMinSize, + WindowSetPosition: () => WindowSetPosition, + WindowSetSize: () => WindowSetSize, + WindowSetSystemDefaultTheme: () => WindowSetSystemDefaultTheme, + WindowSetTitle: () => WindowSetTitle, + WindowShow: () => WindowShow, + WindowToggleMaximise: () => WindowToggleMaximise, + WindowUnfullscreen: () => WindowUnfullscreen, + WindowUnmaximise: () => WindowUnmaximise, + WindowUnminimise: () => WindowUnminimise + }); + function WindowReload() { + window.location.reload(); + } + function WindowReloadApp() { + window.WailsInvoke("WR"); + } + function WindowSetSystemDefaultTheme() { + window.WailsInvoke("WASDT"); + } + function WindowSetLightTheme() { + window.WailsInvoke("WALT"); + } + function WindowSetDarkTheme() { + window.WailsInvoke("WADT"); + } + function WindowCenter() { + window.WailsInvoke("Wc"); + } + function WindowSetTitle(title) { + window.WailsInvoke("WT" + title); + } + function WindowFullscreen() { + window.WailsInvoke("WF"); + } + function WindowUnfullscreen() { + window.WailsInvoke("Wf"); + } + function WindowIsFullscreen() { + return Call(":wails:WindowIsFullscreen"); + } + function WindowSetSize(width, height) { + window.WailsInvoke("Ws:" + width + ":" + height); + } + function WindowGetSize() { + return Call(":wails:WindowGetSize"); + } + function WindowSetMaxSize(width, height) { + window.WailsInvoke("WZ:" + width + ":" + height); + } + function WindowSetMinSize(width, height) { + window.WailsInvoke("Wz:" + width + ":" + height); + } + function WindowSetAlwaysOnTop(b) { + window.WailsInvoke("WATP:" + (b ? "1" : "0")); + } + function WindowSetPosition(x, y) { + window.WailsInvoke("Wp:" + x + ":" + y); + } + function WindowGetPosition() { + return Call(":wails:WindowGetPos"); + } + function WindowHide() { + window.WailsInvoke("WH"); + } + function WindowShow() { + window.WailsInvoke("WS"); + } + function WindowMaximise() { + window.WailsInvoke("WM"); + } + function WindowToggleMaximise() { + window.WailsInvoke("Wt"); + } + function WindowUnmaximise() { + window.WailsInvoke("WU"); + } + function WindowIsMaximised() { + return Call(":wails:WindowIsMaximised"); + } + function WindowMinimise() { + window.WailsInvoke("Wm"); + } + function WindowUnminimise() { + window.WailsInvoke("Wu"); + } + function WindowIsMinimised() { + return Call(":wails:WindowIsMinimised"); + } + function WindowIsNormal() { + return Call(":wails:WindowIsNormal"); + } + function WindowSetBackgroundColour(R, G, B, A) { + let rgba = JSON.stringify({ r: R || 0, g: G || 0, b: B || 0, a: A || 255 }); + window.WailsInvoke("Wr:" + rgba); + } + + // desktop/screen.js + var screen_exports = {}; + __export(screen_exports, { + ScreenGetAll: () => ScreenGetAll + }); + function ScreenGetAll() { + return Call(":wails:ScreenGetAll"); + } + + // desktop/browser.js + var browser_exports = {}; + __export(browser_exports, { + BrowserOpenURL: () => BrowserOpenURL + }); + function BrowserOpenURL(url) { + window.WailsInvoke("BO:" + url); + } + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + ClipboardGetText: () => ClipboardGetText, + ClipboardSetText: () => ClipboardSetText + }); + function ClipboardSetText(text) { + return Call(":wails:ClipboardSetText", [text]); + } + function ClipboardGetText() { + return Call(":wails:ClipboardGetText"); + } + + // desktop/main.js + function Quit() { + window.WailsInvoke("Q"); + } + function Show() { + window.WailsInvoke("S"); + } + function Hide() { + window.WailsInvoke("H"); + } + function Environment() { + return Call(":wails:Environment"); + } + window.runtime = { + ...log_exports, + ...window_exports, + ...browser_exports, + ...screen_exports, + ...clipboard_exports, + EventsOn, + EventsOnce, + EventsOnMultiple, + EventsEmit, + EventsOff, + Environment, + Show, + Hide, + Quit + }; + window.wails = { + Callback, + EventsNotify, + SetBindings, + eventListeners, + callbacks, + flags: { + disableScrollbarDrag: false, + disableWailsDefaultContextMenu: false, + enableResize: false, + defaultCursor: null, + borderThickness: 6, + shouldDrag: false, + deferDragToMouseMove: true, + cssDragProperty: "--wails-draggable", + cssDragValue: "drag" + } + }; + if (window.wailsbindings) { + window.wails.SetBindings(window.wailsbindings); + delete window.wails.SetBindings; + } + if (false) { + delete window.wailsbindings; + } + var dragTest = function(e) { + var val = window.getComputedStyle(e.target).getPropertyValue(window.wails.flags.cssDragProperty); + if (val) { + val = val.trim(); + } + if (val !== window.wails.flags.cssDragValue) { + return false; + } + if (e.buttons !== 1) { + return false; + } + if (e.detail !== 1) { + return false; + } + return true; + }; + window.wails.setCSSDragProperties = function(property, value) { + window.wails.flags.cssDragProperty = property; + window.wails.flags.cssDragValue = value; + }; + window.addEventListener("mousedown", (e) => { + if (window.wails.flags.resizeEdge) { + window.WailsInvoke("resize:" + window.wails.flags.resizeEdge); + e.preventDefault(); + return; + } + if (dragTest(e)) { + if (window.wails.flags.disableScrollbarDrag) { + if (e.offsetX > e.target.clientWidth || e.offsetY > e.target.clientHeight) { + return; + } + } + if (window.wails.flags.deferDragToMouseMove) { + window.wails.flags.shouldDrag = true; + } else { + e.preventDefault(); + window.WailsInvoke("drag"); + } + return; + } else { + window.wails.flags.shouldDrag = false; + } + }); + window.addEventListener("mouseup", () => { + window.wails.flags.shouldDrag = false; + }); + function setResize(cursor) { + document.documentElement.style.cursor = cursor || window.wails.flags.defaultCursor; + window.wails.flags.resizeEdge = cursor; + } + window.addEventListener("mousemove", function(e) { + if (window.wails.flags.shouldDrag) { + window.wails.flags.shouldDrag = false; + let mousePressed = e.buttons !== void 0 ? e.buttons : e.which; + if (mousePressed > 0) { + window.WailsInvoke("drag"); + return; + } + } + if (!window.wails.flags.enableResize) { + return; + } + if (window.wails.flags.defaultCursor == null) { + window.wails.flags.defaultCursor = document.documentElement.style.cursor; + } + if (window.outerWidth - e.clientX < window.wails.flags.borderThickness && window.outerHeight - e.clientY < window.wails.flags.borderThickness) { + document.documentElement.style.cursor = "se-resize"; + } + let rightBorder = window.outerWidth - e.clientX < window.wails.flags.borderThickness; + let leftBorder = e.clientX < window.wails.flags.borderThickness; + let topBorder = e.clientY < window.wails.flags.borderThickness; + let bottomBorder = window.outerHeight - e.clientY < window.wails.flags.borderThickness; + if (!leftBorder && !rightBorder && !topBorder && !bottomBorder && window.wails.flags.resizeEdge !== void 0) { + setResize(); + } else if (rightBorder && bottomBorder) + setResize("se-resize"); + else if (leftBorder && bottomBorder) + setResize("sw-resize"); + else if (leftBorder && topBorder) + setResize("nw-resize"); + else if (topBorder && rightBorder) + 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"); + }); + window.addEventListener("contextmenu", function(e) { + if (window.wails.flags.disableWailsDefaultContextMenu) { + e.preventDefault(); + } + }); + window.WailsInvoke("runtime:ready"); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9ldmVudHMuanMiLCAiZGVza3RvcC9jYWxscy5qcyIsICJkZXNrdG9wL2JpbmRpbmdzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3Avc2NyZWVuLmpzIiwgImRlc2t0b3AvYnJvd3Nlci5qcyIsICJkZXNrdG9wL2NsaXBib2FyZC5qcyIsICJkZXNrdG9wL21haW4uanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbIi8qXG4gXyAgICAgICBfXyAgICAgIF8gX19cbnwgfCAgICAgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA2ICovXG5cbi8qKlxuICogU2VuZHMgYSBsb2cgbWVzc2FnZSB0byB0aGUgYmFja2VuZCB3aXRoIHRoZSBnaXZlbiBsZXZlbCArIG1lc3NhZ2VcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gbGV2ZWxcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmZ1bmN0aW9uIHNlbmRMb2dNZXNzYWdlKGxldmVsLCBtZXNzYWdlKSB7XG5cblx0Ly8gTG9nIE1lc3NhZ2UgZm9ybWF0OlxuXHQvLyBsW3R5cGVdW21lc3NhZ2VdXG5cdHdpbmRvdy5XYWlsc0ludm9rZSgnTCcgKyBsZXZlbCArIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIExvZyB0aGUgZ2l2ZW4gdHJhY2UgbWVzc2FnZSB3aXRoIHRoZSBiYWNrZW5kXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IG1lc3NhZ2VcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZ1RyYWNlKG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ1QnLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dQcmludChtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdQJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiBkZWJ1ZyBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gTG9nRGVidWcobWVzc2FnZSkge1xuXHRzZW5kTG9nTWVzc2FnZSgnRCcsIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIExvZyB0aGUgZ2l2ZW4gaW5mbyBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gTG9nSW5mbyhtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdJJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiB3YXJuaW5nIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dXYXJuaW5nKG1lc3NhZ2UpIHtcblx0c2VuZExvZ01lc3NhZ2UoJ1cnLCBtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBMb2cgdGhlIGdpdmVuIGVycm9yIG1lc3NhZ2Ugd2l0aCB0aGUgYmFja2VuZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBMb2dFcnJvcihtZXNzYWdlKSB7XG5cdHNlbmRMb2dNZXNzYWdlKCdFJywgbWVzc2FnZSk7XG59XG5cbi8qKlxuICogTG9nIHRoZSBnaXZlbiBmYXRhbCBtZXNzYWdlIHdpdGggdGhlIGJhY2tlbmRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gbWVzc2FnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gTG9nRmF0YWwobWVzc2FnZSkge1xuXHRzZW5kTG9nTWVzc2FnZSgnRicsIG1lc3NhZ2UpO1xufVxuXG4vKipcbiAqIFNldHMgdGhlIExvZyBsZXZlbCB0byB0aGUgZ2l2ZW4gbG9nIGxldmVsXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IGxvZ2xldmVsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTZXRMb2dMZXZlbChsb2dsZXZlbCkge1xuXHRzZW5kTG9nTWVzc2FnZSgnUycsIGxvZ2xldmVsKTtcbn1cblxuLy8gTG9nIGxldmVsc1xuZXhwb3J0IGNvbnN0IExvZ0xldmVsID0ge1xuXHRUUkFDRTogMSxcblx0REVCVUc6IDIsXG5cdElORk86IDMsXG5cdFdBUk5JTkc6IDQsXG5cdEVSUk9SOiA1LFxufTtcbiIsICIvKlxuIF8gICAgICAgX18gICAgICBfIF9fXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuLy8gRGVmaW5lcyBhIHNpbmdsZSBsaXN0ZW5lciB3aXRoIGEgbWF4aW11bSBudW1iZXIgb2YgdGltZXMgdG8gY2FsbGJhY2tcblxuLyoqXG4gKiBUaGUgTGlzdGVuZXIgY2xhc3MgZGVmaW5lcyBhIGxpc3RlbmVyISA6LSlcbiAqXG4gKiBAY2xhc3MgTGlzdGVuZXJcbiAqL1xuY2xhc3MgTGlzdGVuZXIge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYW4gaW5zdGFuY2Ugb2YgTGlzdGVuZXIuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICAgICAqIEBtZW1iZXJvZiBMaXN0ZW5lclxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xuICAgICAgICB0aGlzLmV2ZW50TmFtZSA9IGV2ZW50TmFtZTtcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyA9IG1heENhbGxiYWNrcyB8fCAtMTtcbiAgICAgICAgLy8gQ2FsbGJhY2sgaW52b2tlcyB0aGUgY2FsbGJhY2sgd2l0aCB0aGUgZ2l2ZW4gZGF0YVxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXG4gICAgICAgIHRoaXMuQ2FsbGJhY2sgPSAoZGF0YSkgPT4ge1xuICAgICAgICAgICAgY2FsbGJhY2suYXBwbHkobnVsbCwgZGF0YSk7XG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXG4gICAgICAgICAgICBpZiAodGhpcy5tYXhDYWxsYmFja3MgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gRGVjcmVtZW50IG1heENhbGxiYWNrcy4gUmV0dXJuIHRydWUgaWYgbm93IDAsIG90aGVyd2lzZSBmYWxzZVxuICAgICAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcbiAgICAgICAgfTtcbiAgICB9XG59XG5cbmV4cG9ydCBjb25zdCBldmVudExpc3RlbmVycyA9IHt9O1xuXG4vKipcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBgbWF4Q2FsbGJhY2tzYCB0aW1lcyBiZWZvcmUgYmVpbmcgZGVzdHJveWVkXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcbiAqIEBwYXJhbSB7bnVtYmVyfSBtYXhDYWxsYmFja3NcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gQSBmdW5jdGlvbiB0byBjYW5jZWwgdGhlIGxpc3RlbmVyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xuICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0gPSBldmVudExpc3RlbmVyc1tldmVudE5hbWVdIHx8IFtdO1xuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xuICAgIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0ucHVzaCh0aGlzTGlzdGVuZXIpO1xuICAgIHJldHVybiAoKSA9PiBsaXN0ZW5lck9mZih0aGlzTGlzdGVuZXIpO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBldmVyeSB0aW1lIHRoZSBldmVudCBpcyBlbWl0dGVkXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gQSBmdW5jdGlvbiB0byBjYW5jZWwgdGhlIGxpc3RlbmVyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPbihldmVudE5hbWUsIGNhbGxiYWNrKSB7XG4gICAgcmV0dXJuIEV2ZW50c09uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgLTEpO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVycyBhbiBldmVudCBsaXN0ZW5lciB0aGF0IHdpbGwgYmUgaW52b2tlZCBvbmNlIHRoZW4gZGVzdHJveWVkXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICogQHBhcmFtIHtmdW5jdGlvbn0gY2FsbGJhY2tcbiAqIEByZXR1cm5zIHtmdW5jdGlvbn0gQSBmdW5jdGlvbiB0byBjYW5jZWwgdGhlIGxpc3RlbmVyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPbmNlKGV2ZW50TmFtZSwgY2FsbGJhY2spIHtcbiAgICByZXR1cm4gRXZlbnRzT25NdWx0aXBsZShldmVudE5hbWUsIGNhbGxiYWNrLCAxKTtcbn1cblxuZnVuY3Rpb24gbm90aWZ5TGlzdGVuZXJzKGV2ZW50RGF0YSkge1xuXG4gICAgLy8gR2V0IHRoZSBldmVudCBuYW1lXG4gICAgbGV0IGV2ZW50TmFtZSA9IGV2ZW50RGF0YS5uYW1lO1xuXG4gICAgLy8gQ2hlY2sgaWYgd2UgaGF2ZSBhbnkgbGlzdGVuZXJzIGZvciB0aGlzIGV2ZW50XG4gICAgaWYgKGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV0pIHtcblxuICAgICAgICAvLyBLZWVwIGEgbGlzdCBvZiBsaXN0ZW5lciBpbmRleGVzIHRvIGRlc3Ryb3lcbiAgICAgICAgY29uc3QgbmV3RXZlbnRMaXN0ZW5lckxpc3QgPSBldmVudExpc3RlbmVyc1tldmVudE5hbWVdLnNsaWNlKCk7XG5cbiAgICAgICAgLy8gSXRlcmF0ZSBsaXN0ZW5lcnNcbiAgICAgICAgZm9yIChsZXQgY291bnQgPSBldmVudExpc3RlbmVyc1tldmVudE5hbWVdLmxlbmd0aCAtIDE7IGNvdW50ID49IDA7IGNvdW50IC09IDEpIHtcblxuICAgICAgICAgICAgLy8gR2V0IG5leHQgbGlzdGVuZXJcbiAgICAgICAgICAgIGNvbnN0IGxpc3RlbmVyID0gZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXVtjb3VudF07XG5cbiAgICAgICAgICAgIGxldCBkYXRhID0gZXZlbnREYXRhLmRhdGE7XG5cbiAgICAgICAgICAgIC8vIERvIHRoZSBjYWxsYmFja1xuICAgICAgICAgICAgY29uc3QgZGVzdHJveSA9IGxpc3RlbmVyLkNhbGxiYWNrKGRhdGEpO1xuICAgICAgICAgICAgaWYgKGRlc3Ryb3kpIHtcbiAgICAgICAgICAgICAgICAvLyBpZiB0aGUgbGlzdGVuZXIgaW5kaWNhdGVkIHRvIGRlc3Ryb3kgaXRzZWxmLCBhZGQgaXQgdG8gdGhlIGRlc3Ryb3kgbGlzdFxuICAgICAgICAgICAgICAgIG5ld0V2ZW50TGlzdGVuZXJMaXN0LnNwbGljZShjb3VudCwgMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBVcGRhdGUgY2FsbGJhY2tzIHdpdGggbmV3IGxpc3Qgb2YgbGlzdGVuZXJzXG4gICAgICAgIGlmIChuZXdFdmVudExpc3RlbmVyTGlzdC5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBldmVudExpc3RlbmVyc1tldmVudE5hbWVdID0gbmV3RXZlbnRMaXN0ZW5lckxpc3Q7XG4gICAgICAgIH1cbiAgICB9XG59XG5cbi8qKlxuICogTm90aWZ5IGluZm9ybXMgZnJvbnRlbmQgbGlzdGVuZXJzIHRoYXQgYW4gZXZlbnQgd2FzIGVtaXR0ZWQgd2l0aCB0aGUgZ2l2ZW4gZGF0YVxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBub3RpZnlNZXNzYWdlIC0gZW5jb2RlZCBub3RpZmljYXRpb24gbWVzc2FnZVxuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNOb3RpZnkobm90aWZ5TWVzc2FnZSkge1xuICAgIC8vIFBhcnNlIHRoZSBtZXNzYWdlXG4gICAgbGV0IG1lc3NhZ2U7XG4gICAgdHJ5IHtcbiAgICAgICAgbWVzc2FnZSA9IEpTT04ucGFyc2Uobm90aWZ5TWVzc2FnZSk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zdCBlcnJvciA9ICdJbnZhbGlkIEpTT04gcGFzc2VkIHRvIE5vdGlmeTogJyArIG5vdGlmeU1lc3NhZ2U7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihlcnJvcik7XG4gICAgfVxuICAgIG5vdGlmeUxpc3RlbmVycyhtZXNzYWdlKTtcbn1cblxuLyoqXG4gKiBFbWl0IGFuIGV2ZW50IHdpdGggdGhlIGdpdmVuIG5hbWUgYW5kIGRhdGFcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNFbWl0KGV2ZW50TmFtZSkge1xuXG4gICAgY29uc3QgcGF5bG9hZCA9IHtcbiAgICAgICAgbmFtZTogZXZlbnROYW1lLFxuICAgICAgICBkYXRhOiBbXS5zbGljZS5hcHBseShhcmd1bWVudHMpLnNsaWNlKDEpLFxuICAgIH07XG5cbiAgICAvLyBOb3RpZnkgSlMgbGlzdGVuZXJzXG4gICAgbm90aWZ5TGlzdGVuZXJzKHBheWxvYWQpO1xuXG4gICAgLy8gTm90aWZ5IEdvIGxpc3RlbmVyc1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnRUUnICsgSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkpO1xufVxuXG5mdW5jdGlvbiByZW1vdmVMaXN0ZW5lcihldmVudE5hbWUpIHtcbiAgICAvLyBSZW1vdmUgbG9jYWwgbGlzdGVuZXJzXG4gICAgZGVsZXRlIGV2ZW50TGlzdGVuZXJzW2V2ZW50TmFtZV07XG5cbiAgICAvLyBOb3RpZnkgR28gbGlzdGVuZXJzXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdFWCcgKyBldmVudE5hbWUpO1xufVxuXG4vKipcbiAqIE9mZiB1bnJlZ2lzdGVycyBhIGxpc3RlbmVyIHByZXZpb3VzbHkgcmVnaXN0ZXJlZCB3aXRoIE9uLFxuICogb3B0aW9uYWxseSBtdWx0aXBsZSBsaXN0ZW5lcmVzIGNhbiBiZSB1bnJlZ2lzdGVyZWQgdmlhIGBhZGRpdGlvbmFsRXZlbnROYW1lc2BcbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0gIHsuLi5zdHJpbmd9IGFkZGl0aW9uYWxFdmVudE5hbWVzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFdmVudHNPZmYoZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lcykge1xuICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSlcblxuICAgIGlmIChhZGRpdGlvbmFsRXZlbnROYW1lcy5sZW5ndGggPiAwKSB7XG4gICAgICAgIGFkZGl0aW9uYWxFdmVudE5hbWVzLmZvckVhY2goZXZlbnROYW1lID0+IHtcbiAgICAgICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50TmFtZSlcbiAgICAgICAgfSlcbiAgICB9XG59XG5cbi8qKlxuICogT2ZmIHVucmVnaXN0ZXJzIGFsbCBldmVudCBsaXN0ZW5lcnMgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT25cbiAqL1xuIGV4cG9ydCBmdW5jdGlvbiBFdmVudHNPZmZBbGwoKSB7XG4gICAgY29uc3QgZXZlbnROYW1lcyA9IE9iamVjdC5rZXlzKGV2ZW50TGlzdGVuZXJzKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSAhPT0gZXZlbnROYW1lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudE5hbWVzW2ldKTtcbiAgICB9XG59XG5cbi8qKlxuICogbGlzdGVuZXJPZmYgdW5yZWdpc3RlcnMgYSBsaXN0ZW5lciBwcmV2aW91c2x5IHJlZ2lzdGVyZWQgd2l0aCBFdmVudHNPblxuICpcbiAqIEBwYXJhbSB7TGlzdGVuZXJ9IGxpc3RlbmVyXG4gKi9cbiBmdW5jdGlvbiBsaXN0ZW5lck9mZihsaXN0ZW5lcikge1xuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcbiAgICAvLyBSZW1vdmUgbG9jYWwgbGlzdGVuZXJcbiAgICBldmVudExpc3RlbmVyc1tldmVudE5hbWVdID0gZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXS5maWx0ZXIobCA9PiBsICE9PSBsaXN0ZW5lcik7XG5cbiAgICAvLyBDbGVhbiB1cCBpZiB0aGVyZSBhcmUgbm8gZXZlbnQgbGlzdGVuZXJzIGxlZnRcbiAgICBpZiAoZXZlbnRMaXN0ZW5lcnNbZXZlbnROYW1lXS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnROYW1lKTtcbiAgICB9XG59XG4iLCAiLypcbiBfICAgICAgIF9fICAgICAgXyBfX1xufCB8ICAgICAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA2ICovXG5cbmV4cG9ydCBjb25zdCBjYWxsYmFja3MgPSB7fTtcblxuLyoqXG4gKiBSZXR1cm5zIGEgbnVtYmVyIGZyb20gdGhlIG5hdGl2ZSBicm93c2VyIHJhbmRvbSBmdW5jdGlvblxuICpcbiAqIEByZXR1cm5zIG51bWJlclxuICovXG5mdW5jdGlvbiBjcnlwdG9SYW5kb20oKSB7XG5cdHZhciBhcnJheSA9IG5ldyBVaW50MzJBcnJheSgxKTtcblx0cmV0dXJuIHdpbmRvdy5jcnlwdG8uZ2V0UmFuZG9tVmFsdWVzKGFycmF5KVswXTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIGEgbnVtYmVyIHVzaW5nIGRhIG9sZC1za29vbCBNYXRoLlJhbmRvbVxuICogSSBsaWtlcyB0byBjYWxsIGl0IExPTFJhbmRvbVxuICpcbiAqIEByZXR1cm5zIG51bWJlclxuICovXG5mdW5jdGlvbiBiYXNpY1JhbmRvbSgpIHtcblx0cmV0dXJuIE1hdGgucmFuZG9tKCkgKiA5MDA3MTk5MjU0NzQwOTkxO1xufVxuXG4vLyBQaWNrIGEgcmFuZG9tIG51bWJlciBmdW5jdGlvbiBiYXNlZCBvbiBicm93c2VyIGNhcGFiaWxpdHlcbnZhciByYW5kb21GdW5jO1xuaWYgKHdpbmRvdy5jcnlwdG8pIHtcblx0cmFuZG9tRnVuYyA9IGNyeXB0b1JhbmRvbTtcbn0gZWxzZSB7XG5cdHJhbmRvbUZ1bmMgPSBiYXNpY1JhbmRvbTtcbn1cblxuXG4vKipcbiAqIENhbGwgc2VuZHMgYSBtZXNzYWdlIHRvIHRoZSBiYWNrZW5kIHRvIGNhbGwgdGhlIGJpbmRpbmcgd2l0aCB0aGVcbiAqIGdpdmVuIGRhdGEuIEEgcHJvbWlzZSBpcyByZXR1cm5lZCBhbmQgd2lsbCBiZSBjb21wbGV0ZWQgd2hlbiB0aGVcbiAqIGJhY2tlbmQgcmVzcG9uZHMuIFRoaXMgd2lsbCBiZSByZXNvbHZlZCB3aGVuIHRoZSBjYWxsIHdhcyBzdWNjZXNzZnVsXG4gKiBvciByZWplY3RlZCBpZiBhbiBlcnJvciBpcyBwYXNzZWQgYmFjay5cbiAqIFRoZXJlIGlzIGEgdGltZW91dCBtZWNoYW5pc20uIElmIHRoZSBjYWxsIGRvZXNuJ3QgcmVzcG9uZCBpbiB0aGUgZ2l2ZW5cbiAqIHRpbWUgKGluIG1pbGxpc2Vjb25kcykgdGhlbiB0aGUgcHJvbWlzZSBpcyByZWplY3RlZC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gbmFtZVxuICogQHBhcmFtIHthbnk9fSBhcmdzXG4gKiBAcGFyYW0ge251bWJlcj19IHRpbWVvdXRcbiAqIEByZXR1cm5zXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDYWxsKG5hbWUsIGFyZ3MsIHRpbWVvdXQpIHtcblxuXHQvLyBUaW1lb3V0IGluZmluaXRlIGJ5IGRlZmF1bHRcblx0aWYgKHRpbWVvdXQgPT0gbnVsbCkge1xuXHRcdHRpbWVvdXQgPSAwO1xuXHR9XG5cblx0Ly8gQ3JlYXRlIGEgcHJvbWlzZVxuXHRyZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24gKHJlc29sdmUsIHJlamVjdCkge1xuXG5cdFx0Ly8gQ3JlYXRlIGEgdW5pcXVlIGNhbGxiYWNrSURcblx0XHR2YXIgY2FsbGJhY2tJRDtcblx0XHRkbyB7XG5cdFx0XHRjYWxsYmFja0lEID0gbmFtZSArICctJyArIHJhbmRvbUZ1bmMoKTtcblx0XHR9IHdoaWxlIChjYWxsYmFja3NbY2FsbGJhY2tJRF0pO1xuXG5cdFx0dmFyIHRpbWVvdXRIYW5kbGU7XG5cdFx0Ly8gU2V0IHRpbWVvdXRcblx0XHRpZiAodGltZW91dCA+IDApIHtcblx0XHRcdHRpbWVvdXRIYW5kbGUgPSBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHtcblx0XHRcdFx0cmVqZWN0KEVycm9yKCdDYWxsIHRvICcgKyBuYW1lICsgJyB0aW1lZCBvdXQuIFJlcXVlc3QgSUQ6ICcgKyBjYWxsYmFja0lEKSk7XG5cdFx0XHR9LCB0aW1lb3V0KTtcblx0XHR9XG5cblx0XHQvLyBTdG9yZSBjYWxsYmFja1xuXHRcdGNhbGxiYWNrc1tjYWxsYmFja0lEXSA9IHtcblx0XHRcdHRpbWVvdXRIYW5kbGU6IHRpbWVvdXRIYW5kbGUsXG5cdFx0XHRyZWplY3Q6IHJlamVjdCxcblx0XHRcdHJlc29sdmU6IHJlc29sdmVcblx0XHR9O1xuXG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IHBheWxvYWQgPSB7XG5cdFx0XHRcdG5hbWUsXG5cdFx0XHRcdGFyZ3MsXG5cdFx0XHRcdGNhbGxiYWNrSUQsXG5cdFx0XHR9O1xuXG4gICAgICAgICAgICAvLyBNYWtlIHRoZSBjYWxsXG4gICAgICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ0MnICsgSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmVcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZSk7XG4gICAgICAgIH1cbiAgICB9KTtcbn1cblxud2luZG93Lk9iZnVzY2F0ZWRDYWxsID0gKGlkLCBhcmdzLCB0aW1lb3V0KSA9PiB7XG5cbiAgICAvLyBUaW1lb3V0IGluZmluaXRlIGJ5IGRlZmF1bHRcbiAgICBpZiAodGltZW91dCA9PSBudWxsKSB7XG4gICAgICAgIHRpbWVvdXQgPSAwO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZSBhIHByb21pc2VcbiAgICByZXR1cm4gbmV3IFByb21pc2UoZnVuY3Rpb24gKHJlc29sdmUsIHJlamVjdCkge1xuXG4gICAgICAgIC8vIENyZWF0ZSBhIHVuaXF1ZSBjYWxsYmFja0lEXG4gICAgICAgIHZhciBjYWxsYmFja0lEO1xuICAgICAgICBkbyB7XG4gICAgICAgICAgICBjYWxsYmFja0lEID0gaWQgKyAnLScgKyByYW5kb21GdW5jKCk7XG4gICAgICAgIH0gd2hpbGUgKGNhbGxiYWNrc1tjYWxsYmFja0lEXSk7XG5cbiAgICAgICAgdmFyIHRpbWVvdXRIYW5kbGU7XG4gICAgICAgIC8vIFNldCB0aW1lb3V0XG4gICAgICAgIGlmICh0aW1lb3V0ID4gMCkge1xuICAgICAgICAgICAgdGltZW91dEhhbmRsZSA9IHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHJlamVjdChFcnJvcignQ2FsbCB0byBtZXRob2QgJyArIGlkICsgJyB0aW1lZCBvdXQuIFJlcXVlc3QgSUQ6ICcgKyBjYWxsYmFja0lEKSk7XG4gICAgICAgICAgICB9LCB0aW1lb3V0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFN0b3JlIGNhbGxiYWNrXG4gICAgICAgIGNhbGxiYWNrc1tjYWxsYmFja0lEXSA9IHtcbiAgICAgICAgICAgIHRpbWVvdXRIYW5kbGU6IHRpbWVvdXRIYW5kbGUsXG4gICAgICAgICAgICByZWplY3Q6IHJlamVjdCxcbiAgICAgICAgICAgIHJlc29sdmU6IHJlc29sdmVcbiAgICAgICAgfTtcblxuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IHtcblx0XHRcdFx0aWQsXG5cdFx0XHRcdGFyZ3MsXG5cdFx0XHRcdGNhbGxiYWNrSUQsXG5cdFx0XHR9O1xuXG4gICAgICAgICAgICAvLyBNYWtlIHRoZSBjYWxsXG4gICAgICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ2MnICsgSlNPTi5zdHJpbmdpZnkocGF5bG9hZCkpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmVcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZSk7XG4gICAgICAgIH1cbiAgICB9KTtcbn07XG5cblxuLyoqXG4gKiBDYWxsZWQgYnkgdGhlIGJhY2tlbmQgdG8gcmV0dXJuIGRhdGEgdG8gYSBwcmV2aW91c2x5IGNhbGxlZFxuICogYmluZGluZyBpbnZvY2F0aW9uXG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtzdHJpbmd9IGluY29taW5nTWVzc2FnZVxuICovXG5leHBvcnQgZnVuY3Rpb24gQ2FsbGJhY2soaW5jb21pbmdNZXNzYWdlKSB7XG5cdC8vIFBhcnNlIHRoZSBtZXNzYWdlXG5cdGxldCBtZXNzYWdlO1xuXHR0cnkge1xuXHRcdG1lc3NhZ2UgPSBKU09OLnBhcnNlKGluY29taW5nTWVzc2FnZSk7XG5cdH0gY2F0Y2ggKGUpIHtcblx0XHRjb25zdCBlcnJvciA9IGBJbnZhbGlkIEpTT04gcGFzc2VkIHRvIGNhbGxiYWNrOiAke2UubWVzc2FnZX0uIE1lc3NhZ2U6ICR7aW5jb21pbmdNZXNzYWdlfWA7XG5cdFx0cnVudGltZS5Mb2dEZWJ1ZyhlcnJvcik7XG5cdFx0dGhyb3cgbmV3IEVycm9yKGVycm9yKTtcblx0fVxuXHRsZXQgY2FsbGJhY2tJRCA9IG1lc3NhZ2UuY2FsbGJhY2tpZDtcblx0bGV0IGNhbGxiYWNrRGF0YSA9IGNhbGxiYWNrc1tjYWxsYmFja0lEXTtcblx0aWYgKCFjYWxsYmFja0RhdGEpIHtcblx0XHRjb25zdCBlcnJvciA9IGBDYWxsYmFjayAnJHtjYWxsYmFja0lEfScgbm90IHJlZ2lzdGVyZWQhISFgO1xuXHRcdGNvbnNvbGUuZXJyb3IoZXJyb3IpOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lXG5cdFx0dGhyb3cgbmV3IEVycm9yKGVycm9yKTtcblx0fVxuXHRjbGVhclRpbWVvdXQoY2FsbGJhY2tEYXRhLnRpbWVvdXRIYW5kbGUpO1xuXG5cdGRlbGV0ZSBjYWxsYmFja3NbY2FsbGJhY2tJRF07XG5cblx0aWYgKG1lc3NhZ2UuZXJyb3IpIHtcblx0XHRjYWxsYmFja0RhdGEucmVqZWN0KG1lc3NhZ2UuZXJyb3IpO1xuXHR9IGVsc2Uge1xuXHRcdGNhbGxiYWNrRGF0YS5yZXNvbHZlKG1lc3NhZ2UucmVzdWx0KTtcblx0fVxufVxuIiwgIi8qXG4gXyAgICAgICBfXyAgICAgIF8gX18gICAgXG58IHwgICAgIC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gICkgXG58X18vfF9fL1xcX18sXy9fL18vX19fXy8gIFxuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDYgKi9cblxuaW1wb3J0IHtDYWxsfSBmcm9tICcuL2NhbGxzJztcblxuLy8gVGhpcyBpcyB3aGVyZSB3ZSBiaW5kIGdvIG1ldGhvZCB3cmFwcGVyc1xud2luZG93LmdvID0ge307XG5cbmV4cG9ydCBmdW5jdGlvbiBTZXRCaW5kaW5ncyhiaW5kaW5nc01hcCkge1xuXHR0cnkge1xuXHRcdGJpbmRpbmdzTWFwID0gSlNPTi5wYXJzZShiaW5kaW5nc01hcCk7XG5cdH0gY2F0Y2ggKGUpIHtcblx0XHRjb25zb2xlLmVycm9yKGUpO1xuXHR9XG5cblx0Ly8gSW5pdGlhbGlzZSB0aGUgYmluZGluZ3MgbWFwXG5cdHdpbmRvdy5nbyA9IHdpbmRvdy5nbyB8fCB7fTtcblxuXHQvLyBJdGVyYXRlIHBhY2thZ2UgbmFtZXNcblx0T2JqZWN0LmtleXMoYmluZGluZ3NNYXApLmZvckVhY2goKHBhY2thZ2VOYW1lKSA9PiB7XG5cblx0XHQvLyBDcmVhdGUgaW5uZXIgbWFwIGlmIGl0IGRvZXNuJ3QgZXhpc3Rcblx0XHR3aW5kb3cuZ29bcGFja2FnZU5hbWVdID0gd2luZG93LmdvW3BhY2thZ2VOYW1lXSB8fCB7fTtcblxuXHRcdC8vIEl0ZXJhdGUgc3RydWN0IG5hbWVzXG5cdFx0T2JqZWN0LmtleXMoYmluZGluZ3NNYXBbcGFja2FnZU5hbWVdKS5mb3JFYWNoKChzdHJ1Y3ROYW1lKSA9PiB7XG5cblx0XHRcdC8vIENyZWF0ZSBpbm5lciBtYXAgaWYgaXQgZG9lc24ndCBleGlzdFxuXHRcdFx0d2luZG93LmdvW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXSA9IHdpbmRvdy5nb1twYWNrYWdlTmFtZV1bc3RydWN0TmFtZV0gfHwge307XG5cblx0XHRcdE9iamVjdC5rZXlzKGJpbmRpbmdzTWFwW3BhY2thZ2VOYW1lXVtzdHJ1Y3ROYW1lXSkuZm9yRWFjaCgobWV0aG9kTmFtZSkgPT4ge1xuXG5cdFx0XHRcdHdpbmRvdy5nb1twYWNrYWdlTmFtZV1bc3RydWN0TmFtZV1bbWV0aG9kTmFtZV0gPSBmdW5jdGlvbiAoKSB7XG5cblx0XHRcdFx0XHQvLyBObyB0aW1lb3V0IGJ5IGRlZmF1bHRcblx0XHRcdFx0XHRsZXQgdGltZW91dCA9IDA7XG5cblx0XHRcdFx0XHQvLyBBY3R1YWwgZnVuY3Rpb25cblx0XHRcdFx0XHRmdW5jdGlvbiBkeW5hbWljKCkge1xuXHRcdFx0XHRcdFx0Y29uc3QgYXJncyA9IFtdLnNsaWNlLmNhbGwoYXJndW1lbnRzKTtcblx0XHRcdFx0XHRcdHJldHVybiBDYWxsKFtwYWNrYWdlTmFtZSwgc3RydWN0TmFtZSwgbWV0aG9kTmFtZV0uam9pbignLicpLCBhcmdzLCB0aW1lb3V0KTtcblx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHQvLyBBbGxvdyBzZXR0aW5nIHRpbWVvdXQgdG8gZnVuY3Rpb25cblx0XHRcdFx0XHRkeW5hbWljLnNldFRpbWVvdXQgPSBmdW5jdGlvbiAobmV3VGltZW91dCkge1xuXHRcdFx0XHRcdFx0dGltZW91dCA9IG5ld1RpbWVvdXQ7XG5cdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdC8vIEFsbG93IGdldHRpbmcgdGltZW91dCB0byBmdW5jdGlvblxuXHRcdFx0XHRcdGR5bmFtaWMuZ2V0VGltZW91dCA9IGZ1bmN0aW9uICgpIHtcblx0XHRcdFx0XHRcdHJldHVybiB0aW1lb3V0O1xuXHRcdFx0XHRcdH07XG5cblx0XHRcdFx0XHRyZXR1cm4gZHluYW1pYztcblx0XHRcdFx0fSgpO1xuXHRcdFx0fSk7XG5cdFx0fSk7XG5cdH0pO1xufVxuIiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cblxuaW1wb3J0IHtDYWxsfSBmcm9tIFwiLi9jYWxsc1wiO1xuXG5leHBvcnQgZnVuY3Rpb24gV2luZG93UmVsb2FkKCkge1xuICAgIHdpbmRvdy5sb2NhdGlvbi5yZWxvYWQoKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1JlbG9hZEFwcCgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dSJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRTeXN0ZW1EZWZhdWx0VGhlbWUoKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXQVNEVCcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0TGlnaHRUaGVtZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBTFQnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1NldERhcmtUaGVtZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dBRFQnKTtcbn1cblxuLyoqXG4gKiBQbGFjZSB0aGUgd2luZG93IGluIHRoZSBjZW50ZXIgb2YgdGhlIHNjcmVlblxuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0NlbnRlcigpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1djJyk7XG59XG5cbi8qKlxuICogU2V0cyB0aGUgd2luZG93IHRpdGxlXG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IHRpdGxlXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRUaXRsZSh0aXRsZSkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV1QnICsgdGl0bGUpO1xufVxuXG4vKipcbiAqIE1ha2VzIHRoZSB3aW5kb3cgZ28gZnVsbHNjcmVlblxuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0Z1bGxzY3JlZW4oKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXRicpO1xufVxuXG4vKipcbiAqIFJldmVydHMgdGhlIHdpbmRvdyBmcm9tIGZ1bGxzY3JlZW5cbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dVbmZ1bGxzY3JlZW4oKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXZicpO1xufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIHN0YXRlIG9mIHRoZSB3aW5kb3csIGkuZS4gd2hldGhlciB0aGUgd2luZG93IGlzIGluIGZ1bGwgc2NyZWVuIG1vZGUgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8Ym9vbGVhbj59IFRoZSBzdGF0ZSBvZiB0aGUgd2luZG93XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dJc0Z1bGxzY3JlZW4oKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93SXNGdWxsc2NyZWVuXCIpO1xufVxuXG4vKipcbiAqIFNldCB0aGUgU2l6ZSBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRTaXplKHdpZHRoLCBoZWlnaHQpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dzOicgKyB3aWR0aCArICc6JyArIGhlaWdodCk7XG59XG5cbi8qKlxuICogR2V0IHRoZSBTaXplIG9mIHRoZSB3aW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPHt3OiBudW1iZXIsIGg6IG51bWJlcn0+fSBUaGUgc2l6ZSBvZiB0aGUgd2luZG93XG5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0dldFNpemUoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93R2V0U2l6ZVwiKTtcbn1cblxuLyoqXG4gKiBTZXQgdGhlIG1heGltdW0gc2l6ZSBvZiB0aGUgd2luZG93XG4gKlxuICogQGV4cG9ydFxuICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gKiBAcGFyYW0ge251bWJlcn0gaGVpZ2h0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRNYXhTaXplKHdpZHRoLCBoZWlnaHQpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1daOicgKyB3aWR0aCArICc6JyArIGhlaWdodCk7XG59XG5cbi8qKlxuICogU2V0IHRoZSBtaW5pbXVtIHNpemUgb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7bnVtYmVyfSB3aWR0aFxuICogQHBhcmFtIHtudW1iZXJ9IGhlaWdodFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0TWluU2l6ZSh3aWR0aCwgaGVpZ2h0KSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXejonICsgd2lkdGggKyAnOicgKyBoZWlnaHQpO1xufVxuXG5cblxuLyoqXG4gKiBTZXQgdGhlIHdpbmRvdyBBbHdheXNPblRvcCBvciBub3Qgb24gdG9wXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0QWx3YXlzT25Ub3AoYikge1xuXG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXQVRQOicgKyAoYiA/ICcxJyA6ICcwJykpO1xufVxuXG5cblxuXG4vKipcbiAqIFNldCB0aGUgUG9zaXRpb24gb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7bnVtYmVyfSB4XG4gKiBAcGFyYW0ge251bWJlcn0geVxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93U2V0UG9zaXRpb24oeCwgeSkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3A6JyArIHggKyAnOicgKyB5KTtcbn1cblxuLyoqXG4gKiBHZXQgdGhlIFBvc2l0aW9uIG9mIHRoZSB3aW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPHt4OiBudW1iZXIsIHk6IG51bWJlcn0+fSBUaGUgcG9zaXRpb24gb2YgdGhlIHdpbmRvd1xuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93R2V0UG9zaXRpb24oKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93R2V0UG9zXCIpO1xufVxuXG4vKipcbiAqIEhpZGUgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0hpZGUoKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXSCcpO1xufVxuXG4vKipcbiAqIFNob3cgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1Nob3coKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXUycpO1xufVxuXG4vKipcbiAqIE1heGltaXNlIHRoZSBXaW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dNYXhpbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dNJyk7XG59XG5cbi8qKlxuICogVG9nZ2xlIHRoZSBNYXhpbWlzZSBvZiB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93VG9nZ2xlTWF4aW1pc2UoKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXdCcpO1xufVxuXG4vKipcbiAqIFVubWF4aW1pc2UgdGhlIFdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd1VubWF4aW1pc2UoKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdXVScpO1xufVxuXG4vKipcbiAqIFJldHVybnMgdGhlIHN0YXRlIG9mIHRoZSB3aW5kb3csIGkuZS4gd2hldGhlciB0aGUgd2luZG93IGlzIG1heGltaXNlZCBvciBub3QuXG4gKlxuICogQGV4cG9ydFxuICogQHJldHVybiB7UHJvbWlzZTxib29sZWFuPn0gVGhlIHN0YXRlIG9mIHRoZSB3aW5kb3dcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFdpbmRvd0lzTWF4aW1pc2VkKCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOldpbmRvd0lzTWF4aW1pc2VkXCIpO1xufVxuXG4vKipcbiAqIE1pbmltaXNlIHRoZSBXaW5kb3dcbiAqXG4gKiBAZXhwb3J0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dNaW5pbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1dtJyk7XG59XG5cbi8qKlxuICogVW5taW5pbWlzZSB0aGUgV2luZG93XG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93VW5taW5pbWlzZSgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1d1Jyk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgc3RhdGUgb2YgdGhlIHdpbmRvdywgaS5lLiB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgbWluaW1pc2VkIG9yIG5vdC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPGJvb2xlYW4+fSBUaGUgc3RhdGUgb2YgdGhlIHdpbmRvd1xuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SXNNaW5pbWlzZWQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93SXNNaW5pbWlzZWRcIik7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgc3RhdGUgb2YgdGhlIHdpbmRvdywgaS5lLiB3aGV0aGVyIHRoZSB3aW5kb3cgaXMgbm9ybWFsIG9yIG5vdC5cbiAqXG4gKiBAZXhwb3J0XG4gKiBAcmV0dXJuIHtQcm9taXNlPGJvb2xlYW4+fSBUaGUgc3RhdGUgb2YgdGhlIHdpbmRvd1xuICovXG5leHBvcnQgZnVuY3Rpb24gV2luZG93SXNOb3JtYWwoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6V2luZG93SXNOb3JtYWxcIik7XG59XG5cbi8qKlxuICogU2V0cyB0aGUgYmFja2dyb3VuZCBjb2xvdXIgb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7bnVtYmVyfSBSIFJlZFxuICogQHBhcmFtIHtudW1iZXJ9IEcgR3JlZW5cbiAqIEBwYXJhbSB7bnVtYmVyfSBCIEJsdWVcbiAqIEBwYXJhbSB7bnVtYmVyfSBBIEFscGhhXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBXaW5kb3dTZXRCYWNrZ3JvdW5kQ29sb3VyKFIsIEcsIEIsIEEpIHtcbiAgICBsZXQgcmdiYSA9IEpTT04uc3RyaW5naWZ5KHtyOiBSIHx8IDAsIGc6IEcgfHwgMCwgYjogQiB8fCAwLCBhOiBBIHx8IDI1NX0pO1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnV3I6JyArIHJnYmEpO1xufVxuXG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuXG5pbXBvcnQge0NhbGx9IGZyb20gXCIuL2NhbGxzXCI7XG5cblxuLyoqXG4gKiBHZXRzIHRoZSBhbGwgc2NyZWVucy4gQ2FsbCB0aGlzIGFuZXcgZWFjaCB0aW1lIHlvdSB3YW50IHRvIHJlZnJlc2ggZGF0YSBmcm9tIHRoZSB1bmRlcmx5aW5nIHdpbmRvd2luZyBzeXN0ZW0uXG4gKiBAZXhwb3J0XG4gKiBAdHlwZWRlZiB7aW1wb3J0KCcuLi93cmFwcGVyL3J1bnRpbWUnKS5TY3JlZW59IFNjcmVlblxuICogQHJldHVybiB7UHJvbWlzZTx7U2NyZWVuW119Pn0gVGhlIHNjcmVlbnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNjcmVlbkdldEFsbCgpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpTY3JlZW5HZXRBbGxcIik7XG59XG4iLCAiLyoqXG4gKiBAZGVzY3JpcHRpb246IFVzZSB0aGUgc3lzdGVtIGRlZmF1bHQgYnJvd3NlciB0byBvcGVuIHRoZSB1cmxcbiAqIEBwYXJhbSB7c3RyaW5nfSB1cmwgXG4gKiBAcmV0dXJuIHt2b2lkfVxuICovXG5leHBvcnQgZnVuY3Rpb24gQnJvd3Nlck9wZW5VUkwodXJsKSB7XG4gIHdpbmRvdy5XYWlsc0ludm9rZSgnQk86JyArIHVybCk7XG59IiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cbmltcG9ydCB7Q2FsbH0gZnJvbSBcIi4vY2FsbHNcIjtcblxuLyoqXG4gKiBTZXQgdGhlIFNpemUgb2YgdGhlIHdpbmRvd1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDbGlwYm9hcmRTZXRUZXh0KHRleHQpIHtcbiAgICByZXR1cm4gQ2FsbChcIjp3YWlsczpDbGlwYm9hcmRTZXRUZXh0XCIsIFt0ZXh0XSk7XG59XG5cbi8qKlxuICogR2V0IHRoZSB0ZXh0IGNvbnRlbnQgb2YgdGhlIGNsaXBib2FyZFxuICpcbiAqIEBleHBvcnRcbiAqIEByZXR1cm4ge1Byb21pc2U8e3N0cmluZ30+fSBUZXh0IGNvbnRlbnQgb2YgdGhlIGNsaXBib2FyZFxuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBDbGlwYm9hcmRHZXRUZXh0KCkge1xuICAgIHJldHVybiBDYWxsKFwiOndhaWxzOkNsaXBib2FyZEdldFRleHRcIik7XG59IiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuaW1wb3J0ICogYXMgTG9nIGZyb20gJy4vbG9nJztcbmltcG9ydCB7ZXZlbnRMaXN0ZW5lcnMsIEV2ZW50c0VtaXQsIEV2ZW50c05vdGlmeSwgRXZlbnRzT2ZmLCBFdmVudHNPbiwgRXZlbnRzT25jZSwgRXZlbnRzT25NdWx0aXBsZX0gZnJvbSAnLi9ldmVudHMnO1xuaW1wb3J0IHtDYWxsLCBDYWxsYmFjaywgY2FsbGJhY2tzfSBmcm9tICcuL2NhbGxzJztcbmltcG9ydCB7U2V0QmluZGluZ3N9IGZyb20gXCIuL2JpbmRpbmdzXCI7XG5pbXBvcnQgKiBhcyBXaW5kb3cgZnJvbSBcIi4vd2luZG93XCI7XG5pbXBvcnQgKiBhcyBTY3JlZW4gZnJvbSBcIi4vc2NyZWVuXCI7XG5pbXBvcnQgKiBhcyBCcm93c2VyIGZyb20gXCIuL2Jyb3dzZXJcIjtcbmltcG9ydCAqIGFzIENsaXBib2FyZCBmcm9tIFwiLi9jbGlwYm9hcmRcIjtcblxuXG5leHBvcnQgZnVuY3Rpb24gUXVpdCgpIHtcbiAgICB3aW5kb3cuV2FpbHNJbnZva2UoJ1EnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFNob3coKSB7XG4gICAgd2luZG93LldhaWxzSW52b2tlKCdTJyk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBIaWRlKCkge1xuICAgIHdpbmRvdy5XYWlsc0ludm9rZSgnSCcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gRW52aXJvbm1lbnQoKSB7XG4gICAgcmV0dXJuIENhbGwoXCI6d2FpbHM6RW52aXJvbm1lbnRcIik7XG59XG5cbi8vIFRoZSBKUyBydW50aW1lXG53aW5kb3cucnVudGltZSA9IHtcbiAgICAuLi5Mb2csXG4gICAgLi4uV2luZG93LFxuICAgIC4uLkJyb3dzZXIsXG4gICAgLi4uU2NyZWVuLFxuICAgIC4uLkNsaXBib2FyZCxcbiAgICBFdmVudHNPbixcbiAgICBFdmVudHNPbmNlLFxuICAgIEV2ZW50c09uTXVsdGlwbGUsXG4gICAgRXZlbnRzRW1pdCxcbiAgICBFdmVudHNPZmYsXG4gICAgRW52aXJvbm1lbnQsXG4gICAgU2hvdyxcbiAgICBIaWRlLFxuICAgIFF1aXRcbn07XG5cbi8vIEludGVybmFsIHdhaWxzIGVuZHBvaW50c1xud2luZG93LndhaWxzID0ge1xuICAgIENhbGxiYWNrLFxuICAgIEV2ZW50c05vdGlmeSxcbiAgICBTZXRCaW5kaW5ncyxcbiAgICBldmVudExpc3RlbmVycyxcbiAgICBjYWxsYmFja3MsXG4gICAgZmxhZ3M6IHtcbiAgICAgICAgZGlzYWJsZVNjcm9sbGJhckRyYWc6IGZhbHNlLFxuICAgICAgICBkaXNhYmxlV2FpbHNEZWZhdWx0Q29udGV4dE1lbnU6IGZhbHNlLFxuICAgICAgICBlbmFibGVSZXNpemU6IGZhbHNlLFxuICAgICAgICBkZWZhdWx0Q3Vyc29yOiBudWxsLFxuICAgICAgICBib3JkZXJUaGlja25lc3M6IDYsXG4gICAgICAgIHNob3VsZERyYWc6IGZhbHNlLFxuICAgICAgICBkZWZlckRyYWdUb01vdXNlTW92ZTogdHJ1ZSxcbiAgICAgICAgY3NzRHJhZ1Byb3BlcnR5OiBcIi0td2FpbHMtZHJhZ2dhYmxlXCIsXG4gICAgICAgIGNzc0RyYWdWYWx1ZTogXCJkcmFnXCIsXG4gICAgfVxufTtcblxuLy8gU2V0IHRoZSBiaW5kaW5nc1xuaWYgKHdpbmRvdy53YWlsc2JpbmRpbmdzKSB7XG4gICAgd2luZG93LndhaWxzLlNldEJpbmRpbmdzKHdpbmRvdy53YWlsc2JpbmRpbmdzKTtcbiAgICBkZWxldGUgd2luZG93LndhaWxzLlNldEJpbmRpbmdzO1xufVxuXG4vLyBUaGlzIGlzIGV2YWx1YXRlZCBhdCBidWlsZCB0aW1lIGluIHBhY2thZ2UuanNvblxuLy8gY29uc3QgZGV2ID0gMDtcbi8vIGNvbnN0IHByb2R1Y3Rpb24gPSAxO1xuaWYgKEVOViA9PT0gMSkge1xuICAgIGRlbGV0ZSB3aW5kb3cud2FpbHNiaW5kaW5ncztcbn1cblxubGV0IGRyYWdUZXN0ID0gZnVuY3Rpb24gKGUpIHtcbiAgICB2YXIgdmFsID0gd2luZG93LmdldENvbXB1dGVkU3R5bGUoZS50YXJnZXQpLmdldFByb3BlcnR5VmFsdWUod2luZG93LndhaWxzLmZsYWdzLmNzc0RyYWdQcm9wZXJ0eSk7XG4gICAgaWYgKHZhbCkge1xuICAgICAgdmFsID0gdmFsLnRyaW0oKTtcbiAgICB9XG4gICAgXG4gICAgaWYgKHZhbCAhPT0gd2luZG93LndhaWxzLmZsYWdzLmNzc0RyYWdWYWx1ZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgaWYgKGUuYnV0dG9ucyAhPT0gMSkge1xuICAgICAgICAvLyBEbyBub3Qgc3RhcnQgZHJhZ2dpbmcgaWYgbm90IHRoZSBwcmltYXJ5IGJ1dHRvbiBoYXMgYmVlbiBjbGlja2VkLlxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgaWYgKGUuZGV0YWlsICE9PSAxKSB7XG4gICAgICAgIC8vIERvIG5vdCBzdGFydCBkcmFnZ2luZyBpZiBtb3JlIHRoYW4gb25jZSBoYXMgYmVlbiBjbGlja2VkLCBlLmcuIHdoZW4gZG91YmxlIGNsaWNraW5nXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbn07XG5cbndpbmRvdy53YWlscy5zZXRDU1NEcmFnUHJvcGVydGllcyA9IGZ1bmN0aW9uIChwcm9wZXJ0eSwgdmFsdWUpIHtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MuY3NzRHJhZ1Byb3BlcnR5ID0gcHJvcGVydHk7XG4gICAgd2luZG93LndhaWxzLmZsYWdzLmNzc0RyYWdWYWx1ZSA9IHZhbHVlO1xufVxuXG53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbW91c2Vkb3duJywgKGUpID0+IHtcblxuICAgIC8vIENoZWNrIGZvciByZXNpemluZ1xuICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSkge1xuICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoXCJyZXNpemU6XCIgKyB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSk7XG4gICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChkcmFnVGVzdChlKSkge1xuICAgICAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRpc2FibGVTY3JvbGxiYXJEcmFnKSB7XG4gICAgICAgICAgICAvLyBUaGlzIGNoZWNrcyBmb3IgY2xpY2tzIG9uIHRoZSBzY3JvbGwgYmFyXG4gICAgICAgICAgICBpZiAoZS5vZmZzZXRYID4gZS50YXJnZXQuY2xpZW50V2lkdGggfHwgZS5vZmZzZXRZID4gZS50YXJnZXQuY2xpZW50SGVpZ2h0KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MuZGVmZXJEcmFnVG9Nb3VzZU1vdmUpIHtcbiAgICAgICAgICAgIHdpbmRvdy53YWlscy5mbGFncy5zaG91bGREcmFnID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgICAgICAgICAgd2luZG93LldhaWxzSW52b2tlKFwiZHJhZ1wiKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgfSBlbHNlIHtcbiAgICAgICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbiAgICB9XG59KTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNldXAnLCAoKSA9PiB7XG4gICAgd2luZG93LndhaWxzLmZsYWdzLnNob3VsZERyYWcgPSBmYWxzZTtcbn0pO1xuXG5mdW5jdGlvbiBzZXRSZXNpemUoY3Vyc29yKSB7XG4gICAgZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IGN1cnNvciB8fCB3aW5kb3cud2FpbHMuZmxhZ3MuZGVmYXVsdEN1cnNvcjtcbiAgICB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSA9IGN1cnNvcjtcbn1cblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21vdXNlbW92ZScsIGZ1bmN0aW9uIChlKSB7XG4gICAgaWYgKHdpbmRvdy53YWlscy5mbGFncy5zaG91bGREcmFnKSB7XG4gICAgICAgIHdpbmRvdy53YWlscy5mbGFncy5zaG91bGREcmFnID0gZmFsc2U7XG4gICAgICAgIGxldCBtb3VzZVByZXNzZWQgPSBlLmJ1dHRvbnMgIT09IHVuZGVmaW5lZCA/IGUuYnV0dG9ucyA6IGUud2hpY2g7XG4gICAgICAgIGlmIChtb3VzZVByZXNzZWQgPiAwKSB7XG4gICAgICAgICAgICB3aW5kb3cuV2FpbHNJbnZva2UoXCJkcmFnXCIpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgfVxuICAgIGlmICghd2luZG93LndhaWxzLmZsYWdzLmVuYWJsZVJlc2l6ZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh3aW5kb3cud2FpbHMuZmxhZ3MuZGVmYXVsdEN1cnNvciA9PSBudWxsKSB7XG4gICAgICAgIHdpbmRvdy53YWlscy5mbGFncy5kZWZhdWx0Q3Vyc29yID0gZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvcjtcbiAgICB9XG4gICAgaWYgKHdpbmRvdy5vdXRlcldpZHRoIC0gZS5jbGllbnRYIDwgd2luZG93LndhaWxzLmZsYWdzLmJvcmRlclRoaWNrbmVzcyAmJiB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzKSB7XG4gICAgICAgIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBcInNlLXJlc2l6ZVwiO1xuICAgIH1cbiAgICBsZXQgcmlnaHRCb3JkZXIgPSB3aW5kb3cub3V0ZXJXaWR0aCAtIGUuY2xpZW50WCA8IHdpbmRvdy53YWlscy5mbGFncy5ib3JkZXJUaGlja25lc3M7XG4gICAgbGV0IGxlZnRCb3JkZXIgPSBlLmNsaWVudFggPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzO1xuICAgIGxldCB0b3BCb3JkZXIgPSBlLmNsaWVudFkgPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzO1xuICAgIGxldCBib3R0b21Cb3JkZXIgPSB3aW5kb3cub3V0ZXJIZWlnaHQgLSBlLmNsaWVudFkgPCB3aW5kb3cud2FpbHMuZmxhZ3MuYm9yZGVyVGhpY2tuZXNzO1xuXG4gICAgLy8gSWYgd2UgYXJlbid0IG9uIGFuIGVkZ2UsIGJ1dCB3ZXJlLCByZXNldCB0aGUgY3Vyc29yIHRvIGRlZmF1bHRcbiAgICBpZiAoIWxlZnRCb3JkZXIgJiYgIXJpZ2h0Qm9yZGVyICYmICF0b3BCb3JkZXIgJiYgIWJvdHRvbUJvcmRlciAmJiB3aW5kb3cud2FpbHMuZmxhZ3MucmVzaXplRWRnZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHNldFJlc2l6ZSgpO1xuICAgIH0gZWxzZSBpZiAocmlnaHRCb3JkZXIgJiYgYm90dG9tQm9yZGVyKSBzZXRSZXNpemUoXCJzZS1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAobGVmdEJvcmRlciAmJiBib3R0b21Cb3JkZXIpIHNldFJlc2l6ZShcInN3LXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChsZWZ0Qm9yZGVyICYmIHRvcEJvcmRlcikgc2V0UmVzaXplKFwibnctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHRvcEJvcmRlciAmJiByaWdodEJvcmRlcikgc2V0UmVzaXplKFwibmUtcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKGxlZnRCb3JkZXIpIHNldFJlc2l6ZShcInctcmVzaXplXCIpO1xuICAgIGVsc2UgaWYgKHRvcEJvcmRlcikgc2V0UmVzaXplKFwibi1yZXNpemVcIik7XG4gICAgZWxzZSBpZiAoYm90dG9tQm9yZGVyKSBzZXRSZXNpemUoXCJzLXJlc2l6ZVwiKTtcbiAgICBlbHNlIGlmIChyaWdodEJvcmRlcikgc2V0UmVzaXplKFwiZS1yZXNpemVcIik7XG5cbn0pO1xuXG4vLyBTZXR1cCBjb250ZXh0IG1lbnUgaG9va1xud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2NvbnRleHRtZW51JywgZnVuY3Rpb24gKGUpIHtcbiAgICBpZiAod2luZG93LndhaWxzLmZsYWdzLmRpc2FibGVXYWlsc0RlZmF1bHRDb250ZXh0TWVudSkge1xuICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7XG4gICAgfVxufSk7XG5cbndpbmRvdy5XYWlsc0ludm9rZShcInJ1bnRpbWU6cmVhZHlcIik7Il0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsV0FBUyxlQUFlLE9BQU8sU0FBUztBQUl2QyxXQUFPLFlBQVksTUFBTSxRQUFRLE9BQU87QUFBQSxFQUN6QztBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxTQUFTLFNBQVM7QUFDakMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsUUFBUSxTQUFTO0FBQ2hDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxXQUFXLFNBQVM7QUFDbkMsbUJBQWUsS0FBSyxPQUFPO0FBQUEsRUFDNUI7QUFRTyxXQUFTLFNBQVMsU0FBUztBQUNqQyxtQkFBZSxLQUFLLE9BQU87QUFBQSxFQUM1QjtBQVFPLFdBQVMsU0FBUyxTQUFTO0FBQ2pDLG1CQUFlLEtBQUssT0FBTztBQUFBLEVBQzVCO0FBUU8sV0FBUyxZQUFZLFVBQVU7QUFDckMsbUJBQWUsS0FBSyxRQUFRO0FBQUEsRUFDN0I7QUFHTyxNQUFNLFdBQVc7QUFBQSxJQUN2QixPQUFPO0FBQUEsSUFDUCxPQUFPO0FBQUEsSUFDUCxNQUFNO0FBQUEsSUFDTixTQUFTO0FBQUEsSUFDVCxPQUFPO0FBQUEsRUFDUjs7O0FDOUZBLE1BQU0sV0FBTixNQUFlO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQVFYLFlBQVksV0FBVyxVQUFVLGNBQWM7QUFDM0MsV0FBSyxZQUFZO0FBRWpCLFdBQUssZUFBZSxnQkFBZ0I7QUFHcEMsV0FBSyxXQUFXLENBQUMsU0FBUztBQUN0QixpQkFBUyxNQUFNLE1BQU0sSUFBSTtBQUV6QixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFFTyxNQUFNLGlCQUFpQixDQUFDO0FBV3hCLFdBQVMsaUJBQWlCLFdBQVcsVUFBVSxjQUFjO0FBQ2hFLG1CQUFlLFNBQVMsSUFBSSxlQUFlLFNBQVMsS0FBSyxDQUFDO0FBQzFELFVBQU0sZUFBZSxJQUFJLFNBQVMsV0FBVyxVQUFVLFlBQVk7QUFDbkUsbUJBQWUsU0FBUyxFQUFFLEtBQUssWUFBWTtBQUMzQyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLFNBQVMsV0FBVyxVQUFVO0FBQzFDLFdBQU8saUJBQWlCLFdBQVcsVUFBVSxFQUFFO0FBQUEsRUFDbkQ7QUFVTyxXQUFTLFdBQVcsV0FBVyxVQUFVO0FBQzVDLFdBQU8saUJBQWlCLFdBQVcsVUFBVSxDQUFDO0FBQUEsRUFDbEQ7QUFFQSxXQUFTLGdCQUFnQixXQUFXO0FBR2hDLFFBQUksWUFBWSxVQUFVO0FBRzFCLFFBQUksZUFBZSxTQUFTLEdBQUc7QUFHM0IsWUFBTSx1QkFBdUIsZUFBZSxTQUFTLEVBQUUsTUFBTTtBQUc3RCxlQUFTLFFBQVEsZUFBZSxTQUFTLEVBQUUsU0FBUyxHQUFHLFNBQVMsR0FBRyxTQUFTLEdBQUc7QUFHM0UsY0FBTSxXQUFXLGVBQWUsU0FBUyxFQUFFLEtBQUs7QUFFaEQsWUFBSSxPQUFPLFVBQVU7QUFHckIsY0FBTSxVQUFVLFNBQVMsU0FBUyxJQUFJO0FBQ3RDLFlBQUksU0FBUztBQUVULCtCQUFxQixPQUFPLE9BQU8sQ0FBQztBQUFBLFFBQ3hDO0FBQUEsTUFDSjtBQUdBLFVBQUkscUJBQXFCLFdBQVcsR0FBRztBQUNuQyx1QkFBZSxTQUFTO0FBQUEsTUFDNUIsT0FBTztBQUNILHVCQUFlLFNBQVMsSUFBSTtBQUFBLE1BQ2hDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFTTyxXQUFTLGFBQWEsZUFBZTtBQUV4QyxRQUFJO0FBQ0osUUFBSTtBQUNBLGdCQUFVLEtBQUssTUFBTSxhQUFhO0FBQUEsSUFDdEMsU0FBUyxHQUFQO0FBQ0UsWUFBTSxRQUFRLG9DQUFvQztBQUNsRCxZQUFNLElBQUksTUFBTSxLQUFLO0FBQUEsSUFDekI7QUFDQSxvQkFBZ0IsT0FBTztBQUFBLEVBQzNCO0FBUU8sV0FBUyxXQUFXLFdBQVc7QUFFbEMsVUFBTSxVQUFVO0FBQUEsTUFDWixNQUFNO0FBQUEsTUFDTixNQUFNLENBQUMsRUFBRSxNQUFNLE1BQU0sU0FBUyxFQUFFLE1BQU0sQ0FBQztBQUFBLElBQzNDO0FBR0Esb0JBQWdCLE9BQU87QUFHdkIsV0FBTyxZQUFZLE9BQU8sS0FBSyxVQUFVLE9BQU8sQ0FBQztBQUFBLEVBQ3JEO0FBRUEsV0FBUyxlQUFlLFdBQVc7QUFFL0IsV0FBTyxlQUFlLFNBQVM7QUFHL0IsV0FBTyxZQUFZLE9BQU8sU0FBUztBQUFBLEVBQ3ZDO0FBU08sV0FBUyxVQUFVLGNBQWMsc0JBQXNCO0FBQzFELG1CQUFlLFNBQVM7QUFFeEIsUUFBSSxxQkFBcUIsU0FBUyxHQUFHO0FBQ2pDLDJCQUFxQixRQUFRLENBQUFBLGVBQWE7QUFDdEMsdUJBQWVBLFVBQVM7QUFBQSxNQUM1QixDQUFDO0FBQUEsSUFDTDtBQUFBLEVBQ0o7QUFpQkMsV0FBUyxZQUFZLFVBQVU7QUFDNUIsVUFBTSxZQUFZLFNBQVM7QUFFM0IsbUJBQWUsU0FBUyxJQUFJLGVBQWUsU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFHaEYsUUFBSSxlQUFlLFNBQVMsRUFBRSxXQUFXLEdBQUc7QUFDeEMscUJBQWUsU0FBUztBQUFBLElBQzVCO0FBQUEsRUFDSjs7O0FDeE1PLE1BQU0sWUFBWSxDQUFDO0FBTzFCLFdBQVMsZUFBZTtBQUN2QixRQUFJLFFBQVEsSUFBSSxZQUFZLENBQUM7QUFDN0IsV0FBTyxPQUFPLE9BQU8sZ0JBQWdCLEtBQUssRUFBRSxDQUFDO0FBQUEsRUFDOUM7QUFRQSxXQUFTLGNBQWM7QUFDdEIsV0FBTyxLQUFLLE9BQU8sSUFBSTtBQUFBLEVBQ3hCO0FBR0EsTUFBSTtBQUNKLE1BQUksT0FBTyxRQUFRO0FBQ2xCLGlCQUFhO0FBQUEsRUFDZCxPQUFPO0FBQ04saUJBQWE7QUFBQSxFQUNkO0FBaUJPLFdBQVMsS0FBSyxNQUFNLE1BQU0sU0FBUztBQUd6QyxRQUFJLFdBQVcsTUFBTTtBQUNwQixnQkFBVTtBQUFBLElBQ1g7QUFHQSxXQUFPLElBQUksUUFBUSxTQUFVLFNBQVMsUUFBUTtBQUc3QyxVQUFJO0FBQ0osU0FBRztBQUNGLHFCQUFhLE9BQU8sTUFBTSxXQUFXO0FBQUEsTUFDdEMsU0FBUyxVQUFVLFVBQVU7QUFFN0IsVUFBSTtBQUVKLFVBQUksVUFBVSxHQUFHO0FBQ2hCLHdCQUFnQixXQUFXLFdBQVk7QUFDdEMsaUJBQU8sTUFBTSxhQUFhLE9BQU8sNkJBQTZCLFVBQVUsQ0FBQztBQUFBLFFBQzFFLEdBQUcsT0FBTztBQUFBLE1BQ1g7QUFHQSxnQkFBVSxVQUFVLElBQUk7QUFBQSxRQUN2QjtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsTUFDRDtBQUVBLFVBQUk7QUFDSCxjQUFNLFVBQVU7QUFBQSxVQUNmO0FBQUEsVUFDQTtBQUFBLFVBQ0E7QUFBQSxRQUNEO0FBR1MsZUFBTyxZQUFZLE1BQU0sS0FBSyxVQUFVLE9BQU8sQ0FBQztBQUFBLE1BQ3BELFNBQVMsR0FBUDtBQUVFLGdCQUFRLE1BQU0sQ0FBQztBQUFBLE1BQ25CO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDtBQUVBLFNBQU8saUJBQWlCLENBQUMsSUFBSSxNQUFNLFlBQVk7QUFHM0MsUUFBSSxXQUFXLE1BQU07QUFDakIsZ0JBQVU7QUFBQSxJQUNkO0FBR0EsV0FBTyxJQUFJLFFBQVEsU0FBVSxTQUFTLFFBQVE7QUFHMUMsVUFBSTtBQUNKLFNBQUc7QUFDQyxxQkFBYSxLQUFLLE1BQU0sV0FBVztBQUFBLE1BQ3ZDLFNBQVMsVUFBVSxVQUFVO0FBRTdCLFVBQUk7QUFFSixVQUFJLFVBQVUsR0FBRztBQUNiLHdCQUFnQixXQUFXLFdBQVk7QUFDbkMsaUJBQU8sTUFBTSxvQkFBb0IsS0FBSyw2QkFBNkIsVUFBVSxDQUFDO0FBQUEsUUFDbEYsR0FBRyxPQUFPO0FBQUEsTUFDZDtBQUdBLGdCQUFVLFVBQVUsSUFBSTtBQUFBLFFBQ3BCO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNKO0FBRUEsVUFBSTtBQUNBLGNBQU0sVUFBVTtBQUFBLFVBQ3hCO0FBQUEsVUFDQTtBQUFBLFVBQ0E7QUFBQSxRQUNEO0FBR1MsZUFBTyxZQUFZLE1BQU0sS0FBSyxVQUFVLE9BQU8sQ0FBQztBQUFBLE1BQ3BELFNBQVMsR0FBUDtBQUVFLGdCQUFRLE1BQU0sQ0FBQztBQUFBLE1BQ25CO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDtBQVVPLFdBQVMsU0FBUyxpQkFBaUI7QUFFekMsUUFBSTtBQUNKLFFBQUk7QUFDSCxnQkFBVSxLQUFLLE1BQU0sZUFBZTtBQUFBLElBQ3JDLFNBQVMsR0FBUDtBQUNELFlBQU0sUUFBUSxvQ0FBb0MsRUFBRSxxQkFBcUI7QUFDekUsY0FBUSxTQUFTLEtBQUs7QUFDdEIsWUFBTSxJQUFJLE1BQU0sS0FBSztBQUFBLElBQ3RCO0FBQ0EsUUFBSSxhQUFhLFFBQVE7QUFDekIsUUFBSSxlQUFlLFVBQVUsVUFBVTtBQUN2QyxRQUFJLENBQUMsY0FBYztBQUNsQixZQUFNLFFBQVEsYUFBYTtBQUMzQixjQUFRLE1BQU0sS0FBSztBQUNuQixZQUFNLElBQUksTUFBTSxLQUFLO0FBQUEsSUFDdEI7QUFDQSxpQkFBYSxhQUFhLGFBQWE7QUFFdkMsV0FBTyxVQUFVLFVBQVU7QUFFM0IsUUFBSSxRQUFRLE9BQU87QUFDbEIsbUJBQWEsT0FBTyxRQUFRLEtBQUs7QUFBQSxJQUNsQyxPQUFPO0FBQ04sbUJBQWEsUUFBUSxRQUFRLE1BQU07QUFBQSxJQUNwQztBQUFBLEVBQ0Q7OztBQzFLQSxTQUFPLEtBQUssQ0FBQztBQUVOLFdBQVMsWUFBWSxhQUFhO0FBQ3hDLFFBQUk7QUFDSCxvQkFBYyxLQUFLLE1BQU0sV0FBVztBQUFBLElBQ3JDLFNBQVMsR0FBUDtBQUNELGNBQVEsTUFBTSxDQUFDO0FBQUEsSUFDaEI7QUFHQSxXQUFPLEtBQUssT0FBTyxNQUFNLENBQUM7QUFHMUIsV0FBTyxLQUFLLFdBQVcsRUFBRSxRQUFRLENBQUMsZ0JBQWdCO0FBR2pELGFBQU8sR0FBRyxXQUFXLElBQUksT0FBTyxHQUFHLFdBQVcsS0FBSyxDQUFDO0FBR3BELGFBQU8sS0FBSyxZQUFZLFdBQVcsQ0FBQyxFQUFFLFFBQVEsQ0FBQyxlQUFlO0FBRzdELGVBQU8sR0FBRyxXQUFXLEVBQUUsVUFBVSxJQUFJLE9BQU8sR0FBRyxXQUFXLEVBQUUsVUFBVSxLQUFLLENBQUM7QUFFNUUsZUFBTyxLQUFLLFlBQVksV0FBVyxFQUFFLFVBQVUsQ0FBQyxFQUFFLFFBQVEsQ0FBQyxlQUFlO0FBRXpFLGlCQUFPLEdBQUcsV0FBVyxFQUFFLFVBQVUsRUFBRSxVQUFVLElBQUksV0FBWTtBQUc1RCxnQkFBSSxVQUFVO0FBR2QscUJBQVMsVUFBVTtBQUNsQixvQkFBTSxPQUFPLENBQUMsRUFBRSxNQUFNLEtBQUssU0FBUztBQUNwQyxxQkFBTyxLQUFLLENBQUMsYUFBYSxZQUFZLFVBQVUsRUFBRSxLQUFLLEdBQUcsR0FBRyxNQUFNLE9BQU87QUFBQSxZQUMzRTtBQUdBLG9CQUFRLGFBQWEsU0FBVSxZQUFZO0FBQzFDLHdCQUFVO0FBQUEsWUFDWDtBQUdBLG9CQUFRLGFBQWEsV0FBWTtBQUNoQyxxQkFBTztBQUFBLFlBQ1I7QUFFQSxtQkFBTztBQUFBLFVBQ1IsRUFBRTtBQUFBLFFBQ0gsQ0FBQztBQUFBLE1BQ0YsQ0FBQztBQUFBLElBQ0YsQ0FBQztBQUFBLEVBQ0Y7OztBQ2xFQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWVPLFdBQVMsZUFBZTtBQUMzQixXQUFPLFNBQVMsT0FBTztBQUFBLEVBQzNCO0FBRU8sV0FBUyxrQkFBa0I7QUFDOUIsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQUVPLFdBQVMsOEJBQThCO0FBQzFDLFdBQU8sWUFBWSxPQUFPO0FBQUEsRUFDOUI7QUFFTyxXQUFTLHNCQUFzQjtBQUNsQyxXQUFPLFlBQVksTUFBTTtBQUFBLEVBQzdCO0FBRU8sV0FBUyxxQkFBcUI7QUFDakMsV0FBTyxZQUFZLE1BQU07QUFBQSxFQUM3QjtBQU9PLFdBQVMsZUFBZTtBQUMzQixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBUU8sV0FBUyxlQUFlLE9BQU87QUFDbEMsV0FBTyxZQUFZLE9BQU8sS0FBSztBQUFBLEVBQ25DO0FBT08sV0FBUyxtQkFBbUI7QUFDL0IsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQU9PLFdBQVMscUJBQXFCO0FBQ2pDLFdBQU8sWUFBWSxJQUFJO0FBQUEsRUFDM0I7QUFRTyxXQUFTLHFCQUFxQjtBQUNqQyxXQUFPLEtBQUssMkJBQTJCO0FBQUEsRUFDM0M7QUFTTyxXQUFTLGNBQWMsT0FBTyxRQUFRO0FBQ3pDLFdBQU8sWUFBWSxRQUFRLFFBQVEsTUFBTSxNQUFNO0FBQUEsRUFDbkQ7QUFTTyxXQUFTLGdCQUFnQjtBQUM1QixXQUFPLEtBQUssc0JBQXNCO0FBQUEsRUFDdEM7QUFTTyxXQUFTLGlCQUFpQixPQUFPLFFBQVE7QUFDNUMsV0FBTyxZQUFZLFFBQVEsUUFBUSxNQUFNLE1BQU07QUFBQSxFQUNuRDtBQVNPLFdBQVMsaUJBQWlCLE9BQU8sUUFBUTtBQUM1QyxXQUFPLFlBQVksUUFBUSxRQUFRLE1BQU0sTUFBTTtBQUFBLEVBQ25EO0FBU08sV0FBUyxxQkFBcUIsR0FBRztBQUVwQyxXQUFPLFlBQVksV0FBVyxJQUFJLE1BQU0sSUFBSTtBQUFBLEVBQ2hEO0FBWU8sV0FBUyxrQkFBa0IsR0FBRyxHQUFHO0FBQ3BDLFdBQU8sWUFBWSxRQUFRLElBQUksTUFBTSxDQUFDO0FBQUEsRUFDMUM7QUFRTyxXQUFTLG9CQUFvQjtBQUNoQyxXQUFPLEtBQUsscUJBQXFCO0FBQUEsRUFDckM7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQU9PLFdBQVMsYUFBYTtBQUN6QixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBT08sV0FBUyxpQkFBaUI7QUFDN0IsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQU9PLFdBQVMsdUJBQXVCO0FBQ25DLFdBQU8sWUFBWSxJQUFJO0FBQUEsRUFDM0I7QUFPTyxXQUFTLG1CQUFtQjtBQUMvQixXQUFPLFlBQVksSUFBSTtBQUFBLEVBQzNCO0FBUU8sV0FBUyxvQkFBb0I7QUFDaEMsV0FBTyxLQUFLLDBCQUEwQjtBQUFBLEVBQzFDO0FBT08sV0FBUyxpQkFBaUI7QUFDN0IsV0FBTyxZQUFZLElBQUk7QUFBQSxFQUMzQjtBQU9PLFdBQVMsbUJBQW1CO0FBQy9CLFdBQU8sWUFBWSxJQUFJO0FBQUEsRUFDM0I7QUFRTyxXQUFTLG9CQUFvQjtBQUNoQyxXQUFPLEtBQUssMEJBQTBCO0FBQUEsRUFDMUM7QUFRTyxXQUFTLGlCQUFpQjtBQUM3QixXQUFPLEtBQUssdUJBQXVCO0FBQUEsRUFDdkM7QUFXTyxXQUFTLDBCQUEwQixHQUFHLEdBQUcsR0FBRyxHQUFHO0FBQ2xELFFBQUksT0FBTyxLQUFLLFVBQVUsRUFBQyxHQUFHLEtBQUssR0FBRyxHQUFHLEtBQUssR0FBRyxHQUFHLEtBQUssR0FBRyxHQUFHLEtBQUssSUFBRyxDQUFDO0FBQ3hFLFdBQU8sWUFBWSxRQUFRLElBQUk7QUFBQSxFQUNuQzs7O0FDM1FBO0FBQUE7QUFBQTtBQUFBO0FBc0JPLFdBQVMsZUFBZTtBQUMzQixXQUFPLEtBQUsscUJBQXFCO0FBQUEsRUFDckM7OztBQ3hCQTtBQUFBO0FBQUE7QUFBQTtBQUtPLFdBQVMsZUFBZSxLQUFLO0FBQ2xDLFdBQU8sWUFBWSxRQUFRLEdBQUc7QUFBQSxFQUNoQzs7O0FDUEE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQW9CTyxXQUFTLGlCQUFpQixNQUFNO0FBQ25DLFdBQU8sS0FBSywyQkFBMkIsQ0FBQyxJQUFJLENBQUM7QUFBQSxFQUNqRDtBQVNPLFdBQVMsbUJBQW1CO0FBQy9CLFdBQU8sS0FBSyx5QkFBeUI7QUFBQSxFQUN6Qzs7O0FDYk8sV0FBUyxPQUFPO0FBQ25CLFdBQU8sWUFBWSxHQUFHO0FBQUEsRUFDMUI7QUFFTyxXQUFTLE9BQU87QUFDbkIsV0FBTyxZQUFZLEdBQUc7QUFBQSxFQUMxQjtBQUVPLFdBQVMsT0FBTztBQUNuQixXQUFPLFlBQVksR0FBRztBQUFBLEVBQzFCO0FBRU8sV0FBUyxjQUFjO0FBQzFCLFdBQU8sS0FBSyxvQkFBb0I7QUFBQSxFQUNwQztBQUdBLFNBQU8sVUFBVTtBQUFBLElBQ2IsR0FBRztBQUFBLElBQ0gsR0FBRztBQUFBLElBQ0gsR0FBRztBQUFBLElBQ0gsR0FBRztBQUFBLElBQ0gsR0FBRztBQUFBLElBQ0g7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLEVBQ0o7QUFHQSxTQUFPLFFBQVE7QUFBQSxJQUNYO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0EsT0FBTztBQUFBLE1BQ0gsc0JBQXNCO0FBQUEsTUFDdEIsZ0NBQWdDO0FBQUEsTUFDaEMsY0FBYztBQUFBLE1BQ2QsZUFBZTtBQUFBLE1BQ2YsaUJBQWlCO0FBQUEsTUFDakIsWUFBWTtBQUFBLE1BQ1osc0JBQXNCO0FBQUEsTUFDdEIsaUJBQWlCO0FBQUEsTUFDakIsY0FBYztBQUFBLElBQ2xCO0FBQUEsRUFDSjtBQUdBLE1BQUksT0FBTyxlQUFlO0FBQ3RCLFdBQU8sTUFBTSxZQUFZLE9BQU8sYUFBYTtBQUM3QyxXQUFPLE9BQU8sTUFBTTtBQUFBLEVBQ3hCO0FBS0EsTUFBSSxPQUFXO0FBQ1gsV0FBTyxPQUFPO0FBQUEsRUFDbEI7QUFFQSxNQUFJLFdBQVcsU0FBVSxHQUFHO0FBQ3hCLFFBQUksTUFBTSxPQUFPLGlCQUFpQixFQUFFLE1BQU0sRUFBRSxpQkFBaUIsT0FBTyxNQUFNLE1BQU0sZUFBZTtBQUMvRixRQUFJLEtBQUs7QUFDUCxZQUFNLElBQUksS0FBSztBQUFBLElBQ2pCO0FBRUEsUUFBSSxRQUFRLE9BQU8sTUFBTSxNQUFNLGNBQWM7QUFDekMsYUFBTztBQUFBLElBQ1g7QUFFQSxRQUFJLEVBQUUsWUFBWSxHQUFHO0FBRWpCLGFBQU87QUFBQSxJQUNYO0FBRUEsUUFBSSxFQUFFLFdBQVcsR0FBRztBQUVoQixhQUFPO0FBQUEsSUFDWDtBQUVBLFdBQU87QUFBQSxFQUNYO0FBRUEsU0FBTyxNQUFNLHVCQUF1QixTQUFVLFVBQVUsT0FBTztBQUMzRCxXQUFPLE1BQU0sTUFBTSxrQkFBa0I7QUFDckMsV0FBTyxNQUFNLE1BQU0sZUFBZTtBQUFBLEVBQ3RDO0FBRUEsU0FBTyxpQkFBaUIsYUFBYSxDQUFDLE1BQU07QUFHeEMsUUFBSSxPQUFPLE1BQU0sTUFBTSxZQUFZO0FBQy9CLGFBQU8sWUFBWSxZQUFZLE9BQU8sTUFBTSxNQUFNLFVBQVU7QUFDNUQsUUFBRSxlQUFlO0FBQ2pCO0FBQUEsSUFDSjtBQUVBLFFBQUksU0FBUyxDQUFDLEdBQUc7QUFDYixVQUFJLE9BQU8sTUFBTSxNQUFNLHNCQUFzQjtBQUV6QyxZQUFJLEVBQUUsVUFBVSxFQUFFLE9BQU8sZUFBZSxFQUFFLFVBQVUsRUFBRSxPQUFPLGNBQWM7QUFDdkU7QUFBQSxRQUNKO0FBQUEsTUFDSjtBQUNBLFVBQUksT0FBTyxNQUFNLE1BQU0sc0JBQXNCO0FBQ3pDLGVBQU8sTUFBTSxNQUFNLGFBQWE7QUFBQSxNQUNwQyxPQUFPO0FBQ0gsVUFBRSxlQUFlO0FBQ2pCLGVBQU8sWUFBWSxNQUFNO0FBQUEsTUFDN0I7QUFDQTtBQUFBLElBQ0osT0FBTztBQUNILGFBQU8sTUFBTSxNQUFNLGFBQWE7QUFBQSxJQUNwQztBQUFBLEVBQ0osQ0FBQztBQUVELFNBQU8saUJBQWlCLFdBQVcsTUFBTTtBQUNyQyxXQUFPLE1BQU0sTUFBTSxhQUFhO0FBQUEsRUFDcEMsQ0FBQztBQUVELFdBQVMsVUFBVSxRQUFRO0FBQ3ZCLGFBQVMsZ0JBQWdCLE1BQU0sU0FBUyxVQUFVLE9BQU8sTUFBTSxNQUFNO0FBQ3JFLFdBQU8sTUFBTSxNQUFNLGFBQWE7QUFBQSxFQUNwQztBQUVBLFNBQU8saUJBQWlCLGFBQWEsU0FBVSxHQUFHO0FBQzlDLFFBQUksT0FBTyxNQUFNLE1BQU0sWUFBWTtBQUMvQixhQUFPLE1BQU0sTUFBTSxhQUFhO0FBQ2hDLFVBQUksZUFBZSxFQUFFLFlBQVksU0FBWSxFQUFFLFVBQVUsRUFBRTtBQUMzRCxVQUFJLGVBQWUsR0FBRztBQUNsQixlQUFPLFlBQVksTUFBTTtBQUN6QjtBQUFBLE1BQ0o7QUFBQSxJQUNKO0FBQ0EsUUFBSSxDQUFDLE9BQU8sTUFBTSxNQUFNLGNBQWM7QUFDbEM7QUFBQSxJQUNKO0FBQ0EsUUFBSSxPQUFPLE1BQU0sTUFBTSxpQkFBaUIsTUFBTTtBQUMxQyxhQUFPLE1BQU0sTUFBTSxnQkFBZ0IsU0FBUyxnQkFBZ0IsTUFBTTtBQUFBLElBQ3RFO0FBQ0EsUUFBSSxPQUFPLGFBQWEsRUFBRSxVQUFVLE9BQU8sTUFBTSxNQUFNLG1CQUFtQixPQUFPLGNBQWMsRUFBRSxVQUFVLE9BQU8sTUFBTSxNQUFNLGlCQUFpQjtBQUMzSSxlQUFTLGdCQUFnQixNQUFNLFNBQVM7QUFBQSxJQUM1QztBQUNBLFFBQUksY0FBYyxPQUFPLGFBQWEsRUFBRSxVQUFVLE9BQU8sTUFBTSxNQUFNO0FBQ3JFLFFBQUksYUFBYSxFQUFFLFVBQVUsT0FBTyxNQUFNLE1BQU07QUFDaEQsUUFBSSxZQUFZLEVBQUUsVUFBVSxPQUFPLE1BQU0sTUFBTTtBQUMvQyxRQUFJLGVBQWUsT0FBTyxjQUFjLEVBQUUsVUFBVSxPQUFPLE1BQU0sTUFBTTtBQUd2RSxRQUFJLENBQUMsY0FBYyxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsZ0JBQWdCLE9BQU8sTUFBTSxNQUFNLGVBQWUsUUFBVztBQUMzRyxnQkFBVTtBQUFBLElBQ2QsV0FBVyxlQUFlO0FBQWMsZ0JBQVUsV0FBVztBQUFBLGFBQ3BELGNBQWM7QUFBYyxnQkFBVSxXQUFXO0FBQUEsYUFDakQsY0FBYztBQUFXLGdCQUFVLFdBQVc7QUFBQSxhQUM5QyxhQUFhO0FBQWEsZ0JBQVUsV0FBVztBQUFBLGFBQy9DO0FBQVksZ0JBQVUsVUFBVTtBQUFBLGFBQ2hDO0FBQVcsZ0JBQVUsVUFBVTtBQUFBLGFBQy9CO0FBQWMsZ0JBQVUsVUFBVTtBQUFBLGFBQ2xDO0FBQWEsZ0JBQVUsVUFBVTtBQUFBLEVBRTlDLENBQUM7QUFHRCxTQUFPLGlCQUFpQixlQUFlLFNBQVUsR0FBRztBQUNoRCxRQUFJLE9BQU8sTUFBTSxNQUFNLGdDQUFnQztBQUNuRCxRQUFFLGVBQWU7QUFBQSxJQUNyQjtBQUFBLEVBQ0osQ0FBQztBQUVELFNBQU8sWUFBWSxlQUFlOyIsCiAgIm5hbWVzIjogWyJldmVudE5hbWUiXQp9Cg== diff --git a/v2/internal/frontend/runtime/runtime_prod_desktop.go b/v2/internal/frontend/runtime/runtime_prod_desktop.go index 7336f0102..4fd8aaa58 100644 --- a/v2/internal/frontend/runtime/runtime_prod_desktop.go +++ b/v2/internal/frontend/runtime/runtime_prod_desktop.go @@ -1,4 +1,4 @@ -//go:build production && !debug +//go:build production package runtime diff --git a/v2/internal/frontend/runtime/runtime_prod_desktop.js b/v2/internal/frontend/runtime/runtime_prod_desktop.js index c285fa642..23b66953b 100644 --- a/v2/internal/frontend/runtime/runtime_prod_desktop.js +++ b/v2/internal/frontend/runtime/runtime_prod_desktop.js @@ -1 +1 @@ -(()=>{var J=Object.defineProperty;var p=(e,t)=>{for(var n in t)J(e,n,{get:t[n],enumerable:!0})};var y={};p(y,{LogDebug:()=>q,LogError:()=>_,LogFatal:()=>Z,LogInfo:()=>Y,LogLevel:()=>ee,LogPrint:()=>$,LogTrace:()=>X,LogWarning:()=>Q,SetLogLevel:()=>K});function u(e,t){window.WailsInvoke("L"+e+t)}function X(e){u("T",e)}function $(e){u("P",e)}function q(e){u("D",e)}function Y(e){u("I",e)}function Q(e){u("W",e)}function _(e){u("E",e)}function Z(e){u("F",e)}function K(e){u("S",e)}var ee={TRACE:1,DEBUG:2,INFO:3,WARNING:4,ERROR:5};var b=class{constructor(t,n,i){this.eventName=t,this.maxCallbacks=i||-1,this.Callback=o=>(n.apply(null,o),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},f={};function v(e,t,n){f[e]=f[e]||[];let i=new b(e,t,n);return f[e].push(i),()=>te(i)}function x(e,t){return v(e,t,-1)}function N(e,t){return v(e,t,1)}function L(e){let t=e.name,n=f[t]?.slice()||[];if(n.length){for(let i=n.length-1;i>=0;i-=1){let o=n[i],s=e.data;o.Callback(s)&&n.splice(i,1)}n.length===0?g(t):f[t]=n}}function P(e){let t;try{t=JSON.parse(e)}catch{let i="Invalid JSON passed to Notify: "+e;throw new Error(i)}L(t)}function z(e){let t={name:e,data:[].slice.apply(arguments).slice(1)};L(t),window.WailsInvoke("EE"+JSON.stringify(t))}function g(e){delete f[e],window.WailsInvoke("EX"+e)}function D(e,...t){g(e),t.length>0&&t.forEach(n=>{g(n)})}function F(){Object.keys(f).forEach(t=>{g(t)})}function te(e){let t=e.eventName;f[t]!==void 0&&(f[t]=f[t].filter(n=>n!==e),f[t].length===0&&g(t))}var c={};function ne(){var e=new Uint32Array(1);return window.crypto.getRandomValues(e)[0]}function ie(){return Math.random()*9007199254740991}var W;window.crypto?W=ne:W=ie;function r(e,t,n){return n==null&&(n=0),new Promise(function(i,o){var s;do s=e+"-"+W();while(c[s]);var l;n>0&&(l=setTimeout(function(){o(Error("Call to "+e+" timed out. Request ID: "+s))},n)),c[s]={timeoutHandle:l,reject:o,resolve:i};try{let w={name:e,args:t,callbackID:s};window.WailsInvoke("C"+JSON.stringify(w))}catch(w){console.error(w)}})}window.ObfuscatedCall=(e,t,n)=>(n==null&&(n=0),new Promise(function(i,o){var s;do s=e+"-"+W();while(c[s]);var l;n>0&&(l=setTimeout(function(){o(Error("Call to method "+e+" timed out. Request ID: "+s))},n)),c[s]={timeoutHandle:l,reject:o,resolve:i};try{let w={id:e,args:t,callbackID:s};window.WailsInvoke("c"+JSON.stringify(w))}catch(w){console.error(w)}}));function M(e){let t;try{t=JSON.parse(e)}catch(o){let s=`Invalid JSON passed to callback: ${o.message}. Message: ${e}`;throw runtime.LogDebug(s),new Error(s)}let n=t.callbackid,i=c[n];if(!i){let o=`Callback '${n}' not registered!!!`;throw console.error(o),new Error(o)}clearTimeout(i.timeoutHandle),delete c[n],t.error?i.reject(t.error):i.resolve(t.result)}window.go={};function B(e){try{e=JSON.parse(e)}catch(t){console.error(t)}window.go=window.go||{},Object.keys(e).forEach(t=>{window.go[t]=window.go[t]||{},Object.keys(e[t]).forEach(n=>{window.go[t][n]=window.go[t][n]||{},Object.keys(e[t][n]).forEach(i=>{window.go[t][n][i]=function(){let o=0;function s(){let l=[].slice.call(arguments);return r([t,n,i].join("."),l,o)}return s.setTimeout=function(l){o=l},s.getTimeout=function(){return o},s}()})})})}var C={};p(C,{WindowCenter:()=>fe,WindowFullscreen:()=>de,WindowGetPosition:()=>We,WindowGetSize:()=>ge,WindowHide:()=>he,WindowIsFullscreen:()=>ce,WindowIsMaximised:()=>Se,WindowIsMinimised:()=>Ie,WindowIsNormal:()=>Ae,WindowMaximise:()=>ye,WindowMinimise:()=>Te,WindowReload:()=>oe,WindowReloadApp:()=>re,WindowSetAlwaysOnTop:()=>xe,WindowSetBackgroundColour:()=>Oe,WindowSetDarkTheme:()=>le,WindowSetLightTheme:()=>ae,WindowSetMaxSize:()=>me,WindowSetMinSize:()=>ve,WindowSetPosition:()=>De,WindowSetSize:()=>pe,WindowSetSystemDefaultTheme:()=>se,WindowSetTitle:()=>we,WindowShow:()=>Ee,WindowToggleMaximise:()=>be,WindowUnfullscreen:()=>ue,WindowUnmaximise:()=>Ce,WindowUnminimise:()=>ke});function oe(){window.location.reload()}function re(){window.WailsInvoke("WR")}function se(){window.WailsInvoke("WASDT")}function ae(){window.WailsInvoke("WALT")}function le(){window.WailsInvoke("WADT")}function fe(){window.WailsInvoke("Wc")}function we(e){window.WailsInvoke("WT"+e)}function de(){window.WailsInvoke("WF")}function ue(){window.WailsInvoke("Wf")}function ce(){return r(":wails:WindowIsFullscreen")}function pe(e,t){window.WailsInvoke("Ws:"+e+":"+t)}function ge(){return r(":wails:WindowGetSize")}function me(e,t){window.WailsInvoke("WZ:"+e+":"+t)}function ve(e,t){window.WailsInvoke("Wz:"+e+":"+t)}function xe(e){window.WailsInvoke("WATP:"+(e?"1":"0"))}function De(e,t){window.WailsInvoke("Wp:"+e+":"+t)}function We(){return r(":wails:WindowGetPos")}function he(){window.WailsInvoke("WH")}function Ee(){window.WailsInvoke("WS")}function ye(){window.WailsInvoke("WM")}function be(){window.WailsInvoke("Wt")}function Ce(){window.WailsInvoke("WU")}function Se(){return r(":wails:WindowIsMaximised")}function Te(){window.WailsInvoke("Wm")}function ke(){window.WailsInvoke("Wu")}function Ie(){return r(":wails:WindowIsMinimised")}function Ae(){return r(":wails:WindowIsNormal")}function Oe(e,t,n,i){let o=JSON.stringify({r:e||0,g:t||0,b:n||0,a:i||255});window.WailsInvoke("Wr:"+o)}var S={};p(S,{ScreenGetAll:()=>Re});function Re(){return r(":wails:ScreenGetAll")}var T={};p(T,{BrowserOpenURL:()=>Ne});function Ne(e){window.WailsInvoke("BO:"+e)}var k={};p(k,{ClipboardGetText:()=>Pe,ClipboardSetText:()=>Le});function Le(e){return r(":wails:ClipboardSetText",[e])}function Pe(){return r(":wails:ClipboardGetText")}var I={};p(I,{CanResolveFilePaths:()=>V,OnFileDrop:()=>Fe,OnFileDropOff:()=>Me,ResolveFilePaths:()=>ze});var a={registered:!1,defaultUseDropTarget:!0,useDropTarget:!0,nextDeactivate:null,nextDeactivateTimeout:null},m="wails-drop-target-active";function h(e){let t=e.getPropertyValue(window.wails.flags.cssDropProperty).trim();return t?t===window.wails.flags.cssDropValue:!1}function G(e){if(!e.dataTransfer.types.includes("Files")||(e.preventDefault(),e.dataTransfer.dropEffect="copy",!window.wails.flags.enableWailsDragAndDrop)||!a.useDropTarget)return;let n=e.target;if(a.nextDeactivate&&a.nextDeactivate(),!n||!h(getComputedStyle(n)))return;let i=n;for(;i;)h(getComputedStyle(i))&&i.classList.add(m),i=i.parentElement}function H(e){if(!!e.dataTransfer.types.includes("Files")&&(e.preventDefault(),!!window.wails.flags.enableWailsDragAndDrop&&!!a.useDropTarget)){if(!e.target||!h(getComputedStyle(e.target)))return null;a.nextDeactivate&&a.nextDeactivate(),a.nextDeactivate=()=>{Array.from(document.getElementsByClassName(m)).forEach(n=>n.classList.remove(m)),a.nextDeactivate=null,a.nextDeactivateTimeout&&(clearTimeout(a.nextDeactivateTimeout),a.nextDeactivateTimeout=null)},a.nextDeactivateTimeout=setTimeout(()=>{a.nextDeactivate&&a.nextDeactivate()},50)}}function U(e){if(!!e.dataTransfer.types.includes("Files")&&(e.preventDefault(),!!window.wails.flags.enableWailsDragAndDrop)){if(V()){let n=[];e.dataTransfer.items?n=[...e.dataTransfer.items].map((i,o)=>{if(i.kind==="file")return i.getAsFile()}):n=[...e.dataTransfer.files],window.runtime.ResolveFilePaths(e.x,e.y,n)}!a.useDropTarget||(a.nextDeactivate&&a.nextDeactivate(),Array.from(document.getElementsByClassName(m)).forEach(n=>n.classList.remove(m)))}}function V(){return window.chrome?.webview?.postMessageWithAdditionalObjects!=null}function ze(e,t,n){window.chrome?.webview?.postMessageWithAdditionalObjects&&chrome.webview.postMessageWithAdditionalObjects(`file:drop:${e}:${t}`,n)}function Fe(e,t){if(typeof e!="function"){console.error("DragAndDropCallback is not a function");return}if(a.registered)return;a.registered=!0;let n=typeof t;a.useDropTarget=n==="undefined"||n!=="boolean"?a.defaultUseDropTarget:t,window.addEventListener("dragover",G),window.addEventListener("dragleave",H),window.addEventListener("drop",U);let i=e;a.useDropTarget&&(i=function(o,s,l){let w=document.elementFromPoint(o,s);if(!w||!h(getComputedStyle(w)))return null;e(o,s,l)}),x("wails:file-drop",i)}function Me(){window.removeEventListener("dragover",G),window.removeEventListener("dragleave",H),window.removeEventListener("drop",U),D("wails:file-drop"),a.registered=!1}function j(e){let t=e.target;switch(window.getComputedStyle(t).getPropertyValue("--default-contextmenu").trim()){case"show":return;case"hide":e.preventDefault();return;default:if(t.isContentEditable)return;let o=window.getSelection(),s=o.toString().length>0;if(s)for(let l=0;lje,CleanupNotifications:()=>He,InitializeNotifications:()=>Ge,IsNotificationAvailable:()=>Ue,RegisterNotificationCategory:()=>$e,RemoveAllDeliveredNotifications:()=>_e,RemoveAllPendingNotifications:()=>Ye,RemoveDeliveredNotification:()=>Ze,RemoveNotification:()=>Ke,RemoveNotificationCategory:()=>qe,RemovePendingNotification:()=>Qe,RequestNotificationAuthorization:()=>Ve,SendNotification:()=>Je,SendNotificationWithActions:()=>Xe});function Ge(){return r(":wails:InitializeNotifications")}function He(){return r(":wails:CleanupNotifications")}function Ue(){return r(":wails:IsNotificationAvailable")}function Ve(){return r(":wails:RequestNotificationAuthorization")}function je(){return r(":wails:CheckNotificationAuthorization")}function Je(e){return r(":wails:SendNotification",[e])}function Xe(e){return r(":wails:SendNotificationWithActions",[e])}function $e(e){return r(":wails:RegisterNotificationCategory",[e])}function qe(e){return r(":wails:RemoveNotificationCategory",[e])}function Ye(){return r(":wails:RemoveAllPendingNotifications")}function Qe(e){return r(":wails:RemovePendingNotification",[e])}function _e(){return r(":wails:RemoveAllDeliveredNotifications")}function Ze(e){return r(":wails:RemoveDeliveredNotification",[e])}function Ke(e){return r(":wails:RemoveNotification",[e])}function et(){window.WailsInvoke("Q")}function tt(){window.WailsInvoke("S")}function nt(){window.WailsInvoke("H")}function it(){return r(":wails:Environment")}window.runtime={...y,...C,...T,...S,...k,...I,...A,EventsOn:x,EventsOnce:N,EventsOnMultiple:v,EventsEmit:z,EventsOff:D,EventsOffAll:F,Environment:it,Show:tt,Hide:nt,Quit:et};window.wails={Callback:M,EventsNotify:P,SetBindings:B,eventListeners:f,callbacks:c,flags:{disableScrollbarDrag:!1,disableDefaultContextMenu:!1,enableResize:!1,defaultCursor:null,borderThickness:6,shouldDrag:!1,deferDragToMouseMove:!0,cssDragProperty:"--wails-draggable",cssDragValue:"drag",cssDropProperty:"--wails-drop-target",cssDropValue:"drop",enableWailsDragAndDrop:!1}};window.wailsbindings&&(window.wails.SetBindings(window.wailsbindings),delete window.wails.SetBindings);delete window.wailsbindings;var ot=function(e){var t=window.getComputedStyle(e.target).getPropertyValue(window.wails.flags.cssDragProperty);return t&&(t=t.trim()),!(t!==window.wails.flags.cssDragValue||e.buttons!==1||e.detail!==1)};window.wails.setCSSDragProperties=function(e,t){window.wails.flags.cssDragProperty=e,window.wails.flags.cssDragValue=t};window.wails.setCSSDropProperties=function(e,t){window.wails.flags.cssDropProperty=e,window.wails.flags.cssDropValue=t};window.addEventListener("mousedown",e=>{if(window.wails.flags.resizeEdge){window.WailsInvoke("resize:"+window.wails.flags.resizeEdge),e.preventDefault();return}if(ot(e)){if(window.wails.flags.disableScrollbarDrag&&(e.offsetX>e.target.clientWidth||e.offsetY>e.target.clientHeight))return;window.wails.flags.deferDragToMouseMove?window.wails.flags.shouldDrag=!0:(e.preventDefault(),window.WailsInvoke("drag"));return}else window.wails.flags.shouldDrag=!1});window.addEventListener("mouseup",()=>{window.wails.flags.shouldDrag=!1});function d(e){document.documentElement.style.cursor=e||window.wails.flags.defaultCursor,window.wails.flags.resizeEdge=e}window.addEventListener("mousemove",function(e){if(window.wails.flags.shouldDrag&&(window.wails.flags.shouldDrag=!1,(e.buttons!==void 0?e.buttons:e.which)>0)){window.WailsInvoke("drag");return}if(!window.wails.flags.enableResize)return;window.wails.flags.defaultCursor==null&&(window.wails.flags.defaultCursor=document.documentElement.style.cursor),window.outerWidth-e.clientX{var L=Object.defineProperty;var c=(e,n)=>{for(var o in n)L(e,o,{get:n[o],enumerable:!0})};var m={};c(m,{LogDebug:()=>M,LogError:()=>A,LogFatal:()=>H,LogInfo:()=>P,LogLevel:()=>J,LogPrint:()=>R,LogTrace:()=>z,LogWarning:()=>B,SetLogLevel:()=>G});function d(e,n){window.WailsInvoke("L"+e+n)}function z(e){d("T",e)}function R(e){d("P",e)}function M(e){d("D",e)}function P(e){d("I",e)}function B(e){d("W",e)}function A(e){d("E",e)}function H(e){d("F",e)}function G(e){d("S",e)}var J={TRACE:1,DEBUG:2,INFO:3,WARNING:4,ERROR:5};var v=class{constructor(n,o,i){this.eventName=n,this.maxCallbacks=i||-1,this.Callback=t=>(o.apply(null,t),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},l={};function p(e,n,o){l[e]=l[e]||[];let i=new v(e,n,o);return l[e].push(i),()=>F(i)}function b(e,n){return p(e,n,-1)}function E(e,n){return p(e,n,1)}function S(e){let n=e.name;if(l[n]){let o=l[n].slice();for(let i=l[n].length-1;i>=0;i-=1){let t=l[n][i],r=e.data;t.Callback(r)&&o.splice(i,1)}o.length===0?g(n):l[n]=o}}function y(e){let n;try{n=JSON.parse(e)}catch{let i="Invalid JSON passed to Notify: "+e;throw new Error(i)}S(n)}function C(e){let n={name:e,data:[].slice.apply(arguments).slice(1)};S(n),window.WailsInvoke("EE"+JSON.stringify(n))}function g(e){delete l[e],window.WailsInvoke("EX"+e)}function D(e,...n){g(e),n.length>0&&n.forEach(o=>{g(o)})}function F(e){let n=e.eventName;l[n]=l[n].filter(o=>o!==e),l[n].length===0&&g(n)}var f={};function U(){var e=new Uint32Array(1);return window.crypto.getRandomValues(e)[0]}function j(){return Math.random()*9007199254740991}var W;window.crypto?W=U:W=j;function s(e,n,o){return o==null&&(o=0),new Promise(function(i,t){var r;do r=e+"-"+W();while(f[r]);var a;o>0&&(a=setTimeout(function(){t(Error("Call to "+e+" timed out. Request ID: "+r))},o)),f[r]={timeoutHandle:a,reject:t,resolve:i};try{let u={name:e,args:n,callbackID:r};window.WailsInvoke("C"+JSON.stringify(u))}catch(u){console.error(u)}})}window.ObfuscatedCall=(e,n,o)=>(o==null&&(o=0),new Promise(function(i,t){var r;do r=e+"-"+W();while(f[r]);var a;o>0&&(a=setTimeout(function(){t(Error("Call to method "+e+" timed out. Request ID: "+r))},o)),f[r]={timeoutHandle:a,reject:t,resolve:i};try{let u={id:e,args:n,callbackID:r};window.WailsInvoke("c"+JSON.stringify(u))}catch(u){console.error(u)}}));function T(e){let n;try{n=JSON.parse(e)}catch(t){let r=`Invalid JSON passed to callback: ${t.message}. Message: ${e}`;throw runtime.LogDebug(r),new Error(r)}let o=n.callbackid,i=f[o];if(!i){let t=`Callback '${o}' not registered!!!`;throw console.error(t),new Error(t)}clearTimeout(i.timeoutHandle),delete f[o],n.error?i.reject(n.error):i.resolve(n.result)}window.go={};function O(e){try{e=JSON.parse(e)}catch(n){console.error(n)}window.go=window.go||{},Object.keys(e).forEach(n=>{window.go[n]=window.go[n]||{},Object.keys(e[n]).forEach(o=>{window.go[n][o]=window.go[n][o]||{},Object.keys(e[n][o]).forEach(i=>{window.go[n][o][i]=function(){let t=0;function r(){let a=[].slice.call(arguments);return s([n,o,i].join("."),a,t)}return r.setTimeout=function(a){t=a},r.getTimeout=function(){return t},r}()})})})}var x={};c(x,{WindowCenter:()=>q,WindowFullscreen:()=>Z,WindowGetPosition:()=>se,WindowGetSize:()=>ne,WindowHide:()=>le,WindowIsFullscreen:()=>_,WindowIsMaximised:()=>ue,WindowIsMinimised:()=>pe,WindowIsNormal:()=>We,WindowMaximise:()=>we,WindowMinimise:()=>ce,WindowReload:()=>N,WindowReloadApp:()=>V,WindowSetAlwaysOnTop:()=>te,WindowSetBackgroundColour:()=>me,WindowSetDarkTheme:()=>$,WindowSetLightTheme:()=>Y,WindowSetMaxSize:()=>oe,WindowSetMinSize:()=>ie,WindowSetPosition:()=>re,WindowSetSize:()=>ee,WindowSetSystemDefaultTheme:()=>X,WindowSetTitle:()=>Q,WindowShow:()=>ae,WindowToggleMaximise:()=>de,WindowUnfullscreen:()=>K,WindowUnmaximise:()=>fe,WindowUnminimise:()=>ge});function N(){window.location.reload()}function V(){window.WailsInvoke("WR")}function X(){window.WailsInvoke("WASDT")}function Y(){window.WailsInvoke("WALT")}function $(){window.WailsInvoke("WADT")}function q(){window.WailsInvoke("Wc")}function Q(e){window.WailsInvoke("WT"+e)}function Z(){window.WailsInvoke("WF")}function K(){window.WailsInvoke("Wf")}function _(){return s(":wails:WindowIsFullscreen")}function ee(e,n){window.WailsInvoke("Ws:"+e+":"+n)}function ne(){return s(":wails:WindowGetSize")}function oe(e,n){window.WailsInvoke("WZ:"+e+":"+n)}function ie(e,n){window.WailsInvoke("Wz:"+e+":"+n)}function te(e){window.WailsInvoke("WATP:"+(e?"1":"0"))}function re(e,n){window.WailsInvoke("Wp:"+e+":"+n)}function se(){return s(":wails:WindowGetPos")}function le(){window.WailsInvoke("WH")}function ae(){window.WailsInvoke("WS")}function we(){window.WailsInvoke("WM")}function de(){window.WailsInvoke("Wt")}function fe(){window.WailsInvoke("WU")}function ue(){return s(":wails:WindowIsMaximised")}function ce(){window.WailsInvoke("Wm")}function ge(){window.WailsInvoke("Wu")}function pe(){return s(":wails:WindowIsMinimised")}function We(){return s(":wails:WindowIsNormal")}function me(e,n,o,i){let t=JSON.stringify({r:e||0,g:n||0,b:o||0,a:i||255});window.WailsInvoke("Wr:"+t)}var k={};c(k,{ScreenGetAll:()=>ve});function ve(){return s(":wails:ScreenGetAll")}var h={};c(h,{BrowserOpenURL:()=>xe});function xe(e){window.WailsInvoke("BO:"+e)}var I={};c(I,{ClipboardGetText:()=>he,ClipboardSetText:()=>ke});function ke(e){return s(":wails:ClipboardSetText",[e])}function he(){return s(":wails:ClipboardGetText")}function Ie(){window.WailsInvoke("Q")}function be(){window.WailsInvoke("S")}function Ee(){window.WailsInvoke("H")}function Se(){return s(":wails:Environment")}window.runtime={...m,...x,...h,...k,...I,EventsOn:b,EventsOnce:E,EventsOnMultiple:p,EventsEmit:C,EventsOff:D,Environment:Se,Show:be,Hide:Ee,Quit:Ie};window.wails={Callback:T,EventsNotify:y,SetBindings:O,eventListeners:l,callbacks:f,flags:{disableScrollbarDrag:!1,disableWailsDefaultContextMenu:!1,enableResize:!1,defaultCursor:null,borderThickness:6,shouldDrag:!1,deferDragToMouseMove:!0,cssDragProperty:"--wails-draggable",cssDragValue:"drag"}};window.wailsbindings&&(window.wails.SetBindings(window.wailsbindings),delete window.wails.SetBindings);delete window.wailsbindings;var ye=function(e){var n=window.getComputedStyle(e.target).getPropertyValue(window.wails.flags.cssDragProperty);return n&&(n=n.trim()),!(n!==window.wails.flags.cssDragValue||e.buttons!==1||e.detail!==1)};window.wails.setCSSDragProperties=function(e,n){window.wails.flags.cssDragProperty=e,window.wails.flags.cssDragValue=n};window.addEventListener("mousedown",e=>{if(window.wails.flags.resizeEdge){window.WailsInvoke("resize:"+window.wails.flags.resizeEdge),e.preventDefault();return}if(ye(e)){if(window.wails.flags.disableScrollbarDrag&&(e.offsetX>e.target.clientWidth||e.offsetY>e.target.clientHeight))return;window.wails.flags.deferDragToMouseMove?window.wails.flags.shouldDrag=!0:(e.preventDefault(),window.WailsInvoke("drag"));return}else window.wails.flags.shouldDrag=!1});window.addEventListener("mouseup",()=>{window.wails.flags.shouldDrag=!1});function w(e){document.documentElement.style.cursor=e||window.wails.flags.defaultCursor,window.wails.flags.resizeEdge=e}window.addEventListener("mousemove",function(e){if(window.wails.flags.shouldDrag&&(window.wails.flags.shouldDrag=!1,(e.buttons!==void 0?e.buttons:e.which)>0)){window.WailsInvoke("drag");return}if(!window.wails.flags.enableResize)return;window.wails.flags.defaultCursor==null&&(window.wails.flags.defaultCursor=document.documentElement.style.cursor),window.outerWidth-e.clientX; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. @@ -233,98 +233,3 @@ export function ClipboardGetText(): Promise; // [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) // Sets a text on the clipboard export function ClipboardSetText(text: string): Promise; - -// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) -// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. -export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void - -// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) -// OnFileDropOff removes the drag and drop listeners and handlers. -export function OnFileDropOff() :void - -// Check if the file path resolver is available -export function CanResolveFilePaths(): boolean; - -// Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void - -// Notification types -export interface NotificationOptions { - id: string; - title: string; - subtitle?: string; // macOS and Linux only - body?: string; - categoryId?: string; - data?: { [key: string]: any }; -} - -export interface NotificationAction { - id?: string; - title?: string; - destructive?: boolean; // macOS-specific -} - -export interface NotificationCategory { - id?: string; - actions?: NotificationAction[]; - hasReplyField?: boolean; - replyPlaceholder?: string; - replyButtonTitle?: string; -} - -// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications) -// Initializes the notification service for the application. -// This must be called before sending any notifications. -export function InitializeNotifications(): Promise; - -// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications) -// Cleans up notification resources and releases any held connections. -export function CleanupNotifications(): Promise; - -// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable) -// Checks if notifications are available on the current platform. -export function IsNotificationAvailable(): Promise; - -// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization) -// Requests notification authorization from the user (macOS only). -export function RequestNotificationAuthorization(): Promise; - -// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization) -// Checks the current notification authorization status (macOS only). -export function CheckNotificationAuthorization(): Promise; - -// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification) -// Sends a basic notification with the given options. -export function SendNotification(options: NotificationOptions): Promise; - -// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions) -// Sends a notification with action buttons. Requires a registered category. -export function SendNotificationWithActions(options: NotificationOptions): Promise; - -// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory) -// Registers a notification category that can be used with SendNotificationWithActions. -export function RegisterNotificationCategory(category: NotificationCategory): Promise; - -// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory) -// Removes a previously registered notification category. -export function RemoveNotificationCategory(categoryId: string): Promise; - -// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications) -// Removes all pending notifications from the notification center. -export function RemoveAllPendingNotifications(): Promise; - -// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification) -// Removes a specific pending notification by its identifier. -export function RemovePendingNotification(identifier: string): Promise; - -// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications) -// Removes all delivered notifications from the notification center. -export function RemoveAllDeliveredNotifications(): Promise; - -// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification) -// Removes a specific delivered notification by its identifier. -export function RemoveDeliveredNotification(identifier: string): Promise; - -// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification) -// Removes a notification by its identifier (cross-platform convenience function). -export function RemoveNotification(identifier: string): Promise; \ No newline at end of file diff --git a/v2/internal/frontend/runtime/wrapper/runtime.js b/v2/internal/frontend/runtime/wrapper/runtime.js index 556621eeb..bd4f371ae 100644 --- a/v2/internal/frontend/runtime/wrapper/runtime.js +++ b/v2/internal/frontend/runtime/wrapper/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName, ...additionalEventNames) { return window.runtime.EventsOff(eventName, ...additionalEventNames); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { return EventsOnMultiple(eventName, callback, 1); } @@ -203,96 +199,4 @@ export function ClipboardGetText() { export function ClipboardSetText(text) { return window.runtime.ClipboardSetText(text); -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - return window.runtime.OnFileDrop(callback, useDropTarget); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - return window.runtime.OnFileDropOff(); -} - -export function CanResolveFilePaths() { - return window.runtime.CanResolveFilePaths(); -} - -export function ResolveFilePaths(files) { - return window.runtime.ResolveFilePaths(files); -} - -export function InitializeNotifications() { - return window.runtime.InitializeNotifications(); -} - -export function CleanupNotifications() { - return window.runtime.CleanupNotifications(); -} - -export function IsNotificationAvailable() { - return window.runtime.IsNotificationAvailable(); -} - -export function RequestNotificationAuthorization() { - return window.runtime.RequestNotificationAuthorization(); -} - -export function CheckNotificationAuthorization() { - return window.runtime.CheckNotificationAuthorization(); -} - -export function SendNotification(options) { - return window.runtime.SendNotification(options); -} - -export function SendNotificationWithActions(options) { - return window.runtime.SendNotificationWithActions(options); -} - -export function RegisterNotificationCategory(category) { - return window.runtime.RegisterNotificationCategory(category); -} - -export function RemoveNotificationCategory(categoryId) { - return window.runtime.RemoveNotificationCategory(categoryId); -} - -export function RemoveAllPendingNotifications() { - return window.runtime.RemoveAllPendingNotifications(); -} - -export function RemovePendingNotification(identifier) { - return window.runtime.RemovePendingNotification(identifier); -} - -export function RemoveAllDeliveredNotifications() { - return window.runtime.RemoveAllDeliveredNotifications(); -} - -export function RemoveDeliveredNotification(identifier) { - return window.runtime.RemoveDeliveredNotification(identifier); -} - -export function RemoveNotification(identifier) { - return window.runtime.RemoveNotification(identifier); } \ No newline at end of file diff --git a/v2/internal/frontend/utils/urlValidator.go b/v2/internal/frontend/utils/urlValidator.go deleted file mode 100644 index 76ba216ce..000000000 --- a/v2/internal/frontend/utils/urlValidator.go +++ /dev/null @@ -1,58 +0,0 @@ -package utils - -import ( - "errors" - "fmt" - "net/url" - "regexp" - "strings" -) - -func ValidateAndSanitizeURL(rawURL string) (string, error) { - // Check for null bytes (can cause truncation issues in some systems) - if strings.Contains(rawURL, "\x00") { - return "", errors.New("null bytes not allowed in URL") - } - - // Parse URL first - this handles most malformed URLs - parsedURL, err := url.Parse(rawURL) - if err != nil { - return "", fmt.Errorf("invalid URL format: %v", err) - } - - scheme := strings.ToLower(parsedURL.Scheme) - - if scheme == "javascript" || scheme == "data" || scheme == "file" || scheme == "ftp" || scheme == "" { - return "", errors.New("scheme not allowed") - } - - // Ensure there's actually a host for http/https URLs - if (scheme == "http" || scheme == "https") && parsedURL.Host == "" { - return "", fmt.Errorf("missing host for %s URL", scheme) - } - - sanitizedURL := parsedURL.String() - - // Check for control characters that might cause issues - // (but allow legitimate URL characters like &, ;, etc.) - for i, r := range sanitizedURL { - // Block control characters except tab, but allow other printable chars - if r < 32 && r != 9 { // 9 is tab, which might be legitimate - return "", fmt.Errorf("control character at position %d not allowed", i) - } - } - - // Shell metacharacter check - shellDangerous := `[;\|` + "`" + `$\\<>*{}\[\]()~! \t\n\r]` - if matched, _ := regexp.MatchString(shellDangerous, sanitizedURL); matched { - return "", errors.New("shell metacharacters not allowed") - } - - // Unicode danger check - unicodeDangerous := "[\u0000-\u001F\u007F\u00A0\u1680\u2000-\u200F\u2028-\u202F\u205F\u2060\u3000\uFEFF]" - if matched, _ := regexp.MatchString(unicodeDangerous, sanitizedURL); matched { - return "", errors.New("unicode dangerous characters not allowed") - } - - return sanitizedURL, nil -} diff --git a/v2/internal/frontend/utils/urlValidator_test.go b/v2/internal/frontend/utils/urlValidator_test.go deleted file mode 100644 index b385ccec1..000000000 --- a/v2/internal/frontend/utils/urlValidator_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package utils_test - -import ( - "strings" - "testing" - - "github.com/wailsapp/wails/v2/internal/frontend/utils" -) - -// Test cases for ValidateAndOpenURL -func TestValidateURL(t *testing.T) { - testCases := []struct { - name string - url string - shouldErr bool - errMsg string - expected string - }{ - // Valid URLs - { - name: "valid https URL", - url: "https://www.example.com", - shouldErr: false, - expected: "https://www.example.com", - }, - { - name: "valid http URL", - url: "http://example.com", - shouldErr: false, - expected: "http://example.com", - }, - { - name: "URL with query parameters", - url: "https://example.com/search?q=cats&dogs", - shouldErr: false, - expected: "https://example.com/search?q=cats&dogs", - }, - { - name: "URL with port", - url: "https://example.com:8080/path", - shouldErr: false, - expected: "https://example.com:8080/path", - }, - { - name: "URL with fragment", - url: "https://example.com/page#section", - shouldErr: false, - expected: "https://example.com/page#section", - }, - { - name: "urlencode params", - url: "http://google.com/ ----browser-subprocess-path=C:\\\\Users\\\\Public\\\\test.bat", - shouldErr: false, - expected: "http://google.com/%20----browser-subprocess-path=C:%5C%5CUsers%5C%5CPublic%5C%5Ctest.bat", - }, - - // Invalid schemes - { - name: "javascript scheme", - url: "javascript:alert('xss')", - shouldErr: true, - errMsg: "scheme not allowed", - }, - { - name: "data scheme", - url: "data:text/html,", - shouldErr: true, - errMsg: "scheme not allowed", - }, - { - name: "file scheme", - url: "file:///etc/passwd", - shouldErr: true, - errMsg: "scheme not allowed", - }, - { - name: "ftp scheme", - url: "ftp://files.example.com/file.txt", - shouldErr: true, - errMsg: "scheme not allowed", - }, - - // Malformed URLs - { - name: "not a URL", - url: "not-a-url", - shouldErr: true, - errMsg: "scheme not allowed", // will have empty scheme - }, - { - name: "missing scheme", - url: "example.com", - shouldErr: true, - errMsg: "scheme not allowed", - }, - { - name: "malformed URL", - url: "https://", - shouldErr: true, - errMsg: "missing host", - }, - { - name: "empty host", - url: "http:///path", - shouldErr: true, - errMsg: "missing host", - }, - - // Security issues - { - name: "null byte in URL", - url: "https://example.com\x00/hidden", - shouldErr: true, - errMsg: "null bytes not allowed", - }, - { - name: "control characters", - url: "https://example.com\n/path", - shouldErr: true, - errMsg: "control character", - }, - { - name: "carriage return", - url: "https://example.com\r/path", - shouldErr: true, - errMsg: "control character", - }, - { - name: "URL with tab character", - url: "https://example.com/path?q=hello\tworld", - shouldErr: true, - errMsg: "control character", - }, - { - name: "URL with path parameters", - url: "https://example.com/path;param=value", - shouldErr: true, - errMsg: "shell metacharacters not allowed", - }, - { - name: "URL with special characters in query", - url: "https://example.com/search?q=hello world&filter=price>100", - shouldErr: true, - errMsg: "shell metacharacters not allowed", - }, - { - name: "URL with special characters in query and params", - url: "https://example.com/search?q=hello ----browser-subprocess-path=C:\\\\Users\\\\Public\\\\test.bat", - shouldErr: true, - errMsg: "shell metacharacters not allowed", - }, - { - name: "URL with dollar sign in query", - url: "https://example.com/search?price=$100", - shouldErr: true, - errMsg: "shell metacharacters not allowed", - }, - { - name: "URL with parentheses", - url: "https://example.com/file(1).html", - shouldErr: true, - errMsg: "shell metacharacters not allowed", - }, - { - name: "URL with unicode", - url: "https://example.com/search?q=hello\u2001foo", - shouldErr: true, - errMsg: "unicode dangerous characters not allowed", - }, - - // Edge cases - { - name: "international domain", - url: "https://例え.テスト/path", - shouldErr: false, - expected: "https://%E4%BE%8B%E3%81%88.%E3%83%86%E3%82%B9%E3%83%88/path", - }, - { - name: "URL with pipe character", - url: "https://example.com/user/123|admin", - shouldErr: false, - expected: "https://example.com/user/123%7Cadmin", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // We'll test only the validation part to avoid actually opening URLs - sanitized, err := utils.ValidateAndSanitizeURL(tc.url) - - if tc.shouldErr { - if err == nil { - t.Errorf("expected error for URL %q, but got none", tc.url) - } else if tc.errMsg != "" && !strings.Contains(err.Error(), tc.errMsg) { - t.Errorf("expected error containing %q, got %q", tc.errMsg, err.Error()) - } - } else { - if err != nil { - t.Errorf("expected no error for URL %q, but got: %v", tc.url, err) - } - if sanitized != tc.expected { - t.Errorf("unexpected sanitized URL for %q: expected %q, got %q", tc.url, tc.expected, sanitized) - } - } - }) - } -} diff --git a/v2/internal/fs/fs.go b/v2/internal/fs/fs.go index 5662c020c..5bfa71b1c 100644 --- a/v2/internal/fs/fs.go +++ b/v2/internal/fs/fs.go @@ -2,7 +2,6 @@ package fs import ( "crypto/md5" - "encoding/hex" "fmt" "io" "io/fs" @@ -28,14 +27,14 @@ func RelativeToCwd(relativePath string) (string, error) { // Mkdir will create the given directory func Mkdir(dirname string) error { - return os.Mkdir(dirname, 0o755) + return os.Mkdir(dirname, 0755) } // MkDirs creates the given nested directories. // Returns error on failure func MkDirs(fullPath string, mode ...os.FileMode) error { var perms os.FileMode - perms = 0o755 + perms = 0755 if len(mode) == 1 { perms = mode[0] } @@ -112,7 +111,7 @@ func RelativePath(relativepath string, optionalpaths ...string) string { // 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 unnecessary error handling. + // that it cuts down a ton of unnecassary error handling. panic(err) } return result @@ -142,7 +141,7 @@ func MD5File(filename string) (string, error) { return "", err } - return hex.EncodeToString(h.Sum(nil)), nil + return fmt.Sprintf("%x", h.Sum(nil)), nil } // MustMD5File will call MD5File and abort the program on error @@ -158,7 +157,7 @@ func MustMD5File(filename string) string { // MustWriteString will attempt to write the given data to the given filename // It will abort the program in the event of a failure func MustWriteString(filename string, data string) { - err := os.WriteFile(filename, []byte(data), 0o755) + err := os.WriteFile(filename, []byte(data), 0755) if err != nil { fatal("Unable to write file", filename, ":", err.Error()) os.Exit(1) @@ -195,6 +194,7 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) { } func DirIsEmpty(dir string) (bool, error) { + // CREDIT: https://stackoverflow.com/a/30708914/8325411 f, err := os.Open(dir) if err != nil { @@ -284,6 +284,7 @@ func SetPermissions(dir string, perm os.FileMode) error { // Symlinks are ignored and skipped. // Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04 func CopyDirExtended(src string, dst string, ignore []string) (err error) { + ignoreList := slicer.String(ignore) src = filepath.Clean(src) dst = filepath.Clean(dst) @@ -381,6 +382,7 @@ func FindPathToFile(fsys fs.FS, file string) (string, error) { // FindFileInParents searches for a file in the current directory and all parent directories. // Returns the absolute path to the file if found, otherwise an empty string func FindFileInParents(path string, filename string) string { + // Check for bad paths if _, err := os.Stat(path); err != nil { return "" diff --git a/v2/internal/github/github.go b/v2/internal/github/github.go index 2aa5e1432..db3b70c58 100644 --- a/v2/internal/github/github.go +++ b/v2/internal/github/github.go @@ -3,15 +3,13 @@ package github import ( "encoding/json" "fmt" - "github.com/charmbracelet/glamour/styles" + "github.com/charmbracelet/glamour" "io" "net/http" "net/url" "runtime" "sort" "strings" - - "github.com/charmbracelet/glamour" ) func GetReleaseNotes(tagVersion string, noColour bool) string { @@ -40,7 +38,7 @@ func GetReleaseNotes(tagVersion string, noColour bool) string { var termRendererOpts []glamour.TermRendererOption if runtime.GOOS == "windows" || noColour { - termRendererOpts = append(termRendererOpts, glamour.WithStyles(styles.NoTTYStyleConfig)) + termRendererOpts = append(termRendererOpts, glamour.WithStyles(glamour.NoTTYStyleConfig)) } else { termRendererOpts = append(termRendererOpts, glamour.WithAutoStyle()) } @@ -59,6 +57,7 @@ func GetReleaseNotes(tagVersion string, noColour bool) string { // 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 @@ -98,6 +97,7 @@ func GetVersionTags() ([]*SemanticVersion, error) { // GetLatestStableRelease gets the latest stable release on GitHub func GetLatestStableRelease() (result *SemanticVersion, err error) { + tags, err := GetVersionTags() if err != nil { return nil, err @@ -114,6 +114,7 @@ func GetLatestStableRelease() (result *SemanticVersion, err error) { // GetLatestPreRelease gets the latest prerelease on GitHub func GetLatestPreRelease() (result *SemanticVersion, err error) { + tags, err := GetVersionTags() if err != nil { return nil, err diff --git a/v2/internal/go-common-file-dialog/README.md b/v2/internal/go-common-file-dialog/README.md index 1cb5902d1..201634595 100644 --- a/v2/internal/go-common-file-dialog/README.md +++ b/v2/internal/go-common-file-dialog/README.md @@ -2,30 +2,36 @@ [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. +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#) -. +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 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 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 +- 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/v2/internal/go-common-file-dialog/cfd/DialogConfig.go b/v2/internal/go-common-file-dialog/cfd/DialogConfig.go index 9e06fb503..221dbef27 100644 --- a/v2/internal/go-common-file-dialog/cfd/DialogConfig.go +++ b/v2/internal/go-common-file-dialog/cfd/DialogConfig.go @@ -2,12 +2,6 @@ package cfd -import ( - "fmt" - "os" - "reflect" -) - type FileFilter struct { // The display name of the filter (That is shown to the user) DisplayName string @@ -15,9 +9,6 @@ type FileFilter struct { Pattern string } -// Never obfuscate the FileFilter type. -var _ = reflect.TypeOf(FileFilter{}) - type DialogConfig struct { // The title of the dialog Title string @@ -76,10 +67,6 @@ func (config *DialogConfig) apply(dialog Dialog) (err error) { } if config.Folder != "" { - _, err = os.Stat(config.Folder) - if err != nil { - return - } err = dialog.SetFolder(config.Folder) if err != nil { return @@ -87,10 +74,6 @@ func (config *DialogConfig) apply(dialog Dialog) (err error) { } if config.DefaultFolder != "" { - _, err = os.Stat(config.DefaultFolder) - if err != nil { - return - } err = dialog.SetDefaultFolder(config.DefaultFolder) if err != nil { return @@ -119,10 +102,6 @@ func (config *DialogConfig) apply(dialog Dialog) (err error) { } 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 diff --git a/v2/internal/go-common-file-dialog/cfd/errors.go b/v2/internal/go-common-file-dialog/cfd/errors.go index 4ca3300b9..c097c8eb2 100644 --- a/v2/internal/go-common-file-dialog/cfd/errors.go +++ b/v2/internal/go-common-file-dialog/cfd/errors.go @@ -3,7 +3,5 @@ package cfd import "errors" var ( - ErrCancelled = errors.New("cancelled by user") - ErrInvalidGUID = errors.New("guid cannot be nil") - ErrEmptyFilters = errors.New("must specify at least one filter") + ErrorCancelled = errors.New("cancelled by user") ) diff --git a/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go b/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go index b1be23fcf..8c82cda2c 100644 --- a/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go +++ b/v2/internal/go-common-file-dialog/cfd/iFileOpenDialog.go @@ -5,7 +5,7 @@ package cfd import ( "github.com/go-ole/go-ole" - "github.com/google/uuid" + "github.com/wailsapp/wails/v2/internal/go-common-file-dialog/util" "syscall" "unsafe" ) @@ -106,7 +106,7 @@ func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error } func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error { - return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), StringToUUID(role)) + return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), util.StringToUUID(role)) } // This should only be callable when the user asks for a multi select because @@ -164,7 +164,8 @@ func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) erro func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) { var shellItemArray *iShellItemArray - ret, _, _ := syscall.SyscallN(vtbl.GetResults, + ret, _, _ := syscall.Syscall(vtbl.GetResults, + 1, uintptr(objPtr), uintptr(unsafe.Pointer(&shellItemArray)), 0) @@ -177,7 +178,7 @@ func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]str return nil, err } if shellItemArray == nil { - return nil, ErrCancelled + return nil, ErrorCancelled } defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray)) count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray)) @@ -194,7 +195,3 @@ func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]str } return results, nil } - -func StringToUUID(str string) *ole.GUID { - return ole.NewGUID(uuid.NewSHA1(uuid.Nil, []byte(str)).String()) -} diff --git a/v2/internal/go-common-file-dialog/cfd/iFileSaveDialog.go b/v2/internal/go-common-file-dialog/cfd/iFileSaveDialog.go index ddee7b246..3effeda25 100644 --- a/v2/internal/go-common-file-dialog/cfd/iFileSaveDialog.go +++ b/v2/internal/go-common-file-dialog/cfd/iFileSaveDialog.go @@ -5,6 +5,7 @@ package cfd import ( "github.com/go-ole/go-ole" + "github.com/wailsapp/wails/v2/internal/go-common-file-dialog/util" "unsafe" ) @@ -76,7 +77,7 @@ func (fileSaveDialog *iFileSaveDialog) SetFileFilters(filter []FileFilter) error } func (fileSaveDialog *iFileSaveDialog) SetRole(role string) error { - return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), StringToUUID(role)) + return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), util.StringToUUID(role)) } func (fileSaveDialog *iFileSaveDialog) SetDefaultExtension(defaultExtension string) error { diff --git a/v2/internal/go-common-file-dialog/cfd/iShellItem.go b/v2/internal/go-common-file-dialog/cfd/iShellItem.go index 080115345..6a747f4d9 100644 --- a/v2/internal/go-common-file-dialog/cfd/iShellItem.go +++ b/v2/internal/go-common-file-dialog/cfd/iShellItem.go @@ -30,10 +30,6 @@ type iShellItemVtbl struct { 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, @@ -44,9 +40,10 @@ func newIShellItem(path string) (*iShellItem, error) { func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) { var ptr *uint16 - ret, _, _ := syscall.SyscallN(vtbl.GetDisplayName, + ret, _, _ := syscall.Syscall(vtbl.GetDisplayName, + 2, uintptr(objPtr), - 0x80058000, // SIGDN_FILESYSPATH, + 0x80058000, // SIGDN_FILESYSPATH uintptr(unsafe.Pointer(&ptr))) if err := hresultToError(ret); err != nil { return "", err diff --git a/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go b/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go index c548160d1..84f26fa20 100644 --- a/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go +++ b/v2/internal/go-common-file-dialog/cfd/iShellItemArray.go @@ -38,9 +38,11 @@ type iShellItemArrayVtbl struct { func (vtbl *iShellItemArrayVtbl) getCount(objPtr unsafe.Pointer) (uintptr, error) { var count uintptr - ret, _, _ := syscall.SyscallN(vtbl.GetCount, + ret, _, _ := syscall.Syscall(vtbl.GetCount, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(&count))) + uintptr(unsafe.Pointer(&count)), + 0) if err := hresultToError(ret); err != nil { return 0, err } @@ -49,7 +51,8 @@ func (vtbl *iShellItemArrayVtbl) getCount(objPtr unsafe.Pointer) (uintptr, error func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) (string, error) { var shellItem *iShellItem - ret, _, _ := syscall.SyscallN(vtbl.GetItemAt, + ret, _, _ := syscall.Syscall(vtbl.GetItemAt, + 2, uintptr(objPtr), index, uintptr(unsafe.Pointer(&shellItem))) @@ -57,7 +60,7 @@ func (vtbl *iShellItemArrayVtbl) getItemAt(objPtr unsafe.Pointer, index uintptr) return "", err } if shellItem == nil { - return "", ErrCancelled + return "", ErrorCancelled } defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) diff --git a/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go b/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go index 581a7b25c..a92100010 100644 --- a/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go +++ b/v2/internal/go-common-file-dialog/cfd/vtblCommonFunc.go @@ -1,8 +1,10 @@ //go:build windows +// +build windows package cfd import ( + "fmt" "github.com/go-ole/go-ole" "strings" "syscall" @@ -17,23 +19,27 @@ func hresultToError(hr uintptr) error { } func (vtbl *iUnknownVtbl) release(objPtr unsafe.Pointer) error { - ret, _, _ := syscall.SyscallN(vtbl.Release, + ret, _, _ := syscall.Syscall(vtbl.Release, + 0, uintptr(objPtr), + 0, 0) return hresultToError(ret) } func (vtbl *iModalWindowVtbl) show(objPtr unsafe.Pointer, hwnd uintptr) error { - ret, _, _ := syscall.SyscallN(vtbl.Show, + ret, _, _ := syscall.Syscall(vtbl.Show, + 1, uintptr(objPtr), - hwnd) + hwnd, + 0) return hresultToError(ret) } func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileFilter) error { cFileTypes := len(filters) if cFileTypes < 0 { - return ErrEmptyFilters + return fmt.Errorf("must specify at least one filter") } comDlgFilterSpecs := make([]comDlgFilterSpec, cFileTypes) for i := 0; i < cFileTypes; i++ { @@ -43,16 +49,8 @@ func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileF 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, + ret, _, _ := syscall.Syscall(vtbl.SetFileTypes, + 2, uintptr(objPtr), uintptr(cFileTypes), uintptr(unsafe.Pointer(&comDlgFilterSpecs[0]))) @@ -84,17 +82,21 @@ func (vtbl *iFileDialogVtbl) setFileTypes(objPtr unsafe.Pointer, filters []FileF // FOS_FORCEPREVIEWPANEON = 0x40000000, // FOS_SUPPORTSTREAMABLEITEMS = 0x80000000 func (vtbl *iFileDialogVtbl) setOptions(objPtr unsafe.Pointer, options uint32) error { - ret, _, _ := syscall.SyscallN(vtbl.SetOptions, + ret, _, _ := syscall.Syscall(vtbl.SetOptions, + 1, uintptr(objPtr), - uintptr(options)) + uintptr(options), + 0) return hresultToError(ret) } func (vtbl *iFileDialogVtbl) getOptions(objPtr unsafe.Pointer) (uint32, error) { var options uint32 - ret, _, _ := syscall.SyscallN(vtbl.GetOptions, + ret, _, _ := syscall.Syscall(vtbl.GetOptions, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(&options))) + uintptr(unsafe.Pointer(&options)), + 0) return options, hresultToError(ret) } @@ -120,9 +122,11 @@ func (vtbl *iFileDialogVtbl) setDefaultFolder(objPtr unsafe.Pointer, path string return err } defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) - ret, _, _ := syscall.SyscallN(vtbl.SetDefaultFolder, + ret, _, _ := syscall.Syscall(vtbl.SetDefaultFolder, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(shellItem))) + uintptr(unsafe.Pointer(shellItem)), + 0) return hresultToError(ret) } @@ -132,32 +136,40 @@ func (vtbl *iFileDialogVtbl) setFolder(objPtr unsafe.Pointer, path string) error return err } defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) - ret, _, _ := syscall.SyscallN(vtbl.SetFolder, + ret, _, _ := syscall.Syscall(vtbl.SetFolder, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(shellItem))) + uintptr(unsafe.Pointer(shellItem)), + 0) 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, + ret, _, _ := syscall.Syscall(vtbl.SetTitle, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(titlePtr))) + uintptr(unsafe.Pointer(titlePtr)), + 0) return hresultToError(ret) } func (vtbl *iFileDialogVtbl) close(objPtr unsafe.Pointer) error { - ret, _, _ := syscall.SyscallN(vtbl.Close, - uintptr(objPtr)) + ret, _, _ := syscall.Syscall(vtbl.Close, + 1, + uintptr(objPtr), + 0, + 0) return hresultToError(ret) } func (vtbl *iFileDialogVtbl) getResult(objPtr unsafe.Pointer) (*iShellItem, error) { var shellItem *iShellItem - ret, _, _ := syscall.SyscallN(vtbl.GetResult, + ret, _, _ := syscall.Syscall(vtbl.GetResult, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(&shellItem))) + uintptr(unsafe.Pointer(&shellItem)), + 0) return shellItem, hresultToError(ret) } @@ -167,58 +179,49 @@ func (vtbl *iFileDialogVtbl) getResultString(objPtr unsafe.Pointer) (string, err return "", err } if shellItem == nil { - return "", ErrCancelled + return "", ErrorCancelled } defer shellItem.vtbl.release(unsafe.Pointer(shellItem)) return shellItem.vtbl.getDisplayName(unsafe.Pointer(shellItem)) } func (vtbl *iFileDialogVtbl) setClientGuid(objPtr unsafe.Pointer, guid *ole.GUID) error { - // Ensure the GUID is not nil - if guid == nil { - return ErrInvalidGUID - } - - // Call the SetClientGuid method - ret, _, _ := syscall.SyscallN(vtbl.SetClientGuid, + ret, _, _ := syscall.Syscall(vtbl.SetClientGuid, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(guid))) - - // Convert the HRESULT to a Go error + uintptr(unsafe.Pointer(guid)), + 0) 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] == '.' { + if 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, + ret, _, _ := syscall.Syscall(vtbl.SetDefaultExtension, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(defaultExtensionPtr))) - - // Convert the HRESULT to a Go error + uintptr(unsafe.Pointer(defaultExtensionPtr)), + 0) 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, + ret, _, _ := syscall.Syscall(vtbl.SetFileName, + 1, uintptr(objPtr), - uintptr(unsafe.Pointer(fileNamePtr))) + uintptr(unsafe.Pointer(fileNamePtr)), + 0) return hresultToError(ret) } func (vtbl *iFileDialogVtbl) setSelectedFileFilterIndex(objPtr unsafe.Pointer, index uint) error { - ret, _, _ := syscall.SyscallN(vtbl.SetFileTypeIndex, + ret, _, _ := syscall.Syscall(vtbl.SetFileTypeIndex, + 1, uintptr(objPtr), - uintptr(index+1)) // SetFileTypeIndex counts from 1 + uintptr(index+1), // SetFileTypeIndex counts from 1 + 0) return hresultToError(ret) } diff --git a/v2/internal/gomod/gomod.go b/v2/internal/gomod/gomod.go index c38e60f0b..8ab7e0b66 100644 --- a/v2/internal/gomod/gomod.go +++ b/v2/internal/gomod/gomod.go @@ -2,7 +2,6 @@ package gomod import ( "fmt" - "github.com/Masterminds/semver" "golang.org/x/mod/modfile" ) diff --git a/v2/internal/gomod/gomod_data_unix.go b/v2/internal/gomod/gomod_data_unix.go index c6004f486..f3a5e04c3 100644 --- a/v2/internal/gomod/gomod_data_unix.go +++ b/v2/internal/gomod/gomod_data_unix.go @@ -10,7 +10,6 @@ require github.com/wailsapp/wails/v2 v2.0.0-beta.7 //replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2 ` - const basicUpdated string = `module changeme go 1.17 @@ -30,7 +29,6 @@ require ( //replace github.com/wailsapp/wails/v2 v2.0.0-beta.7 => /home/lea/wails/v2 ` - const multilineReplace = `module changeme go 1.17 @@ -100,7 +98,6 @@ require ( replace github.com/wailsapp/wails/v2 v2.0.0-beta.20 => /home/lea/wails/v2 ` - const multilineReplaceNoVersionUpdated = `module changeme go 1.17 @@ -111,7 +108,6 @@ require ( replace github.com/wailsapp/wails/v2 => /home/lea/wails/v2 ` - const multilineReplaceNoVersionBlockUpdated = `module changeme go 1.17 diff --git a/v2/internal/goversion/min.go b/v2/internal/goversion/min.go index 8c057b3c2..17edc0c94 100644 --- a/v2/internal/goversion/min.go +++ b/v2/internal/goversion/min.go @@ -1,3 +1,3 @@ package goversion -const MinRequirement string = "1.20" +const MinRequirement string = "1.18" diff --git a/v2/internal/logger/custom_logger.go b/v2/internal/logger/custom_logger.go index 51e07c0fc..5e24aa093 100644 --- a/v2/internal/logger/custom_logger.go +++ b/v2/internal/logger/custom_logger.go @@ -86,6 +86,7 @@ func (l *customLogger) Warning(format string, args ...interface{}) { func (l *customLogger) Error(format string, args ...interface{}) { format = fmt.Sprintf("%s | %s", l.name, format) l.logger.Error(format, args...) + } // Fatal level logging. Works like Sprintf. diff --git a/v2/internal/logger/default_logger.go b/v2/internal/logger/default_logger.go index 5c72ae209..fe5c05387 100644 --- a/v2/internal/logger/default_logger.go +++ b/v2/internal/logger/default_logger.go @@ -84,6 +84,7 @@ func (l *Logger) Info(format string, args ...interface{}) { if l.logLevel <= logger.INFO { l.output.Info(fmt.Sprintf(format, args...)) } + } // Warning level logging. Works like Sprintf. @@ -98,6 +99,7 @@ func (l *Logger) Error(format string, args ...interface{}) { if l.logLevel <= logger.ERROR { l.output.Error(fmt.Sprintf(format, args...)) } + } // Fatal level logging. Works like Sprintf. diff --git a/v2/internal/menumanager/applicationmenu.go b/v2/internal/menumanager/applicationmenu.go index 4446a00cb..424834e53 100644 --- a/v2/internal/menumanager/applicationmenu.go +++ b/v2/internal/menumanager/applicationmenu.go @@ -3,6 +3,7 @@ package menumanager import "github.com/wailsapp/wails/v2/pkg/menu" func (m *Manager) SetApplicationMenu(applicationMenu *menu.Menu) error { + if applicationMenu == nil { return nil } @@ -37,6 +38,7 @@ func (m *Manager) UpdateApplicationMenu() (string, error) { } func (m *Manager) processApplicationMenu() error { + // Process the menu m.processedApplicationMenu = NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu) m.processRadioGroups(m.processedApplicationMenu, m.applicationMenuItemMap) diff --git a/v2/internal/menumanager/contextmenu.go b/v2/internal/menumanager/contextmenu.go index f05bcdc49..77c47891c 100644 --- a/v2/internal/menumanager/contextmenu.go +++ b/v2/internal/menumanager/contextmenu.go @@ -23,6 +23,7 @@ func (t *ContextMenu) AsJSON() (string, error) { } func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu { + result := &ContextMenu{ ID: contextMenu.ID, menu: contextMenu.Menu, @@ -36,6 +37,7 @@ func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu { } func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) { + newContextMenu := NewContextMenu(contextMenu) // Save the references diff --git a/v2/internal/menumanager/menuitemmap.go b/v2/internal/menumanager/menuitemmap.go index e4e291be6..790d5d06d 100644 --- a/v2/internal/menumanager/menuitemmap.go +++ b/v2/internal/menumanager/menuitemmap.go @@ -2,10 +2,8 @@ package menumanager import ( "fmt" - "strconv" - "sync" - "github.com/wailsapp/wails/v2/pkg/menu" + "sync" ) // MenuItemMap holds a mapping between menuIDs and menu items @@ -50,13 +48,14 @@ func (m *MenuItemMap) Dump() { // GenerateMenuID returns a unique string ID for a menu item func (m *MenuItemMap) generateMenuID() string { m.menuIDCounterMutex.Lock() - result := strconv.FormatInt(m.menuIDCounter, 10) + result := fmt.Sprintf("%d", m.menuIDCounter) m.menuIDCounter++ m.menuIDCounterMutex.Unlock() return result } func (m *MenuItemMap) processMenuItem(item *menu.MenuItem) { + if item.SubMenu != nil { for _, submenuitem := range item.SubMenu.Items { m.processMenuItem(submenuitem) diff --git a/v2/internal/menumanager/menumanager.go b/v2/internal/menumanager/menumanager.go index 0c6be0df2..ea7939415 100644 --- a/v2/internal/menumanager/menumanager.go +++ b/v2/internal/menumanager/menumanager.go @@ -2,11 +2,11 @@ package menumanager import ( "fmt" - "github.com/wailsapp/wails/v2/pkg/menu" ) type Manager struct { + // The application menu. applicationMenu *menu.Menu applicationMenuJSON string @@ -43,6 +43,7 @@ func (m *Manager) getMenuItemByID(menuMap *MenuItemMap, menuId string) *menu.Men } func (m *Manager) ProcessClick(menuID string, data string, menuType string, parentID string) error { + var menuItemMap *MenuItemMap switch menuType { @@ -92,7 +93,7 @@ func (m *Manager) ProcessClick(menuID string, data string, menuType string, pare // Create new Callback struct callbackData := &menu.CallbackData{ MenuItem: menuItem, - // ContextData: data, + //ContextData: data, } // Call back! diff --git a/v2/internal/menumanager/processedMenu.go b/v2/internal/menumanager/processedMenu.go index c87646ccb..8d886e19e 100644 --- a/v2/internal/menumanager/processedMenu.go +++ b/v2/internal/menumanager/processedMenu.go @@ -2,7 +2,6 @@ package menumanager import ( "encoding/json" - "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/menu/keys" ) @@ -12,7 +11,7 @@ type ProcessedMenuItem struct { // Label is what appears as the menu text Label string `json:",omitempty"` // Role is a predefined menu type - // Role menu.Role `json:",omitempty"` + //Role menu.Role `json:",omitempty"` // Accelerator holds a representation of a key binding Accelerator *keys.Accelerator `json:",omitempty"` // Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu @@ -23,8 +22,8 @@ type ProcessedMenuItem struct { Hidden bool `json:",omitempty"` // Checked indicates if the item is selected (used by Checkbox and Radio types only) Checked bool `json:",omitempty"` - // SubMenu contains a list of menu items that will be shown as a submenu - // SubMenu []*MenuItem `json:"SubMenu,omitempty"` + // Submenu contains a list of menu items that will be shown as a submenu + //SubMenu []*MenuItem `json:"SubMenu,omitempty"` SubMenu *ProcessedMenu `json:",omitempty"` /* // Colour @@ -48,6 +47,7 @@ type ProcessedMenuItem struct { } func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem { + ID := menuItemMap.menuItemToIDMap[menuItem] // Parse ANSI text @@ -63,21 +63,21 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr result := &ProcessedMenuItem{ ID: ID, Label: menuItem.Label, - // Role: menuItem.Role, + //Role: menuItem.Role, Accelerator: menuItem.Accelerator, Type: menuItem.Type, Disabled: menuItem.Disabled, Hidden: menuItem.Hidden, Checked: menuItem.Checked, SubMenu: nil, - // BackgroundColour: menuItem.BackgroundColour, - // FontSize: menuItem.FontSize, - // FontName: menuItem.FontName, - // Image: menuItem.Image, - // MacTemplateImage: menuItem.MacTemplateImage, - // MacAlternate: menuItem.MacAlternate, - // Tooltip: menuItem.Tooltip, - // StyledLabel: styledLabel, + //BackgroundColour: menuItem.BackgroundColour, + //FontSize: menuItem.FontSize, + //FontName: menuItem.FontName, + //Image: menuItem.Image, + //MacTemplateImage: menuItem.MacTemplateImage, + //MacAlternate: menuItem.MacAlternate, + //Tooltip: menuItem.Tooltip, + //StyledLabel: styledLabel, } if menuItem.SubMenu != nil { @@ -92,6 +92,7 @@ type ProcessedMenu struct { } func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu { + result := &ProcessedMenu{} if menu != nil { for _, item := range menu.Items { @@ -130,6 +131,7 @@ func NewWailsMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *WailsMenu { } func (w *WailsMenu) AsJSON() (string, error) { + menuAsJSON, err := json.Marshal(w) if err != nil { return "", err @@ -148,6 +150,7 @@ func (w *WailsMenu) processRadioGroups() { } func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) { + switch item.Type { // We need to recurse submenus @@ -169,6 +172,7 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) { } func (w *WailsMenu) finaliseRadioGroup() { + // If we were processing a radio group, fix up the references if len(w.currentRadioGroup) > 0 { diff --git a/v2/internal/menumanager/traymenu.go b/v2/internal/menumanager/traymenu.go index 5efc4a861..aed5b05ac 100644 --- a/v2/internal/menumanager/traymenu.go +++ b/v2/internal/menumanager/traymenu.go @@ -13,10 +13,8 @@ import ( "github.com/wailsapp/wails/v2/pkg/menu" ) -var ( - trayMenuID int - trayMenuIDMutex sync.Mutex -) +var trayMenuID int +var trayMenuIDMutex sync.Mutex func generateTrayID() string { var idStr string @@ -53,6 +51,7 @@ func (t *TrayMenu) AsJSON() (string, error) { } func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu { + // Parse ANSI text var styledLabel []*ansi.StyledText tempLabel := trayMenu.Label @@ -206,6 +205,7 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) { } return string(data), nil + } func (m *Manager) GetContextMenus() ([]string, error) { diff --git a/v2/internal/platform/win32/consts.go b/v2/internal/platform/win32/consts.go index 43149b036..03f42b1a6 100644 --- a/v2/internal/platform/win32/consts.go +++ b/v2/internal/platform/win32/consts.go @@ -80,7 +80,7 @@ ShouldSystemUseDarkMode = bool () // ordinal 138 SetPreferredAppMode = PreferredAppMode (PreferredAppMode appMode) // ordinal 135, since 18334 IsDarkModeAllowedForApp = bool () // ordinal 139 */ -func Init() { +func init() { if IsWindowsVersionAtLeast(10, 0, 18334) { // AllowDarkModeForWindow is only available on Windows 10+ diff --git a/v2/internal/process/process.go b/v2/internal/process/process.go index 18c9f45da..6d497ed8e 100644 --- a/v2/internal/process/process.go +++ b/v2/internal/process/process.go @@ -25,6 +25,7 @@ func NewProcess(cmd string, args ...string) *Process { // Start the process func (p *Process) Start(exitCodeChannel chan int) error { + err := p.cmd.Start() if err != nil { return err diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go index 2df99bdfa..023ca1dfe 100644 --- a/v2/internal/project/project.go +++ b/v2/internal/project/project.go @@ -12,6 +12,7 @@ import ( // Project holds the data related to a Wails project type Project struct { + /*** Application Data ***/ Name string `json:"name"` AssetDirectory string `json:"assetdir,omitempty"` @@ -42,9 +43,6 @@ type Project struct { // Build directory BuildDir string `json:"build:dir"` - // BuildTags Extra tags to process during build - BuildTags string `json:"build:tags"` - // The output filename OutputFilename string `json:"outputfilename"` @@ -83,7 +81,7 @@ type Project struct { // The address to bind the wails dev server to. Default "localhost:34115" DevServer string `json:"devServer"` - // Arguments that are forward to the application in dev mode + // Arguments that are forwared to the application in dev mode AppArgs string `json:"appargs"` // NSISType to be build @@ -96,9 +94,6 @@ type Project struct { // Frontend directory FrontendDir string `json:"frontend:dir"` - // The timeout in seconds for Vite server detection. Default 10 - ViteServerTimeout int `json:"viteServerTimeout"` - Bindings Bindings `json:"bindings"` } @@ -149,10 +144,11 @@ func (p *Project) Save() error { if err != nil { return err } - return os.WriteFile(p.filename, data, 0o755) + return os.WriteFile(p.filename, data, 0755) } func (p *Project) setDefaults() { + if p.Path == "" { p.Path = lo.Must(os.Getwd()) } @@ -181,9 +177,6 @@ func (p *Project) setDefaults() { if p.DevServer == "" { p.DevServer = "localhost:34115" } - if p.ViteServerTimeout == 0 { - p.ViteServerTimeout = 10 - } if p.NSISType == "" { p.NSISType = "multiple" } @@ -223,27 +216,11 @@ type Author struct { } type Info struct { - CompanyName string `json:"companyName"` - ProductName string `json:"productName"` - ProductVersion string `json:"productVersion"` - Copyright *string `json:"copyright"` - Comments *string `json:"comments"` - FileAssociations []FileAssociation `json:"fileAssociations"` - Protocols []Protocol `json:"protocols"` -} - -type FileAssociation struct { - Ext string `json:"ext"` - Name string `json:"name"` - Description string `json:"description"` - IconName string `json:"iconName"` - Role string `json:"role"` -} - -type Protocol struct { - Scheme string `json:"scheme"` - Description string `json:"description"` - Role string `json:"role"` + CompanyName string `json:"companyName"` + ProductName string `json:"productName"` + ProductVersion string `json:"productVersion"` + Copyright *string `json:"copyright"` + Comments *string `json:"comments"` } type Bindings struct { @@ -251,9 +228,8 @@ type Bindings struct { } type TsGeneration struct { - Prefix string `json:"prefix"` - Suffix string `json:"suffix"` - OutputType string `json:"outputType"` + Prefix string `json:"prefix"` + Suffix string `json:"suffix"` } // Parse the given JSON data into a Project struct diff --git a/v2/internal/s/s.go b/v2/internal/s/s.go index adb304178..86536e24c 100644 --- a/v2/internal/s/s.go +++ b/v2/internal/s/s.go @@ -2,9 +2,9 @@ package s import ( "crypto/md5" - "encoding/hex" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -28,7 +28,7 @@ func checkError(err error) { func mute() { originalOutput = Output - Output = io.Discard + Output = ioutil.Discard } func unmute() { @@ -74,10 +74,9 @@ func CD(dir string) { checkError(err) log("CD %s [%s]", dir, CWD()) } - func MKDIR(path string, mode ...os.FileMode) { var perms os.FileMode - perms = 0o755 + perms = 0755 if len(mode) == 1 { perms = mode[0] } @@ -89,7 +88,7 @@ func MKDIR(path string, mode ...os.FileMode) { // ENDIR ensures that the path gets created if it doesn't exist func ENDIR(path string, mode ...os.FileMode) { var perms os.FileMode - perms = 0o755 + perms = 0755 if len(mode) == 1 { perms = mode[0] } @@ -151,7 +150,6 @@ func COPY(source string, target string) { defer closefile(src) d, err := os.Create(target) checkError(err) - defer closefile(d) _, err = io.Copy(d, src) checkError(err) } @@ -212,13 +210,17 @@ func ISDIR(path string) bool { // 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) - return err == io.EOF + if err == io.EOF { + return true + } + return false } // ISFILE returns true if the given file exists @@ -268,7 +270,7 @@ func LOADSTRING(filename string) string { // SAVEBYTES will create a file with the given string func SAVEBYTES(filename string, data []byte) { log("SAVEBYTES %s", filename) - err := os.WriteFile(filename, data, 0o755) + err := os.WriteFile(filename, data, 0755) checkError(err) } @@ -295,7 +297,7 @@ func MD5FILE(filename string) string { _, err = io.Copy(h, f) checkError(err) - return hex.EncodeToString(h.Sum(nil)) + return fmt.Sprintf("%x", h.Sum(nil)) } // Sub is the substitution type diff --git a/v2/internal/shell/shell.go b/v2/internal/shell/shell.go index 349e27bff..e16b96ca4 100644 --- a/v2/internal/shell/shell.go +++ b/v2/internal/shell/shell.go @@ -42,13 +42,14 @@ func (c *Command) Run() error { func (c *Command) Stdout() string { return c.stdo.String() } - func (c *Command) Stderr() string { return c.stde.String() } func (c *Command) AddArgs(args []string) { - c.args = append(c.args, args...) + for _, arg := range args { + c.args = append(c.args, arg) + } } // CreateCommand returns a *Cmd struct that when run, will run the given command + args in the given directory @@ -94,5 +95,8 @@ func RunCommandVerbose(directory string, command string, args ...string) error { // CommandExists returns true if the given command can be found on the shell func CommandExists(name string) bool { _, err := exec.LookPath(name) - return err == nil + if err != nil { + return false + } + return true } diff --git a/v2/internal/signal/signal.go b/v2/internal/signal/signal.go index fa797453f..96e10bee6 100644 --- a/v2/internal/signal/signal.go +++ b/v2/internal/signal/signal.go @@ -9,10 +9,8 @@ import ( var signalChannel = make(chan os.Signal, 2) -var ( - callbacks []func() - lock sync.Mutex -) +var callbacks []func() +var lock sync.Mutex func OnShutdown(callback func()) { lock.Lock() @@ -22,17 +20,20 @@ func OnShutdown(callback func()) { // Start the Signal Manager func Start() { + // Hook into interrupts gosignal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) // Spin off signal listener and wait for either a cancellation // or signal go func() { - <-signalChannel - println("") - println("Ctrl+C detected. Shutting down...") - for _, callback := range callbacks { - callback() + select { + case <-signalChannel: + println("") + println("Ctrl+C detected. Shutting down...") + for _, callback := range callbacks { + callback() + } } }() } diff --git a/v2/internal/staticanalysis/staticanalysis.go b/v2/internal/staticanalysis/staticanalysis.go index cde436633..0d8fb92d3 100644 --- a/v2/internal/staticanalysis/staticanalysis.go +++ b/v2/internal/staticanalysis/staticanalysis.go @@ -2,10 +2,9 @@ package staticanalysis import ( "go/ast" + "golang.org/x/tools/go/packages" "path/filepath" "strings" - - "golang.org/x/tools/go/packages" ) type EmbedDetails struct { @@ -52,31 +51,24 @@ func GetEmbedDetailsForFile(file *ast.File, baseDir string) []*EmbedDetails { for _, c := range comment.List { if strings.HasPrefix(c.Text, "//go:embed") { sl := strings.Split(c.Text, " ") + path := "" + all := false if len(sl) == 1 { continue } - // support for multiple paths in one comment - for _, arg := range sl[1:] { - embedPath := strings.TrimSpace(arg) - // ignores all pattern matching characters except escape sequence - if strings.Contains(embedPath, "*") || strings.Contains(embedPath, "?") || strings.Contains(embedPath, "[") { - continue - } - if strings.HasPrefix(embedPath, "all:") { - result = append(result, &EmbedDetails{ - EmbedPath: strings.TrimPrefix(embedPath, "all:"), - All: true, - BaseDir: baseDir, - }) - } else { - result = append(result, &EmbedDetails{ - EmbedPath: embedPath, - All: false, - BaseDir: baseDir, - }) - } - + embedPath := strings.TrimSpace(sl[1]) + switch true { + case strings.HasPrefix(embedPath, "all:"): + path = strings.TrimPrefix(embedPath, "all:") + all = true + default: + path = embedPath } + result = append(result, &EmbedDetails{ + EmbedPath: path, + All: all, + BaseDir: baseDir, + }) } } } diff --git a/v2/internal/staticanalysis/staticanalysis_test.go b/v2/internal/staticanalysis/staticanalysis_test.go index 77ad2fa6c..17599676e 100644 --- a/v2/internal/staticanalysis/staticanalysis_test.go +++ b/v2/internal/staticanalysis/staticanalysis_test.go @@ -1,9 +1,8 @@ package staticanalysis import ( - "testing" - "github.com/stretchr/testify/require" + "testing" ) func TestGetEmbedDetails(t *testing.T) { @@ -26,10 +25,6 @@ func TestGetEmbedDetails(t *testing.T) { EmbedPath: "frontend/dist", All: true, }, - { - EmbedPath: "frontend/static", - All: false, - }, }, wantErr: false, }, diff --git a/v2/internal/staticanalysis/test/standard/README.md b/v2/internal/staticanalysis/test/standard/README.md index 397b08b92..d91813e5f 100644 --- a/v2/internal/staticanalysis/test/standard/README.md +++ b/v2/internal/staticanalysis/test/standard/README.md @@ -4,15 +4,17 @@ This is the official Wails Vanilla template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/internal/staticanalysis/test/standard/go.mod b/v2/internal/staticanalysis/test/standard/go.mod index c9fe1fb52..56184c62d 100644 --- a/v2/internal/staticanalysis/test/standard/go.mod +++ b/v2/internal/staticanalysis/test/standard/go.mod @@ -25,11 +25,11 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect ) // replace github.com/wailsapp/wails/v2 v2.0.0 => C:\Users\leaan diff --git a/v2/internal/staticanalysis/test/standard/go.sum b/v2/internal/staticanalysis/test/standard/go.sum index 2cd0cf773..54b3fbb03 100644 --- a/v2/internal/staticanalysis/test/standard/go.sum +++ b/v2/internal/staticanalysis/test/standard/go.sum @@ -57,13 +57,13 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw 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= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 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,12 +73,12 @@ 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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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/staticanalysis/test/standard/main.go b/v2/internal/staticanalysis/test/standard/main.go index 2b6ab33b6..3f735d640 100644 --- a/v2/internal/staticanalysis/test/standard/main.go +++ b/v2/internal/staticanalysis/test/standard/main.go @@ -8,12 +8,9 @@ import ( "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) -//go:embed all:frontend/dist frontend/static +//go:embed all:frontend/dist var assets embed.FS -//go:embed frontend/src/*.json -var srcjson embed.FS - func main() { // Create an instance of the app structure app := NewApp() diff --git a/v2/internal/system/operatingsystem/os.go b/v2/internal/system/operatingsystem/os.go index 028a97b2e..39f1de8e0 100644 --- a/v2/internal/system/operatingsystem/os.go +++ b/v2/internal/system/operatingsystem/os.go @@ -2,10 +2,9 @@ package operatingsystem // OS contains information about the operating system type OS struct { - ID string - Name string - Version string - Branding string + ID string + Name string + Version string } // Info retrieves information about the current platform diff --git a/v2/internal/system/operatingsystem/os_windows.go b/v2/internal/system/operatingsystem/os_windows.go index a9aa05a92..38ea43a12 100644 --- a/v2/internal/system/operatingsystem/os_windows.go +++ b/v2/internal/system/operatingsystem/os_windows.go @@ -4,44 +4,10 @@ package operatingsystem import ( "fmt" - "strings" - "syscall" - "unsafe" - "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) -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 { - panic(err) - } - return result -} - -func getBranding() string { - var modBranding = syscall.NewLazyDLL("winbrand.dll") - var brandingFormatString = modBranding.NewProc("BrandingFormatString") - - windowsLong := mustStringToUTF16Ptr("%WINDOWS_LONG%\x00") - ret, _, _ := brandingFormatString.Call( - uintptr(unsafe.Pointer(windowsLong)), - ) - return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(ret))) -} - func platformInfo() (*OS, error) { // Default value var result OS @@ -61,7 +27,6 @@ func platformInfo() (*OS, error) { result.Name = productName result.Version = fmt.Sprintf("%s (Build: %s)", releaseId, currentBuild) result.ID = displayVersion - result.Branding = getBranding() return &result, key.Close() } diff --git a/v2/internal/system/packagemanager/dnf.go b/v2/internal/system/packagemanager/dnf.go index fec676f11..d9eaf8062 100644 --- a/v2/internal/system/packagemanager/dnf.go +++ b/v2/internal/system/packagemanager/dnf.go @@ -44,7 +44,6 @@ func (y *Dnf) Packages() packagemap { }, "npm": []*Package{ {Name: "npm", SystemPackage: true}, - {Name: "nodejs-npm", SystemPackage: true}, }, "upx": []*Package{ {Name: "upx", SystemPackage: true, Optional: true}, @@ -58,7 +57,6 @@ func (y *Dnf) Packages() packagemap { "fedora": "Follow the guide: https://docs.docker.com/engine/install/fedora/", }, }, - {Name: "moby-engine", SystemPackage: true, Optional: true}, }, } } diff --git a/v2/internal/system/packagemanager/eopkg.go b/v2/internal/system/packagemanager/eopkg.go index 936127eac..dbeab96de 100644 --- a/v2/internal/system/packagemanager/eopkg.go +++ b/v2/internal/system/packagemanager/eopkg.go @@ -40,7 +40,7 @@ func (e *Eopkg) Packages() packagemap { {Name: "gcc", SystemPackage: true}, }, "pkg-config": []*Package{ - {Name: "pkgconf", SystemPackage: true}, + {Name: "pkg-config", SystemPackage: true}, }, "npm": []*Package{ {Name: "nodejs", SystemPackage: true}, diff --git a/v2/internal/system/packagemanager/pm.go b/v2/internal/system/packagemanager/pm.go index bba45cd05..dfd394299 100644 --- a/v2/internal/system/packagemanager/pm.go +++ b/v2/internal/system/packagemanager/pm.go @@ -16,9 +16,9 @@ type packagemap = map[string][]*Package type PackageManager interface { Name() string Packages() packagemap - PackageInstalled(pkg *Package) (bool, error) - PackageAvailable(pkg *Package) (bool, error) - InstallCommand(pkg *Package) string + PackageInstalled(*Package) (bool, error) + PackageAvailable(*Package) (bool, error) + InstallCommand(*Package) string } // Dependency represents a system package that we require @@ -37,6 +37,7 @@ 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 { @@ -49,6 +50,7 @@ func (d DependencyList) InstallAllRequiredCommand() string { // 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 { diff --git a/v2/internal/system/packagemanager/zypper.go b/v2/internal/system/packagemanager/zypper.go index efaeb0b1b..c486b53e1 100644 --- a/v2/internal/system/packagemanager/zypper.go +++ b/v2/internal/system/packagemanager/zypper.go @@ -45,7 +45,6 @@ func (z *Zypper) Packages() packagemap { }, "npm": []*Package{ {Name: "npm10", SystemPackage: true}, - {Name: "npm20", SystemPackage: true}, }, "docker": []*Package{ {Name: "docker", SystemPackage: true, Optional: true}, diff --git a/v2/internal/system/system.go b/v2/internal/system/system.go index 67453538f..a633989ef 100644 --- a/v2/internal/system/system.go +++ b/v2/internal/system/system.go @@ -9,10 +9,12 @@ import ( "github.com/wailsapp/wails/v2/internal/system/packagemanager" ) -var IsAppleSilicon bool +var ( + IsAppleSilicon bool +) // Info holds information about the current operating system, -// package manager and required dependencies +// package manager and required dependancies type Info struct { OS *operatingsystem.OS PM packagemanager.PackageManager @@ -21,7 +23,7 @@ type Info struct { // GetInfo scans the system for operating system details, // the system package manager and the status of required -// dependencies. +// dependancies. func GetInfo() (*Info, error) { var result Info err := result.discover() @@ -32,6 +34,7 @@ func GetInfo() (*Info, error) { } func checkNodejs() *packagemanager.Dependency { + // Check for Nodejs output, err := exec.Command("node", "-v").Output() installed := true @@ -55,6 +58,7 @@ func checkNodejs() *packagemanager.Dependency { } func checkNPM() *packagemanager.Dependency { + // Check for npm output, err := exec.Command("npm", "-version").Output() installed := true @@ -76,6 +80,7 @@ func checkNPM() *packagemanager.Dependency { } func checkUPX() *packagemanager.Dependency { + // Check for npm output, err := exec.Command("upx", "-V").Output() installed := true @@ -97,6 +102,7 @@ func checkUPX() *packagemanager.Dependency { } func checkNSIS() *packagemanager.Dependency { + // Check for nsis installer output, err := exec.Command("makensis", "-VERSION").Output() installed := true @@ -135,6 +141,7 @@ func checkLibrary(name string) func() *packagemanager.Dependency { } func checkDocker() *packagemanager.Dependency { + // Check for npm output, err := exec.Command("docker", "version").Output() installed := true diff --git a/v2/internal/system/system_windows.go b/v2/internal/system/system_windows.go index 40b8f0340..1ef076cd8 100644 --- a/v2/internal/system/system_windows.go +++ b/v2/internal/system/system_windows.go @@ -4,7 +4,7 @@ package system import ( - "github.com/wailsapp/go-webview2/webviewloader" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/webviewloader" "github.com/wailsapp/wails/v2/internal/system/operatingsystem" "github.com/wailsapp/wails/v2/internal/system/packagemanager" ) diff --git a/v2/internal/typescriptify/README.md b/v2/internal/typescriptify/README.md index b5c961835..7a0619c34 100644 --- a/v2/internal/typescriptify/README.md +++ b/v2/internal/typescriptify/README.md @@ -1,2 +1,2 @@ -Based on: https://github.com/tkrajina/typescriptify-golang-structs -License: LICENSE.txt \ No newline at end of file +Based on: https://github.com/tkrajina/typescriptify-golang-structs License: +LICENSE.txt diff --git a/v2/internal/typescriptify/typescriptify.go b/v2/internal/typescriptify/typescriptify.go index e732c5976..b14059a51 100644 --- a/v2/internal/typescriptify/typescriptify.go +++ b/v2/internal/typescriptify/typescriptify.go @@ -2,15 +2,13 @@ package typescriptify import ( "bufio" - "cmp" "fmt" - "io" + "io/ioutil" "log" "os" "path" "reflect" "regexp" - "slices" "strings" "time" @@ -26,7 +24,7 @@ const ( if (!a) { return a; } - if (a.slice && a.map) { + if (a.slice) { return (a as any[]).map(elem => this.convertValues(elem, classs)); } else if ("object" === typeof a) { if (asMap) { @@ -42,18 +40,6 @@ const ( jsVariableNameRegex = `^([A-Z]|[a-z]|\$|_)([A-Z]|[a-z]|[0-9]|\$|_)*$` ) -var jsVariableUnsafeChars = regexp.MustCompile(`[^A-Za-z0-9_]`) - -func nameTypeOf(typeOf reflect.Type) string { - tname := typeOf.Name() - gidx := strings.IndexRune(tname, '[') - if gidx > 0 { // its a generic type - rem := strings.SplitN(tname, "[", 2) - tname = rem[0] + "_" + jsVariableUnsafeChars.ReplaceAllLiteralString(rem[1], "_") - } - return tname -} - // TypeOptions overrides options set by `ts_*` tags. type TypeOptions struct { TSType string @@ -118,7 +104,6 @@ type TypeScriptify struct { Namespace string KnownStructs *slicer.StringSlicer - KnownEnums *slicer.StringSlicer } func New() *TypeScriptify { @@ -171,10 +156,10 @@ func (t *TypeScriptify) deepFields(typeOf reflect.Type) []reflect.StructField { kind := f.Type.Kind() isPointer := kind == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct if f.Anonymous && kind == reflect.Struct { - // fmt.Println(v.Interface()) + //fmt.Println(v.Interface()) fields = append(fields, t.deepFields(f.Type)...) } else if f.Anonymous && isPointer { - // fmt.Println(v.Interface()) + //fmt.Println(v.Interface()) fields = append(fields, t.deepFields(f.Type.Elem())...) } else { // Check we have a json tag @@ -275,34 +260,15 @@ func (t *TypeScriptify) AddType(typeOf reflect.Type) *TypeScriptify { func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.StructField) { keyType := field.Type.Key() valueType := field.Type.Elem() - valueTypeName := nameTypeOf(valueType) - valueTypeSuffix := "" - valueTypePrefix := "" - if valueType.Kind() == reflect.Ptr { - valueType = valueType.Elem() - valueTypeName = nameTypeOf(valueType) - } - if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice { - arrayDepth := 1 - for valueType.Elem().Kind() == reflect.Array || valueType.Elem().Kind() == reflect.Slice { - valueType = valueType.Elem() - arrayDepth++ - } - valueType = valueType.Elem() - valueTypeName = nameTypeOf(valueType) - valueTypeSuffix = strings.Repeat(">", arrayDepth) - valueTypePrefix = strings.Repeat("Array<", arrayDepth) - } - if valueType.Kind() == reflect.Ptr { - valueType = valueType.Elem() - valueTypeName = nameTypeOf(valueType) - } + valueTypeName := valueType.Name() if name, ok := t.types[valueType.Kind()]; ok { valueTypeName = name } - if valueType.Kind() == reflect.Map { - // TODO: support nested maps - valueTypeName = "any" // valueType.Elem().Name() + if valueType.Kind() == reflect.Array || valueType.Kind() == reflect.Slice { + valueTypeName = valueType.Elem().Name() + "[]" + } + if valueType.Kind() == reflect.Ptr { + valueTypeName = valueType.Elem().Name() } if valueType.Kind() == reflect.Struct && differentNamespaces(t.namespace, valueType) { valueTypeName = valueType.String() @@ -327,13 +293,11 @@ func (t *typeScriptClassBuilder) AddMapField(fieldName string, field reflect.Str fieldName = fmt.Sprintf(`"%s"?`, strippedFieldName) } } - t.fields = append(t.fields, fmt.Sprintf("%s%s: Record<%s, %s>;", t.indent, fieldName, keyTypeStr, valueTypePrefix+valueTypeName+valueTypeSuffix)) + t.fields = append(t.fields, fmt.Sprintf("%s%s: {[key: %s]: %s};", t.indent, fieldName, keyTypeStr, valueTypeName)) if valueType.Kind() == reflect.Struct { - t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = this.convertValues(source[\"%s\"], %s, true);", - t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypePrefix+valueTypeName+valueTypeSuffix+t.suffix)) + t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = this.convertValues(source[\"%s\"], %s, true);", t.indent, t.indent, dotField, strippedFieldName, t.prefix+valueTypeName+t.suffix)) } else { - t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", - t.indent, t.indent, dotField, strippedFieldName)) + t.constructorBody = append(t.constructorBody, fmt.Sprintf("%s%sthis%s = source[\"%s\"];", t.indent, t.indent, dotField, strippedFieldName)) } } @@ -374,9 +338,6 @@ func (t *TypeScriptify) AddEnum(values interface{}) *TypeScriptify { elements = append(elements, el) } - slices.SortFunc(elements, func(a, b enumElement) int { - return cmp.Compare(a.name, b.name) - }) ty := reflect.TypeOf(elements[0].value) t.enums[ty] = elements t.enumTypes = append(t.enumTypes, EnumType{Type: ty}) @@ -432,7 +393,7 @@ func loadCustomCode(fileName string) (map[string]string, error) { } defer f.Close() - bytes, err := io.ReadAll(f) + bytes, err := ioutil.ReadAll(f) if err != nil { return result, err } @@ -468,7 +429,7 @@ func (t TypeScriptify) backup(fileName string) error { } defer fileIn.Close() - bytes, err := io.ReadAll(fileIn) + bytes, err := ioutil.ReadAll(fileIn) if err != nil { return err } @@ -478,7 +439,7 @@ func (t TypeScriptify) backup(fileName string) error { backupFn = path.Join(t.BackupDir, backupFn) } - return os.WriteFile(backupFn, bytes, os.FileMode(0o700)) + return ioutil.WriteFile(backupFn, bytes, os.FileMode(0700)) } func (t TypeScriptify) ConvertToFile(fileName string, packageName string) error { @@ -521,6 +482,9 @@ func (t TypeScriptify) ConvertToFile(fileName string, packageName string) error if _, err := f.WriteString(converted); err != nil { return err } + if err != nil { + return err + } return nil } @@ -536,7 +500,7 @@ func (t *TypeScriptify) convertEnum(depth int, typeOf reflect.Type, elements []e } t.alreadyConverted[typeOf.String()] = true - entityName := t.Prefix + nameTypeOf(typeOf) + t.Suffix + entityName := t.Prefix + typeOf.Name() + t.Suffix result := "enum " + entityName + " {\n" for _, val := range elements { @@ -588,21 +552,7 @@ func (t *TypeScriptify) getFieldOptions(structType reflect.Type, field reflect.S func (t *TypeScriptify) getJSONFieldName(field reflect.StructField, isPtr bool) string { jsonFieldName := "" - // function, complex, and channel types cannot be json-encoded - if field.Type.Kind() == reflect.Chan || - field.Type.Kind() == reflect.Func || - field.Type.Kind() == reflect.UnsafePointer || - field.Type.Kind() == reflect.Complex128 || - field.Type.Kind() == reflect.Complex64 { - return "" - } - jsonTag, hasTag := field.Tag.Lookup("json") - if !hasTag && field.IsExported() { - jsonFieldName = field.Name - if isPtr { - jsonFieldName += "?" - } - } + jsonTag := field.Tag.Get("json") if len(jsonTag) > 0 { jsonTagParts := strings.Split(jsonTag, ",") if len(jsonTagParts) > 0 { @@ -635,6 +585,9 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m return "", nil } fields := t.deepFields(typeOf) + if len(fields) == 0 { + return "", nil + } t.logf(depth, "Converting type %s", typeOf.String()) if differentNamespaces(t.Namespace, typeOf) { return "", nil @@ -642,7 +595,7 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m t.alreadyConverted[typeOf.String()] = true - entityName := t.Prefix + nameTypeOf(typeOf) + t.Suffix + entityName := t.Prefix + typeOf.Name() + t.Suffix if typeClashWithReservedKeyword(entityName) { warnAboutTypesClash(entityName) @@ -702,10 +655,8 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m } isKnownType := t.KnownStructs.Contains(getStructFQN(field.Type.String())) - if !isKnownType { - println("KnownStructs:", t.KnownStructs.Join("\t")) - println("Not found:", getStructFQN(field.Type.String())) - } + println("KnownStructs:", t.KnownStructs.Join("\t")) + println(getStructFQN(field.Type.String())) builder.AddStructField(jsonFieldName, field, !isKnownType) } else if field.Type.Kind() == reflect.Map { t.logf(depth, "- map field %s.%s", typeOf.Name(), field.Name) @@ -746,20 +697,16 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m builder.AddMapField(jsonFieldName, field) } else if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { // Slice: - if field.Type.Elem().Kind() == reflect.Ptr { // extract ptr type + if field.Type.Elem().Kind() == reflect.Ptr { //extract ptr type field.Type = field.Type.Elem() } arrayDepth := 1 - for field.Type.Elem().Kind() == reflect.Slice || field.Type.Elem().Kind() == reflect.Array { // Slice of slices: + for field.Type.Elem().Kind() == reflect.Slice { // Slice of slices: field.Type = field.Type.Elem() arrayDepth++ } - if field.Type.Elem().Kind() == reflect.Ptr { // extract ptr type - field.Type = field.Type.Elem() - } - if field.Type.Elem().Kind() == reflect.Struct { // Slice of structs: t.logf(depth, "- struct slice %s.%s (%s)", typeOf.Name(), field.Name, field.Type.String()) typeScriptChunk, err := t.convertType(depth+1, field.Type.Elem(), customCode) @@ -776,16 +723,7 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m } } else { // Simple field: t.logf(depth, "- simple field %s.%s", typeOf.Name(), field.Name) - // check if type is in known enum. If so, then replace TStype with enum name to avoid missing types - isKnownEnum := t.KnownEnums.Contains(getStructFQN(field.Type.String())) - if isKnownEnum { - err = builder.AddSimpleField(jsonFieldName, field, TypeOptions{ - TSType: getStructFQN(field.Type.String()), - TSTransform: fldOpts.TSTransform, - }) - } else { - err = builder.AddSimpleField(jsonFieldName, field, fldOpts) - } + err = builder.AddSimpleField(jsonFieldName, field, fldOpts) } if err != nil { return "", err @@ -849,12 +787,8 @@ type typeScriptClassBuilder struct { } func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field reflect.StructField, arrayDepth int, opts TypeOptions) error { - fieldType := nameTypeOf(field.Type.Elem()) - kind := field.Type.Elem().Kind() - typeScriptType, ok := t.types[kind] - if !ok { - typeScriptType = "any" - } + fieldType, kind := field.Type.Elem().Name(), field.Type.Elem().Kind() + typeScriptType := t.types[kind] if len(fieldName) > 0 { strippedFieldName := strings.ReplaceAll(fieldName, "?", "") @@ -873,14 +807,9 @@ func (t *typeScriptClassBuilder) AddSimpleArrayField(fieldName string, field ref } func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect.StructField, opts TypeOptions) error { - fieldType := nameTypeOf(field.Type) - kind := field.Type.Kind() - - typeScriptType, ok := t.types[kind] - if !ok { - typeScriptType = "any" - } + fieldType, kind := field.Type.Name(), field.Type.Kind() + typeScriptType := t.types[kind] if len(opts.TSType) > 0 { typeScriptType = opts.TSType } @@ -902,7 +831,7 @@ func (t *typeScriptClassBuilder) AddSimpleField(fieldName string, field reflect. } func (t *typeScriptClassBuilder) AddEnumField(fieldName string, field reflect.StructField) { - fieldType := nameTypeOf(field.Type) + fieldType := field.Type.Name() t.addField(fieldName, t.prefix+fieldType+t.suffix, false) strippedFieldName := strings.ReplaceAll(fieldName, "?", "") t.addInitializerFieldLine(strippedFieldName, fmt.Sprintf("source[\"%s\"]", strippedFieldName)) @@ -912,7 +841,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect. strippedFieldName := strings.ReplaceAll(fieldName, "?", "") classname := "null" namespace := strings.Split(field.Type.String(), ".")[0] - fqname := t.prefix + nameTypeOf(field.Type) + t.suffix + fqname := t.prefix + field.Type.Name() + t.suffix if namespace != t.namespace { fqname = namespace + "." + fqname } @@ -931,7 +860,7 @@ func (t *typeScriptClassBuilder) AddStructField(fieldName string, field reflect. } func (t *typeScriptClassBuilder) AddArrayOfStructsField(fieldName string, field reflect.StructField, arrayDepth int) { - fieldType := nameTypeOf(field.Type.Elem()) + fieldType := field.Type.Elem().Name() if differentNamespaces(t.namespace, field.Type.Elem()) { fieldType = field.Type.Elem().String() } @@ -955,7 +884,7 @@ func (t *typeScriptClassBuilder) addField(fld, fldType string, isAnyType bool) { isOptional := strings.HasSuffix(fld, "?") strippedFieldName := strings.ReplaceAll(fld, "?", "") if !regexp.MustCompile(jsVariableNameRegex).Match([]byte(strippedFieldName)) { - fld = fmt.Sprintf(`"%s"`, strippedFieldName) + fld = fmt.Sprintf(`"%s"`, fld) if isOptional { fld += "?" } @@ -1006,6 +935,6 @@ func typeClashWithReservedKeyword(input string) bool { func warnAboutTypesClash(entity string) { // TODO: Refactor logging l := log.New(os.Stderr, "", 0) - l.Printf("Usage of reserved keyword found and not supported: %s", entity) + l.Println(fmt.Sprintf("Usage of reserved keyword found and not supported: %s", entity)) log.Println("Please rename returned type or consider adding bindings config to your wails.json") } diff --git a/v2/internal/webview2runtime/webview2installer.go b/v2/internal/webview2runtime/webview2installer.go index 3645dae02..a2a2922dc 100644 --- a/v2/internal/webview2runtime/webview2installer.go +++ b/v2/internal/webview2runtime/webview2installer.go @@ -11,7 +11,7 @@ var setupexe []byte // WriteInstallerToFile writes the installer file to the given file. func WriteInstallerToFile(targetFile string) error { - return os.WriteFile(targetFile, setupexe, 0o755) + return os.WriteFile(targetFile, setupexe, 0755) } // WriteInstaller writes the installer exe file to the given directory and returns the path to it. diff --git a/v2/internal/wv2installer/wv2installer.go b/v2/internal/wv2installer/wv2installer.go index c89ad196f..ce754cee7 100644 --- a/v2/internal/wv2installer/wv2installer.go +++ b/v2/internal/wv2installer/wv2installer.go @@ -5,7 +5,7 @@ package wv2installer import ( "fmt" - "github.com/wailsapp/go-webview2/webviewloader" + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/webviewloader" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/windows" ) diff --git a/v2/pkg/application/application.go b/v2/pkg/application/application.go index 8ba586969..8d8d72ef6 100644 --- a/v2/pkg/application/application.go +++ b/v2/pkg/application/application.go @@ -2,12 +2,11 @@ package application import ( "context" - "sync" - "github.com/wailsapp/wails/v2/internal/app" "github.com/wailsapp/wails/v2/internal/signal" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" + "sync" ) // Application is the main Wails application @@ -87,6 +86,7 @@ func (a *Application) Bind(boundStruct any) { } func (a *Application) On(eventType EventType, callback func()) { + c := func(ctx context.Context) { callback() } diff --git a/v2/pkg/assetserver/assethandler.go b/v2/pkg/assetserver/assethandler.go index b8e2df076..c85bf81e6 100644 --- a/v2/pkg/assetserver/assethandler.go +++ b/v2/pkg/assetserver/assethandler.go @@ -10,7 +10,6 @@ import ( "net/http" "os" "path" - "strconv" "strings" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -38,6 +37,7 @@ type assetHandler struct { } func NewAssetHandler(options assetserver.Options, log Logger) (http.Handler, error) { + vfs := options.Assets if vfs != nil { if _, err := vfs.Open("."); err != nil { @@ -110,7 +110,7 @@ func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } -// serveFSFile will try to load the file from the fs.FS and write it to the response +// serveFile will try to load the file from the fs.FS and write it to the response func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { if d.fs == nil { return os.ErrNotExist @@ -178,8 +178,7 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi return nil } - size := strconv.FormatInt(statInfo.Size(), 10) - rw.Header().Set(HeaderContentLength, size) + 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])) diff --git a/v2/pkg/assetserver/assethandler_external.go b/v2/pkg/assetserver/assethandler_external.go index 98b3404e9..588b350f5 100644 --- a/v2/pkg/assetserver/assethandler_external.go +++ b/v2/pkg/assetserver/assethandler_external.go @@ -1,21 +1,17 @@ +//go:build dev +// +build dev + package assetserver import ( "errors" "fmt" - "github.com/wailsapp/wails/v2/pkg/options/assetserver" "net/http" "net/http/httputil" "net/url" -) -func NewProxyServer(proxyURL string) http.Handler { - parsedURL, err := url.Parse(proxyURL) - if err != nil { - panic(err) - } - return httputil.NewSingleHostReverseProxy(parsedURL) -} + "github.com/wailsapp/wails/v2/pkg/options/assetserver" +) func NewExternalAssetsHandler(logger Logger, options assetserver.Options, url *url.URL) http.Handler { baseHandler := options.Handler @@ -50,7 +46,7 @@ func NewExternalAssetsHandler(logger Logger, options assetserver.Options, url *u proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) { if baseHandler != nil && errors.Is(err, errSkipProxy) { if logger != nil { - logger.Debug("[ExternalAssetHandler] '%s' returned not found, using AssetHandler", r.URL) + logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL) } baseHandler.ServeHTTP(rw, r) } else { diff --git a/v2/pkg/assetserver/assetserver.go b/v2/pkg/assetserver/assetserver.go index 59665c091..625c3f245 100644 --- a/v2/pkg/assetserver/assetserver.go +++ b/v2/pkg/assetserver/assetserver.go @@ -5,10 +5,10 @@ import ( "fmt" "math/rand" "net/http" + "net/http/httptest" "strings" "golang.org/x/net/html" - "html/template" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -68,11 +68,9 @@ func NewAssetServer(bindingsJSON string, options assetserver.Options, servingFro } func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) { - var buffer bytes.Buffer if bindingsJSON != "" { - escapedBindingsJSON := template.JSEscapeString(bindingsJSON) - buffer.WriteString(`window.wailsbindings='` + escapedBindingsJSON + `';` + "\n") + buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n") } buffer.Write(runtime.RuntimeDesktopJS()) @@ -113,58 +111,23 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } + header := rw.Header() if d.servingFromDisk { - rw.Header().Add(HeaderCacheControl, "no-cache") - } - - handler := d.handler - if req.Method != http.MethodGet { - handler.ServeHTTP(rw, req) - return + header.Add(HeaderCacheControl, "no-cache") } path := req.URL.Path - if path == runtimeJSPath { - d.writeBlob(rw, path, d.runtimeJS) - } else if path == runtimePath && d.runtimeHandler != nil { - d.runtimeHandler.HandleRuntimeCall(rw, req) - } else if path == ipcJSPath { - content := d.runtime.DesktopIPC() - if d.ipcJS != nil { - content = d.ipcJS(req) - } - d.writeBlob(rw, path, content) - - } else if script, ok := d.pluginScripts[path]; ok { - d.writeBlob(rw, path, []byte(script)) - } else if d.isRuntimeInjectionMatch(path) { - recorder := &bodyRecorder{ - ResponseWriter: rw, - doRecord: func(code int, h http.Header) bool { - if code == http.StatusNotFound { - return true - } - - if code != http.StatusOK { - return false - } - - return strings.Contains(h.Get(HeaderContentType), "text/html") - }, + switch path { + case "", "/", "/index.html": + recorder := httptest.NewRecorder() + d.handler.ServeHTTP(recorder, req) + for k, v := range recorder.HeaderMap { + header[k] = v } - handler.ServeHTTP(recorder, req) - - body := recorder.Body() - if body == nil { - // The body has been streamed and not recorded, we are finished - return - } - - code := recorder.Code() - switch code { + switch recorder.Code { case http.StatusOK: - content, err := d.processIndexHTML(body.Bytes()) + content, err := d.processIndexHTML(recorder.Body.Bytes()) if err != nil { d.serveError(rw, err, "Unable to processIndexHTML") return @@ -175,12 +138,34 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { d.writeBlob(rw, indexHTML, defaultHTML) default: - rw.WriteHeader(code) + rw.WriteHeader(recorder.Code) } - } else { - handler.ServeHTTP(rw, req) + case runtimeJSPath: + d.writeBlob(rw, path, d.runtimeJS) + + case runtimePath: + if d.runtimeHandler != nil { + d.runtimeHandler.HandleRuntimeCall(rw, req) + } else { + d.handler.ServeHTTP(rw, req) + } + + case ipcJSPath: + content := d.runtime.DesktopIPC() + if d.ipcJS != nil { + content = d.ipcJS(req) + } + d.writeBlob(rw, path, content) + + default: + // Check if this is a plugin script + if script, ok := d.pluginScripts[path]; ok { + d.writeBlob(rw, path, []byte(script)) + return + } + d.handler.ServeHTTP(rw, req) } } @@ -244,12 +229,3 @@ func (d *AssetServer) logError(message string, args ...interface{}) { d.logger.Error("[AssetServer] "+message, args...) } } - -func (AssetServer) isRuntimeInjectionMatch(path string) bool { - if path == "" { - path = "/" - } - - return strings.HasSuffix(path, "/") || - strings.HasSuffix(path, "/"+indexHTML) -} diff --git a/v2/pkg/assetserver/assetserver_legacy.go b/v2/pkg/assetserver/assetserver_legacy.go new file mode 100644 index 000000000..4df671bc2 --- /dev/null +++ b/v2/pkg/assetserver/assetserver_legacy.go @@ -0,0 +1,78 @@ +package assetserver + +import ( + "io" + "net/http" + + "github.com/wailsapp/wails/v2/pkg/assetserver/webview" +) + +// 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 (d *AssetServer) ProcessHTTPRequestLegacy(rw http.ResponseWriter, reqGetter func() (*http.Request, error)) { + d.processWebViewRequest(&legacyRequest{reqGetter: reqGetter, rw: rw}) +} + +type legacyRequest struct { + req *http.Request + rw http.ResponseWriter + + reqGetter func() (*http.Request, error) +} + +func (r *legacyRequest) URL() (string, error) { + req, err := r.request() + if err != nil { + return "", err + } + return req.URL.String(), nil +} + +func (r *legacyRequest) Method() (string, error) { + req, err := r.request() + if err != nil { + return "", err + } + return req.Method, nil +} + +func (r *legacyRequest) Header() (http.Header, error) { + req, err := r.request() + if err != nil { + return nil, err + } + return req.Header, nil +} + +func (r *legacyRequest) Body() (io.ReadCloser, error) { + req, err := r.request() + if err != nil { + return nil, err + } + return req.Body, nil +} + +func (r legacyRequest) Response() webview.ResponseWriter { + return &legacyRequestNoOpCloserResponseWriter{r.rw} +} + +func (r legacyRequest) Close() error { return nil } + +func (r *legacyRequest) request() (*http.Request, error) { + if r.req != nil { + return r.req, nil + } + + req, err := r.reqGetter() + if err != nil { + return nil, err + } + r.req = req + return req, nil +} + +type legacyRequestNoOpCloserResponseWriter struct { + http.ResponseWriter +} + +func (*legacyRequestNoOpCloserResponseWriter) Finish() {} diff --git a/v2/pkg/assetserver/assetserver_webview.go b/v2/pkg/assetserver/assetserver_webview.go index 63f80f0ae..ae85f2513 100644 --- a/v2/pkg/assetserver/assetserver_webview.go +++ b/v2/pkg/assetserver/assetserver_webview.go @@ -26,15 +26,19 @@ type assetServerWebView struct { func (d *AssetServer) ServeWebViewRequest(req webview.Request) { d.dispatchInit.Do(func() { workers := d.dispatchWorkers - if workers <= 0 { - return + if workers == 0 { + workers = 10 } workerC := make(chan webview.Request, workers*2) for i := 0; i < workers; i++ { go func() { for req := range workerC { + uri, _ := req.URL() d.processWebViewRequest(req) + if err := req.Close(); err != nil { + d.logError("Unable to call close for request for uri '%s'", uri) + } } }() } @@ -45,38 +49,19 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) { d.dispatchReqC = dispatchC }) - if d.dispatchReqC == nil { - go d.processWebViewRequest(req) - } else { - d.dispatchReqC <- req - } + d.dispatchReqC <- req } -func (d *AssetServer) processWebViewRequest(r webview.Request) { - uri, _ := r.URL() - d.processWebViewRequestInternal(r) - if err := r.Close(); err != nil { - d.logError("Unable to call close for request for uri '%s'", uri) - } -} - -// processWebViewRequestInternal processes the HTTP Request by faking a golang HTTP Server. +// 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 (d *AssetServer) processWebViewRequestInternal(r webview.Request) { - uri := "unknown" - var err error - +func (d *AssetServer) processWebViewRequest(r webview.Request) { wrw := r.Response() - defer func() { - if err := wrw.Finish(); err != nil { - d.logError("Error finishing request '%s': %s", uri, err) - } - }() + defer wrw.Finish() var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status - uri, err = r.URL() + uri, err := r.URL() if err != nil { d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err) http.Error(rw, err.Error(), http.StatusInternalServerError) @@ -111,18 +96,6 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) { d.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 url := req.URL; req.RequestURI == "" && url != nil { - req.RequestURI = url.String() - } - req.Header = header if req.RemoteAddr == "" { @@ -130,11 +103,14 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) { req.RemoteAddr = "192.0.2.1:1234" } + if req.RequestURI == "" && req.URL != nil { + req.RequestURI = req.URL.String() + } + if req.ContentLength == 0 { - req.ContentLength = -1 + req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64) } else { - size := strconv.FormatInt(req.ContentLength, 10) - req.Header.Set(HeaderContentLength, size) + req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength)) } if host := req.Header.Get(HeaderHost); host != "" { diff --git a/v2/pkg/assetserver/body_recorder.go b/v2/pkg/assetserver/body_recorder.go deleted file mode 100644 index fa3bc1e7c..000000000 --- a/v2/pkg/assetserver/body_recorder.go +++ /dev/null @@ -1,61 +0,0 @@ -package assetserver - -import ( - "bytes" - "net/http" -) - -type bodyRecorder struct { - http.ResponseWriter - doRecord func(code int, header http.Header) bool - - body *bytes.Buffer - code int - wroteHeader bool -} - -func (rw *bodyRecorder) Write(buf []byte) (int, error) { - rw.writeHeader(buf, http.StatusOK) - if rw.body != nil { - return rw.body.Write(buf) - } - return rw.ResponseWriter.Write(buf) -} - -func (rw *bodyRecorder) WriteHeader(code int) { - rw.writeHeader(nil, code) -} - -func (rw *bodyRecorder) Code() int { - return rw.code -} - -func (rw *bodyRecorder) Body() *bytes.Buffer { - return rw.body -} - -func (rw *bodyRecorder) writeHeader(buf []byte, code int) { - if rw.wroteHeader { - return - } - - if rw.doRecord != nil { - header := rw.Header() - if len(buf) != 0 { - if _, hasType := header[HeaderContentType]; !hasType { - header.Set(HeaderContentType, http.DetectContentType(buf)) - } - } - - if rw.doRecord(code, header) { - rw.body = bytes.NewBuffer(nil) - } - } - - if rw.body == nil { - rw.ResponseWriter.WriteHeader(code) - } - - rw.code = code - rw.wroteHeader = true -} diff --git a/v2/pkg/assetserver/common.go b/v2/pkg/assetserver/common.go index 57934e08e..01e51f2be 100644 --- a/v2/pkg/assetserver/common.go +++ b/v2/pkg/assetserver/common.go @@ -3,9 +3,9 @@ package assetserver import ( "bytes" "errors" + "fmt" "io" "net/http" - "strconv" "strings" "github.com/wailsapp/wails/v2/pkg/options" @@ -44,7 +44,7 @@ const ( func serveFile(rw http.ResponseWriter, filename string, blob []byte) error { header := rw.Header() - header.Set(HeaderContentLength, strconv.Itoa(len(blob))) + header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) if mimeType := header.Get(HeaderContentType); mimeType == "" { mimeType = GetMimetype(filename, blob) header.Set(HeaderContentType, mimeType) diff --git a/v2/pkg/assetserver/webview/request_darwin.go b/v2/pkg/assetserver/webview/request_darwin.go index c44e5f196..f0e85780b 100644 --- a/v2/pkg/assetserver/webview/request_darwin.go +++ b/v2/pkg/assetserver/webview/request_darwin.go @@ -197,10 +197,7 @@ func (r *request) Close() error { if r.body != nil { err = r.body.Close() } - err = r.Response().Finish() - if err != nil { - return err - } + r.Response().Finish() C.URLSchemeTaskRelease(r.task) return err } diff --git a/v2/pkg/assetserver/webview/request_linux.go b/v2/pkg/assetserver/webview/request_linux.go index c6785fb1c..101ee12fb 100644 --- a/v2/pkg/assetserver/webview/request_linux.go +++ b/v2/pkg/assetserver/webview/request_linux.go @@ -4,9 +4,7 @@ package webview /* -#cgo linux pkg-config: gtk+-3.0 gio-unix-2.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 #include "gtk/gtk.h" #include "webkit2/webkit2.h" diff --git a/v2/pkg/assetserver/webview/request_windows.go b/v2/pkg/assetserver/webview/request_windows.go deleted file mode 100644 index fa83cd8d7..000000000 --- a/v2/pkg/assetserver/webview/request_windows.go +++ /dev/null @@ -1,217 +0,0 @@ -//go:build windows -// +build windows - -package webview - -import ( - "fmt" - "io" - "net/http" - "strings" - - "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 { - // TODO use Go1.20 errors.Join - if len(errs) == 0 { - return nil - } - - errStrings := make([]string, len(errs)) - for i, err := range errs { - errStrings[i] = err.Error() - } - - return fmt.Errorf(strings.Join(errStrings, "\n")) -} diff --git a/v2/pkg/assetserver/webview/responsewriter.go b/v2/pkg/assetserver/webview/responsewriter.go index dacbb567d..d67802a05 100644 --- a/v2/pkg/assetserver/webview/responsewriter.go +++ b/v2/pkg/assetserver/webview/responsewriter.go @@ -21,5 +21,5 @@ 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 + Finish() } diff --git a/v2/pkg/assetserver/webview/responsewriter_darwin.go b/v2/pkg/assetserver/webview/responsewriter_darwin.go index a3c73b6f1..1c0cbee72 100644 --- a/v2/pkg/assetserver/webview/responsewriter_darwin.go +++ b/v2/pkg/assetserver/webview/responsewriter_darwin.go @@ -69,7 +69,6 @@ import "C" import ( "encoding/json" - "fmt" "net/http" "unsafe" ) @@ -99,31 +98,16 @@ func (rw *responseWriter) Write(buf []byte) (int, error) { rw.WriteHeader(http.StatusOK) + var content unsafe.Pointer var contentLen int if buf != nil { + content = unsafe.Pointer(&buf[0]) contentLen = len(buf) } - if contentLen > 0 { - // Create a C array to hold the data - cBuf := C.malloc(C.size_t(contentLen)) - if cBuf == nil { - return 0, fmt.Errorf("memory allocation failed for %d bytes", contentLen) - } - defer C.free(cBuf) - - // Copy the Go slice to the C array - C.memcpy(cBuf, unsafe.Pointer(&buf[0]), C.size_t(contentLen)) - - if !C.URLSchemeTaskDidReceiveData(rw.r.task, cBuf, C.int(contentLen)) { - return 0, errRequestStopped - } - } else { - if !C.URLSchemeTaskDidReceiveData(rw.r.task, nil, 0) { - return 0, errRequestStopped - } + if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) { + return 0, errRequestStopped } - return contentLen, nil } @@ -149,16 +133,15 @@ func (rw *responseWriter) WriteHeader(code int) { C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen)) } -func (rw *responseWriter) Finish() error { +func (rw *responseWriter) Finish() { if !rw.wroteHeader { rw.WriteHeader(http.StatusNotImplemented) } if rw.finished { - return nil + return } rw.finished = true C.URLSchemeTaskDidFinish(rw.r.task) - return nil } diff --git a/v2/pkg/assetserver/webview/responsewriter_linux.go b/v2/pkg/assetserver/webview/responsewriter_linux.go index 59646ce29..9b3f53a78 100644 --- a/v2/pkg/assetserver/webview/responsewriter_linux.go +++ b/v2/pkg/assetserver/webview/responsewriter_linux.go @@ -4,9 +4,7 @@ package webview /* -#cgo linux pkg-config: gtk+-3.0 gio-unix-2.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 #include "gtk/gtk.h" #include "webkit2/webkit2.h" @@ -86,19 +84,18 @@ func (rw *responseWriter) WriteHeader(code int) { } } -func (rw *responseWriter) Finish() error { +func (rw *responseWriter) Finish() { if !rw.wroteHeader { rw.WriteHeader(http.StatusNotImplemented) } if rw.finished { - return nil + return } rw.finished = true if rw.w != nil { rw.w.Close() } - return nil } func (rw *responseWriter) finishWithError(code int, err error) { diff --git a/v2/pkg/assetserver/webview/responsewriter_windows.go b/v2/pkg/assetserver/webview/responsewriter_windows.go deleted file mode 100644 index 748d9511b..000000000 --- a/v2/pkg/assetserver/webview/responsewriter_windows.go +++ /dev/null @@ -1,105 +0,0 @@ -//go:build windows -// +build windows - -package webview - -import ( - "bytes" - "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, fmt.Errorf("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) -} diff --git a/v2/pkg/assetserver/webview/webkit2_36+.go b/v2/pkg/assetserver/webview/webkit2_36+.go index 1f0db3c89..2c1a79c43 100644 --- a/v2/pkg/assetserver/webview/webkit2_36+.go +++ b/v2/pkg/assetserver/webview/webkit2_36+.go @@ -1,11 +1,9 @@ -//go:build linux && (webkit2_36 || webkit2_40 || webkit2_41 ) +//go:build linux && (webkit2_36 || webkit2_40) package webview /* -#cgo linux pkg-config: gtk+-3.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 libsoup-2.4 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 libsoup-3.0 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 libsoup-2.4 #include "gtk/gtk.h" #include "webkit2/webkit2.h" diff --git a/v2/pkg/assetserver/webview/webkit2_40+.go b/v2/pkg/assetserver/webview/webkit2_40+.go index eb3e439f2..dceb0803d 100644 --- a/v2/pkg/assetserver/webview/webkit2_40+.go +++ b/v2/pkg/assetserver/webview/webkit2_40+.go @@ -1,11 +1,9 @@ -//go:build linux && (webkit2_40 || webkit2_41) +//go:build linux && webkit2_40 package webview /* -#cgo linux pkg-config: gtk+-3.0 gio-unix-2.0 -#cgo !webkit2_41 pkg-config: webkit2gtk-4.0 -#cgo webkit2_41 pkg-config: webkit2gtk-4.1 +#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0 #include "gtk/gtk.h" #include "webkit2/webkit2.h" diff --git a/v2/pkg/assetserver/webview/webkit2_41.go b/v2/pkg/assetserver/webview/webkit2_41.go deleted file mode 100644 index 82f948d06..000000000 --- a/v2/pkg/assetserver/webview/webkit2_41.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build linux && webkit2_41 - -package webview - -const Webkit2MinMinorVersion = 41 diff --git a/v2/pkg/assetserver/webview/webkit2_legacy.go b/v2/pkg/assetserver/webview/webkit2_legacy.go index 1d1cf7c2b..1a87fe96a 100644 --- a/v2/pkg/assetserver/webview/webkit2_legacy.go +++ b/v2/pkg/assetserver/webview/webkit2_legacy.go @@ -1,4 +1,4 @@ -//go:build linux && !(webkit2_36 || webkit2_40 || webkit2_41) +//go:build linux && !(webkit2_36 || webkit2_40) package webview diff --git a/v2/pkg/buildassets/build/README.md b/v2/pkg/buildassets/build/README.md index 1ae2f677f..fc547b771 100644 --- a/v2/pkg/buildassets/build/README.md +++ b/v2/pkg/buildassets/build/README.md @@ -1,35 +1,41 @@ # Build Directory -The build directory is used to house all the build files and assets for your application. +The build directory is used to house all the build files and assets for your +application. The structure is: -* bin - Output directory -* darwin - macOS specific files -* windows - Windows specific files +- bin - Output directory +- darwin - macOS specific files +- windows - Windows specific files ## Mac -The `darwin` directory holds files specific to Mac builds. -These may be customised and used as part of the build. To return these files to the default state, simply delete them -and -build with `wails build`. +The `darwin` directory holds files specific to Mac builds. These may be +customised and used as part of the build. To return these files to the default +state, simply delete them and build with `wails build`. The directory contains the following files: -- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. -- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. +- `Info.plist` - the main plist file used for Mac builds. It is used when + building using `wails build`. +- `Info.dev.plist` - same as the main plist file but used when building using + `wails dev`. ## Windows -The `windows` directory contains the manifest and rc files used when building with `wails build`. -These may be customised for your application. To return these files to the default state, simply delete them and -build with `wails build`. +The `windows` directory contains the manifest and rc files used when building +with `wails build`. These may be customised for your application. To return +these files to the default state, simply delete them and build with +`wails build`. -- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to - use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file - will be created using the `appicon.png` file in the build directory. -- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. -- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, - as well as the application itself (right click the exe -> properties -> details) -- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file +- `icon.ico` - The icon used for the application. This is used when building + using `wails build`. If you wish to use a different icon, simply replace this + file with your own. If it is missing, a new `icon.ico` file will be created + using the `appicon.png` file in the build directory. +- `installer/*` - The files used to create the Windows installer. These are used + when building using `wails build`. +- `info.json` - Application details used for Windows builds. The data here will + be used by the Windows installer, as well as the application itself (right + click the exe -> properties -> details) +- `wails.exe.manifest` - The main application manifest file. diff --git a/v2/pkg/buildassets/build/darwin/Info.dev.plist b/v2/pkg/buildassets/build/darwin/Info.dev.plist index 14121ef7c..02e7358ee 100644 --- a/v2/pkg/buildassets/build/darwin/Info.dev.plist +++ b/v2/pkg/buildassets/build/darwin/Info.dev.plist @@ -6,7 +6,7 @@ CFBundleName {{.Info.ProductName}} CFBundleExecutable - {{.OutputFilename}} + {{.Name}} CFBundleIdentifier com.wails.{{.Name}} CFBundleVersion @@ -23,46 +23,10 @@ true NSHumanReadableCopyright {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} NSAppTransportSecurity NSAllowsLocalNetworking - + \ No newline at end of file diff --git a/v2/pkg/buildassets/build/darwin/Info.plist b/v2/pkg/buildassets/build/darwin/Info.plist index d17a7475c..e7819a7e8 100644 --- a/v2/pkg/buildassets/build/darwin/Info.plist +++ b/v2/pkg/buildassets/build/darwin/Info.plist @@ -6,7 +6,7 @@ CFBundleName {{.Info.ProductName}} CFBundleExecutable - {{.OutputFilename}} + {{.Name}} CFBundleIdentifier com.wails.{{.Name}} CFBundleVersion @@ -23,41 +23,5 @@ true NSHumanReadableCopyright {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} - + \ No newline at end of file diff --git a/v2/pkg/buildassets/build/windows/installer/project.nsi b/v2/pkg/buildassets/build/windows/installer/project.nsi index 654ae2e49..13cc4f023 100644 --- a/v2/pkg/buildassets/build/windows/installer/project.nsi +++ b/v2/pkg/buildassets/build/windows/installer/project.nsi @@ -3,10 +3,10 @@ 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 with the values from ProjectInfo. -## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually +## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows 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: @@ -17,7 +17,7 @@ Unicode true ## 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 ProjectInfo file, but they can be overwritten here. +## The following information is taken from the ProjectInfo file, but they can be overwritten here. #### ## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" ## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" @@ -85,19 +85,16 @@ Section !insertmacro wails.webview2runtime SetOutPath $INSTDIR - + !insertmacro wails.files CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" - !insertmacro wails.associateFiles - !insertmacro wails.associateCustomProtocols - !insertmacro wails.writeUninstaller SectionEnd -Section "uninstall" +Section "uninstall" !insertmacro wails.setShellContext RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath @@ -107,8 +104,5 @@ Section "uninstall" Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" - !insertmacro wails.unassociateFiles - !insertmacro wails.unassociateCustomProtocols - !insertmacro wails.deleteUninstaller SectionEnd diff --git a/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh b/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh index 2f6d32195..467c349ac 100644 --- a/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh +++ b/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh @@ -158,92 +158,22 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" ${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" + 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 "tmp\MicrosoftEdgeWebview2Setup.exe" ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' - + SetDetailsPrint both ok: -!macroend - -# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b -!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" - - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" - - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` - WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` -!macroend - -!macro APP_UNASSOCIATE EXT FILECLASS - ; Backup the previously associated file class - ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` - WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" - - DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` -!macroend - -!macro wails.associateFiles - ; Create file associations - {{range .Info.FileAssociations}} - !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - File "..\{{.IconName}}.ico" - {{end}} -!macroend - -!macro wails.unassociateFiles - ; Delete app associations - {{range .Info.FileAssociations}} - !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" - - Delete "$INSTDIR\{{.IconName}}.ico" - {{end}} -!macroend - -!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" - WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" -!macroend - -!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL - DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" -!macroend - -!macro wails.associateCustomProtocols - ; Create custom protocols associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" - - {{end}} -!macroend - -!macro wails.unassociateCustomProtocols - ; Delete app custom protocol associations - {{range .Info.Protocols}} - !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" - {{end}} -!macroend +!macroend \ No newline at end of file diff --git a/v2/pkg/buildassets/buildassets.go b/v2/pkg/buildassets/buildassets.go index 6934b98bd..26401745d 100644 --- a/v2/pkg/buildassets/buildassets.go +++ b/v2/pkg/buildassets/buildassets.go @@ -102,9 +102,8 @@ func ReadOriginalFileWithProjectDataAndSave(projectData *project.Project, file s } type assetData struct { - Name string - Info project.Info - OutputFilename string + Name string + Info project.Info } func resolveProjectData(content []byte, projectData *project.Project) ([]byte, error) { @@ -114,9 +113,8 @@ func resolveProjectData(content []byte, projectData *project.Project) ([]byte, e } data := &assetData{ - Name: projectData.Name, - Info: projectData.Info, - OutputFilename: projectData.OutputFilename, + Name: projectData.Name, + Info: projectData.Info, } var out bytes.Buffer @@ -130,12 +128,12 @@ func writeFileSystemFile(projectData *project.Project, file string, content []by targetPath := GetLocalPath(projectData, file) if dir := filepath.Dir(targetPath); !fs.DirExists(dir) { - if err := fs.MkDirs(dir, 0o755); err != nil { + if err := fs.MkDirs(dir, 0755); err != nil { return fmt.Errorf("Unable to create directory: %w", err) } } - if err := os.WriteFile(targetPath, content, 0o644); err != nil { + if err := os.WriteFile(targetPath, content, 0644); err != nil { return err } return nil diff --git a/v2/pkg/buildassets/onhold/dialog/README.md b/v2/pkg/buildassets/onhold/dialog/README.md index 3b9189a8f..5dd92ab88 100644 --- a/v2/pkg/buildassets/onhold/dialog/README.md +++ b/v2/pkg/buildassets/onhold/dialog/README.md @@ -3,27 +3,34 @@ NOTE: Currently, this is a Mac only feature. Place any PNG file in this directory to be able to use them in message dialogs. -The files should have names in the following format: `name[-(light|dark)][2x].png` +The files should have names in the following format: +`name[-(light|dark)][2x].png` Examples: -* `mypic.png` - Standard definition icon with ID `mypic` -* `mypic-light.png` - Standard definition icon with ID `mypic`, used when system theme is light -* `mypic-dark.png` - Standard definition icon with ID `mypic`, used when system theme is dark -* `mypic2x.png` - High definition icon with ID `mypic` -* `mypic-light2x.png` - High definition icon with ID `mypic`, used when system theme is light -* `mypic-dark2x.png` - High definition icon with ID `mypic`, used when system theme is dark +- `mypic.png` - Standard definition icon with ID `mypic` +- `mypic-light.png` - Standard definition icon with ID `mypic`, used when system + theme is light +- `mypic-dark.png` - Standard definition icon with ID `mypic`, used when system + theme is dark +- `mypic2x.png` - High definition icon with ID `mypic` +- `mypic-light2x.png` - High definition icon with ID `mypic`, used when system + theme is light +- `mypic-dark2x.png` - High definition icon with ID `mypic`, used when system + theme is dark ### Order of preference Icons are selected with the following order of preference: For High Definition displays: -* name-(theme)2x.png -* name2x.png -* name-(theme).png -* name.png + +- name-(theme)2x.png +- name2x.png +- name-(theme).png +- name.png For Standard Definition displays: -* name-(theme).png -* name.png \ No newline at end of file + +- name-(theme).png +- name.png diff --git a/v2/pkg/buildassets/onhold/tray/README.md b/v2/pkg/buildassets/onhold/tray/README.md index 5f4e7b4e6..3f83e75f4 100644 --- a/v2/pkg/buildassets/onhold/tray/README.md +++ b/v2/pkg/buildassets/onhold/tray/README.md @@ -1,8 +1,8 @@ ## Tray -Place any PNG file in this directory to be able to use them as tray icons. -The name of the filename will be the ID to reference the image. +Place any PNG file in this directory to be able to use them as tray icons. The +name of the filename will be the ID to reference the image. Example: -* `mypic.png` - May be referenced using `runtime.Tray.SetIcon("mypic")` +- `mypic.png` - May be referenced using `runtime.Tray.SetIcon("mypic")` diff --git a/v2/pkg/commands/bindings/bindings.go b/v2/pkg/commands/bindings/bindings.go index 82ce0d58f..71c1747b7 100644 --- a/v2/pkg/commands/bindings/bindings.go +++ b/v2/pkg/commands/bindings/bindings.go @@ -18,16 +18,15 @@ type Options struct { Filename string Tags []string ProjectDirectory string - Compiler string GoModTidy bool TsPrefix string TsSuffix string - TsOutputType string } // GenerateBindings generates bindings for the Wails project in the given ProjectDirectory. // If no project directory is given then the current working directory is used. func GenerateBindings(options Options) (string, error) { + filename, _ := lo.Coalesce(options.Filename, "wailsbindings") if runtime.GOOS == "windows" { filename += ".exe" @@ -47,32 +46,17 @@ func GenerateBindings(options Options) (string, error) { tagString := buildtags.Stringify(genModuleTags) if options.GoModTidy { - stdout, stderr, err = shell.RunCommand(workingDirectory, options.Compiler, "mod", "tidy") + stdout, stderr, err = shell.RunCommand(workingDirectory, "go", "mod", "tidy") if err != nil { return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) } } - envBuild := os.Environ() - envBuild = shell.SetEnv(envBuild, "GOOS", runtime.GOOS) - envBuild = shell.SetEnv(envBuild, "GOARCH", runtime.GOARCH) - // wailsbindings is executed on the build machine. - // So, use the default C compiler, not the one set for cross compiling. - envBuild = shell.RemoveEnv(envBuild, "CC") - - stdout, stderr, err = shell.RunCommandWithEnv(envBuild, workingDirectory, options.Compiler, "build", "-buildvcs=false", "-tags", tagString, "-o", filename) + stdout, stderr, err = shell.RunCommand(workingDirectory, "go", "build", "-tags", tagString, "-o", filename) if err != nil { return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) } - if runtime.GOOS == "darwin" { - // Remove quarantine attribute - stdout, stderr, err = shell.RunCommand(workingDirectory, "/usr/bin/xattr", "-rc", filename) - if err != nil { - return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) - } - } - defer func() { // Best effort removal of temp file _ = os.Remove(filename) @@ -82,7 +66,6 @@ func GenerateBindings(options Options) (string, error) { env := os.Environ() env = shell.SetEnv(env, "tsprefix", options.TsPrefix) env = shell.SetEnv(env, "tssuffix", options.TsSuffix) - env = shell.SetEnv(env, "tsoutputtype", options.TsOutputType) stdout, stderr, err = shell.RunCommandWithEnv(env, workingDirectory, filename) if err != nil { diff --git a/v2/pkg/commands/bindings/bindings_test.go b/v2/pkg/commands/bindings/bindings_test.go index 53f42f2c7..a2cbed436 100644 --- a/v2/pkg/commands/bindings/bindings_test.go +++ b/v2/pkg/commands/bindings/bindings_test.go @@ -1,14 +1,13 @@ package bindings import ( + "github.com/matryer/is" + "github.com/wailsapp/wails/v2/pkg/templates" "os" "path/filepath" "runtime" "strings" "testing" - - "github.com/matryer/is" - "github.com/wailsapp/wails/v2/pkg/templates" ) const standardBindings = `// @ts-check @@ -81,7 +80,6 @@ func TestGenerateBindings(t *testing.T) { name: "should generate standard bindings with no user tags", options: Options{ ProjectDirectory: projectDir, - Compiler: "go", GoModTidy: true, }, expectedBindings: standardBindings, @@ -92,7 +90,6 @@ func TestGenerateBindings(t *testing.T) { name: "should generate bindings when given tags", options: Options{ ProjectDirectory: projectDir, - Compiler: "go", Tags: []string{"test"}, GoModTidy: true, }, @@ -104,7 +101,6 @@ func TestGenerateBindings(t *testing.T) { name: "should generate obfuscated bindings", options: Options{ ProjectDirectory: projectDir, - Compiler: "go", Tags: []string{"obfuscated"}, GoModTidy: true, }, diff --git a/v2/pkg/commands/build/base.go b/v2/pkg/commands/build/base.go index 239932ce8..fbae6ce7e 100644 --- a/v2/pkg/commands/build/base.go +++ b/v2/pkg/commands/build/base.go @@ -74,6 +74,7 @@ func (b *BaseBuilder) convertFileToIntegerString(filename string) (string, error } func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string { + // Create string builder var result strings.Builder @@ -84,7 +85,8 @@ func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string { result.WriteString(fmt.Sprintf("%v,", data[i])) } - result.WriteString(strconv.FormatUint(uint64(data[len(data)-1]), 10)) + result.WriteString(fmt.Sprintf("%v", data[len(data)-1])) + } return result.String() @@ -92,8 +94,10 @@ func (b *BaseBuilder) convertByteSliceToIntegerString(data []byte) string { // CleanUp does post-build housekeeping func (b *BaseBuilder) CleanUp() { + // Delete all the files b.filesToDelete.Each(func(filename string) { + // if file doesn't exist, ignore if !b.fileExists(filename) { return @@ -102,6 +106,7 @@ func (b *BaseBuilder) CleanUp() { // Delete file. We ignore errors because these files will be overwritten // by the next build anyway. _ = os.Remove(filename) + }) } @@ -154,6 +159,7 @@ func (b *BaseBuilder) OutputFilename(options *Options) string { // CompileProject compiles the project func (b *BaseBuilder) CompileProject(options *Options) error { + // Check if the runtime wrapper exists err := generateRuntimeWrapper(options) if err != nil { @@ -193,8 +199,6 @@ func (b *BaseBuilder) CompileProject(options *Options) error { // Default go build command commands.Add("build") - commands.Add("-buildvcs=false") - // Add better debugging flags if options.Mode == Dev || options.Mode == Debug { commands.Add("-gcflags") @@ -230,11 +234,6 @@ func (b *BaseBuilder) CompileProject(options *Options) error { tags.Add("debug") } - // This options allows you to enable devtools in production build (not dev build as it's always enabled there) - if options.Devtools { - tags.Add("devtools") - } - if options.Obfuscated { tags.Add("obfuscated") } @@ -309,9 +308,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error { if v != "" { v += " " } - if !strings.Contains(v, "-mmacosx-version-min") { - v += "-mmacosx-version-min=10.13" - } + v += "-mmacosx-version-min=10.13" } return v }) @@ -348,9 +345,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error { if addUTIFramework { v += "-framework UniformTypeIdentifiers " } - if !strings.Contains(v, "-mmacosx-version-min") { - v += "-mmacosx-version-min=10.13" - } + v += "-mmacosx-version-min=10.13" return v }) @@ -402,7 +397,7 @@ Please reinstall by doing the following: return nil } - args := []string{"--best", "--no-color", "--no-progress", options.CompiledBinary} + var args = []string{"--best", "--no-color", "--no-progress", options.CompiledBinary} if options.CompressFlags != "" { args = strings.Split(options.CompressFlags, " ") @@ -426,6 +421,7 @@ Please reinstall by doing the following: } func generateRuntimeWrapper(options *Options) error { + if options.WailsJSDir == "" { cwd, err := os.Getwd() if err != nil { @@ -451,6 +447,7 @@ func (b *BaseBuilder) NpmInstall(sourceDir string, verbose bool) error { // NpmInstallUsingCommand runs the given install command in the specified npm project directory func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string, verbose bool) error { + packageJSON := filepath.Join(sourceDir, "package.json") // Check package.json exists @@ -490,7 +487,7 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st } // Shortcut installation - if !install { + if install == false { if verbose { pterm.Println("Skipping npm install") } @@ -547,6 +544,7 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb // BuildFrontend executes the `npm build` command for the frontend directory func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error { + verbose := b.options.Verbosity == VERBOSE frontendDir := b.projectData.GetFrontendDir() diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go index 491a57801..cad768fa2 100644 --- a/v2/pkg/commands/build/build.go +++ b/v2/pkg/commands/build/build.go @@ -3,12 +3,10 @@ package build import ( "fmt" "os" - "os/exec" "path/filepath" "runtime" "strings" - "github.com/google/shlex" "github.com/pterm/pterm" "github.com/samber/lo" @@ -42,7 +40,6 @@ type Options struct { Logger *clilogger.CLILogger // All output to the logger OutputType string // EG: desktop, server.... Mode Mode // release or dev - Devtools bool // Enable devtools in production ProjectData *project.Project // The project data Pack bool // Create a package for the app after building Platform string // The platform to build for @@ -70,11 +67,11 @@ type Options struct { Obfuscated bool // Indicates that bound methods should be obfuscated GarbleArgs string // The arguments for Garble SkipBindings bool // Skip binding generation - SkipEmbedCreate bool // Skip creation of embed files } // Build the project! func Build(options *Options) (string, error) { + // Extract logger outputLogger := options.Logger @@ -122,10 +119,8 @@ func Build(options *Options) (string, error) { } // Create embed directories if they don't exist - if !options.SkipEmbedCreate { - if err := CreateEmbedDirectories(cwd, options); err != nil { - return "", err - } + if err := CreateEmbedDirectories(cwd, options); err != nil { + return "", err } // Generate bindings @@ -149,15 +144,15 @@ func Build(options *Options) (string, error) { if err != nil { return "", err } - - hookArgs["${bin}"] = compileBinary - for _, hook := range []string{options.Platform + "/" + options.Arch, options.Platform + "/*", "*/*"} { - if err := execPostBuildHook(outputLogger, options, hook, hookArgs); err != nil { - return "", err - } - } - } + + hookArgs["${bin}"] = compileBinary + for _, hook := range []string{options.Platform + "/" + options.Arch, options.Platform + "/*", "*/*"} { + if err := execPostBuildHook(outputLogger, options, hook, hookArgs); err != nil { + return "", err + } + } + return compileBinary, nil } @@ -173,23 +168,21 @@ func CreateEmbedDirectories(cwd string, buildOptions *Options) error { for _, embedDetail := range embedDetails { fullPath := embedDetail.GetFullPath() - // assumes path is directory only if it has no extension - if filepath.Ext(fullPath) == "" { - if _, err := os.Stat(fullPath); os.IsNotExist(err) { - err := os.MkdirAll(fullPath, 0o755) - if err != nil { - return err - } - f, err := os.Create(filepath.Join(fullPath, "gitkeep")) - if err != nil { - return err - } - _ = f.Close() + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + err := os.MkdirAll(fullPath, 0755) + if err != nil { + return err } + f, err := os.Create(filepath.Join(fullPath, "gitkeep")) + if err != nil { + return err + } + _ = f.Close() } } return nil + } func fatal(message string) { @@ -214,10 +207,11 @@ func printBulletPoint(text string, args ...any) { fatal(err.Error()) } t = strings.Trim(t, "\n\r") - pterm.Printf(t, args...) + pterm.Printfln(t, args...) } func GenerateBindings(buildOptions *Options) error { + obfuscated := buildOptions.Obfuscated if obfuscated { printBulletPoint("Generating obfuscated bindings: ") @@ -226,18 +220,12 @@ func GenerateBindings(buildOptions *Options) error { printBulletPoint("Generating bindings: ") } - if buildOptions.ProjectData.Bindings.TsGeneration.OutputType == "" { - buildOptions.ProjectData.Bindings.TsGeneration.OutputType = "classes" - } - // Generate Bindings output, err := bindings.GenerateBindings(bindings.Options{ - Compiler: buildOptions.Compiler, - Tags: buildOptions.UserTags, - GoModTidy: !buildOptions.SkipModTidy, - TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix, - TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix, - TsOutputType: buildOptions.ProjectData.Bindings.TsGeneration.OutputType, + Tags: buildOptions.UserTags, + GoModTidy: !buildOptions.SkipModTidy, + TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix, + TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix, }) if err != nil { return err @@ -265,7 +253,7 @@ func execBuildApplication(builder Builder, options *Options) (string, error) { // When we finish, we will want to remove the syso file defer func() { - err := os.Remove(filepath.Join(options.ProjectData.Path, strings.ReplaceAll(options.ProjectData.Name, " ", "_")+"-res.syso")) + err := os.Remove(filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso")) if err != nil { fatal(err.Error()) } @@ -329,20 +317,6 @@ func execBuildApplication(builder Builder, options *Options) (string, error) { } } - if runtime.GOOS == "darwin" { - // Remove quarantine attribute - if _, err := os.Stat(options.CompiledBinary); os.IsNotExist(err) { - return "", fmt.Errorf("compiled binary does not exist at path: %s", options.CompiledBinary) - } - stdout, stderr, err := shell.RunCommand(options.BinDirectory, "/usr/bin/xattr", "-rc", options.CompiledBinary) - if err != nil { - return "", fmt.Errorf("%s - %s", err.Error(), stderr) - } - if options.Verbosity == VERBOSE && stdout != "" { - pterm.Info.Println(stdout) - } - } - pterm.Println("Done.") // Do we need to pack the app for non-windows? @@ -358,16 +332,6 @@ func execBuildApplication(builder Builder, options *Options) (string, error) { pterm.Println("Done.") } - if runtime.GOOS == "darwin" && options.Platform == "darwin" { - // On macOS, self-sign the .app bundle so notifications work - printBulletPoint("Self-signing application: ") - cmd := exec.Command("/usr/bin/codesign", "--force", "--deep", "--sign", "-", options.CompiledBinary) - if out, err := cmd.CombinedOutput(); err != nil { - return "", fmt.Errorf("codesign failed: %v – %s", err, out) - } - pterm.Println("Done.") - } - if options.Platform == "windows" { const nativeWebView2Loader = "native_webview2loader" @@ -382,8 +346,8 @@ func execBuildApplication(builder Builder, options *Options) (string, error) { } } - if options.Platform == "darwin" && (options.Mode == Debug || options.Devtools) { - pterm.Warning.Println("This darwin build contains the use of private APIs. This will not pass Apple's AppStore approval process. Please use it only as a test build for testing and debug purposes.") + if options.Platform == "darwin" && options.Mode == Debug { + pterm.Warning.Println("A darwin debug build contains private APIs, please don't distribute this build. Please use it only as a test build for testing and debug purposes.") } return options.CompiledBinary, nil @@ -405,9 +369,10 @@ func execPostBuildHook(outputLogger *clilogger.CLILogger, options *Options, hook } return executeBuildHook(outputLogger, options, hookIdentifier, argReplacements, postBuildHook, "post") + } -func executeBuildHook(_ *clilogger.CLILogger, options *Options, hookIdentifier string, argReplacements map[string]string, buildHook string, hookName string) error { +func executeBuildHook(outputLogger *clilogger.CLILogger, options *Options, hookIdentifier string, argReplacements map[string]string, buildHook string, hookName string) error { if !options.ProjectData.RunNonNativeBuildHooks { if hookIdentifier == "" { // That's the global hook @@ -426,10 +391,7 @@ func executeBuildHook(_ *clilogger.CLILogger, options *Options, hookIdentifier s } printBulletPoint("Executing %s build hook '%s': ", hookName, hookIdentifier) - args, err := shlex.Split(buildHook) - if err != nil { - return fmt.Errorf("could not parse %s build hook command: %w", hookName, err) - } + args := strings.Split(buildHook, " ") for i, arg := range args { newArg := argReplacements[arg] if newArg == "" { @@ -440,6 +402,7 @@ func executeBuildHook(_ *clilogger.CLILogger, options *Options, hookIdentifier s if options.Verbosity == VERBOSE { pterm.Info.Println(strings.Join(args, " ")) + } if !fs.DirExists(options.BinDirectory) { diff --git a/v2/pkg/commands/build/builder.go b/v2/pkg/commands/build/builder.go index 6a220c530..4840341c0 100644 --- a/v2/pkg/commands/build/builder.go +++ b/v2/pkg/commands/build/builder.go @@ -8,8 +8,8 @@ import ( // Builder defines a builder that can build Wails applications type Builder interface { SetProjectData(projectData *project.Project) - BuildFrontend(logger *clilogger.CLILogger) error - CompileProject(options *Options) error - OutputFilename(options *Options) string + BuildFrontend(*clilogger.CLILogger) error + CompileProject(*Options) error + OutputFilename(*Options) string CleanUp() } diff --git a/v2/pkg/commands/build/nsis_installer.go b/v2/pkg/commands/build/nsis_installer.go index 820df2d1d..11f1407a3 100644 --- a/v2/pkg/commands/build/nsis_installer.go +++ b/v2/pkg/commands/build/nsis_installer.go @@ -41,7 +41,7 @@ func GenerateNSISInstaller(options *Options, amd64Binary string, arm64Binary str // Write the WebView2 SetupFile webviewSetup := buildassets.GetLocalPath(options.ProjectData, path.Join(nsisFolder, nsisWebView2SetupFile)) if dir := filepath.Dir(webviewSetup); !fs.DirExists(dir) { - if err := fs.MkDirs(dir, 0o755); err != nil { + if err := fs.MkDirs(dir, 0755); err != nil { return err } } @@ -92,7 +92,7 @@ func makeNSIS(options *Options, installerKind string, amd64Binary string, arm64B outputLogger := options.Logger outputLogger.Print(" - Building '%s' installer: ", installerKind) - args := []string{} + var args = []string{} if amd64Binary != "" { args = append(args, "-DARG_WAILS_AMD64_BINARY="+amd64Binary) } diff --git a/v2/pkg/commands/build/packager.go b/v2/pkg/commands/build/packager.go index d406256f9..92ce37e90 100644 --- a/v2/pkg/commands/build/packager.go +++ b/v2/pkg/commands/build/packager.go @@ -3,15 +3,12 @@ package build import ( "bytes" "fmt" - "image" - "os" - "path/filepath" - "strings" - "github.com/leaanthony/winicon" "github.com/tc-hib/winres" "github.com/tc-hib/winres/version" - "github.com/wailsapp/wails/v2/internal/project" + "image" + "os" + "path/filepath" "github.com/jackmordaunt/icns" "github.com/pkg/errors" @@ -22,6 +19,7 @@ import ( // PackageProject packages the application func packageProject(options *Options, platform string) error { + var err error switch platform { case "darwin": @@ -43,6 +41,7 @@ func packageProject(options *Options, platform string) error { // cleanBinDirectory will remove an existing bin directory and recreate it func cleanBinDirectory(options *Options) error { + buildDirectory := options.BinDirectory // Clear out old builds @@ -54,7 +53,7 @@ func cleanBinDirectory(options *Options) error { } // Create clean directory - err := os.MkdirAll(buildDirectory, 0o700) + err := os.MkdirAll(buildDirectory, 0700) if err != nil { return err } @@ -63,6 +62,7 @@ func cleanBinDirectory(options *Options) error { } func packageApplicationForDarwin(options *Options) error { + var err error // Create directory structure @@ -73,20 +73,20 @@ func packageApplicationForDarwin(options *Options) error { contentsDirectory := filepath.Join(options.BinDirectory, bundlename, "/Contents") exeDir := filepath.Join(contentsDirectory, "/MacOS") - err = fs.MkDirs(exeDir, 0o755) + err = fs.MkDirs(exeDir, 0755) if err != nil { return err } resourceDir := filepath.Join(contentsDirectory, "/Resources") - err = fs.MkDirs(resourceDir, 0o755) + err = fs.MkDirs(resourceDir, 0755) if err != nil { return err } // Copy binary - packedBinaryPath := filepath.Join(exeDir, options.ProjectData.OutputFilename) + packedBinaryPath := filepath.Join(exeDir, options.ProjectData.Name) err = fs.MoveFile(options.CompiledBinary, packedBinaryPath) if err != nil { - return errors.Wrap(err, "Cannot move file: "+options.CompiledBinary) + return errors.Wrap(err, "Cannot move file: "+options.ProjectData.OutputFilename) } // Generate Info.plist @@ -95,26 +95,19 @@ func packageApplicationForDarwin(options *Options) error { return err } - // Generate App Icon - err = processDarwinIcon(options.ProjectData, "appicon", resourceDir, "iconfile") + // Generate Icons + err = processApplicationIcon(options, resourceDir) if err != nil { return err } - // Generate FileAssociation Icons - for _, fileAssociation := range options.ProjectData.Info.FileAssociations { - err = processDarwinIcon(options.ProjectData, fileAssociation.IconName, resourceDir, "") - if err != nil { - return err - } - } - options.CompiledBinary = packedBinaryPath return nil } func processPList(options *Options, contentsDirectory string) error { + sourcePList := "Info.plist" if options.Mode == Dev { // Use Info.dev.plist if using build mode @@ -128,11 +121,11 @@ func processPList(options *Options, contentsDirectory string) error { } targetFile := filepath.Join(contentsDirectory, "Info.plist") - return os.WriteFile(targetFile, content, 0o644) + return os.WriteFile(targetFile, content, 0644) } -func processDarwinIcon(projectData *project.Project, iconName string, resourceDir string, destIconName string) (err error) { - appIcon, err := buildassets.ReadFile(projectData, iconName+".png") +func processApplicationIcon(options *Options, resourceDir string) (err error) { + appIcon, err := buildassets.ReadFile(options.ProjectData, "appicon.png") if err != nil { return err } @@ -142,14 +135,11 @@ func processDarwinIcon(projectData *project.Project, iconName string, resourceDi return err } - if destIconName == "" { - destIconName = iconName - } - - tgtBundle := filepath.Join(resourceDir, destIconName+".icns") + tgtBundle := filepath.Join(resourceDir, "iconfile.icns") dest, err := os.Create(tgtBundle) if err != nil { return err + } defer func() { err = dest.Close() @@ -161,21 +151,13 @@ func processDarwinIcon(projectData *project.Project, iconName string, resourceDi } func packageApplicationForWindows(options *Options) error { - // Generate app icon + // Generate icon var err error - err = generateIcoFile(options, "appicon", "icon") + err = generateIcoFile(options) if err != nil { return err } - // Generate FileAssociation Icons - for _, fileAssociation := range options.ProjectData.Info.FileAssociations { - err = generateIcoFile(options, fileAssociation.IconName, "") - if err != nil { - return err - } - } - // Create syso file err = compileResources(options) if err != nil { @@ -189,26 +171,21 @@ func packageApplicationForLinux(_ *Options) error { return nil } -func generateIcoFile(options *Options, iconName string, destIconName string) error { - content, err := buildassets.ReadFile(options.ProjectData, iconName+".png") +func generateIcoFile(options *Options) error { + content, err := buildassets.ReadFile(options.ProjectData, "appicon.png") if err != nil { return err } - - if destIconName == "" { - destIconName = iconName - } - // Check ico file exists already - icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/"+destIconName+".ico") + icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/icon.ico") if !fs.FileExists(icoFile) { if dir := filepath.Dir(icoFile); !fs.DirExists(dir) { - if err := fs.MkDirs(dir, 0o755); err != nil { + if err := fs.MkDirs(dir, 0755); err != nil { return err } } - output, err := os.OpenFile(icoFile, os.O_CREATE|os.O_WRONLY, 0o644) + output, err := os.OpenFile(icoFile, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } @@ -223,12 +200,13 @@ func generateIcoFile(options *Options, iconName string, destIconName string) err } func compileResources(options *Options) error { + currentDir, err := os.Getwd() if err != nil { return err } defer func() { - _ = os.Chdir(currentDir) + os.Chdir(currentDir) }() windowsDir := filepath.Join(options.ProjectData.GetBuildDir(), "windows") err = os.Chdir(windowsDir) @@ -275,8 +253,7 @@ func compileResources(options *Options) error { rs.SetVersionInfo(v) } - // replace spaces with underscores as go build behaves weirdly with spaces in syso filename - targetFile := filepath.Join(options.ProjectData.Path, strings.ReplaceAll(options.ProjectData.Name, " ", "_")+"-res.syso") + targetFile := filepath.Join(options.ProjectData.Path, options.ProjectData.Name+"-res.syso") fout, err := os.Create(targetFile) if err != nil { return err diff --git a/v2/pkg/commands/buildtags/buildtags.go b/v2/pkg/commands/buildtags/buildtags.go index 5cca16acf..70820d03d 100644 --- a/v2/pkg/commands/buildtags/buildtags.go +++ b/v2/pkg/commands/buildtags/buildtags.go @@ -8,7 +8,7 @@ import ( ) // Parse parses the given tags string and returns -// a cleaned slice of strings. Both comma and space delimited +// a cleaned slice of strings. Both comma and space delimeted // tags are supported but not mixed. If mixed, an error is returned. func Parse(tags string) ([]string, error) { if tags == "" { diff --git a/v2/pkg/git/git.go b/v2/pkg/git/git.go index a0ac68ca9..319c5672b 100644 --- a/v2/pkg/git/git.go +++ b/v2/pkg/git/git.go @@ -1,8 +1,7 @@ package git import ( - "encoding/json" - "fmt" + "html/template" "runtime" "strings" @@ -31,31 +30,9 @@ func Email() (string, error) { // Name tries to retrieve the func Name() (string, error) { - errMsg := "failed to retrieve git user name: %w" stdout, _, err := shell.RunCommand(".", gitcommand(), "config", "user.name") - if err != nil { - return "", fmt.Errorf(errMsg, err) - } - name := strings.TrimSpace(stdout) - return EscapeName(name) -} - -func EscapeName(str string) (string, error) { - b, err := json.Marshal(str) - if err != nil { - return "", err - } - // Remove the surrounding quotes - escaped := string(b[1 : len(b)-1]) - - // Check if username is JSON compliant - var js json.RawMessage - jsonVal := fmt.Sprintf(`{"name": "%s"}`, escaped) - err = json.Unmarshal([]byte(jsonVal), &js) - if err != nil { - return "", fmt.Errorf("failed to retrieve git user name: %w", err) - } - return escaped, nil + name := template.JSEscapeString(strings.TrimSpace(stdout)) + return name, err } func InitRepo(projectDir string) error { diff --git a/v2/pkg/git/git_test.go b/v2/pkg/git/git_test.go deleted file mode 100644 index 238008ec3..000000000 --- a/v2/pkg/git/git_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package git - -import ( - "testing" -) - -func TestEscapeName1(t *testing.T) { - type args struct { - str string - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "Escape Apostrophe", - args: args{ - str: `John O'Keefe`, - }, - want: `John O'Keefe`, - }, - { - name: "Escape backslash", - args: args{ - str: `MYDOMAIN\USER`, - }, - want: `MYDOMAIN\\USER`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := EscapeName(tt.args.str) - if (err != nil) != tt.wantErr { - t.Errorf("EscapeName() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("EscapeName() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/v2/pkg/logger/filelogger.go b/v2/pkg/logger/filelogger.go index 954c46f59..5cf68fc1a 100644 --- a/v2/pkg/logger/filelogger.go +++ b/v2/pkg/logger/filelogger.go @@ -19,7 +19,7 @@ func NewFileLogger(filename string) Logger { // Print works like Sprintf. func (l *FileLogger) Print(message string) { - f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } diff --git a/v2/pkg/logger/logger.go b/v2/pkg/logger/logger.go index 990dffe75..abc288265 100644 --- a/v2/pkg/logger/logger.go +++ b/v2/pkg/logger/logger.go @@ -41,24 +41,6 @@ func StringToLogLevel(input string) (LogLevel, error) { return result, nil } -// String returns the string representation of the LogLevel -func (l LogLevel) String() string { - switch l { - case TRACE: - return "trace" - case DEBUG: - return "debug" - case INFO: - return "info" - case WARNING: - return "warning" - case ERROR: - return "error" - default: - return "debug" - } -} - // Logger specifies the methods required to attach // a logger to a Wails application type Logger interface { diff --git a/v2/pkg/mac/login_darwin.go b/v2/pkg/mac/login_darwin.go index b2390e305..2ff49be83 100644 --- a/v2/pkg/mac/login_darwin.go +++ b/v2/pkg/mac/login_darwin.go @@ -33,7 +33,7 @@ func StartAtLogin(enabled bool) error { } _, stde, err := shell.RunCommand("/tmp", "osascript", "-e", command) if err != nil { - return errors.Wrap(err, stde) + errors.Wrap(err, stde) } return nil } diff --git a/v2/pkg/mac/notification_darwin.go b/v2/pkg/mac/notification_darwin.go index 243f07c78..a06ecb53a 100644 --- a/v2/pkg/mac/notification_darwin.go +++ b/v2/pkg/mac/notification_darwin.go @@ -8,7 +8,7 @@ import ( "github.com/wailsapp/wails/v2/internal/shell" ) -// ShowNotification will either add or remove this application to/from the login +// StartAtLogin will either add or remove this application to/from the login // items, depending on the given boolean flag. The limitation is that the // currently running app must be in an app bundle. func ShowNotification(title string, subtitle string, message string, sound string) error { @@ -24,7 +24,7 @@ func ShowNotification(title string, subtitle string, message string, sound strin } _, stde, err := shell.RunCommand("/tmp", "osascript", "-e", command) if err != nil { - return errors.Wrap(err, stde) + errors.Wrap(err, stde) } return nil } diff --git a/v2/pkg/menu/README.md b/v2/pkg/menu/README.md index 7c66a1051..0fbbac6ee 100644 --- a/v2/pkg/menu/README.md +++ b/v2/pkg/menu/README.md @@ -4,7 +4,7 @@ Menu support is heavily inspired by Electron's approach. ## Features - * Supports Text, Checkbox, Radio, Submenu and Separator - * Radio groups are defined as any number of adjacent radio items - * UTF-8 menu labels - * UTF-8 menu IDs \ No newline at end of file +- Supports Text, Checkbox, Radio, Submenu and Separator +- Radio groups are defined as any number of adjacent radio items +- UTF-8 menu labels +- UTF-8 menu IDs diff --git a/v2/pkg/menu/callback.go b/v2/pkg/menu/callback.go index a02664ac0..fe6160361 100644 --- a/v2/pkg/menu/callback.go +++ b/v2/pkg/menu/callback.go @@ -2,7 +2,7 @@ package menu type CallbackData struct { MenuItem *MenuItem - // ContextData string + //ContextData string } type Callback func(*CallbackData) diff --git a/v2/pkg/menu/colours/colours.go b/v2/pkg/menu/colours/colours.go index 5fb74eabd..28564a09e 100644 --- a/v2/pkg/menu/colours/colours.go +++ b/v2/pkg/menu/colours/colours.go @@ -36,6 +36,7 @@ type InputCol struct { var Template string func main() { + var Cols []InputCol resp, err := http.Get("https://jonasjacek.github.io/colors/data.json") @@ -61,8 +62,5 @@ func main() { if err != nil { log.Fatal(err) } - err = os.WriteFile(filepath.Join("..", "cols.go"), buffer.Bytes(), 0o755) - if err != nil { - log.Fatal(err) - } + os.WriteFile(filepath.Join("..", "cols.go"), buffer.Bytes(), 0755) } diff --git a/v2/pkg/menu/keys/keys.go b/v2/pkg/menu/keys/keys.go index 961edab2d..73bc9414f 100644 --- a/v2/pkg/menu/keys/keys.go +++ b/v2/pkg/menu/keys/keys.go @@ -16,7 +16,7 @@ const ( // ShiftKey represents the shift key on all systems ShiftKey Modifier = "shift" // SuperKey represents Command on Mac and the Windows key on the other platforms - // SuperKey Modifier = "super" + //SuperKey Modifier = "super" // ControlKey represents the control key on all systems ControlKey Modifier = "ctrl" ) @@ -99,6 +99,8 @@ func Combo(key string, modifier1 Modifier, modifier2 Modifier, rest ...Modifier) Key: key, Modifiers: []Modifier{modifier1, modifier2}, } - result.Modifiers = append(result.Modifiers, rest...) + for _, extra := range rest { + result.Modifiers = append(result.Modifiers, extra) + } return result } diff --git a/v2/pkg/menu/keys/parser.go b/v2/pkg/menu/keys/parser.go index 6e8e12376..91a05783d 100644 --- a/v2/pkg/menu/keys/parser.go +++ b/v2/pkg/menu/keys/parser.go @@ -11,6 +11,7 @@ import ( var namedKeys = slicer.String([]string{"backspace", "tab", "return", "enter", "escape", "left", "right", "up", "down", "space", "delete", "home", "end", "page up", "page down", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35", "numlock"}) func parseKey(key string) (string, bool) { + // Lowercase! key = strings.ToLower(key) @@ -37,9 +38,11 @@ func parseKey(key string) (string, bool) { } return "", false + } func Parse(shortcut string) (*Accelerator, error) { + var result Accelerator // Split the shortcut by + diff --git a/v2/pkg/menu/keys/stringify.go b/v2/pkg/menu/keys/stringify.go index 92498f5d4..ccc8c5e9e 100644 --- a/v2/pkg/menu/keys/stringify.go +++ b/v2/pkg/menu/keys/stringify.go @@ -1,9 +1,8 @@ package keys import ( - "strings" - "github.com/leaanthony/slicer" + "strings" ) var modifierStringMap = map[string]map[Modifier]string{ @@ -12,21 +11,21 @@ var modifierStringMap = map[string]map[Modifier]string{ ControlKey: "Ctrl", OptionOrAltKey: "Alt", ShiftKey: "Shift", - // SuperKey: "Win", + //SuperKey: "Win", }, "darwin": { CmdOrCtrlKey: "Cmd", ControlKey: "Ctrl", OptionOrAltKey: "Option", ShiftKey: "Shift", - // SuperKey: "Cmd", + //SuperKey: "Cmd", }, "linux": { CmdOrCtrlKey: "Ctrl", ControlKey: "Ctrl", OptionOrAltKey: "Alt", ShiftKey: "Shift", - // SuperKey: "Super", + //SuperKey: "Super", }, } diff --git a/v2/pkg/menu/menu.go b/v2/pkg/menu/menu.go index 86acbd1d0..0c3ddb618 100644 --- a/v2/pkg/menu/menu.go +++ b/v2/pkg/menu/menu.go @@ -17,7 +17,9 @@ func (m *Menu) Append(item *MenuItem) { // Merge will append the items in the given menu // into this menu func (m *Menu) Merge(menu *Menu) { - m.Items = append(m.Items, menu.Items...) + for _, item := range menu.Items { + m.Items = append(m.Items, item) + } } // AddText adds a TextMenu item to the menu @@ -59,7 +61,8 @@ func (m *Menu) Prepend(item *MenuItem) { } func NewMenuFromItems(first *MenuItem, rest ...*MenuItem) *Menu { - result := NewMenu() + + var result = NewMenu() result.Append(first) for _, item := range rest { result.Append(item) diff --git a/v2/pkg/menu/menuitem.go b/v2/pkg/menu/menuitem.go index bffc522d8..f6ea681d7 100644 --- a/v2/pkg/menu/menuitem.go +++ b/v2/pkg/menu/menuitem.go @@ -22,8 +22,8 @@ type MenuItem struct { Hidden bool // Checked indicates if the item is selected (used by Checkbox and Radio types only) Checked bool - // SubMenu contains a list of menu items that will be shown as a submenu - // SubMenu []*MenuItem `json:"SubMenu,omitempty"` + // Submenu contains a list of menu items that will be shown as a submenu + //SubMenu []*MenuItem `json:"SubMenu,omitempty"` SubMenu *Menu // Callback function when menu clicked @@ -106,6 +106,7 @@ func (m *MenuItem) removeChild(item *MenuItem) { // menu. If there is no parent menu (we are a top level menu) then false is // returned func (m *MenuItem) InsertAfter(item *MenuItem) bool { + // We need to find my parent if m.parent == nil { return false @@ -119,6 +120,7 @@ func (m *MenuItem) InsertAfter(item *MenuItem) bool { // menu. If there is no parent menu (we are a top level menu) then false is // returned func (m *MenuItem) InsertBefore(item *MenuItem) bool { + // We need to find my parent if m.parent == nil { return false @@ -132,8 +134,8 @@ func (m *MenuItem) InsertBefore(item *MenuItem) bool { // in this item's submenu. If we are not a submenu, // then something bad has happened :/ func (m *MenuItem) insertNewItemAfterGivenItem(target *MenuItem, - newItem *MenuItem, -) bool { + newItem *MenuItem) bool { + if !m.isSubMenu() { return false } @@ -152,8 +154,8 @@ func (m *MenuItem) insertNewItemAfterGivenItem(target *MenuItem, // target in this item's submenu. If we are not a submenu, then something bad // has happened :/ func (m *MenuItem) insertNewItemBeforeGivenItem(target *MenuItem, - newItem *MenuItem, -) bool { + newItem *MenuItem) bool { + if !m.isSubMenu() { return false } @@ -174,6 +176,7 @@ func (m *MenuItem) isSubMenu() bool { // getItemIndex returns the index of the given target relative to this menu func (m *MenuItem) getItemIndex(target *MenuItem) int { + // This should only be called on submenus if !m.isSubMenu() { return -1 @@ -193,6 +196,7 @@ func (m *MenuItem) getItemIndex(target *MenuItem) int { // the given index // Credit: https://stackoverflow.com/a/61822301 func (m *MenuItem) insertItemAtIndex(index int, target *MenuItem) bool { + // If index is OOB, return false if index > len(m.SubMenu.Items) { return false diff --git a/v2/pkg/menu/menuroles.go b/v2/pkg/menu/menuroles.go index bcc0657fc..e6b15b243 100644 --- a/v2/pkg/menu/menuroles.go +++ b/v2/pkg/menu/menuroles.go @@ -11,29 +11,29 @@ const ( AppMenuRole Role = 1 EditMenuRole = 2 WindowMenuRole = 3 - // AboutRole Role = "about" - // UndoRole Role = "undo" - // RedoRole Role = "redo" - // CutRole Role = "cut" - // CopyRole Role = "copy" - // PasteRole Role = "paste" - // PasteAndMatchStyleRole Role = "pasteAndMatchStyle" - // SelectAllRole Role = "selectAll" - // DeleteRole Role = "delete" - // MinimizeRole Role = "minimize" - // QuitRole Role = "quit" - // TogglefullscreenRole Role = "togglefullscreen" - // FileMenuRole Role = "fileMenu" - // ViewMenuRole Role = "viewMenu" - // WindowMenuRole Role = "windowMenu" - // HideRole Role = "hide" - // HideOthersRole Role = "hideOthers" - // UnhideRole Role = "unhide" - // FrontRole Role = "front" - // ZoomRole Role = "zoom" - // WindowSubMenuRole Role = "windowSubMenu" - // HelpSubMenuRole Role = "helpSubMenu" - // SeparatorItemRole Role = "separatorItem" + //AboutRole Role = "about" + //UndoRole Role = "undo" + //RedoRole Role = "redo" + //CutRole Role = "cut" + //CopyRole Role = "copy" + //PasteRole Role = "paste" + //PasteAndMatchStyleRole Role = "pasteAndMatchStyle" + //SelectAllRole Role = "selectAll" + //DeleteRole Role = "delete" + //MinimizeRole Role = "minimize" + //QuitRole Role = "quit" + //TogglefullscreenRole Role = "togglefullscreen" + //FileMenuRole Role = "fileMenu" + //ViewMenuRole Role = "viewMenu" + //WindowMenuRole Role = "windowMenu" + //HideRole Role = "hide" + //HideOthersRole Role = "hideOthers" + //UnhideRole Role = "unhide" + //FrontRole Role = "front" + //ZoomRole Role = "zoom" + //WindowSubMenuRole Role = "windowSubMenu" + //HelpSubMenuRole Role = "helpSubMenu" + //SeparatorItemRole Role = "separatorItem" ) /* diff --git a/v2/pkg/menu/styledlabel.go b/v2/pkg/menu/styledlabel.go index 1e996b971..11a0254c9 100644 --- a/v2/pkg/menu/styledlabel.go +++ b/v2/pkg/menu/styledlabel.go @@ -29,31 +29,24 @@ type StyledText struct { func (s *StyledText) Bold() bool { return s.Style&Bold == Bold } - func (s *StyledText) Faint() bool { return s.Style&Faint == Faint } - func (s *StyledText) Italic() bool { return s.Style&Italic == Italic } - func (s *StyledText) Blinking() bool { return s.Style&Blinking == Blinking } - func (s *StyledText) Inversed() bool { return s.Style&Inversed == Inversed } - func (s *StyledText) Invisible() bool { return s.Style&Invisible == Invisible } - func (s *StyledText) Underlined() bool { return s.Style&Underlined == Underlined } - func (s *StyledText) Strikethrough() bool { return s.Style&Strikethrough == Strikethrough } diff --git a/v2/pkg/menu/tray.go b/v2/pkg/menu/tray.go index c8728f1f7..7554795ad 100644 --- a/v2/pkg/menu/tray.go +++ b/v2/pkg/menu/tray.go @@ -2,6 +2,7 @@ package menu // TrayMenu are the options type TrayMenu struct { + // Label is the text we wish to display in the tray Label string @@ -26,7 +27,7 @@ type TrayMenu struct { Tooltip string // Callback function when menu clicked - // Click Callback `json:"-"` + //Click Callback `json:"-"` // Disabled makes the item unselectable Disabled bool diff --git a/v2/pkg/options/linux/linux.go b/v2/pkg/options/linux/linux.go index 797450c27..3726297c4 100644 --- a/v2/pkg/options/linux/linux.go +++ b/v2/pkg/options/linux/linux.go @@ -28,21 +28,7 @@ type Options struct { // - 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 - - // 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 } type Messages struct { diff --git a/v2/pkg/options/mac/mac.go b/v2/pkg/options/mac/mac.go index 152145114..033cfd1a2 100644 --- a/v2/pkg/options/mac/mac.go +++ b/v2/pkg/options/mac/mac.go @@ -18,14 +18,9 @@ type AboutInfo struct { type Options struct { TitleBar *TitleBar Appearance AppearanceType - ContentProtection bool WebviewIsTransparent bool WindowIsTranslucent bool - Preferences *Preferences - DisableZoom bool - // ActivationPolicy ActivationPolicy - About *AboutInfo - OnFileOpen func(filePath string) `json:"-"` - OnUrlOpen func(filePath string) `json:"-"` - // URLHandlers map[string]func(string) + //ActivationPolicy ActivationPolicy + About *AboutInfo + //URLHandlers map[string]func(string) } diff --git a/v2/pkg/options/mac/preferences.go b/v2/pkg/options/mac/preferences.go deleted file mode 100644 index 0749ccb18..000000000 --- a/v2/pkg/options/mac/preferences.go +++ /dev/null @@ -1,21 +0,0 @@ -package mac - -import "github.com/leaanthony/u" - -var ( - Enabled = u.True - Disabled = u.False -) - -// Preferences allows to set webkit preferences -type Preferences struct { - // A Boolean value that indicates whether pressing the tab key changes the focus to links and form controls. - // Set to false by default. - TabFocusesLinks u.Bool - // A Boolean value that indicates whether to allow people to select or otherwise interact with text. - // Set to true by default. - TextInteractionEnabled u.Bool - // A Boolean value that indicates whether a web view can display content full screen. - // Set to false by default - FullscreenEnabled u.Bool -} diff --git a/v2/pkg/options/mac/titlebar.go b/v2/pkg/options/mac/titlebar.go index 51e0832ca..c18c4eea8 100644 --- a/v2/pkg/options/mac/titlebar.go +++ b/v2/pkg/options/mac/titlebar.go @@ -41,6 +41,7 @@ func TitleBarHidden() *TitleBar { // TitleBarHiddenInset results in a hidden title bar with an alternative look where // the traffic light buttons are slightly more inset from the window edge. func TitleBarHiddenInset() *TitleBar { + return &TitleBar{ TitlebarAppearsTransparent: true, HideTitle: true, @@ -49,4 +50,5 @@ func TitleBarHiddenInset() *TitleBar { UseToolbar: true, HideToolbarSeparator: true, } + } diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index 0f62d5e4b..74b2aef72 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -5,8 +5,6 @@ import ( "html" "io/fs" "net/http" - "os" - "path/filepath" "runtime" "github.com/wailsapp/wails/v2/pkg/options/assetserver" @@ -28,7 +26,8 @@ const ( Fullscreen WindowStartState = 3 ) -type Experimental struct{} +type Experimental struct { +} // App contains options for creating the App type App struct { @@ -63,29 +62,19 @@ type App struct { OnShutdown func(ctx context.Context) `json:"-"` OnBeforeClose func(ctx context.Context) (prevent bool) `json:"-"` Bind []interface{} - EnumBind []interface{} WindowStartState WindowStartState - // ErrorFormatter overrides the formatting of errors returned by backend methods - ErrorFormatter ErrorFormatter - // CSS property to test for draggable elements. Default "--wails-draggable" CSSDragProperty string // The CSS Value that the CSSDragProperty must have to be draggable, EG: "drag" CSSDragValue string - // EnableDefaultContextMenu enables the browser's default context-menu in production - // This menu is already enabled in development and debug builds - EnableDefaultContextMenu bool - // EnableFraudulentWebsiteDetection enables scan services for fraudulent content, such as malware or phishing attempts. // These services might send information from your app like URLs navigated to and possibly other content to cloud // services of Apple and Microsoft. EnableFraudulentWebsiteDetection bool - SingleInstanceLock *SingleInstanceLock - Windows *windows.Options Mac *mac.Options Linux *linux.Options @@ -95,19 +84,8 @@ type App struct { // Debug options for debug builds. These options will be ignored in a production build. Debug Debug - - // DragAndDrop options for drag and drop behavior - DragAndDrop *DragAndDrop - - // DisablePanicRecovery disables the panic recovery system in messages processing - DisablePanicRecovery bool - - // List of additional allowed origins for bindings in format "https://*.myapp.com,https://example.com" - BindingsAllowedOrigins string } -type ErrorFormatter func(error) any - type RGBA struct { R uint8 `json:"r"` G uint8 `json:"g"` @@ -159,15 +137,6 @@ func MergeDefaults(appoptions *App) { if appoptions.CSSDragValue == "" { appoptions.CSSDragValue = "drag" } - if appoptions.DragAndDrop == nil { - appoptions.DragAndDrop = &DragAndDrop{} - } - if appoptions.DragAndDrop.CSSDropProperty == "" { - appoptions.DragAndDrop.CSSDropProperty = "--wails-drop-target" - } - if appoptions.DragAndDrop.CSSDropValue == "" { - appoptions.DragAndDrop.CSSDropValue = "drop" - } if appoptions.BackgroundColour == nil { appoptions.BackgroundColour = &RGBA{ R: 255, @@ -187,47 +156,6 @@ func MergeDefaults(appoptions *App) { processDragOptions(appoptions) } -type SingleInstanceLock struct { - // uniqueId that will be used for setting up messaging between instances - UniqueId string - OnSecondInstanceLaunch func(secondInstanceData SecondInstanceData) -} - -type SecondInstanceData struct { - Args []string - WorkingDirectory string -} - -type DragAndDrop struct { - - // EnableFileDrop enables wails' drag and drop functionality that returns the dropped in files' absolute paths. - EnableFileDrop bool - - // Disable webview's drag and drop functionality. - // - // It can be used to prevent accidental file opening of dragged in files in the webview, when there is no need for drag and drop. - DisableWebViewDrop bool - - // CSS property to test for drag and drop target elements. Default "--wails-drop-target" - CSSDropProperty string - - // The CSS Value that the CSSDropProperty must have to be a valid drop target. Default "drop" - CSSDropValue string -} - -func NewSecondInstanceData() (*SecondInstanceData, error) { - ex, err := os.Executable() - if err != nil { - return nil, err - } - workingDirectory := filepath.Dir(ex) - - return &SecondInstanceData{ - Args: os.Args[1:], - WorkingDirectory: workingDirectory, - }, nil -} - func processMenus(appoptions *App) { switch runtime.GOOS { case "darwin": diff --git a/v2/pkg/options/systemtray.go b/v2/pkg/options/systemtray.go new file mode 100644 index 000000000..117abb4d6 --- /dev/null +++ b/v2/pkg/options/systemtray.go @@ -0,0 +1,26 @@ +package options + +import ( + "github.com/wailsapp/wails/v2/pkg/menu" +) + +// SystemTray contains options for the system tray +type SystemTray struct { + LightModeIcon *SystemTrayIcon + DarkModeIcon *SystemTrayIcon + Title string + Tooltip string + StartHidden bool + Menu *menu.Menu + OnLeftClick func() + OnRightClick func() + OnLeftDoubleClick func() + OnRightDoubleClick func() + OnMenuClose func() + OnMenuOpen func() +} + +// SystemTrayIcon represents a system tray icon +type SystemTrayIcon struct { + Data []byte +} diff --git a/v2/pkg/options/windows/windows.go b/v2/pkg/options/windows/windows.go index 1fe351455..ea6fdc35e 100644 --- a/v2/pkg/options/windows/windows.go +++ b/v2/pkg/options/windows/windows.go @@ -35,29 +35,8 @@ const ( Tabbed BackdropType = 4 ) -const ( - // Default is 0, which means no changes to the default Windows DLL search behavior - DLLSearchDefault uint32 = 0 - // LoadLibrary flags for determining from where to search for a DLL - DLLSearchDontResolveDllReferences uint32 = 0x1 // windows.DONT_RESOLVE_DLL_REFERENCES - DLLSearchAsDataFile uint32 = 0x2 // windows.LOAD_LIBRARY_AS_DATAFILE - DLLSearchWithAlteredPath uint32 = 0x8 // windows.LOAD_WITH_ALTERED_SEARCH_PATH - DLLSearchIgnoreCodeAuthzLevel uint32 = 0x10 // windows.LOAD_IGNORE_CODE_AUTHZ_LEVEL - DLLSearchAsImageResource uint32 = 0x20 // windows.LOAD_LIBRARY_AS_IMAGE_RESOURCE - DLLSearchAsDataFileExclusive uint32 = 0x40 // windows.LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE - DLLSearchRequireSignedTarget uint32 = 0x80 // windows.LOAD_LIBRARY_REQUIRE_SIGNED_TARGET - DLLSearchDllLoadDir uint32 = 0x100 // windows.LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR - DLLSearchApplicationDir uint32 = 0x200 // windows.LOAD_LIBRARY_SEARCH_APPLICATION_DIR - DLLSearchUserDirs uint32 = 0x400 // windows.LOAD_LIBRARY_SEARCH_USER_DIRS - DLLSearchSystem32 uint32 = 0x800 // windows.LOAD_LIBRARY_SEARCH_SYSTEM32 - DLLSearchDefaultDirs uint32 = 0x1000 // windows.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS - DLLSearchSafeCurrentDirs uint32 = 0x2000 // windows.LOAD_LIBRARY_SAFE_CURRENT_DIRS - DLLSearchSystem32NoForwarder uint32 = 0x4000 // windows.LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER - DLLSearchOsIntegrityContinuity uint32 = 0x8000 // windows.LOAD_LIBRARY_OS_INTEGRITY_CONTINUITY -) - func RGB(r, g, b uint8) int32 { - col := int32(b) + var col = int32(b) col = col<<8 | int32(g) col = col<<8 | int32(r) return col @@ -82,7 +61,6 @@ type ThemeSettings struct { // Options are options specific to Windows type Options struct { - ContentProtection bool WebviewIsTransparent bool WindowIsTranslucent bool DisableWindowIcon bool @@ -90,8 +68,6 @@ type Options struct { IsZoomControlEnabled bool ZoomFactor float64 - DisablePinchZoom bool - // 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. DisableFramelessWindowDecorations bool @@ -137,17 +113,6 @@ type Options struct { // // !! Please keep in mind when disabling this feature, this also allows malicious software to inject into the WebView2 !! WebviewDisableRendererCodeIntegrity bool - - // Configure whether swipe gestures should be enabled - EnableSwipeGestures bool - - // Class name for the window. If empty, 'wailsWindow' will be used. - WindowClassName string - - // DLLSearchPaths controls which directories are searched when loading DLLs - // Set to 0 for default behavior, or combine multiple flags with bitwise OR - // Example: DLLSearchApplicationDir | DLLSearchSystem32 - DLLSearchPaths uint32 } func DefaultMessages() *Messages { diff --git a/v2/pkg/runtime/dialog.go b/v2/pkg/runtime/dialog.go index 16ae659e1..d53a89c15 100644 --- a/v2/pkg/runtime/dialog.go +++ b/v2/pkg/runtime/dialog.go @@ -3,7 +3,6 @@ package runtime import ( "context" "fmt" - "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/fs" ) diff --git a/v2/pkg/runtime/draganddrop.go b/v2/pkg/runtime/draganddrop.go deleted file mode 100644 index 2db9c773c..000000000 --- a/v2/pkg/runtime/draganddrop.go +++ /dev/null @@ -1,37 +0,0 @@ -package runtime - -import ( - "context" - "fmt" -) - -// OnFileDrop returns a slice of file path strings when a drop is finished. -func OnFileDrop(ctx context.Context, callback func(x, y int, paths []string)) { - if callback == nil { - LogError(ctx, "OnFileDrop called with a nil callback") - return - } - EventsOn(ctx, "wails:file-drop", func(optionalData ...interface{}) { - if len(optionalData) != 3 { - callback(0, 0, nil) - } - x, ok := optionalData[0].(int) - if !ok { - LogError(ctx, fmt.Sprintf("invalid x coordinate in drag and drop: %v", optionalData[0])) - } - y, ok := optionalData[1].(int) - if !ok { - LogError(ctx, fmt.Sprintf("invalid y coordinate in drag and drop: %v", optionalData[1])) - } - paths, ok := optionalData[2].([]string) - if !ok { - LogError(ctx, fmt.Sprintf("invalid path data in drag and drop: %v", optionalData[2])) - } - callback(x, y, paths) - }) -} - -// OnFileDropOff removes the drag and drop listeners and handlers. -func OnFileDropOff(ctx context.Context) { - EventsOff(ctx, "wails:file-drop") -} diff --git a/v2/pkg/runtime/events.go b/v2/pkg/runtime/events.go index 84aff7d74..493d81168 100644 --- a/v2/pkg/runtime/events.go +++ b/v2/pkg/runtime/events.go @@ -10,7 +10,7 @@ func EventsOn(ctx context.Context, eventName string, callback func(optionalData return events.On(eventName, callback) } -// EventsOff unregisters a listener for the given event name, optionally multiple listeners can be unregistered via `additionalEventNames` +// EventsOff unregisters a listener for the given event name, optionally multiple listeneres can be unregistered via `additionalEventNames` func EventsOff(ctx context.Context, eventName string, additionalEventNames ...string) { events := getEvents(ctx) events.Off(eventName) @@ -22,7 +22,7 @@ func EventsOff(ctx context.Context, eventName string, additionalEventNames ...st } } -// EventsOff unregisters a listener for the given event name, optionally multiple listeners can be unregistered via `additionalEventNames` +// EventsOff unregisters a listener for the given event name, optionally multiple listeneres can be unregistered via `additionalEventNames` func EventsOffAll(ctx context.Context) { events := getEvents(ctx) events.OffAll() diff --git a/v2/pkg/runtime/log.go b/v2/pkg/runtime/log.go index 3c2756f06..4d3f56d3f 100644 --- a/v2/pkg/runtime/log.go +++ b/v2/pkg/runtime/log.go @@ -3,7 +3,6 @@ package runtime import ( "context" "fmt" - "github.com/wailsapp/wails/v2/pkg/logger" ) diff --git a/v2/pkg/runtime/menu.go b/v2/pkg/runtime/menu.go index 09bd640c5..176c9bb1d 100644 --- a/v2/pkg/runtime/menu.go +++ b/v2/pkg/runtime/menu.go @@ -2,7 +2,6 @@ package runtime import ( "context" - "github.com/wailsapp/wails/v2/pkg/menu" ) diff --git a/v2/pkg/runtime/notifications.go b/v2/pkg/runtime/notifications.go deleted file mode 100644 index 46ae09fac..000000000 --- a/v2/pkg/runtime/notifications.go +++ /dev/null @@ -1,136 +0,0 @@ -package runtime - -import ( - "context" - - "github.com/wailsapp/wails/v2/internal/frontend" -) - -// NotificationOptions contains configuration for a notification. -type NotificationOptions = frontend.NotificationOptions - -// NotificationAction represents an action button for a notification. -type NotificationAction = frontend.NotificationAction - -// NotificationCategory groups actions for notifications. -type NotificationCategory = frontend.NotificationCategory - -// NotificationResponse represents the response sent by interacting with a notification. -type NotificationResponse = frontend.NotificationResponse - -// NotificationResult represents the result of a notification response, -// returning the response or any errors that occurred. -type NotificationResult = frontend.NotificationResult - -// InitializeNotifications initializes the notification service for the application. -// This must be called before sending any notifications. On macOS, this also ensures -// the notification delegate is properly initialized. -func InitializeNotifications(ctx context.Context) error { - fe := getFrontend(ctx) - return fe.InitializeNotifications() -} - -// CleanupNotifications cleans up notification resources and releases any held connections. -// This should be called when shutting down the application to properly release resources -// (primarily needed on Linux to close D-Bus connections). -func CleanupNotifications(ctx context.Context) { - fe := getFrontend(ctx) - fe.CleanupNotifications() -} - -// IsNotificationAvailable checks if notifications are available on the current platform. -func IsNotificationAvailable(ctx context.Context) bool { - fe := getFrontend(ctx) - return fe.IsNotificationAvailable() -} - -// RequestNotificationAuthorization requests notification authorization from the user. -// On macOS, this prompts the user to allow notifications. On other platforms, this -// always returns true. Returns true if authorization was granted, false otherwise. -func RequestNotificationAuthorization(ctx context.Context) (bool, error) { - fe := getFrontend(ctx) - return fe.RequestNotificationAuthorization() -} - -// CheckNotificationAuthorization checks the current notification authorization status. -// On macOS, this checks if the app has notification permissions. On other platforms, -// this always returns true. -func CheckNotificationAuthorization(ctx context.Context) (bool, error) { - fe := getFrontend(ctx) - return fe.CheckNotificationAuthorization() -} - -// SendNotification sends a basic notification with the given options. -// The notification will display with the provided title, subtitle (if supported), -// and body text. -func SendNotification(ctx context.Context, options NotificationOptions) error { - fe := getFrontend(ctx) - return fe.SendNotification(options) -} - -// SendNotificationWithActions sends a notification with action buttons. -// A NotificationCategory must be registered first using RegisterNotificationCategory. -// The options.CategoryID must match a previously registered category ID. -// If the category is not found, a basic notification will be sent instead. -func SendNotificationWithActions(ctx context.Context, options NotificationOptions) error { - fe := getFrontend(ctx) - return fe.SendNotificationWithActions(options) -} - -// RegisterNotificationCategory registers a notification category that can be used -// with SendNotificationWithActions. Categories define the action buttons and optional -// reply fields that will appear on notifications. -func RegisterNotificationCategory(ctx context.Context, category NotificationCategory) error { - fe := getFrontend(ctx) - return fe.RegisterNotificationCategory(category) -} - -// RemoveNotificationCategory removes a previously registered notification category. -func RemoveNotificationCategory(ctx context.Context, categoryId string) error { - fe := getFrontend(ctx) - return fe.RemoveNotificationCategory(categoryId) -} - -// RemoveAllPendingNotifications removes all pending notifications from the notification center. -// On Windows, this is a no-op as the platform manages notification lifecycle automatically. -func RemoveAllPendingNotifications(ctx context.Context) error { - fe := getFrontend(ctx) - return fe.RemoveAllPendingNotifications() -} - -// RemovePendingNotification removes a specific pending notification by its identifier. -// On Windows, this is a no-op as the platform manages notification lifecycle automatically. -func RemovePendingNotification(ctx context.Context, identifier string) error { - fe := getFrontend(ctx) - return fe.RemovePendingNotification(identifier) -} - -// RemoveAllDeliveredNotifications removes all delivered notifications from the notification center. -// On Windows, this is a no-op as the platform manages notification lifecycle automatically. -func RemoveAllDeliveredNotifications(ctx context.Context) error { - fe := getFrontend(ctx) - return fe.RemoveAllDeliveredNotifications() -} - -// RemoveDeliveredNotification removes a specific delivered notification by its identifier. -// On Windows, this is a no-op as the platform manages notification lifecycle automatically. -func RemoveDeliveredNotification(ctx context.Context, identifier string) error { - fe := getFrontend(ctx) - return fe.RemoveDeliveredNotification(identifier) -} - -// RemoveNotification removes a notification by its identifier. -// This is a convenience function that works across platforms. On macOS, use the -// more specific RemovePendingNotification or RemoveDeliveredNotification functions. -func RemoveNotification(ctx context.Context, identifier string) error { - fe := getFrontend(ctx) - return fe.RemoveNotification(identifier) -} - -// OnNotificationResponse registers a callback function that will be invoked when -// a user interacts with a notification (e.g., clicks an action button or the notification itself). -// The callback receives a NotificationResult containing the response details or any errors. -func OnNotificationResponse(ctx context.Context, callback func(result NotificationResult)) { - fe := getFrontend(ctx) - fe.OnNotificationResponse(callback) -} diff --git a/v2/pkg/runtime/runtime.go b/v2/pkg/runtime/runtime.go index 6de5ea798..4702b439a 100644 --- a/v2/pkg/runtime/runtime.go +++ b/v2/pkg/runtime/runtime.go @@ -27,7 +27,6 @@ func getFrontend(ctx context.Context) frontend.Frontend { log.Fatalf("cannot call '%s': %s", funcName, contextError) return nil } - func getLogger(ctx context.Context) *logger.Logger { if ctx == nil { pc, _, _, _ := goruntime.Caller(1) diff --git a/v2/pkg/runtime/screen.go b/v2/pkg/runtime/screen.go index c4d526692..d92ed8308 100644 --- a/v2/pkg/runtime/screen.go +++ b/v2/pkg/runtime/screen.go @@ -1,14 +1,11 @@ package runtime -import ( - "context" - - "github.com/wailsapp/wails/v2/internal/frontend" -) +import "context" +import "github.com/wailsapp/wails/v2/internal/frontend" type Screen = frontend.Screen -// ScreenGetAll returns all screens +// ScreenGetAllScreens returns all screens func ScreenGetAll(ctx context.Context) ([]Screen, error) { appFrontend := getFrontend(ctx) return appFrontend.ScreenGetAll() diff --git a/v2/pkg/runtime/signal_linux.go b/v2/pkg/runtime/signal_linux.go deleted file mode 100644 index 6a7ed5db3..000000000 --- a/v2/pkg/runtime/signal_linux.go +++ /dev/null @@ -1,65 +0,0 @@ -//go:build linux - -package runtime - -/* -#include -#include -#include -#include - -static void fix_signal(int signum) -{ - struct sigaction st; - - if (sigaction(signum, NULL, &st) < 0) { - return; - } - st.sa_flags |= SA_ONSTACK; - sigaction(signum, &st, NULL); -} - -static void fix_all_signals() -{ -#if defined(SIGSEGV) - fix_signal(SIGSEGV); -#endif -#if defined(SIGBUS) - fix_signal(SIGBUS); -#endif -#if defined(SIGFPE) - fix_signal(SIGFPE); -#endif -#if defined(SIGABRT) - fix_signal(SIGABRT); -#endif -} -*/ -import "C" - -// ResetSignalHandlers resets signal handlers to allow panic recovery. -// -// On Linux, WebKit (used for the webview) may install signal handlers without -// the SA_ONSTACK flag, which prevents Go from properly recovering from panics -// caused by nil pointer dereferences or other memory access violations. -// -// Call this function immediately before code that might panic to ensure -// the signal handlers are properly configured for Go's panic recovery mechanism. -// -// Example usage: -// -// go func() { -// defer func() { -// if err := recover(); err != nil { -// log.Printf("Recovered from panic: %v", err) -// } -// }() -// runtime.ResetSignalHandlers() -// // Code that might panic... -// }() -// -// Note: This function only has an effect on Linux. On other platforms, -// it is a no-op. -func ResetSignalHandlers() { - C.fix_all_signals() -} diff --git a/v2/pkg/runtime/signal_other.go b/v2/pkg/runtime/signal_other.go deleted file mode 100644 index 3171a700c..000000000 --- a/v2/pkg/runtime/signal_other.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !linux - -package runtime - -// ResetSignalHandlers resets signal handlers to allow panic recovery. -// -// On Linux, WebKit (used for the webview) may install signal handlers without -// the SA_ONSTACK flag, which prevents Go from properly recovering from panics -// caused by nil pointer dereferences or other memory access violations. -// -// Call this function immediately before code that might panic to ensure -// the signal handlers are properly configured for Go's panic recovery mechanism. -// -// Note: This function only has an effect on Linux. On other platforms, -// it is a no-op. -func ResetSignalHandlers() { - // No-op on non-Linux platforms -} diff --git a/v2/pkg/runtime/window.go b/v2/pkg/runtime/window.go index 62345e2e4..ca28e9d30 100644 --- a/v2/pkg/runtime/window.go +++ b/v2/pkg/runtime/window.go @@ -179,8 +179,3 @@ func WindowSetBackgroundColour(ctx context.Context, R, G, B, A uint8) { } appFrontend.WindowSetBackgroundColour(col) } - -func WindowPrint(ctx context.Context) { - appFrontend := getFrontend(ctx) - appFrontend.WindowPrint() -} diff --git a/v2/pkg/templates/base/README.md b/v2/pkg/templates/base/README.md index abd8b9cd2..19df8c1b5 100644 --- a/v2/pkg/templates/base/README.md +++ b/v2/pkg/templates/base/README.md @@ -4,15 +4,17 @@ This is the official Wails $NAME template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/base/go.mod.tmpl b/v2/pkg/templates/base/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/base/go.mod.tmpl +++ b/v2/pkg/templates/base/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/generate/assets/common/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/generate/assets/lit-ts/frontend/index.tmpl.html b/v2/pkg/templates/generate/assets/lit-ts/frontend/index.tmpl.html index febcb76cb..4944992b5 100644 --- a/v2/pkg/templates/generate/assets/lit-ts/frontend/index.tmpl.html +++ b/v2/pkg/templates/generate/assets/lit-ts/frontend/index.tmpl.html @@ -4,6 +4,7 @@ {{.ProjectName}} + diff --git a/v2/pkg/templates/generate/assets/lit/frontend/index.tmpl.html b/v2/pkg/templates/generate/assets/lit/frontend/index.tmpl.html index fbe3eb240..e2db01c7d 100644 --- a/v2/pkg/templates/generate/assets/lit/frontend/index.tmpl.html +++ b/v2/pkg/templates/generate/assets/lit/frontend/index.tmpl.html @@ -4,6 +4,7 @@ {{.ProjectName}} + diff --git a/v2/pkg/templates/generate/assets/svelte-ts/frontend/index.tmpl.html b/v2/pkg/templates/generate/assets/svelte-ts/frontend/index.tmpl.html index 3dd212f2d..e88b655ef 100644 --- a/v2/pkg/templates/generate/assets/svelte-ts/frontend/index.tmpl.html +++ b/v2/pkg/templates/generate/assets/svelte-ts/frontend/index.tmpl.html @@ -3,6 +3,7 @@ + {{.ProjectName}} diff --git a/v2/pkg/templates/generate/assets/vue-ts/frontend/READ-THIS.md b/v2/pkg/templates/generate/assets/vue-ts/frontend/READ-THIS.md new file mode 100644 index 000000000..8c873dac3 --- /dev/null +++ b/v2/pkg/templates/generate/assets/vue-ts/frontend/READ-THIS.md @@ -0,0 +1,5 @@ +This template uses a work around as the default template does not compile due to +this issue: https://github.com/vuejs/core/issues/1228 + +In `tsconfig.json`, `isolatedModules` is set to `false` rather than `true` to +work around the issue. diff --git a/v2/pkg/templates/generate/assets/vue-ts/frontend/index.tmpl.html b/v2/pkg/templates/generate/assets/vue-ts/frontend/index.tmpl.html index cc259435b..5c0949b5e 100644 --- a/v2/pkg/templates/generate/assets/vue-ts/frontend/index.tmpl.html +++ b/v2/pkg/templates/generate/assets/vue-ts/frontend/index.tmpl.html @@ -4,6 +4,7 @@ {{.ProjectName}} +
diff --git a/v2/pkg/templates/generate/assets/vue/frontend/index.tmpl.html b/v2/pkg/templates/generate/assets/vue/frontend/index.tmpl.html index d45b7a8c4..b3d4289c3 100644 --- a/v2/pkg/templates/generate/assets/vue/frontend/index.tmpl.html +++ b/v2/pkg/templates/generate/assets/vue/frontend/index.tmpl.html @@ -4,6 +4,7 @@ {{.ProjectName}} +
diff --git a/v2/pkg/templates/generate/generate.go b/v2/pkg/templates/generate/generate.go index 6842dc196..3b01e5f2a 100644 --- a/v2/pkg/templates/generate/generate.go +++ b/v2/pkg/templates/generate/generate.go @@ -159,6 +159,7 @@ var templates = []*template{ } func main() { + rebuildRuntime() for _, t := range templates { diff --git a/v2/pkg/templates/generate/plain/README.md b/v2/pkg/templates/generate/plain/README.md index 9fcd85bdd..d3e22cd3d 100644 --- a/v2/pkg/templates/generate/plain/README.md +++ b/v2/pkg/templates/generate/plain/README.md @@ -4,15 +4,16 @@ This template uses plain JS / HTML and CSS. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. The frontend dev server will run -on http://localhost:34115. Open this in your browser to connect to your application. +To run in live development mode, run `wails dev` in the project directory. The +frontend dev server will run on http://localhost:34115. Open this in your +browser to connect to your application. ## Building For a production build, use `wails build`. - diff --git a/v2/pkg/templates/generate/plain/go.mod.tmpl b/v2/pkg/templates/generate/plain/go.mod.tmpl index f6d0daec4..e01fbe9e7 100644 --- a/v2/pkg/templates/generate/plain/go.mod.tmpl +++ b/v2/pkg/templates/generate/plain/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates.go b/v2/pkg/templates/templates.go index e18185520..d982454d0 100644 --- a/v2/pkg/templates/templates.go +++ b/v2/pkg/templates/templates.go @@ -72,6 +72,7 @@ type Options struct { // Template holds data relating to a template // including the metadata stored in template.json type Template struct { + // Template details Name string `json:"name"` ShortName string `json:"shortname"` @@ -99,6 +100,7 @@ func parseTemplate(template gofs.FS) (Template, error) { // List returns the list of available templates func List() ([]Template, error) { + // If the cache isn't loaded, load it if templateCache == nil { err := loadTemplateCache() @@ -112,6 +114,7 @@ func List() ([]Template, error) { // getTemplateByShortname returns the template with the given short name func getTemplateByShortname(shortname string) (Template, error) { + var result Template // If the cache isn't loaded, load it @@ -133,6 +136,7 @@ func getTemplateByShortname(shortname string) (Template, error) { // Loads the template cache func loadTemplateCache() error { + templatesFS, err := debme.FS(templates, "templates") if err != nil { return err @@ -186,16 +190,7 @@ func Install(options *Options) (bool, *Template, error) { return false, nil, err } options.TargetDir = targetDir - if fs.DirExists(options.TargetDir) { - // Check if directory is non-empty - entries, err := os.ReadDir(options.TargetDir) - if err != nil { - return false, nil, err - } - if len(entries) > 0 { - return false, nil, fmt.Errorf("cannot initialise project in non-empty directory: %s", options.TargetDir) - } - } else { + if !fs.DirExists(options.TargetDir) { err := fs.Mkdir(options.TargetDir) if err != nil { return false, nil, err @@ -314,9 +309,11 @@ func gitclone(options *Options) (string, error) { _, err = git.PlainClone(dirname, false, cloneOption) return dirname, err + } func generateIDEFiles(options *Options) error { + switch options.IDE { case "vscode": return generateVSCodeFiles(options) @@ -364,6 +361,7 @@ func generateVSCodeFiles(options *Options) error { options: options, } return installIDEFiles(ideoptions) + } func installIDEFiles(o ideOptions) error { diff --git a/v2/pkg/templates/templates/lit-ts/README.md b/v2/pkg/templates/templates/lit-ts/README.md index 98d4d0447..0074f9261 100644 --- a/v2/pkg/templates/templates/lit-ts/README.md +++ b/v2/pkg/templates/templates/lit-ts/README.md @@ -4,15 +4,17 @@ This is the official Wails Lit-TS template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/lit-ts/frontend/index.tmpl.html b/v2/pkg/templates/templates/lit-ts/frontend/index.tmpl.html index febcb76cb..4944992b5 100644 --- a/v2/pkg/templates/templates/lit-ts/frontend/index.tmpl.html +++ b/v2/pkg/templates/templates/lit-ts/frontend/index.tmpl.html @@ -4,6 +4,7 @@ {{.ProjectName}} + diff --git a/v2/pkg/templates/templates/lit-ts/frontend/src/my-element.ts b/v2/pkg/templates/templates/lit-ts/frontend/src/my-element.ts index af4e9ce20..27fd71e45 100644 --- a/v2/pkg/templates/templates/lit-ts/frontend/src/my-element.ts +++ b/v2/pkg/templates/templates/lit-ts/frontend/src/my-element.ts @@ -2,7 +2,6 @@ import {css, html, LitElement} from 'lit' import logo from './assets/images/logo-universal.png' import {Greet} from "../wailsjs/go/main/App"; import {customElement, property} from 'lit/decorators.js' -import './style.css'; /** * An example element. diff --git a/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/lit-ts/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/lit-ts/go.mod.tmpl b/v2/pkg/templates/templates/lit-ts/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/lit-ts/go.mod.tmpl +++ b/v2/pkg/templates/templates/lit-ts/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/lit/README.md b/v2/pkg/templates/templates/lit/README.md index dc8efed65..4e645979a 100644 --- a/v2/pkg/templates/templates/lit/README.md +++ b/v2/pkg/templates/templates/lit/README.md @@ -4,15 +4,17 @@ This is the official Wails Lit template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/lit/frontend/index.tmpl.html b/v2/pkg/templates/templates/lit/frontend/index.tmpl.html index fbe3eb240..e2db01c7d 100644 --- a/v2/pkg/templates/templates/lit/frontend/index.tmpl.html +++ b/v2/pkg/templates/templates/lit/frontend/index.tmpl.html @@ -4,6 +4,7 @@ {{.ProjectName}} + diff --git a/v2/pkg/templates/templates/lit/frontend/src/my-element.js b/v2/pkg/templates/templates/lit/frontend/src/my-element.js index 017632c09..ed65e2225 100644 --- a/v2/pkg/templates/templates/lit/frontend/src/my-element.js +++ b/v2/pkg/templates/templates/lit/frontend/src/my-element.js @@ -1,7 +1,6 @@ import {css, html, LitElement} from 'lit' import logo from './assets/images/logo-universal.png' import {Greet} from "../wailsjs/go/main/App"; -import './style.css'; /** * An example element. diff --git a/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/lit/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/lit/go.mod.tmpl b/v2/pkg/templates/templates/lit/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/lit/go.mod.tmpl +++ b/v2/pkg/templates/templates/lit/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/plain/README.md b/v2/pkg/templates/templates/plain/README.md index 3a71c4e9b..ab2d032e0 100644 --- a/v2/pkg/templates/templates/plain/README.md +++ b/v2/pkg/templates/templates/plain/README.md @@ -4,15 +4,17 @@ This template uses plain JS / HTML and CSS. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/plain/frontend/src/main.js b/v2/pkg/templates/templates/plain/frontend/src/main.js index e4945441d..3346d59ff 100644 --- a/v2/pkg/templates/templates/plain/frontend/src/main.js +++ b/v2/pkg/templates/templates/plain/frontend/src/main.js @@ -1,7 +1,6 @@ // Get input + focus let nameElement = document.getElementById("name"); nameElement.focus(); -import './main.css'; // Setup the greet function window.greet = function () { diff --git a/v2/pkg/templates/templates/plain/go.mod.tmpl b/v2/pkg/templates/templates/plain/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/plain/go.mod.tmpl +++ b/v2/pkg/templates/templates/plain/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/preact-ts/README.md b/v2/pkg/templates/templates/preact-ts/README.md index 923ecb002..e2651917a 100644 --- a/v2/pkg/templates/templates/preact-ts/README.md +++ b/v2/pkg/templates/templates/preact-ts/README.md @@ -4,15 +4,17 @@ This is the official Wails Preact-TS template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/preact-ts/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/preact-ts/go.mod.tmpl b/v2/pkg/templates/templates/preact-ts/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/preact-ts/go.mod.tmpl +++ b/v2/pkg/templates/templates/preact-ts/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/preact/README.md b/v2/pkg/templates/templates/preact/README.md index 8a354d53b..37bf31439 100644 --- a/v2/pkg/templates/templates/preact/README.md +++ b/v2/pkg/templates/templates/preact/README.md @@ -4,15 +4,17 @@ This is the official Wails Preact template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg b/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg index 23433fcf8..8d4155b1d 100644 --- a/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg +++ b/v2/pkg/templates/templates/preact/frontend/src/assets/preact.svg @@ -1 +1,10 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/preact/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/preact/go.mod.tmpl b/v2/pkg/templates/templates/preact/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/preact/go.mod.tmpl +++ b/v2/pkg/templates/templates/preact/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/react-ts/README.md b/v2/pkg/templates/templates/react-ts/README.md index d2169cc44..ba2b0cba1 100644 --- a/v2/pkg/templates/templates/react-ts/README.md +++ b/v2/pkg/templates/templates/react-ts/README.md @@ -4,15 +4,17 @@ This is the official Wails React-TS template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/react-ts/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/react-ts/go.mod.tmpl b/v2/pkg/templates/templates/react-ts/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/react-ts/go.mod.tmpl +++ b/v2/pkg/templates/templates/react-ts/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/react/README.md b/v2/pkg/templates/templates/react/README.md index 4db88f690..c841607ca 100644 --- a/v2/pkg/templates/templates/react/README.md +++ b/v2/pkg/templates/templates/react/README.md @@ -4,15 +4,17 @@ This is the official Wails React template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/react/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/react/go.mod.tmpl b/v2/pkg/templates/templates/react/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/react/go.mod.tmpl +++ b/v2/pkg/templates/templates/react/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/svelte-ts/README.md b/v2/pkg/templates/templates/svelte-ts/README.md index 2e62a374f..64b93d293 100644 --- a/v2/pkg/templates/templates/svelte-ts/README.md +++ b/v2/pkg/templates/templates/svelte-ts/README.md @@ -6,10 +6,11 @@ This is the official Wails Svelte-TS template. ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/svelte-ts/frontend/README.md b/v2/pkg/templates/templates/svelte-ts/frontend/README.md index bd0780d0a..88edd05da 100644 --- a/v2/pkg/templates/templates/svelte-ts/frontend/README.md +++ b/v2/pkg/templates/templates/svelte-ts/frontend/README.md @@ -1,65 +1,77 @@ # Svelte + TS + Vite -This template should help get you started developing with Svelte and TypeScript in Vite. +This template should help get you started developing with Svelte and TypeScript +in Vite. ## Recommended IDE Setup [VS Code](https://code.visualstudio.com/) -+ [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). +- [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). ## Need an official Svelte framework? -Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its -serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also +powered by Vite. Deploy anywhere with its serverless-first approach and adapt to +various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. ## Technical considerations **Why use this over SvelteKit?** -- It brings its own routing solution which might not be preferable for some users. -- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - `vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example. +- It brings its own routing solution which might not be preferable for some + users. +- It is first and foremost a framework that just happens to use Vite under the + hood, not a Vite app. `vite dev` and `vite build` wouldn't work in a SvelteKit + environment, for example. -This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account -the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the -other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte -project. +This template contains as little as possible to get started with Vite + +TypeScript + Svelte, while taking into account the developer experience with +regards to HMR and intellisense. It demonstrates capabilities on par with the +other `create-vite` templates and is a good starting point for beginners dipping +their toes into a Vite + Svelte project. -Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been -structured similarly to SvelteKit so that it is easy to migrate. +Should you later need the extended capabilities and extensibility provided by +SvelteKit, the template has been structured similarly to SvelteKit so that it is +easy to migrate. -**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or +`tsconfig.json`?** -Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash -references keeps the default TypeScript setting of accepting type information from the entire workspace, while also +Setting `compilerOptions.types` shuts out all other types not explicitly listed +in the configuration. Using triple-slash references keeps the default TypeScript +setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. **Why include `.vscode/extensions.json`?** -Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to -install the recommended extension upon opening the project. +Other templates indirectly recommend extensions via the README, but this file +allows VS Code to prompt the user to install the recommended extension upon +opening the project. **Why enable `allowJs` in the TS template?** -While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of -JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: -not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing -JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. +While `allowJs: false` would indeed prevent the use of `.js` files in the +project, it does not prevent the use of JavaScript syntax in `.svelte` files. In +addition, it would force `checkJs: false`, bringing the worst of both worlds: +not being able to guarantee the entire codebase is TypeScript, and also having +worse typechecking for the existing JavaScript. In addition, there are valid use +cases in which a mixed codebase may be relevant. **Why is HMR not preserving my local component state?** -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` -and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the -details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). +HMR state preservation comes with a number of gotchas! It has been disabled by +default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often +surprising behavior. You can read the details +[here](https://github.com/rixo/svelte-hmr#svelte-hmr). -If you have state that's important to retain within a component, consider creating an external store which would not be -replaced by HMR. +If you have state that's important to retain within a component, consider +creating an external store which would not be replaced by HMR. ```ts // store.ts // An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) +import { writable } from "svelte/store"; +export default writable(0); ``` diff --git a/v2/pkg/templates/templates/svelte-ts/frontend/index.tmpl.html b/v2/pkg/templates/templates/svelte-ts/frontend/index.tmpl.html index 3dd212f2d..e88b655ef 100644 --- a/v2/pkg/templates/templates/svelte-ts/frontend/index.tmpl.html +++ b/v2/pkg/templates/templates/svelte-ts/frontend/index.tmpl.html @@ -3,6 +3,7 @@ + {{.ProjectName}} diff --git a/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/svelte-ts/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/svelte-ts/go.mod.tmpl b/v2/pkg/templates/templates/svelte-ts/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/svelte-ts/go.mod.tmpl +++ b/v2/pkg/templates/templates/svelte-ts/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/svelte/README.md b/v2/pkg/templates/templates/svelte/README.md index eefcd5c4e..2815e0618 100644 --- a/v2/pkg/templates/templates/svelte/README.md +++ b/v2/pkg/templates/templates/svelte/README.md @@ -6,10 +6,11 @@ This is the official Wails Svelte template. ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/svelte/frontend/README.md b/v2/pkg/templates/templates/svelte/frontend/README.md index a346289c5..45c9d14f6 100644 --- a/v2/pkg/templates/templates/svelte/frontend/README.md +++ b/v2/pkg/templates/templates/svelte/frontend/README.md @@ -6,58 +6,69 @@ This template should help get you started developing with Svelte in Vite. [VS Code](https://code.visualstudio.com/) -+ [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). +- [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). ## Need an official Svelte framework? -Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its -serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also +powered by Vite. Deploy anywhere with its serverless-first approach and adapt to +various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. ## Technical considerations **Why use this over SvelteKit?** -- It brings its own routing solution which might not be preferable for some users. -- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - `vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example. +- It brings its own routing solution which might not be preferable for some + users. +- It is first and foremost a framework that just happens to use Vite under the + hood, not a Vite app. `vite dev` and `vite build` wouldn't work in a SvelteKit + environment, for example. -This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer -experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` -templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. +This template contains as little as possible to get started with Vite + Svelte, +while taking into account the developer experience with regards to HMR and +intellisense. It demonstrates capabilities on par with the other `create-vite` +templates and is a good starting point for beginners dipping their toes into a +Vite + Svelte project. -Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been -structured similarly to SvelteKit so that it is easy to migrate. +Should you later need the extended capabilities and extensibility provided by +SvelteKit, the template has been structured similarly to SvelteKit so that it is +easy to migrate. -**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or +`tsconfig.json`?** -Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash -references keeps the default TypeScript setting of accepting type information from the entire workspace, while also +Setting `compilerOptions.types` shuts out all other types not explicitly listed +in the configuration. Using triple-slash references keeps the default TypeScript +setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. **Why include `.vscode/extensions.json`?** -Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to -install the recommended extension upon opening the project. +Other templates indirectly recommend extensions via the README, but this file +allows VS Code to prompt the user to install the recommended extension upon +opening the project. **Why enable `checkJs` in the JS template?** -It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. -This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of +It is likely that most cases of changing variable types in runtime are likely to +be accidental, rather than deliberate. This provides advanced typechecking out +of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration. **Why is HMR not preserving my local component state?** -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` -and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the -details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). +HMR state preservation comes with a number of gotchas! It has been disabled by +default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often +surprising behavior. You can read the details +[here](https://github.com/rixo/svelte-hmr#svelte-hmr). -If you have state that's important to retain within a component, consider creating an external store which would not be -replaced by HMR. +If you have state that's important to retain within a component, consider +creating an external store which would not be replaced by HMR. ```js // store.js // An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) +import { writable } from "svelte/store"; +export default writable(0); ``` diff --git a/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/svelte/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/svelte/go.mod.tmpl b/v2/pkg/templates/templates/svelte/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/svelte/go.mod.tmpl +++ b/v2/pkg/templates/templates/svelte/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/vanilla-ts/README.md b/v2/pkg/templates/templates/vanilla-ts/README.md index 4d7bcd378..b312bdbed 100644 --- a/v2/pkg/templates/templates/vanilla-ts/README.md +++ b/v2/pkg/templates/templates/vanilla-ts/README.md @@ -4,15 +4,17 @@ This is the official Wails Vanilla-TS template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/vanilla-ts/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/vanilla-ts/go.mod.tmpl b/v2/pkg/templates/templates/vanilla-ts/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/vanilla-ts/go.mod.tmpl +++ b/v2/pkg/templates/templates/vanilla-ts/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/vanilla/README.md b/v2/pkg/templates/templates/vanilla/README.md index 397b08b92..d91813e5f 100644 --- a/v2/pkg/templates/templates/vanilla/README.md +++ b/v2/pkg/templates/templates/vanilla/README.md @@ -4,15 +4,17 @@ This is the official Wails Vanilla template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.d.ts b/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.d.ts index 336fb07aa..e0d662b38 100644 --- a/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.d.ts +++ b/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.d.ts @@ -52,10 +52,6 @@ export function EventsOnce(eventName: string, callback: (...data: any) => void): // unregisters the listener for the given event name. export function EventsOff(eventName: string): void; -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all event listeners. -export function EventsOffAll(): void; - // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) // logs the given message as a raw message export function LogPrint(message: string): void; @@ -130,7 +126,7 @@ export function WindowUnfullscreen(): void; // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) // Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; +export function WindowSetSize(width: number, height: number): Promise; // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) // Gets the width and height of the window. diff --git a/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.js b/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.js index b5ae16d56..2c3dafcc3 100644 --- a/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.js +++ b/v2/pkg/templates/templates/vanilla/frontend/wailsjs/runtime/runtime.js @@ -48,10 +48,6 @@ export function EventsOff(eventName) { return window.runtime.EventsOff(eventName); } -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - export function EventsOnce(eventName, callback) { EventsOnMultiple(eventName, callback, 1); } diff --git a/v2/pkg/templates/templates/vanilla/go.mod.tmpl b/v2/pkg/templates/templates/vanilla/go.mod.tmpl index 4b34d1668..dd7184879 100644 --- a/v2/pkg/templates/templates/vanilla/go.mod.tmpl +++ b/v2/pkg/templates/templates/vanilla/go.mod.tmpl @@ -1,6 +1,6 @@ module changeme -go 1.23.0 +go 1.18 require github.com/wailsapp/wails/v2 {{.WailsVersion}} diff --git a/v2/pkg/templates/templates/vue-ts/README.md b/v2/pkg/templates/templates/vue-ts/README.md index f0eaef091..910a6d962 100644 --- a/v2/pkg/templates/templates/vue-ts/README.md +++ b/v2/pkg/templates/templates/vue-ts/README.md @@ -4,15 +4,17 @@ This is the official Wails Vue-TS template. -You can configure the project by editing `wails.json`. More information about the project settings can be found -here: https://wails.io/docs/reference/project-config +You can configure the project by editing `wails.json`. More information about +the project settings can be found here: +https://wails.io/docs/reference/project-config ## Live Development -To run in live development mode, run `wails dev` in the project directory. This will run a Vite development -server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser -and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect -to this in your browser, and you can call your Go code from devtools. +To run in live development mode, run `wails dev` in the project directory. This +will run a Vite development server that will provide very fast hot reload of +your frontend changes. If you want to develop in a browser and have access to +your Go methods, there is also a dev server that runs on http://localhost:34115. +Connect to this in your browser, and you can call your Go code from devtools. ## Building diff --git a/v2/pkg/templates/templates/vue-ts/frontend/READ-THIS.md b/v2/pkg/templates/templates/vue-ts/frontend/READ-THIS.md new file mode 100644 index 000000000..8c873dac3 --- /dev/null +++ b/v2/pkg/templates/templates/vue-ts/frontend/READ-THIS.md @@ -0,0 +1,5 @@ +This template uses a work around as the default template does not compile due to +this issue: https://github.com/vuejs/core/issues/1228 + +In `tsconfig.json`, `isolatedModules` is set to `false` rather than `true` to +work around the issue. diff --git a/v2/pkg/templates/templates/vue-ts/frontend/README.md b/v2/pkg/templates/templates/vue-ts/frontend/README.md index 98f4a52ae..ef8be9b08 100644 --- a/v2/pkg/templates/templates/vue-ts/frontend/README.md +++ b/v2/pkg/templates/templates/vue-ts/frontend/README.md @@ -1,23 +1,30 @@ # Vue 3 + TypeScript + Vite -This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue -3 ` + + \ No newline at end of file diff --git a/v3/examples/contextmenus/main.go b/v3/examples/contextmenus/main.go new file mode 100644 index 000000000..a94b9d340 --- /dev/null +++ b/v3/examples/contextmenus/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "embed" + _ "embed" + "fmt" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Context Menu Demo", + Description: "A demo of the Context Menu API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + mainWindow := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Context Menu Demo", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Context Menu Demo", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + contextMenu := app.NewMenu() + contextMenu.Add("Click Me").OnClick(func(data *application.Context) { + fmt.Printf("Context menu data: %+v\n", data.ContextMenuData()) + }) + + globalContextMenu := app.NewMenu() + globalContextMenu.Add("Default context menu item").OnClick(func(data *application.Context) { + fmt.Printf("Context menu data: %+v\n", data.ContextMenuData()) + }) + + // Registering the menu with a window will make it available to that window only + mainWindow.RegisterContextMenu("test", contextMenu) + + // Registering the menu with the app will make it available to all windows + app.RegisterContextMenu("test", globalContextMenu) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/dialogs/main.go b/v3/examples/dialogs/main.go new file mode 100644 index 000000000..ff55949ae --- /dev/null +++ b/v3/examples/dialogs/main.go @@ -0,0 +1,333 @@ +package main + +import ( + _ "embed" + "log" + "os" + "runtime" + "strings" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "Dialogs Demo", + Description: "A demo of the dialogs API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + // Let's make a "Demo" menu + infoMenu := menu.AddSubmenu("Info") + infoMenu.Add("Info").OnClick(func(ctx *application.Context) { + dialog := app.InfoDialog() + dialog.SetTitle("Custom Title") + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + infoMenu.Add("Info (Title only)").OnClick(func(ctx *application.Context) { + dialog := app.InfoDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + infoMenu.Add("Info (Message only)").OnClick(func(ctx *application.Context) { + dialog := app.InfoDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + infoMenu.Add("Info (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.InfoDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(application.DefaultApplicationIcon) + dialog.Show() + }) + + questionMenu := menu.AddSubmenu("Question") + questionMenu.Add("Question (No default)").OnClick(func(ctx *application.Context) { + dialog := app.QuestionDialog() + dialog.SetMessage("No default button") + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() + }) + questionMenu.Add("Question (With Default)").OnClick(func(ctx *application.Context) { + dialog := app.QuestionDialog() + dialog.SetTitle("Quit") + dialog.SetMessage("You have unsaved work. Are you sure you want to quit?") + dialog.AddButton("Yes").OnClick(func() { + app.Quit() + }) + no := dialog.AddButton("No") + dialog.SetDefaultButton(no) + dialog.Show() + }) + questionMenu.Add("Question (With Cancel)").OnClick(func(ctx *application.Context) { + dialog := app.QuestionDialog(). + SetTitle("Update"). + SetMessage("The cancel button is selected when pressing escape") + download := dialog.AddButton("📥 Download") + download.OnClick(func() { + app.InfoDialog().SetMessage("Downloading...").Show() + }) + no := dialog.AddButton("Cancel") + dialog.SetDefaultButton(download) + dialog.SetCancelButton(no) + dialog.Show() + }) + questionMenu.Add("Question (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.QuestionDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(application.WailsLogoWhiteTransparent) + dialog.SetDefaultButton(dialog.AddButton("I like it!")) + dialog.AddButton("Not so keen...") + dialog.Show() + }) + + warningMenu := menu.AddSubmenu("Warning") + warningMenu.Add("Warning").OnClick(func(ctx *application.Context) { + dialog := app.WarningDialog() + dialog.SetTitle("Custom Title") + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + warningMenu.Add("Warning (Title only)").OnClick(func(ctx *application.Context) { + dialog := app.WarningDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + warningMenu.Add("Warning (Message only)").OnClick(func(ctx *application.Context) { + dialog := app.WarningDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + warningMenu.Add("Warning (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.WarningDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(application.DefaultApplicationIcon) + dialog.Show() + }) + + errorMenu := menu.AddSubmenu("Error") + errorMenu.Add("Error").OnClick(func(ctx *application.Context) { + dialog := app.ErrorDialog() + dialog.SetTitle("Ooops") + dialog.SetMessage("I accidentally the whole of Twitter") + dialog.Show() + }) + errorMenu.Add("Error (Title Only)").OnClick(func(ctx *application.Context) { + dialog := app.ErrorDialog() + dialog.SetTitle("Custom Title") + dialog.Show() + }) + errorMenu.Add("Error (Custom Message)").OnClick(func(ctx *application.Context) { + dialog := app.ErrorDialog() + dialog.SetMessage("This is a custom message") + dialog.Show() + }) + errorMenu.Add("Error (Custom Icon)").OnClick(func(ctx *application.Context) { + dialog := app.ErrorDialog() + dialog.SetTitle("Custom Icon Example") + dialog.SetMessage("Using a custom icon") + dialog.SetIcon(application.WailsLogoWhite) + dialog.Show() + }) + + openMenu := menu.AddSubmenu("Open") + openMenu.Add("Open File").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseFiles(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open File (Attach to window)").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + AttachToWindow(app.CurrentWindow()). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Multiple Files (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseFiles(true). + CanCreateDirectories(true). + ShowHiddenFiles(true). + PromptForMultipleSelection() + if len(result) > 0 { + app.InfoDialog().SetMessage(strings.Join(result, ",")).Show() + } else { + app.InfoDialog().SetMessage("No file selected").Show() + } + }) + openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseDirectories(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open Directory (Resolves Aliases)").OnClick(func(ctx *application.Context) { + result, _ := app.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + ResolvesAliases(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No directory selected").Show() + } + }) + openMenu.Add("Open File/Directory (Set Title)").OnClick(func(ctx *application.Context) { + dialog := app.OpenFileDialog(). + CanChooseDirectories(true). + CanCreateDirectories(true). + ResolvesAliases(true) + if runtime.GOOS == "darwin" { + dialog.SetMessage("Select a file/directory") + } else { + dialog.SetTitle("Select a file/directory") + } + + result, _ := dialog.PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No file/directory selected").Show() + } + }) + openMenu.Add("Open (Full Example)").OnClick(func(ctx *application.Context) { + cwd, _ := os.Getwd() + dialog := app.OpenFileDialog(). + SetTitle("Select a file"). + SetMessage("Select a file to open"). + SetButtonText("Let's do this!"). + SetDirectory(cwd). + CanCreateDirectories(true). + ResolvesAliases(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + CanSelectHiddenExtension(true). + AddFilter("Text Files", "*.txt; *.md"). + AddFilter("Video Files", "*.mov; *.mp4; *.avi") + + if runtime.GOOS == "darwin" { + dialog.SetMessage("Select a file") + } else { + dialog.SetTitle("Select a file") + } + + result, _ := dialog.PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } else { + app.InfoDialog().SetMessage("No file selected").Show() + } + }) + + saveMenu := menu.AddSubmenu("Save") + saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) { + result, _ := app.SaveFileDialog(). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Attach To WebviewWindow)").OnClick(func(ctx *application.Context) { + result, _ := app.SaveFileDialog(). + AttachToWindow(app.CurrentWindow()). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) { + result, _ := app.SaveFileDialog(). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) { + result, _ := app.SaveFileDialog(). + CanCreateDirectories(false). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } + }) + saveMenu.Add("Select File (Full Example)").OnClick(func(ctx *application.Context) { + result, _ := app.SaveFileDialog(). + CanCreateDirectories(false). + ShowHiddenFiles(true). + SetMessage("Select a file"). + SetDirectory("/Applications"). + SetButtonText("Let's do this!"). + SetFilename("README.md"). + HideExtension(true). + AllowsOtherFileTypes(true). + TreatsFilePackagesAsDirectories(true). + ShowHiddenFiles(true). + PromptForSingleSelection() + if result != "" { + app.InfoDialog().SetMessage(result).Show() + } + }) + + app.SetMenu(menu) + + app.NewWebviewWindow() + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} 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..1ec2339dd --- /dev/null +++ b/v3/examples/drag-n-drop/assets/index.html @@ -0,0 +1,25 @@ + + + + + Title + + + +

Drag-n-drop Demo

+
+Drop Files onto this window... +
+ + + + + \ No newline at end of file diff --git a/v3/examples/drag-n-drop/main.go b/v3/examples/drag-n-drop/main.go new file mode 100644 index 000000000..1346a2bac --- /dev/null +++ b/v3/examples/drag-n-drop/main.go @@ -0,0 +1,52 @@ +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", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + window := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Drag-n-drop Demo", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + EnableDragAndDrop: true, + }) + + window.On(events.FilesDropped, func(ctx *application.WindowEventContext) { + files := ctx.DroppedFiles() + app.Events.Emit(&application.WailsEvent{ + Name: "files", + Data: files, + }) + log.Printf("[Go] FilesDropped received: %+v\n", files) + }) + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/events/assets/index.html b/v3/examples/events/assets/index.html new file mode 100644 index 000000000..20993e735 --- /dev/null +++ b/v3/examples/events/assets/index.html @@ -0,0 +1,23 @@ + + + + + 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: +
+ + + + + \ No newline at end of file diff --git a/v3/examples/events/main.go b/v3/examples/events/main.go new file mode 100644 index 000000000..7f0c23653 --- /dev/null +++ b/v3/examples/events/main.go @@ -0,0 +1,66 @@ +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: "Events Demo", + Description: "A demo of the Events API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + app.Events.On("myevent", func(e *application.WailsEvent) { + log.Printf("[Go] WailsEvent received: %+v\n", e) + }) + + app.On(events.Mac.ApplicationDidFinishLaunching, func() { + for { + log.Println("Sending event") + app.Events.Emit(&application.WailsEvent{ + Name: "myevent", + Data: "hello", + }) + time.Sleep(10 * time.Second) + } + }) + + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Events Demo", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Events 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/kitchensink/main.go b/v3/examples/kitchensink/main.go new file mode 100644 index 000000000..509da3415 --- /dev/null +++ b/v3/examples/kitchensink/main.go @@ -0,0 +1,224 @@ +package main + +import ( + _ "embed" + "log" + "runtime" + "sync" + "time" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Menu Demo", + Description: "A demo of the menu system", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + /* + app.On(events.Mac.ApplicationDidFinishLaunching, func() { + println("ApplicationDidFinishLaunching") + }) + app.On(events.Mac.ApplicationWillTerminate, func() { + println("ApplicationWillTerminate") + }) + app.On(events.Mac.ApplicationDidBecomeActive, func() { + println("ApplicationDidBecomeActive") + }) + app.On(events.Mac.ApplicationDidChangeBackingProperties, func() { + println("ApplicationDidChangeBackingProperties") + }) + + app.On(events.Mac.ApplicationDidChangeEffectiveAppearance, func() { + println("ApplicationDidChangeEffectiveAppearance") + }) + app.On(events.Mac.ApplicationDidHide, func() { + println("ApplicationDidHide") + }) + + */ + + menuCallback := func(ctx *application.Context) { + menuItem := ctx.ClickedMenuItem() + menuItem.SetLabel("Clicked!") + } + + radioCallback := func(ctx *application.Context) { + menuItem := ctx.ClickedMenuItem() + menuItem.SetLabel(menuItem.Label() + "!") + } + + myMenu := app.NewMenu() + file1 := myMenu.Add("File") + file1.SetTooltip("Create New Tray Menu") + file1.OnClick(menuCallback) + myMenu.Add("Create New Tray Menu"). + SetAccelerator("CmdOrCtrl+N"). + SetTooltip("ROFLCOPTER!!!!"). + OnClick(func(ctx *application.Context) { + mySystray := app.NewSystemTray() + mySystray.SetLabel("Wails") + if runtime.GOOS == "darwin" { + mySystray.SetTemplateIcon(application.DefaultMacTemplateIcon) + } else { + mySystray.SetIcon(application.DefaultApplicationIcon) + } + myMenu := app.NewMenu() + myMenu.Add("Item 1") + myMenu.AddSeparator() + myMenu.Add("Kill this menu").OnClick(func(ctx *application.Context) { + mySystray.Destroy() + }) + mySystray.SetMenu(myMenu) + + }) + myMenu.Add("Not Enabled").SetEnabled(false) + myMenu.AddSeparator() + myMenu.AddCheckbox("My checkbox", true).OnClick(menuCallback) + myMenu.AddSeparator() + myMenu.AddRadio("Radio 1", true).OnClick(radioCallback) + myMenu.AddRadio("Radio 2", false).OnClick(radioCallback) + myMenu.AddRadio("Radio 3", false).OnClick(radioCallback) + + submenu := myMenu.AddSubmenu("Submenu") + submenu.Add("Submenu item 1").OnClick(menuCallback) + submenu.Add("Submenu item 2").OnClick(menuCallback) + submenu.Add("Submenu item 3").OnClick(menuCallback) + myMenu.AddSeparator() + file4 := myMenu.Add("File 4").OnClick(func(*application.Context) { + println("File 4 clicked") + }) + + myMenu.Add("Click to toggle").OnClick(func(*application.Context) { + enabled := file4.Enabled() + println("Enabled: ", enabled) + file4.SetEnabled(!enabled) + }) + myMenu.Add("File 5").OnClick(menuCallback) + + mySystray := app.NewSystemTray() + mySystray.SetLabel("Wails is awesome") + if runtime.GOOS == "darwin" { + mySystray.SetTemplateIcon(application.DefaultMacTemplateIcon) + } else { + mySystray.SetIcon(application.DefaultApplicationIcon) + } + mySystray.SetMenu(myMenu) + mySystray.SetIconPosition(application.NSImageLeading) + + myWindow := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Kitchen Sink", + Width: 600, + Height: 400, + AlwaysOnTop: true, + DisableResize: false, + BackgroundColour: &application.RGBA{ + Red: 255, + Green: 255, + Blue: 255, + Alpha: 30, + }, + StartState: application.WindowStateMaximised, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + Appearance: application.NSAppearanceNameDarkAqua, + }, + }) + /* + myWindow.On(events.Mac.WindowWillClose, func() { + println(myWindow.ID(), "WindowWillClose") + }) + myWindow.On(events.Mac.WindowDidResize, func() { + //w, h := myWindow.Size() + //println(myWindow.ID(), "WindowDidResize", w, h) + }) + myWindow.On(events.Mac.WindowDidMove, func() { + //x, y := myWindow.Position() + //println(myWindow.ID(), "WindowDidMove", x, y) + }) + myWindow.On(events.Mac.WindowDidMiniaturize, func() { + println(myWindow.ID(), "WindowDidMiniaturize") + }) + myWindow.On(events.Mac.WindowDidDeminiaturize, func() { + println(myWindow.ID(), "WindowDidDeminiaturize") + }) + myWindow.On(events.Mac.WindowDidBecomeKey, func() { + println(myWindow.ID(), "WindowDidBecomeKey") + }) + myWindow.On(events.Mac.WindowDidResignKey, func() { + println(myWindow.ID(), "WindowDidResignKey") + }) + myWindow.On(events.Mac.WindowDidBecomeMain, func() { + println(myWindow.ID(), "WindowDidBecomeMain") + }) + myWindow.On(events.Mac.WindowDidResignMain, func() { + println(myWindow.ID(), "WindowDidResignMain") + }) + myWindow.On(events.Mac.WindowWillEnterFullScreen, func() { + println(myWindow.ID(), "WindowWillEnterFullScreen") + }) + myWindow.On(events.Mac.WindowDidEnterFullScreen, func() { + println(myWindow.ID(), "WindowDidEnterFullScreen") + }) + myWindow.On(events.Mac.WindowWillExitFullScreen, func() { + println(myWindow.ID(), "WindowWillExitFullScreen") + }) + myWindow.On(events.Mac.WindowDidExitFullScreen, func() { + println(myWindow.ID(), "WindowDidExitFullScreen") + }) + myWindow.On(events.Mac.WindowWillEnterVersionBrowser, func() { + println(myWindow.ID(), "WindowWillEnterVersionBrowser") + }) + myWindow.On(events.Mac.WindowDidEnterVersionBrowser, func() { + println(myWindow.ID(), "WindowDidEnterVersionBrowser") + }) + myWindow.On(events.Mac.WindowWillExitVersionBrowser, func() { + println(myWindow.ID(), "WindowWillExitVersionBrowser") + }) + myWindow.On(events.Mac.WindowDidExitVersionBrowser, func() { + println(myWindow.ID(), "WindowDidExitVersionBrowser") + }) + */ + var myWindow2 *application.WebviewWindow + var myWindow2Lock sync.RWMutex + myWindow2 = app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "#2", + Width: 1024, + Height: 768, + AlwaysOnTop: false, + URL: "https://google.com", + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + }, + }) + //myWindow2.On(events.Mac.WindowDidMove, func() { + // myWindow2Lock.RLock() + // x, y := myWindow2.Position() + // println(myWindow2.ID(), "WindowDidMove: ", x, y) + // myWindow2Lock.RUnlock() + //}) + // + + go func() { + time.Sleep(5 * time.Second) + myWindow2Lock.RLock() + myWindow.SetTitle("Wooooo") + myWindow.SetAlwaysOnTop(true) + myWindow2.SetTitle("OMG") + myWindow2.SetURL("https://wails.io") + myWindow.SetMinSize(600, 600) + myWindow.SetMaxSize(650, 650) + myWindow.Center() + myWindow2Lock.RUnlock() + + }() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/menu/main.go b/v3/examples/menu/main.go new file mode 100644 index 000000000..3ca15cf47 --- /dev/null +++ b/v3/examples/menu/main.go @@ -0,0 +1,95 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + + app := application.New(application.Options{ + Name: "Menu Demo", + Description: "A demo of the menu system", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("Demo") + + // Disabled menu item + myMenu.Add("Not Enabled").SetEnabled(false) + + // Click callbacks + myMenu.Add("Click Me!").OnClick(func(ctx *application.Context) { + ctx.ClickedMenuItem().SetLabel("Thanks mate!") + }) + + // You can control the current window from the menu + myMenu.Add("Lock WebviewWindow Resize").OnClick(func(ctx *application.Context) { + if app.CurrentWindow().Resizable() { + app.CurrentWindow().SetResizable(false) + ctx.ClickedMenuItem().SetLabel("Unlock WebviewWindow Resize") + } else { + app.CurrentWindow().SetResizable(true) + ctx.ClickedMenuItem().SetLabel("Lock WebviewWindow Resize") + } + }) + + myMenu.AddSeparator() + + // Checkboxes will tell you their new state so you don't need to track it + myMenu.AddCheckbox("My checkbox", true).OnClick(func(context *application.Context) { + println("Clicked checkbox. Checked:", context.ClickedMenuItem().Checked()) + }) + myMenu.AddSeparator() + + // Callbacks can be shared. This is useful for radio groups + radioCallback := func(ctx *application.Context) { + menuItem := ctx.ClickedMenuItem() + menuItem.SetLabel(menuItem.Label() + "!") + } + + // Radio groups are created implicitly by placing radio items next to each other in a menu + myMenu.AddRadio("Radio 1", true).OnClick(radioCallback) + myMenu.AddRadio("Radio 2", false).OnClick(radioCallback) + myMenu.AddRadio("Radio 3", false).OnClick(radioCallback) + + // Submenus are also supported + submenu := myMenu.AddSubmenu("Submenu") + submenu.Add("Submenu item 1") + submenu.Add("Submenu item 2") + submenu.Add("Submenu item 3") + + myMenu.AddSeparator() + + beatles := myMenu.Add("Hello").OnClick(func(*application.Context) { + println("The beatles would be proud") + }) + myMenu.Add("Toggle the menuitem above").OnClick(func(*application.Context) { + if beatles.Enabled() { + beatles.SetEnabled(false) + beatles.SetLabel("Goodbye") + } else { + beatles.SetEnabled(true) + beatles.SetLabel("Hello") + } + }) + + app.SetMenu(menu) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/v3/examples/plain/main.go b/v3/examples/plain/main.go new file mode 100644 index 000000000..bc8406e82 --- /dev/null +++ b/v3/examples/plain/main.go @@ -0,0 +1,46 @@ +package main + +import ( + _ "embed" + "log" + "net/http" + + "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 + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Plain Bundle", + CSS: `body { background-color: rgba(255, 255, 255, 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%; }`, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + }, + URL: "/", + }) + + app.Events.On("clicked", func(_ *application.WailsEvent) { + println("clicked") + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/plugins/Taskfile.yml b/v3/examples/plugins/Taskfile.yml new file mode 100644 index 000000000..3c88c18c3 --- /dev/null +++ b/v3/examples/plugins/Taskfile.yml @@ -0,0 +1,41 @@ +version: '3' + +tasks: + + pre-build: + summary: Pre-build hooks + + post-build: + summary: Post-build hooks + + build: + summary: Builds the application + cmds: + - task: pre-build + - go build -gcflags=all="-N -l" -o bin/testapp main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + cmds: + # Generates both .ico and .icns files + - wails generate icons -input build/appicon.png + + build-prod: + summary: Creates a production build of the application + cmds: + - go build -tags production -ldflags="-w -s" -o bin/testapp + + package-darwin: + summary: Packages a production build of the application into a `.app` bundle + deps: + - build-prod + - generate-icons + cmds: + - mkdir -p buildtest.app/Contents/{MacOS,Resources} + - cp build/icons.icns buildtest.app/Contents/Resources + - cp bin/testapp buildtest.app/Contents/MacOS + - cp build/Info.plist buildtest.app/Contents \ No newline at end of file diff --git a/v3/examples/plugins/assets/index.html b/v3/examples/plugins/assets/index.html new file mode 100644 index 000000000..f77c18294 --- /dev/null +++ b/v3/examples/plugins/assets/index.html @@ -0,0 +1,10 @@ + + + + + Title + + +HELLO! + + \ No newline at end of file diff --git a/v3/examples/plugins/build/Info.dev.plist b/v3/examples/plugins/build/Info.dev.plist new file mode 100644 index 000000000..d6d28b179 --- /dev/null +++ b/v3/examples/plugins/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/plugins/build/Info.plist b/v3/examples/plugins/build/Info.plist new file mode 100644 index 000000000..ab571ad4f --- /dev/null +++ b/v3/examples/plugins/build/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My App + CFBundleExecutable + testapp + 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/plugins/build/appicon.png b/v3/examples/plugins/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/examples/plugins/build/appicon.png differ diff --git a/v3/examples/plugins/build/icons.icns b/v3/examples/plugins/build/icons.icns new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/plugins/build/icons.ico b/v3/examples/plugins/build/icons.ico new file mode 100644 index 000000000..e69de29bb diff --git a/v3/examples/plugins/build/info.json b/v3/examples/plugins/build/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/examples/plugins/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/plugins/build/wails.exe.manifest b/v3/examples/plugins/build/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/examples/plugins/build/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/examples/plugins/go.mod b/v3/examples/plugins/go.mod new file mode 100644 index 000000000..dd1ddaacd --- /dev/null +++ b/v3/examples/plugins/go.mod @@ -0,0 +1,41 @@ +module plugin_demo + +go 1.20 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/tools v0.1.12 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.3 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.21.0 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) + +replace github.com/wailsapp/wails/v3 => ../.. + +replace github.com/wailsapp/wails/v2 => ../../../v2 diff --git a/v3/examples/plugins/go.sum b/v3/examples/plugins/go.sum new file mode 100644 index 000000000..9477ac48a --- /dev/null +++ b/v3/examples/plugins/go.sum @@ -0,0 +1,83 @@ +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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +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/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-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +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/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= diff --git a/v3/examples/plugins/icon.ico b/v3/examples/plugins/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/examples/plugins/icon.ico differ diff --git a/v3/examples/plugins/icons.icns b/v3/examples/plugins/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/examples/plugins/icons.icns differ diff --git a/v3/examples/plugins/main.go b/v3/examples/plugins/main.go new file mode 100644 index 000000000..d5c0b66c1 --- /dev/null +++ b/v3/examples/plugins/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/browser" + "github.com/wailsapp/wails/v3/plugins/kvstore" + "github.com/wailsapp/wails/v3/plugins/log" + "github.com/wailsapp/wails/v3/plugins/single_instance" + "github.com/wailsapp/wails/v3/plugins/sqlite" + "github.com/wailsapp/wails/v3/plugins/start_at_login" + "os" + "plugin_demo/plugins/hashes" +) + +//go:embed assets/* +var assets embed.FS + +func main() { + + app := application.New(application.Options{ + Name: "Plugin Demo", + Description: "A demo of the plugins API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Plugins: map[string]application.Plugin{ + "hashes": hashes.NewPlugin(), + "browser": browser.NewPlugin(), + "log": log.NewPlugin(), + "sqlite": sqlite.NewPlugin(&sqlite.Config{ + DBFile: "test.db", + }), + "kvstore": kvstore.NewPlugin(&kvstore.Config{ + Filename: "store.json", + AutoSave: true, + }), + "single_instance": single_instance.NewPlugin(&single_instance.Config{ + // When true, the original app will be activated when a second instance is launched + ActivateAppOnSubsequentLaunch: true, + }), + "start_at_login": start_at_login.NewPlugin(), + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + window := app.NewWebviewWindow() + window.ToggleDevTools() + + err := app.Run() + + if err != nil { + println(err.Error()) + os.Exit(1) + } +} diff --git a/v3/examples/plugins/plugins/hashes/README.md b/v3/examples/plugins/plugins/hashes/README.md new file mode 100644 index 000000000..3e40be121 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/README.md @@ -0,0 +1,33 @@ +# Hashes Plugin + +This example plugin provides a way to generate hashes of strings. + +## Usage + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "hashes": hashes.NewPlugin(), + }, +``` + +You can then call the Generate method from the frontend: + +```js +wails + .Plugin("hashes", "Generate", "hello world") + .then((result) => console.log(result)); +``` + +This method returns a struct with the following fields: + +```typescript +interface Hashes { + md5: string; + sha1: string; + sha256: string; +} +``` + +A TypeScript definition file is provided for this interface. diff --git a/v3/examples/plugins/plugins/hashes/hashes.d.ts b/v3/examples/plugins/plugins/hashes/hashes.d.ts new file mode 100644 index 000000000..72b88e0f4 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/hashes.d.ts @@ -0,0 +1,8 @@ + +export namespace HashesPlugin { + export interface Hashes { + md5: string; + sha1: string; + sha256: string; + } +} \ No newline at end of file diff --git a/v3/examples/plugins/plugins/hashes/hashes.js b/v3/examples/plugins/plugins/hashes/hashes.js new file mode 100644 index 000000000..f9f8cf3b0 --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/hashes.js @@ -0,0 +1,4 @@ +// Generate takes a string and returns a number of hashes for it +export function Generate(input) { + return wails.Plugin("hashes","Generate",input); +} \ No newline at end of file diff --git a/v3/examples/plugins/plugins/hashes/plugin.go b/v3/examples/plugins/plugins/hashes/plugin.go new file mode 100644 index 000000000..afdcec84a --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/plugin.go @@ -0,0 +1,58 @@ +package hashes + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Plugin Setup ---------------- + +type Plugin struct{} + +func NewPlugin() *Plugin { + return &Plugin{} +} + +func (r *Plugin) Shutdown() {} + +func (r *Plugin) Name() string { + return "Hashes Plugin" +} + +func (r *Plugin) Init(_ *application.App) error { + return nil +} + +func (r *Plugin) CallableByJS() []string { + return []string{ + "Generate", + } +} + +func (r *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- + +type Hashes struct { + MD5 string `json:"md5"` + SHA1 string `json:"sha1"` + SHA256 string `json:"sha256"` +} + +func (r *Plugin) 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[:]), + } +} diff --git a/v3/examples/plugins/plugins/hashes/plugin.toml b/v3/examples/plugins/plugins/hashes/plugin.toml new file mode 100644 index 000000000..7835721be --- /dev/null +++ b/v3/examples/plugins/plugins/hashes/plugin.toml @@ -0,0 +1,10 @@ +# This is the plugin definition file for the "Hashes" plugin. + +Name = "Hashes" +Description = "Provides a method to generate a number of hashes." +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +License = "MIT" + + diff --git a/v3/examples/plugins/store.json b/v3/examples/plugins/store.json new file mode 100644 index 000000000..948bb52b0 --- /dev/null +++ b/v3/examples/plugins/store.json @@ -0,0 +1 @@ +{"url2":"https://reddit.com"} \ No newline at end of file diff --git a/v3/examples/plugins/test.db b/v3/examples/plugins/test.db new file mode 100644 index 000000000..156136694 Binary files /dev/null and b/v3/examples/plugins/test.db differ diff --git a/v3/examples/screen/assets/index.html b/v3/examples/screen/assets/index.html new file mode 100644 index 000000000..31b38e046 --- /dev/null +++ b/v3/examples/screen/assets/index.html @@ -0,0 +1,66 @@ + + + + + Screens Demo + + + + + + + \ No newline at end of file diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go new file mode 100644 index 000000000..e71f5c4be --- /dev/null +++ b/v3/examples/screen/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: "Screen Demo", + Description: "A demo of the Screen API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + app.NewWebviewWindowWithOptions(&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/systray/main.go b/v3/examples/systray/main.go new file mode 100644 index 000000000..2beb4501d --- /dev/null +++ b/v3/examples/systray/main.go @@ -0,0 +1,45 @@ +package main + +import ( + _ "embed" + "log" + "runtime" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Systray Demo", + Description: "A demo of the Systray API", + Mac: application.MacOptions{ + ActivationPolicy: application.ActivationPolicyAccessory, + }, + }) + + systemTray := app.NewSystemTray() + if runtime.GOOS == "darwin" { + systemTray.SetIcon(application.DefaultMacTemplateIcon) + } + + myMenu := app.NewMenu() + myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) { + app.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show() + }) + subMenu := myMenu.AddSubmenu("Submenu") + subMenu.Add("Click me!").OnClick(func(ctx *application.Context) { + ctx.ClickedMenuItem().SetLabel("Clicked!") + }) + myMenu.AddSeparator() + myMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systemTray.SetMenu(myMenu) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/examples/window/main.go b/v3/examples/window/main.go new file mode 100644 index 000000000..7a6e174c5 --- /dev/null +++ b/v3/examples/window/main.go @@ -0,0 +1,285 @@ +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", + Description: "A demo of the WebviewWindow API", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + app.On(events.Mac.ApplicationDidFinishLaunching, func() { + log.Println("ApplicationDidFinishLaunching") + }) + + currentWindow := func(fn func(window *application.WebviewWindow)) { + if app.CurrentWindow() != nil { + fn(app.CurrentWindow()) + } else { + println("Current WebviewWindow is nil") + } + } + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + windowCounter := 1 + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("New") + + myMenu.Add("New WebviewWindow"). + SetAccelerator("CmdOrCtrl+N"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindow(). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetPosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hide on Close"). + SetAccelerator("CmdOrCtrl+H"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{HideOnClose: true}). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetPosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New Frameless WebviewWindow"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(&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.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInset, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetPosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("

A MacTitleBarHiddenInset WebviewWindow example

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

A MacTitleBarHiddenInsetUnified WebviewWindow example

"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHidden)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetPosition(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.SetFullscreenButtonEnabled(false) + w.SetMaxSize(600, 600) + }) + }) + sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + width, height := w.Size() + app.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() + }) + }) + + sizeMenu.Add("Reset Min Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMinSize(0, 0) + }) + }) + + sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMaxSize(0, 0) + w.SetFullscreenButtonEnabled(true) + }) + }) + positionMenu := menu.AddSubmenu("Position") + positionMenu.Add("Set Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetPosition(0, 0) + }) + }) + positionMenu.Add("Set Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetPosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + x, y := w.Position() + app.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Center").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Center() + }) + }) + stateMenu := menu.AddSubmenu("State") + stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Minimise() + time.Sleep(2 * time.Second) + w.Restore() + }) + }) + stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Maximise() + }) + }) + stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Fullscreen() + }) + }) + stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.UnFullscreen() + }) + }) + stateMenu.Add("Restore").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Restore() + }) + }) + stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Hide() + time.Sleep(2 * time.Second) + w.Show() + }) + }) + stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAlwaysOnTop(true) + }) + }) + stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAlwaysOnTop(false) + }) + }) + stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetURL("https://google.com") + }) + }) + stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetURL("https://wails.io") + }) + }) + stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { + screen, err := app.GetPrimaryScreen() + if err != nil { + app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + app.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + }) + stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { + screens, err := app.GetScreens() + if err != nil { + app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + for _, screen := range screens { + msg := fmt.Sprintf("Screen: %+v", screen) + app.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + } + }) + stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + screen, err := w.GetScreen() + if err != nil { + app.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + app.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + }) + }) + app.NewWebviewWindow() + + app.SetMenu(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/windowjs/assets/index.html b/v3/examples/windowjs/assets/index.html new file mode 100644 index 000000000..f77c18294 --- /dev/null +++ b/v3/examples/windowjs/assets/index.html @@ -0,0 +1,10 @@ + + + + + Title + + +HELLO! + + \ No newline at end of file diff --git a/v3/examples/windowjs/main.go b/v3/examples/windowjs/main.go new file mode 100644 index 000000000..ff7f61cd7 --- /dev/null +++ b/v3/examples/windowjs/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "embed" + _ "embed" + "log" + "math/rand" + "strconv" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "WebviewWindow Javascript Demo", + Description: "A demo of the WebviewWindow API from Javascript", + Icon: nil, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + windowCounter := 1 + + newWindow := func() { + windowName := "WebviewWindow " + strconv.Itoa(windowCounter) + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Name: windowName, + }). + SetTitle(windowName). + SetPosition(rand.Intn(1000), rand.Intn(800)). + Show() + windowCounter++ + } + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("New") + + myMenu.Add("New WebviewWindow"). + SetAccelerator("CmdOrCtrl+N"). + OnClick(func(ctx *application.Context) { + newWindow() + }) + + newWindow() + newWindow() + + app.SetMenu(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/examples/wml/assets/index.html b/v3/examples/wml/assets/index.html new file mode 100644 index 000000000..26819800d --- /dev/null +++ b/v3/examples/wml/assets/index.html @@ -0,0 +1,19 @@ + + + + + Wails ML Demo + + +

Wails ML Demo

+

This application contains no Javascript!

+ + + + + + + +
Hover over me
+ + \ No newline at end of file diff --git a/v3/examples/wml/main.go b/v3/examples/wml/main.go new file mode 100644 index 000000000..1a9f4723b --- /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{ + FS: assets, + }, + }) + + app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{ + Title: "Wails ML Demo", + Width: 800, + Height: 600, + Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }) + + app.Events.On("button-pressed", func(_ *application.WailsEvent) { + println("Button Pressed!") + }) + app.Events.On("hover", func(_ *application.WailsEvent) { + 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..4ffb85ce6 --- /dev/null +++ b/v3/go.mod @@ -0,0 +1,74 @@ +module github.com/wailsapp/wails/v3 + +go 1.19 + +require ( + github.com/go-task/task/v3 v3.20.0 + github.com/google/go-cmp v0.5.9 + github.com/jackmordaunt/icns/v2 v2.2.1 + github.com/json-iterator/go v1.1.12 + github.com/leaanthony/clir v1.6.0 + github.com/leaanthony/gosod v1.0.3 + github.com/leaanthony/winicon v1.0.0 + github.com/matryer/is v1.4.0 + github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 + github.com/pkg/errors v0.9.1 + github.com/pterm/pterm v0.12.51 + github.com/samber/lo v1.37.0 + github.com/tc-hib/winres v0.1.6 + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 + modernc.org/sqlite v1.21.0 +) + +require ( + atomicgo.dev/cursor v0.1.1 // indirect + atomicgo.dev/keyboard v0.2.8 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gookit/color v1.5.2 // indirect + github.com/joho/godotenv v1.4.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/lithammer/fuzzysearch v1.1.5 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-zglob v0.0.4 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/radovskyb/watcher v1.0.7 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sajari/fuzzy v1.0.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/image v0.5.0 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/tools v0.1.12 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.3 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect + mvdan.cc/sh/v3 v3.6.0 // indirect +) + +replace github.com/wailsapp/wails/v2 => ../v2 diff --git a/v3/go.sum b/v3/go.sum new file mode 100644 index 000000000..ff474ab93 --- /dev/null +++ b/v3/go.sum @@ -0,0 +1,239 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= +atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.8 h1:Di09BitwZgdTV1hPyX/b9Cqxi8HVuJQwWivnZUEqlj4= +atomicgo.dev/keyboard v0.2.8/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.1 h1:a9Fqx6vQrHQ4CyiaLhktfTTelwGotmFWy8MNhyaohw8= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/task/v3 v3.20.0 h1:pTavuhP+AiEpKLzh5I6Lja9Ux7ypYO5QMsEPTbhYEDc= +github.com/go-task/task/v3 v3.20.0/go.mod h1:y7rWakbLR5gFElGgo6rA2dyr6vU/zNIDVfn3S4Of6OI= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.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.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= +github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= +github.com/jackmordaunt/icns/v2 v2.2.1 h1:MGklwYP2yohKn2Bw7XxlF69LZe98S1vUfl5OvAulPwg= +github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +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.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +github.com/leaanthony/clir v1.6.0 h1:mLV9thGkmqFqJU7ozmqlER8sBtGdZlz6H3gKsfIiB3o= +github.com/leaanthony/clir v1.6.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/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= +github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= +github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= +github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= +github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= +github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= +github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= +github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/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-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM= +github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.51 h1:iwhNG1FhQMgks+5kVyr/ClRk3WJCuL907nJN7RqmEpw= +github.com/pterm/pterm v0.12.51/go.mod h1:79BLm4vos2z+eOoHnDG7ZWuYtLaSStyaspKjGmSoxc4= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= +github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8= +github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= +golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +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 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.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= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= +mvdan.cc/sh/v3 v3.6.0 h1:gtva4EXJ0dFNvl5bHjcUEvws+KRcDslT8VKheTYkbGU= +mvdan.cc/sh/v3 v3.6.0/go.mod h1:U4mhtBLZ32iWhif5/lD+ygy1zrgaQhUu+XFy7C8+TTA= diff --git a/v3/internal/commands/bindings.go b/v3/internal/commands/bindings.go new file mode 100644 index 000000000..1c89544da --- /dev/null +++ b/v3/internal/commands/bindings.go @@ -0,0 +1,15 @@ +package commands + +import "github.com/wailsapp/wails/v3/internal/parser" + +type GenerateBindingsOptions struct { + Silent bool `name:"silent" description:"Silent mode"` + ModelsFilename string `name:"m" description:"The filename for the models file" default:"models.ts"` + BindingsFilename string `name:"b" description:"The filename for the bindings file" default:"bindings.js"` + ProjectDirectory string `name:"p" description:"The project directory" default:"."` + OutputDirectory string `name:"d" description:"The output directory" default:"."` +} + +func GenerateBindings(options *GenerateBindingsOptions) error { + return parser.GenerateBindingsAndModels(options.ProjectDirectory, options.OutputDirectory) +} diff --git a/v3/internal/commands/build.go b/v3/internal/commands/build.go new file mode 100644 index 000000000..b6c0677d1 --- /dev/null +++ b/v3/internal/commands/build.go @@ -0,0 +1,15 @@ +package commands + +import ( + "os" + + "github.com/pterm/pterm" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +func Build(_ *flags.Build) error { + pterm.Info.Println("`wails build` is an alias for `wails task build`. Use `wails task` for much better control over your builds.") + os.Args = []string{"wails", "task", "build"} + return RunTask(&RunTaskOptions{}, []string{}) +} diff --git a/v3/internal/commands/defaults.go b/v3/internal/commands/defaults.go new file mode 100644 index 000000000..0d7b6d0db --- /dev/null +++ b/v3/internal/commands/defaults.go @@ -0,0 +1,61 @@ +package commands + +import ( + _ "embed" + "os" +) + +//go:embed defaults/info.json +var Info []byte + +//go:embed defaults/wails.exe.manifest +var Manifest []byte + +//go:embed defaults/appicon.png +var AppIcon []byte + +//go:embed defaults/icons.ico +var IconsIco []byte + +//go:embed defaults/Info.plist +var InfoPlist []byte + +//go:embed defaults/Info.dev.plist +var InfoDevPlist []byte + +//go:embed defaults/icons.icns +var IconsIcns []byte + +var AllAssets = map[string][]byte{ + "info.json": Info, + "wails.exe.manifest": Manifest, + "appicon.png": AppIcon, + "icons.ico": IconsIco, + "Info.plist": InfoPlist, + "Info.dev.plist": InfoDevPlist, + "icons.icns": IconsIcns, +} + +type DefaultsOptions struct { + Dir string `description:"The directory to generate the files into"` +} + +func Defaults(options *DefaultsOptions) error { + dir := options.Dir + if dir == "" { + dir = "." + } + for filename, data := range AllAssets { + // If file exists, skip it + if _, err := os.Stat(dir + "/" + filename); err == nil { + println("Skipping " + filename) + continue + } + err := os.WriteFile(dir+"/"+filename, data, 0644) + if err != nil { + return err + } + println("Generated " + filename) + } + return nil +} diff --git a/v3/internal/commands/defaults/Info.dev.plist b/v3/internal/commands/defaults/Info.dev.plist new file mode 100644 index 000000000..28c8c4828 --- /dev/null +++ b/v3/internal/commands/defaults/Info.dev.plist @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.Name}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + \ No newline at end of file diff --git a/v3/internal/commands/defaults/Info.plist b/v3/internal/commands/defaults/Info.plist new file mode 100644 index 000000000..24490f5c8 --- /dev/null +++ b/v3/internal/commands/defaults/Info.plist @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Productname + CFBundleExecutable + {{.Name}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + icons + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + + \ No newline at end of file diff --git a/v3/internal/commands/defaults/appicon.png b/v3/internal/commands/defaults/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/commands/defaults/appicon.png differ diff --git a/v3/internal/commands/defaults/icon.ico b/v3/internal/commands/defaults/icon.ico new file mode 100644 index 000000000..bfa0690b7 Binary files /dev/null and b/v3/internal/commands/defaults/icon.ico differ diff --git a/v3/internal/commands/defaults/icons.icns b/v3/internal/commands/defaults/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/commands/defaults/icons.icns differ diff --git a/v2/examples/dragdrop-test/build/windows/icon.ico b/v3/internal/commands/defaults/icons.ico similarity index 100% rename from v2/examples/dragdrop-test/build/windows/icon.ico rename to v3/internal/commands/defaults/icons.ico diff --git a/v3/internal/commands/defaults/info.json b/v3/internal/commands/defaults/info.json new file mode 100644 index 000000000..1005eb5cb --- /dev/null +++ b/v3/internal/commands/defaults/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/internal/commands/defaults/wails.exe.manifest b/v3/internal/commands/defaults/wails.exe.manifest new file mode 100644 index 000000000..fb1ce5bde --- /dev/null +++ b/v3/internal/commands/defaults/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/v3/internal/commands/icons.go b/v3/internal/commands/icons.go new file mode 100644 index 000000000..4eee8734f --- /dev/null +++ b/v3/internal/commands/icons.go @@ -0,0 +1,126 @@ +package commands + +import ( + "bytes" + "fmt" + "image" + "os" + "strconv" + "strings" + + "github.com/jackmordaunt/icns/v2" + "github.com/leaanthony/winicon" +) + +type IconsOptions struct { + Example bool `description:"Generate example icon file (appicon.png) in the current directory"` + Input string `description:"The input image file"` + Sizes string `description:"The sizes to generate in .ico file (comma separated)" default:"256,128,64,48,32,16"` + WindowsFilename string `description:"The output filename for the Windows icon" default:"icon.ico"` + MacFilename string `description:"The output filename for the Mac icon bundle" default:"icons.icns"` +} + +func GenerateIcons(options *IconsOptions) error { + + if options.Example { + return generateExampleIcon() + } + + if options.Input == "" { + return fmt.Errorf("input is required") + } + + if options.WindowsFilename == "" && options.MacFilename == "" { + return fmt.Errorf("at least one output filename is required") + } + + // Parse sizes + var sizes = []int{256, 128, 64, 48, 32, 16} + var err error + if options.Sizes != "" { + sizes, err = parseSizes(options.Sizes) + if err != nil { + return err + } + } + iconData, err := os.ReadFile(options.Input) + if err != nil { + return err + } + + if options.WindowsFilename != "" { + err := generateWindowsIcon(iconData, sizes, options) + if err != nil { + return err + } + } + + if options.MacFilename != "" { + err := generateMacIcon(iconData, options) + if err != nil { + return err + } + } + + return nil +} + +func generateExampleIcon() error { + return os.WriteFile("appicon.png", []byte(AppIcon), 0644) +} + +func parseSizes(sizes string) ([]int, error) { + // split the input string by comma and confirm that each one is an integer + parsedSizes := strings.Split(sizes, ",") + var result []int + for _, size := range parsedSizes { + s, err := strconv.Atoi(size) + if err != nil { + return nil, err + } + if s == 0 { + continue + } + result = append(result, s) + } + + // put all integers in a slice and return + return result, nil +} + +func generateMacIcon(iconData []byte, options *IconsOptions) error { + + srcImg, _, err := image.Decode(bytes.NewBuffer(iconData)) + if err != nil { + return err + } + + dest, err := os.Create(options.MacFilename) + if err != nil { + return err + + } + defer func() { + err = dest.Close() + if err == nil { + return + } + }() + return icns.Encode(dest, srcImg) +} + +func generateWindowsIcon(iconData []byte, sizes []int, options *IconsOptions) error { + + var output bytes.Buffer + + err := winicon.GenerateIcon(bytes.NewBuffer(iconData), &output, sizes) + if err != nil { + return err + } + + err = os.WriteFile(options.WindowsFilename, output.Bytes(), 0644) + if err != nil { + return err + } + return nil +} diff --git a/v3/internal/commands/icons_test.go b/v3/internal/commands/icons_test.go new file mode 100644 index 000000000..a41e1bd6c --- /dev/null +++ b/v3/internal/commands/icons_test.go @@ -0,0 +1,285 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" +) + +func TestGenerateIcon(t *testing.T) { + tests := []struct { + name string + setup func() *IconsOptions + wantErr bool + test func() error + }{ + { + name: "should generate an icon when using the `example` flag", + setup: func() *IconsOptions { + return &IconsOptions{ + Example: true, + } + }, + wantErr: false, + test: func() error { + // the file `appicon.png` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.png") + if err != nil { + return err + } + defer func() { + err := os.Remove("appicon.png") + if err != nil { + panic(err) + } + }() + if f.IsDir() { + return fmt.Errorf("appicon.png is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.png is empty") + } + return nil + }, + }, + { + name: "should generate a .ico file when using the `input` flag and `windowsfilena me` flag", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + WindowsFilename: "appicon.ico", + } + }, + wantErr: false, + test: func() error { + // the file `appicon.ico` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.ico") + if err != nil { + return err + } + defer func() { + // Remove the file + err = os.Remove("appicon.ico") + if err != nil { + return + } + }() + if f.IsDir() { + return fmt.Errorf("appicon.ico is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.ico is empty") + } + // Remove the file + err = os.Remove("appicon.ico") + if err != nil { + return err + } + return nil + }, + }, + { + name: "should generate a .icns file when using the `input` flag and `macfilename` flag", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + MacFilename: "appicon.icns", + } + }, + wantErr: false, + test: func() error { + // the file `appicon.icns` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.icns") + if err != nil { + return err + } + defer func() { + // Remove the file + err = os.Remove("appicon.icns") + if err != nil { + panic(err) + } + }() + if f.IsDir() { + return fmt.Errorf("appicon.icns is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.icns is empty") + } + // Remove the file + + return nil + }, + }, + { + name: "should generate a small .ico file when using the `input` flag and `sizes` flag", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + Sizes: "16", + WindowsFilename: "appicon.ico", + } + }, + wantErr: false, + test: func() error { + // the file `appicon.ico` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("appicon.ico") + if err != nil { + return err + } + defer func() { + err := os.Remove("appicon.ico") + if err != nil { + panic(err) + } + }() + // The size of the file should be 571 bytes + if f.Size() != 571 { + return fmt.Errorf("appicon.ico is not the correct size. Got %d", f.Size()) + } + if f.IsDir() { + return fmt.Errorf("appicon.ico is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.ico is empty") + } + return nil + }, + }, + { + name: "should error if no input file is provided", + setup: func() *IconsOptions { + return &IconsOptions{} + }, + wantErr: true, + }, + { + name: "should error if neither mac or windows filename is provided", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + } + }, + wantErr: true, + }, + { + name: "should error if bad sizes provided", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + WindowsFilename: "appicon.ico", + Sizes: "bad", + } + }, + wantErr: true, + }, + { + name: "should ignore 0 size", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "appicon.png") + return &IconsOptions{ + Input: exampleIcon, + WindowsFilename: "appicon.ico", + Sizes: "0,16", + } + }, + wantErr: false, + test: func() error { + // Test the file exists and has 571 bytes + f, err := os.Stat("appicon.ico") + if err != nil { + return err + } + defer func() { + err := os.Remove("appicon.ico") + if err != nil { + panic(err) + } + }() + if f.Size() != 571 { + return fmt.Errorf("appicon.ico is not the correct size. Got %d", f.Size()) + } + if f.IsDir() { + return fmt.Errorf("appicon.ico is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("appicon.ico is empty") + } + return nil + }, + }, + { + name: "should error if the input file does not exist", + setup: func() *IconsOptions { + return &IconsOptions{ + Input: "doesnotexist.png", + WindowsFilename: "appicon.ico", + } + }, + wantErr: true, + }, + { + name: "should error if the input file is not a png", + setup: func() *IconsOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + return &IconsOptions{ + Input: thisFile, + WindowsFilename: "appicon.ico", + } + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := tt.setup() + err := GenerateIcons(options) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateIcon() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.test != nil { + if err := tt.test(); err != nil { + t.Errorf("GenerateIcon() test error = %v", err) + } + } + }) + } +} diff --git a/v3/internal/commands/init.go b/v3/internal/commands/init.go new file mode 100644 index 000000000..651d4a008 --- /dev/null +++ b/v3/internal/commands/init.go @@ -0,0 +1,43 @@ +package commands + +import ( + "fmt" + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/templates" + + "github.com/pterm/pterm" +) + +func Init(options *flags.Init) error { + if options.List { + return printTemplates() + } + + if options.Quiet { + pterm.DisableOutput() + } + + if options.ProjectName == "" { + return fmt.Errorf("please use the -n flag to specify a project name") + } + + if !templates.ValidTemplateName(options.TemplateName) { + return fmt.Errorf("invalid template name: %s. Use -l flag to view available templates", options.TemplateName) + } + + return templates.Install(options) +} + +func printTemplates() error { + defaultTemplates := templates.GetDefaultTemplates() + + pterm.DefaultSection.Println("Available templates") + + table := pterm.TableData{{"Name", "Description"}} + for _, template := range defaultTemplates { + table = append(table, []string{template.Name, template.Description}) + } + err := pterm.DefaultTable.WithHasHeader(true).WithBoxed(true).WithData(table).Render() + pterm.Println() + return err +} diff --git a/v3/internal/commands/plugins.go b/v3/internal/commands/plugins.go new file mode 100644 index 000000000..a42790ab3 --- /dev/null +++ b/v3/internal/commands/plugins.go @@ -0,0 +1,45 @@ +package commands + +import ( + "github.com/wailsapp/wails/v3/internal/flags" + "github.com/wailsapp/wails/v3/internal/plugins" + "strings" + + "github.com/pterm/pterm" +) + +func toCamelCasePlugin(s string) string { + var camelCase string + var capitalize = true + + for _, c := range s { + if c >= 'a' && c <= 'z' || c >= '0' && c <= '9' { + if capitalize { + camelCase += strings.ToUpper(string(c)) + capitalize = false + } else { + camelCase += string(c) + } + } else if c >= 'A' && c <= 'Z' { + camelCase += string(c) + capitalize = false + } else { + capitalize = true + } + } + + return camelCase + "Plugin" +} + +func PluginInit(options *flags.PluginInit) error { + + if options.Quiet { + pterm.DisableOutput() + } + + if options.PackageName == "" { + options.PackageName = toCamelCasePlugin(options.Name) + } + + return plugins.Install(options) +} diff --git a/v3/internal/commands/syso.go b/v3/internal/commands/syso.go new file mode 100644 index 000000000..70821789f --- /dev/null +++ b/v3/internal/commands/syso.go @@ -0,0 +1,123 @@ +package commands + +import ( + "fmt" + "os" + "runtime" + + "github.com/tc-hib/winres" + "github.com/tc-hib/winres/version" +) + +type SysoOptions struct { + Example bool `description:"Generate example manifest & info files"` + Manifest string `description:"The manifest file"` + Info string `description:"The info.json file"` + Icon string `description:"The icon file"` + Out string `description:"The output filename for the syso file"` + Arch string `description:"The target architecture"` +} + +func (i *SysoOptions) Default() *SysoOptions { + return &SysoOptions{ + Arch: runtime.GOARCH, + } +} + +func GenerateSyso(options *SysoOptions) error { + + // Generate example files? + if options.Example { + return generateExampleSyso() + } + + if options.Manifest == "" { + return fmt.Errorf("manifest is required") + } + if options.Icon == "" { + return fmt.Errorf("icon is required") + } + + rs := winres.ResourceSet{} + + // Process Icon + iconFile, err := os.Open(options.Icon) + if err != nil { + return err + } + defer iconFile.Close() + ico, err := winres.LoadICO(iconFile) + if err != nil { + return fmt.Errorf("couldn't load icon '%s': %v", options.Icon, err) + } + err = rs.SetIcon(winres.RT_ICON, ico) + if err != nil { + return err + } + + // Process Manifest + manifestData, err := os.ReadFile(options.Manifest) + if err != nil { + return err + } + + xmlData, err := winres.AppManifestFromXML(manifestData) + if err != nil { + return err + } + rs.SetManifest(xmlData) + + if options.Info != "" { + infoData, err := os.ReadFile(options.Info) + if err != nil { + return err + } + if len(infoData) != 0 { + var v version.Info + if err := v.UnmarshalJSON(infoData); err != nil { + return err + } + rs.SetVersionInfo(v) + } + } + + targetFile := options.Out + if targetFile == "" { + targetFile = "rsrc_windows_" + options.Arch + ".syso" + } + fout, err := os.Create(targetFile) + if err != nil { + return err + } + defer fout.Close() + + archs := map[string]winres.Arch{ + "amd64": winres.ArchAMD64, + "arm64": winres.ArchARM64, + "386": winres.ArchI386, + } + targetArch, supported := archs[options.Arch] + if !supported { + return fmt.Errorf("arch '%s' not supported", options.Arch) + } + + err = rs.WriteObject(fout, targetArch) + if err != nil { + return err + } + return nil +} + +func generateExampleSyso() error { + // Generate example info.json + err := os.WriteFile("info.json", Info, 0644) + if err != nil { + return err + } + // Generate example manifest + err = os.WriteFile("wails.exe.manifest", Manifest, 0644) + if err != nil { + return err + } + return nil +} diff --git a/v3/internal/commands/syso_test.go b/v3/internal/commands/syso_test.go new file mode 100644 index 000000000..a1e18e3dc --- /dev/null +++ b/v3/internal/commands/syso_test.go @@ -0,0 +1,189 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "testing" +) + +func TestGenerateSyso(t *testing.T) { + tests := []struct { + name string + setup func() *SysoOptions + wantErr bool + test func() error + }{ + { + name: "should generate example info and manifest files when using the `example` flag", + setup: func() *SysoOptions { + return &SysoOptions{ + Example: true, + } + }, + wantErr: false, + test: func() error { + // the file `info.json` should be created in the current directory + // check for the existence of the file + f, err := os.Stat("info.json") + if err != nil { + return err + } + m, err := os.Stat("wails.exe.manifest") + if err != nil { + return err + } + defer func() { + err := os.Remove("info.json") + err2 := os.Remove("wails.exe.manifest") + if err != nil { + panic(err) + } + if err2 != nil { + panic(err2) + } + }() + if f.IsDir() { + return fmt.Errorf("info.json is a directory") + } + if f.Size() == 0 { + return fmt.Errorf("info.json is empty") + } + if m.IsDir() { + return fmt.Errorf("wails.exe.manifest is a directory") + } + if m.Size() == 0 { + return fmt.Errorf("wails.exe.manifest is empty") + } + return nil + }, + }, + { + name: "should error if manifest filename is not provided", + setup: func() *SysoOptions { + return &SysoOptions{ + Manifest: "", + } + }, + wantErr: true, + }, + { + name: "should error if icon filename is not provided", + setup: func() *SysoOptions { + return &SysoOptions{ + Manifest: "test.manifest", + Icon: "", + } + }, + wantErr: true, + }, + { + name: "should error if icon filename does not exist", + setup: func() *SysoOptions { + return &SysoOptions{ + Manifest: "test.manifest", + Icon: "icon.ico", + } + }, + wantErr: true, + }, + { + name: "should error if icon is wrong format", + setup: func() *SysoOptions { + _, thisFile, _, _ := runtime.Caller(1) + return &SysoOptions{ + Manifest: "test.manifest", + Icon: thisFile, + } + }, + wantErr: true, + }, + { + name: "should error if manifest filename does not exist", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + return &SysoOptions{ + Manifest: "test.manifest", + Icon: exampleIcon, + } + }, + wantErr: true, + }, + { + name: "should error if manifest is wrong format", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + return &SysoOptions{ + Manifest: exampleIcon, + Icon: exampleIcon, + } + }, + wantErr: true, + }, + { + name: "should error if info file does not exist", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + // Get the path to the example manifest + exampleManifest := filepath.Join(localDir, "examples", "wails.exe.manifest") + return &SysoOptions{ + Manifest: exampleManifest, + Icon: exampleIcon, + Info: "doesnotexist.json", + } + }, + wantErr: true, + }, + { + name: "should error if info file is wrong format", + setup: func() *SysoOptions { + // Get the directory of this file + _, thisFile, _, _ := runtime.Caller(1) + localDir := filepath.Dir(thisFile) + // Get the path to the example icon + exampleIcon := filepath.Join(localDir, "examples", "icon.ico") + // Get the path to the example manifest + exampleManifest := filepath.Join(localDir, "examples", "wails.exe.manifest") + return &SysoOptions{ + Manifest: exampleManifest, + Icon: exampleIcon, + Info: thisFile, + } + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := tt.setup() + err := GenerateSyso(options) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateSyso() error = %v, wantErr %v", err, tt.wantErr) + return + } + if (err != nil) && tt.wantErr { + println(err.Error()) + return + } + if tt.test != nil { + if err := tt.test(); err != nil { + t.Errorf("GenerateSyso() test error = %v", err) + } + } + }) + } +} diff --git a/v3/internal/commands/task.go b/v3/internal/commands/task.go new file mode 100644 index 000000000..c80d677e6 --- /dev/null +++ b/v3/internal/commands/task.go @@ -0,0 +1,175 @@ +package commands + +import ( + "context" + "fmt" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/pterm/pterm" + + "github.com/go-task/task/v3/args" + + "github.com/go-task/task/v3" + "github.com/go-task/task/v3/taskfile" +) + +type RunTaskOptions struct { + Name string `pos:"1"` + Help bool `name:"h" description:"shows Task usage"` + Init bool `name:"i" description:"creates a new Taskfile.yml"` + List bool `name:"list" description:"tasks with description of current Taskfile"` + ListAll bool `name:"list-all" description:"lists tasks with or without a description"` + ListJSON bool `name:"json" description:"formats task list as json"` + Status bool `name:"status" description:"exits with non-zero exit code if any of the given tasks is not up-to-date"` + Force bool `name:"f" description:"forces execution even when the task is up-to-date"` + Watch bool `name:"w" description:"enables watch of the given task"` + Verbose bool `name:"v" description:"enables verbose mode"` + Silent bool `name:"s" description:"disables echoing"` + Parallel bool `name:"p" description:"executes tasks provided on command line in parallel"` + Dry bool `name:"dry" description:"compiles and prints tasks in the order that they would be run, without executing them"` + Summary bool `name:"summary" description:"show summary about a task"` + ExitCode bool `name:"x" description:"pass-through the exit code of the task command"` + Dir string `name:"dir" description:"sets directory of execution"` + EntryPoint string `name:"taskfile" description:"choose which Taskfile to run."` + OutputName string `name:"output" description:"sets output style: [interleaved|group|prefixed]"` + OutputGroupBegin string `name:"output-group-begin" description:"message template to print before a task's grouped output"` + OutputGroupEnd string `name:"output-group-end" description:"message template to print after a task's grouped output"` + Color bool `name:"c" description:"colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable" default:"true"` + Concurrency int `name:"C" description:"limit number tasks to run concurrently"` + Interval int64 `name:"interval" description:"interval to watch for changes"` +} + +func RunTask(options *RunTaskOptions, otherArgs []string) error { + + if options.Init { + wd, err := os.Getwd() + if err != nil { + return err + } + return task.InitTaskfile(os.Stdout, wd) + } + + if options.Dir != "" && options.EntryPoint != "" { + return fmt.Errorf("task: You can't set both --dir and --taskfile") + } + + if options.EntryPoint != "" { + options.Dir = filepath.Dir(options.EntryPoint) + options.EntryPoint = filepath.Base(options.EntryPoint) + } + + if options.OutputName != "group" { + if options.OutputGroupBegin != "" { + return fmt.Errorf("task: You can't set --output-group-begin without --output=group") + } + if options.OutputGroupBegin != "" { + return fmt.Errorf("task: You can't set --output-group-end without --output=group") + } + } + + e := task.Executor{ + Force: options.Force, + Watch: options.Watch, + Verbose: options.Verbose, + Silent: options.Silent, + Dir: options.Dir, + Dry: options.Dry, + Entrypoint: options.EntryPoint, + Summary: options.Summary, + Parallel: options.Parallel, + Color: options.Color, + Concurrency: options.Concurrency, + Interval: time.Duration(options.Interval) * time.Second, + + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + + OutputStyle: taskfile.Output{ + Name: options.OutputName, + Group: taskfile.OutputGroup{ + Begin: options.OutputGroupBegin, + End: options.OutputGroupEnd, + }, + }, + } + + var listOptions = task.NewListOptions(options.List, options.ListAll, options.ListJSON) + if err := listOptions.Validate(); err != nil { + log.Fatal(err) + } + + if (listOptions.ShouldListTasks()) && options.Silent { + e.ListTaskNames(options.ListAll) + return nil + } + + if err := e.Setup(); err != nil { + log.Fatal(err) + } + v, err := e.Taskfile.ParsedVersion() + if err != nil { + return err + } + + if listOptions.ShouldListTasks() { + if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil { + os.Exit(1) + } + return nil + } + + var ( + calls []taskfile.Call + globals *taskfile.Vars + ) + + var taskAndVars []string + for _, taskAndVar := range os.Args[2:] { + if taskAndVar == "--" { + break + } + taskAndVars = append(taskAndVars, taskAndVar) + } + + if len(taskAndVars) > 0 && len(otherArgs) > 0 { + if taskAndVars[0] == otherArgs[0] { + otherArgs = otherArgs[1:] + } + } + + if v >= 3.0 { + calls, globals = args.ParseV3(taskAndVars...) + } else { + calls, globals = args.ParseV2(taskAndVars...) + } + + globals.Set("CLI_ARGS", taskfile.Var{Static: strings.Join(otherArgs, " ")}) + e.Taskfile.Vars.Merge(globals) + + if !options.Watch { + e.InterceptInterruptSignals() + } + + ctx := context.Background() + + if options.Status { + return e.Status(ctx, calls...) + } + + if err := e.Run(ctx, calls...); err != nil { + pterm.Error.Println(err.Error()) + + if options.ExitCode { + if err, ok := err.(*task.TaskRunError); ok { + os.Exit(err.ExitCode()) + } + } + os.Exit(1) + } + return nil +} diff --git a/v3/internal/commands/task_test.go b/v3/internal/commands/task_test.go new file mode 100644 index 000000000..a88304272 --- /dev/null +++ b/v3/internal/commands/task_test.go @@ -0,0 +1,39 @@ +package commands + +import "testing" + +func TestBuild(t *testing.T) { + type args struct { + options *RunTaskOptions + otherArgs []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "should error if task name not provided", + args: args{ + options: &RunTaskOptions{}, + }, + wantErr: true, + }, + { + name: "should work if task name provided", + args: args{ + options: &RunTaskOptions{ + Name: "build", + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := RunTask(tt.args.options, tt.args.otherArgs); (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/v3/internal/debug/debug.go b/v3/internal/debug/debug.go new file mode 100644 index 000000000..47db8b626 --- /dev/null +++ b/v3/internal/debug/debug.go @@ -0,0 +1,56 @@ +package debug + +import ( + "github.com/samber/lo" + "path/filepath" + "runtime" +) + +import "runtime/debug" + +// Why go doesn't provide this as a map already is beyond me. +var buildSettings = map[string]string{} +var LocalModulePath = "" + +func init() { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return + } + buildSettings = lo.Associate(buildInfo.Settings, func(setting debug.BuildSetting) (string, string) { + return setting.Key, setting.Value + }) + if isLocalBuild() { + modulePath := RelativePath("..", "..", "..") + LocalModulePath, _ = filepath.Abs(modulePath) + } +} + +func isLocalBuild() bool { + return buildSettings["vcs.modified"] == "true" +} + +// RelativePath returns a qualified path created by joining the +// directory of the calling file and the given relative path. +// +// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal` +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/flags/build.go b/v3/internal/flags/build.go new file mode 100644 index 000000000..260c6067d --- /dev/null +++ b/v3/internal/flags/build.go @@ -0,0 +1,5 @@ +package flags + +type Build struct { + Common +} 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..182d98184 --- /dev/null +++ b/v3/internal/flags/init.go @@ -0,0 +1,12 @@ +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"` +} diff --git a/v3/internal/flags/plugin.go b/v3/internal/flags/plugin.go new file mode 100644 index 000000000..62c0dd968 --- /dev/null +++ b/v3/internal/flags/plugin.go @@ -0,0 +1,9 @@ +package flags + +type PluginInit struct { + Name string `name:"n" description:"Name of plugin" default:"example_plugin"` + Description string `name:"d" description:"Description of plugin" default:"Example plugin"` + PackageName string `name:"p" description:"Package name for plugin" default:""` + OutputDir string `name:"o" description:"Output directory" default:"."` + Quiet bool `name:"q" description:"Suppress output to console"` +} diff --git a/v3/internal/parser/README.md b/v3/internal/parser/README.md new file mode 100644 index 000000000..4f672b2e6 --- /dev/null +++ b/v3/internal/parser/README.md @@ -0,0 +1,71 @@ +# Parser + +This package contains the static analyser used for parsing Wails projects so +that we may: + +- Generate the bindings for the frontend +- Generate Typescript definitions for the structs used by the bindings + +## Implemented + +- [ ] Bound types + + - [x] Struct Literal Pointer + - [ ] Variable + - [ ] Assignment + - [x] Struct Literal Pointer + - [ ] Function + - [ ] Same package + - [ ] Different package + - [ ] Function + +- [x] Parsing of bound methods + - [x] Method names + - [x] Method parameters + - [x] Zero parameters + - [x] Single input parameter + - [x] Single output parameter + - [x] Multiple input parameters + - [x] Multiple output parameters + - [x] Named output parameters + - [x] int/8/16/32/64 + - [x] Pointer + - [x] uint/8/16/32/64 + - [x] Pointer + - [x] float + - [x] Pointer + - [x] string + - [x] Pointer + - [x] bool + - [x] Pointer + - [x] Struct + - [x] Pointer + - [x] Slices + - [x] Pointer + - [x] Maps + - [x] Pointer +- [x] Model Parsing + - [x] In same package + - [x] In different package + - [x] Multiple named fields, e.g. one,two,three string + - [x] Scalars + - [x] Arrays + - [x] Maps + - [x] Structs + - [x] Recursive + - [x] Anonymous +- [ ] Generation of models + - [x] Scalars + - [ ] Arrays + - [ ] Maps + - [x] Structs +- [ ] Generation of bindings + +## Limitations + +There are many ways to write a Go program so there are many program structures +that we would need to support. This is a work in progress and will be improved +over time. The current limitations are: + +- The call to `application.New()` must be in the `main` package +- Bound structs must be declared as struct literals diff --git a/v3/internal/parser/bindings.go b/v3/internal/parser/bindings.go new file mode 100644 index 000000000..a5562648a --- /dev/null +++ b/v3/internal/parser/bindings.go @@ -0,0 +1,285 @@ +package parser + +import ( + "sort" + "strconv" + "strings" + + "github.com/samber/lo" +) + +const header = `// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +` + +const helperTemplate = `function {{structName}}(method) { + return { + packageName: "{{packageName}}", + serviceName: "{{structName}}", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} +` + +func GenerateHelper(packageName, structName string) string { + result := strings.ReplaceAll(helperTemplate, "{{packageName}}", packageName) + result = strings.ReplaceAll(result, "{{structName}}", structName) + return result +} + +const bindingTemplate = ` +/** + * {{structName}}.{{methodName}} + * Comments + * @param name {string} + * @returns {Promise} + **/ +function {{methodName}}({{inputs}}) { + return wails.Call({{structName}}("{{methodName}}"{{args}})); +} +` + +var reservedWords = []string{ + "abstract", + "arguments", + "await", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "double", + "else", + "enum", + "eval", + "export", + "extends", + "false", + "final", + "finally", + "float", + "for", + "function", + "goto", + "if", + "implements", + "import", + "in", + "instanceof", + "int", + "interface", + "let", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "typeof", + "var", + "void", + "volatile", + "while", + "with", + "yield", + "object", +} + +func sanitiseJSVarName(name string) string { + // if the name is a reserved word, prefix with an + // underscore + if lo.Contains(reservedWords, name) { + return "_" + name + } + return name +} + +func GenerateBinding(structName string, method *BoundMethod) (string, []string) { + var models []string + result := strings.ReplaceAll(bindingTemplate, "{{structName}}", structName) + result = strings.ReplaceAll(result, "{{methodName}}", method.Name) + comments := strings.TrimSpace(method.DocComment) + result = strings.ReplaceAll(result, "Comments", comments) + var params string + for _, input := range method.Inputs { + pkgName := getPackageName(input) + if pkgName != "" { + models = append(models, pkgName) + } + params += " * @param " + sanitiseJSVarName(input.Name) + " {" + input.JSType() + "}\n" + } + params = strings.TrimSuffix(params, "\n") + if len(params) == 0 { + params = " *" + } + result = strings.ReplaceAll(result, " * @param name {string}", params) + var inputs string + for _, input := range method.Inputs { + pkgName := getPackageName(input) + if pkgName != "" { + models = append(models, pkgName) + } + inputs += sanitiseJSVarName(input.Name) + ", " + } + inputs = strings.TrimSuffix(inputs, ", ") + args := inputs + if len(args) > 0 { + args = ", " + args + } + result = strings.ReplaceAll(result, "{{inputs}}", inputs) + result = strings.ReplaceAll(result, "{{args}}", args) + + // outputs + var returns string + if len(method.Outputs) == 0 { + returns = " * @returns {Promise}" + } else { + returns = " * @returns {Promise<" + for _, output := range method.Outputs { + pkgName := getPackageName(output) + if pkgName != "" { + models = append(models, pkgName) + } + jsType := output.JSType() + if jsType == "error" { + jsType = "void" + } + returns += jsType + ", " + } + returns = strings.TrimSuffix(returns, ", ") + returns += ">}" + } + result = strings.ReplaceAll(result, " * @returns {Promise}", returns) + + return result, lo.Uniq(models) +} + +func getPackageName(input *Parameter) string { + if !input.Type.IsStruct { + return "" + } + result := input.Type.Package + if result == "" { + result = "main" + } + return result +} + +func normalisePackageNames(packageNames []string) map[string]string { + // We iterate over the package names and determine if any of them + // have a forward slash. If this is the case, we assume that the + // package name is the last element of the path. If this has already + // been found, then we need to add a digit to the end of the package + // name to make it unique. We return a map of the original package + // name to the new package name. + var result = make(map[string]string) + var packagesConverted = make(map[string]struct{}) + var count = 1 + for _, packageName := range packageNames { + var originalPackageName = packageName + if strings.Contains(packageName, "/") { + parts := strings.Split(packageName, "/") + packageName = parts[len(parts)-1] + } + if _, ok := packagesConverted[packageName]; ok { + // We've already seen this package name. Add a digit + // to the end of the package name to make it unique + count += 1 + packageName += strconv.Itoa(count) + + } + packagesConverted[packageName] = struct{}{} + result[originalPackageName] = packageName + } + + return result +} + +func GenerateBindings(bindings map[string]map[string][]*BoundMethod) map[string]string { + + var result = make(map[string]string) + + var normalisedPackageNames = normalisePackageNames(lo.Keys(bindings)) + // sort the bindings keys + packageNames := lo.Keys(bindings) + sort.Strings(packageNames) + for _, packageName := range packageNames { + var allModels []string + + packageBindings := bindings[packageName] + structNames := lo.Keys(packageBindings) + sort.Strings(structNames) + for _, structName := range structNames { + result[normalisedPackageNames[packageName]] += GenerateHelper(normalisedPackageNames[packageName], structName) + methods := packageBindings[structName] + sort.Slice(methods, func(i, j int) bool { + return methods[i].Name < methods[j].Name + }) + for _, method := range methods { + thisBinding, models := GenerateBinding(structName, method) + result[normalisedPackageNames[packageName]] += thisBinding + allModels = append(allModels, models...) + } + } + + result[normalisedPackageNames[packageName]] += ` +window.go = window.go || {}; +` + // Iterate over the sorted struct keys + result[normalisedPackageNames[packageName]] += "window.go." + normalisedPackageNames[packageName] + " = {\n" + for _, structName := range structNames { + result[normalisedPackageNames[packageName]] += " " + structName + ": {\n" + methods := packageBindings[structName] + sort.Slice(methods, func(i, j int) bool { + return methods[i].Name < methods[j].Name + }) + for _, method := range methods { + result[normalisedPackageNames[packageName]] += " " + method.Name + ",\n" + } + result[normalisedPackageNames[packageName]] += " },\n" + } + result[normalisedPackageNames[packageName]] += "};\n" + + // add imports + if len(allModels) > 0 { + allModels := lo.Uniq(allModels) + var models []string + for _, model := range allModels { + models = append(models, normalisedPackageNames[model]) + } + sort.Strings(models) + result[normalisedPackageNames[packageName]] += "\n" + imports := "import {" + strings.Join(models, ", ") + "} from './models';\n" + result[normalisedPackageNames[packageName]] = imports + "\n" + result[normalisedPackageNames[packageName]] + } + + result[normalisedPackageNames[packageName]] = header + result[normalisedPackageNames[packageName]] + } + + return result +} diff --git a/v3/internal/parser/bindings_test.go b/v3/internal/parser/bindings_test.go new file mode 100644 index 000000000..f6f309b10 --- /dev/null +++ b/v3/internal/parser/bindings_test.go @@ -0,0 +1,124 @@ +package parser + +import ( + "embed" + "io/fs" + "os" + "testing" + + "github.com/google/go-cmp/cmp" +) + +//go:embed testdata +var testdata embed.FS + +func getFile(filename string) string { + // get the file from the testdata FS + file, err := fs.ReadFile(testdata, filename) + if err != nil { + panic(err) + } + return string(file) +} + +func TestGenerateBindings(t *testing.T) { + + tests := []struct { + dir string + want map[string]string + }{ + { + "testdata/function_single", + map[string]string{ + "main": getFile("testdata/function_single/bindings_main.js"), + }, + }, + { + "testdata/function_from_imported_package", + map[string]string{ + "main": getFile("testdata/function_from_imported_package/bindings_main.js"), + "services": getFile("testdata/function_from_imported_package/bindings_services.js"), + }, + }, + { + "testdata/variable_single", + map[string]string{ + "main": getFile("testdata/variable_single/bindings_main.js"), + }, + }, + { + "testdata/variable_single_from_function", + map[string]string{ + "main": getFile("testdata/variable_single_from_function/bindings_main.js"), + }, + }, + { + "testdata/variable_single_from_other_function", + map[string]string{ + "main": getFile("testdata/variable_single_from_other_function/bindings_main.js"), + "services": getFile("testdata/variable_single_from_other_function/bindings_services.js"), + }, + }, + { + "testdata/struct_literal_single", + map[string]string{ + "main": getFile("testdata/struct_literal_single/bindings_main.js"), + }, + }, + { + "testdata/struct_literal_multiple", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple/bindings_main.js"), + }, + }, + { + "testdata/struct_literal_multiple_other", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple_other/bindings_main.js"), + "services": getFile("testdata/struct_literal_multiple_other/bindings_services.js"), + }, + }, + { + "testdata/struct_literal_multiple_files", + map[string]string{ + "main": getFile("testdata/struct_literal_multiple_files/bindings_main.js"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.dir, func(t *testing.T) { + // Run parser on directory + project, err := ParseProject(tt.dir) + if err != nil { + t.Errorf("ParseProject() error = %v", err) + return + } + + // Generate Bindings + got := GenerateBindings(project.BoundMethods) + + for name, binding := range got { + // check if the binding is in the expected bindings + expected, ok := tt.want[name] + if !ok { + err = os.WriteFile(tt.dir+"/bindings_"+name+".got.js", []byte(binding), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Errorf("GenerateBindings() unexpected binding = %v", name) + return + } + // compare the binding + if diff := cmp.Diff(expected, binding); diff != "" { + err = os.WriteFile(tt.dir+"/bindings_"+name+".got.js", []byte(binding), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Fatalf("GenerateBindings() mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/v3/internal/parser/models.go b/v3/internal/parser/models.go new file mode 100644 index 000000000..07ecb3052 --- /dev/null +++ b/v3/internal/parser/models.go @@ -0,0 +1,184 @@ +package parser + +import ( + "bytes" + "embed" + "io" + "sort" + "strings" + "text/template" +) + +//go:embed templates +var templates embed.FS + +type ModelDefinitions struct { + Package string + Models map[string]*StructDef +} + +func GenerateModel(wr io.Writer, def *ModelDefinitions) error { + tmpl, err := template.New("model.ts.tmpl").ParseFS(templates, "templates/model.ts.tmpl") + if err != nil { + println("Unable to create class template: " + err.Error()) + return err + } + + err = tmpl.ExecuteTemplate(wr, "model.ts.tmpl", def) + if err != nil { + println("Problem executing template: " + err.Error()) + return err + } + return nil +} + +const modelsHeader = `// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +` + +func pkgAlias(fullPkg string) string { + pkgParts := strings.Split(fullPkg, "/") + return pkgParts[len(pkgParts)-1] +} + +func GenerateModels(models map[packagePath]map[structName]*StructDef) (string, error) { + if models == nil { + return "", nil + } + + var buffer bytes.Buffer + buffer.WriteString(modelsHeader) + + // sort pkgs by alias (e.g. services) instead of full pkg name (e.g. github.com/wailsapp/wails/somedir/services) + // and then sort resulting list by the alias + var keys []string + for pkg, _ := range models { + keys = append(keys, pkg) + } + + sort.Slice(keys, func(i, j int) bool { + return pkgAlias(keys[i]) < pkgAlias(keys[j]) + }) + + for _, pkg := range keys { + err := GenerateModel(&buffer, &ModelDefinitions{ + Package: pkgAlias(pkg), + Models: models[pkg], + }) + if err != nil { + return "", err + } + } + return buffer.String(), nil +} + +//func GenerateClass(wr io.Writer, def *StructDef) error { +// tmpl, err := template.New("class.ts.tmpl").ParseFiles("templates/class.ts.tmpl") +// if err != nil { +// println("Unable to create class template: " + err.Error()) +// return err +// } +// +// err = tmpl.ExecuteTemplate(wr, "class.ts.tmpl", def) +// if err != nil { +// println("Problem executing template: " + err.Error()) +// return err +// } +// return nil +//} + +// +//import ( +// "bytes" +// "fmt" +// "go/ast" +// "go/types" +// "sort" +// "strings" +// "unicode" +//) +// +//func GenerateModels(context *Context) ([]byte, error) { +// var buf bytes.Buffer +// var pkgs []Package +// specs := context.GetBoundStructs() +// for pkg, pkgSpecs := range specs { +// pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs}) +// } +// knownStructs := newAllModels(specs) +// sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name }) +// for _, pkg := range pkgs { +// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil { +// return nil, err +// } +// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name }) +// for _, spec := range pkg.Specs { +// if structType, ok := spec.Type.(*ast.StructType); ok { +// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil { +// return nil, err +// } +// +// for _, field := range structType.Fields.List { +// +// // Ignore field names that have a lower case first letter +// if !unicode.IsUpper(rune(field.Names[0].Name[0])) { +// continue +// } +// +// // Get the Go type of the field +// goType := types.ExprString(field.Type) +// // Check if the type is an array +// if arrayType, ok := field.Type.(*ast.ArrayType); ok { +// // Get the element type of the array +// elementType := types.ExprString(arrayType.Elt) +// // Look up the corresponding TypeScript type +// tsType, ok := goToTS[elementType] +// if !ok { +// // strip off the * prefix if it is there +// if strings.HasPrefix(elementType, "*") { +// elementType = elementType[1:] +// } +// if knownStructs.exists(elementType) { +// tsType = elementType +// } else { +// tsType = "any" +// } +// } +// // Output the field as an array of the corresponding TypeScript type +// if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil { +// return nil, err +// } +// } else { +// // strip off the * prefix if it is there +// if strings.HasPrefix(goType, "*") { +// goType = goType[1:] +// } +// // Look up the corresponding TypeScript type +// tsType, ok := goToTS[goType] +// if !ok { +// if knownStructs.exists(goType) { +// tsType = goType +// } else { +// tsType = "any" +// } +// } +// // Output the field as the corresponding TypeScript type +// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil { +// return nil, err +// } +// } +// } +// +// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil { +// return nil, err +// } +// } +// } +// +// if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil { +// return nil, err +// } +// } +// return buf.Bytes(), nil +//} diff --git a/v3/internal/parser/models_test.go b/v3/internal/parser/models_test.go new file mode 100644 index 000000000..b2668511e --- /dev/null +++ b/v3/internal/parser/models_test.go @@ -0,0 +1,77 @@ +package parser + +import ( + "github.com/google/go-cmp/cmp" + "os" + "path/filepath" + "testing" +) + +func TestGenerateModels(t *testing.T) { + + tests := []struct { + dir string + want string + }{ + { + "testdata/function_single", + "", + }, + { + "testdata/function_from_imported_package", + getFile("testdata/function_from_imported_package/models.ts"), + }, + { + "testdata/variable_single", + "", + }, + { + "testdata/variable_single_from_function", + "", + }, + { + "testdata/variable_single_from_other_function", + getFile("testdata/variable_single_from_other_function/models.ts"), + }, + { + "testdata/struct_literal_single", + getFile("testdata/struct_literal_single/models.ts"), + }, + { + "testdata/struct_literal_multiple", + "", + }, + { + "testdata/struct_literal_multiple_other", + getFile("testdata/struct_literal_multiple_other/models.ts"), + }, + { + "testdata/struct_literal_multiple_files", + "", + }, + } + for _, tt := range tests { + t.Run(tt.dir, func(t *testing.T) { + // Run parser on directory + project, err := ParseProject(tt.dir) + if err != nil { + t.Fatalf("ParseProject() error = %v", err) + } + + // Generate Models + got, err := GenerateModels(project.Models) + if err != nil { + t.Fatalf("GenerateModels() error = %v", err) + } + + if diff := cmp.Diff(tt.want, got); diff != "" { + err = os.WriteFile(filepath.Join(tt.dir, "models.got.ts"), []byte(got), 0644) + if err != nil { + t.Errorf("os.WriteFile() error = %v", err) + return + } + t.Fatalf("GenerateModels() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go new file mode 100644 index 000000000..cae51fa13 --- /dev/null +++ b/v3/internal/parser/parser.go @@ -0,0 +1,809 @@ +package parser + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" +) + +type packagePath = string +type structName = string + +type StructDef struct { + Name string + DocComment string + Fields []*Field +} + +type ParameterType struct { + Name string + IsStruct bool + IsSlice bool + IsPointer bool + MapKey *ParameterType + MapValue *ParameterType + Package string +} + +type Parameter struct { + Name string + Type *ParameterType +} + +func (p *Parameter) JSType() string { + // Convert type to javascript equivalent type + var typeName string + switch p.Type.Name { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64": + typeName = "number" + case "string": + typeName = "string" + case "bool": + typeName = "boolean" + default: + typeName = p.Type.Name + } + + // if the type is a struct, we need to add the package name + if p.Type.IsStruct { + if p.Type.Package != "" { + parts := strings.Split(p.Type.Package, "/") + typeName = parts[len(parts)-1] + "." + typeName + // TODO: Check if this is a duplicate package name + } + } + + // Add slice suffix + if p.Type.IsSlice { + typeName += "[]" + } + + // Add pointer suffix + if p.Type.IsPointer { + typeName += " | null" + } + + return typeName +} + +type BoundMethod struct { + Name string + DocComment string + Inputs []*Parameter + Outputs []*Parameter +} + +type Field struct { + Name string + Type *ParameterType +} + +func (f *Field) JSName() string { + return strings.ToLower(f.Name[0:1]) + f.Name[1:] +} + +// TSBuild contains the typescript to build a field for a JS object +// via assignment for simple types or constructors for structs +func (f *Field) TSBuild(pkg string) string { + if !f.Type.IsStruct { + return fmt.Sprintf("source['%s']", f.JSName()) + } + + if f.Type.Package == "" || f.Type.Package == pkg { + return fmt.Sprintf("%s.createFrom(source['%s'])", f.Type.Name, f.JSName()) + } + + return fmt.Sprintf("%s.%s.createFrom(source['%s'])", pkgAlias(f.Type.Package), f.Type.Name, f.JSName()) +} + +func (f *Field) JSDef(pkg string) string { + var jsType string + switch f.Type.Name { + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64": + jsType = "number" + case "string": + jsType = "string" + case "bool": + jsType = "boolean" + default: + jsType = f.Type.Name + } + + var result string + if f.Type.Package == "" || f.Type.Package == pkg { + result += fmt.Sprintf("%s: %s;", f.JSName(), jsType) + } else { + parts := strings.Split(f.Type.Package, "/") + result += fmt.Sprintf("%s: %s.%s;", f.JSName(), parts[len(parts)-1], jsType) + } + + if !ast.IsExported(f.Name) { + result += " // Warning: this is unexported in the Go struct." + } + + return result +} + +type ParsedPackage struct { + Pkg *ast.Package + Name string + Path string + Dir string + StructCache map[structName]*StructDef +} + +type Project struct { + packageCache map[string]*ParsedPackage + Path string + BoundMethods map[packagePath]map[structName][]*BoundMethod + Models map[packagePath]map[structName]*StructDef + anonymousStructIDCounter int +} + +func ParseProject(projectPath string) (*Project, error) { + result := &Project{ + BoundMethods: make(map[packagePath]map[structName][]*BoundMethod), + packageCache: make(map[string]*ParsedPackage), + } + pkgs, err := result.parseDirectory(projectPath) + if err != nil { + return nil, err + } + err = result.findApplicationNewCalls(pkgs) + if err != nil { + return nil, err + } + for _, pkg := range result.packageCache { + if len(pkg.StructCache) > 0 { + if result.Models == nil { + result.Models = make(map[packagePath]map[structName]*StructDef) + } + result.Models[pkg.Path] = pkg.StructCache + } + } + return result, nil +} + +func GenerateBindingsAndModels(projectDir string, outputDir string) error { + p, err := ParseProject(projectDir) + if err != nil { + return err + } + + if p.BoundMethods == nil { + return nil + } + err = os.MkdirAll(outputDir, 0755) + if err != nil { + return err + } + generatedMethods := GenerateBindings(p.BoundMethods) + for pkg, text := range generatedMethods { + // Write the file + err = os.WriteFile(filepath.Join(outputDir, "bindings_"+pkg+".js"), []byte(text), 0644) + if err != nil { + return err + } + } + + // Generate Models + if len(p.Models) > 0 { + generatedModels, err := GenerateModels(p.Models) + if err != nil { + return err + } + err = os.WriteFile(filepath.Join(outputDir, "models.ts"), []byte(generatedModels), 0644) + if err != nil { + return err + } + } + + absPath, err := filepath.Abs(projectDir) + if err != nil { + return err + } + println("Generated bindings and models for project: " + absPath) + absPath, err = filepath.Abs(outputDir) + if err != nil { + return err + } + println("Output directory: " + absPath) + + return nil +} + +func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { + if p.packageCache[dir] != nil { + return map[string]*ParsedPackage{dir: p.packageCache[dir]}, nil + } + // Parse the directory + fset := token.NewFileSet() + if dir == "." || dir == "" { + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + dir = cwd + } + pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments) + if err != nil { + return nil, err + } + var result = make(map[string]*ParsedPackage) + for packageName, pkg := range pkgs { + parsedPackage := &ParsedPackage{ + Pkg: pkg, + Name: packageName, + Path: packageName, + Dir: getDirectoryForPackage(pkg), + StructCache: make(map[structName]*StructDef), + } + p.packageCache[packageName] = parsedPackage + result[packageName] = parsedPackage + } + return result, nil +} + +func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err error) { + + var callFound bool + + for _, pkg := range pkgs { + thisPackage := pkg.Pkg + // Iterate through the package's files + for _, file := range thisPackage.Files { + // Use an ast.Inspector to find the calls to application.New + ast.Inspect(file, func(n ast.Node) bool { + // Check if the node is a call expression + callExpr, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + // Check if the function being called is "application.New" + selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + if selExpr.Sel.Name != "New" { + return true + } + if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" { + return true + } + + // Check there is only 1 argument + if len(callExpr.Args) != 1 { + return true + } + + // Check argument 1 is a struct literal + structLit, ok := callExpr.Args[0].(*ast.CompositeLit) + if !ok { + return true + } + + // Check struct literal is of type "application.Options" + selectorExpr, ok := structLit.Type.(*ast.SelectorExpr) + if !ok { + return true + } + if selectorExpr.Sel.Name != "Options" { + return true + } + if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "application" { + return true + } + + for _, elt := range structLit.Elts { + // Find the "Bind" field + kvExpr, ok := elt.(*ast.KeyValueExpr) + if !ok { + continue + } + if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" { + continue + } + // Check the value is a slice of interfaces + sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit) + if !ok { + continue + } + var arrayType *ast.ArrayType + if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok { + continue + } + + // Check array type is of type "interface{}" + if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok { + continue + } + callFound = true + // Iterate through the slice elements + for _, elt := range sliceExpr.Elts { + result, shouldContinue := p.parseBoundExpression(elt, pkg) + if shouldContinue { + continue + } + return result + + } + } + + return true + }) + } + if !callFound { + return fmt.Errorf("no Bound structs found") + } + } + return nil +} + +func (p *Project) parseBoundUnaryExpression(unaryExpr *ast.UnaryExpr, pkg *ParsedPackage) (bool, bool) { + // Check the unary expression is a composite lit + + switch t := unaryExpr.X.(type) { + case *ast.CompositeLit: + return p.parseBoundCompositeLit(t, pkg) + } + return false, true + +} + +func (p *Project) addBoundMethods(packagePath string, name string, boundMethods []*BoundMethod) { + _, ok := p.BoundMethods[packagePath] + if !ok { + p.BoundMethods[packagePath] = make(map[structName][]*BoundMethod) + } + p.BoundMethods[packagePath][name] = boundMethods +} + +func (p *Project) parseBoundStructMethods(name string, pkg *ParsedPackage) error { + var methods []*BoundMethod + // Iterate over all files in the package + for _, file := range pkg.Pkg.Files { + // Iterate over all declarations in the file + for _, decl := range file.Decls { + // Check if the declaration is a type declaration + if funcDecl, ok := decl.(*ast.FuncDecl); ok && funcDecl.Recv != nil { + // Check if the function has a receiver argument of the struct type + recvType, ok := funcDecl.Recv.List[0].Type.(*ast.StarExpr) + if ok { + if ident, ok := recvType.X.(*ast.Ident); ok && ident.Name == name { + // Add the method to the list of methods + method := &BoundMethod{ + Name: funcDecl.Name.Name, + DocComment: funcDecl.Doc.Text(), + } + + if funcDecl.Type.Params != nil { + method.Inputs = p.parseParameters(funcDecl.Type.Params, pkg) + } + if funcDecl.Type.Results != nil { + method.Outputs = p.parseParameters(funcDecl.Type.Results, pkg) + } + + methods = append(methods, method) + } + } + } + } + } + p.addBoundMethods(pkg.Path, name, methods) + return nil +} + +func (p *Project) parseParameters(params *ast.FieldList, pkg *ParsedPackage) []*Parameter { + var result []*Parameter + for _, field := range params.List { + var theseFields []*Parameter + if len(field.Names) > 0 { + for _, name := range field.Names { + theseFields = append(theseFields, &Parameter{ + Name: name.Name, + }) + } + } else { + theseFields = append(theseFields, &Parameter{ + Name: "", + }) + } + // loop over fields + for _, thisField := range theseFields { + thisField.Type = p.parseParameterType(field, pkg) + result = append(result, thisField) + } + } + return result +} + +func (p *Project) parseParameterType(field *ast.Field, pkg *ParsedPackage) *ParameterType { + result := &ParameterType{} + result.Name = getTypeString(field.Type) + switch t := field.Type.(type) { + case *ast.Ident: + result.IsStruct = isStructType(t) + case *ast.StarExpr: + result = p.parseParameterType(&ast.Field{Type: t.X}, pkg) + result.IsPointer = true + case *ast.StructType: + result.IsStruct = true + if result.Name == "" { + // Anonymous struct + result.Name = p.anonymousStructID() + // Create a new struct definition + result := &StructDef{ + Name: result.Name, + } + pkg.StructCache[result.Name] = result + // Parse the fields + result.Fields = p.parseStructFields(&ast.StructType{ + Fields: t.Fields, + }, pkg) + _ = result + } + case *ast.SelectorExpr: + extPackage, err := p.getParsedPackageFromName(t.X.(*ast.Ident).Name, pkg) + if err != nil { + log.Fatal(err) + } + result.IsStruct = p.getStructDef(t.Sel.Name, extPackage) + result.Package = extPackage.Path + case *ast.ArrayType: + result.IsSlice = true + result.IsStruct = isStructType(t.Elt) + case *ast.MapType: + tempfield := &ast.Field{Type: t.Key} + result.MapKey = p.parseParameterType(tempfield, pkg) + tempfield.Type = t.Value + result.MapValue = p.parseParameterType(tempfield, pkg) + default: + } + if result.IsStruct { + p.getStructDef(result.Name, pkg) + if result.Package == "" { + result.Package = pkg.Path + } + } + return result +} + +func (p *Project) getStructDef(name string, pkg *ParsedPackage) bool { + _, ok := pkg.StructCache[name] + if ok { + return true + } + // Iterate over all files in the package + for _, file := range pkg.Pkg.Files { + // Iterate over all declarations in the file + for _, decl := range file.Decls { + // Check if the declaration is a type declaration + if typeDecl, ok := decl.(*ast.GenDecl); ok { + // Check if the type declaration is a struct type + if typeDecl.Tok == token.TYPE { + for _, spec := range typeDecl.Specs { + if typeSpec, ok := spec.(*ast.TypeSpec); ok { + if structType, ok := typeSpec.Type.(*ast.StructType); ok { + if typeSpec.Name.Name == name { + result := &StructDef{ + Name: name, + DocComment: typeDecl.Doc.Text(), + } + pkg.StructCache[name] = result + result.Fields = p.parseStructFields(structType, pkg) + return true + } + } + } + } + } + } + } + } + return false +} + +func (p *Project) parseStructFields(structType *ast.StructType, pkg *ParsedPackage) []*Field { + var result []*Field + for _, field := range structType.Fields.List { + var theseFields []*Field + if len(field.Names) > 0 { + for _, name := range field.Names { + theseFields = append(theseFields, &Field{ + Name: name.Name, + }) + } + } else { + theseFields = append(theseFields, &Field{ + Name: "", + }) + } + // loop over fields + for _, thisField := range theseFields { + paramType := p.parseParameterType(field, pkg) + if paramType.IsStruct { + _, ok := pkg.StructCache[paramType.Name] + if !ok { + p.getStructDef(paramType.Name, pkg) + } + if paramType.Package == "" { + paramType.Package = pkg.Path + } + } + thisField.Type = paramType + result = append(result, thisField) + } + } + return result +} + +func (p *Project) getParsedPackageFromName(packageName string, currentPackage *ParsedPackage) (*ParsedPackage, error) { + for _, file := range currentPackage.Pkg.Files { + for _, imp := range file.Imports { + path, err := strconv.Unquote(imp.Path.Value) + if err != nil { + return nil, err + } + _, lastPathElement := filepath.Split(path) + if imp.Name != nil && imp.Name.Name == packageName || lastPathElement == packageName { + // Get the directory for the package + dir, err := getPackageDir(path) + if err != nil { + return nil, err + } + pkg, err := p.getPackageFromPath(dir, path) + if err != nil { + return nil, err + } + result := &ParsedPackage{ + Pkg: pkg, + Name: packageName, + Path: path, + Dir: dir, + StructCache: make(map[string]*StructDef), + } + p.packageCache[path] = result + return result, nil + } + } + } + return nil, fmt.Errorf("package %s not found in %s", packageName, currentPackage.Name) +} + +func getPackageDir(importPath string) (string, error) { + pkg, err := build.Import(importPath, "", build.FindOnly) + if err != nil { + return "", err + } + return pkg.Dir, nil +} + +func (p *Project) getPackageFromPath(packagedir string, packagepath string) (*ast.Package, error) { + impPkg, err := parser.ParseDir(token.NewFileSet(), packagedir, nil, parser.AllErrors) + if err != nil { + return nil, err + } + for impName, impPkg := range impPkg { + if impName == "main" { + continue + } + return impPkg, nil + } + return nil, fmt.Errorf("package not found in imported package %s", packagepath) +} + +func (p *Project) anonymousStructID() string { + p.anonymousStructIDCounter++ + return fmt.Sprintf("anon%d", p.anonymousStructIDCounter) +} + +func (p *Project) parseBoundExpression(elt ast.Expr, pkg *ParsedPackage) (bool, bool) { + + switch t := elt.(type) { + case *ast.UnaryExpr: + return p.parseBoundUnaryExpression(t, pkg) + case *ast.Ident: + return p.parseBoundIdent(t, pkg) + case *ast.CallExpr: + return p.parseBoundCallExpression(t, pkg) + default: + println("unhandled expression type", reflect.TypeOf(t).String()) + } + + return false, false +} + +func (p *Project) parseBoundIdent(ident *ast.Ident, pkg *ParsedPackage) (bool, bool) { + if ident.Obj == nil { + return false, true + } + switch t := ident.Obj.Decl.(type) { + //case *ast.StructType: + // return p.parseBoundStruct(t, pkg) + case *ast.TypeSpec: + return p.parseBoundTypeSpec(t, pkg) + case *ast.AssignStmt: + return p.parseBoundAssignment(t, pkg) + default: + println("unhandled ident type", reflect.TypeOf(t).String()) + } + return false, false +} + +func (p *Project) parseBoundAssignment(assign *ast.AssignStmt, pkg *ParsedPackage) (bool, bool) { + return p.parseBoundExpression(assign.Rhs[0], pkg) +} + +func (p *Project) parseBoundCompositeLit(lit *ast.CompositeLit, pkg *ParsedPackage) (bool, bool) { + + switch t := lit.Type.(type) { + case *ast.StructType: + //return p.parseBoundStructType(t, pkg) + return false, true + case *ast.Ident: + err := p.parseBoundStructMethods(t.Name, pkg) + if err != nil { + return true, false + } + return false, true + case *ast.SelectorExpr: + return p.parseBoundSelectorExpression(t, pkg) + } + + return false, true +} + +func (p *Project) parseBoundSelectorExpression(selector *ast.SelectorExpr, pkg *ParsedPackage) (bool, bool) { + + switch t := selector.X.(type) { + case *ast.Ident: + // Look up the package + var parsedPackage *ParsedPackage + parsedPackage, err := p.getParsedPackageFromName(t.Name, pkg) + if err != nil { + return true, false + } + err = p.parseBoundStructMethods(selector.Sel.Name, parsedPackage) + if err != nil { + return true, false + } + return false, true + default: + println("unhandled selector type", reflect.TypeOf(t).String()) + } + return false, true +} + +func (p *Project) parseBoundCallExpression(callExpr *ast.CallExpr, pkg *ParsedPackage) (bool, bool) { + + // Check if this call returns a struct pointer + switch t := callExpr.Fun.(type) { + case *ast.Ident: + if t.Obj == nil { + return false, true + } + switch t := t.Obj.Decl.(type) { + case *ast.FuncDecl: + return p.parseBoundFuncDecl(t, pkg) + } + + case *ast.SelectorExpr: + // Get package for selector + var parsedPackage *ParsedPackage + parsedPackage, err := p.getParsedPackageFromName(t.X.(*ast.Ident).Name, pkg) + if err != nil { + return true, false + } + // Get function from package + var extFundDecl *ast.FuncDecl + extFundDecl, err = p.getFunctionFromName(t.Sel.Name, parsedPackage) + if err != nil { + return true, false + } + return p.parseBoundFuncDecl(extFundDecl, parsedPackage) + default: + println("unhandled call type", reflect.TypeOf(t).String()) + } + + return false, true +} + +func (p *Project) parseBoundFuncDecl(t *ast.FuncDecl, pkg *ParsedPackage) (bool, bool) { + if t.Type.Results == nil { + return false, true + } + if len(t.Type.Results.List) != 1 { + return false, true + } + switch t := t.Type.Results.List[0].Type.(type) { + case *ast.StarExpr: + return p.parseBoundExpression(t.X, pkg) + default: + println("Unhandled funcdecl type", reflect.TypeOf(t).String()) + } + return false, false +} + +func (p *Project) parseBoundTypeSpec(typeSpec *ast.TypeSpec, pkg *ParsedPackage) (bool, bool) { + switch t := typeSpec.Type.(type) { + case *ast.StructType: + err := p.parseBoundStructMethods(typeSpec.Name.Name, pkg) + if err != nil { + return true, false + } + default: + println("unhandled type spec type", reflect.TypeOf(t).String()) + } + return false, true +} + +func (p *Project) getFunctionFromName(name string, parsedPackage *ParsedPackage) (*ast.FuncDecl, error) { + for _, f := range parsedPackage.Pkg.Files { + for _, decl := range f.Decls { + switch t := decl.(type) { + case *ast.FuncDecl: + if t.Name.Name == name { + return t, nil + } + } + } + } + return nil, fmt.Errorf("function not found") +} + +func getTypeString(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return getTypeString(t.X) + case *ast.ArrayType: + return getTypeString(t.Elt) + case *ast.MapType: + return "map" + case *ast.SelectorExpr: + return getTypeString(t.Sel) + default: + return "" + } +} + +func isStructType(expr ast.Expr) bool { + switch e := expr.(type) { + case *ast.StructType: + return true + case *ast.StarExpr: + return isStructType(e.X) + case *ast.SelectorExpr: + return isStructType(e.Sel) + case *ast.ArrayType: + return isStructType(e.Elt) + case *ast.SliceExpr: + return isStructType(e.X) + case *ast.Ident: + return e.Obj != nil && e.Obj.Kind == ast.Typ + default: + return false + } +} + +func getDirectoryForPackage(pkg *ast.Package) string { + for filename := range pkg.Files { + path := filepath.Dir(filename) + abs, err := filepath.Abs(path) + if err != nil { + panic(err) + } + return abs + } + return "" +} diff --git a/v3/internal/parser/parser_test.go b/v3/internal/parser/parser_test.go new file mode 100644 index 000000000..ea1705daa --- /dev/null +++ b/v3/internal/parser/parser_test.go @@ -0,0 +1,1559 @@ +package parser + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestParseDirectory(t *testing.T) { + tests := []struct { + name string + dir string + wantBoundMethods map[string]map[string][]*BoundMethod + wantModels map[string]map[string]*StructDef + wantErr bool + }{ + { + name: "should find single bound service", + dir: "testdata/struct_literal_single", + //wantModels: []string{"main.GreetService"}, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "NoInputsStringOut", + DocComment: "", + Inputs: nil, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "StringArrayInputStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "StringArrayInputStringArrayOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + }, + { + Name: "StringArrayInputNamedOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + }, + { + Name: "StringArrayInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, + }, + { + Name: "err", + Type: &ParameterType{ + Name: "error", + }, + }, + }, + }, + { + Name: "IntPointerInputNamedOutputs", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "output", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + { + Name: "err", + Type: &ParameterType{ + Name: "error", + }}, + }, + }, + { + Name: "UIntPointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + IsPointer: true, + }, + }, + }, + }, + { + Name: "UInt8PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint8", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint8", + IsPointer: true, + }, + }, + }, + }, + { + Name: "UInt16PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint16", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint16", + IsPointer: true, + }, + }, + }, + }, + { + Name: "UInt32PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint32", + IsPointer: true, + }, + }, + }, + }, + { + Name: "UInt64PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint64", + IsPointer: true, + }, + }, + }, + }, + { + Name: "IntPointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, + }, + }, + }, + { + Name: "Int8PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int8", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int8", + IsPointer: true, + }, + }, + }, + }, + { + Name: "Int16PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int16", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int16", + IsPointer: true, + }, + }, + }, + }, + { + Name: "Int32PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int32", + IsPointer: true, + }, + }, + }, + }, + { + Name: "Int64PointerInAndOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int64", + IsPointer: true, + }, + }, + }, + }, + { + Name: "IntInIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + }, + }, + }, + }, + { + Name: "Int8InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int8", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int8", + }, + }, + }, + }, + { + Name: "Int16InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int16", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int16", + }, + }, + }, + }, + { + Name: "Int32InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int32", + }, + }, + }, + }, + { + Name: "Int64InIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int64", + }, + }, + }, + }, + { + Name: "UIntInUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + }, + { + Name: "UInt8InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint8", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint8", + }, + }, + }, + }, + { + Name: "UInt16InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint16", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint16", + }, + }, + }, + }, + { + Name: "UInt32InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint32", + }, + }, + }, + }, + { + Name: "UInt64InUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint64", + }, + }, + }, + }, + { + Name: "Float32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + }, + { + Name: "Float64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + }, + { + Name: "PointerFloat32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + IsPointer: true, + }, + }, + }, + }, + { + Name: "PointerFloat64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + IsPointer: true, + }, + }, + }, + }, + { + Name: "BoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + }, + { + Name: "PointerBoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + IsPointer: true, + }, + }, + }, + }, + { + Name: "PointerStringInStringOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "string", + IsPointer: true, + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "string", + IsPointer: true, + }, + }, + }, + }, + { + Name: "StructPointerInputErrorOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "error", + }, + }, + }, + }, + { + Name: "StructInputStructOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + { + Name: "StructPointerInputStructPointerOutput", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + }, + { + Name: "MapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + }, + { + Name: "PointerMapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + IsPointer: true, + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + }, + { + Name: "MapIntPointerInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + IsPointer: true, + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + }, + { + Name: "MapIntSliceInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + }, + { + Name: "MapIntSliceIntInMapIntSliceIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "out", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + }, + { + Name: "ArrayInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Parent", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + IsPointer: true, + Package: "main", + }, + }, + { + Name: "Details", + Type: &ParameterType{ + Name: "anon1", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + "anon1": { + Name: "anon1", + Fields: []*Field{ + { + Name: "Age", + Type: &ParameterType{ + Name: "int", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "anon2", + IsStruct: true, + Package: "main", + }, + }, + }, + }, + "anon2": { + Name: "anon2", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find multiple bound services", + dir: "testdata/struct_literal_multiple", + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + "OtherService": { + { + Name: "Hello", + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find multiple bound services over multiple files", + dir: "testdata/struct_literal_multiple_files", + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + "OtherService": { + { + Name: "Hello", + }, + }, + }, + }, + wantErr: false, + }, + { + name: "should find multiple bound services over multiple packages", + dir: "testdata/struct_literal_multiple_other", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services", + }, + }, + }, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound services using a variable", + dir: "testdata/variable_single", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound services using a variable from function call", + dir: "testdata/variable_single_from_function", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound services using a variable from function call in another package", + dir: "testdata/variable_single_from_other_function", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound service returned from a function call", + dir: "testdata/function_single", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet someone\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "should find a bound service returned from a function call in another package", + dir: "testdata/function_from_imported_package", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + Package: "main", + }, + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + }, + }, + }, + }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseProject(tt.dir) + if (err != nil) != tt.wantErr { + t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := cmp.Diff(tt.wantBoundMethods, got.BoundMethods); diff != "" { + t.Errorf("ParseDirectory() failed:\n" + diff) + } + if diff := cmp.Diff(tt.wantModels, got.Models); diff != "" { + t.Errorf("ParseDirectory() failed:\n" + diff) + } + }) + } + +} + +//func TestGenerateTypeScript(t *testing.T) { +// tests := []struct { +// name string +// dir string +// wantModels string +// wantErr bool +// }{ +// { +// name: "should find single bound service", +// dir: "testdata/struct_literal_single", +// wantModels: `namespace main { +// class GreetService { +// SomeVariable: number; +// } +//} +//`, +// wantErr: false, +// }, +// { +// name: "should find multiple bound services", +// dir: "testdata/struct_literal_multiple", +// wantModels: `namespace main { +// class GreetService { +// SomeVariable: number; +// } +// class OtherService { +// } +//} +//`, +// wantErr: false, +// }, +// { +// name: "should find multiple bound services over multiple files", +// dir: "testdata/struct_literal_multiple_files", +// wantModels: `namespace main { +// class GreetService { +// SomeVariable: number; +// } +// class OtherService { +// } +//} +//`, +// wantErr: false, +// }, +// { +// name: "should find bound services from other packages", +// dir: "../../examples/binding", +// wantModels: `namespace main { +// class localStruct { +// } +//} +//namespace models { +// class Person { +// Name: string; +// } +//} +//namespace services { +// class GreetService { +// SomeVariable: number; +// Parent: models.Person; +// } +//} +//`, +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// Debug = true +// context, err := ParseDirectory(tt.dir) +// if (err != nil) != tt.wantErr { +// t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// +// ts, err := GenerateModels(context) +// require.NoError(t, err) +// require.Equal(t, tt.wantModels, string(ts)) +// +// }) +// } +//} diff --git a/v3/internal/parser/templates/class.ts.tmpl b/v3/internal/parser/templates/class.ts.tmpl new file mode 100644 index 000000000..886f6b663 --- /dev/null +++ b/v3/internal/parser/templates/class.ts.tmpl @@ -0,0 +1,16 @@ + export class {{.Name}} { + {{range .Fields}}{{.}} + {{end}} + static createFrom(source: any = {}) { + return new {{.Name}}(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + {{range .Fields}}this.{{jsName .}} = source["{{jsName .}}"] + {{end}} + } + } diff --git a/v3/internal/parser/templates/model.ts.tmpl b/v3/internal/parser/templates/model.ts.tmpl new file mode 100644 index 000000000..232db7479 --- /dev/null +++ b/v3/internal/parser/templates/model.ts.tmpl @@ -0,0 +1,21 @@ +{{$pkg := .Package}} +export namespace {{.Package}} { + {{range $name, $def := .Models}} + export class {{$def.Name}} { + {{range $def.Fields}}{{.JSDef $pkg}} + {{end}} + static createFrom(source: any = {}) { + return new {{$def.Name}}(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + {{range $def.Fields}}this.{{.JSName}} = {{.TSBuild $pkg}}; + {{end}} + } + } + {{end}} +} diff --git a/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js b/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js new file mode 100644 index 000000000..53a819885 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/bindings_main.js @@ -0,0 +1,43 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet does XYZ + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +/** + * GreetService.NewPerson + * NewPerson creates a new person + * @param name {string} + * @returns {Promise} + **/ +function NewPerson(name) { + return wails.Call(GreetService("NewPerson", name)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + NewPerson, + }, +}; + diff --git a/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js b/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js new file mode 100644 index 000000000..48e7ed652 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/bindings_services.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +function OtherService(method) { + return { + packageName: "services", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Yay + * + * + * @returns {Promise} + **/ +function Yay() { + return wails.Call(OtherService("Yay")); +} + +window.go = window.go || {}; +window.go.services = { + OtherService: { + Yay, + }, +}; + diff --git a/v3/internal/parser/testdata/function_from_imported_package/main.go b/v3/internal/parser/testdata/function_from_imported_package/main.go new file mode 100644 index 000000000..3c061aa9b --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/main.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services" + "log" + + "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{ + Bind: []interface{}{ + &GreetService{}, + services.NewOtherService(), + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/function_from_imported_package/models.ts b/v3/internal/parser/testdata/function_from_imported_package/models.ts new file mode 100644 index 000000000..a6639f8eb --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/models.ts @@ -0,0 +1,51 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + address: services.Address; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.address = services.Address.createFrom(source['address']); + + } + } + +} + +export namespace services { + + export class Address { + street: string; + state: string; + country: string; + + static createFrom(source: any = {}) { + return new Address(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + this.state = source['state']; + this.country = source['country']; + + } + } + +} diff --git a/v3/internal/parser/testdata/function_from_imported_package/services/other.go b/v3/internal/parser/testdata/function_from_imported_package/services/other.go new file mode 100644 index 000000000..2daa9df17 --- /dev/null +++ b/v3/internal/parser/testdata/function_from_imported_package/services/other.go @@ -0,0 +1,26 @@ +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", + } +} + +func NewOtherService() *OtherService { + return &OtherService{} +} diff --git a/v3/internal/parser/testdata/function_single/bindings_main.js b/v3/internal/parser/testdata/function_single/bindings_main.js new file mode 100644 index 000000000..0a156d149 --- /dev/null +++ b/v3/internal/parser/testdata/function_single/bindings_main.js @@ -0,0 +1,29 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + }, +}; diff --git a/v3/internal/parser/testdata/function_single/main.go b/v3/internal/parser/testdata/function_single/main.go new file mode 100644 index 000000000..88de7bf89 --- /dev/null +++ b/v3/internal/parser/testdata/function_single/main.go @@ -0,0 +1,39 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() *GreetService { + return &GreetService{} +} + +func main() { + app := application.New(application.Options{ + Bind: []interface{}{ + NewGreetService(), + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/function_single/models.ts b/v3/internal/parser/testdata/function_single/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/function_single/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js b/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js new file mode 100644 index 000000000..277073941 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple/bindings_main.js @@ -0,0 +1,50 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} +function OtherService(method) { + return { + packageName: "main", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Hello + * + * + * @returns {Promise} + **/ +function Hello() { + return wails.Call(OtherService("Hello")); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + }, + OtherService: { + Hello, + }, +}; diff --git a/v3/internal/parser/testdata/struct_literal_multiple/main.go b/v3/internal/parser/testdata/struct_literal_multiple/main.go new file mode 100644 index 000000000..fb555d79f --- /dev/null +++ b/v3/internal/parser/testdata/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{ + Bind: []interface{}{ + &GreetService{}, + &OtherService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple/models.ts b/v3/internal/parser/testdata/struct_literal_multiple/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js b/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js new file mode 100644 index 000000000..277073941 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/bindings_main.js @@ -0,0 +1,50 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} +function OtherService(method) { + return { + packageName: "main", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Hello + * + * + * @returns {Promise} + **/ +function Hello() { + return wails.Call(OtherService("Hello")); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + }, + OtherService: { + Hello, + }, +}; diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/greet.go b/v3/internal/parser/testdata/struct_literal_multiple_files/greet.go new file mode 100644 index 000000000..2a45396a7 --- /dev/null +++ b/v3/internal/parser/testdata/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/parser/testdata/struct_literal_multiple_files/main.go b/v3/internal/parser/testdata/struct_literal_multiple_files/main.go new file mode 100644 index 000000000..a75d4d7bd --- /dev/null +++ b/v3/internal/parser/testdata/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{ + Bind: []interface{}{ + &GreetService{}, + &OtherService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/models.ts b/v3/internal/parser/testdata/struct_literal_multiple_files/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_files/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/struct_literal_multiple_files/other.go b/v3/internal/parser/testdata/struct_literal_multiple_files/other.go new file mode 100644 index 000000000..ad5e661ef --- /dev/null +++ b/v3/internal/parser/testdata/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/parser/testdata/struct_literal_multiple_other/bindings_main.js b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js new file mode 100644 index 000000000..53a819885 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_main.js @@ -0,0 +1,43 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet does XYZ + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +/** + * GreetService.NewPerson + * NewPerson creates a new person + * @param name {string} + * @returns {Promise} + **/ +function NewPerson(name) { + return wails.Call(GreetService("NewPerson", name)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + NewPerson, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js new file mode 100644 index 000000000..48e7ed652 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/bindings_services.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +function OtherService(method) { + return { + packageName: "services", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Yay + * + * + * @returns {Promise} + **/ +function Yay() { + return wails.Call(OtherService("Yay")); +} + +window.go = window.go || {}; +window.go.services = { + OtherService: { + Yay, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/main.go b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go new file mode 100644 index 000000000..8c6953dbb --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + "log" + + "github.com/wailsapp/wails/v3/internal/parser/testdata/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{ + Bind: []interface{}{ + &GreetService{}, + &services.OtherService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/models.ts b/v3/internal/parser/testdata/struct_literal_multiple_other/models.ts new file mode 100644 index 000000000..a6639f8eb --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/models.ts @@ -0,0 +1,51 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + address: services.Address; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.address = services.Address.createFrom(source['address']); + + } + } + +} + +export namespace services { + + export class Address { + street: string; + state: string; + country: string; + + static createFrom(source: any = {}) { + return new Address(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + this.state = source['state']; + this.country = source['country']; + + } + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go new file mode 100644 index 000000000..55472595b --- /dev/null +++ b/v3/internal/parser/testdata/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/parser/testdata/struct_literal_single/bindings_main.js b/v3/internal/parser/testdata/struct_literal_single/bindings_main.js new file mode 100644 index 000000000..5b3fcd587 --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_single/bindings_main.js @@ -0,0 +1,494 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.ArrayInt + * + * @param _in {number[]} + * @returns {Promise} + **/ +function ArrayInt(_in) { + return wails.Call(GreetService("ArrayInt", _in)); +} + +/** + * GreetService.BoolInBoolOut + * + * @param _in {boolean} + * @returns {Promise} + **/ +function BoolInBoolOut(_in) { + return wails.Call(GreetService("BoolInBoolOut", _in)); +} + +/** + * GreetService.Float32InFloat32Out + * + * @param _in {number} + * @returns {Promise} + **/ +function Float32InFloat32Out(_in) { + return wails.Call(GreetService("Float32InFloat32Out", _in)); +} + +/** + * GreetService.Float64InFloat64Out + * + * @param _in {number} + * @returns {Promise} + **/ +function Float64InFloat64Out(_in) { + return wails.Call(GreetService("Float64InFloat64Out", _in)); +} + +/** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +/** + * GreetService.Int16InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function Int16InIntOut(_in) { + return wails.Call(GreetService("Int16InIntOut", _in)); +} + +/** + * GreetService.Int16PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function Int16PointerInAndOutput(_in) { + return wails.Call(GreetService("Int16PointerInAndOutput", _in)); +} + +/** + * GreetService.Int32InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function Int32InIntOut(_in) { + return wails.Call(GreetService("Int32InIntOut", _in)); +} + +/** + * GreetService.Int32PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function Int32PointerInAndOutput(_in) { + return wails.Call(GreetService("Int32PointerInAndOutput", _in)); +} + +/** + * GreetService.Int64InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function Int64InIntOut(_in) { + return wails.Call(GreetService("Int64InIntOut", _in)); +} + +/** + * GreetService.Int64PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function Int64PointerInAndOutput(_in) { + return wails.Call(GreetService("Int64PointerInAndOutput", _in)); +} + +/** + * GreetService.Int8InIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function Int8InIntOut(_in) { + return wails.Call(GreetService("Int8InIntOut", _in)); +} + +/** + * GreetService.Int8PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function Int8PointerInAndOutput(_in) { + return wails.Call(GreetService("Int8PointerInAndOutput", _in)); +} + +/** + * GreetService.IntInIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function IntInIntOut(_in) { + return wails.Call(GreetService("IntInIntOut", _in)); +} + +/** + * GreetService.IntPointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function IntPointerInAndOutput(_in) { + return wails.Call(GreetService("IntPointerInAndOutput", _in)); +} + +/** + * GreetService.IntPointerInputNamedOutputs + * + * @param _in {number | null} + * @returns {Promise} + **/ +function IntPointerInputNamedOutputs(_in) { + return wails.Call(GreetService("IntPointerInputNamedOutputs", _in)); +} + +/** + * GreetService.MapIntInt + * + * @param _in {map} + * @returns {Promise} + **/ +function MapIntInt(_in) { + return wails.Call(GreetService("MapIntInt", _in)); +} + +/** + * GreetService.MapIntPointerInt + * + * @param _in {map} + * @returns {Promise} + **/ +function MapIntPointerInt(_in) { + return wails.Call(GreetService("MapIntPointerInt", _in)); +} + +/** + * GreetService.MapIntSliceInt + * + * @param _in {map} + * @returns {Promise} + **/ +function MapIntSliceInt(_in) { + return wails.Call(GreetService("MapIntSliceInt", _in)); +} + +/** + * GreetService.MapIntSliceIntInMapIntSliceIntOut + * + * @param _in {map} + * @returns {Promise} + **/ +function MapIntSliceIntInMapIntSliceIntOut(_in) { + return wails.Call(GreetService("MapIntSliceIntInMapIntSliceIntOut", _in)); +} + +/** + * GreetService.NoInputsStringOut + * + * + * @returns {Promise} + **/ +function NoInputsStringOut() { + return wails.Call(GreetService("NoInputsStringOut")); +} + +/** + * GreetService.PointerBoolInBoolOut + * + * @param _in {boolean | null} + * @returns {Promise} + **/ +function PointerBoolInBoolOut(_in) { + return wails.Call(GreetService("PointerBoolInBoolOut", _in)); +} + +/** + * GreetService.PointerFloat32InFloat32Out + * + * @param _in {number | null} + * @returns {Promise} + **/ +function PointerFloat32InFloat32Out(_in) { + return wails.Call(GreetService("PointerFloat32InFloat32Out", _in)); +} + +/** + * GreetService.PointerFloat64InFloat64Out + * + * @param _in {number | null} + * @returns {Promise} + **/ +function PointerFloat64InFloat64Out(_in) { + return wails.Call(GreetService("PointerFloat64InFloat64Out", _in)); +} + +/** + * GreetService.PointerMapIntInt + * + * @param _in {map | null} + * @returns {Promise} + **/ +function PointerMapIntInt(_in) { + return wails.Call(GreetService("PointerMapIntInt", _in)); +} + +/** + * GreetService.PointerStringInStringOut + * + * @param _in {string | null} + * @returns {Promise} + **/ +function PointerStringInStringOut(_in) { + return wails.Call(GreetService("PointerStringInStringOut", _in)); +} + +/** + * GreetService.StringArrayInputNamedOutput + * + * @param _in {string[]} + * @returns {Promise} + **/ +function StringArrayInputNamedOutput(_in) { + return wails.Call(GreetService("StringArrayInputNamedOutput", _in)); +} + +/** + * GreetService.StringArrayInputNamedOutputs + * + * @param _in {string[]} + * @returns {Promise} + **/ +function StringArrayInputNamedOutputs(_in) { + return wails.Call(GreetService("StringArrayInputNamedOutputs", _in)); +} + +/** + * GreetService.StringArrayInputStringArrayOut + * + * @param _in {string[]} + * @returns {Promise} + **/ +function StringArrayInputStringArrayOut(_in) { + return wails.Call(GreetService("StringArrayInputStringArrayOut", _in)); +} + +/** + * GreetService.StringArrayInputStringOut + * + * @param _in {string[]} + * @returns {Promise} + **/ +function StringArrayInputStringOut(_in) { + return wails.Call(GreetService("StringArrayInputStringOut", _in)); +} + +/** + * GreetService.StructInputStructOutput + * + * @param _in {main.Person} + * @returns {Promise} + **/ +function StructInputStructOutput(_in) { + return wails.Call(GreetService("StructInputStructOutput", _in)); +} + +/** + * GreetService.StructPointerInputErrorOutput + * + * @param _in {main.Person | null} + * @returns {Promise} + **/ +function StructPointerInputErrorOutput(_in) { + return wails.Call(GreetService("StructPointerInputErrorOutput", _in)); +} + +/** + * GreetService.StructPointerInputStructPointerOutput + * + * @param _in {main.Person | null} + * @returns {Promise} + **/ +function StructPointerInputStructPointerOutput(_in) { + return wails.Call(GreetService("StructPointerInputStructPointerOutput", _in)); +} + +/** + * GreetService.UInt16InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function UInt16InUIntOut(_in) { + return wails.Call(GreetService("UInt16InUIntOut", _in)); +} + +/** + * GreetService.UInt16PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function UInt16PointerInAndOutput(_in) { + return wails.Call(GreetService("UInt16PointerInAndOutput", _in)); +} + +/** + * GreetService.UInt32InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function UInt32InUIntOut(_in) { + return wails.Call(GreetService("UInt32InUIntOut", _in)); +} + +/** + * GreetService.UInt32PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function UInt32PointerInAndOutput(_in) { + return wails.Call(GreetService("UInt32PointerInAndOutput", _in)); +} + +/** + * GreetService.UInt64InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function UInt64InUIntOut(_in) { + return wails.Call(GreetService("UInt64InUIntOut", _in)); +} + +/** + * GreetService.UInt64PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function UInt64PointerInAndOutput(_in) { + return wails.Call(GreetService("UInt64PointerInAndOutput", _in)); +} + +/** + * GreetService.UInt8InUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function UInt8InUIntOut(_in) { + return wails.Call(GreetService("UInt8InUIntOut", _in)); +} + +/** + * GreetService.UInt8PointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function UInt8PointerInAndOutput(_in) { + return wails.Call(GreetService("UInt8PointerInAndOutput", _in)); +} + +/** + * GreetService.UIntInUIntOut + * + * @param _in {number} + * @returns {Promise} + **/ +function UIntInUIntOut(_in) { + return wails.Call(GreetService("UIntInUIntOut", _in)); +} + +/** + * GreetService.UIntPointerInAndOutput + * + * @param _in {number | null} + * @returns {Promise} + **/ +function UIntPointerInAndOutput(_in) { + return wails.Call(GreetService("UIntPointerInAndOutput", _in)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + ArrayInt, + BoolInBoolOut, + Float32InFloat32Out, + Float64InFloat64Out, + Greet, + Int16InIntOut, + Int16PointerInAndOutput, + Int32InIntOut, + Int32PointerInAndOutput, + Int64InIntOut, + Int64PointerInAndOutput, + Int8InIntOut, + Int8PointerInAndOutput, + IntInIntOut, + IntPointerInAndOutput, + IntPointerInputNamedOutputs, + MapIntInt, + MapIntPointerInt, + MapIntSliceInt, + MapIntSliceIntInMapIntSliceIntOut, + NoInputsStringOut, + PointerBoolInBoolOut, + PointerFloat32InFloat32Out, + PointerFloat64InFloat64Out, + PointerMapIntInt, + PointerStringInStringOut, + StringArrayInputNamedOutput, + StringArrayInputNamedOutputs, + StringArrayInputStringArrayOut, + StringArrayInputStringOut, + StructInputStructOutput, + StructPointerInputErrorOutput, + StructPointerInputStructPointerOutput, + UInt16InUIntOut, + UInt16PointerInAndOutput, + UInt32InUIntOut, + UInt32PointerInAndOutput, + UInt64InUIntOut, + UInt64PointerInAndOutput, + UInt8InUIntOut, + UInt8PointerInAndOutput, + UIntInUIntOut, + UIntPointerInAndOutput, + }, +}; + diff --git a/v3/internal/parser/testdata/struct_literal_single/main.go b/v3/internal/parser/testdata/struct_literal_single/main.go new file mode 100644 index 000000000..2438a69cb --- /dev/null +++ b/v3/internal/parser/testdata/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) MapIntPointerInt(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{ + Bind: []interface{}{ + &GreetService{}, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/struct_literal_single/models.ts b/v3/internal/parser/testdata/struct_literal_single/models.ts new file mode 100644 index 000000000..91bab56ac --- /dev/null +++ b/v3/internal/parser/testdata/struct_literal_single/models.ts @@ -0,0 +1,64 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + parent: Person; + details: anon1; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.parent = Person.createFrom(source['parent']); + this.details = anon1.createFrom(source['details']); + + } + } + + export class anon1 { + age: number; + address: anon2; + + static createFrom(source: any = {}) { + return new anon1(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.age = source['age']; + this.address = anon2.createFrom(source['address']); + + } + } + + export class anon2 { + street: string; + + static createFrom(source: any = {}) { + return new anon2(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + + } + } + +} diff --git a/v3/internal/parser/testdata/variable_single/bindings_main.js b/v3/internal/parser/testdata/variable_single/bindings_main.js new file mode 100644 index 000000000..0a156d149 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/bindings_main.js @@ -0,0 +1,29 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + }, +}; diff --git a/v3/internal/parser/testdata/variable_single/main.go b/v3/internal/parser/testdata/variable_single/main.go new file mode 100644 index 000000000..baffd783c --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/main.go @@ -0,0 +1,36 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func main() { + greetService := &GreetService{} + app := application.New(application.Options{ + Bind: []interface{}{ + greetService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/variable_single/models.ts b/v3/internal/parser/testdata/variable_single/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/variable_single/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js b/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js new file mode 100644 index 000000000..0a156d149 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_function/bindings_main.js @@ -0,0 +1,29 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet someone + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + }, +}; diff --git a/v3/internal/parser/testdata/variable_single_from_function/main.go b/v3/internal/parser/testdata/variable_single_from_function/main.go new file mode 100644 index 000000000..0d7121ca9 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_function/main.go @@ -0,0 +1,40 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "log" +) + +// GreetService is great +type GreetService struct { + SomeVariable int + lowerCase string +} + +// Greet someone +func (*GreetService) Greet(name string) string { + return "Hello " + name +} + +func NewGreetService() *GreetService { + return &GreetService{} +} + +func main() { + greetService := NewGreetService() + app := application.New(application.Options{ + Bind: []interface{}{ + greetService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/variable_single_from_function/models.ts b/v3/internal/parser/testdata/variable_single_from_function/models.ts new file mode 100644 index 000000000..0817f259b --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_function/models.ts @@ -0,0 +1,5 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// TODO : nothing generated yet \ No newline at end of file diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js new file mode 100644 index 000000000..53a819885 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_main.js @@ -0,0 +1,43 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {main} from './models'; + +function GreetService(method) { + return { + packageName: "main", + serviceName: "GreetService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * GreetService.Greet + * Greet does XYZ + * @param name {string} + * @returns {Promise} + **/ +function Greet(name) { + return wails.Call(GreetService("Greet", name)); +} + +/** + * GreetService.NewPerson + * NewPerson creates a new person + * @param name {string} + * @returns {Promise} + **/ +function NewPerson(name) { + return wails.Call(GreetService("NewPerson", name)); +} + +window.go = window.go || {}; +window.go.main = { + GreetService: { + Greet, + NewPerson, + }, +}; + diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js new file mode 100644 index 000000000..48e7ed652 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/bindings_services.js @@ -0,0 +1,32 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import {services} from './models'; + +function OtherService(method) { + return { + packageName: "services", + serviceName: "OtherService", + methodName: method, + args: Array.prototype.slice.call(arguments, 1), + }; +} + +/** + * OtherService.Yay + * + * + * @returns {Promise} + **/ +function Yay() { + return wails.Call(OtherService("Yay")); +} + +window.go = window.go || {}; +window.go.services = { + OtherService: { + Yay, + }, +}; + diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/main.go b/v3/internal/parser/testdata/variable_single_from_other_function/main.go new file mode 100644 index 000000000..9d10a301e --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/main.go @@ -0,0 +1,50 @@ +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/internal/parser/testdata/variable_single_from_other_function/services" + "log" + + "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() { + otherService := services.NewOtherService() + app := application.New(application.Options{ + Bind: []interface{}{ + &GreetService{}, + otherService, + }, + }) + + app.NewWebviewWindow() + + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/models.ts b/v3/internal/parser/testdata/variable_single_from_other_function/models.ts new file mode 100644 index 000000000..a6639f8eb --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/models.ts @@ -0,0 +1,51 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export namespace main { + + export class Person { + name: string; + address: services.Address; + + static createFrom(source: any = {}) { + return new Person(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.name = source['name']; + this.address = services.Address.createFrom(source['address']); + + } + } + +} + +export namespace services { + + export class Address { + street: string; + state: string; + country: string; + + static createFrom(source: any = {}) { + return new Address(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) { + source = JSON.parse(source); + } + + this.street = source['street']; + this.state = source['state']; + this.country = source['country']; + + } + } + +} diff --git a/v3/internal/parser/testdata/variable_single_from_other_function/services/other.go b/v3/internal/parser/testdata/variable_single_from_other_function/services/other.go new file mode 100644 index 000000000..2daa9df17 --- /dev/null +++ b/v3/internal/parser/testdata/variable_single_from_other_function/services/other.go @@ -0,0 +1,26 @@ +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", + } +} + +func NewOtherService() *OtherService { + return &OtherService{} +} diff --git a/v3/internal/plugins/plugins.go b/v3/internal/plugins/plugins.go new file mode 100644 index 000000000..05de5f908 --- /dev/null +++ b/v3/internal/plugins/plugins.go @@ -0,0 +1,36 @@ +package plugins + +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 pluginTemplate embed.FS + +type TemplateOptions struct { + *flags.PluginInit +} + +func Install(options *flags.PluginInit) error { + + if options.OutputDir == "." || options.OutputDir == "" { + options.OutputDir = filepath.Join(lo.Must(os.Getwd()), options.Name) + } + fmt.Printf("Creating plugin '%s' into '%s'\n", options.Name, options.OutputDir) + tfs, err := fs.Sub(pluginTemplate, "template") + if err != nil { + return err + } + + return gosod.New(tfs).Extract(options.OutputDir, options) +} diff --git a/v3/internal/plugins/template/NEXT STEPS.md b/v3/internal/plugins/template/NEXT STEPS.md new file mode 100644 index 000000000..c4ec1d913 --- /dev/null +++ b/v3/internal/plugins/template/NEXT STEPS.md @@ -0,0 +1,87 @@ +# Next Steps + +Congratulations on generating a plugin. This guide will help you author your +plugin and provide some tips on how to get started. + +## Plugin Structure + +The plugin is a standard Go module that adheres to the following interface: + +```go +type Plugin interface { + Name() string + Init(app *App) error + Shutdown() +} +``` + +The `Name()` method returns the name of the plugin. It should follow the Go +module naming convention and have a prefix of `wails-plugin-`, e.g. +`github.com/myuser/wails-plugin-example`. + +The `Init()` method is called when the plugin is loaded. The `App` parameter is +a pointer to the main application struct. This may be used for showing dialogs, +listening for events or even opening new windows. The `Init()` method should +return an error if it fails to initialise. This method is called synchronously +so the application will not start until it returns. + +The `Shutdown()` method is called when the application is shutting down. This is +a good place to perform any cleanup. This method is called synchronously so the +application will not exit completely until it returns. + +## Plugin Directory Structure + +The plugin directory structure is as follows: + +``` +plugin-name +├── models.d.ts +├── plugin.js +├── plugin.go +├── README.md +├── go.mod +├── go.sum +└── plugin.toml +``` + +### `plugin.go` + +This file contains the plugin code. It should contain a struct that implements +the `Plugin` interface and a `NewPlugin()` method that returns a pointer to the +struct. Methods are exported by capitalising the first letter of the method +name. These methods may be called from the frontend. If methods accept or return +structs, these structs must be exported. + +### `plugin.js` + +This file should contain any JavaScript code that may help developers use the +plugin. In the example plugin, this file contains function wrappers for the +plugin methods. It's good to include JSDocs as that will help developers using +your plugin. + +### `models.d.ts` + +This file should contain TypeScript definitions for any structs that are passed +or returned from the plugin. ` + +### `plugin.toml` + +This file contains the plugin metadata. It is important to fill this out +correctly as it will be used by the Wails CLI. + +### `README.md` + +This file should contain a description of the plugin and how to use it. It +should also contain a link to the plugin repository and how to report bugs. + +### `go.mod` and `go.sum` + +These are standard Go module files. The package name in `go.mod` should match +the name of the plugin, e.g. `github.com/myuser/wails-plugin-example`. + +## Promoting your Plugin + +Once you have created your plugin, you should promote it on the Wails Discord +server in the `#plugins` channel. You should also open a PR to promote your +plugin on the Wails website. Update the `website/content/plugins.md` file and +add your plugin to the list. diff --git a/v3/internal/plugins/template/README.tmpl.md b/v3/internal/plugins/template/README.tmpl.md new file mode 100644 index 000000000..c6217266c --- /dev/null +++ b/v3/internal/plugins/template/README.tmpl.md @@ -0,0 +1,41 @@ +# {{.Name}} Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "{{.Name}}": {{.Name}}.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js +wails + .Plugin("{{.Name}}", "All", "hello world") + .then((result) => console.log(result)); +``` + +This method returns a struct with the following fields: + +```typescript +interface Hashes { + MD5: string; + SHA1: string; + SHA256: string; +} +``` + +A TypeScript definition file is provided for this interface. + +## Support + +If you find a bug in this plugin, please raise a ticket +[here](https://github.com/plugin/repository). Please do not contact the Wails +team for support. diff --git a/v3/internal/plugins/template/go.mod.tmpl b/v3/internal/plugins/template/go.mod.tmpl new file mode 100644 index 000000000..77249136f --- /dev/null +++ b/v3/internal/plugins/template/go.mod.tmpl @@ -0,0 +1,13 @@ +module {{.Name}} + +go 1.20 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect +) diff --git a/v3/internal/plugins/template/go.sum b/v3/internal/plugins/template/go.sum new file mode 100644 index 000000000..29c7b303e --- /dev/null +++ b/v3/internal/plugins/template/go.sum @@ -0,0 +1,22 @@ +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/v2 v2.3.2-0.20230117193915-45c3a501d9e6 h1:Wn+nhnS+VytzE0PegUzSh4T3hXJCtggKGD/4U5H9+wQ= +github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8= +github.com/wailsapp/wails/v3 v3.0.0-alpha.0 h1:T5gqG98Xr8LBf69oxlPkhpsFD59w2SnqUZk6XHj8Zoc= +github.com/wailsapp/wails/v3 v3.0.0-alpha.0/go.mod h1:OAfO5bP0TSUvCIHZYc6Dqfow/9RqxzHvYtmhWPpo1c0= +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/plugins/template/models.d.ts.tmpl b/v3/internal/plugins/template/models.d.ts.tmpl new file mode 100644 index 000000000..567c91d70 --- /dev/null +++ b/v3/internal/plugins/template/models.d.ts.tmpl @@ -0,0 +1,10 @@ +// models.d.ts +// This file should contain any models that are used by the plugin. + +export namespace {{.Name}}Plugin { + export interface Hashes { + MD5: string; + SHA1: string; + SHA256: string; + } +} \ No newline at end of file diff --git a/v3/internal/plugins/template/plugin.go.tmpl b/v3/internal/plugins/template/plugin.go.tmpl new file mode 100644 index 000000000..e8104f2bd --- /dev/null +++ b/v3/internal/plugins/template/plugin.go.tmpl @@ -0,0 +1,67 @@ +package {{.Name}} + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Config struct { + // Add any configuration options here +} + +type Plugin struct{ + config *Config + app *application.App +} + +func NewPlugin(config *Config) *Plugin { + return &Plugin{ + config: config, + } +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() {} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/myuser/{{.Name}}" +} + +// Init 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. +func (p *Plugin) Init(app *application.App) error { + p.app = app + return nil +} + +// Exported returns a list of exported methods that can be called from the frontend +func (p *Plugin) CallableByJS() []string { + return []string{ + "Greet", + } +} + +// InjectJS returns any JS that should be injected into the frontend +func (p *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- +// Plugin 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. +// Any methods that you want to be callable from the frontend must be returned by the +// CallableByJS() method above. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *Plugin) Greet(name string) string { + return "Hello " + name +} diff --git a/v3/internal/plugins/template/plugin.tmpl.js b/v3/internal/plugins/template/plugin.tmpl.js new file mode 100644 index 000000000..c62bbb6e7 --- /dev/null +++ b/v3/internal/plugins/template/plugin.tmpl.js @@ -0,0 +1,46 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * @typedef {Object} Hashes - A collection of hashes. + * @property {string} md5 - The MD5 hash of a string, represented as a hexadecimal string. + * @property {string} sha1 - The SHA-1 hash of a string, represented as a hexadecimal string. + * @property {string} sha256 - The SHA-256 hash of a string, represented as a hexadecimal string. + */ + +/** + * Generate all hashes for a string. + * @param input {string} - The string to generate hashes for. + * @returns {Promise} + */ +export function All(input) { + return wails.Plugin("{{.Name}}", "All", input); +} + +/** + * Generate the MD5 hash for a string. + * @param input {string} - The string to generate the hash for. + * @returns {Promise} + */ +export function MD5(input) { + return wails.Plugin("{{.Name}}", "MD5", input); +} + +/** + * Generate the SHA-1 hash for a string. + * @param input {string} - The string to generate the hash for. + * @returns {Promise} + */ +export function SHA1(input) { + return wails.Plugin("{{.Name}}", "SHA1", input); +} + +/** + * Generate the SHA-256 hash for a string. + * @param input {string} - The string to generate the hash for. + * @returns {Promise} + */ +export function SHA256(input) { + return wails.Plugin("{{.Name}}", "SHA256", input); +} \ No newline at end of file diff --git a/v3/internal/plugins/template/plugin.tmpl.toml b/v3/internal/plugins/template/plugin.tmpl.toml new file mode 100644 index 000000000..76e7aa384 --- /dev/null +++ b/v3/internal/plugins/template/plugin.tmpl.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "{{.Name}}" plugin. + +Name = "{{.Name}}" +Description = "{{.Description}}" +Author = "" +Version = "" +Website = "" +Repository = "" +License = "" + + diff --git a/v3/internal/runtime/README.md b/v3/internal/runtime/README.md new file mode 100644 index 000000000..6b0091a16 --- /dev/null +++ b/v3/internal/runtime/README.md @@ -0,0 +1,4 @@ +# Runtime + +To rebuild the runtime run `task build-runtime` or if you have Wails v3 CLI, you +can use `wails task build-runtime`. diff --git a/v3/internal/runtime/assets.go b/v3/internal/runtime/assets.go new file mode 100644 index 000000000..fb3f09ebe --- /dev/null +++ b/v3/internal/runtime/assets.go @@ -0,0 +1,23 @@ +//go:build production + +package runtime + +var RuntimeAssetsBundle = &RuntimeAssets{ + runtimeDesktopJS: DesktopRuntime, +} + +type RuntimeAssets struct { + runtimeDesktopJS []byte +} + +func (r *RuntimeAssets) DesktopIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) WebsocketIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) RuntimeDesktopJS() []byte { + return r.runtimeDesktopJS +} diff --git a/v3/internal/runtime/assets_dev.go b/v3/internal/runtime/assets_dev.go new file mode 100644 index 000000000..c5e5ffe7d --- /dev/null +++ b/v3/internal/runtime/assets_dev.go @@ -0,0 +1,23 @@ +//go:build !production + +package runtime + +var RuntimeAssetsBundle = &RuntimeAssets{ + runtimeDesktopJS: DesktopRuntime, +} + +type RuntimeAssets struct { + runtimeDesktopJS []byte +} + +func (r *RuntimeAssets) DesktopIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) WebsocketIPC() []byte { + return []byte("") +} + +func (r *RuntimeAssets) RuntimeDesktopJS() []byte { + return r.runtimeDesktopJS +} diff --git a/v3/internal/runtime/desktop/README.md b/v3/internal/runtime/desktop/README.md new file mode 100644 index 000000000..aeaf80f8c --- /dev/null +++ b/v3/internal/runtime/desktop/README.md @@ -0,0 +1,4 @@ +# README + +After updating any files in this directory, you must run +`wails task build-runtime` to regenerate the compiled JS. diff --git a/v3/internal/runtime/desktop/api/README.md b/v3/internal/runtime/desktop/api/README.md new file mode 100644 index 000000000..90c63787e --- /dev/null +++ b/v3/internal/runtime/desktop/api/README.md @@ -0,0 +1,367 @@ +# Wails API + +This package provides a typed Javascript API for Wails applications. + +It provides methods for the following components: + +- [Dialog](#dialog) +- [Events](#events) +- [Window](#window) +- [Plugin](#plugin) +- [Screens](#screens) +- [Application](#application) + +## Installation + +In your Wails application, run the following command in the frontend project +directory: + +```bash +npm install -D @wailsapp/api +``` + +## Usage + +Import the API into your application: + +```javascript +import * as Wails from "@wailsapp/api"; +``` + +Then use the API components: + +```javascript +function showDialog() { + Wails.Dialog.Info({ + Title: "Hello", + }).then((result) => { + console.log("Result: " + result); + }); +} +``` + +Individual components of the API can also be imported directly. + +## API + +### Dialog + +The Dialog API provides access to the native system dialogs. + +```javascript +import { Dialog } from "@wailsapp/api"; + +function example() { + Dialog.Info({ + Title: "Hello", + }).then((result) => { + console.log("Result: " + result); + }); +} +``` + +#### Message Dialogs + +Message dialogs are used to display a message to the user. They can be used to +display information, errors, warnings and questions. Each method returns the +button that was pressed by the user. + +- `Info(options: MessageDialogOptions): Promise` +- `Error(options: MessageDialogOptions): Promise` +- `Warning(options: MessageDialogOptions): Promise` +- `Question(options: MessageDialogOptions): Promise` + +#### Open Dialog + +The Open Dialog is used to open a file or directory. It returns the path of the +selected file or directory. If the `AllowsMultipleFiles` option is set, multiple +files or directories can be selected and are returned as an array of file paths. + +- `Open(options: OpenDialogOptions): Promise` + +#### Save Dialog + +The Save Dialog is used to save a file. It returns the path of the selected +file. + +- `Save(options: SaveDialogOptions): Promise` + +### Events + +The Events API provides access to the Wails event system. This is a global event +system that can be used to send events between the Go and Javascript. Events are +available to every window in a multi-window application. These API methods are +specific to the window in which they are called in. + +```javascript +import { Events } from "@wailsapp/api"; + +function example() { + // Emit an event + Events.Emit("myevent", { message: "Hello" }); + + // Subscribe to an event + let unsub = Events.On("otherEvent", (data) => { + console.log("Received event: " + data); + }); + + // Unsubscribe from the event + unsub(); +} +``` + +#### Emit + +Emit an event with optional data. + +- `Emit(eventName: string, data?: any): void` + +#### Subscribe + +Three methods are provided to subscribe to an event: + +- `On(eventName: string, callback: (data: any) => void): () => void` - Subscribe + to all events of the given name +- `Once(eventName: string, callback: (data: any) => void): () => void` - + Subscribe to one event of the given name +- `OnMultiple(eventName: string, callback: (data: any) => void, count: number): () => void` - + Subscribe to multiple events of the given name + +The callback will be called when the event is emitted. The returned function can +be called to unsubscribe from the event. + +#### Unsubscribe + +As well as unsubscribing from a single event, you can unsubscribe from events of +a given name or all events. + +- `Off(eventName: string, additionalEventNames: ...string): void` - Unsubscribe + from all events of the given name(s) +- `OffAll(): void` - Unsubscribe all events + +### Window + +The Window API provides a number of methods that interact with the window in +which the API is called. + +- `Center: (void) => void` - Center the window +- `SetTitle: (title) => void` - Set the window title +- `Fullscreen: () => void` - Set the window to fullscreen +- `UnFullscreen: () => void` - Restore a fullscreen window +- `SetSize: (width: number, height: number) => void` - Set the window size +- `Size: () => Size` - Get the window size +- `SetMaxSize: (width, height) => void` - Set the window maximum size +- `SetMinSize: (width, height) => void` - Set the window minimum size +- `SetAlwaysOnTop: (onTop) => void` - Set window to be always on top +- `SetPosition: (x, y) => void` - Set the window position +- `Position: () => Position` - Get the window position +- `SetResizable: (resizable) => void` - Set whether the window is resizable +- `Screen: () => Screen` - Get information about the screen the window is on +- `Hide: () => void` - Hide the window +- `Show: () => void` - Show the window +- `Maximise: () => void` - Maximise the window +- `Close: () => void` - Close the window +- `ToggleMaximise: () => void` - Toggle the window maximise state +- `UnMaximise: () => void` - UnMaximise the window +- `Minimise: () => void` - Minimise the window +- `UnMinimise: () => void` - UnMinimise the window +- `SetBackgroundColour: (r, g, b, a) => void` - Set the background colour of the + window + +### Plugin + +The Plugin API provides access to the Wails plugin system. This method provides +the ability to call a plugin method from the frontend. + +```javascript +import { Plugin } from "@wailsapp/api"; + +function example() { + // Call a plugin method + Plugin.Call("myplugin", "MyMethod", { message: "Hello" }).then((result) => { + console.log("Result: " + result); + }); +} +``` + +### Screens + +The Screens API provides access to the Wails screen system. + +```javascript +import { Screens } from "@wailsapp/api"; + +function example() { + // Get all attatched screens + Screens.GetAll().then((screens) => { + console.log("Screens: " + screens); + }); + + // Get the primary screen + Screens.GetPrimary().then((screen) => { + console.log("Primary screen: " + screen); + }); + + // Get the screen the window is on + Screens.GetCurrent().then((screen) => { + console.log("Window screen: " + screen); + }); +} +``` + +- `GetAll: () => Promise` - Get all screens +- `GetPrimary: () => Promise` - Get the primary screen +- `GetCurrent: () => Promise` - Get the screen the window is on + +### Application + +The Application API provides access to the Wails application system. + +```javascript +import { Application } from "@wailsapp/api"; + +function example() { + // Hide the application + Application.Hide(); + + // Shopw the application + Application.Show(); + + // Quit the application + Application.Quit(); +} +``` + +- `Hide: () => void` - Hide the application +- `Show: () => void` - Show the application +- `Quit: () => void` - Quit the application + +## Types + +This is a comprehensive list of types used by the Wails API. + +```typescript +export interface Button { + // The label of the button + Label?: string; + // True if this button is the cancel button (selected when pressing escape) + IsCancel?: boolean; + // True if this button is the default button (selected when pressing enter) + IsDefault?: boolean; +} + +interface MessageDialogOptions { + // The title for the dialog + Title?: string; + // The message to display + Message?: string; + // The buttons to use on the dialog + Buttons?: Button[]; +} + +export interface OpenFileDialogOptions { + // Allows the user to be able to select directories + CanChooseDirectories?: boolean; + // Allows the user to be able to select files + CanChooseFiles?: boolean; + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Whether the dialog should follow filesystem aliases + ResolvesAliases?: boolean; + // Allow the user to select multiple files or directories + AllowsMultipleSelection?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // Allows selection of filetypes not specified in the filters + AllowsOtherFiletypes?: boolean; + // The file filters to use in the dialog + Filters?: FileFilter[]; + // The title of the dialog + Title?: string; + // The message to display + Message?: string; + // The label for the select button + ButtonText?: string; + // The default directory to open the dialog in + Directory?: string; +} +export interface FileFilter { + // The display name for the filter, e.g. "Text Files" + DisplayName?: string; + // The pattern to use for the filter, e.g. "*.txt;*.md" + Pattern?: string; +} +export interface SaveFileDialogOptions { + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Allows selection of filetypes not specified in the filters + AllowOtherFiletypes?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // The message to show in the dialog + Message?: string; + // The default directory to open the dialog in + Directory?: string; + // The default filename to use in the dialog + Filename?: string; + // The label for the select button + ButtonText?: string; +} + +export interface Screen { + // The screen ID + Id: string; + // The screen name + Name: string; + // The screen scale. 1 = standard resolution, 2: 2x retina, etc. + Scale: number; + // The X position of the screen + X: number; + // The Y position of the screen + Y: number; + // The width and height of the screen + Size: Size; + // The bounds of the screen + Bounds: Rect; + // The work area of the screen + WorkArea: Rect; + // True if this is the primary screen + IsPrimary: boolean; + // The rotation of the screen + Rotation: number; +} +export interface Rect { + X: number; + Y: number; + Width: number; + Height: number; +} + +export interface WailsEvent { + // The name of the event + Name: string; + // The data associated with the event + Data?: any; +} + +export interface Size { + Width: number; + Height: number; +} +export interface Position { + X: number; + Y: number; +} +``` diff --git a/v3/internal/runtime/desktop/api/index.js b/v3/internal/runtime/desktop/api/index.js new file mode 100644 index 000000000..edc342299 --- /dev/null +++ b/v3/internal/runtime/desktop/api/index.js @@ -0,0 +1,349 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ +/* jshint esversion: 9 */ + +/** + * @typedef {import("./types").MessageDialogOptions} MessageDialogOptions + * @typedef {import("./types").OpenDialogOptions} OpenDialogOptions + * @typedef {import("./types").SaveDialogOptions} SaveDialogOptions + * @typedef {import("./types").Screen} Screen + * @typedef {import("./types").Size} Size + * @typedef {import("./types").Position} Position + * + */ + +/** + * The Clipboard API provides methods to interact with the system clipboard. + */ +export const Clipboard = { + /** + * Gets the text from the clipboard + * @returns {Promise} + */ + Text: () => { + return wails.Clipboard.Text(); + }, + /** + * Sets the text on the clipboard + * @param {string} text - text to set in the clipboard + */ + SetText: (text) => { + return wails.Clipboard.SetText(text); + }, +}; + +/** + * The Application API provides methods to interact with the application. + */ +export const Application = { + /** + * Hides the application + */ + Hide: () => { + return wails.Application.Hide(); + }, + /** + * Shows the application + */ + Show: () => { + return wails.Application.Show(); + }, + /** + * Quits the application + */ + Quit: () => { + return wails.Application.Quit(); + }, +}; + +/** + * The Screens API provides methods to interact with the system screens/monitors. + */ +export const Screens = { + /** + * Get the primary screen + * @returns {Promise} + */ + GetPrimary: () => { + return wails.Screens.GetPrimary(); + }, + /** + * Get all screens + * @returns {Promise} + */ + GetAll: () => { + return wails.Screens.GetAll(); + }, + /** + * Get the current screen + * @returns {Promise} + */ + GetCurrent: () => { + return wails.Screens.GetCurrent(); + }, +}; + +/** + * Call a plugin method + * @param {string} pluginName - name of the plugin + * @param {string} methodName - name of the method + * @param {...any} args - arguments to pass to the method + * @returns {Promise} - promise that resolves with the result + */ +export const Plugin = (pluginName, methodName, ...args) => { + return wails.Plugin(pluginName, methodName, ...args); +}; + +/** + * The Dialog API provides methods to interact with system dialogs. + */ +export const Dialog = { + /** + * Shows an info dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Info: (options) => { + return wails.Dialog.Info(options); + }, + /** + * Shows a warning dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Warning: (options) => { + return wails.Dialog.Warning(options); + }, + /** + * Shows an error dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Error: (options) => { + return wails.Dialog.Error(options); + }, + + /** + * Shows a question dialog + * @param {MessageDialogOptions} options - options for the dialog + * @returns {Promise} + */ + Question: (options) => { + return wails.Dialog.Question(options); + }, + + /** + * Shows a file open dialog and returns the files selected by the user. + * A blank string indicates that the dialog was cancelled. + * @param {OpenDialogOptions} options - options for the dialog + * @returns {Promise|Promise} + */ + OpenFile: (options) => { + return wails.Dialog.OpenFile(options); + }, + + /** + * Shows a file save dialog and returns the filename given by the user. + * A blank string indicates that the dialog was cancelled. + * @param {SaveDialogOptions} options - options for the dialog + * @returns {Promise} + */ + SaveFile: (options) => { + return wails.Dialog.SaveFile(options); + }, +}; + +/** + * The Events API provides methods to interact with the event system. + */ +export const Events = { + /** + * Emit an event + * @param {string} name + * @param {any=} data + */ + Emit: (name, data) => { + return wails.Events.Emit(name, data); + }, + /** + * Subscribe to an event + * @param {string} name - name of the event + * @param {(any) => void} callback - callback to call when the event is emitted + @returns {function()} unsubscribeMethod - method to unsubscribe from the event + */ + On: (name, callback) => { + return wails.Events.On(name, callback); + }, + /** + * Subscribe to an event once + * @param {string} name - name of the event + * @param {(any) => void} callback - callback to call when the event is emitted + * @returns {function()} unsubscribeMethod - method to unsubscribe from the event + */ + Once: (name, callback) => { + return wails.Events.Once(name, callback); + }, + /** + * Subscribe to an event multiple times + * @param {string} name - name of the event + * @param {(any) => void} callback - callback to call when the event is emitted + * @param {number} count - number of times to call the callback + * @returns {Promise} unsubscribeMethod - method to unsubscribe from the event + */ + OnMultiple: (name, callback, count) => { + return wails.Events.OnMultiple(name, callback, count); + }, + /** + * Unsubscribe from an event + * @param {string} name - name of the event to unsubscribe from + * @param {...string} additionalNames - additional names of events to unsubscribe from + */ + Off: (name, ...additionalNames) => { + wails.Events.Off(name, additionalNames); + }, + /** + * Unsubscribe all listeners from all events + */ + OffAll: () => { + wails.Events.OffAll(); + }, +}; + +/** + * The Window API provides methods to interact with the window. + */ +export const Window = { + /** + * Center the window. + */ + Center: () => void wails.Window.Center(), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void wails.Window.SetTitle(title), + + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void wails.Window.Fullscreen(), + + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void wails.Window.UnFullscreen(), + + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => void wails.Window.SetSize(width, height), + + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return wails.Window.Size(); + }, + + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void wails.Window.SetMaxSize(width, height), + + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void wails.Window.SetMinSize(width, height), + + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void wails.Window.SetAlwaysOnTop(onTop), + + /** + * Set the window position. + * @param {number} x + * @param {number} y + */ + SetPosition: (x, y) => void wails.Window.SetPosition(x, y), + + /** + * Get the window position. + * @returns {Promise} The window position + */ + Position: () => { + return wails.Window.Position(); + }, + + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return wails.Window.Screen(); + }, + + /** + * Hide the window + */ + Hide: () => void wails.Window.Hide(), + + /** + * Maximise the window + */ + Maximise: () => void wails.Window.Maximise(), + + /** + * Show the window + */ + Show: () => void wails.Window.Show(), + + /** + * Close the window + */ + Close: () => void wails.Window.Close(), + + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void wails.Window.ToggleMaximise(), + + /** + * Unmaximise the window + */ + UnMaximise: () => void wails.Window.UnMaximise(), + + /** + * Minimise the window + */ + Minimise: () => void wails.Window.Minimise(), + + /** + * Unminimise the window + */ + UnMinimise: () => void wails.Window.UnMinimise(), + + /** + * Set the background colour of the window. + * @param {number} r - The red value between 0 and 255 + * @param {number} g - The green value between 0 and 255 + * @param {number} b - The blue value between 0 and 255 + * @param {number} a - The alpha value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void wails.Window.SetBackgroundColour(r, g, b, a), +}; diff --git a/v3/internal/runtime/desktop/api/package.json b/v3/internal/runtime/desktop/api/package.json new file mode 100644 index 000000000..fa19b24bb --- /dev/null +++ b/v3/internal/runtime/desktop/api/package.json @@ -0,0 +1,16 @@ +{ + "name": "@wailsapp/api", + "version": "3.0.0-alpha.3", + "description": "Wails Runtime API", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/wailsapp/wails.git" + }, + "author": "The Wails Team", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://wails.io" +} diff --git a/v3/internal/runtime/desktop/api/types.d.ts b/v3/internal/runtime/desktop/api/types.d.ts new file mode 100644 index 000000000..bbbc0662e --- /dev/null +++ b/v3/internal/runtime/desktop/api/types.d.ts @@ -0,0 +1,124 @@ + +export interface Button { + // The label of the button + Label?: string; + // True if this button is the cancel button (selected when pressing escape) + IsCancel?: boolean; + // True if this button is the default button (selected when pressing enter) + IsDefault?: boolean; +} + +interface MessageDialogOptions { + // The title for the dialog + Title?: string; + // The message to display + Message?: string; + // The buttons to use on the dialog + Buttons?: Button[]; +} + +export interface OpenFileDialogOptions { + // Allows the user to be able to select directories + CanChooseDirectories?: boolean; + // Allows the user to be able to select files + CanChooseFiles?: boolean; + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Whether the dialog should follow filesystem aliases + ResolvesAliases?: boolean; + // Allow the user to select multiple files or directories + AllowsMultipleSelection?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // Allows selection of filetypes not specified in the filters + AllowsOtherFiletypes?: boolean; + // The file filters to use in the dialog + Filters?: FileFilter[]; + // The title of the dialog + Title?: string; + // The message to display + Message?: string; + // The label for the select button + ButtonText?: string; + // The default directory to open the dialog in + Directory?: string; +} +export interface FileFilter { + // The display name for the filter, e.g. "Text Files" + DisplayName?: string; + // The pattern to use for the filter, e.g. "*.txt;*.md" + Pattern?: string; +} +export interface SaveFileDialogOptions { + // Provide an option to create directories in the dialog + CanCreateDirectories?: boolean; + // Makes the dialog show hidden files + ShowHiddenFiles?: boolean; + // Allow the user to select files where the system hides their extensions + CanSelectHiddenExtension?: boolean; + // Allows selection of filetypes not specified in the filters + AllowOtherFiletypes?: boolean; + // Hide the extension when showing the filename + HideExtension?: boolean; + // Treats file packages as directories, e.g. .app on macOS + TreatsFilePackagesAsDirectories?: boolean; + // The message to show in the dialog + Message?: string; + // The default directory to open the dialog in + Directory?: string; + // The default filename to use in the dialog + Filename?: string; + // The label for the select button + ButtonText?: string; +} + +export interface Screen { + // The screen ID + Id: string; + // The screen name + Name: string; + // The screen scale. 1 = standard resolution, 2: 2x retina, etc. + Scale: number; + // The X position of the screen + X: number; + // The Y position of the screen + Y: number; + // The width and height of the screen + Size: Size; + // The bounds of the screen + Bounds: Rect; + // The work area of the screen + WorkArea: Rect; + // True if this is the primary screen + IsPrimary: boolean; + // The rotation of the screen + Rotation: number; +} +export interface Rect { + X: number; + Y: number; + Width: number; + Height: number; +} + +export interface WailsEvent { + // The name of the event + Name: string; + // The data associated with the event + Data?: any; +} + +export interface Size { + Width: number; + Height: number; +} +export interface Position { + X: number; + Y: number; +} diff --git a/v3/internal/runtime/desktop/application.js b/v3/internal/runtime/desktop/application.js new file mode 100644 index 000000000..b884ad34e --- /dev/null +++ b/v3/internal/runtime/desktop/application.js @@ -0,0 +1,37 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCaller} from "./runtime"; + +let call = newRuntimeCaller("application"); + +/** + * Hide the application + */ +export function Hide() { + void call("Hide"); +} + +/** + * Show the application + */ +export function Show() { + void call("Show"); +} + + +/** + * Quit the application + */ +export function Quit() { + void call("Quit"); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/calls.js b/v3/internal/runtime/desktop/calls.js new file mode 100644 index 000000000..0900c5812 --- /dev/null +++ b/v3/internal/runtime/desktop/calls.js @@ -0,0 +1,80 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCaller} from "./runtime"; + +import { nanoid } from 'nanoid/non-secure'; + +let call = newRuntimeCaller("call"); + +let callResponses = new Map(); + +function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; +} + +export function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } +} + +export function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } +} + +function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, {resolve, reject}); + call(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); +} + +export function Call(options) { + return callBinding("Call", options); +} + +/** + * Call a plugin method + * @param {string} pluginName - name of the plugin + * @param {string} methodName - name of the method + * @param {...any} args - arguments to pass to the method + * @returns {Promise} - promise that resolves with the result + */ +export function Plugin(pluginName, methodName, ...args) { + return callBinding("Call", { + packageName: "wails-plugins", + structName: pluginName, + methodName: methodName, + args: args, + }); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/clipboard.js b/v3/internal/runtime/desktop/clipboard.js new file mode 100644 index 000000000..0d814d2dc --- /dev/null +++ b/v3/internal/runtime/desktop/clipboard.js @@ -0,0 +1,30 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCaller} from "./runtime"; + +let call = newRuntimeCaller("clipboard"); + +/** + * Set the Clipboard text + */ +export function SetText(text) { + void call("SetText", {text}); +} + +/** + * Get the Clipboard text + * @returns {Promise} + */ +export function Text() { + return call("Text"); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/contextmenu.js b/v3/internal/runtime/desktop/contextmenu.js new file mode 100644 index 000000000..7b6e2c1f4 --- /dev/null +++ b/v3/internal/runtime/desktop/contextmenu.js @@ -0,0 +1,32 @@ +import {newRuntimeCaller} from "./runtime"; + +let call = newRuntimeCaller("contextmenu"); + +function openContextMenu(id, x, y, data) { + return call("OpenContextMenu", {id, x, y, data}); +} + +export function enableContextMenus(enabled) { + if (enabled) { + window.addEventListener('contextmenu', contextMenuHandler); + } else { + window.removeEventListener('contextmenu', contextMenuHandler); + } +} + +function contextMenuHandler(event) { + processContextMenu(event.target, event); +} + +function processContextMenu(element, event) { + let id = element.getAttribute('data-contextmenu'); + if (id) { + event.preventDefault(); + openContextMenu(id, event.clientX, event.clientY, element.getAttribute('data-contextmenu-data')); + } else { + let parent = element.parentElement; + if (parent) { + processContextMenu(parent, event); + } + } +} diff --git a/v3/internal/runtime/desktop/dialogs.js b/v3/internal/runtime/desktop/dialogs.js new file mode 100644 index 000000000..e923f53ce --- /dev/null +++ b/v3/internal/runtime/desktop/dialogs.js @@ -0,0 +1,121 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./api/types").MessageDialogOptions} MessageDialogOptions + * @typedef {import("./api/types").OpenDialogOptions} OpenDialogOptions + * @typedef {import("./api/types").SaveDialogOptions} SaveDialogOptions + */ + +import {newRuntimeCaller} from "./runtime"; + +import { nanoid } from 'nanoid/non-secure'; + +let call = newRuntimeCaller("dialog"); + +let dialogResponses = new Map(); + +function generateID() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; +} + +export function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } +} +export function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } +} + +function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, {resolve, reject}); + call(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); +} + + +/** + * Shows an Info dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Info(options) { + return dialog("Info", options); +} + +/** + * Shows an Warning dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Warning(options) { + return dialog("Warning", options); +} + +/** + * Shows an Error dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Error(options) { + return dialog("Error", options); +} + +/** + * Shows a Question dialog with the given options. + * @param {MessageDialogOptions} options + * @returns {Promise} The label of the button pressed + */ +export function Question(options) { + return dialog("Question", options); +} + +/** + * Shows an Open dialog with the given options. + * @param {OpenDialogOptions} options + * @returns {Promise} Returns the selected file or an array of selected files if AllowsMultipleSelection is true. A blank string is returned if no file was selected. + */ +export function OpenFile(options) { + return dialog("OpenFile", options); +} + +/** + * Shows a Save dialog with the given options. + * @param {OpenDialogOptions} options + * @returns {Promise} Returns the selected file. A blank string is returned if no file was selected. + */ +export function SaveFile(options) { + return dialog("SaveFile", options); +} + diff --git a/v3/internal/runtime/desktop/events.js b/v3/internal/runtime/desktop/events.js new file mode 100644 index 000000000..b1d740092 --- /dev/null +++ b/v3/internal/runtime/desktop/events.js @@ -0,0 +1,194 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./api/types").WailsEvent} WailsEvent + */ + +import {newRuntimeCaller} from "./runtime"; + +let call = newRuntimeCaller("events"); + +/** + * The Listener class defines a listener! :-) + * + * @class Listener + */ +class Listener { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + // Default of -1 means infinite + this.maxCallbacks = maxCallbacks || -1; + // Callback invokes the callback with the given data + // Returns true if this listener should be destroyed + this.Callback = (data) => { + callback(data); + // If maxCallbacks is infinite, return false (do not destroy) + if (this.maxCallbacks === -1) { + return false; + } + // Decrement maxCallbacks. Return true if now 0, otherwise false + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } +} + + +/** + * WailsEvent defines a custom event. It is passed to event listeners. + * + * @class WailsEvent + * @property {string} name - Name of the event + * @property {any} data - Data associated with the event + */ +export class WailsEvent { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } +} + +export const eventListeners = new Map(); + +/** + * Registers an event listener that will be invoked `maxCallbacks` times before being destroyed + * + * @export + * @param {string} eventName + * @param {function(WailsEvent): void} callback + * @param {number} maxCallbacks + * @returns {function} A function to cancel the listener + */ +export 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); +} + +/** + * Registers an event listener that will be invoked every time the event is emitted + * + * @export + * @param {string} eventName + * @param {function(WailsEvent): void} callback + * @returns {function} A function to cancel the listener + */ +export function On(eventName, callback) { + return OnMultiple(eventName, callback, -1); +} + +/** + * Registers an event listener that will be invoked once then destroyed + * + * @export + * @param {string} eventName + * @param {function(WailsEvent): void} callback + @returns {function} A function to cancel the listener + */ +export function Once(eventName, callback) { + return OnMultiple(eventName, callback, 1); +} + +/** + * listenerOff unregisters a listener previously registered with On + * + * @param {Listener} listener + */ +function listenerOff(listener) { + const eventName = listener.eventName; + // Remove local listener + let listeners = eventListeners.get(eventName).filter(l => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } +} + +/** + * dispatches an event to all listeners + * + * @export + * @param {WailsEvent} event + */ +export function dispatchWailsEvent(event) { + console.log("dispatching event: ", {event}); + let listeners = eventListeners.get(event.name); + if (listeners) { + // iterate listeners and call callback. If callback returns true, remove listener + let toRemove = []; + listeners.forEach(listener => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + // remove listeners + if (toRemove.length > 0) { + listeners = listeners.filter(l => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } +} + +/** + * Off unregisters a listener previously registered with On, + * optionally multiple listeners can be unregistered via `additionalEventNames` + * + [v3 CHANGE] Off only unregisters listeners within the current window + * + * @param {string} eventName + * @param {...string} additionalEventNames + */ +export function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach(eventName => { + eventListeners.delete(eventName); + }); +} + +/** + * OffAll unregisters all listeners + * [v3 CHANGE] OffAll only unregisters listeners within the current window + * + */ +export function OffAll() { + eventListeners.clear(); +} + +/** + * Emit an event + * @param {WailsEvent} event The event to emit + */ +export function Emit(event) { + void call("Emit", event); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/events.test.js b/v3/internal/runtime/desktop/events.test.js new file mode 100644 index 000000000..a9b679657 --- /dev/null +++ b/v3/internal/runtime/desktop/events.test.js @@ -0,0 +1,115 @@ +import { On, Off, OffAll, OnMultiple, WailsEvent, dispatchWailsEvent, eventListeners, Once } from './events'; +import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest'; + +afterEach(() => { + OffAll(); + vi.resetAllMocks(); +}); + +describe('OnMultiple', () => { + let testEvent = new WailsEvent('a', {}); + + it('should stop after a specified number of times', () => { + const cb = vi.fn(); + OnMultiple('a', cb, 5); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + dispatchWailsEvent(testEvent); + expect(cb).toBeCalledTimes(5); + }); + + it('should return a cancel fn', () => { + const cb = vi.fn() + const cancel = OnMultiple('a', cb, 5) + dispatchWailsEvent(testEvent) + dispatchWailsEvent(testEvent) + cancel() + dispatchWailsEvent(testEvent) + dispatchWailsEvent(testEvent) + expect(cb).toBeCalledTimes(2) + }) +}) + +describe('On', () => { + it('should create a listener with a count of -1', () => { + On('a', () => {}) + expect(eventListeners.get("a")[0].maxCallbacks).toBe(-1) + }) + + it('should return a cancel fn', () => { + const cancel = On('a', () => {}) + cancel(); + }) +}) + +describe('Once', () => { + it('should create a listener with a count of 1', () => { + Once('a', () => {}) + expect(eventListeners.get("a")[0].maxCallbacks).toBe(1) + }) + + it('should return a cancel fn', () => { + const cancel = EventsOn('a', () => {}) + cancel(); + }) +}) +// +// describe('EventsNotify', () => { +// it('should inform a listener', () => { +// const cb = vi.fn() +// EventsOn('a', cb) +// EventsNotify(JSON.stringify({name: 'a', data: ["one", "two", "three"]})) +// expect(cb).toBeCalledTimes(1); +// expect(cb).toHaveBeenLastCalledWith("one", "two", "three"); +// expect(window.WailsInvoke).toBeCalledTimes(0); +// }) +// }) +// +// describe('EventsEmit', () => { +// it('should emit an event', () => { +// EventsEmit('a', 'one', 'two', 'three') +// expect(window.WailsInvoke).toBeCalledTimes(1); +// const calledWith = window.WailsInvoke.calls[0][0]; +// expect(calledWith.slice(0, 2)).toBe('EE') +// expect(JSON.parse(calledWith.slice(2))).toStrictEqual({data: ["one", "two", "three"], name: "a"}) +// }) +// }) +// +describe('Off', () => { + beforeEach(() => { + On('a', () => {}) + On('a', () => {}) + On('a', () => {}) + On('b', () => {}) + On('c', () => {}) + }) + + it('should cancel all event listeners for a single type', () => { + Off('a') + expect(eventListeners.get('a')).toBeUndefined() + expect(eventListeners.get('b')).not.toBeUndefined() + expect(eventListeners.get('c')).not.toBeUndefined() + }) + + it('should cancel all event listeners for multiple types', () => { + Off('a', 'b') + expect(eventListeners.get('a')).toBeUndefined() + expect(eventListeners.get('b')).toBeUndefined() + expect(eventListeners.get('c')).not.toBeUndefined() + }) +}) + +describe('OffAll', () => { + it('should cancel all event listeners', () => { + On('a', () => {}) + On('a', () => {}) + On('a', () => {}) + On('b', () => {}) + On('c', () => {}) + OffAll() + expect(eventListeners.size).toBe(0) + }) +}) diff --git a/v3/internal/runtime/desktop/log.js b/v3/internal/runtime/desktop/log.js new file mode 100644 index 000000000..e1ef8d6ad --- /dev/null +++ b/v3/internal/runtime/desktop/log.js @@ -0,0 +1,23 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +import {newRuntimeCaller} from "./runtime"; + +let call = newRuntimeCaller("log"); + +/** + * Logs a message. + * @param {message} Message to log + */ +export function Log(message) { + return call("Log", message); +} diff --git a/v3/internal/runtime/desktop/main.js b/v3/internal/runtime/desktop/main.js new file mode 100644 index 000000000..26b4e9de9 --- /dev/null +++ b/v3/internal/runtime/desktop/main.js @@ -0,0 +1,83 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ +/* jshint esversion: 9 */ + + +import * as Clipboard from './clipboard'; +import * as Application from './application'; +import * as Log from './log'; +import * as Screens from './screens'; +import {Plugin, Call, callErrorCallback, callCallback} from "./calls"; +import {newWindow} from "./window"; +import {dispatchWailsEvent, Emit, Off, OffAll, On, Once, OnMultiple} from "./events"; +import {dialogCallback, dialogErrorCallback, Error, Info, OpenFile, Question, SaveFile, Warning,} from "./dialogs"; +import {enableContextMenus} from "./contextmenu"; +import {reloadWML} from "./wml"; + +window.wails = { + ...newRuntime(null), +}; + +// Internal wails endpoints +window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback, +}; + +export function newRuntime(windowName) { + return { + Clipboard: { + ...Clipboard + }, + Application: { + ...Application, + GetWindowByName(windowName) { + return newRuntime(windowName); + } + }, + Log, + Screens, + Call, + Plugin, + WML: { + Reload: reloadWML, + }, + Dialog: { + Info, + Warning, + Error, + Question, + OpenFile, + SaveFile, + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll, + }, + Window: newWindow(windowName), + }; +} + +if (DEBUG) { + console.log("Wails v3.0.0 Debug Mode Enabled"); +} + +enableContextMenus(true); + +document.addEventListener("DOMContentLoaded", function(event) { + reloadWML(); +}); \ No newline at end of file diff --git a/v3/internal/runtime/desktop/runtime.js b/v3/internal/runtime/desktop/runtime.js new file mode 100644 index 000000000..8f2748325 --- /dev/null +++ b/v3/internal/runtime/desktop/runtime.js @@ -0,0 +1,49 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +const runtimeURL = window.location.origin + "/wails/runtime"; + +function runtimeCall(method, windowName, args) { + let url = new URL(runtimeURL); + url.searchParams.append("method", method); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let fetchOptions = { + headers: {}, + }; + if (windowName) { + fetchOptions.headers["x-wails-window-name"] = windowName; + } + return new Promise((resolve, reject) => { + fetch(url, fetchOptions) + .then(response => { + if (response.ok) { + // check content type + if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) { + return response.json(); + } else { + return response.text(); + } + } + reject(Error(response.statusText)); + }) + .then(data => resolve(data)) + .catch(error => reject(error)); + }); +} + +export function newRuntimeCaller(object, windowName) { + return function (method, args=null) { + return runtimeCall(object + "." + method, windowName, args); + }; +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/screens.js b/v3/internal/runtime/desktop/screens.js new file mode 100644 index 000000000..2933290ee --- /dev/null +++ b/v3/internal/runtime/desktop/screens.js @@ -0,0 +1,44 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("./api/types").Screen} Screen + */ + +import {newRuntimeCaller} from "./runtime"; + +let call = newRuntimeCaller("screens"); + +/** + * Gets all screens. + * @returns {Promise} + */ +export function GetAll() { + return call("GetAll"); +} + +/** + * Gets the primary screen. + * @returns {Promise} + */ +export function GetPrimary() { + return call("GetPrimary"); +} + +/** + * Gets the current active screen. + * @returns {Promise} + * @constructor + */ +export function GetCurrent() { + return call("GetCurrent"); +} \ No newline at end of file diff --git a/v3/internal/runtime/desktop/window.js b/v3/internal/runtime/desktop/window.js new file mode 100644 index 000000000..13df2a98f --- /dev/null +++ b/v3/internal/runtime/desktop/window.js @@ -0,0 +1,157 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +/* jshint esversion: 9 */ + +/** + * @typedef {import("../api/types").Size} Size + * @typedef {import("../api/types").Position} Position + * @typedef {import("../api/types").Screen} Screen + */ + +import {newRuntimeCaller} from "./runtime"; + +export function newWindow(windowName) { + let call = newRuntimeCaller("window", windowName); + return { + // Reload: () => call('WR'), + // ReloadApp: () => call('WR'), + // SetSystemDefaultTheme: () => call('WASDT'), + // SetLightTheme: () => call('WALT'), + // SetDarkTheme: () => call('WADT'), + // IsFullscreen: () => call('WIF'), + // IsMaximized: () => call('WIM'), + // IsMinimized: () => call('WIMN'), + // IsWindowed: () => call('WIF'), + + + /** + * Centers the window. + */ + Center: () => void call('Center'), + + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call('SetTitle', {title}), + + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call('Fullscreen'), + + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call('UnFullscreen'), + + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call('SetSize', {width,height}), + + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { return call('Size'); }, + + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call('SetMaxSize', {width,height}), + + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call('SetMinSize', {width,height}), + + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call('SetAlwaysOnTop', {alwaysOnTop:onTop}), + + /** + * Set the window position. + * @param {number} x + * @param {number} y + */ + SetPosition: (x, y) => call('SetPosition', {x,y}), + + /** + * Get the window position. + * @returns {Promise} The window position + */ + Position: () => { return call('Position'); }, + + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { return call('Screen'); }, + + /** + * Hide the window + */ + Hide: () => void call('Hide'), + + /** + * Maximise the window + */ + Maximise: () => void call('Maximise'), + + /** + * Show the window + */ + Show: () => void call('Show'), + + /** + * Close the window + */ + Close: () => void call('Close'), + + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call('ToggleMaximise'), + + /** + * Unmaximise the window + */ + UnMaximise: () => void call('UnMaximise'), + + /** + * Minimise the window + */ + Minimise: () => void call('Minimise'), + + /** + * Unminimise the window + */ + UnMinimise: () => void call('UnMinimise'), + + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call('SetBackgroundColour', {r, g, b, a}), + }; +} diff --git a/v3/internal/runtime/desktop/wml.js b/v3/internal/runtime/desktop/wml.js new file mode 100644 index 000000000..865c3d628 --- /dev/null +++ b/v3/internal/runtime/desktop/wml.js @@ -0,0 +1,74 @@ + +import {Emit, WailsEvent} from "./events"; +import {Question} from "./dialogs"; + +function sendEvent(eventName, data=null) { + let event = new WailsEvent(eventName, data); + Emit(event); +} + +function addWMLEventListeners() { + const elements = document.querySelectorAll('[data-wml-event]'); + elements.forEach(function (element) { + const eventType = element.getAttribute('data-wml-event'); + const confirm = element.getAttribute('data-wml-confirm'); + const trigger = element.getAttribute('data-wml-trigger') || "click"; + + let callback = function () { + if (confirm) { + Question({Title: "Confirm", Message:confirm, Buttons:[{Label:"Yes"},{Label:"No", IsDefault:true}]}).then(function (result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + // Remove existing listeners + + element.removeEventListener(trigger, callback); + + // Add new listener + element.addEventListener(trigger, callback); + }); +} + +function callWindowMethod(method) { + if (wails.Window[method] === undefined) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); +} + +function addWMLWindowListeners() { + const elements = document.querySelectorAll('[data-wml-window]'); + elements.forEach(function (element) { + const windowMethod = element.getAttribute('data-wml-window'); + const confirm = element.getAttribute('data-wml-confirm'); + const trigger = element.getAttribute('data-wml-trigger') || "click"; + + let callback = function () { + if (confirm) { + Question({Title: "Confirm", Message:confirm, Buttons:[{Label:"Yes"},{Label:"No", IsDefault:true}]}).then(function (result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + + // Remove existing listeners + element.removeEventListener(trigger, callback); + + // Add new listener + element.addEventListener(trigger, callback); + }); +} + +export function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); +} diff --git a/v3/internal/runtime/package-lock.json b/v3/internal/runtime/package-lock.json new file mode 100644 index 000000000..a029e31be --- /dev/null +++ b/v3/internal/runtime/package-lock.json @@ -0,0 +1,8685 @@ +{ + "name": "runtime", + "version": "3.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "runtime", + "version": "3.0.0", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.17.5", + "happy-dom": "^8.1.5", + "nanoid": "^4.0.0", + "npm-check-updates": "^16.6.3", + "svelte": "^3.55.1", + "vitest": "^0.28.3" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.5.tgz", + "integrity": "sha512-crmPUzgCmF+qZXfl1YkiFoUta2XAfixR1tEnr/gXIixE+WL8Z0BGqfydP5oox0EUOgQMMRgtATtakyAcClQVqQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.5.tgz", + "integrity": "sha512-KHWkDqYAMmKZjY4RAN1PR96q6UOtfkWlTS8uEwWxdLtkRt/0F/csUhXIrVfaSIFxnscIBMPynGfhsMwQDRIBQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.5.tgz", + "integrity": "sha512-8fI/AnIdmWz/+1iza2WrCw8kwXK9wZp/yZY/iS8ioC+U37yJCeppi9EHY05ewJKN64ASoBIseufZROtcFnX5GA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.5.tgz", + "integrity": "sha512-EAvaoyIySV6Iif3NQCglUNpnMfHSUgC5ugt2efl3+QDntucJe5spn0udNZjTgNi6tKVqSceOw9tQ32liNZc1Xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.5.tgz", + "integrity": "sha512-ha7QCJh1fuSwwCgoegfdaljowwWozwTDjBgjD3++WAy/qwee5uUi1gvOg2WENJC6EUyHBOkcd3YmLDYSZ2TPPA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.5.tgz", + "integrity": "sha512-VbdXJkn2aI2pQ/wxNEjEcnEDwPpxt3CWWMFYmO7CcdFBoOsABRy2W8F3kjbF9F/pecEUDcI3b5i2w+By4VQFPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.5.tgz", + "integrity": "sha512-olgGYND1/XnnWxwhjtY3/ryjOG/M4WfcA6XH8dBTH1cxMeBemMODXSFhkw71Kf4TeZFFTN25YOomaNh0vq2iXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.5.tgz", + "integrity": "sha512-YBdCyQwA3OQupi6W2/WO4FnI+NWFWe79cZEtlbqSESOHEg7a73htBIRiE6uHPQe7Yp5E4aALv+JxkRLGEUL7tw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.5.tgz", + "integrity": "sha512-8a0bqSwu3OlLCfu2FBbDNgQyBYdPJh1B9PvNX7jMaKGC9/KopgHs37t+pQqeMLzcyRqG6z55IGNQAMSlCpBuqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.5.tgz", + "integrity": "sha512-uCwm1r/+NdP7vndctgq3PoZrnmhmnecWAr114GWMRwg2QMFFX+kIWnp7IO220/JLgnXK/jP7VKAFBGmeOYBQYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.5.tgz", + "integrity": "sha512-3YxhSBl5Sb6TtBjJu+HP93poBruFzgXmf3PVfIe4xOXMj1XpxboYZyw3W8BhoX/uwxzZz4K1I99jTE/5cgDT1g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.5.tgz", + "integrity": "sha512-Hy5Z0YVWyYHdtQ5mfmfp8LdhVwGbwVuq8mHzLqrG16BaMgEmit2xKO+iDakHs+OetEx0EN/2mUzDdfdktI+Nmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.5.tgz", + "integrity": "sha512-5dbQvBLbU/Y3Q4ABc9gi23hww1mQcM7KZ9KBqabB7qhJswYMf8WrDDOSw3gdf3p+ffmijMd28mfVMvFucuECyg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.5.tgz", + "integrity": "sha512-fp/KUB/ZPzEWGTEUgz9wIAKCqu7CjH1GqXUO2WJdik1UNBQ7Xzw7myIajpxztE4Csb9504ERiFMxZg5KZ6HlZQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.5.tgz", + "integrity": "sha512-kRV3yw19YDqHTp8SfHXfObUFXlaiiw4o2lvT1XjsPZ++22GqZwSsYWJLjMi1Sl7j9qDlDUduWDze/nQx0d6Lzw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.5.tgz", + "integrity": "sha512-vnxuhh9e4pbtABNLbT2ANW4uwQ/zvcHRCm1JxaYkzSehugoFd5iXyC4ci1nhXU13mxEwCnrnTIiiSGwa/uAF1g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.5.tgz", + "integrity": "sha512-cigBpdiSx/vPy7doUyImsQQBnBjV5f1M99ZUlaJckDAJjgXWl6y9W17FIfJTy8TxosEF6MXq+fpLsitMGts2nA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.5.tgz", + "integrity": "sha512-VdqRqPVIjjZfkf40LrqOaVuhw9EQiAZ/GNCSM2UplDkaIzYVsSnycxcFfAnHdWI8Gyt6dO15KHikbpxwx+xHbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.5.tgz", + "integrity": "sha512-ItxPaJ3MBLtI4nK+mALLEoUs6amxsx+J1ibnfcYMkqaCqHST1AkF4aENpBehty3czqw64r/XqL+W9WqU6kc2Qw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.5.tgz", + "integrity": "sha512-4u2Q6qsJTYNFdS9zHoAi80spzf78C16m2wla4eJPh4kSbRv+BpXIfl6TmBSWupD8e47B1NrTfrOlEuco7mYQtg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.5.tgz", + "integrity": "sha512-KYlm+Xu9TXsfTWAcocLuISRtqxKp/Y9ZBVg6CEEj0O5J9mn7YvBKzAszo2j1ndyzUPk+op+Tie2PJeN+BnXGqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.5.tgz", + "integrity": "sha512-XgA9qWRqby7xdYXuF6KALsn37QGBMHsdhmnpjfZtYxKxbTOwfnDM6MYi2WuUku5poNaX2n9XGVr20zgT/2QwCw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.3.tgz", + "integrity": "sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.1.tgz", + "integrity": "sha512-GIykAFdOVK31Q1/zAtT5MbxqQL2vyl9mvFJv+OGu01zxbhL3p0xc8gJjdNGX1mWmUT43aEKVO2L6V/2j4TOsAA==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.0.tgz", + "integrity": "sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/npm-conf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz", + "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==", + "dev": true, + "dependencies": { + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@vitest/expect": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", + "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "chai": "^4.3.7" + } + }, + "node_modules/@vitest/runner": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", + "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.28.3", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/spy": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", + "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", + "dev": true, + "dependencies": { + "tinyspy": "^1.0.2" + } + }, + "node_modules/@vitest/utils": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", + "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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 + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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 + }, + "node_modules/boxen": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.1.tgz", + "integrity": "sha512-8k2eH6SRAK00NDl1iX5q17RJ8rfl53TajdYxE3ssMLehbg487dEVgsad4pIsZb/QqBgYWIl6JOauMTLGX2Kpkw==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/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 + }, + "node_modules/boxen/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, + "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/boxen/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", + "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^8.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz", + "integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/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 + }, + "node_modules/cli-truncate/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, + "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/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "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 + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/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 + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "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/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "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 + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.5.tgz", + "integrity": "sha512-Bu6WLCc9NMsNoMJUjGl3yBzTjVLXdysMltxQWiLAypP+/vQrf+3L1Xe8fCXzxaECus2cEJ9M7pk4yKatEwQMqQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.5", + "@esbuild/android-arm64": "0.17.5", + "@esbuild/android-x64": "0.17.5", + "@esbuild/darwin-arm64": "0.17.5", + "@esbuild/darwin-x64": "0.17.5", + "@esbuild/freebsd-arm64": "0.17.5", + "@esbuild/freebsd-x64": "0.17.5", + "@esbuild/linux-arm": "0.17.5", + "@esbuild/linux-arm64": "0.17.5", + "@esbuild/linux-ia32": "0.17.5", + "@esbuild/linux-loong64": "0.17.5", + "@esbuild/linux-mips64el": "0.17.5", + "@esbuild/linux-ppc64": "0.17.5", + "@esbuild/linux-riscv64": "0.17.5", + "@esbuild/linux-s390x": "0.17.5", + "@esbuild/linux-x64": "0.17.5", + "@esbuild/netbsd-x64": "0.17.5", + "@esbuild/openbsd-x64": "0.17.5", + "@esbuild/sunos-x64": "0.17.5", + "@esbuild/win32-arm64": "0.17.5", + "@esbuild/win32-ia32": "0.17.5", + "@esbuild/win32-x64": "0.17.5" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "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-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/fp-and-or": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.3.tgz", + "integrity": "sha512-wJaE62fLaB3jCYvY2ZHjZvmKK2iiLiiehX38rz5QZxtdN8fVPJDeZUiVvJrHStdTc+23LHlyZuSEKgFc0pxi2g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.0.tgz", + "integrity": "sha512-EUojgQaSPy6sxcqcZgQv6TVF6jiKvurji3AxhAivs/Ep4O1UpS8TusaxpybfFHZ2skRhLqzk6WR8nqNYIMMDeA==", + "dev": true, + "dependencies": { + "minipass": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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 + }, + "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/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "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/got": { + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/got/-/got-12.5.3.tgz", + "integrity": "sha512-8wKnb9MGU8IPGRIo+/ukTy9XLJBwDiCpIf5TVzQ9Cpol50eMTpBq2GAuDsuDIz7hTYmZgMgC1e9ydr6kSDWs3w==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/happy-dom": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.1.5.tgz", + "integrity": "sha512-/UXAJ2fHTs4H3vy7TS7c9PKFvPyaNialk2Er9NdXfpBKNaCITMOH03rkjHXp5jnJnSmRBa+av8E08PUAaIB1jQ==", + "dev": true, + "dependencies": { + "css.escape": "^1.5.1", + "he": "^1.2.0", + "node-fetch": "^2.x.x", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.0.tgz", + "integrity": "sha512-bTf9UWe/UP1yxG3QUrj/KOvEhTAUWPcv+WvbFZ28LcqznXabp7Xu6o9y1JEC18+oqODuS7VhTpekV5XvFwsxJg==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "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==", + "dev": true, + "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 + }, + "node_modules/ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "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, + "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, + "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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-npm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", + "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "dev": true, + "dependencies": { + "jju": "^1.1.0" + } + }, + "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, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonlines": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", + "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", + "dev": true + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/local-pkg": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz", + "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", + "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", + "dev": true, + "dependencies": { + "acorn": "^8.8.1", + "pathe": "^1.0.0", + "pkg-types": "^1.0.1", + "ufo": "^1.0.1" + } + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz", + "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/node-gyp/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-check-updates": { + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.6.3.tgz", + "integrity": "sha512-EKhsCbBcVrPlYKzaYQtRhGv9fxpexwROcvl5HebCUNpiCSlOWrzaJvrMlwi9i9GCyJCnH+YAeBPYdqnArA390A==", + "dev": true, + "dependencies": { + "chalk": "^5.2.0", + "cli-table": "^0.3.11", + "commander": "^9.4.1", + "fast-memoize": "^2.5.2", + "find-up": "5.0.0", + "fp-and-or": "^0.1.3", + "get-stdin": "^8.0.0", + "globby": "^11.0.4", + "hosted-git-info": "^5.1.0", + "ini": "^3.0.1", + "json-parse-helpfulerror": "^1.0.3", + "jsonlines": "^0.1.1", + "lodash": "^4.17.21", + "minimatch": "^5.1.2", + "p-map": "^4.0.0", + "pacote": "15.0.8", + "parse-github-url": "^1.0.2", + "progress": "^2.0.3", + "prompts-ncu": "^2.5.1", + "rc-config-loader": "^4.1.1", + "remote-git-tags": "^3.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.8", + "semver-utils": "^1.1.4", + "source-map-support": "^0.5.21", + "spawn-please": "^2.0.1", + "untildify": "^4.0.0", + "update-notifier": "^6.0.2", + "yaml": "^2.2.0" + }, + "bin": { + "ncu": "build/src/bin/cli.js", + "npm-check-updates": "build/src/bin/cli.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/npm-install-checks": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.0.0.tgz", + "integrity": "sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz", + "integrity": "sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz", + "integrity": "sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^4.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.2.tgz", + "integrity": "sha512-5n/Pq41w/uZghpdlXAY5kIM85RgJThtTH/NYBRAZ9VUOBWV90USaQjwGrw76fZP3Lj5hl/VZjpVvOaRBMoL/2w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", + "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "dev": true, + "dependencies": { + "minipass": "^4.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "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, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.0.tgz", + "integrity": "sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==", + "dev": true, + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pacote": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz", + "integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==", + "dev": true, + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^4.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true, + "bin": { + "parse-github-url": "cli.js" + }, + "engines": { + "node": ">=0.10.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, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", + "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.0.0", + "pathe": "^1.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prompts-ncu": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-2.5.1.tgz", + "integrity": "sha512-Hdd7GgV7b76Yh9FP9HL1D9xqtJCJdVPpiM2vDtuoc8W1KfweJe15gutFYmxkq83ViFaagFM8K0UcPCQ/tZq8bA==", + "dev": true, + "dependencies": { + "kleur": "^4.0.1", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/pupa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "dev": true, + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "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" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "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/rc-config-loader": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.2.tgz", + "integrity": "sha512-qKTnVWFl9OQYKATPzdfaZIbTxcHziQl92zYSxYC6umhOqyAsoj8H8Gq/+aFjAso68sBdjTz3A7omqeAkkF1MWg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "js-yaml": "^4.1.0", + "json5": "^2.2.2", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc/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 + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/read-package-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.0.tgz", + "integrity": "sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", + "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^1.0.4" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/remote-git-tags": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz", + "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "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, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/rimraf/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "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" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-utils": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", + "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==", + "dev": true + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "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, + "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, + "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 + }, + "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 + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "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/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawn-please": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.1.tgz", + "integrity": "sha512-W+cFbZR2q2mMTfjz5ZGvhBAiX+e/zczFCNlbS9mxiSdYswBXwUuBUT+a0urH+xZZa8f/bs0mXHyZsZHR9hKogA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", + "integrity": "sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==", + "dev": true, + "dependencies": { + "minipass": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.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 + }, + "node_modules/std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", + "dev": true + }, + "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, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", + "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", + "dev": true, + "dependencies": { + "acorn": "^8.8.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "3.55.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", + "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", + "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", + "dev": true, + "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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/ufo": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", + "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", + "dev": true + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "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 + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vite": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.9", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", + "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/vitest": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", + "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.3", + "@vitest/runner": "0.28.3", + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.3", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "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, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "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, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/which": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", + "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/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 + }, + "node_modules/widest-line/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, + "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/widest-line/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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, + "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/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/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 + }, + "node_modules/wrap-ansi/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, + "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/wrap-ansi/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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 + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.5.tgz", + "integrity": "sha512-crmPUzgCmF+qZXfl1YkiFoUta2XAfixR1tEnr/gXIixE+WL8Z0BGqfydP5oox0EUOgQMMRgtATtakyAcClQVqQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.5.tgz", + "integrity": "sha512-KHWkDqYAMmKZjY4RAN1PR96q6UOtfkWlTS8uEwWxdLtkRt/0F/csUhXIrVfaSIFxnscIBMPynGfhsMwQDRIBQw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.5.tgz", + "integrity": "sha512-8fI/AnIdmWz/+1iza2WrCw8kwXK9wZp/yZY/iS8ioC+U37yJCeppi9EHY05ewJKN64ASoBIseufZROtcFnX5GA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.5.tgz", + "integrity": "sha512-EAvaoyIySV6Iif3NQCglUNpnMfHSUgC5ugt2efl3+QDntucJe5spn0udNZjTgNi6tKVqSceOw9tQ32liNZc1Xw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.5.tgz", + "integrity": "sha512-ha7QCJh1fuSwwCgoegfdaljowwWozwTDjBgjD3++WAy/qwee5uUi1gvOg2WENJC6EUyHBOkcd3YmLDYSZ2TPPA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.5.tgz", + "integrity": "sha512-VbdXJkn2aI2pQ/wxNEjEcnEDwPpxt3CWWMFYmO7CcdFBoOsABRy2W8F3kjbF9F/pecEUDcI3b5i2w+By4VQFPg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.5.tgz", + "integrity": "sha512-olgGYND1/XnnWxwhjtY3/ryjOG/M4WfcA6XH8dBTH1cxMeBemMODXSFhkw71Kf4TeZFFTN25YOomaNh0vq2iXg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.5.tgz", + "integrity": "sha512-YBdCyQwA3OQupi6W2/WO4FnI+NWFWe79cZEtlbqSESOHEg7a73htBIRiE6uHPQe7Yp5E4aALv+JxkRLGEUL7tw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.5.tgz", + "integrity": "sha512-8a0bqSwu3OlLCfu2FBbDNgQyBYdPJh1B9PvNX7jMaKGC9/KopgHs37t+pQqeMLzcyRqG6z55IGNQAMSlCpBuqg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.5.tgz", + "integrity": "sha512-uCwm1r/+NdP7vndctgq3PoZrnmhmnecWAr114GWMRwg2QMFFX+kIWnp7IO220/JLgnXK/jP7VKAFBGmeOYBQYQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.5.tgz", + "integrity": "sha512-3YxhSBl5Sb6TtBjJu+HP93poBruFzgXmf3PVfIe4xOXMj1XpxboYZyw3W8BhoX/uwxzZz4K1I99jTE/5cgDT1g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.5.tgz", + "integrity": "sha512-Hy5Z0YVWyYHdtQ5mfmfp8LdhVwGbwVuq8mHzLqrG16BaMgEmit2xKO+iDakHs+OetEx0EN/2mUzDdfdktI+Nmg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.5.tgz", + "integrity": "sha512-5dbQvBLbU/Y3Q4ABc9gi23hww1mQcM7KZ9KBqabB7qhJswYMf8WrDDOSw3gdf3p+ffmijMd28mfVMvFucuECyg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.5.tgz", + "integrity": "sha512-fp/KUB/ZPzEWGTEUgz9wIAKCqu7CjH1GqXUO2WJdik1UNBQ7Xzw7myIajpxztE4Csb9504ERiFMxZg5KZ6HlZQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.5.tgz", + "integrity": "sha512-kRV3yw19YDqHTp8SfHXfObUFXlaiiw4o2lvT1XjsPZ++22GqZwSsYWJLjMi1Sl7j9qDlDUduWDze/nQx0d6Lzw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.5.tgz", + "integrity": "sha512-vnxuhh9e4pbtABNLbT2ANW4uwQ/zvcHRCm1JxaYkzSehugoFd5iXyC4ci1nhXU13mxEwCnrnTIiiSGwa/uAF1g==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.5.tgz", + "integrity": "sha512-cigBpdiSx/vPy7doUyImsQQBnBjV5f1M99ZUlaJckDAJjgXWl6y9W17FIfJTy8TxosEF6MXq+fpLsitMGts2nA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.5.tgz", + "integrity": "sha512-VdqRqPVIjjZfkf40LrqOaVuhw9EQiAZ/GNCSM2UplDkaIzYVsSnycxcFfAnHdWI8Gyt6dO15KHikbpxwx+xHbw==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.5.tgz", + "integrity": "sha512-ItxPaJ3MBLtI4nK+mALLEoUs6amxsx+J1ibnfcYMkqaCqHST1AkF4aENpBehty3czqw64r/XqL+W9WqU6kc2Qw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.5.tgz", + "integrity": "sha512-4u2Q6qsJTYNFdS9zHoAi80spzf78C16m2wla4eJPh4kSbRv+BpXIfl6TmBSWupD8e47B1NrTfrOlEuco7mYQtg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.5.tgz", + "integrity": "sha512-KYlm+Xu9TXsfTWAcocLuISRtqxKp/Y9ZBVg6CEEj0O5J9mn7YvBKzAszo2j1ndyzUPk+op+Tie2PJeN+BnXGqQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.5.tgz", + "integrity": "sha512-XgA9qWRqby7xdYXuF6KALsn37QGBMHsdhmnpjfZtYxKxbTOwfnDM6MYi2WuUku5poNaX2n9XGVr20zgT/2QwCw==", + "dev": true, + "optional": true + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.3.tgz", + "integrity": "sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + } + }, + "@npmcli/installed-package-contents": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.1.tgz", + "integrity": "sha512-GIykAFdOVK31Q1/zAtT5MbxqQL2vyl9mvFJv+OGu01zxbhL3p0xc8gJjdNGX1mWmUT43aEKVO2L6V/2j4TOsAA==", + "dev": true, + "requires": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "requires": { + "which": "^3.0.0" + } + }, + "@npmcli/run-script": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.0.tgz", + "integrity": "sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + } + }, + "@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "requires": { + "graceful-fs": "4.2.10" + } + }, + "@pnpm/npm-conf": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-1.0.5.tgz", + "integrity": "sha512-hD8ml183638O3R6/Txrh0L8VzGOrFXgRtRDG4qQC4tONdZ5Z1M+tlUUDUvrjYdmK6G+JTBTeaCLMna11cXzi8A==", + "dev": true, + "requires": { + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + } + }, + "@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "@vitest/expect": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz", + "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==", + "dev": true, + "requires": { + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "chai": "^4.3.7" + } + }, + "@vitest/runner": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz", + "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==", + "dev": true, + "requires": { + "@vitest/utils": "0.28.3", + "p-limit": "^4.0.0", + "pathe": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "requires": { + "yocto-queue": "^1.0.0" + } + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true + } + } + }, + "@vitest/spy": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz", + "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==", + "dev": true, + "requires": { + "tinyspy": "^1.0.2" + } + }, + "@vitest/utils": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz", + "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==", + "dev": true, + "requires": { + "cli-truncate": "^3.1.0", + "diff": "^5.1.0", + "loupe": "^2.3.6", + "picocolors": "^1.0.0", + "pretty-format": "^27.5.1" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "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 + }, + "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 + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "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 + }, + "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 + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "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 + }, + "boxen": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.1.tgz", + "integrity": "sha512-8k2eH6SRAK00NDl1iX5q17RJ8rfl53TajdYxE3ssMLehbg487dEVgsad4pIsZb/QqBgYWIl6JOauMTLGX2Kpkw==", + "dev": true, + "requires": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "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 + }, + "cacache": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", + "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "dev": true, + "requires": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^8.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + } + }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.7.tgz", + "integrity": "sha512-I4SA6mKgDxcxVbSt/UmIkb9Ny8qSkg6ReBHtAAXnZHk7KOSx5g3DTiAOaYzcHCs6oOdHn+bip9T48E6tMvK9hw==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, + "camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "ci-info": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", + "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "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==", + "dev": true + }, + "cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + } + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, + "configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "requires": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "requires": { + "type-fest": "^1.0.1" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true + } + } + }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true + } + } + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "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 + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "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 + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "esbuild": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.5.tgz", + "integrity": "sha512-Bu6WLCc9NMsNoMJUjGl3yBzTjVLXdysMltxQWiLAypP+/vQrf+3L1Xe8fCXzxaECus2cEJ9M7pk4yKatEwQMqQ==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.5", + "@esbuild/android-arm64": "0.17.5", + "@esbuild/android-x64": "0.17.5", + "@esbuild/darwin-arm64": "0.17.5", + "@esbuild/darwin-x64": "0.17.5", + "@esbuild/freebsd-arm64": "0.17.5", + "@esbuild/freebsd-x64": "0.17.5", + "@esbuild/linux-arm": "0.17.5", + "@esbuild/linux-arm64": "0.17.5", + "@esbuild/linux-ia32": "0.17.5", + "@esbuild/linux-loong64": "0.17.5", + "@esbuild/linux-mips64el": "0.17.5", + "@esbuild/linux-ppc64": "0.17.5", + "@esbuild/linux-riscv64": "0.17.5", + "@esbuild/linux-s390x": "0.17.5", + "@esbuild/linux-x64": "0.17.5", + "@esbuild/netbsd-x64": "0.17.5", + "@esbuild/openbsd-x64": "0.17.5", + "@esbuild/sunos-x64": "0.17.5", + "@esbuild/win32-arm64": "0.17.5", + "@esbuild/win32-ia32": "0.17.5", + "@esbuild/win32-x64": "0.17.5" + } + }, + "esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "dev": true, + "optional": true + }, + "escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@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" + } + }, + "fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true + }, + "fp-and-or": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.3.tgz", + "integrity": "sha512-wJaE62fLaB3jCYvY2ZHjZvmKK2iiLiiehX38rz5QZxtdN8fVPJDeZUiVvJrHStdTc+23LHlyZuSEKgFc0pxi2g==", + "dev": true + }, + "fs-minipass": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.0.tgz", + "integrity": "sha512-EUojgQaSPy6sxcqcZgQv6TVF6jiKvurji3AxhAivs/Ep4O1UpS8TusaxpybfFHZ2skRhLqzk6WR8nqNYIMMDeA==", + "dev": true, + "requires": { + "minipass": "^4.0.0" + } + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "requires": { + "ini": "2.0.0" + }, + "dependencies": { + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + } + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "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" + } + }, + "got": { + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/got/-/got-12.5.3.tgz", + "integrity": "sha512-8wKnb9MGU8IPGRIo+/ukTy9XLJBwDiCpIf5TVzQ9Cpol50eMTpBq2GAuDsuDIz7hTYmZgMgC1e9ydr6kSDWs3w==", + "dev": true, + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.1", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "happy-dom": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.1.5.tgz", + "integrity": "sha512-/UXAJ2fHTs4H3vy7TS7c9PKFvPyaNialk2Er9NdXfpBKNaCITMOH03rkjHXp5jnJnSmRBa+av8E08PUAaIB1jQ==", + "dev": true, + "requires": { + "css.escape": "^1.5.1", + "he": "^1.2.0", + "node-fetch": "^2.x.x", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "dev": true + }, + "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 + }, + "hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + }, + "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==", + "dev": true + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dev": true, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "ignore-walk": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.0.tgz", + "integrity": "sha512-bTf9UWe/UP1yxG3QUrj/KOvEhTAUWPcv+WvbFZ28LcqznXabp7Xu6o9y1JEC18+oqODuS7VhTpekV5XvFwsxJg==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + } + }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "requires": { + "ci-info": "^3.2.0" + } + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "is-npm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", + "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "dev": true + }, + "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 + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true + }, + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "dev": true, + "requires": { + "jju": "^1.1.0" + } + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsonlines": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", + "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true + }, + "keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "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 + }, + "latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "requires": { + "package-json": "^8.1.0" + } + }, + "local-pkg": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz", + "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==", + "dev": true + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true + }, + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "dependencies": { + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mlly": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.1.0.tgz", + "integrity": "sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1", + "pathe": "^1.0.0", + "pkg-types": "^1.0.1", + "ufo": "^1.0.1" + } + }, + "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 + }, + "nanoid": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz", + "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "dependencies": { + "hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + } + } + }, + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true + }, + "npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^3.0.0" + } + }, + "npm-check-updates": { + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.6.3.tgz", + "integrity": "sha512-EKhsCbBcVrPlYKzaYQtRhGv9fxpexwROcvl5HebCUNpiCSlOWrzaJvrMlwi9i9GCyJCnH+YAeBPYdqnArA390A==", + "dev": true, + "requires": { + "chalk": "^5.2.0", + "cli-table": "^0.3.11", + "commander": "^9.4.1", + "fast-memoize": "^2.5.2", + "find-up": "5.0.0", + "fp-and-or": "^0.1.3", + "get-stdin": "^8.0.0", + "globby": "^11.0.4", + "hosted-git-info": "^5.1.0", + "ini": "^3.0.1", + "json-parse-helpfulerror": "^1.0.3", + "jsonlines": "^0.1.1", + "lodash": "^4.17.21", + "minimatch": "^5.1.2", + "p-map": "^4.0.0", + "pacote": "15.0.8", + "parse-github-url": "^1.0.2", + "progress": "^2.0.3", + "prompts-ncu": "^2.5.1", + "rc-config-loader": "^4.1.1", + "remote-git-tags": "^3.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.8", + "semver-utils": "^1.1.4", + "source-map-support": "^0.5.21", + "spawn-please": "^2.0.1", + "untildify": "^4.0.0", + "update-notifier": "^6.0.2", + "yaml": "^2.2.0" + } + }, + "npm-install-checks": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.0.0.tgz", + "integrity": "sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz", + "integrity": "sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==", + "dev": true + }, + "npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + } + } + }, + "npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "requires": { + "ignore-walk": "^6.0.0" + } + }, + "npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "requires": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz", + "integrity": "sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA==", + "dev": true, + "requires": { + "make-fetch-happen": "^11.0.0", + "minipass": "^4.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "dependencies": { + "make-fetch-happen": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.2.tgz", + "integrity": "sha512-5n/Pq41w/uZghpdlXAY5kIM85RgJThtTH/NYBRAZ9VUOBWV90USaQjwGrw76fZP3Lj5hl/VZjpVvOaRBMoL/2w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^4.0.0", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + } + }, + "minipass-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", + "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^4.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + } + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "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, + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true + }, + "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, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "package-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.0.tgz", + "integrity": "sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==", + "dev": true, + "requires": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + } + }, + "pacote": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.0.8.tgz", + "integrity": "sha512-UlcumB/XS6xyyIMwg/WwMAyUmga+RivB5KgkRwA1hZNtrx+0Bt41KxHCvg1kr0pZ/ZeD8qjhW4fph6VaYRCbLw==", + "dev": true, + "requires": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^4.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + } + }, + "parse-github-url": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", + "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "pathe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz", + "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz", + "integrity": "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==", + "dev": true, + "requires": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.0.0", + "pathe": "^1.0.0" + } + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + } + } + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "prompts-ncu": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-2.5.1.tgz", + "integrity": "sha512-Hdd7GgV7b76Yh9FP9HL1D9xqtJCJdVPpiM2vDtuoc8W1KfweJe15gutFYmxkq83ViFaagFM8K0UcPCQ/tZq8bA==", + "dev": true, + "requires": { + "kleur": "^4.0.1", + "sisteransi": "^1.0.5" + } + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "pupa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "dev": true, + "requires": { + "escape-goat": "^4.0.0" + } + }, + "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 + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, + "rc-config-loader": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.2.tgz", + "integrity": "sha512-qKTnVWFl9OQYKATPzdfaZIbTxcHziQl92zYSxYC6umhOqyAsoj8H8Gq/+aFjAso68sBdjTz3A7omqeAkkF1MWg==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "js-yaml": "^4.1.0", + "json5": "^2.2.2", + "require-from-string": "^2.0.2" + } + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "read-package-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.0.tgz", + "integrity": "sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "registry-auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.1.tgz", + "integrity": "sha512-UfxVOj8seK1yaIOiieV4FIP01vfBDLsY0H9sQzi9EbbUdJiuuBjJgLa1DpImXMNPnVkBD4eVxTEXcrZA6kfpJA==", + "dev": true, + "requires": { + "@pnpm/npm-conf": "^1.0.4" + } + }, + "registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "requires": { + "rc": "1.2.8" + } + }, + "remote-git-tags": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz", + "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==", + "dev": true + }, + "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==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "requires": { + "lowercase-keys": "^3.0.0" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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 + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "semver-utils": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", + "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "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 + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + } + } + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "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 + }, + "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 + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spawn-please": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.1.tgz", + "integrity": "sha512-W+cFbZR2q2mMTfjz5ZGvhBAiX+e/zczFCNlbS9mxiSdYswBXwUuBUT+a0urH+xZZa8f/bs0mXHyZsZHR9hKogA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "ssri": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", + "integrity": "sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==", + "dev": true, + "requires": { + "minipass": "^4.0.0" + } + }, + "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 + }, + "std-env": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz", + "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==", + "dev": true + }, + "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, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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 + }, + "strip-literal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz", + "integrity": "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==", + "dev": true, + "requires": { + "acorn": "^8.8.1" + } + }, + "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 + }, + "svelte": { + "version": "3.55.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", + "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", + "dev": true + }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + } + } + }, + "tinybench": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.3.1.tgz", + "integrity": "sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==", + "dev": true + }, + "tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true + }, + "tinyspy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.0.2.tgz", + "integrity": "sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==", + "dev": true + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "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 + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "ufo": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz", + "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", + "dev": true + }, + "unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "requires": { + "unique-slug": "^4.0.0" + } + }, + "unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "requires": { + "crypto-random-string": "^4.0.0" + } + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "requires": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + } + }, + "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 + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "vite": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", + "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", + "dev": true, + "requires": { + "esbuild": "^0.15.9", + "fsevents": "~2.3.2", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "dev": true, + "optional": true + }, + "esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + } + } + }, + "vite-node": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz", + "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.1.0", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "source-map-support": "^0.5.21", + "vite": "^3.0.0 || ^4.0.0" + } + }, + "vitest": { + "version": "0.28.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz", + "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==", + "dev": true, + "requires": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.28.3", + "@vitest/runner": "0.28.3", + "@vitest/spy": "0.28.3", + "@vitest/utils": "0.28.3", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "pathe": "^1.1.0", + "picocolors": "^1.0.0", + "source-map": "^0.6.1", + "std-env": "^3.3.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.1", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0", + "vite-node": "0.28.3", + "why-is-node-running": "^2.2.2" + } + }, + "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 + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "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 + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + } + } + }, + "which": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", + "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "requires": { + "string-width": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "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, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "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 + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true + }, + "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 + } + } +} diff --git a/v3/internal/runtime/package.json b/v3/internal/runtime/package.json new file mode 100644 index 000000000..e923ea45a --- /dev/null +++ b/v3/internal/runtime/package.json @@ -0,0 +1,17 @@ +{ + "name": "runtime", + "version": "3.0.0", + "description": "Wails JS Runtime", + "main": "index.js", + "scripts": {}, + "author": "Lea Anthony ", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.17.5", + "happy-dom": "^8.1.5", + "nanoid": "^4.0.0", + "npm-check-updates": "^16.6.3", + "svelte": "^3.55.1", + "vitest": "^0.28.3" + } +} diff --git a/v3/internal/runtime/runtime_debug_darwin.go b/v3/internal/runtime/runtime_debug_darwin.go new file mode 100644 index 000000000..14656126f --- /dev/null +++ b/v3/internal/runtime/runtime_debug_darwin.go @@ -0,0 +1,8 @@ +//go:build darwin && !production + +package runtime + +import _ "embed" + +//go:embed runtime_debug_desktop_darwin.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_debug_desktop_darwin.js b/v3/internal/runtime/runtime_debug_desktop_darwin.js new file mode 100644 index 000000000..0ca537592 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_desktop_darwin.js @@ -0,0 +1,582 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text + }); + + // desktop/runtime.js + var runtimeURL = window.location.origin + "/wails/runtime"; + function runtimeCall(method, windowName, args) { + let url = new URL(runtimeURL); + url.searchParams.append("method", method); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let fetchOptions = { + headers: {} + }; + if (windowName) { + fetchOptions.headers["x-wails-window-name"] = windowName; + } + return new Promise((resolve, reject) => { + fetch(url, fetchOptions).then((response) => { + if (response.ok) { + if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) { + return response.json(); + } else { + return response.text(); + } + } + reject(Error(response.statusText)); + }).then((data) => resolve(data)).catch((error) => reject(error)); + }); + } + function newRuntimeCaller(object, windowName) { + return function(method, args = null) { + return runtimeCall(object + "." + method, windowName, args); + }; + } + + // desktop/clipboard.js + var call = newRuntimeCaller("clipboard"); + function SetText(text) { + void call("SetText", { text }); + } + function Text() { + return call("Text"); + } + + // desktop/application.js + var application_exports = {}; + __export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show + }); + var call2 = newRuntimeCaller("application"); + function Hide() { + void call2("Hide"); + } + function Show() { + void call2("Show"); + } + function Quit() { + void call2("Quit"); + } + + // desktop/log.js + var log_exports = {}; + __export(log_exports, { + Log: () => Log + }); + var call3 = newRuntimeCaller("log"); + function Log(message) { + return call3("Log", message); + } + + // desktop/screens.js + var screens_exports = {}; + __export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary + }); + var call4 = newRuntimeCaller("screens"); + function GetAll() { + return call4("GetAll"); + } + function GetPrimary() { + return call4("GetPrimary"); + } + function GetCurrent() { + return call4("GetCurrent"); + } + + // node_modules/nanoid/non-secure/index.js + var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + var nanoid = (size = 21) => { + let id = ""; + let i = size; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; + }; + + // desktop/calls.js + var call5 = newRuntimeCaller("call"); + var callResponses = /* @__PURE__ */ new Map(); + function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; + } + function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } + } + function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } + } + function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, { resolve, reject }); + call5(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); + } + function Call(options) { + return callBinding("Call", options); + } + function Plugin(pluginName, methodName, ...args) { + return callBinding("Call", { + packageName: "wails-plugins", + structName: pluginName, + methodName, + args + }); + } + + // desktop/window.js + function newWindow(windowName) { + let call9 = newRuntimeCaller("window", windowName); + return { + // Reload: () => call('WR'), + // ReloadApp: () => call('WR'), + // SetSystemDefaultTheme: () => call('WASDT'), + // SetLightTheme: () => call('WALT'), + // SetDarkTheme: () => call('WADT'), + // IsFullscreen: () => call('WIF'), + // IsMaximized: () => call('WIM'), + // IsMinimized: () => call('WIMN'), + // IsWindowed: () => call('WIF'), + /** + * Centers the window. + */ + Center: () => void call9("Center"), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call9("SetTitle", { title }), + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call9("Fullscreen"), + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call9("UnFullscreen"), + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call9("SetSize", { width, height }), + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return call9("Size"); + }, + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call9("SetMaxSize", { width, height }), + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call9("SetMinSize", { width, height }), + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call9("SetAlwaysOnTop", { alwaysOnTop: onTop }), + /** + * Set the window position. + * @param {number} x + * @param {number} y + */ + SetPosition: (x, y) => call9("SetPosition", { x, y }), + /** + * Get the window position. + * @returns {Promise} The window position + */ + Position: () => { + return call9("Position"); + }, + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return call9("Screen"); + }, + /** + * Hide the window + */ + Hide: () => void call9("Hide"), + /** + * Maximise the window + */ + Maximise: () => void call9("Maximise"), + /** + * Show the window + */ + Show: () => void call9("Show"), + /** + * Close the window + */ + Close: () => void call9("Close"), + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call9("ToggleMaximise"), + /** + * Unmaximise the window + */ + UnMaximise: () => void call9("UnMaximise"), + /** + * Minimise the window + */ + Minimise: () => void call9("Minimise"), + /** + * Unminimise the window + */ + UnMinimise: () => void call9("UnMinimise"), + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call9("SetBackgroundColour", { r, g, b, a }) + }; + } + + // desktop/events.js + var call6 = newRuntimeCaller("events"); + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback(data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var WailsEvent = class { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } + }; + var eventListeners = /* @__PURE__ */ new Map(); + 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 listenerOff(listener) { + const eventName = listener.eventName; + let listeners = eventListeners.get(eventName).filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } + } + function dispatchWailsEvent(event) { + console.log("dispatching event: ", { event }); + let listeners = eventListeners.get(event.name); + if (listeners) { + let toRemove = []; + listeners.forEach((listener) => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + if (toRemove.length > 0) { + listeners = listeners.filter((l) => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } + } + function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach((eventName2) => { + eventListeners.delete(eventName2); + }); + } + function OffAll() { + eventListeners.clear(); + } + function Emit(event) { + void call6("Emit", event); + } + + // desktop/dialogs.js + var call7 = newRuntimeCaller("dialog"); + var dialogResponses = /* @__PURE__ */ new Map(); + function generateID2() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; + } + function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } + } + function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } + } + function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID2(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, { resolve, reject }); + call7(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); + } + function Info(options) { + return dialog("Info", options); + } + function Warning(options) { + return dialog("Warning", options); + } + function Error2(options) { + return dialog("Error", options); + } + function Question(options) { + return dialog("Question", options); + } + function OpenFile(options) { + return dialog("OpenFile", options); + } + function SaveFile(options) { + return dialog("SaveFile", options); + } + + // desktop/contextmenu.js + var call8 = newRuntimeCaller("contextmenu"); + function openContextMenu(id, x, y, data) { + return call8("OpenContextMenu", { id, x, y, data }); + } + function enableContextMenus(enabled) { + if (enabled) { + window.addEventListener("contextmenu", contextMenuHandler); + } else { + window.removeEventListener("contextmenu", contextMenuHandler); + } + } + function contextMenuHandler(event) { + processContextMenu(event.target, event); + } + function processContextMenu(element, event) { + let id = element.getAttribute("data-contextmenu"); + if (id) { + event.preventDefault(); + openContextMenu(id, event.clientX, event.clientY, element.getAttribute("data-contextmenu-data")); + } else { + let parent = element.parentElement; + if (parent) { + processContextMenu(parent, event); + } + } + } + + // desktop/wml.js + function sendEvent(eventName, data = null) { + let event = new WailsEvent(eventName, data); + Emit(event); + } + function addWMLEventListeners() { + const elements = document.querySelectorAll("[data-wml-event]"); + elements.forEach(function(element) { + const eventType = element.getAttribute("data-wml-event"); + const confirm = element.getAttribute("data-wml-confirm"); + const trigger = element.getAttribute("data-wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function callWindowMethod(method) { + if (wails.Window[method] === void 0) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); + } + function addWMLWindowListeners() { + const elements = document.querySelectorAll("[data-wml-window]"); + elements.forEach(function(element) { + const windowMethod = element.getAttribute("data-wml-window"); + const confirm = element.getAttribute("data-wml-confirm"); + const trigger = element.getAttribute("data-wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + } + + // desktop/main.js + window.wails = { + ...newRuntime(null) + }; + window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback + }; + function newRuntime(windowName) { + return { + Clipboard: { + ...clipboard_exports + }, + Application: { + ...application_exports, + GetWindowByName(windowName2) { + return newRuntime(windowName2); + } + }, + Log: log_exports, + Screens: screens_exports, + Call, + Plugin, + WML: { + Reload: reloadWML + }, + Dialog: { + Info, + Warning, + Error: Error2, + Question, + OpenFile, + SaveFile + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll + }, + Window: newWindow(windowName) + }; + } + if (true) { + console.log("Wails v3.0.0 Debug Mode Enabled"); + } + enableContextMenus(true); + document.addEventListener("DOMContentLoaded", function(event) { + reloadWML(); + }); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9jbGlwYm9hcmQuanMiLCAiZGVza3RvcC9ydW50aW1lLmpzIiwgImRlc2t0b3AvYXBwbGljYXRpb24uanMiLCAiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9zY3JlZW5zLmpzIiwgIm5vZGVfbW9kdWxlcy9uYW5vaWQvbm9uLXNlY3VyZS9pbmRleC5qcyIsICJkZXNrdG9wL2NhbGxzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3AvZXZlbnRzLmpzIiwgImRlc2t0b3AvZGlhbG9ncy5qcyIsICJkZXNrdG9wL2NvbnRleHRtZW51LmpzIiwgImRlc2t0b3Avd21sLmpzIiwgImRlc2t0b3AvbWFpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImNsaXBib2FyZFwiKTtcblxuLyoqXG4gKiBTZXQgdGhlIENsaXBib2FyZCB0ZXh0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTZXRUZXh0KHRleHQpIHtcbiAgICB2b2lkIGNhbGwoXCJTZXRUZXh0XCIsIHt0ZXh0fSk7XG59XG5cbi8qKlxuICogR2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFRleHQoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJUZXh0XCIpO1xufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5jb25zdCBydW50aW1lVVJMID0gd2luZG93LmxvY2F0aW9uLm9yaWdpbiArIFwiL3dhaWxzL3J1bnRpbWVcIjtcblxuZnVuY3Rpb24gcnVudGltZUNhbGwobWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKSB7XG4gICAgbGV0IHVybCA9IG5ldyBVUkwocnVudGltZVVSTCk7XG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kKTtcbiAgICBpZiAoYXJncykge1xuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcImFyZ3NcIiwgSlNPTi5zdHJpbmdpZnkoYXJncykpO1xuICAgIH1cbiAgICBsZXQgZmV0Y2hPcHRpb25zID0ge1xuICAgICAgICBoZWFkZXJzOiB7fSxcbiAgICB9O1xuICAgIGlmICh3aW5kb3dOYW1lKSB7XG4gICAgICAgIGZldGNoT3B0aW9ucy5oZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XG4gICAgfVxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGZldGNoKHVybCwgZmV0Y2hPcHRpb25zKVxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgICAgICAvLyBjaGVjayBjb250ZW50IHR5cGVcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAudGhlbihkYXRhID0+IHJlc29sdmUoZGF0YSkpXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XG4gICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdCwgd2luZG93TmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAobWV0aG9kLCBhcmdzPW51bGwpIHtcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsKG9iamVjdCArIFwiLlwiICsgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcbiAgICB9O1xufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiYXBwbGljYXRpb25cIik7XG5cbi8qKlxuICogSGlkZSB0aGUgYXBwbGljYXRpb25cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEhpZGUoKSB7XG4gICAgdm9pZCBjYWxsKFwiSGlkZVwiKTtcbn1cblxuLyoqXG4gKiBTaG93IHRoZSBhcHBsaWNhdGlvblxuICovXG5leHBvcnQgZnVuY3Rpb24gU2hvdygpIHtcbiAgICB2b2lkIGNhbGwoXCJTaG93XCIpO1xufVxuXG5cbi8qKlxuICogUXVpdCB0aGUgYXBwbGljYXRpb25cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKSB7XG4gICAgdm9pZCBjYWxsKFwiUXVpdFwiKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImxvZ1wiKTtcblxuLyoqXG4gKiBMb2dzIGEgbWVzc2FnZS5cbiAqIEBwYXJhbSB7bWVzc2FnZX0gTWVzc2FnZSB0byBsb2dcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZyhtZXNzYWdlKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJMb2dcIiwgbWVzc2FnZSk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi9hcGkvdHlwZXNcIikuU2NyZWVufSBTY3JlZW5cbiAqL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwic2NyZWVuc1wiKTtcblxuLyoqXG4gKiBHZXRzIGFsbCBzY3JlZW5zLlxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuW10+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0QWxsKCkge1xuICAgIHJldHVybiBjYWxsKFwiR2V0QWxsXCIpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIHByaW1hcnkgc2NyZWVuLlxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldFByaW1hcnkoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJHZXRQcmltYXJ5XCIpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbj59XG4gKiBAY29uc3RydWN0b3JcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEN1cnJlbnQoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJHZXRDdXJyZW50XCIpO1xufSIsICJsZXQgdXJsQWxwaGFiZXQgPVxuICAndXNlYW5kb20tMjZUMTk4MzQwUFg3NXB4SkFDS1ZFUllNSU5EQlVTSFdPTEZfR1FaYmZnaGprbHF2d3l6cmljdCdcbmV4cG9ydCBsZXQgY3VzdG9tQWxwaGFiZXQgPSAoYWxwaGFiZXQsIGRlZmF1bHRTaXplID0gMjEpID0+IHtcbiAgcmV0dXJuIChzaXplID0gZGVmYXVsdFNpemUpID0+IHtcbiAgICBsZXQgaWQgPSAnJ1xuICAgIGxldCBpID0gc2l6ZVxuICAgIHdoaWxlIChpLS0pIHtcbiAgICAgIGlkICs9IGFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogYWxwaGFiZXQubGVuZ3RoKSB8IDBdXG4gICAgfVxuICAgIHJldHVybiBpZFxuICB9XG59XG5leHBvcnQgbGV0IG5hbm9pZCA9IChzaXplID0gMjEpID0+IHtcbiAgbGV0IGlkID0gJydcbiAgbGV0IGkgPSBzaXplXG4gIHdoaWxlIChpLS0pIHtcbiAgICBpZCArPSB1cmxBbHBoYWJldFsoTWF0aC5yYW5kb20oKSAqIDY0KSB8IDBdXG4gIH1cbiAgcmV0dXJuIGlkXG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiY2FsbFwiKTtcblxubGV0IGNhbGxSZXNwb25zZXMgPSBuZXcgTWFwKCk7XG5cbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKSB7XG4gICAgbGV0IHJlc3VsdDtcbiAgICBkbyB7XG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xuICAgIH0gd2hpbGUgKGNhbGxSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxsQ2FsbGJhY2soaWQsIGRhdGEsIGlzSlNPTikge1xuICAgIGxldCBwID0gY2FsbFJlc3BvbnNlcy5nZXQoaWQpO1xuICAgIGlmIChwKSB7XG4gICAgICAgIGlmIChpc0pTT04pIHtcbiAgICAgICAgICAgIHAucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHAucmVzb2x2ZShkYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYWxsUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gY2FsbEVycm9yQ2FsbGJhY2soaWQsIG1lc3NhZ2UpIHtcbiAgICBsZXQgcCA9IGNhbGxSZXNwb25zZXMuZ2V0KGlkKTtcbiAgICBpZiAocCkge1xuICAgICAgICBwLnJlamVjdChtZXNzYWdlKTtcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gY2FsbEJpbmRpbmcodHlwZSwgb3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG4gICAgICAgIG9wdGlvbnNbXCJjYWxsLWlkXCJdID0gaWQ7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuc2V0KGlkLCB7cmVzb2x2ZSwgcmVqZWN0fSk7XG4gICAgICAgIGNhbGwodHlwZSwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgICAgICByZWplY3QoZXJyb3IpO1xuICAgICAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIENhbGwob3B0aW9ucykge1xuICAgIHJldHVybiBjYWxsQmluZGluZyhcIkNhbGxcIiwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogQ2FsbCBhIHBsdWdpbiBtZXRob2RcbiAqIEBwYXJhbSB7c3RyaW5nfSBwbHVnaW5OYW1lIC0gbmFtZSBvZiB0aGUgcGx1Z2luXG4gKiBAcGFyYW0ge3N0cmluZ30gbWV0aG9kTmFtZSAtIG5hbWUgb2YgdGhlIG1ldGhvZFxuICogQHBhcmFtIHsuLi5hbnl9IGFyZ3MgLSBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgbWV0aG9kXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fSAtIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSByZXN1bHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFBsdWdpbihwbHVnaW5OYW1lLCBtZXRob2ROYW1lLCAuLi5hcmdzKSB7XG4gICAgcmV0dXJuIGNhbGxCaW5kaW5nKFwiQ2FsbFwiLCB7XG4gICAgICAgIHBhY2thZ2VOYW1lOiBcIndhaWxzLXBsdWdpbnNcIixcbiAgICAgICAgc3RydWN0TmFtZTogcGx1Z2luTmFtZSxcbiAgICAgICAgbWV0aG9kTmFtZTogbWV0aG9kTmFtZSxcbiAgICAgICAgYXJnczogYXJncyxcbiAgICB9KTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNpemV9IFNpemVcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuLi9hcGkvdHlwZXNcIikuUG9zaXRpb259IFBvc2l0aW9uXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNjcmVlbn0gU2NyZWVuXG4gKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdXaW5kb3cod2luZG93TmFtZSkge1xuICAgIGxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcIndpbmRvd1wiLCB3aW5kb3dOYW1lKTtcbiAgICByZXR1cm4ge1xuICAgICAgICAvLyBSZWxvYWQ6ICgpID0+IGNhbGwoJ1dSJyksXG4gICAgICAgIC8vIFJlbG9hZEFwcDogKCkgPT4gY2FsbCgnV1InKSxcbiAgICAgICAgLy8gU2V0U3lzdGVtRGVmYXVsdFRoZW1lOiAoKSA9PiBjYWxsKCdXQVNEVCcpLFxuICAgICAgICAvLyBTZXRMaWdodFRoZW1lOiAoKSA9PiBjYWxsKCdXQUxUJyksXG4gICAgICAgIC8vIFNldERhcmtUaGVtZTogKCkgPT4gY2FsbCgnV0FEVCcpLFxuICAgICAgICAvLyBJc0Z1bGxzY3JlZW46ICgpID0+IGNhbGwoJ1dJRicpLFxuICAgICAgICAvLyBJc01heGltaXplZDogKCkgPT4gY2FsbCgnV0lNJyksXG4gICAgICAgIC8vIElzTWluaW1pemVkOiAoKSA9PiBjYWxsKCdXSU1OJyksXG4gICAgICAgIC8vIElzV2luZG93ZWQ6ICgpID0+IGNhbGwoJ1dJRicpLFxuXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIENlbnRlcnMgdGhlIHdpbmRvdy5cbiAgICAgICAgICovXG4gICAgICAgIENlbnRlcjogKCkgPT4gdm9pZCBjYWxsKCdDZW50ZXInKSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogU2V0IHRoZSB3aW5kb3cgdGl0bGUuXG4gICAgICAgICAqIEBwYXJhbSB0aXRsZVxuICAgICAgICAgKi9cbiAgICAgICAgU2V0VGl0bGU6ICh0aXRsZSkgPT4gdm9pZCBjYWxsKCdTZXRUaXRsZScsIHt0aXRsZX0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNYWtlcyB0aGUgd2luZG93IGZ1bGxzY3JlZW4uXG4gICAgICAgICAqL1xuICAgICAgICBGdWxsc2NyZWVuOiAoKSA9PiB2b2lkIGNhbGwoJ0Z1bGxzY3JlZW4nKSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogVW5mdWxsc2NyZWVuIHRoZSB3aW5kb3cuXG4gICAgICAgICAqL1xuICAgICAgICBVbkZ1bGxzY3JlZW46ICgpID0+IHZvaWQgY2FsbCgnVW5GdWxsc2NyZWVuJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHNpemUuXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB3aWR0aCBUaGUgd2luZG93IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHQgVGhlIHdpbmRvdyBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiBjYWxsKCdTZXRTaXplJywge3dpZHRoLGhlaWdodH0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBzaXplLlxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxTaXplPn0gVGhlIHdpbmRvdyBzaXplXG4gICAgICAgICAqL1xuICAgICAgICBTaXplOiAoKSA9PiB7IHJldHVybiBjYWxsKCdTaXplJyk7IH0sXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IG1heGltdW0gc2l6ZS5cbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldE1heFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoJ1NldE1heFNpemUnLCB7d2lkdGgsaGVpZ2h0fSksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IG1pbmltdW0gc2l6ZS5cbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldE1pblNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoJ1NldE1pblNpemUnLCB7d2lkdGgsaGVpZ2h0fSksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB3aW5kb3cgdG8gYmUgYWx3YXlzIG9uIHRvcC5cbiAgICAgICAgICogQHBhcmFtIHtib29sZWFufSBvblRvcCBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGFsd2F5cyBvbiB0b3BcbiAgICAgICAgICovXG4gICAgICAgIFNldEFsd2F5c09uVG9wOiAob25Ub3ApID0+IHZvaWQgY2FsbCgnU2V0QWx3YXlzT25Ub3AnLCB7YWx3YXlzT25Ub3A6b25Ub3B9KSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogU2V0IHRoZSB3aW5kb3cgcG9zaXRpb24uXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB4XG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gICAgICAgICAqL1xuICAgICAgICBTZXRQb3NpdGlvbjogKHgsIHkpID0+IGNhbGwoJ1NldFBvc2l0aW9uJywge3gseX0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBwb3NpdGlvbi5cbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8UG9zaXRpb24+fSBUaGUgd2luZG93IHBvc2l0aW9uXG4gICAgICAgICAqL1xuICAgICAgICBQb3NpdGlvbjogKCkgPT4geyByZXR1cm4gY2FsbCgnUG9zaXRpb24nKTsgfSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogR2V0IHRoZSBzY3JlZW4gdGhlIHdpbmRvdyBpcyBvbi5cbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cbiAgICAgICAgICovXG4gICAgICAgIFNjcmVlbjogKCkgPT4geyByZXR1cm4gY2FsbCgnU2NyZWVuJyk7IH0sXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhpZGUgdGhlIHdpbmRvd1xuICAgICAgICAgKi9cbiAgICAgICAgSGlkZTogKCkgPT4gdm9pZCBjYWxsKCdIaWRlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIE1heGltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIE1heGltaXNlOiAoKSA9PiB2b2lkIGNhbGwoJ01heGltaXNlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNob3cgdGhlIHdpbmRvd1xuICAgICAgICAgKi9cbiAgICAgICAgU2hvdzogKCkgPT4gdm9pZCBjYWxsKCdTaG93JyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIENsb3NlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIENsb3NlOiAoKSA9PiB2b2lkIGNhbGwoJ0Nsb3NlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFRvZ2dsZSB0aGUgd2luZG93IG1heGltaXNlIHN0YXRlXG4gICAgICAgICAqL1xuICAgICAgICBUb2dnbGVNYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKCdUb2dnbGVNYXhpbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBVbm1heGltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIFVuTWF4aW1pc2U6ICgpID0+IHZvaWQgY2FsbCgnVW5NYXhpbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNaW5pbWlzZSB0aGUgd2luZG93XG4gICAgICAgICAqL1xuICAgICAgICBNaW5pbWlzZTogKCkgPT4gdm9pZCBjYWxsKCdNaW5pbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBVbm1pbmltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIFVuTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbCgnVW5NaW5pbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBTZXQgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSByIC0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDI1NVxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0gZyAtIEEgdmFsdWUgYmV0d2VlbiAwIGFuZCAyNTVcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBhIC0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDI1NVxuICAgICAgICAgKi9cbiAgICAgICAgU2V0QmFja2dyb3VuZENvbG91cjogKHIsIGcsIGIsIGEpID0+IHZvaWQgY2FsbCgnU2V0QmFja2dyb3VuZENvbG91cicsIHtyLCBnLCBiLCBhfSksXG4gICAgfTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG4vKipcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5XYWlsc0V2ZW50fSBXYWlsc0V2ZW50XG4gKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImV2ZW50c1wiKTtcblxuLyoqXG4gKiBUaGUgTGlzdGVuZXIgY2xhc3MgZGVmaW5lcyBhIGxpc3RlbmVyISA6LSlcbiAqXG4gKiBAY2xhc3MgTGlzdGVuZXJcbiAqL1xuY2xhc3MgTGlzdGVuZXIge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYW4gaW5zdGFuY2Ugb2YgTGlzdGVuZXIuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICAgICAqIEBtZW1iZXJvZiBMaXN0ZW5lclxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xuICAgICAgICB0aGlzLmV2ZW50TmFtZSA9IGV2ZW50TmFtZTtcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyA9IG1heENhbGxiYWNrcyB8fCAtMTtcbiAgICAgICAgLy8gQ2FsbGJhY2sgaW52b2tlcyB0aGUgY2FsbGJhY2sgd2l0aCB0aGUgZ2l2ZW4gZGF0YVxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXG4gICAgICAgIHRoaXMuQ2FsbGJhY2sgPSAoZGF0YSkgPT4ge1xuICAgICAgICAgICAgY2FsbGJhY2soZGF0YSk7XG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXG4gICAgICAgICAgICBpZiAodGhpcy5tYXhDYWxsYmFja3MgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gRGVjcmVtZW50IG1heENhbGxiYWNrcy4gUmV0dXJuIHRydWUgaWYgbm93IDAsIG90aGVyd2lzZSBmYWxzZVxuICAgICAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcbiAgICAgICAgfTtcbiAgICB9XG59XG5cblxuLyoqXG4gKiBXYWlsc0V2ZW50IGRlZmluZXMgYSBjdXN0b20gZXZlbnQuIEl0IGlzIHBhc3NlZCB0byBldmVudCBsaXN0ZW5lcnMuXG4gKlxuICogQGNsYXNzIFdhaWxzRXZlbnRcbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBuYW1lIC0gTmFtZSBvZiB0aGUgZXZlbnRcbiAqIEBwcm9wZXJ0eSB7YW55fSBkYXRhIC0gRGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XG4gKi9cbmV4cG9ydCBjbGFzcyBXYWlsc0V2ZW50IHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGFuIGluc3RhbmNlIG9mIFdhaWxzRXZlbnQuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBldmVudFxuICAgICAqIEBwYXJhbSB7YW55PW51bGx9IGRhdGEgLSBEYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgZXZlbnRcbiAgICAgKiBAbWVtYmVyb2YgV2FpbHNFdmVudFxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG5hbWUsIGRhdGEgPSBudWxsKSB7XG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XG4gICAgICAgIHRoaXMuZGF0YSA9IGRhdGE7XG4gICAgfVxufVxuXG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnMgPSBuZXcgTWFwKCk7XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGBtYXhDYWxsYmFja3NgIHRpbWVzIGJlZm9yZSBiZWluZyBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpIHx8IFtdO1xuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xuICAgIGxpc3RlbmVycy5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50TmFtZSwgbGlzdGVuZXJzKTtcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgZXZlcnkgdGltZSB0aGUgZXZlbnQgaXMgZW1pdHRlZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oV2FpbHNFdmVudCk6IHZvaWR9IGNhbGxiYWNrXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IEEgZnVuY3Rpb24gdG8gY2FuY2VsIHRoZSBsaXN0ZW5lclxuICovXG5leHBvcnQgZnVuY3Rpb24gT24oZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgb25jZSB0aGVuIGRlc3Ryb3llZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oV2FpbHNFdmVudCk6IHZvaWR9IGNhbGxiYWNrXG4gQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uY2UoZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG4vKipcbiAqIGxpc3RlbmVyT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT25cbiAqXG4gKiBAcGFyYW0ge0xpc3RlbmVyfSBsaXN0ZW5lclxuICovXG5mdW5jdGlvbiBsaXN0ZW5lck9mZihsaXN0ZW5lcikge1xuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcbiAgICAvLyBSZW1vdmUgbG9jYWwgbGlzdGVuZXJcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSkuZmlsdGVyKGwgPT4gbCAhPT0gbGlzdGVuZXIpO1xuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XG4gICAgfVxufVxuXG4vKipcbiAqIGRpc3BhdGNoZXMgYW4gZXZlbnQgdG8gYWxsIGxpc3RlbmVyc1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7V2FpbHNFdmVudH0gZXZlbnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRpc3BhdGNoV2FpbHNFdmVudChldmVudCkge1xuICAgIGNvbnNvbGUubG9nKFwiZGlzcGF0Y2hpbmcgZXZlbnQ6IFwiLCB7ZXZlbnR9KTtcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xuICAgIGlmIChsaXN0ZW5lcnMpIHtcbiAgICAgICAgLy8gaXRlcmF0ZSBsaXN0ZW5lcnMgYW5kIGNhbGwgY2FsbGJhY2suIElmIGNhbGxiYWNrIHJldHVybnMgdHJ1ZSwgcmVtb3ZlIGxpc3RlbmVyXG4gICAgICAgIGxldCB0b1JlbW92ZSA9IFtdO1xuICAgICAgICBsaXN0ZW5lcnMuZm9yRWFjaChsaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICBsZXQgcmVtb3ZlID0gbGlzdGVuZXIuQ2FsbGJhY2soZXZlbnQpO1xuICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xuICAgICAgICAgICAgICAgIHRvUmVtb3ZlLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gcmVtb3ZlIGxpc3RlbmVyc1xuICAgICAgICBpZiAodG9SZW1vdmUubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+ICF0b1JlbW92ZS5pbmNsdWRlcyhsKSk7XG4gICAgICAgICAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudC5uYW1lKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50Lm5hbWUsIGxpc3RlbmVycyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5cbi8qKlxuICogT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT24sXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVycyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXG4gKlxuIFt2MyBDSEFOR0VdIE9mZiBvbmx5IHVucmVnaXN0ZXJzIGxpc3RlbmVycyB3aXRoaW4gdGhlIGN1cnJlbnQgd2luZG93XG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICogQHBhcmFtICB7Li4uc3RyaW5nfSBhZGRpdGlvbmFsRXZlbnROYW1lc1xuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmKGV2ZW50TmFtZSwgLi4uYWRkaXRpb25hbEV2ZW50TmFtZXMpIHtcbiAgICBsZXQgZXZlbnRzVG9SZW1vdmUgPSBbZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lc107XG4gICAgZXZlbnRzVG9SZW1vdmUuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBPZmZBbGwgdW5yZWdpc3RlcnMgYWxsIGxpc3RlbmVyc1xuICogW3YzIENIQU5HRV0gT2ZmQWxsIG9ubHkgdW5yZWdpc3RlcnMgbGlzdGVuZXJzIHdpdGhpbiB0aGUgY3VycmVudCB3aW5kb3dcbiAqXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPZmZBbGwoKSB7XG4gICAgZXZlbnRMaXN0ZW5lcnMuY2xlYXIoKTtcbn1cblxuLyoqXG4gKiBFbWl0IGFuIGV2ZW50XG4gKiBAcGFyYW0ge1dhaWxzRXZlbnR9IGV2ZW50IFRoZSBldmVudCB0byBlbWl0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbWl0KGV2ZW50KSB7XG4gICAgdm9pZCBjYWxsKFwiRW1pdFwiLCBldmVudCk7XG59IiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cbi8qKlxuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk1lc3NhZ2VEaWFsb2dPcHRpb25zfSBNZXNzYWdlRGlhbG9nT3B0aW9uc1xuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk9wZW5EaWFsb2dPcHRpb25zfSBPcGVuRGlhbG9nT3B0aW9uc1xuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLlNhdmVEaWFsb2dPcHRpb25zfSBTYXZlRGlhbG9nT3B0aW9uc1xuICovXG5cbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcn0gZnJvbSBcIi4vcnVudGltZVwiO1xuXG5pbXBvcnQgeyBuYW5vaWQgfSBmcm9tICduYW5vaWQvbm9uLXNlY3VyZSc7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImRpYWxvZ1wiKTtcblxubGV0IGRpYWxvZ1Jlc3BvbnNlcyA9IG5ldyBNYXAoKTtcblxuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGRvIHtcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XG4gICAgfSB3aGlsZSAoZGlhbG9nUmVzcG9uc2VzLmhhcyhyZXN1bHQpKTtcbiAgICByZXR1cm4gcmVzdWx0O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZGlhbG9nQ2FsbGJhY2soaWQsIGRhdGEsIGlzSlNPTikge1xuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XG4gICAgaWYgKHApIHtcbiAgICAgICAgaWYgKGlzSlNPTikge1xuICAgICAgICAgICAgcC5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcC5yZXNvbHZlKGRhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIH1cbn1cbmV4cG9ydCBmdW5jdGlvbiBkaWFsb2dFcnJvckNhbGxiYWNrKGlkLCBtZXNzYWdlKSB7XG4gICAgbGV0IHAgPSBkaWFsb2dSZXNwb25zZXMuZ2V0KGlkKTtcbiAgICBpZiAocCkge1xuICAgICAgICBwLnJlamVjdChtZXNzYWdlKTtcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBkaWFsb2codHlwZSwgb3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG4gICAgICAgIG9wdGlvbnNbXCJkaWFsb2ctaWRcIl0gPSBpZDtcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLnNldChpZCwge3Jlc29sdmUsIHJlamVjdH0pO1xuICAgICAgICBjYWxsKHR5cGUsIG9wdGlvbnMpLmNhdGNoKChlcnJvcikgPT4ge1xuICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuXG4vKipcbiAqIFNob3dzIGFuIEluZm8gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJbmZvKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gZGlhbG9nKFwiSW5mb1wiLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTaG93cyBhbiBXYXJuaW5nIGRpYWxvZyB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIldhcm5pbmdcIiwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogU2hvd3MgYW4gRXJyb3IgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFcnJvcihvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIkVycm9yXCIsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNob3dzIGEgUXVlc3Rpb24gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBRdWVzdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIlF1ZXN0aW9uXCIsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNob3dzIGFuIE9wZW4gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmdbXXxzdHJpbmc+fSBSZXR1cm5zIHRoZSBzZWxlY3RlZCBmaWxlIG9yIGFuIGFycmF5IG9mIHNlbGVjdGVkIGZpbGVzIGlmIEFsbG93c011bHRpcGxlU2VsZWN0aW9uIGlzIHRydWUuIEEgYmxhbmsgc3RyaW5nIGlzIHJldHVybmVkIGlmIG5vIGZpbGUgd2FzIHNlbGVjdGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9ucykge1xuICAgIHJldHVybiBkaWFsb2coXCJPcGVuRmlsZVwiLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTaG93cyBhIFNhdmUgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBSZXR1cm5zIHRoZSBzZWxlY3RlZCBmaWxlLiBBIGJsYW5rIHN0cmluZyBpcyByZXR1cm5lZCBpZiBubyBmaWxlIHdhcyBzZWxlY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNhdmVGaWxlKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gZGlhbG9nKFwiU2F2ZUZpbGVcIiwgb3B0aW9ucyk7XG59XG5cbiIsICJpbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiY29udGV4dG1lbnVcIik7XG5cbmZ1bmN0aW9uIG9wZW5Db250ZXh0TWVudShpZCwgeCwgeSwgZGF0YSkge1xuICAgIHJldHVybiBjYWxsKFwiT3BlbkNvbnRleHRNZW51XCIsIHtpZCwgeCwgeSwgZGF0YX0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZW5hYmxlQ29udGV4dE1lbnVzKGVuYWJsZWQpIHtcbiAgICBpZiAoZW5hYmxlZCkge1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdjb250ZXh0bWVudScsIGNvbnRleHRNZW51SGFuZGxlcik7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQpIHtcbiAgICBwcm9jZXNzQ29udGV4dE1lbnUoZXZlbnQudGFyZ2V0LCBldmVudCk7XG59XG5cbmZ1bmN0aW9uIHByb2Nlc3NDb250ZXh0TWVudShlbGVtZW50LCBldmVudCkge1xuICAgIGxldCBpZCA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLWNvbnRleHRtZW51Jyk7XG4gICAgaWYgKGlkKSB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIG9wZW5Db250ZXh0TWVudShpZCwgZXZlbnQuY2xpZW50WCwgZXZlbnQuY2xpZW50WSwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtY29udGV4dG1lbnUtZGF0YScpKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBsZXQgcGFyZW50ID0gZWxlbWVudC5wYXJlbnRFbGVtZW50O1xuICAgICAgICBpZiAocGFyZW50KSB7XG4gICAgICAgICAgICBwcm9jZXNzQ29udGV4dE1lbnUocGFyZW50LCBldmVudCk7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiXG5pbXBvcnQge0VtaXQsIFdhaWxzRXZlbnR9IGZyb20gXCIuL2V2ZW50c1wiO1xuaW1wb3J0IHtRdWVzdGlvbn0gZnJvbSBcIi4vZGlhbG9nc1wiO1xuXG5mdW5jdGlvbiBzZW5kRXZlbnQoZXZlbnROYW1lLCBkYXRhPW51bGwpIHtcbiAgICBsZXQgZXZlbnQgPSBuZXcgV2FpbHNFdmVudChldmVudE5hbWUsIGRhdGEpO1xuICAgIEVtaXQoZXZlbnQpO1xufVxuXG5mdW5jdGlvbiBhZGRXTUxFdmVudExpc3RlbmVycygpIHtcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1tkYXRhLXdtbC1ldmVudF0nKTtcbiAgICBlbGVtZW50cy5mb3JFYWNoKGZ1bmN0aW9uIChlbGVtZW50KSB7XG4gICAgICAgIGNvbnN0IGV2ZW50VHlwZSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1ldmVudCcpO1xuICAgICAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWNvbmZpcm0nKTtcbiAgICAgICAgY29uc3QgdHJpZ2dlciA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiO1xuXG4gICAgICAgIGxldCBjYWxsYmFjayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGlmIChjb25maXJtKSB7XG4gICAgICAgICAgICAgICAgUXVlc3Rpb24oe1RpdGxlOiBcIkNvbmZpcm1cIiwgTWVzc2FnZTpjb25maXJtLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xuICAgICAgICAgICAgICAgICAgICBpZiAocmVzdWx0ICE9PSBcIk5vXCIpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbmRFdmVudChldmVudFR5cGUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2VuZEV2ZW50KGV2ZW50VHlwZSk7XG4gICAgICAgIH07XG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcblxuICAgICAgICBlbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xuXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcbiAgICAgICAgZWxlbWVudC5hZGRFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcbiAgICB9KTtcbn1cblxuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZChtZXRob2QpIHtcbiAgICBpZiAod2FpbHMuV2luZG93W21ldGhvZF0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICBjb25zb2xlLmxvZyhcIldpbmRvdyBtZXRob2QgXCIgKyBtZXRob2QgKyBcIiBub3QgZm91bmRcIik7XG4gICAgfVxuICAgIHdhaWxzLldpbmRvd1ttZXRob2RdKCk7XG59XG5cbmZ1bmN0aW9uIGFkZFdNTFdpbmRvd0xpc3RlbmVycygpIHtcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1tkYXRhLXdtbC13aW5kb3ddJyk7XG4gICAgZWxlbWVudHMuZm9yRWFjaChmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtd2luZG93Jyk7XG4gICAgICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtY29uZmlybScpO1xuICAgICAgICBjb25zdCB0cmlnZ2VyID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCI7XG5cbiAgICAgICAgbGV0IGNhbGxiYWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgaWYgKGNvbmZpcm0pIHtcbiAgICAgICAgICAgICAgICBRdWVzdGlvbih7VGl0bGU6IFwiQ29uZmlybVwiLCBNZXNzYWdlOmNvbmZpcm0sIEJ1dHRvbnM6W3tMYWJlbDpcIlllc1wifSx7TGFiZWw6XCJOb1wiLCBJc0RlZmF1bHQ6dHJ1ZX1dfSkudGhlbihmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xuICAgICAgICAgICAgICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dNZXRob2QpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dNZXRob2QpO1xuICAgICAgICB9O1xuXG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcbiAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcblxuICAgICAgICAvLyBBZGQgbmV3IGxpc3RlbmVyXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XG4gICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZWxvYWRXTUwoKSB7XG4gICAgYWRkV01MRXZlbnRMaXN0ZW5lcnMoKTtcbiAgICBhZGRXTUxXaW5kb3dMaXN0ZW5lcnMoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuXG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSAnLi9jbGlwYm9hcmQnO1xuaW1wb3J0ICogYXMgQXBwbGljYXRpb24gZnJvbSAnLi9hcHBsaWNhdGlvbic7XG5pbXBvcnQgKiBhcyBMb2cgZnJvbSAnLi9sb2cnO1xuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tICcuL3NjcmVlbnMnO1xuaW1wb3J0IHtQbHVnaW4sIENhbGwsIGNhbGxFcnJvckNhbGxiYWNrLCBjYWxsQ2FsbGJhY2t9IGZyb20gXCIuL2NhbGxzXCI7XG5pbXBvcnQge25ld1dpbmRvd30gZnJvbSBcIi4vd2luZG93XCI7XG5pbXBvcnQge2Rpc3BhdGNoV2FpbHNFdmVudCwgRW1pdCwgT2ZmLCBPZmZBbGwsIE9uLCBPbmNlLCBPbk11bHRpcGxlfSBmcm9tIFwiLi9ldmVudHNcIjtcbmltcG9ydCB7ZGlhbG9nQ2FsbGJhY2ssIGRpYWxvZ0Vycm9yQ2FsbGJhY2ssIEVycm9yLCBJbmZvLCBPcGVuRmlsZSwgUXVlc3Rpb24sIFNhdmVGaWxlLCBXYXJuaW5nLH0gZnJvbSBcIi4vZGlhbG9nc1wiO1xuaW1wb3J0IHtlbmFibGVDb250ZXh0TWVudXN9IGZyb20gXCIuL2NvbnRleHRtZW51XCI7XG5pbXBvcnQge3JlbG9hZFdNTH0gZnJvbSBcIi4vd21sXCI7XG5cbndpbmRvdy53YWlscyA9IHtcbiAgICAuLi5uZXdSdW50aW1lKG51bGwpLFxufTtcblxuLy8gSW50ZXJuYWwgd2FpbHMgZW5kcG9pbnRzXG53aW5kb3cuX3dhaWxzID0ge1xuICAgIGRpYWxvZ0NhbGxiYWNrLFxuICAgIGRpYWxvZ0Vycm9yQ2FsbGJhY2ssXG4gICAgZGlzcGF0Y2hXYWlsc0V2ZW50LFxuICAgIGNhbGxDYWxsYmFjayxcbiAgICBjYWxsRXJyb3JDYWxsYmFjayxcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lKHdpbmRvd05hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBDbGlwYm9hcmQ6IHtcbiAgICAgICAgICAgIC4uLkNsaXBib2FyZFxuICAgICAgICB9LFxuICAgICAgICBBcHBsaWNhdGlvbjoge1xuICAgICAgICAgICAgLi4uQXBwbGljYXRpb24sXG4gICAgICAgICAgICBHZXRXaW5kb3dCeU5hbWUod2luZG93TmFtZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBuZXdSdW50aW1lKHdpbmRvd05hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBMb2csXG4gICAgICAgIFNjcmVlbnMsXG4gICAgICAgIENhbGwsXG4gICAgICAgIFBsdWdpbixcbiAgICAgICAgV01MOiB7XG4gICAgICAgICAgICBSZWxvYWQ6IHJlbG9hZFdNTCxcbiAgICAgICAgfSxcbiAgICAgICAgRGlhbG9nOiB7XG4gICAgICAgICAgICBJbmZvLFxuICAgICAgICAgICAgV2FybmluZyxcbiAgICAgICAgICAgIEVycm9yLFxuICAgICAgICAgICAgUXVlc3Rpb24sXG4gICAgICAgICAgICBPcGVuRmlsZSxcbiAgICAgICAgICAgIFNhdmVGaWxlLFxuICAgICAgICB9LFxuICAgICAgICBFdmVudHM6IHtcbiAgICAgICAgICAgIEVtaXQsXG4gICAgICAgICAgICBPbixcbiAgICAgICAgICAgIE9uY2UsXG4gICAgICAgICAgICBPbk11bHRpcGxlLFxuICAgICAgICAgICAgT2ZmLFxuICAgICAgICAgICAgT2ZmQWxsLFxuICAgICAgICB9LFxuICAgICAgICBXaW5kb3c6IG5ld1dpbmRvdyh3aW5kb3dOYW1lKSxcbiAgICB9O1xufVxuXG5pZiAoREVCVUcpIHtcbiAgICBjb25zb2xlLmxvZyhcIldhaWxzIHYzLjAuMCBEZWJ1ZyBNb2RlIEVuYWJsZWRcIik7XG59XG5cbmVuYWJsZUNvbnRleHRNZW51cyh0cnVlKTtcblxuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihcIkRPTUNvbnRlbnRMb2FkZWRcIiwgZnVuY3Rpb24oZXZlbnQpIHtcbiAgICByZWxvYWRXTUwoKTtcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTs7O0FDWUEsTUFBTSxhQUFhLE9BQU8sU0FBUyxTQUFTO0FBRTVDLFdBQVMsWUFBWSxRQUFRLFlBQVksTUFBTTtBQUMzQyxRQUFJLE1BQU0sSUFBSSxJQUFJLFVBQVU7QUFDNUIsUUFBSSxhQUFhLE9BQU8sVUFBVSxNQUFNO0FBQ3hDLFFBQUksTUFBTTtBQUNOLFVBQUksYUFBYSxPQUFPLFFBQVEsS0FBSyxVQUFVLElBQUksQ0FBQztBQUFBLElBQ3hEO0FBQ0EsUUFBSSxlQUFlO0FBQUEsTUFDZixTQUFTLENBQUM7QUFBQSxJQUNkO0FBQ0EsUUFBSSxZQUFZO0FBQ1osbUJBQWEsUUFBUSxxQkFBcUIsSUFBSTtBQUFBLElBQ2xEO0FBQ0EsV0FBTyxJQUFJLFFBQVEsQ0FBQyxTQUFTLFdBQVc7QUFDcEMsWUFBTSxLQUFLLFlBQVksRUFDbEIsS0FBSyxjQUFZO0FBQ2QsWUFBSSxTQUFTLElBQUk7QUFFYixjQUFJLFNBQVMsUUFBUSxJQUFJLGNBQWMsS0FBSyxTQUFTLFFBQVEsSUFBSSxjQUFjLEVBQUUsUUFBUSxrQkFBa0IsTUFBTSxJQUFJO0FBQ2pILG1CQUFPLFNBQVMsS0FBSztBQUFBLFVBQ3pCLE9BQU87QUFDSCxtQkFBTyxTQUFTLEtBQUs7QUFBQSxVQUN6QjtBQUFBLFFBQ0o7QUFDQSxlQUFPLE1BQU0sU0FBUyxVQUFVLENBQUM7QUFBQSxNQUNyQyxDQUFDLEVBQ0EsS0FBSyxVQUFRLFFBQVEsSUFBSSxDQUFDLEVBQzFCLE1BQU0sV0FBUyxPQUFPLEtBQUssQ0FBQztBQUFBLElBQ3JDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxpQkFBaUIsUUFBUSxZQUFZO0FBQ2pELFdBQU8sU0FBVSxRQUFRLE9BQUssTUFBTTtBQUNoQyxhQUFPLFlBQVksU0FBUyxNQUFNLFFBQVEsWUFBWSxJQUFJO0FBQUEsSUFDOUQ7QUFBQSxFQUNKOzs7QURsQ0EsTUFBSSxPQUFPLGlCQUFpQixXQUFXO0FBS2hDLFdBQVMsUUFBUSxNQUFNO0FBQzFCLFNBQUssS0FBSyxXQUFXLEVBQUMsS0FBSSxDQUFDO0FBQUEsRUFDL0I7QUFNTyxXQUFTLE9BQU87QUFDbkIsV0FBTyxLQUFLLE1BQU07QUFBQSxFQUN0Qjs7O0FFN0JBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWNBLE1BQUlBLFFBQU8saUJBQWlCLGFBQWE7QUFLbEMsV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCO0FBS08sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCO0FBTU8sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCOzs7QUNwQ0E7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQyxRQUFPLGlCQUFpQixLQUFLO0FBTTFCLFdBQVMsSUFBSSxTQUFTO0FBQ3pCLFdBQU9BLE1BQUssT0FBTyxPQUFPO0FBQUEsRUFDOUI7OztBQ3RCQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsTUFBSUMsUUFBTyxpQkFBaUIsU0FBUztBQU05QixXQUFTLFNBQVM7QUFDckIsV0FBT0EsTUFBSyxRQUFRO0FBQUEsRUFDeEI7QUFNTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxZQUFZO0FBQUEsRUFDNUI7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxZQUFZO0FBQUEsRUFDNUI7OztBQzNDQSxNQUFJLGNBQ0Y7QUFXSyxNQUFJLFNBQVMsQ0FBQyxPQUFPLE9BQU87QUFDakMsUUFBSSxLQUFLO0FBQ1QsUUFBSSxJQUFJO0FBQ1IsV0FBTyxLQUFLO0FBQ1YsWUFBTSxZQUFhLEtBQUssT0FBTyxJQUFJLEtBQU0sQ0FBQztBQUFBLElBQzVDO0FBQ0EsV0FBTztBQUFBLEVBQ1Q7OztBQ0hBLE1BQUlDLFFBQU8saUJBQWlCLE1BQU07QUFFbEMsTUFBSSxnQkFBZ0Isb0JBQUksSUFBSTtBQUU1QixXQUFTLGFBQWE7QUFDbEIsUUFBSTtBQUNKLE9BQUc7QUFDQyxlQUFTLE9BQU87QUFBQSxJQUNwQixTQUFTLGNBQWMsSUFBSSxNQUFNO0FBQ2pDLFdBQU87QUFBQSxFQUNYO0FBRU8sV0FBUyxhQUFhLElBQUksTUFBTSxRQUFRO0FBQzNDLFFBQUksSUFBSSxjQUFjLElBQUksRUFBRTtBQUM1QixRQUFJLEdBQUc7QUFDSCxVQUFJLFFBQVE7QUFDUixVQUFFLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLE1BQzlCLE9BQU87QUFDSCxVQUFFLFFBQVEsSUFBSTtBQUFBLE1BQ2xCO0FBQ0Esb0JBQWMsT0FBTyxFQUFFO0FBQUEsSUFDM0I7QUFBQSxFQUNKO0FBRU8sV0FBUyxrQkFBa0IsSUFBSSxTQUFTO0FBQzNDLFFBQUksSUFBSSxjQUFjLElBQUksRUFBRTtBQUM1QixRQUFJLEdBQUc7QUFDSCxRQUFFLE9BQU8sT0FBTztBQUNoQixvQkFBYyxPQUFPLEVBQUU7QUFBQSxJQUMzQjtBQUFBLEVBQ0o7QUFFQSxXQUFTLFlBQVksTUFBTSxTQUFTO0FBQ2hDLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFVBQUksS0FBSyxXQUFXO0FBQ3BCLGdCQUFVLFdBQVcsQ0FBQztBQUN0QixjQUFRLFNBQVMsSUFBSTtBQUNyQixvQkFBYyxJQUFJLElBQUksRUFBQyxTQUFTLE9BQU0sQ0FBQztBQUN2QyxNQUFBQSxNQUFLLE1BQU0sT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVO0FBQ2pDLGVBQU8sS0FBSztBQUNaLHNCQUFjLE9BQU8sRUFBRTtBQUFBLE1BQzNCLENBQUM7QUFBQSxJQUNMLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxLQUFLLFNBQVM7QUFDMUIsV0FBTyxZQUFZLFFBQVEsT0FBTztBQUFBLEVBQ3RDO0FBU08sV0FBUyxPQUFPLFlBQVksZUFBZSxNQUFNO0FBQ3BELFdBQU8sWUFBWSxRQUFRO0FBQUEsTUFDdkIsYUFBYTtBQUFBLE1BQ2IsWUFBWTtBQUFBLE1BQ1o7QUFBQSxNQUNBO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDs7O0FDM0RPLFdBQVMsVUFBVSxZQUFZO0FBQ2xDLFFBQUlDLFFBQU8saUJBQWlCLFVBQVUsVUFBVTtBQUNoRCxXQUFPO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFlSCxRQUFRLE1BQU0sS0FBS0EsTUFBSyxRQUFRO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU1oQyxVQUFVLENBQUMsVUFBVSxLQUFLQSxNQUFLLFlBQVksRUFBQyxNQUFLLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtsRCxZQUFZLE1BQU0sS0FBS0EsTUFBSyxZQUFZO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLeEMsY0FBYyxNQUFNLEtBQUtBLE1BQUssY0FBYztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU81QyxTQUFTLENBQUMsT0FBTyxXQUFXQSxNQUFLLFdBQVcsRUFBQyxPQUFNLE9BQU0sQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNMUQsTUFBTSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxNQUFNO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU9uQyxZQUFZLENBQUMsT0FBTyxXQUFXLEtBQUtBLE1BQUssY0FBYyxFQUFDLE9BQU0sT0FBTSxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT3JFLFlBQVksQ0FBQyxPQUFPLFdBQVcsS0FBS0EsTUFBSyxjQUFjLEVBQUMsT0FBTSxPQUFNLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXJFLGdCQUFnQixDQUFDLFVBQVUsS0FBS0EsTUFBSyxrQkFBa0IsRUFBQyxhQUFZLE1BQUssQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU8xRSxhQUFhLENBQUMsR0FBRyxNQUFNQSxNQUFLLGVBQWUsRUFBQyxHQUFFLEVBQUMsQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNaEQsVUFBVSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxVQUFVO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNM0MsUUFBUSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxRQUFRO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS3ZDLE1BQU0sTUFBTSxLQUFLQSxNQUFLLE1BQU07QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUs1QixVQUFVLE1BQU0sS0FBS0EsTUFBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEMsTUFBTSxNQUFNLEtBQUtBLE1BQUssTUFBTTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzVCLE9BQU8sTUFBTSxLQUFLQSxNQUFLLE9BQU87QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUs5QixnQkFBZ0IsTUFBTSxLQUFLQSxNQUFLLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2hELFlBQVksTUFBTSxLQUFLQSxNQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUt4QyxVQUFVLE1BQU0sS0FBS0EsTUFBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEMsWUFBWSxNQUFNLEtBQUtBLE1BQUssWUFBWTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFTeEMscUJBQXFCLENBQUMsR0FBRyxHQUFHLEdBQUcsTUFBTSxLQUFLQSxNQUFLLHVCQUF1QixFQUFDLEdBQUcsR0FBRyxHQUFHLEVBQUMsQ0FBQztBQUFBLElBQ3RGO0FBQUEsRUFDSjs7O0FDMUlBLE1BQUlDLFFBQU8saUJBQWlCLFFBQVE7QUFPcEMsTUFBTSxXQUFOLE1BQWU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLElBUVgsWUFBWSxXQUFXLFVBQVUsY0FBYztBQUMzQyxXQUFLLFlBQVk7QUFFakIsV0FBSyxlQUFlLGdCQUFnQjtBQUdwQyxXQUFLLFdBQVcsQ0FBQyxTQUFTO0FBQ3RCLGlCQUFTLElBQUk7QUFFYixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFVTyxNQUFNLGFBQU4sTUFBaUI7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQU9wQixZQUFZLE1BQU0sT0FBTyxNQUFNO0FBQzNCLFdBQUssT0FBTztBQUNaLFdBQUssT0FBTztBQUFBLElBQ2hCO0FBQUEsRUFDSjtBQUVPLE1BQU0saUJBQWlCLG9CQUFJLElBQUk7QUFXL0IsV0FBUyxXQUFXLFdBQVcsVUFBVSxjQUFjO0FBQzFELFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsVUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxjQUFVLEtBQUssWUFBWTtBQUMzQixtQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLEdBQUcsV0FBVyxVQUFVO0FBQ3BDLFdBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQzdDO0FBVU8sV0FBUyxLQUFLLFdBQVcsVUFBVTtBQUN0QyxXQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFBQSxFQUM1QztBQU9BLFdBQVMsWUFBWSxVQUFVO0FBQzNCLFVBQU0sWUFBWSxTQUFTO0FBRTNCLFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDeEUsUUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixxQkFBZSxPQUFPLFNBQVM7QUFBQSxJQUNuQyxPQUFPO0FBQ0gscUJBQWUsSUFBSSxXQUFXLFNBQVM7QUFBQSxJQUMzQztBQUFBLEVBQ0o7QUFRTyxXQUFTLG1CQUFtQixPQUFPO0FBQ3RDLFlBQVEsSUFBSSx1QkFBdUIsRUFBQyxNQUFLLENBQUM7QUFDMUMsUUFBSSxZQUFZLGVBQWUsSUFBSSxNQUFNLElBQUk7QUFDN0MsUUFBSSxXQUFXO0FBRVgsVUFBSSxXQUFXLENBQUM7QUFDaEIsZ0JBQVUsUUFBUSxjQUFZO0FBQzFCLFlBQUksU0FBUyxTQUFTLFNBQVMsS0FBSztBQUNwQyxZQUFJLFFBQVE7QUFDUixtQkFBUyxLQUFLLFFBQVE7QUFBQSxRQUMxQjtBQUFBLE1BQ0osQ0FBQztBQUVELFVBQUksU0FBUyxTQUFTLEdBQUc7QUFDckIsb0JBQVksVUFBVSxPQUFPLE9BQUssQ0FBQyxTQUFTLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZELFlBQUksVUFBVSxXQUFXLEdBQUc7QUFDeEIseUJBQWUsT0FBTyxNQUFNLElBQUk7QUFBQSxRQUNwQyxPQUFPO0FBQ0gseUJBQWUsSUFBSSxNQUFNLE1BQU0sU0FBUztBQUFBLFFBQzVDO0FBQUEsTUFDSjtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBV08sV0FBUyxJQUFJLGNBQWMsc0JBQXNCO0FBQ3BELFFBQUksaUJBQWlCLENBQUMsV0FBVyxHQUFHLG9CQUFvQjtBQUN4RCxtQkFBZSxRQUFRLENBQUFDLGVBQWE7QUFDaEMscUJBQWUsT0FBT0EsVUFBUztBQUFBLElBQ25DLENBQUM7QUFBQSxFQUNMO0FBT08sV0FBUyxTQUFTO0FBQ3JCLG1CQUFlLE1BQU07QUFBQSxFQUN6QjtBQU1PLFdBQVMsS0FBSyxPQUFPO0FBQ3hCLFNBQUtELE1BQUssUUFBUSxLQUFLO0FBQUEsRUFDM0I7OztBQzNLQSxNQUFJRSxRQUFPLGlCQUFpQixRQUFRO0FBRXBDLE1BQUksa0JBQWtCLG9CQUFJLElBQUk7QUFFOUIsV0FBU0MsY0FBYTtBQUNsQixRQUFJO0FBQ0osT0FBRztBQUNDLGVBQVMsT0FBTztBQUFBLElBQ3BCLFNBQVMsZ0JBQWdCLElBQUksTUFBTTtBQUNuQyxXQUFPO0FBQUEsRUFDWDtBQUVPLFdBQVMsZUFBZSxJQUFJLE1BQU0sUUFBUTtBQUM3QyxRQUFJLElBQUksZ0JBQWdCLElBQUksRUFBRTtBQUM5QixRQUFJLEdBQUc7QUFDSCxVQUFJLFFBQVE7QUFDUixVQUFFLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLE1BQzlCLE9BQU87QUFDSCxVQUFFLFFBQVEsSUFBSTtBQUFBLE1BQ2xCO0FBQ0Esc0JBQWdCLE9BQU8sRUFBRTtBQUFBLElBQzdCO0FBQUEsRUFDSjtBQUNPLFdBQVMsb0JBQW9CLElBQUksU0FBUztBQUM3QyxRQUFJLElBQUksZ0JBQWdCLElBQUksRUFBRTtBQUM5QixRQUFJLEdBQUc7QUFDSCxRQUFFLE9BQU8sT0FBTztBQUNoQixzQkFBZ0IsT0FBTyxFQUFFO0FBQUEsSUFDN0I7QUFBQSxFQUNKO0FBRUEsV0FBUyxPQUFPLE1BQU0sU0FBUztBQUMzQixXQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxVQUFJLEtBQUtBLFlBQVc7QUFDcEIsZ0JBQVUsV0FBVyxDQUFDO0FBQ3RCLGNBQVEsV0FBVyxJQUFJO0FBQ3ZCLHNCQUFnQixJQUFJLElBQUksRUFBQyxTQUFTLE9BQU0sQ0FBQztBQUN6QyxNQUFBRCxNQUFLLE1BQU0sT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVO0FBQ2pDLGVBQU8sS0FBSztBQUNaLHdCQUFnQixPQUFPLEVBQUU7QUFBQSxNQUM3QixDQUFDO0FBQUEsSUFDTCxDQUFDO0FBQUEsRUFDTDtBQVFPLFdBQVMsS0FBSyxTQUFTO0FBQzFCLFdBQU8sT0FBTyxRQUFRLE9BQU87QUFBQSxFQUNqQztBQU9PLFdBQVMsUUFBUSxTQUFTO0FBQzdCLFdBQU8sT0FBTyxXQUFXLE9BQU87QUFBQSxFQUNwQztBQU9PLFdBQVNFLE9BQU0sU0FBUztBQUMzQixXQUFPLE9BQU8sU0FBUyxPQUFPO0FBQUEsRUFDbEM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7OztBQ3JIQSxNQUFJQyxRQUFPLGlCQUFpQixhQUFhO0FBRXpDLFdBQVMsZ0JBQWdCLElBQUksR0FBRyxHQUFHLE1BQU07QUFDckMsV0FBT0EsTUFBSyxtQkFBbUIsRUFBQyxJQUFJLEdBQUcsR0FBRyxLQUFJLENBQUM7QUFBQSxFQUNuRDtBQUVPLFdBQVMsbUJBQW1CLFNBQVM7QUFDeEMsUUFBSSxTQUFTO0FBQ1QsYUFBTyxpQkFBaUIsZUFBZSxrQkFBa0I7QUFBQSxJQUM3RCxPQUFPO0FBQ0gsYUFBTyxvQkFBb0IsZUFBZSxrQkFBa0I7QUFBQSxJQUNoRTtBQUFBLEVBQ0o7QUFFQSxXQUFTLG1CQUFtQixPQUFPO0FBQy9CLHVCQUFtQixNQUFNLFFBQVEsS0FBSztBQUFBLEVBQzFDO0FBRUEsV0FBUyxtQkFBbUIsU0FBUyxPQUFPO0FBQ3hDLFFBQUksS0FBSyxRQUFRLGFBQWEsa0JBQWtCO0FBQ2hELFFBQUksSUFBSTtBQUNKLFlBQU0sZUFBZTtBQUNyQixzQkFBZ0IsSUFBSSxNQUFNLFNBQVMsTUFBTSxTQUFTLFFBQVEsYUFBYSx1QkFBdUIsQ0FBQztBQUFBLElBQ25HLE9BQU87QUFDSCxVQUFJLFNBQVMsUUFBUTtBQUNyQixVQUFJLFFBQVE7QUFDUiwyQkFBbUIsUUFBUSxLQUFLO0FBQUEsTUFDcEM7QUFBQSxJQUNKO0FBQUEsRUFDSjs7O0FDM0JBLFdBQVMsVUFBVSxXQUFXLE9BQUssTUFBTTtBQUNyQyxRQUFJLFFBQVEsSUFBSSxXQUFXLFdBQVcsSUFBSTtBQUMxQyxTQUFLLEtBQUs7QUFBQSxFQUNkO0FBRUEsV0FBUyx1QkFBdUI7QUFDNUIsVUFBTSxXQUFXLFNBQVMsaUJBQWlCLGtCQUFrQjtBQUM3RCxhQUFTLFFBQVEsU0FBVSxTQUFTO0FBQ2hDLFlBQU0sWUFBWSxRQUFRLGFBQWEsZ0JBQWdCO0FBQ3ZELFlBQU0sVUFBVSxRQUFRLGFBQWEsa0JBQWtCO0FBQ3ZELFlBQU0sVUFBVSxRQUFRLGFBQWEsa0JBQWtCLEtBQUs7QUFFNUQsVUFBSSxXQUFXLFdBQVk7QUFDdkIsWUFBSSxTQUFTO0FBQ1QsbUJBQVMsRUFBQyxPQUFPLFdBQVcsU0FBUSxTQUFTLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3ZILGdCQUFJLFdBQVcsTUFBTTtBQUNqQix3QkFBVSxTQUFTO0FBQUEsWUFDdkI7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSxrQkFBVSxTQUFTO0FBQUEsTUFDdkI7QUFHQSxjQUFRLG9CQUFvQixTQUFTLFFBQVE7QUFHN0MsY0FBUSxpQkFBaUIsU0FBUyxRQUFRO0FBQUEsSUFDOUMsQ0FBQztBQUFBLEVBQ0w7QUFFQSxXQUFTLGlCQUFpQixRQUFRO0FBQzlCLFFBQUksTUFBTSxPQUFPLE1BQU0sTUFBTSxRQUFXO0FBQ3BDLGNBQVEsSUFBSSxtQkFBbUIsU0FBUyxZQUFZO0FBQUEsSUFDeEQ7QUFDQSxVQUFNLE9BQU8sTUFBTSxFQUFFO0FBQUEsRUFDekI7QUFFQSxXQUFTLHdCQUF3QjtBQUM3QixVQUFNLFdBQVcsU0FBUyxpQkFBaUIsbUJBQW1CO0FBQzlELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxlQUFlLFFBQVEsYUFBYSxpQkFBaUI7QUFDM0QsWUFBTSxVQUFVLFFBQVEsYUFBYSxrQkFBa0I7QUFDdkQsWUFBTSxVQUFVLFFBQVEsYUFBYSxrQkFBa0IsS0FBSztBQUU1RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsU0FBUSxDQUFDLEVBQUMsT0FBTSxNQUFLLEdBQUUsRUFBQyxPQUFNLE1BQU0sV0FBVSxLQUFJLENBQUMsRUFBQyxDQUFDLEVBQUUsS0FBSyxTQUFVLFFBQVE7QUFDdkgsZ0JBQUksV0FBVyxNQUFNO0FBQ2pCLCtCQUFpQixZQUFZO0FBQUEsWUFDakM7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSx5QkFBaUIsWUFBWTtBQUFBLE1BQ2pDO0FBR0EsY0FBUSxvQkFBb0IsU0FBUyxRQUFRO0FBRzdDLGNBQVEsaUJBQWlCLFNBQVMsUUFBUTtBQUFBLElBQzlDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLHlCQUFxQjtBQUNyQiwwQkFBc0I7QUFBQSxFQUMxQjs7O0FDbERBLFNBQU8sUUFBUTtBQUFBLElBQ1gsR0FBRyxXQUFXLElBQUk7QUFBQSxFQUN0QjtBQUdBLFNBQU8sU0FBUztBQUFBLElBQ1o7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsRUFDSjtBQUVPLFdBQVMsV0FBVyxZQUFZO0FBQ25DLFdBQU87QUFBQSxNQUNILFdBQVc7QUFBQSxRQUNQLEdBQUc7QUFBQSxNQUNQO0FBQUEsTUFDQSxhQUFhO0FBQUEsUUFDVCxHQUFHO0FBQUEsUUFDSCxnQkFBZ0JDLGFBQVk7QUFDeEIsaUJBQU8sV0FBV0EsV0FBVTtBQUFBLFFBQ2hDO0FBQUEsTUFDSjtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBLEtBQUs7QUFBQSxRQUNELFFBQVE7QUFBQSxNQUNaO0FBQUEsTUFDQSxRQUFRO0FBQUEsUUFDSjtBQUFBLFFBQ0E7QUFBQSxRQUNBLE9BQUFDO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsTUFDSjtBQUFBLE1BQ0EsUUFBUTtBQUFBLFFBQ0o7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0o7QUFBQSxNQUNBLFFBQVEsVUFBVSxVQUFVO0FBQUEsSUFDaEM7QUFBQSxFQUNKO0FBRUEsTUFBSSxNQUFPO0FBQ1AsWUFBUSxJQUFJLGlDQUFpQztBQUFBLEVBQ2pEO0FBRUEscUJBQW1CLElBQUk7QUFFdkIsV0FBUyxpQkFBaUIsb0JBQW9CLFNBQVMsT0FBTztBQUMxRCxjQUFVO0FBQUEsRUFDZCxDQUFDOyIsCiAgIm5hbWVzIjogWyJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJldmVudE5hbWUiLCAiY2FsbCIsICJnZW5lcmF0ZUlEIiwgIkVycm9yIiwgImNhbGwiLCAid2luZG93TmFtZSIsICJFcnJvciJdCn0K diff --git a/v3/internal/runtime/runtime_debug_desktop_linux.js b/v3/internal/runtime/runtime_debug_desktop_linux.js new file mode 100644 index 000000000..0ca537592 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_desktop_linux.js @@ -0,0 +1,582 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text + }); + + // desktop/runtime.js + var runtimeURL = window.location.origin + "/wails/runtime"; + function runtimeCall(method, windowName, args) { + let url = new URL(runtimeURL); + url.searchParams.append("method", method); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let fetchOptions = { + headers: {} + }; + if (windowName) { + fetchOptions.headers["x-wails-window-name"] = windowName; + } + return new Promise((resolve, reject) => { + fetch(url, fetchOptions).then((response) => { + if (response.ok) { + if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) { + return response.json(); + } else { + return response.text(); + } + } + reject(Error(response.statusText)); + }).then((data) => resolve(data)).catch((error) => reject(error)); + }); + } + function newRuntimeCaller(object, windowName) { + return function(method, args = null) { + return runtimeCall(object + "." + method, windowName, args); + }; + } + + // desktop/clipboard.js + var call = newRuntimeCaller("clipboard"); + function SetText(text) { + void call("SetText", { text }); + } + function Text() { + return call("Text"); + } + + // desktop/application.js + var application_exports = {}; + __export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show + }); + var call2 = newRuntimeCaller("application"); + function Hide() { + void call2("Hide"); + } + function Show() { + void call2("Show"); + } + function Quit() { + void call2("Quit"); + } + + // desktop/log.js + var log_exports = {}; + __export(log_exports, { + Log: () => Log + }); + var call3 = newRuntimeCaller("log"); + function Log(message) { + return call3("Log", message); + } + + // desktop/screens.js + var screens_exports = {}; + __export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary + }); + var call4 = newRuntimeCaller("screens"); + function GetAll() { + return call4("GetAll"); + } + function GetPrimary() { + return call4("GetPrimary"); + } + function GetCurrent() { + return call4("GetCurrent"); + } + + // node_modules/nanoid/non-secure/index.js + var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + var nanoid = (size = 21) => { + let id = ""; + let i = size; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; + }; + + // desktop/calls.js + var call5 = newRuntimeCaller("call"); + var callResponses = /* @__PURE__ */ new Map(); + function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; + } + function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } + } + function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } + } + function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, { resolve, reject }); + call5(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); + } + function Call(options) { + return callBinding("Call", options); + } + function Plugin(pluginName, methodName, ...args) { + return callBinding("Call", { + packageName: "wails-plugins", + structName: pluginName, + methodName, + args + }); + } + + // desktop/window.js + function newWindow(windowName) { + let call9 = newRuntimeCaller("window", windowName); + return { + // Reload: () => call('WR'), + // ReloadApp: () => call('WR'), + // SetSystemDefaultTheme: () => call('WASDT'), + // SetLightTheme: () => call('WALT'), + // SetDarkTheme: () => call('WADT'), + // IsFullscreen: () => call('WIF'), + // IsMaximized: () => call('WIM'), + // IsMinimized: () => call('WIMN'), + // IsWindowed: () => call('WIF'), + /** + * Centers the window. + */ + Center: () => void call9("Center"), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call9("SetTitle", { title }), + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call9("Fullscreen"), + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call9("UnFullscreen"), + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call9("SetSize", { width, height }), + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return call9("Size"); + }, + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call9("SetMaxSize", { width, height }), + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call9("SetMinSize", { width, height }), + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call9("SetAlwaysOnTop", { alwaysOnTop: onTop }), + /** + * Set the window position. + * @param {number} x + * @param {number} y + */ + SetPosition: (x, y) => call9("SetPosition", { x, y }), + /** + * Get the window position. + * @returns {Promise} The window position + */ + Position: () => { + return call9("Position"); + }, + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return call9("Screen"); + }, + /** + * Hide the window + */ + Hide: () => void call9("Hide"), + /** + * Maximise the window + */ + Maximise: () => void call9("Maximise"), + /** + * Show the window + */ + Show: () => void call9("Show"), + /** + * Close the window + */ + Close: () => void call9("Close"), + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call9("ToggleMaximise"), + /** + * Unmaximise the window + */ + UnMaximise: () => void call9("UnMaximise"), + /** + * Minimise the window + */ + Minimise: () => void call9("Minimise"), + /** + * Unminimise the window + */ + UnMinimise: () => void call9("UnMinimise"), + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call9("SetBackgroundColour", { r, g, b, a }) + }; + } + + // desktop/events.js + var call6 = newRuntimeCaller("events"); + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback(data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var WailsEvent = class { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } + }; + var eventListeners = /* @__PURE__ */ new Map(); + 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 listenerOff(listener) { + const eventName = listener.eventName; + let listeners = eventListeners.get(eventName).filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } + } + function dispatchWailsEvent(event) { + console.log("dispatching event: ", { event }); + let listeners = eventListeners.get(event.name); + if (listeners) { + let toRemove = []; + listeners.forEach((listener) => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + if (toRemove.length > 0) { + listeners = listeners.filter((l) => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } + } + function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach((eventName2) => { + eventListeners.delete(eventName2); + }); + } + function OffAll() { + eventListeners.clear(); + } + function Emit(event) { + void call6("Emit", event); + } + + // desktop/dialogs.js + var call7 = newRuntimeCaller("dialog"); + var dialogResponses = /* @__PURE__ */ new Map(); + function generateID2() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; + } + function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } + } + function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } + } + function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID2(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, { resolve, reject }); + call7(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); + } + function Info(options) { + return dialog("Info", options); + } + function Warning(options) { + return dialog("Warning", options); + } + function Error2(options) { + return dialog("Error", options); + } + function Question(options) { + return dialog("Question", options); + } + function OpenFile(options) { + return dialog("OpenFile", options); + } + function SaveFile(options) { + return dialog("SaveFile", options); + } + + // desktop/contextmenu.js + var call8 = newRuntimeCaller("contextmenu"); + function openContextMenu(id, x, y, data) { + return call8("OpenContextMenu", { id, x, y, data }); + } + function enableContextMenus(enabled) { + if (enabled) { + window.addEventListener("contextmenu", contextMenuHandler); + } else { + window.removeEventListener("contextmenu", contextMenuHandler); + } + } + function contextMenuHandler(event) { + processContextMenu(event.target, event); + } + function processContextMenu(element, event) { + let id = element.getAttribute("data-contextmenu"); + if (id) { + event.preventDefault(); + openContextMenu(id, event.clientX, event.clientY, element.getAttribute("data-contextmenu-data")); + } else { + let parent = element.parentElement; + if (parent) { + processContextMenu(parent, event); + } + } + } + + // desktop/wml.js + function sendEvent(eventName, data = null) { + let event = new WailsEvent(eventName, data); + Emit(event); + } + function addWMLEventListeners() { + const elements = document.querySelectorAll("[data-wml-event]"); + elements.forEach(function(element) { + const eventType = element.getAttribute("data-wml-event"); + const confirm = element.getAttribute("data-wml-confirm"); + const trigger = element.getAttribute("data-wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function callWindowMethod(method) { + if (wails.Window[method] === void 0) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); + } + function addWMLWindowListeners() { + const elements = document.querySelectorAll("[data-wml-window]"); + elements.forEach(function(element) { + const windowMethod = element.getAttribute("data-wml-window"); + const confirm = element.getAttribute("data-wml-confirm"); + const trigger = element.getAttribute("data-wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + } + + // desktop/main.js + window.wails = { + ...newRuntime(null) + }; + window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback + }; + function newRuntime(windowName) { + return { + Clipboard: { + ...clipboard_exports + }, + Application: { + ...application_exports, + GetWindowByName(windowName2) { + return newRuntime(windowName2); + } + }, + Log: log_exports, + Screens: screens_exports, + Call, + Plugin, + WML: { + Reload: reloadWML + }, + Dialog: { + Info, + Warning, + Error: Error2, + Question, + OpenFile, + SaveFile + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll + }, + Window: newWindow(windowName) + }; + } + if (true) { + console.log("Wails v3.0.0 Debug Mode Enabled"); + } + enableContextMenus(true); + document.addEventListener("DOMContentLoaded", function(event) { + reloadWML(); + }); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9jbGlwYm9hcmQuanMiLCAiZGVza3RvcC9ydW50aW1lLmpzIiwgImRlc2t0b3AvYXBwbGljYXRpb24uanMiLCAiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9zY3JlZW5zLmpzIiwgIm5vZGVfbW9kdWxlcy9uYW5vaWQvbm9uLXNlY3VyZS9pbmRleC5qcyIsICJkZXNrdG9wL2NhbGxzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3AvZXZlbnRzLmpzIiwgImRlc2t0b3AvZGlhbG9ncy5qcyIsICJkZXNrdG9wL2NvbnRleHRtZW51LmpzIiwgImRlc2t0b3Avd21sLmpzIiwgImRlc2t0b3AvbWFpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImNsaXBib2FyZFwiKTtcblxuLyoqXG4gKiBTZXQgdGhlIENsaXBib2FyZCB0ZXh0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTZXRUZXh0KHRleHQpIHtcbiAgICB2b2lkIGNhbGwoXCJTZXRUZXh0XCIsIHt0ZXh0fSk7XG59XG5cbi8qKlxuICogR2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFRleHQoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJUZXh0XCIpO1xufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5jb25zdCBydW50aW1lVVJMID0gd2luZG93LmxvY2F0aW9uLm9yaWdpbiArIFwiL3dhaWxzL3J1bnRpbWVcIjtcblxuZnVuY3Rpb24gcnVudGltZUNhbGwobWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKSB7XG4gICAgbGV0IHVybCA9IG5ldyBVUkwocnVudGltZVVSTCk7XG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kKTtcbiAgICBpZiAoYXJncykge1xuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcImFyZ3NcIiwgSlNPTi5zdHJpbmdpZnkoYXJncykpO1xuICAgIH1cbiAgICBsZXQgZmV0Y2hPcHRpb25zID0ge1xuICAgICAgICBoZWFkZXJzOiB7fSxcbiAgICB9O1xuICAgIGlmICh3aW5kb3dOYW1lKSB7XG4gICAgICAgIGZldGNoT3B0aW9ucy5oZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XG4gICAgfVxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGZldGNoKHVybCwgZmV0Y2hPcHRpb25zKVxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgICAgICAvLyBjaGVjayBjb250ZW50IHR5cGVcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAudGhlbihkYXRhID0+IHJlc29sdmUoZGF0YSkpXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XG4gICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdCwgd2luZG93TmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAobWV0aG9kLCBhcmdzPW51bGwpIHtcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsKG9iamVjdCArIFwiLlwiICsgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcbiAgICB9O1xufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiYXBwbGljYXRpb25cIik7XG5cbi8qKlxuICogSGlkZSB0aGUgYXBwbGljYXRpb25cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEhpZGUoKSB7XG4gICAgdm9pZCBjYWxsKFwiSGlkZVwiKTtcbn1cblxuLyoqXG4gKiBTaG93IHRoZSBhcHBsaWNhdGlvblxuICovXG5leHBvcnQgZnVuY3Rpb24gU2hvdygpIHtcbiAgICB2b2lkIGNhbGwoXCJTaG93XCIpO1xufVxuXG5cbi8qKlxuICogUXVpdCB0aGUgYXBwbGljYXRpb25cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKSB7XG4gICAgdm9pZCBjYWxsKFwiUXVpdFwiKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImxvZ1wiKTtcblxuLyoqXG4gKiBMb2dzIGEgbWVzc2FnZS5cbiAqIEBwYXJhbSB7bWVzc2FnZX0gTWVzc2FnZSB0byBsb2dcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZyhtZXNzYWdlKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJMb2dcIiwgbWVzc2FnZSk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi9hcGkvdHlwZXNcIikuU2NyZWVufSBTY3JlZW5cbiAqL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwic2NyZWVuc1wiKTtcblxuLyoqXG4gKiBHZXRzIGFsbCBzY3JlZW5zLlxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuW10+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0QWxsKCkge1xuICAgIHJldHVybiBjYWxsKFwiR2V0QWxsXCIpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIHByaW1hcnkgc2NyZWVuLlxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldFByaW1hcnkoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJHZXRQcmltYXJ5XCIpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbj59XG4gKiBAY29uc3RydWN0b3JcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEN1cnJlbnQoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJHZXRDdXJyZW50XCIpO1xufSIsICJsZXQgdXJsQWxwaGFiZXQgPVxuICAndXNlYW5kb20tMjZUMTk4MzQwUFg3NXB4SkFDS1ZFUllNSU5EQlVTSFdPTEZfR1FaYmZnaGprbHF2d3l6cmljdCdcbmV4cG9ydCBsZXQgY3VzdG9tQWxwaGFiZXQgPSAoYWxwaGFiZXQsIGRlZmF1bHRTaXplID0gMjEpID0+IHtcbiAgcmV0dXJuIChzaXplID0gZGVmYXVsdFNpemUpID0+IHtcbiAgICBsZXQgaWQgPSAnJ1xuICAgIGxldCBpID0gc2l6ZVxuICAgIHdoaWxlIChpLS0pIHtcbiAgICAgIGlkICs9IGFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogYWxwaGFiZXQubGVuZ3RoKSB8IDBdXG4gICAgfVxuICAgIHJldHVybiBpZFxuICB9XG59XG5leHBvcnQgbGV0IG5hbm9pZCA9IChzaXplID0gMjEpID0+IHtcbiAgbGV0IGlkID0gJydcbiAgbGV0IGkgPSBzaXplXG4gIHdoaWxlIChpLS0pIHtcbiAgICBpZCArPSB1cmxBbHBoYWJldFsoTWF0aC5yYW5kb20oKSAqIDY0KSB8IDBdXG4gIH1cbiAgcmV0dXJuIGlkXG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiY2FsbFwiKTtcblxubGV0IGNhbGxSZXNwb25zZXMgPSBuZXcgTWFwKCk7XG5cbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKSB7XG4gICAgbGV0IHJlc3VsdDtcbiAgICBkbyB7XG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xuICAgIH0gd2hpbGUgKGNhbGxSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxsQ2FsbGJhY2soaWQsIGRhdGEsIGlzSlNPTikge1xuICAgIGxldCBwID0gY2FsbFJlc3BvbnNlcy5nZXQoaWQpO1xuICAgIGlmIChwKSB7XG4gICAgICAgIGlmIChpc0pTT04pIHtcbiAgICAgICAgICAgIHAucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHAucmVzb2x2ZShkYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYWxsUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gY2FsbEVycm9yQ2FsbGJhY2soaWQsIG1lc3NhZ2UpIHtcbiAgICBsZXQgcCA9IGNhbGxSZXNwb25zZXMuZ2V0KGlkKTtcbiAgICBpZiAocCkge1xuICAgICAgICBwLnJlamVjdChtZXNzYWdlKTtcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gY2FsbEJpbmRpbmcodHlwZSwgb3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG4gICAgICAgIG9wdGlvbnNbXCJjYWxsLWlkXCJdID0gaWQ7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuc2V0KGlkLCB7cmVzb2x2ZSwgcmVqZWN0fSk7XG4gICAgICAgIGNhbGwodHlwZSwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgICAgICByZWplY3QoZXJyb3IpO1xuICAgICAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIENhbGwob3B0aW9ucykge1xuICAgIHJldHVybiBjYWxsQmluZGluZyhcIkNhbGxcIiwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogQ2FsbCBhIHBsdWdpbiBtZXRob2RcbiAqIEBwYXJhbSB7c3RyaW5nfSBwbHVnaW5OYW1lIC0gbmFtZSBvZiB0aGUgcGx1Z2luXG4gKiBAcGFyYW0ge3N0cmluZ30gbWV0aG9kTmFtZSAtIG5hbWUgb2YgdGhlIG1ldGhvZFxuICogQHBhcmFtIHsuLi5hbnl9IGFyZ3MgLSBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgbWV0aG9kXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fSAtIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSByZXN1bHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFBsdWdpbihwbHVnaW5OYW1lLCBtZXRob2ROYW1lLCAuLi5hcmdzKSB7XG4gICAgcmV0dXJuIGNhbGxCaW5kaW5nKFwiQ2FsbFwiLCB7XG4gICAgICAgIHBhY2thZ2VOYW1lOiBcIndhaWxzLXBsdWdpbnNcIixcbiAgICAgICAgc3RydWN0TmFtZTogcGx1Z2luTmFtZSxcbiAgICAgICAgbWV0aG9kTmFtZTogbWV0aG9kTmFtZSxcbiAgICAgICAgYXJnczogYXJncyxcbiAgICB9KTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNpemV9IFNpemVcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuLi9hcGkvdHlwZXNcIikuUG9zaXRpb259IFBvc2l0aW9uXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNjcmVlbn0gU2NyZWVuXG4gKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdXaW5kb3cod2luZG93TmFtZSkge1xuICAgIGxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcIndpbmRvd1wiLCB3aW5kb3dOYW1lKTtcbiAgICByZXR1cm4ge1xuICAgICAgICAvLyBSZWxvYWQ6ICgpID0+IGNhbGwoJ1dSJyksXG4gICAgICAgIC8vIFJlbG9hZEFwcDogKCkgPT4gY2FsbCgnV1InKSxcbiAgICAgICAgLy8gU2V0U3lzdGVtRGVmYXVsdFRoZW1lOiAoKSA9PiBjYWxsKCdXQVNEVCcpLFxuICAgICAgICAvLyBTZXRMaWdodFRoZW1lOiAoKSA9PiBjYWxsKCdXQUxUJyksXG4gICAgICAgIC8vIFNldERhcmtUaGVtZTogKCkgPT4gY2FsbCgnV0FEVCcpLFxuICAgICAgICAvLyBJc0Z1bGxzY3JlZW46ICgpID0+IGNhbGwoJ1dJRicpLFxuICAgICAgICAvLyBJc01heGltaXplZDogKCkgPT4gY2FsbCgnV0lNJyksXG4gICAgICAgIC8vIElzTWluaW1pemVkOiAoKSA9PiBjYWxsKCdXSU1OJyksXG4gICAgICAgIC8vIElzV2luZG93ZWQ6ICgpID0+IGNhbGwoJ1dJRicpLFxuXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIENlbnRlcnMgdGhlIHdpbmRvdy5cbiAgICAgICAgICovXG4gICAgICAgIENlbnRlcjogKCkgPT4gdm9pZCBjYWxsKCdDZW50ZXInKSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogU2V0IHRoZSB3aW5kb3cgdGl0bGUuXG4gICAgICAgICAqIEBwYXJhbSB0aXRsZVxuICAgICAgICAgKi9cbiAgICAgICAgU2V0VGl0bGU6ICh0aXRsZSkgPT4gdm9pZCBjYWxsKCdTZXRUaXRsZScsIHt0aXRsZX0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNYWtlcyB0aGUgd2luZG93IGZ1bGxzY3JlZW4uXG4gICAgICAgICAqL1xuICAgICAgICBGdWxsc2NyZWVuOiAoKSA9PiB2b2lkIGNhbGwoJ0Z1bGxzY3JlZW4nKSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogVW5mdWxsc2NyZWVuIHRoZSB3aW5kb3cuXG4gICAgICAgICAqL1xuICAgICAgICBVbkZ1bGxzY3JlZW46ICgpID0+IHZvaWQgY2FsbCgnVW5GdWxsc2NyZWVuJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHNpemUuXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB3aWR0aCBUaGUgd2luZG93IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHQgVGhlIHdpbmRvdyBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiBjYWxsKCdTZXRTaXplJywge3dpZHRoLGhlaWdodH0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBzaXplLlxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxTaXplPn0gVGhlIHdpbmRvdyBzaXplXG4gICAgICAgICAqL1xuICAgICAgICBTaXplOiAoKSA9PiB7IHJldHVybiBjYWxsKCdTaXplJyk7IH0sXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IG1heGltdW0gc2l6ZS5cbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldE1heFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoJ1NldE1heFNpemUnLCB7d2lkdGgsaGVpZ2h0fSksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IG1pbmltdW0gc2l6ZS5cbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldE1pblNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoJ1NldE1pblNpemUnLCB7d2lkdGgsaGVpZ2h0fSksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB3aW5kb3cgdG8gYmUgYWx3YXlzIG9uIHRvcC5cbiAgICAgICAgICogQHBhcmFtIHtib29sZWFufSBvblRvcCBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGFsd2F5cyBvbiB0b3BcbiAgICAgICAgICovXG4gICAgICAgIFNldEFsd2F5c09uVG9wOiAob25Ub3ApID0+IHZvaWQgY2FsbCgnU2V0QWx3YXlzT25Ub3AnLCB7YWx3YXlzT25Ub3A6b25Ub3B9KSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogU2V0IHRoZSB3aW5kb3cgcG9zaXRpb24uXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB4XG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gICAgICAgICAqL1xuICAgICAgICBTZXRQb3NpdGlvbjogKHgsIHkpID0+IGNhbGwoJ1NldFBvc2l0aW9uJywge3gseX0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBwb3NpdGlvbi5cbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8UG9zaXRpb24+fSBUaGUgd2luZG93IHBvc2l0aW9uXG4gICAgICAgICAqL1xuICAgICAgICBQb3NpdGlvbjogKCkgPT4geyByZXR1cm4gY2FsbCgnUG9zaXRpb24nKTsgfSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogR2V0IHRoZSBzY3JlZW4gdGhlIHdpbmRvdyBpcyBvbi5cbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cbiAgICAgICAgICovXG4gICAgICAgIFNjcmVlbjogKCkgPT4geyByZXR1cm4gY2FsbCgnU2NyZWVuJyk7IH0sXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhpZGUgdGhlIHdpbmRvd1xuICAgICAgICAgKi9cbiAgICAgICAgSGlkZTogKCkgPT4gdm9pZCBjYWxsKCdIaWRlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIE1heGltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIE1heGltaXNlOiAoKSA9PiB2b2lkIGNhbGwoJ01heGltaXNlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNob3cgdGhlIHdpbmRvd1xuICAgICAgICAgKi9cbiAgICAgICAgU2hvdzogKCkgPT4gdm9pZCBjYWxsKCdTaG93JyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIENsb3NlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIENsb3NlOiAoKSA9PiB2b2lkIGNhbGwoJ0Nsb3NlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFRvZ2dsZSB0aGUgd2luZG93IG1heGltaXNlIHN0YXRlXG4gICAgICAgICAqL1xuICAgICAgICBUb2dnbGVNYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKCdUb2dnbGVNYXhpbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBVbm1heGltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIFVuTWF4aW1pc2U6ICgpID0+IHZvaWQgY2FsbCgnVW5NYXhpbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNaW5pbWlzZSB0aGUgd2luZG93XG4gICAgICAgICAqL1xuICAgICAgICBNaW5pbWlzZTogKCkgPT4gdm9pZCBjYWxsKCdNaW5pbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBVbm1pbmltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIFVuTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbCgnVW5NaW5pbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBTZXQgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSByIC0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDI1NVxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0gZyAtIEEgdmFsdWUgYmV0d2VlbiAwIGFuZCAyNTVcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBhIC0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDI1NVxuICAgICAgICAgKi9cbiAgICAgICAgU2V0QmFja2dyb3VuZENvbG91cjogKHIsIGcsIGIsIGEpID0+IHZvaWQgY2FsbCgnU2V0QmFja2dyb3VuZENvbG91cicsIHtyLCBnLCBiLCBhfSksXG4gICAgfTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG4vKipcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5XYWlsc0V2ZW50fSBXYWlsc0V2ZW50XG4gKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImV2ZW50c1wiKTtcblxuLyoqXG4gKiBUaGUgTGlzdGVuZXIgY2xhc3MgZGVmaW5lcyBhIGxpc3RlbmVyISA6LSlcbiAqXG4gKiBAY2xhc3MgTGlzdGVuZXJcbiAqL1xuY2xhc3MgTGlzdGVuZXIge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYW4gaW5zdGFuY2Ugb2YgTGlzdGVuZXIuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICAgICAqIEBtZW1iZXJvZiBMaXN0ZW5lclxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xuICAgICAgICB0aGlzLmV2ZW50TmFtZSA9IGV2ZW50TmFtZTtcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyA9IG1heENhbGxiYWNrcyB8fCAtMTtcbiAgICAgICAgLy8gQ2FsbGJhY2sgaW52b2tlcyB0aGUgY2FsbGJhY2sgd2l0aCB0aGUgZ2l2ZW4gZGF0YVxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXG4gICAgICAgIHRoaXMuQ2FsbGJhY2sgPSAoZGF0YSkgPT4ge1xuICAgICAgICAgICAgY2FsbGJhY2soZGF0YSk7XG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXG4gICAgICAgICAgICBpZiAodGhpcy5tYXhDYWxsYmFja3MgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gRGVjcmVtZW50IG1heENhbGxiYWNrcy4gUmV0dXJuIHRydWUgaWYgbm93IDAsIG90aGVyd2lzZSBmYWxzZVxuICAgICAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcbiAgICAgICAgfTtcbiAgICB9XG59XG5cblxuLyoqXG4gKiBXYWlsc0V2ZW50IGRlZmluZXMgYSBjdXN0b20gZXZlbnQuIEl0IGlzIHBhc3NlZCB0byBldmVudCBsaXN0ZW5lcnMuXG4gKlxuICogQGNsYXNzIFdhaWxzRXZlbnRcbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBuYW1lIC0gTmFtZSBvZiB0aGUgZXZlbnRcbiAqIEBwcm9wZXJ0eSB7YW55fSBkYXRhIC0gRGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XG4gKi9cbmV4cG9ydCBjbGFzcyBXYWlsc0V2ZW50IHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGFuIGluc3RhbmNlIG9mIFdhaWxzRXZlbnQuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBldmVudFxuICAgICAqIEBwYXJhbSB7YW55PW51bGx9IGRhdGEgLSBEYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgZXZlbnRcbiAgICAgKiBAbWVtYmVyb2YgV2FpbHNFdmVudFxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG5hbWUsIGRhdGEgPSBudWxsKSB7XG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XG4gICAgICAgIHRoaXMuZGF0YSA9IGRhdGE7XG4gICAgfVxufVxuXG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnMgPSBuZXcgTWFwKCk7XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGBtYXhDYWxsYmFja3NgIHRpbWVzIGJlZm9yZSBiZWluZyBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpIHx8IFtdO1xuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xuICAgIGxpc3RlbmVycy5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50TmFtZSwgbGlzdGVuZXJzKTtcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgZXZlcnkgdGltZSB0aGUgZXZlbnQgaXMgZW1pdHRlZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oV2FpbHNFdmVudCk6IHZvaWR9IGNhbGxiYWNrXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IEEgZnVuY3Rpb24gdG8gY2FuY2VsIHRoZSBsaXN0ZW5lclxuICovXG5leHBvcnQgZnVuY3Rpb24gT24oZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgb25jZSB0aGVuIGRlc3Ryb3llZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oV2FpbHNFdmVudCk6IHZvaWR9IGNhbGxiYWNrXG4gQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uY2UoZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG4vKipcbiAqIGxpc3RlbmVyT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT25cbiAqXG4gKiBAcGFyYW0ge0xpc3RlbmVyfSBsaXN0ZW5lclxuICovXG5mdW5jdGlvbiBsaXN0ZW5lck9mZihsaXN0ZW5lcikge1xuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcbiAgICAvLyBSZW1vdmUgbG9jYWwgbGlzdGVuZXJcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSkuZmlsdGVyKGwgPT4gbCAhPT0gbGlzdGVuZXIpO1xuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XG4gICAgfVxufVxuXG4vKipcbiAqIGRpc3BhdGNoZXMgYW4gZXZlbnQgdG8gYWxsIGxpc3RlbmVyc1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7V2FpbHNFdmVudH0gZXZlbnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRpc3BhdGNoV2FpbHNFdmVudChldmVudCkge1xuICAgIGNvbnNvbGUubG9nKFwiZGlzcGF0Y2hpbmcgZXZlbnQ6IFwiLCB7ZXZlbnR9KTtcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xuICAgIGlmIChsaXN0ZW5lcnMpIHtcbiAgICAgICAgLy8gaXRlcmF0ZSBsaXN0ZW5lcnMgYW5kIGNhbGwgY2FsbGJhY2suIElmIGNhbGxiYWNrIHJldHVybnMgdHJ1ZSwgcmVtb3ZlIGxpc3RlbmVyXG4gICAgICAgIGxldCB0b1JlbW92ZSA9IFtdO1xuICAgICAgICBsaXN0ZW5lcnMuZm9yRWFjaChsaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICBsZXQgcmVtb3ZlID0gbGlzdGVuZXIuQ2FsbGJhY2soZXZlbnQpO1xuICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xuICAgICAgICAgICAgICAgIHRvUmVtb3ZlLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gcmVtb3ZlIGxpc3RlbmVyc1xuICAgICAgICBpZiAodG9SZW1vdmUubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+ICF0b1JlbW92ZS5pbmNsdWRlcyhsKSk7XG4gICAgICAgICAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudC5uYW1lKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50Lm5hbWUsIGxpc3RlbmVycyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5cbi8qKlxuICogT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT24sXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVycyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXG4gKlxuIFt2MyBDSEFOR0VdIE9mZiBvbmx5IHVucmVnaXN0ZXJzIGxpc3RlbmVycyB3aXRoaW4gdGhlIGN1cnJlbnQgd2luZG93XG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICogQHBhcmFtICB7Li4uc3RyaW5nfSBhZGRpdGlvbmFsRXZlbnROYW1lc1xuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmKGV2ZW50TmFtZSwgLi4uYWRkaXRpb25hbEV2ZW50TmFtZXMpIHtcbiAgICBsZXQgZXZlbnRzVG9SZW1vdmUgPSBbZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lc107XG4gICAgZXZlbnRzVG9SZW1vdmUuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBPZmZBbGwgdW5yZWdpc3RlcnMgYWxsIGxpc3RlbmVyc1xuICogW3YzIENIQU5HRV0gT2ZmQWxsIG9ubHkgdW5yZWdpc3RlcnMgbGlzdGVuZXJzIHdpdGhpbiB0aGUgY3VycmVudCB3aW5kb3dcbiAqXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPZmZBbGwoKSB7XG4gICAgZXZlbnRMaXN0ZW5lcnMuY2xlYXIoKTtcbn1cblxuLyoqXG4gKiBFbWl0IGFuIGV2ZW50XG4gKiBAcGFyYW0ge1dhaWxzRXZlbnR9IGV2ZW50IFRoZSBldmVudCB0byBlbWl0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbWl0KGV2ZW50KSB7XG4gICAgdm9pZCBjYWxsKFwiRW1pdFwiLCBldmVudCk7XG59IiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cbi8qKlxuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk1lc3NhZ2VEaWFsb2dPcHRpb25zfSBNZXNzYWdlRGlhbG9nT3B0aW9uc1xuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk9wZW5EaWFsb2dPcHRpb25zfSBPcGVuRGlhbG9nT3B0aW9uc1xuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLlNhdmVEaWFsb2dPcHRpb25zfSBTYXZlRGlhbG9nT3B0aW9uc1xuICovXG5cbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcn0gZnJvbSBcIi4vcnVudGltZVwiO1xuXG5pbXBvcnQgeyBuYW5vaWQgfSBmcm9tICduYW5vaWQvbm9uLXNlY3VyZSc7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImRpYWxvZ1wiKTtcblxubGV0IGRpYWxvZ1Jlc3BvbnNlcyA9IG5ldyBNYXAoKTtcblxuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGRvIHtcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XG4gICAgfSB3aGlsZSAoZGlhbG9nUmVzcG9uc2VzLmhhcyhyZXN1bHQpKTtcbiAgICByZXR1cm4gcmVzdWx0O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZGlhbG9nQ2FsbGJhY2soaWQsIGRhdGEsIGlzSlNPTikge1xuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XG4gICAgaWYgKHApIHtcbiAgICAgICAgaWYgKGlzSlNPTikge1xuICAgICAgICAgICAgcC5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcC5yZXNvbHZlKGRhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIH1cbn1cbmV4cG9ydCBmdW5jdGlvbiBkaWFsb2dFcnJvckNhbGxiYWNrKGlkLCBtZXNzYWdlKSB7XG4gICAgbGV0IHAgPSBkaWFsb2dSZXNwb25zZXMuZ2V0KGlkKTtcbiAgICBpZiAocCkge1xuICAgICAgICBwLnJlamVjdChtZXNzYWdlKTtcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBkaWFsb2codHlwZSwgb3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG4gICAgICAgIG9wdGlvbnNbXCJkaWFsb2ctaWRcIl0gPSBpZDtcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLnNldChpZCwge3Jlc29sdmUsIHJlamVjdH0pO1xuICAgICAgICBjYWxsKHR5cGUsIG9wdGlvbnMpLmNhdGNoKChlcnJvcikgPT4ge1xuICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuXG4vKipcbiAqIFNob3dzIGFuIEluZm8gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJbmZvKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gZGlhbG9nKFwiSW5mb1wiLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTaG93cyBhbiBXYXJuaW5nIGRpYWxvZyB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIldhcm5pbmdcIiwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogU2hvd3MgYW4gRXJyb3IgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFcnJvcihvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIkVycm9yXCIsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNob3dzIGEgUXVlc3Rpb24gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBRdWVzdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIlF1ZXN0aW9uXCIsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNob3dzIGFuIE9wZW4gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmdbXXxzdHJpbmc+fSBSZXR1cm5zIHRoZSBzZWxlY3RlZCBmaWxlIG9yIGFuIGFycmF5IG9mIHNlbGVjdGVkIGZpbGVzIGlmIEFsbG93c011bHRpcGxlU2VsZWN0aW9uIGlzIHRydWUuIEEgYmxhbmsgc3RyaW5nIGlzIHJldHVybmVkIGlmIG5vIGZpbGUgd2FzIHNlbGVjdGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9ucykge1xuICAgIHJldHVybiBkaWFsb2coXCJPcGVuRmlsZVwiLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTaG93cyBhIFNhdmUgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBSZXR1cm5zIHRoZSBzZWxlY3RlZCBmaWxlLiBBIGJsYW5rIHN0cmluZyBpcyByZXR1cm5lZCBpZiBubyBmaWxlIHdhcyBzZWxlY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNhdmVGaWxlKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gZGlhbG9nKFwiU2F2ZUZpbGVcIiwgb3B0aW9ucyk7XG59XG5cbiIsICJpbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiY29udGV4dG1lbnVcIik7XG5cbmZ1bmN0aW9uIG9wZW5Db250ZXh0TWVudShpZCwgeCwgeSwgZGF0YSkge1xuICAgIHJldHVybiBjYWxsKFwiT3BlbkNvbnRleHRNZW51XCIsIHtpZCwgeCwgeSwgZGF0YX0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZW5hYmxlQ29udGV4dE1lbnVzKGVuYWJsZWQpIHtcbiAgICBpZiAoZW5hYmxlZCkge1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdjb250ZXh0bWVudScsIGNvbnRleHRNZW51SGFuZGxlcik7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQpIHtcbiAgICBwcm9jZXNzQ29udGV4dE1lbnUoZXZlbnQudGFyZ2V0LCBldmVudCk7XG59XG5cbmZ1bmN0aW9uIHByb2Nlc3NDb250ZXh0TWVudShlbGVtZW50LCBldmVudCkge1xuICAgIGxldCBpZCA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLWNvbnRleHRtZW51Jyk7XG4gICAgaWYgKGlkKSB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIG9wZW5Db250ZXh0TWVudShpZCwgZXZlbnQuY2xpZW50WCwgZXZlbnQuY2xpZW50WSwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtY29udGV4dG1lbnUtZGF0YScpKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBsZXQgcGFyZW50ID0gZWxlbWVudC5wYXJlbnRFbGVtZW50O1xuICAgICAgICBpZiAocGFyZW50KSB7XG4gICAgICAgICAgICBwcm9jZXNzQ29udGV4dE1lbnUocGFyZW50LCBldmVudCk7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiXG5pbXBvcnQge0VtaXQsIFdhaWxzRXZlbnR9IGZyb20gXCIuL2V2ZW50c1wiO1xuaW1wb3J0IHtRdWVzdGlvbn0gZnJvbSBcIi4vZGlhbG9nc1wiO1xuXG5mdW5jdGlvbiBzZW5kRXZlbnQoZXZlbnROYW1lLCBkYXRhPW51bGwpIHtcbiAgICBsZXQgZXZlbnQgPSBuZXcgV2FpbHNFdmVudChldmVudE5hbWUsIGRhdGEpO1xuICAgIEVtaXQoZXZlbnQpO1xufVxuXG5mdW5jdGlvbiBhZGRXTUxFdmVudExpc3RlbmVycygpIHtcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1tkYXRhLXdtbC1ldmVudF0nKTtcbiAgICBlbGVtZW50cy5mb3JFYWNoKGZ1bmN0aW9uIChlbGVtZW50KSB7XG4gICAgICAgIGNvbnN0IGV2ZW50VHlwZSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1ldmVudCcpO1xuICAgICAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWNvbmZpcm0nKTtcbiAgICAgICAgY29uc3QgdHJpZ2dlciA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiO1xuXG4gICAgICAgIGxldCBjYWxsYmFjayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGlmIChjb25maXJtKSB7XG4gICAgICAgICAgICAgICAgUXVlc3Rpb24oe1RpdGxlOiBcIkNvbmZpcm1cIiwgTWVzc2FnZTpjb25maXJtLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xuICAgICAgICAgICAgICAgICAgICBpZiAocmVzdWx0ICE9PSBcIk5vXCIpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbmRFdmVudChldmVudFR5cGUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2VuZEV2ZW50KGV2ZW50VHlwZSk7XG4gICAgICAgIH07XG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcblxuICAgICAgICBlbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xuXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcbiAgICAgICAgZWxlbWVudC5hZGRFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcbiAgICB9KTtcbn1cblxuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZChtZXRob2QpIHtcbiAgICBpZiAod2FpbHMuV2luZG93W21ldGhvZF0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICBjb25zb2xlLmxvZyhcIldpbmRvdyBtZXRob2QgXCIgKyBtZXRob2QgKyBcIiBub3QgZm91bmRcIik7XG4gICAgfVxuICAgIHdhaWxzLldpbmRvd1ttZXRob2RdKCk7XG59XG5cbmZ1bmN0aW9uIGFkZFdNTFdpbmRvd0xpc3RlbmVycygpIHtcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1tkYXRhLXdtbC13aW5kb3ddJyk7XG4gICAgZWxlbWVudHMuZm9yRWFjaChmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtd2luZG93Jyk7XG4gICAgICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtY29uZmlybScpO1xuICAgICAgICBjb25zdCB0cmlnZ2VyID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCI7XG5cbiAgICAgICAgbGV0IGNhbGxiYWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgaWYgKGNvbmZpcm0pIHtcbiAgICAgICAgICAgICAgICBRdWVzdGlvbih7VGl0bGU6IFwiQ29uZmlybVwiLCBNZXNzYWdlOmNvbmZpcm0sIEJ1dHRvbnM6W3tMYWJlbDpcIlllc1wifSx7TGFiZWw6XCJOb1wiLCBJc0RlZmF1bHQ6dHJ1ZX1dfSkudGhlbihmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xuICAgICAgICAgICAgICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dNZXRob2QpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dNZXRob2QpO1xuICAgICAgICB9O1xuXG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcbiAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcblxuICAgICAgICAvLyBBZGQgbmV3IGxpc3RlbmVyXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XG4gICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZWxvYWRXTUwoKSB7XG4gICAgYWRkV01MRXZlbnRMaXN0ZW5lcnMoKTtcbiAgICBhZGRXTUxXaW5kb3dMaXN0ZW5lcnMoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuXG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSAnLi9jbGlwYm9hcmQnO1xuaW1wb3J0ICogYXMgQXBwbGljYXRpb24gZnJvbSAnLi9hcHBsaWNhdGlvbic7XG5pbXBvcnQgKiBhcyBMb2cgZnJvbSAnLi9sb2cnO1xuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tICcuL3NjcmVlbnMnO1xuaW1wb3J0IHtQbHVnaW4sIENhbGwsIGNhbGxFcnJvckNhbGxiYWNrLCBjYWxsQ2FsbGJhY2t9IGZyb20gXCIuL2NhbGxzXCI7XG5pbXBvcnQge25ld1dpbmRvd30gZnJvbSBcIi4vd2luZG93XCI7XG5pbXBvcnQge2Rpc3BhdGNoV2FpbHNFdmVudCwgRW1pdCwgT2ZmLCBPZmZBbGwsIE9uLCBPbmNlLCBPbk11bHRpcGxlfSBmcm9tIFwiLi9ldmVudHNcIjtcbmltcG9ydCB7ZGlhbG9nQ2FsbGJhY2ssIGRpYWxvZ0Vycm9yQ2FsbGJhY2ssIEVycm9yLCBJbmZvLCBPcGVuRmlsZSwgUXVlc3Rpb24sIFNhdmVGaWxlLCBXYXJuaW5nLH0gZnJvbSBcIi4vZGlhbG9nc1wiO1xuaW1wb3J0IHtlbmFibGVDb250ZXh0TWVudXN9IGZyb20gXCIuL2NvbnRleHRtZW51XCI7XG5pbXBvcnQge3JlbG9hZFdNTH0gZnJvbSBcIi4vd21sXCI7XG5cbndpbmRvdy53YWlscyA9IHtcbiAgICAuLi5uZXdSdW50aW1lKG51bGwpLFxufTtcblxuLy8gSW50ZXJuYWwgd2FpbHMgZW5kcG9pbnRzXG53aW5kb3cuX3dhaWxzID0ge1xuICAgIGRpYWxvZ0NhbGxiYWNrLFxuICAgIGRpYWxvZ0Vycm9yQ2FsbGJhY2ssXG4gICAgZGlzcGF0Y2hXYWlsc0V2ZW50LFxuICAgIGNhbGxDYWxsYmFjayxcbiAgICBjYWxsRXJyb3JDYWxsYmFjayxcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lKHdpbmRvd05hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBDbGlwYm9hcmQ6IHtcbiAgICAgICAgICAgIC4uLkNsaXBib2FyZFxuICAgICAgICB9LFxuICAgICAgICBBcHBsaWNhdGlvbjoge1xuICAgICAgICAgICAgLi4uQXBwbGljYXRpb24sXG4gICAgICAgICAgICBHZXRXaW5kb3dCeU5hbWUod2luZG93TmFtZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBuZXdSdW50aW1lKHdpbmRvd05hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBMb2csXG4gICAgICAgIFNjcmVlbnMsXG4gICAgICAgIENhbGwsXG4gICAgICAgIFBsdWdpbixcbiAgICAgICAgV01MOiB7XG4gICAgICAgICAgICBSZWxvYWQ6IHJlbG9hZFdNTCxcbiAgICAgICAgfSxcbiAgICAgICAgRGlhbG9nOiB7XG4gICAgICAgICAgICBJbmZvLFxuICAgICAgICAgICAgV2FybmluZyxcbiAgICAgICAgICAgIEVycm9yLFxuICAgICAgICAgICAgUXVlc3Rpb24sXG4gICAgICAgICAgICBPcGVuRmlsZSxcbiAgICAgICAgICAgIFNhdmVGaWxlLFxuICAgICAgICB9LFxuICAgICAgICBFdmVudHM6IHtcbiAgICAgICAgICAgIEVtaXQsXG4gICAgICAgICAgICBPbixcbiAgICAgICAgICAgIE9uY2UsXG4gICAgICAgICAgICBPbk11bHRpcGxlLFxuICAgICAgICAgICAgT2ZmLFxuICAgICAgICAgICAgT2ZmQWxsLFxuICAgICAgICB9LFxuICAgICAgICBXaW5kb3c6IG5ld1dpbmRvdyh3aW5kb3dOYW1lKSxcbiAgICB9O1xufVxuXG5pZiAoREVCVUcpIHtcbiAgICBjb25zb2xlLmxvZyhcIldhaWxzIHYzLjAuMCBEZWJ1ZyBNb2RlIEVuYWJsZWRcIik7XG59XG5cbmVuYWJsZUNvbnRleHRNZW51cyh0cnVlKTtcblxuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihcIkRPTUNvbnRlbnRMb2FkZWRcIiwgZnVuY3Rpb24oZXZlbnQpIHtcbiAgICByZWxvYWRXTUwoKTtcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTs7O0FDWUEsTUFBTSxhQUFhLE9BQU8sU0FBUyxTQUFTO0FBRTVDLFdBQVMsWUFBWSxRQUFRLFlBQVksTUFBTTtBQUMzQyxRQUFJLE1BQU0sSUFBSSxJQUFJLFVBQVU7QUFDNUIsUUFBSSxhQUFhLE9BQU8sVUFBVSxNQUFNO0FBQ3hDLFFBQUksTUFBTTtBQUNOLFVBQUksYUFBYSxPQUFPLFFBQVEsS0FBSyxVQUFVLElBQUksQ0FBQztBQUFBLElBQ3hEO0FBQ0EsUUFBSSxlQUFlO0FBQUEsTUFDZixTQUFTLENBQUM7QUFBQSxJQUNkO0FBQ0EsUUFBSSxZQUFZO0FBQ1osbUJBQWEsUUFBUSxxQkFBcUIsSUFBSTtBQUFBLElBQ2xEO0FBQ0EsV0FBTyxJQUFJLFFBQVEsQ0FBQyxTQUFTLFdBQVc7QUFDcEMsWUFBTSxLQUFLLFlBQVksRUFDbEIsS0FBSyxjQUFZO0FBQ2QsWUFBSSxTQUFTLElBQUk7QUFFYixjQUFJLFNBQVMsUUFBUSxJQUFJLGNBQWMsS0FBSyxTQUFTLFFBQVEsSUFBSSxjQUFjLEVBQUUsUUFBUSxrQkFBa0IsTUFBTSxJQUFJO0FBQ2pILG1CQUFPLFNBQVMsS0FBSztBQUFBLFVBQ3pCLE9BQU87QUFDSCxtQkFBTyxTQUFTLEtBQUs7QUFBQSxVQUN6QjtBQUFBLFFBQ0o7QUFDQSxlQUFPLE1BQU0sU0FBUyxVQUFVLENBQUM7QUFBQSxNQUNyQyxDQUFDLEVBQ0EsS0FBSyxVQUFRLFFBQVEsSUFBSSxDQUFDLEVBQzFCLE1BQU0sV0FBUyxPQUFPLEtBQUssQ0FBQztBQUFBLElBQ3JDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxpQkFBaUIsUUFBUSxZQUFZO0FBQ2pELFdBQU8sU0FBVSxRQUFRLE9BQUssTUFBTTtBQUNoQyxhQUFPLFlBQVksU0FBUyxNQUFNLFFBQVEsWUFBWSxJQUFJO0FBQUEsSUFDOUQ7QUFBQSxFQUNKOzs7QURsQ0EsTUFBSSxPQUFPLGlCQUFpQixXQUFXO0FBS2hDLFdBQVMsUUFBUSxNQUFNO0FBQzFCLFNBQUssS0FBSyxXQUFXLEVBQUMsS0FBSSxDQUFDO0FBQUEsRUFDL0I7QUFNTyxXQUFTLE9BQU87QUFDbkIsV0FBTyxLQUFLLE1BQU07QUFBQSxFQUN0Qjs7O0FFN0JBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWNBLE1BQUlBLFFBQU8saUJBQWlCLGFBQWE7QUFLbEMsV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCO0FBS08sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCO0FBTU8sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCOzs7QUNwQ0E7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQyxRQUFPLGlCQUFpQixLQUFLO0FBTTFCLFdBQVMsSUFBSSxTQUFTO0FBQ3pCLFdBQU9BLE1BQUssT0FBTyxPQUFPO0FBQUEsRUFDOUI7OztBQ3RCQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsTUFBSUMsUUFBTyxpQkFBaUIsU0FBUztBQU05QixXQUFTLFNBQVM7QUFDckIsV0FBT0EsTUFBSyxRQUFRO0FBQUEsRUFDeEI7QUFNTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxZQUFZO0FBQUEsRUFDNUI7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxZQUFZO0FBQUEsRUFDNUI7OztBQzNDQSxNQUFJLGNBQ0Y7QUFXSyxNQUFJLFNBQVMsQ0FBQyxPQUFPLE9BQU87QUFDakMsUUFBSSxLQUFLO0FBQ1QsUUFBSSxJQUFJO0FBQ1IsV0FBTyxLQUFLO0FBQ1YsWUFBTSxZQUFhLEtBQUssT0FBTyxJQUFJLEtBQU0sQ0FBQztBQUFBLElBQzVDO0FBQ0EsV0FBTztBQUFBLEVBQ1Q7OztBQ0hBLE1BQUlDLFFBQU8saUJBQWlCLE1BQU07QUFFbEMsTUFBSSxnQkFBZ0Isb0JBQUksSUFBSTtBQUU1QixXQUFTLGFBQWE7QUFDbEIsUUFBSTtBQUNKLE9BQUc7QUFDQyxlQUFTLE9BQU87QUFBQSxJQUNwQixTQUFTLGNBQWMsSUFBSSxNQUFNO0FBQ2pDLFdBQU87QUFBQSxFQUNYO0FBRU8sV0FBUyxhQUFhLElBQUksTUFBTSxRQUFRO0FBQzNDLFFBQUksSUFBSSxjQUFjLElBQUksRUFBRTtBQUM1QixRQUFJLEdBQUc7QUFDSCxVQUFJLFFBQVE7QUFDUixVQUFFLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLE1BQzlCLE9BQU87QUFDSCxVQUFFLFFBQVEsSUFBSTtBQUFBLE1BQ2xCO0FBQ0Esb0JBQWMsT0FBTyxFQUFFO0FBQUEsSUFDM0I7QUFBQSxFQUNKO0FBRU8sV0FBUyxrQkFBa0IsSUFBSSxTQUFTO0FBQzNDLFFBQUksSUFBSSxjQUFjLElBQUksRUFBRTtBQUM1QixRQUFJLEdBQUc7QUFDSCxRQUFFLE9BQU8sT0FBTztBQUNoQixvQkFBYyxPQUFPLEVBQUU7QUFBQSxJQUMzQjtBQUFBLEVBQ0o7QUFFQSxXQUFTLFlBQVksTUFBTSxTQUFTO0FBQ2hDLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFVBQUksS0FBSyxXQUFXO0FBQ3BCLGdCQUFVLFdBQVcsQ0FBQztBQUN0QixjQUFRLFNBQVMsSUFBSTtBQUNyQixvQkFBYyxJQUFJLElBQUksRUFBQyxTQUFTLE9BQU0sQ0FBQztBQUN2QyxNQUFBQSxNQUFLLE1BQU0sT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVO0FBQ2pDLGVBQU8sS0FBSztBQUNaLHNCQUFjLE9BQU8sRUFBRTtBQUFBLE1BQzNCLENBQUM7QUFBQSxJQUNMLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxLQUFLLFNBQVM7QUFDMUIsV0FBTyxZQUFZLFFBQVEsT0FBTztBQUFBLEVBQ3RDO0FBU08sV0FBUyxPQUFPLFlBQVksZUFBZSxNQUFNO0FBQ3BELFdBQU8sWUFBWSxRQUFRO0FBQUEsTUFDdkIsYUFBYTtBQUFBLE1BQ2IsWUFBWTtBQUFBLE1BQ1o7QUFBQSxNQUNBO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDs7O0FDM0RPLFdBQVMsVUFBVSxZQUFZO0FBQ2xDLFFBQUlDLFFBQU8saUJBQWlCLFVBQVUsVUFBVTtBQUNoRCxXQUFPO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFlSCxRQUFRLE1BQU0sS0FBS0EsTUFBSyxRQUFRO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU1oQyxVQUFVLENBQUMsVUFBVSxLQUFLQSxNQUFLLFlBQVksRUFBQyxNQUFLLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtsRCxZQUFZLE1BQU0sS0FBS0EsTUFBSyxZQUFZO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLeEMsY0FBYyxNQUFNLEtBQUtBLE1BQUssY0FBYztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU81QyxTQUFTLENBQUMsT0FBTyxXQUFXQSxNQUFLLFdBQVcsRUFBQyxPQUFNLE9BQU0sQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNMUQsTUFBTSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxNQUFNO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU9uQyxZQUFZLENBQUMsT0FBTyxXQUFXLEtBQUtBLE1BQUssY0FBYyxFQUFDLE9BQU0sT0FBTSxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT3JFLFlBQVksQ0FBQyxPQUFPLFdBQVcsS0FBS0EsTUFBSyxjQUFjLEVBQUMsT0FBTSxPQUFNLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXJFLGdCQUFnQixDQUFDLFVBQVUsS0FBS0EsTUFBSyxrQkFBa0IsRUFBQyxhQUFZLE1BQUssQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU8xRSxhQUFhLENBQUMsR0FBRyxNQUFNQSxNQUFLLGVBQWUsRUFBQyxHQUFFLEVBQUMsQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNaEQsVUFBVSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxVQUFVO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNM0MsUUFBUSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxRQUFRO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS3ZDLE1BQU0sTUFBTSxLQUFLQSxNQUFLLE1BQU07QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUs1QixVQUFVLE1BQU0sS0FBS0EsTUFBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEMsTUFBTSxNQUFNLEtBQUtBLE1BQUssTUFBTTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzVCLE9BQU8sTUFBTSxLQUFLQSxNQUFLLE9BQU87QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUs5QixnQkFBZ0IsTUFBTSxLQUFLQSxNQUFLLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2hELFlBQVksTUFBTSxLQUFLQSxNQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUt4QyxVQUFVLE1BQU0sS0FBS0EsTUFBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEMsWUFBWSxNQUFNLEtBQUtBLE1BQUssWUFBWTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFTeEMscUJBQXFCLENBQUMsR0FBRyxHQUFHLEdBQUcsTUFBTSxLQUFLQSxNQUFLLHVCQUF1QixFQUFDLEdBQUcsR0FBRyxHQUFHLEVBQUMsQ0FBQztBQUFBLElBQ3RGO0FBQUEsRUFDSjs7O0FDMUlBLE1BQUlDLFFBQU8saUJBQWlCLFFBQVE7QUFPcEMsTUFBTSxXQUFOLE1BQWU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLElBUVgsWUFBWSxXQUFXLFVBQVUsY0FBYztBQUMzQyxXQUFLLFlBQVk7QUFFakIsV0FBSyxlQUFlLGdCQUFnQjtBQUdwQyxXQUFLLFdBQVcsQ0FBQyxTQUFTO0FBQ3RCLGlCQUFTLElBQUk7QUFFYixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFVTyxNQUFNLGFBQU4sTUFBaUI7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQU9wQixZQUFZLE1BQU0sT0FBTyxNQUFNO0FBQzNCLFdBQUssT0FBTztBQUNaLFdBQUssT0FBTztBQUFBLElBQ2hCO0FBQUEsRUFDSjtBQUVPLE1BQU0saUJBQWlCLG9CQUFJLElBQUk7QUFXL0IsV0FBUyxXQUFXLFdBQVcsVUFBVSxjQUFjO0FBQzFELFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsVUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxjQUFVLEtBQUssWUFBWTtBQUMzQixtQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLEdBQUcsV0FBVyxVQUFVO0FBQ3BDLFdBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQzdDO0FBVU8sV0FBUyxLQUFLLFdBQVcsVUFBVTtBQUN0QyxXQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFBQSxFQUM1QztBQU9BLFdBQVMsWUFBWSxVQUFVO0FBQzNCLFVBQU0sWUFBWSxTQUFTO0FBRTNCLFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDeEUsUUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixxQkFBZSxPQUFPLFNBQVM7QUFBQSxJQUNuQyxPQUFPO0FBQ0gscUJBQWUsSUFBSSxXQUFXLFNBQVM7QUFBQSxJQUMzQztBQUFBLEVBQ0o7QUFRTyxXQUFTLG1CQUFtQixPQUFPO0FBQ3RDLFlBQVEsSUFBSSx1QkFBdUIsRUFBQyxNQUFLLENBQUM7QUFDMUMsUUFBSSxZQUFZLGVBQWUsSUFBSSxNQUFNLElBQUk7QUFDN0MsUUFBSSxXQUFXO0FBRVgsVUFBSSxXQUFXLENBQUM7QUFDaEIsZ0JBQVUsUUFBUSxjQUFZO0FBQzFCLFlBQUksU0FBUyxTQUFTLFNBQVMsS0FBSztBQUNwQyxZQUFJLFFBQVE7QUFDUixtQkFBUyxLQUFLLFFBQVE7QUFBQSxRQUMxQjtBQUFBLE1BQ0osQ0FBQztBQUVELFVBQUksU0FBUyxTQUFTLEdBQUc7QUFDckIsb0JBQVksVUFBVSxPQUFPLE9BQUssQ0FBQyxTQUFTLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZELFlBQUksVUFBVSxXQUFXLEdBQUc7QUFDeEIseUJBQWUsT0FBTyxNQUFNLElBQUk7QUFBQSxRQUNwQyxPQUFPO0FBQ0gseUJBQWUsSUFBSSxNQUFNLE1BQU0sU0FBUztBQUFBLFFBQzVDO0FBQUEsTUFDSjtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBV08sV0FBUyxJQUFJLGNBQWMsc0JBQXNCO0FBQ3BELFFBQUksaUJBQWlCLENBQUMsV0FBVyxHQUFHLG9CQUFvQjtBQUN4RCxtQkFBZSxRQUFRLENBQUFDLGVBQWE7QUFDaEMscUJBQWUsT0FBT0EsVUFBUztBQUFBLElBQ25DLENBQUM7QUFBQSxFQUNMO0FBT08sV0FBUyxTQUFTO0FBQ3JCLG1CQUFlLE1BQU07QUFBQSxFQUN6QjtBQU1PLFdBQVMsS0FBSyxPQUFPO0FBQ3hCLFNBQUtELE1BQUssUUFBUSxLQUFLO0FBQUEsRUFDM0I7OztBQzNLQSxNQUFJRSxRQUFPLGlCQUFpQixRQUFRO0FBRXBDLE1BQUksa0JBQWtCLG9CQUFJLElBQUk7QUFFOUIsV0FBU0MsY0FBYTtBQUNsQixRQUFJO0FBQ0osT0FBRztBQUNDLGVBQVMsT0FBTztBQUFBLElBQ3BCLFNBQVMsZ0JBQWdCLElBQUksTUFBTTtBQUNuQyxXQUFPO0FBQUEsRUFDWDtBQUVPLFdBQVMsZUFBZSxJQUFJLE1BQU0sUUFBUTtBQUM3QyxRQUFJLElBQUksZ0JBQWdCLElBQUksRUFBRTtBQUM5QixRQUFJLEdBQUc7QUFDSCxVQUFJLFFBQVE7QUFDUixVQUFFLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLE1BQzlCLE9BQU87QUFDSCxVQUFFLFFBQVEsSUFBSTtBQUFBLE1BQ2xCO0FBQ0Esc0JBQWdCLE9BQU8sRUFBRTtBQUFBLElBQzdCO0FBQUEsRUFDSjtBQUNPLFdBQVMsb0JBQW9CLElBQUksU0FBUztBQUM3QyxRQUFJLElBQUksZ0JBQWdCLElBQUksRUFBRTtBQUM5QixRQUFJLEdBQUc7QUFDSCxRQUFFLE9BQU8sT0FBTztBQUNoQixzQkFBZ0IsT0FBTyxFQUFFO0FBQUEsSUFDN0I7QUFBQSxFQUNKO0FBRUEsV0FBUyxPQUFPLE1BQU0sU0FBUztBQUMzQixXQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxVQUFJLEtBQUtBLFlBQVc7QUFDcEIsZ0JBQVUsV0FBVyxDQUFDO0FBQ3RCLGNBQVEsV0FBVyxJQUFJO0FBQ3ZCLHNCQUFnQixJQUFJLElBQUksRUFBQyxTQUFTLE9BQU0sQ0FBQztBQUN6QyxNQUFBRCxNQUFLLE1BQU0sT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVO0FBQ2pDLGVBQU8sS0FBSztBQUNaLHdCQUFnQixPQUFPLEVBQUU7QUFBQSxNQUM3QixDQUFDO0FBQUEsSUFDTCxDQUFDO0FBQUEsRUFDTDtBQVFPLFdBQVMsS0FBSyxTQUFTO0FBQzFCLFdBQU8sT0FBTyxRQUFRLE9BQU87QUFBQSxFQUNqQztBQU9PLFdBQVMsUUFBUSxTQUFTO0FBQzdCLFdBQU8sT0FBTyxXQUFXLE9BQU87QUFBQSxFQUNwQztBQU9PLFdBQVNFLE9BQU0sU0FBUztBQUMzQixXQUFPLE9BQU8sU0FBUyxPQUFPO0FBQUEsRUFDbEM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7OztBQ3JIQSxNQUFJQyxRQUFPLGlCQUFpQixhQUFhO0FBRXpDLFdBQVMsZ0JBQWdCLElBQUksR0FBRyxHQUFHLE1BQU07QUFDckMsV0FBT0EsTUFBSyxtQkFBbUIsRUFBQyxJQUFJLEdBQUcsR0FBRyxLQUFJLENBQUM7QUFBQSxFQUNuRDtBQUVPLFdBQVMsbUJBQW1CLFNBQVM7QUFDeEMsUUFBSSxTQUFTO0FBQ1QsYUFBTyxpQkFBaUIsZUFBZSxrQkFBa0I7QUFBQSxJQUM3RCxPQUFPO0FBQ0gsYUFBTyxvQkFBb0IsZUFBZSxrQkFBa0I7QUFBQSxJQUNoRTtBQUFBLEVBQ0o7QUFFQSxXQUFTLG1CQUFtQixPQUFPO0FBQy9CLHVCQUFtQixNQUFNLFFBQVEsS0FBSztBQUFBLEVBQzFDO0FBRUEsV0FBUyxtQkFBbUIsU0FBUyxPQUFPO0FBQ3hDLFFBQUksS0FBSyxRQUFRLGFBQWEsa0JBQWtCO0FBQ2hELFFBQUksSUFBSTtBQUNKLFlBQU0sZUFBZTtBQUNyQixzQkFBZ0IsSUFBSSxNQUFNLFNBQVMsTUFBTSxTQUFTLFFBQVEsYUFBYSx1QkFBdUIsQ0FBQztBQUFBLElBQ25HLE9BQU87QUFDSCxVQUFJLFNBQVMsUUFBUTtBQUNyQixVQUFJLFFBQVE7QUFDUiwyQkFBbUIsUUFBUSxLQUFLO0FBQUEsTUFDcEM7QUFBQSxJQUNKO0FBQUEsRUFDSjs7O0FDM0JBLFdBQVMsVUFBVSxXQUFXLE9BQUssTUFBTTtBQUNyQyxRQUFJLFFBQVEsSUFBSSxXQUFXLFdBQVcsSUFBSTtBQUMxQyxTQUFLLEtBQUs7QUFBQSxFQUNkO0FBRUEsV0FBUyx1QkFBdUI7QUFDNUIsVUFBTSxXQUFXLFNBQVMsaUJBQWlCLGtCQUFrQjtBQUM3RCxhQUFTLFFBQVEsU0FBVSxTQUFTO0FBQ2hDLFlBQU0sWUFBWSxRQUFRLGFBQWEsZ0JBQWdCO0FBQ3ZELFlBQU0sVUFBVSxRQUFRLGFBQWEsa0JBQWtCO0FBQ3ZELFlBQU0sVUFBVSxRQUFRLGFBQWEsa0JBQWtCLEtBQUs7QUFFNUQsVUFBSSxXQUFXLFdBQVk7QUFDdkIsWUFBSSxTQUFTO0FBQ1QsbUJBQVMsRUFBQyxPQUFPLFdBQVcsU0FBUSxTQUFTLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3ZILGdCQUFJLFdBQVcsTUFBTTtBQUNqQix3QkFBVSxTQUFTO0FBQUEsWUFDdkI7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSxrQkFBVSxTQUFTO0FBQUEsTUFDdkI7QUFHQSxjQUFRLG9CQUFvQixTQUFTLFFBQVE7QUFHN0MsY0FBUSxpQkFBaUIsU0FBUyxRQUFRO0FBQUEsSUFDOUMsQ0FBQztBQUFBLEVBQ0w7QUFFQSxXQUFTLGlCQUFpQixRQUFRO0FBQzlCLFFBQUksTUFBTSxPQUFPLE1BQU0sTUFBTSxRQUFXO0FBQ3BDLGNBQVEsSUFBSSxtQkFBbUIsU0FBUyxZQUFZO0FBQUEsSUFDeEQ7QUFDQSxVQUFNLE9BQU8sTUFBTSxFQUFFO0FBQUEsRUFDekI7QUFFQSxXQUFTLHdCQUF3QjtBQUM3QixVQUFNLFdBQVcsU0FBUyxpQkFBaUIsbUJBQW1CO0FBQzlELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxlQUFlLFFBQVEsYUFBYSxpQkFBaUI7QUFDM0QsWUFBTSxVQUFVLFFBQVEsYUFBYSxrQkFBa0I7QUFDdkQsWUFBTSxVQUFVLFFBQVEsYUFBYSxrQkFBa0IsS0FBSztBQUU1RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsU0FBUSxDQUFDLEVBQUMsT0FBTSxNQUFLLEdBQUUsRUFBQyxPQUFNLE1BQU0sV0FBVSxLQUFJLENBQUMsRUFBQyxDQUFDLEVBQUUsS0FBSyxTQUFVLFFBQVE7QUFDdkgsZ0JBQUksV0FBVyxNQUFNO0FBQ2pCLCtCQUFpQixZQUFZO0FBQUEsWUFDakM7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSx5QkFBaUIsWUFBWTtBQUFBLE1BQ2pDO0FBR0EsY0FBUSxvQkFBb0IsU0FBUyxRQUFRO0FBRzdDLGNBQVEsaUJBQWlCLFNBQVMsUUFBUTtBQUFBLElBQzlDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLHlCQUFxQjtBQUNyQiwwQkFBc0I7QUFBQSxFQUMxQjs7O0FDbERBLFNBQU8sUUFBUTtBQUFBLElBQ1gsR0FBRyxXQUFXLElBQUk7QUFBQSxFQUN0QjtBQUdBLFNBQU8sU0FBUztBQUFBLElBQ1o7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsRUFDSjtBQUVPLFdBQVMsV0FBVyxZQUFZO0FBQ25DLFdBQU87QUFBQSxNQUNILFdBQVc7QUFBQSxRQUNQLEdBQUc7QUFBQSxNQUNQO0FBQUEsTUFDQSxhQUFhO0FBQUEsUUFDVCxHQUFHO0FBQUEsUUFDSCxnQkFBZ0JDLGFBQVk7QUFDeEIsaUJBQU8sV0FBV0EsV0FBVTtBQUFBLFFBQ2hDO0FBQUEsTUFDSjtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBLEtBQUs7QUFBQSxRQUNELFFBQVE7QUFBQSxNQUNaO0FBQUEsTUFDQSxRQUFRO0FBQUEsUUFDSjtBQUFBLFFBQ0E7QUFBQSxRQUNBLE9BQUFDO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsTUFDSjtBQUFBLE1BQ0EsUUFBUTtBQUFBLFFBQ0o7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0o7QUFBQSxNQUNBLFFBQVEsVUFBVSxVQUFVO0FBQUEsSUFDaEM7QUFBQSxFQUNKO0FBRUEsTUFBSSxNQUFPO0FBQ1AsWUFBUSxJQUFJLGlDQUFpQztBQUFBLEVBQ2pEO0FBRUEscUJBQW1CLElBQUk7QUFFdkIsV0FBUyxpQkFBaUIsb0JBQW9CLFNBQVMsT0FBTztBQUMxRCxjQUFVO0FBQUEsRUFDZCxDQUFDOyIsCiAgIm5hbWVzIjogWyJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJldmVudE5hbWUiLCAiY2FsbCIsICJnZW5lcmF0ZUlEIiwgIkVycm9yIiwgImNhbGwiLCAid2luZG93TmFtZSIsICJFcnJvciJdCn0K diff --git a/v3/internal/runtime/runtime_debug_desktop_windows.js b/v3/internal/runtime/runtime_debug_desktop_windows.js new file mode 100644 index 000000000..0ca537592 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_desktop_windows.js @@ -0,0 +1,582 @@ +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // desktop/clipboard.js + var clipboard_exports = {}; + __export(clipboard_exports, { + SetText: () => SetText, + Text: () => Text + }); + + // desktop/runtime.js + var runtimeURL = window.location.origin + "/wails/runtime"; + function runtimeCall(method, windowName, args) { + let url = new URL(runtimeURL); + url.searchParams.append("method", method); + if (args) { + url.searchParams.append("args", JSON.stringify(args)); + } + let fetchOptions = { + headers: {} + }; + if (windowName) { + fetchOptions.headers["x-wails-window-name"] = windowName; + } + return new Promise((resolve, reject) => { + fetch(url, fetchOptions).then((response) => { + if (response.ok) { + if (response.headers.get("Content-Type") && response.headers.get("Content-Type").indexOf("application/json") !== -1) { + return response.json(); + } else { + return response.text(); + } + } + reject(Error(response.statusText)); + }).then((data) => resolve(data)).catch((error) => reject(error)); + }); + } + function newRuntimeCaller(object, windowName) { + return function(method, args = null) { + return runtimeCall(object + "." + method, windowName, args); + }; + } + + // desktop/clipboard.js + var call = newRuntimeCaller("clipboard"); + function SetText(text) { + void call("SetText", { text }); + } + function Text() { + return call("Text"); + } + + // desktop/application.js + var application_exports = {}; + __export(application_exports, { + Hide: () => Hide, + Quit: () => Quit, + Show: () => Show + }); + var call2 = newRuntimeCaller("application"); + function Hide() { + void call2("Hide"); + } + function Show() { + void call2("Show"); + } + function Quit() { + void call2("Quit"); + } + + // desktop/log.js + var log_exports = {}; + __export(log_exports, { + Log: () => Log + }); + var call3 = newRuntimeCaller("log"); + function Log(message) { + return call3("Log", message); + } + + // desktop/screens.js + var screens_exports = {}; + __export(screens_exports, { + GetAll: () => GetAll, + GetCurrent: () => GetCurrent, + GetPrimary: () => GetPrimary + }); + var call4 = newRuntimeCaller("screens"); + function GetAll() { + return call4("GetAll"); + } + function GetPrimary() { + return call4("GetPrimary"); + } + function GetCurrent() { + return call4("GetCurrent"); + } + + // node_modules/nanoid/non-secure/index.js + var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + var nanoid = (size = 21) => { + let id = ""; + let i = size; + while (i--) { + id += urlAlphabet[Math.random() * 64 | 0]; + } + return id; + }; + + // desktop/calls.js + var call5 = newRuntimeCaller("call"); + var callResponses = /* @__PURE__ */ new Map(); + function generateID() { + let result; + do { + result = nanoid(); + } while (callResponses.has(result)); + return result; + } + function callCallback(id, data, isJSON) { + let p = callResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + callResponses.delete(id); + } + } + function callErrorCallback(id, message) { + let p = callResponses.get(id); + if (p) { + p.reject(message); + callResponses.delete(id); + } + } + function callBinding(type, options) { + return new Promise((resolve, reject) => { + let id = generateID(); + options = options || {}; + options["call-id"] = id; + callResponses.set(id, { resolve, reject }); + call5(type, options).catch((error) => { + reject(error); + callResponses.delete(id); + }); + }); + } + function Call(options) { + return callBinding("Call", options); + } + function Plugin(pluginName, methodName, ...args) { + return callBinding("Call", { + packageName: "wails-plugins", + structName: pluginName, + methodName, + args + }); + } + + // desktop/window.js + function newWindow(windowName) { + let call9 = newRuntimeCaller("window", windowName); + return { + // Reload: () => call('WR'), + // ReloadApp: () => call('WR'), + // SetSystemDefaultTheme: () => call('WASDT'), + // SetLightTheme: () => call('WALT'), + // SetDarkTheme: () => call('WADT'), + // IsFullscreen: () => call('WIF'), + // IsMaximized: () => call('WIM'), + // IsMinimized: () => call('WIMN'), + // IsWindowed: () => call('WIF'), + /** + * Centers the window. + */ + Center: () => void call9("Center"), + /** + * Set the window title. + * @param title + */ + SetTitle: (title) => void call9("SetTitle", { title }), + /** + * Makes the window fullscreen. + */ + Fullscreen: () => void call9("Fullscreen"), + /** + * Unfullscreen the window. + */ + UnFullscreen: () => void call9("UnFullscreen"), + /** + * Set the window size. + * @param {number} width The window width + * @param {number} height The window height + */ + SetSize: (width, height) => call9("SetSize", { width, height }), + /** + * Get the window size. + * @returns {Promise} The window size + */ + Size: () => { + return call9("Size"); + }, + /** + * Set the window maximum size. + * @param {number} width + * @param {number} height + */ + SetMaxSize: (width, height) => void call9("SetMaxSize", { width, height }), + /** + * Set the window minimum size. + * @param {number} width + * @param {number} height + */ + SetMinSize: (width, height) => void call9("SetMinSize", { width, height }), + /** + * Set window to be always on top. + * @param {boolean} onTop Whether the window should be always on top + */ + SetAlwaysOnTop: (onTop) => void call9("SetAlwaysOnTop", { alwaysOnTop: onTop }), + /** + * Set the window position. + * @param {number} x + * @param {number} y + */ + SetPosition: (x, y) => call9("SetPosition", { x, y }), + /** + * Get the window position. + * @returns {Promise} The window position + */ + Position: () => { + return call9("Position"); + }, + /** + * Get the screen the window is on. + * @returns {Promise} + */ + Screen: () => { + return call9("Screen"); + }, + /** + * Hide the window + */ + Hide: () => void call9("Hide"), + /** + * Maximise the window + */ + Maximise: () => void call9("Maximise"), + /** + * Show the window + */ + Show: () => void call9("Show"), + /** + * Close the window + */ + Close: () => void call9("Close"), + /** + * Toggle the window maximise state + */ + ToggleMaximise: () => void call9("ToggleMaximise"), + /** + * Unmaximise the window + */ + UnMaximise: () => void call9("UnMaximise"), + /** + * Minimise the window + */ + Minimise: () => void call9("Minimise"), + /** + * Unminimise the window + */ + UnMinimise: () => void call9("UnMinimise"), + /** + * Set the background colour of the window. + * @param {number} r - A value between 0 and 255 + * @param {number} g - A value between 0 and 255 + * @param {number} b - A value between 0 and 255 + * @param {number} a - A value between 0 and 255 + */ + SetBackgroundColour: (r, g, b, a) => void call9("SetBackgroundColour", { r, g, b, a }) + }; + } + + // desktop/events.js + var call6 = newRuntimeCaller("events"); + var Listener = class { + /** + * Creates an instance of Listener. + * @param {string} eventName + * @param {function} callback + * @param {number} maxCallbacks + * @memberof Listener + */ + constructor(eventName, callback, maxCallbacks) { + this.eventName = eventName; + this.maxCallbacks = maxCallbacks || -1; + this.Callback = (data) => { + callback(data); + if (this.maxCallbacks === -1) { + return false; + } + this.maxCallbacks -= 1; + return this.maxCallbacks === 0; + }; + } + }; + var WailsEvent = class { + /** + * Creates an instance of WailsEvent. + * @param {string} name - Name of the event + * @param {any=null} data - Data associated with the event + * @memberof WailsEvent + */ + constructor(name, data = null) { + this.name = name; + this.data = data; + } + }; + var eventListeners = /* @__PURE__ */ new Map(); + 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 listenerOff(listener) { + const eventName = listener.eventName; + let listeners = eventListeners.get(eventName).filter((l) => l !== listener); + if (listeners.length === 0) { + eventListeners.delete(eventName); + } else { + eventListeners.set(eventName, listeners); + } + } + function dispatchWailsEvent(event) { + console.log("dispatching event: ", { event }); + let listeners = eventListeners.get(event.name); + if (listeners) { + let toRemove = []; + listeners.forEach((listener) => { + let remove = listener.Callback(event); + if (remove) { + toRemove.push(listener); + } + }); + if (toRemove.length > 0) { + listeners = listeners.filter((l) => !toRemove.includes(l)); + if (listeners.length === 0) { + eventListeners.delete(event.name); + } else { + eventListeners.set(event.name, listeners); + } + } + } + } + function Off(eventName, ...additionalEventNames) { + let eventsToRemove = [eventName, ...additionalEventNames]; + eventsToRemove.forEach((eventName2) => { + eventListeners.delete(eventName2); + }); + } + function OffAll() { + eventListeners.clear(); + } + function Emit(event) { + void call6("Emit", event); + } + + // desktop/dialogs.js + var call7 = newRuntimeCaller("dialog"); + var dialogResponses = /* @__PURE__ */ new Map(); + function generateID2() { + let result; + do { + result = nanoid(); + } while (dialogResponses.has(result)); + return result; + } + function dialogCallback(id, data, isJSON) { + let p = dialogResponses.get(id); + if (p) { + if (isJSON) { + p.resolve(JSON.parse(data)); + } else { + p.resolve(data); + } + dialogResponses.delete(id); + } + } + function dialogErrorCallback(id, message) { + let p = dialogResponses.get(id); + if (p) { + p.reject(message); + dialogResponses.delete(id); + } + } + function dialog(type, options) { + return new Promise((resolve, reject) => { + let id = generateID2(); + options = options || {}; + options["dialog-id"] = id; + dialogResponses.set(id, { resolve, reject }); + call7(type, options).catch((error) => { + reject(error); + dialogResponses.delete(id); + }); + }); + } + function Info(options) { + return dialog("Info", options); + } + function Warning(options) { + return dialog("Warning", options); + } + function Error2(options) { + return dialog("Error", options); + } + function Question(options) { + return dialog("Question", options); + } + function OpenFile(options) { + return dialog("OpenFile", options); + } + function SaveFile(options) { + return dialog("SaveFile", options); + } + + // desktop/contextmenu.js + var call8 = newRuntimeCaller("contextmenu"); + function openContextMenu(id, x, y, data) { + return call8("OpenContextMenu", { id, x, y, data }); + } + function enableContextMenus(enabled) { + if (enabled) { + window.addEventListener("contextmenu", contextMenuHandler); + } else { + window.removeEventListener("contextmenu", contextMenuHandler); + } + } + function contextMenuHandler(event) { + processContextMenu(event.target, event); + } + function processContextMenu(element, event) { + let id = element.getAttribute("data-contextmenu"); + if (id) { + event.preventDefault(); + openContextMenu(id, event.clientX, event.clientY, element.getAttribute("data-contextmenu-data")); + } else { + let parent = element.parentElement; + if (parent) { + processContextMenu(parent, event); + } + } + } + + // desktop/wml.js + function sendEvent(eventName, data = null) { + let event = new WailsEvent(eventName, data); + Emit(event); + } + function addWMLEventListeners() { + const elements = document.querySelectorAll("[data-wml-event]"); + elements.forEach(function(element) { + const eventType = element.getAttribute("data-wml-event"); + const confirm = element.getAttribute("data-wml-confirm"); + const trigger = element.getAttribute("data-wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + sendEvent(eventType); + } + }); + return; + } + sendEvent(eventType); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function callWindowMethod(method) { + if (wails.Window[method] === void 0) { + console.log("Window method " + method + " not found"); + } + wails.Window[method](); + } + function addWMLWindowListeners() { + const elements = document.querySelectorAll("[data-wml-window]"); + elements.forEach(function(element) { + const windowMethod = element.getAttribute("data-wml-window"); + const confirm = element.getAttribute("data-wml-confirm"); + const trigger = element.getAttribute("data-wml-trigger") || "click"; + let callback = function() { + if (confirm) { + Question({ Title: "Confirm", Message: confirm, Buttons: [{ Label: "Yes" }, { Label: "No", IsDefault: true }] }).then(function(result) { + if (result !== "No") { + callWindowMethod(windowMethod); + } + }); + return; + } + callWindowMethod(windowMethod); + }; + element.removeEventListener(trigger, callback); + element.addEventListener(trigger, callback); + }); + } + function reloadWML() { + addWMLEventListeners(); + addWMLWindowListeners(); + } + + // desktop/main.js + window.wails = { + ...newRuntime(null) + }; + window._wails = { + dialogCallback, + dialogErrorCallback, + dispatchWailsEvent, + callCallback, + callErrorCallback + }; + function newRuntime(windowName) { + return { + Clipboard: { + ...clipboard_exports + }, + Application: { + ...application_exports, + GetWindowByName(windowName2) { + return newRuntime(windowName2); + } + }, + Log: log_exports, + Screens: screens_exports, + Call, + Plugin, + WML: { + Reload: reloadWML + }, + Dialog: { + Info, + Warning, + Error: Error2, + Question, + OpenFile, + SaveFile + }, + Events: { + Emit, + On, + Once, + OnMultiple, + Off, + OffAll + }, + Window: newWindow(windowName) + }; + } + if (true) { + console.log("Wails v3.0.0 Debug Mode Enabled"); + } + enableContextMenus(true); + document.addEventListener("DOMContentLoaded", function(event) { + reloadWML(); + }); +})(); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiZGVza3RvcC9jbGlwYm9hcmQuanMiLCAiZGVza3RvcC9ydW50aW1lLmpzIiwgImRlc2t0b3AvYXBwbGljYXRpb24uanMiLCAiZGVza3RvcC9sb2cuanMiLCAiZGVza3RvcC9zY3JlZW5zLmpzIiwgIm5vZGVfbW9kdWxlcy9uYW5vaWQvbm9uLXNlY3VyZS9pbmRleC5qcyIsICJkZXNrdG9wL2NhbGxzLmpzIiwgImRlc2t0b3Avd2luZG93LmpzIiwgImRlc2t0b3AvZXZlbnRzLmpzIiwgImRlc2t0b3AvZGlhbG9ncy5qcyIsICJkZXNrdG9wL2NvbnRleHRtZW51LmpzIiwgImRlc2t0b3Avd21sLmpzIiwgImRlc2t0b3AvbWFpbi5qcyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImNsaXBib2FyZFwiKTtcblxuLyoqXG4gKiBTZXQgdGhlIENsaXBib2FyZCB0ZXh0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTZXRUZXh0KHRleHQpIHtcbiAgICB2b2lkIGNhbGwoXCJTZXRUZXh0XCIsIHt0ZXh0fSk7XG59XG5cbi8qKlxuICogR2V0IHRoZSBDbGlwYm9hcmQgdGV4dFxuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFRleHQoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJUZXh0XCIpO1xufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5jb25zdCBydW50aW1lVVJMID0gd2luZG93LmxvY2F0aW9uLm9yaWdpbiArIFwiL3dhaWxzL3J1bnRpbWVcIjtcblxuZnVuY3Rpb24gcnVudGltZUNhbGwobWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKSB7XG4gICAgbGV0IHVybCA9IG5ldyBVUkwocnVudGltZVVSTCk7XG4gICAgdXJsLnNlYXJjaFBhcmFtcy5hcHBlbmQoXCJtZXRob2RcIiwgbWV0aG9kKTtcbiAgICBpZiAoYXJncykge1xuICAgICAgICB1cmwuc2VhcmNoUGFyYW1zLmFwcGVuZChcImFyZ3NcIiwgSlNPTi5zdHJpbmdpZnkoYXJncykpO1xuICAgIH1cbiAgICBsZXQgZmV0Y2hPcHRpb25zID0ge1xuICAgICAgICBoZWFkZXJzOiB7fSxcbiAgICB9O1xuICAgIGlmICh3aW5kb3dOYW1lKSB7XG4gICAgICAgIGZldGNoT3B0aW9ucy5oZWFkZXJzW1wieC13YWlscy13aW5kb3ctbmFtZVwiXSA9IHdpbmRvd05hbWU7XG4gICAgfVxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGZldGNoKHVybCwgZmV0Y2hPcHRpb25zKVxuICAgICAgICAgICAgLnRoZW4ocmVzcG9uc2UgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChyZXNwb25zZS5vaykge1xuICAgICAgICAgICAgICAgICAgICAvLyBjaGVjayBjb250ZW50IHR5cGVcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpICYmIHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwiQ29udGVudC1UeXBlXCIpLmluZGV4T2YoXCJhcHBsaWNhdGlvbi9qc29uXCIpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmVqZWN0KEVycm9yKHJlc3BvbnNlLnN0YXR1c1RleHQpKTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAudGhlbihkYXRhID0+IHJlc29sdmUoZGF0YSkpXG4gICAgICAgICAgICAuY2F0Y2goZXJyb3IgPT4gcmVqZWN0KGVycm9yKSk7XG4gICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lQ2FsbGVyKG9iamVjdCwgd2luZG93TmFtZSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAobWV0aG9kLCBhcmdzPW51bGwpIHtcbiAgICAgICAgcmV0dXJuIHJ1bnRpbWVDYWxsKG9iamVjdCArIFwiLlwiICsgbWV0aG9kLCB3aW5kb3dOYW1lLCBhcmdzKTtcbiAgICB9O1xufSIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiYXBwbGljYXRpb25cIik7XG5cbi8qKlxuICogSGlkZSB0aGUgYXBwbGljYXRpb25cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEhpZGUoKSB7XG4gICAgdm9pZCBjYWxsKFwiSGlkZVwiKTtcbn1cblxuLyoqXG4gKiBTaG93IHRoZSBhcHBsaWNhdGlvblxuICovXG5leHBvcnQgZnVuY3Rpb24gU2hvdygpIHtcbiAgICB2b2lkIGNhbGwoXCJTaG93XCIpO1xufVxuXG5cbi8qKlxuICogUXVpdCB0aGUgYXBwbGljYXRpb25cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFF1aXQoKSB7XG4gICAgdm9pZCBjYWxsKFwiUXVpdFwiKTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImxvZ1wiKTtcblxuLyoqXG4gKiBMb2dzIGEgbWVzc2FnZS5cbiAqIEBwYXJhbSB7bWVzc2FnZX0gTWVzc2FnZSB0byBsb2dcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIExvZyhtZXNzYWdlKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJMb2dcIiwgbWVzc2FnZSk7XG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi9hcGkvdHlwZXNcIikuU2NyZWVufSBTY3JlZW5cbiAqL1xuXG5pbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwic2NyZWVuc1wiKTtcblxuLyoqXG4gKiBHZXRzIGFsbCBzY3JlZW5zLlxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuW10+fVxuICovXG5leHBvcnQgZnVuY3Rpb24gR2V0QWxsKCkge1xuICAgIHJldHVybiBjYWxsKFwiR2V0QWxsXCIpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIHByaW1hcnkgc2NyZWVuLlxuICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldFByaW1hcnkoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJHZXRQcmltYXJ5XCIpO1xufVxuXG4vKipcbiAqIEdldHMgdGhlIGN1cnJlbnQgYWN0aXZlIHNjcmVlbi5cbiAqIEByZXR1cm5zIHtQcm9taXNlPFNjcmVlbj59XG4gKiBAY29uc3RydWN0b3JcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEdldEN1cnJlbnQoKSB7XG4gICAgcmV0dXJuIGNhbGwoXCJHZXRDdXJyZW50XCIpO1xufSIsICJsZXQgdXJsQWxwaGFiZXQgPVxuICAndXNlYW5kb20tMjZUMTk4MzQwUFg3NXB4SkFDS1ZFUllNSU5EQlVTSFdPTEZfR1FaYmZnaGprbHF2d3l6cmljdCdcbmV4cG9ydCBsZXQgY3VzdG9tQWxwaGFiZXQgPSAoYWxwaGFiZXQsIGRlZmF1bHRTaXplID0gMjEpID0+IHtcbiAgcmV0dXJuIChzaXplID0gZGVmYXVsdFNpemUpID0+IHtcbiAgICBsZXQgaWQgPSAnJ1xuICAgIGxldCBpID0gc2l6ZVxuICAgIHdoaWxlIChpLS0pIHtcbiAgICAgIGlkICs9IGFscGhhYmV0WyhNYXRoLnJhbmRvbSgpICogYWxwaGFiZXQubGVuZ3RoKSB8IDBdXG4gICAgfVxuICAgIHJldHVybiBpZFxuICB9XG59XG5leHBvcnQgbGV0IG5hbm9pZCA9IChzaXplID0gMjEpID0+IHtcbiAgbGV0IGlkID0gJydcbiAgbGV0IGkgPSBzaXplXG4gIHdoaWxlIChpLS0pIHtcbiAgICBpZCArPSB1cmxBbHBoYWJldFsoTWF0aC5yYW5kb20oKSAqIDY0KSB8IDBdXG4gIH1cbiAgcmV0dXJuIGlkXG59XG4iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmltcG9ydCB7IG5hbm9pZCB9IGZyb20gJ25hbm9pZC9ub24tc2VjdXJlJztcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiY2FsbFwiKTtcblxubGV0IGNhbGxSZXNwb25zZXMgPSBuZXcgTWFwKCk7XG5cbmZ1bmN0aW9uIGdlbmVyYXRlSUQoKSB7XG4gICAgbGV0IHJlc3VsdDtcbiAgICBkbyB7XG4gICAgICAgIHJlc3VsdCA9IG5hbm9pZCgpO1xuICAgIH0gd2hpbGUgKGNhbGxSZXNwb25zZXMuaGFzKHJlc3VsdCkpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBjYWxsQ2FsbGJhY2soaWQsIGRhdGEsIGlzSlNPTikge1xuICAgIGxldCBwID0gY2FsbFJlc3BvbnNlcy5nZXQoaWQpO1xuICAgIGlmIChwKSB7XG4gICAgICAgIGlmIChpc0pTT04pIHtcbiAgICAgICAgICAgIHAucmVzb2x2ZShKU09OLnBhcnNlKGRhdGEpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHAucmVzb2x2ZShkYXRhKTtcbiAgICAgICAgfVxuICAgICAgICBjYWxsUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gY2FsbEVycm9yQ2FsbGJhY2soaWQsIG1lc3NhZ2UpIHtcbiAgICBsZXQgcCA9IGNhbGxSZXNwb25zZXMuZ2V0KGlkKTtcbiAgICBpZiAocCkge1xuICAgICAgICBwLnJlamVjdChtZXNzYWdlKTtcbiAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gY2FsbEJpbmRpbmcodHlwZSwgb3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG4gICAgICAgIG9wdGlvbnNbXCJjYWxsLWlkXCJdID0gaWQ7XG4gICAgICAgIGNhbGxSZXNwb25zZXMuc2V0KGlkLCB7cmVzb2x2ZSwgcmVqZWN0fSk7XG4gICAgICAgIGNhbGwodHlwZSwgb3B0aW9ucykuY2F0Y2goKGVycm9yKSA9PiB7XG4gICAgICAgICAgICByZWplY3QoZXJyb3IpO1xuICAgICAgICAgICAgY2FsbFJlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIENhbGwob3B0aW9ucykge1xuICAgIHJldHVybiBjYWxsQmluZGluZyhcIkNhbGxcIiwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogQ2FsbCBhIHBsdWdpbiBtZXRob2RcbiAqIEBwYXJhbSB7c3RyaW5nfSBwbHVnaW5OYW1lIC0gbmFtZSBvZiB0aGUgcGx1Z2luXG4gKiBAcGFyYW0ge3N0cmluZ30gbWV0aG9kTmFtZSAtIG5hbWUgb2YgdGhlIG1ldGhvZFxuICogQHBhcmFtIHsuLi5hbnl9IGFyZ3MgLSBhcmd1bWVudHMgdG8gcGFzcyB0byB0aGUgbWV0aG9kXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fSAtIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSByZXN1bHRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFBsdWdpbihwbHVnaW5OYW1lLCBtZXRob2ROYW1lLCAuLi5hcmdzKSB7XG4gICAgcmV0dXJuIGNhbGxCaW5kaW5nKFwiQ2FsbFwiLCB7XG4gICAgICAgIHBhY2thZ2VOYW1lOiBcIndhaWxzLXBsdWdpbnNcIixcbiAgICAgICAgc3RydWN0TmFtZTogcGx1Z2luTmFtZSxcbiAgICAgICAgbWV0aG9kTmFtZTogbWV0aG9kTmFtZSxcbiAgICAgICAgYXJnczogYXJncyxcbiAgICB9KTtcbn0iLCAiLypcbiBfXHQgICBfX1x0ICBfIF9fXG58IHxcdCAvIC9fX18gXyhfKSAvX19fX1xufCB8IC98IC8gLyBfXyBgLyAvIC8gX19fL1xufCB8LyB8LyAvIC9fLyAvIC8gKF9fICApXG58X18vfF9fL1xcX18sXy9fL18vX19fXy9cblRoZSBlbGVjdHJvbiBhbHRlcm5hdGl2ZSBmb3IgR29cbihjKSBMZWEgQW50aG9ueSAyMDE5LXByZXNlbnRcbiovXG5cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuLyoqXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNpemV9IFNpemVcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuLi9hcGkvdHlwZXNcIikuUG9zaXRpb259IFBvc2l0aW9uXG4gKiBAdHlwZWRlZiB7aW1wb3J0KFwiLi4vYXBpL3R5cGVzXCIpLlNjcmVlbn0gU2NyZWVuXG4gKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdXaW5kb3cod2luZG93TmFtZSkge1xuICAgIGxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcIndpbmRvd1wiLCB3aW5kb3dOYW1lKTtcbiAgICByZXR1cm4ge1xuICAgICAgICAvLyBSZWxvYWQ6ICgpID0+IGNhbGwoJ1dSJyksXG4gICAgICAgIC8vIFJlbG9hZEFwcDogKCkgPT4gY2FsbCgnV1InKSxcbiAgICAgICAgLy8gU2V0U3lzdGVtRGVmYXVsdFRoZW1lOiAoKSA9PiBjYWxsKCdXQVNEVCcpLFxuICAgICAgICAvLyBTZXRMaWdodFRoZW1lOiAoKSA9PiBjYWxsKCdXQUxUJyksXG4gICAgICAgIC8vIFNldERhcmtUaGVtZTogKCkgPT4gY2FsbCgnV0FEVCcpLFxuICAgICAgICAvLyBJc0Z1bGxzY3JlZW46ICgpID0+IGNhbGwoJ1dJRicpLFxuICAgICAgICAvLyBJc01heGltaXplZDogKCkgPT4gY2FsbCgnV0lNJyksXG4gICAgICAgIC8vIElzTWluaW1pemVkOiAoKSA9PiBjYWxsKCdXSU1OJyksXG4gICAgICAgIC8vIElzV2luZG93ZWQ6ICgpID0+IGNhbGwoJ1dJRicpLFxuXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIENlbnRlcnMgdGhlIHdpbmRvdy5cbiAgICAgICAgICovXG4gICAgICAgIENlbnRlcjogKCkgPT4gdm9pZCBjYWxsKCdDZW50ZXInKSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogU2V0IHRoZSB3aW5kb3cgdGl0bGUuXG4gICAgICAgICAqIEBwYXJhbSB0aXRsZVxuICAgICAgICAgKi9cbiAgICAgICAgU2V0VGl0bGU6ICh0aXRsZSkgPT4gdm9pZCBjYWxsKCdTZXRUaXRsZScsIHt0aXRsZX0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNYWtlcyB0aGUgd2luZG93IGZ1bGxzY3JlZW4uXG4gICAgICAgICAqL1xuICAgICAgICBGdWxsc2NyZWVuOiAoKSA9PiB2b2lkIGNhbGwoJ0Z1bGxzY3JlZW4nKSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogVW5mdWxsc2NyZWVuIHRoZSB3aW5kb3cuXG4gICAgICAgICAqL1xuICAgICAgICBVbkZ1bGxzY3JlZW46ICgpID0+IHZvaWQgY2FsbCgnVW5GdWxsc2NyZWVuJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IHNpemUuXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB3aWR0aCBUaGUgd2luZG93IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHQgVGhlIHdpbmRvdyBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiBjYWxsKCdTZXRTaXplJywge3dpZHRoLGhlaWdodH0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBzaXplLlxuICAgICAgICAgKiBAcmV0dXJucyB7UHJvbWlzZTxTaXplPn0gVGhlIHdpbmRvdyBzaXplXG4gICAgICAgICAqL1xuICAgICAgICBTaXplOiAoKSA9PiB7IHJldHVybiBjYWxsKCdTaXplJyk7IH0sXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IG1heGltdW0gc2l6ZS5cbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldE1heFNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoJ1NldE1heFNpemUnLCB7d2lkdGgsaGVpZ2h0fSksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB0aGUgd2luZG93IG1pbmltdW0gc2l6ZS5cbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IHdpZHRoXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBoZWlnaHRcbiAgICAgICAgICovXG4gICAgICAgIFNldE1pblNpemU6ICh3aWR0aCwgaGVpZ2h0KSA9PiB2b2lkIGNhbGwoJ1NldE1pblNpemUnLCB7d2lkdGgsaGVpZ2h0fSksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNldCB3aW5kb3cgdG8gYmUgYWx3YXlzIG9uIHRvcC5cbiAgICAgICAgICogQHBhcmFtIHtib29sZWFufSBvblRvcCBXaGV0aGVyIHRoZSB3aW5kb3cgc2hvdWxkIGJlIGFsd2F5cyBvbiB0b3BcbiAgICAgICAgICovXG4gICAgICAgIFNldEFsd2F5c09uVG9wOiAob25Ub3ApID0+IHZvaWQgY2FsbCgnU2V0QWx3YXlzT25Ub3AnLCB7YWx3YXlzT25Ub3A6b25Ub3B9KSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogU2V0IHRoZSB3aW5kb3cgcG9zaXRpb24uXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB4XG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSB5XG4gICAgICAgICAqL1xuICAgICAgICBTZXRQb3NpdGlvbjogKHgsIHkpID0+IGNhbGwoJ1NldFBvc2l0aW9uJywge3gseX0pLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBHZXQgdGhlIHdpbmRvdyBwb3NpdGlvbi5cbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8UG9zaXRpb24+fSBUaGUgd2luZG93IHBvc2l0aW9uXG4gICAgICAgICAqL1xuICAgICAgICBQb3NpdGlvbjogKCkgPT4geyByZXR1cm4gY2FsbCgnUG9zaXRpb24nKTsgfSxcblxuICAgICAgICAvKipcbiAgICAgICAgICogR2V0IHRoZSBzY3JlZW4gdGhlIHdpbmRvdyBpcyBvbi5cbiAgICAgICAgICogQHJldHVybnMge1Byb21pc2U8U2NyZWVuPn1cbiAgICAgICAgICovXG4gICAgICAgIFNjcmVlbjogKCkgPT4geyByZXR1cm4gY2FsbCgnU2NyZWVuJyk7IH0sXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIEhpZGUgdGhlIHdpbmRvd1xuICAgICAgICAgKi9cbiAgICAgICAgSGlkZTogKCkgPT4gdm9pZCBjYWxsKCdIaWRlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIE1heGltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIE1heGltaXNlOiAoKSA9PiB2b2lkIGNhbGwoJ01heGltaXNlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFNob3cgdGhlIHdpbmRvd1xuICAgICAgICAgKi9cbiAgICAgICAgU2hvdzogKCkgPT4gdm9pZCBjYWxsKCdTaG93JyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIENsb3NlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIENsb3NlOiAoKSA9PiB2b2lkIGNhbGwoJ0Nsb3NlJyksXG5cbiAgICAgICAgLyoqXG4gICAgICAgICAqIFRvZ2dsZSB0aGUgd2luZG93IG1heGltaXNlIHN0YXRlXG4gICAgICAgICAqL1xuICAgICAgICBUb2dnbGVNYXhpbWlzZTogKCkgPT4gdm9pZCBjYWxsKCdUb2dnbGVNYXhpbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBVbm1heGltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIFVuTWF4aW1pc2U6ICgpID0+IHZvaWQgY2FsbCgnVW5NYXhpbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBNaW5pbWlzZSB0aGUgd2luZG93XG4gICAgICAgICAqL1xuICAgICAgICBNaW5pbWlzZTogKCkgPT4gdm9pZCBjYWxsKCdNaW5pbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBVbm1pbmltaXNlIHRoZSB3aW5kb3dcbiAgICAgICAgICovXG4gICAgICAgIFVuTWluaW1pc2U6ICgpID0+IHZvaWQgY2FsbCgnVW5NaW5pbWlzZScpLFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBTZXQgdGhlIGJhY2tncm91bmQgY29sb3VyIG9mIHRoZSB3aW5kb3cuXG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSByIC0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDI1NVxuICAgICAgICAgKiBAcGFyYW0ge251bWJlcn0gZyAtIEEgdmFsdWUgYmV0d2VlbiAwIGFuZCAyNTVcbiAgICAgICAgICogQHBhcmFtIHtudW1iZXJ9IGIgLSBBIHZhbHVlIGJldHdlZW4gMCBhbmQgMjU1XG4gICAgICAgICAqIEBwYXJhbSB7bnVtYmVyfSBhIC0gQSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDI1NVxuICAgICAgICAgKi9cbiAgICAgICAgU2V0QmFja2dyb3VuZENvbG91cjogKHIsIGcsIGIsIGEpID0+IHZvaWQgY2FsbCgnU2V0QmFja2dyb3VuZENvbG91cicsIHtyLCBnLCBiLCBhfSksXG4gICAgfTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cblxuLyoganNoaW50IGVzdmVyc2lvbjogOSAqL1xuXG4vKipcbiAqIEB0eXBlZGVmIHtpbXBvcnQoXCIuL2FwaS90eXBlc1wiKS5XYWlsc0V2ZW50fSBXYWlsc0V2ZW50XG4gKi9cblxuaW1wb3J0IHtuZXdSdW50aW1lQ2FsbGVyfSBmcm9tIFwiLi9ydW50aW1lXCI7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImV2ZW50c1wiKTtcblxuLyoqXG4gKiBUaGUgTGlzdGVuZXIgY2xhc3MgZGVmaW5lcyBhIGxpc3RlbmVyISA6LSlcbiAqXG4gKiBAY2xhc3MgTGlzdGVuZXJcbiAqL1xuY2xhc3MgTGlzdGVuZXIge1xuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYW4gaW5zdGFuY2Ugb2YgTGlzdGVuZXIuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICAgICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNhbGxiYWNrXG4gICAgICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICAgICAqIEBtZW1iZXJvZiBMaXN0ZW5lclxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKGV2ZW50TmFtZSwgY2FsbGJhY2ssIG1heENhbGxiYWNrcykge1xuICAgICAgICB0aGlzLmV2ZW50TmFtZSA9IGV2ZW50TmFtZTtcbiAgICAgICAgLy8gRGVmYXVsdCBvZiAtMSBtZWFucyBpbmZpbml0ZVxuICAgICAgICB0aGlzLm1heENhbGxiYWNrcyA9IG1heENhbGxiYWNrcyB8fCAtMTtcbiAgICAgICAgLy8gQ2FsbGJhY2sgaW52b2tlcyB0aGUgY2FsbGJhY2sgd2l0aCB0aGUgZ2l2ZW4gZGF0YVxuICAgICAgICAvLyBSZXR1cm5zIHRydWUgaWYgdGhpcyBsaXN0ZW5lciBzaG91bGQgYmUgZGVzdHJveWVkXG4gICAgICAgIHRoaXMuQ2FsbGJhY2sgPSAoZGF0YSkgPT4ge1xuICAgICAgICAgICAgY2FsbGJhY2soZGF0YSk7XG4gICAgICAgICAgICAvLyBJZiBtYXhDYWxsYmFja3MgaXMgaW5maW5pdGUsIHJldHVybiBmYWxzZSAoZG8gbm90IGRlc3Ryb3kpXG4gICAgICAgICAgICBpZiAodGhpcy5tYXhDYWxsYmFja3MgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gRGVjcmVtZW50IG1heENhbGxiYWNrcy4gUmV0dXJuIHRydWUgaWYgbm93IDAsIG90aGVyd2lzZSBmYWxzZVxuICAgICAgICAgICAgdGhpcy5tYXhDYWxsYmFja3MgLT0gMTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLm1heENhbGxiYWNrcyA9PT0gMDtcbiAgICAgICAgfTtcbiAgICB9XG59XG5cblxuLyoqXG4gKiBXYWlsc0V2ZW50IGRlZmluZXMgYSBjdXN0b20gZXZlbnQuIEl0IGlzIHBhc3NlZCB0byBldmVudCBsaXN0ZW5lcnMuXG4gKlxuICogQGNsYXNzIFdhaWxzRXZlbnRcbiAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBuYW1lIC0gTmFtZSBvZiB0aGUgZXZlbnRcbiAqIEBwcm9wZXJ0eSB7YW55fSBkYXRhIC0gRGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XG4gKi9cbmV4cG9ydCBjbGFzcyBXYWlsc0V2ZW50IHtcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGFuIGluc3RhbmNlIG9mIFdhaWxzRXZlbnQuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IG5hbWUgLSBOYW1lIG9mIHRoZSBldmVudFxuICAgICAqIEBwYXJhbSB7YW55PW51bGx9IGRhdGEgLSBEYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgZXZlbnRcbiAgICAgKiBAbWVtYmVyb2YgV2FpbHNFdmVudFxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKG5hbWUsIGRhdGEgPSBudWxsKSB7XG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWU7XG4gICAgICAgIHRoaXMuZGF0YSA9IGRhdGE7XG4gICAgfVxufVxuXG5leHBvcnQgY29uc3QgZXZlbnRMaXN0ZW5lcnMgPSBuZXcgTWFwKCk7XG5cbi8qKlxuICogUmVnaXN0ZXJzIGFuIGV2ZW50IGxpc3RlbmVyIHRoYXQgd2lsbCBiZSBpbnZva2VkIGBtYXhDYWxsYmFja3NgIHRpbWVzIGJlZm9yZSBiZWluZyBkZXN0cm95ZWRcbiAqXG4gKiBAZXhwb3J0XG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lXG4gKiBAcGFyYW0ge2Z1bmN0aW9uKFdhaWxzRXZlbnQpOiB2b2lkfSBjYWxsYmFja1xuICogQHBhcmFtIHtudW1iZXJ9IG1heENhbGxiYWNrc1xuICogQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uTXVsdGlwbGUoZXZlbnROYW1lLCBjYWxsYmFjaywgbWF4Q2FsbGJhY2tzKSB7XG4gICAgbGV0IGxpc3RlbmVycyA9IGV2ZW50TGlzdGVuZXJzLmdldChldmVudE5hbWUpIHx8IFtdO1xuICAgIGNvbnN0IHRoaXNMaXN0ZW5lciA9IG5ldyBMaXN0ZW5lcihldmVudE5hbWUsIGNhbGxiYWNrLCBtYXhDYWxsYmFja3MpO1xuICAgIGxpc3RlbmVycy5wdXNoKHRoaXNMaXN0ZW5lcik7XG4gICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50TmFtZSwgbGlzdGVuZXJzKTtcbiAgICByZXR1cm4gKCkgPT4gbGlzdGVuZXJPZmYodGhpc0xpc3RlbmVyKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgZXZlcnkgdGltZSB0aGUgZXZlbnQgaXMgZW1pdHRlZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oV2FpbHNFdmVudCk6IHZvaWR9IGNhbGxiYWNrXG4gKiBAcmV0dXJucyB7ZnVuY3Rpb259IEEgZnVuY3Rpb24gdG8gY2FuY2VsIHRoZSBsaXN0ZW5lclxuICovXG5leHBvcnQgZnVuY3Rpb24gT24oZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIC0xKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlcnMgYW4gZXZlbnQgbGlzdGVuZXIgdGhhdCB3aWxsIGJlIGludm9rZWQgb25jZSB0aGVuIGRlc3Ryb3llZFxuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7c3RyaW5nfSBldmVudE5hbWVcbiAqIEBwYXJhbSB7ZnVuY3Rpb24oV2FpbHNFdmVudCk6IHZvaWR9IGNhbGxiYWNrXG4gQHJldHVybnMge2Z1bmN0aW9ufSBBIGZ1bmN0aW9uIHRvIGNhbmNlbCB0aGUgbGlzdGVuZXJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIE9uY2UoZXZlbnROYW1lLCBjYWxsYmFjaykge1xuICAgIHJldHVybiBPbk11bHRpcGxlKGV2ZW50TmFtZSwgY2FsbGJhY2ssIDEpO1xufVxuXG4vKipcbiAqIGxpc3RlbmVyT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT25cbiAqXG4gKiBAcGFyYW0ge0xpc3RlbmVyfSBsaXN0ZW5lclxuICovXG5mdW5jdGlvbiBsaXN0ZW5lck9mZihsaXN0ZW5lcikge1xuICAgIGNvbnN0IGV2ZW50TmFtZSA9IGxpc3RlbmVyLmV2ZW50TmFtZTtcbiAgICAvLyBSZW1vdmUgbG9jYWwgbGlzdGVuZXJcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSkuZmlsdGVyKGwgPT4gbCAhPT0gbGlzdGVuZXIpO1xuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50TGlzdGVuZXJzLnNldChldmVudE5hbWUsIGxpc3RlbmVycyk7XG4gICAgfVxufVxuXG4vKipcbiAqIGRpc3BhdGNoZXMgYW4gZXZlbnQgdG8gYWxsIGxpc3RlbmVyc1xuICpcbiAqIEBleHBvcnRcbiAqIEBwYXJhbSB7V2FpbHNFdmVudH0gZXZlbnRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGRpc3BhdGNoV2FpbHNFdmVudChldmVudCkge1xuICAgIGNvbnNvbGUubG9nKFwiZGlzcGF0Y2hpbmcgZXZlbnQ6IFwiLCB7ZXZlbnR9KTtcbiAgICBsZXQgbGlzdGVuZXJzID0gZXZlbnRMaXN0ZW5lcnMuZ2V0KGV2ZW50Lm5hbWUpO1xuICAgIGlmIChsaXN0ZW5lcnMpIHtcbiAgICAgICAgLy8gaXRlcmF0ZSBsaXN0ZW5lcnMgYW5kIGNhbGwgY2FsbGJhY2suIElmIGNhbGxiYWNrIHJldHVybnMgdHJ1ZSwgcmVtb3ZlIGxpc3RlbmVyXG4gICAgICAgIGxldCB0b1JlbW92ZSA9IFtdO1xuICAgICAgICBsaXN0ZW5lcnMuZm9yRWFjaChsaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICBsZXQgcmVtb3ZlID0gbGlzdGVuZXIuQ2FsbGJhY2soZXZlbnQpO1xuICAgICAgICAgICAgaWYgKHJlbW92ZSkge1xuICAgICAgICAgICAgICAgIHRvUmVtb3ZlLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gcmVtb3ZlIGxpc3RlbmVyc1xuICAgICAgICBpZiAodG9SZW1vdmUubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgbGlzdGVuZXJzID0gbGlzdGVuZXJzLmZpbHRlcihsID0+ICF0b1JlbW92ZS5pbmNsdWRlcyhsKSk7XG4gICAgICAgICAgICBpZiAobGlzdGVuZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGV2ZW50TGlzdGVuZXJzLmRlbGV0ZShldmVudC5uYW1lKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgZXZlbnRMaXN0ZW5lcnMuc2V0KGV2ZW50Lm5hbWUsIGxpc3RlbmVycyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG5cbi8qKlxuICogT2ZmIHVucmVnaXN0ZXJzIGEgbGlzdGVuZXIgcHJldmlvdXNseSByZWdpc3RlcmVkIHdpdGggT24sXG4gKiBvcHRpb25hbGx5IG11bHRpcGxlIGxpc3RlbmVycyBjYW4gYmUgdW5yZWdpc3RlcmVkIHZpYSBgYWRkaXRpb25hbEV2ZW50TmFtZXNgXG4gKlxuIFt2MyBDSEFOR0VdIE9mZiBvbmx5IHVucmVnaXN0ZXJzIGxpc3RlbmVycyB3aXRoaW4gdGhlIGN1cnJlbnQgd2luZG93XG4gKlxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZVxuICogQHBhcmFtICB7Li4uc3RyaW5nfSBhZGRpdGlvbmFsRXZlbnROYW1lc1xuICovXG5leHBvcnQgZnVuY3Rpb24gT2ZmKGV2ZW50TmFtZSwgLi4uYWRkaXRpb25hbEV2ZW50TmFtZXMpIHtcbiAgICBsZXQgZXZlbnRzVG9SZW1vdmUgPSBbZXZlbnROYW1lLCAuLi5hZGRpdGlvbmFsRXZlbnROYW1lc107XG4gICAgZXZlbnRzVG9SZW1vdmUuZm9yRWFjaChldmVudE5hbWUgPT4ge1xuICAgICAgICBldmVudExpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBPZmZBbGwgdW5yZWdpc3RlcnMgYWxsIGxpc3RlbmVyc1xuICogW3YzIENIQU5HRV0gT2ZmQWxsIG9ubHkgdW5yZWdpc3RlcnMgbGlzdGVuZXJzIHdpdGhpbiB0aGUgY3VycmVudCB3aW5kb3dcbiAqXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBPZmZBbGwoKSB7XG4gICAgZXZlbnRMaXN0ZW5lcnMuY2xlYXIoKTtcbn1cblxuLyoqXG4gKiBFbWl0IGFuIGV2ZW50XG4gKiBAcGFyYW0ge1dhaWxzRXZlbnR9IGV2ZW50IFRoZSBldmVudCB0byBlbWl0XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFbWl0KGV2ZW50KSB7XG4gICAgdm9pZCBjYWxsKFwiRW1pdFwiLCBldmVudCk7XG59IiwgIi8qXG4gX1x0ICAgX19cdCAgXyBfX1xufCB8XHQgLyAvX19fIF8oXykgL19fX19cbnwgfCAvfCAvIC8gX18gYC8gLyAvIF9fXy9cbnwgfC8gfC8gLyAvXy8gLyAvIChfXyAgKVxufF9fL3xfXy9cXF9fLF8vXy9fL19fX18vXG5UaGUgZWxlY3Ryb24gYWx0ZXJuYXRpdmUgZm9yIEdvXG4oYykgTGVhIEFudGhvbnkgMjAxOS1wcmVzZW50XG4qL1xuXG4vKiBqc2hpbnQgZXN2ZXJzaW9uOiA5ICovXG5cbi8qKlxuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk1lc3NhZ2VEaWFsb2dPcHRpb25zfSBNZXNzYWdlRGlhbG9nT3B0aW9uc1xuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLk9wZW5EaWFsb2dPcHRpb25zfSBPcGVuRGlhbG9nT3B0aW9uc1xuICogQHR5cGVkZWYge2ltcG9ydChcIi4vYXBpL3R5cGVzXCIpLlNhdmVEaWFsb2dPcHRpb25zfSBTYXZlRGlhbG9nT3B0aW9uc1xuICovXG5cbmltcG9ydCB7bmV3UnVudGltZUNhbGxlcn0gZnJvbSBcIi4vcnVudGltZVwiO1xuXG5pbXBvcnQgeyBuYW5vaWQgfSBmcm9tICduYW5vaWQvbm9uLXNlY3VyZSc7XG5cbmxldCBjYWxsID0gbmV3UnVudGltZUNhbGxlcihcImRpYWxvZ1wiKTtcblxubGV0IGRpYWxvZ1Jlc3BvbnNlcyA9IG5ldyBNYXAoKTtcblxuZnVuY3Rpb24gZ2VuZXJhdGVJRCgpIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGRvIHtcbiAgICAgICAgcmVzdWx0ID0gbmFub2lkKCk7XG4gICAgfSB3aGlsZSAoZGlhbG9nUmVzcG9uc2VzLmhhcyhyZXN1bHQpKTtcbiAgICByZXR1cm4gcmVzdWx0O1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZGlhbG9nQ2FsbGJhY2soaWQsIGRhdGEsIGlzSlNPTikge1xuICAgIGxldCBwID0gZGlhbG9nUmVzcG9uc2VzLmdldChpZCk7XG4gICAgaWYgKHApIHtcbiAgICAgICAgaWYgKGlzSlNPTikge1xuICAgICAgICAgICAgcC5yZXNvbHZlKEpTT04ucGFyc2UoZGF0YSkpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcC5yZXNvbHZlKGRhdGEpO1xuICAgICAgICB9XG4gICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgIH1cbn1cbmV4cG9ydCBmdW5jdGlvbiBkaWFsb2dFcnJvckNhbGxiYWNrKGlkLCBtZXNzYWdlKSB7XG4gICAgbGV0IHAgPSBkaWFsb2dSZXNwb25zZXMuZ2V0KGlkKTtcbiAgICBpZiAocCkge1xuICAgICAgICBwLnJlamVjdChtZXNzYWdlKTtcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLmRlbGV0ZShpZCk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBkaWFsb2codHlwZSwgb3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBpZCA9IGdlbmVyYXRlSUQoKTtcbiAgICAgICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG4gICAgICAgIG9wdGlvbnNbXCJkaWFsb2ctaWRcIl0gPSBpZDtcbiAgICAgICAgZGlhbG9nUmVzcG9uc2VzLnNldChpZCwge3Jlc29sdmUsIHJlamVjdH0pO1xuICAgICAgICBjYWxsKHR5cGUsIG9wdGlvbnMpLmNhdGNoKChlcnJvcikgPT4ge1xuICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICAgIGRpYWxvZ1Jlc3BvbnNlcy5kZWxldGUoaWQpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbn1cblxuXG4vKipcbiAqIFNob3dzIGFuIEluZm8gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBJbmZvKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gZGlhbG9nKFwiSW5mb1wiLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTaG93cyBhbiBXYXJuaW5nIGRpYWxvZyB3aXRoIHRoZSBnaXZlbiBvcHRpb25zLlxuICogQHBhcmFtIHtNZXNzYWdlRGlhbG9nT3B0aW9uc30gb3B0aW9uc1xuICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gVGhlIGxhYmVsIG9mIHRoZSBidXR0b24gcHJlc3NlZFxuICovXG5leHBvcnQgZnVuY3Rpb24gV2FybmluZyhvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIldhcm5pbmdcIiwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogU2hvd3MgYW4gRXJyb3IgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBFcnJvcihvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIkVycm9yXCIsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNob3dzIGEgUXVlc3Rpb24gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge01lc3NhZ2VEaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgbGFiZWwgb2YgdGhlIGJ1dHRvbiBwcmVzc2VkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBRdWVzdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIGRpYWxvZyhcIlF1ZXN0aW9uXCIsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFNob3dzIGFuIE9wZW4gZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmdbXXxzdHJpbmc+fSBSZXR1cm5zIHRoZSBzZWxlY3RlZCBmaWxlIG9yIGFuIGFycmF5IG9mIHNlbGVjdGVkIGZpbGVzIGlmIEFsbG93c011bHRpcGxlU2VsZWN0aW9uIGlzIHRydWUuIEEgYmxhbmsgc3RyaW5nIGlzIHJldHVybmVkIGlmIG5vIGZpbGUgd2FzIHNlbGVjdGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gT3BlbkZpbGUob3B0aW9ucykge1xuICAgIHJldHVybiBkaWFsb2coXCJPcGVuRmlsZVwiLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBTaG93cyBhIFNhdmUgZGlhbG9nIHdpdGggdGhlIGdpdmVuIG9wdGlvbnMuXG4gKiBAcGFyYW0ge09wZW5EaWFsb2dPcHRpb25zfSBvcHRpb25zXG4gKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBSZXR1cm5zIHRoZSBzZWxlY3RlZCBmaWxlLiBBIGJsYW5rIHN0cmluZyBpcyByZXR1cm5lZCBpZiBubyBmaWxlIHdhcyBzZWxlY3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFNhdmVGaWxlKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gZGlhbG9nKFwiU2F2ZUZpbGVcIiwgb3B0aW9ucyk7XG59XG5cbiIsICJpbXBvcnQge25ld1J1bnRpbWVDYWxsZXJ9IGZyb20gXCIuL3J1bnRpbWVcIjtcblxubGV0IGNhbGwgPSBuZXdSdW50aW1lQ2FsbGVyKFwiY29udGV4dG1lbnVcIik7XG5cbmZ1bmN0aW9uIG9wZW5Db250ZXh0TWVudShpZCwgeCwgeSwgZGF0YSkge1xuICAgIHJldHVybiBjYWxsKFwiT3BlbkNvbnRleHRNZW51XCIsIHtpZCwgeCwgeSwgZGF0YX0pO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZW5hYmxlQ29udGV4dE1lbnVzKGVuYWJsZWQpIHtcbiAgICBpZiAoZW5hYmxlZCkge1xuICAgICAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignY29udGV4dG1lbnUnLCBjb250ZXh0TWVudUhhbmRsZXIpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdjb250ZXh0bWVudScsIGNvbnRleHRNZW51SGFuZGxlcik7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBjb250ZXh0TWVudUhhbmRsZXIoZXZlbnQpIHtcbiAgICBwcm9jZXNzQ29udGV4dE1lbnUoZXZlbnQudGFyZ2V0LCBldmVudCk7XG59XG5cbmZ1bmN0aW9uIHByb2Nlc3NDb250ZXh0TWVudShlbGVtZW50LCBldmVudCkge1xuICAgIGxldCBpZCA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLWNvbnRleHRtZW51Jyk7XG4gICAgaWYgKGlkKSB7XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgIG9wZW5Db250ZXh0TWVudShpZCwgZXZlbnQuY2xpZW50WCwgZXZlbnQuY2xpZW50WSwgZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtY29udGV4dG1lbnUtZGF0YScpKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBsZXQgcGFyZW50ID0gZWxlbWVudC5wYXJlbnRFbGVtZW50O1xuICAgICAgICBpZiAocGFyZW50KSB7XG4gICAgICAgICAgICBwcm9jZXNzQ29udGV4dE1lbnUocGFyZW50LCBldmVudCk7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCAiXG5pbXBvcnQge0VtaXQsIFdhaWxzRXZlbnR9IGZyb20gXCIuL2V2ZW50c1wiO1xuaW1wb3J0IHtRdWVzdGlvbn0gZnJvbSBcIi4vZGlhbG9nc1wiO1xuXG5mdW5jdGlvbiBzZW5kRXZlbnQoZXZlbnROYW1lLCBkYXRhPW51bGwpIHtcbiAgICBsZXQgZXZlbnQgPSBuZXcgV2FpbHNFdmVudChldmVudE5hbWUsIGRhdGEpO1xuICAgIEVtaXQoZXZlbnQpO1xufVxuXG5mdW5jdGlvbiBhZGRXTUxFdmVudExpc3RlbmVycygpIHtcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1tkYXRhLXdtbC1ldmVudF0nKTtcbiAgICBlbGVtZW50cy5mb3JFYWNoKGZ1bmN0aW9uIChlbGVtZW50KSB7XG4gICAgICAgIGNvbnN0IGV2ZW50VHlwZSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC1ldmVudCcpO1xuICAgICAgICBjb25zdCBjb25maXJtID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLWNvbmZpcm0nKTtcbiAgICAgICAgY29uc3QgdHJpZ2dlciA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLXdtbC10cmlnZ2VyJykgfHwgXCJjbGlja1wiO1xuXG4gICAgICAgIGxldCBjYWxsYmFjayA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGlmIChjb25maXJtKSB7XG4gICAgICAgICAgICAgICAgUXVlc3Rpb24oe1RpdGxlOiBcIkNvbmZpcm1cIiwgTWVzc2FnZTpjb25maXJtLCBCdXR0b25zOlt7TGFiZWw6XCJZZXNcIn0se0xhYmVsOlwiTm9cIiwgSXNEZWZhdWx0OnRydWV9XX0pLnRoZW4oZnVuY3Rpb24gKHJlc3VsdCkge1xuICAgICAgICAgICAgICAgICAgICBpZiAocmVzdWx0ICE9PSBcIk5vXCIpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbmRFdmVudChldmVudFR5cGUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2VuZEV2ZW50KGV2ZW50VHlwZSk7XG4gICAgICAgIH07XG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcblxuICAgICAgICBlbGVtZW50LnJlbW92ZUV2ZW50TGlzdGVuZXIodHJpZ2dlciwgY2FsbGJhY2spO1xuXG4gICAgICAgIC8vIEFkZCBuZXcgbGlzdGVuZXJcbiAgICAgICAgZWxlbWVudC5hZGRFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcbiAgICB9KTtcbn1cblxuZnVuY3Rpb24gY2FsbFdpbmRvd01ldGhvZChtZXRob2QpIHtcbiAgICBpZiAod2FpbHMuV2luZG93W21ldGhvZF0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICBjb25zb2xlLmxvZyhcIldpbmRvdyBtZXRob2QgXCIgKyBtZXRob2QgKyBcIiBub3QgZm91bmRcIik7XG4gICAgfVxuICAgIHdhaWxzLldpbmRvd1ttZXRob2RdKCk7XG59XG5cbmZ1bmN0aW9uIGFkZFdNTFdpbmRvd0xpc3RlbmVycygpIHtcbiAgICBjb25zdCBlbGVtZW50cyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ1tkYXRhLXdtbC13aW5kb3ddJyk7XG4gICAgZWxlbWVudHMuZm9yRWFjaChmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICBjb25zdCB3aW5kb3dNZXRob2QgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtd2luZG93Jyk7XG4gICAgICAgIGNvbnN0IGNvbmZpcm0gPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS13bWwtY29uZmlybScpO1xuICAgICAgICBjb25zdCB0cmlnZ2VyID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtd21sLXRyaWdnZXInKSB8fCBcImNsaWNrXCI7XG5cbiAgICAgICAgbGV0IGNhbGxiYWNrID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgaWYgKGNvbmZpcm0pIHtcbiAgICAgICAgICAgICAgICBRdWVzdGlvbih7VGl0bGU6IFwiQ29uZmlybVwiLCBNZXNzYWdlOmNvbmZpcm0sIEJ1dHRvbnM6W3tMYWJlbDpcIlllc1wifSx7TGFiZWw6XCJOb1wiLCBJc0RlZmF1bHQ6dHJ1ZX1dfSkudGhlbihmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHQgIT09IFwiTm9cIikge1xuICAgICAgICAgICAgICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dNZXRob2QpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2FsbFdpbmRvd01ldGhvZCh3aW5kb3dNZXRob2QpO1xuICAgICAgICB9O1xuXG4gICAgICAgIC8vIFJlbW92ZSBleGlzdGluZyBsaXN0ZW5lcnNcbiAgICAgICAgZWxlbWVudC5yZW1vdmVFdmVudExpc3RlbmVyKHRyaWdnZXIsIGNhbGxiYWNrKTtcblxuICAgICAgICAvLyBBZGQgbmV3IGxpc3RlbmVyXG4gICAgICAgIGVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcih0cmlnZ2VyLCBjYWxsYmFjayk7XG4gICAgfSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiByZWxvYWRXTUwoKSB7XG4gICAgYWRkV01MRXZlbnRMaXN0ZW5lcnMoKTtcbiAgICBhZGRXTUxXaW5kb3dMaXN0ZW5lcnMoKTtcbn1cbiIsICIvKlxuIF9cdCAgIF9fXHQgIF8gX19cbnwgfFx0IC8gL19fXyBfKF8pIC9fX19fXG58IHwgL3wgLyAvIF9fIGAvIC8gLyBfX18vXG58IHwvIHwvIC8gL18vIC8gLyAoX18gIClcbnxfXy98X18vXFxfXyxfL18vXy9fX19fL1xuVGhlIGVsZWN0cm9uIGFsdGVybmF0aXZlIGZvciBHb1xuKGMpIExlYSBBbnRob255IDIwMTktcHJlc2VudFxuKi9cbi8qIGpzaGludCBlc3ZlcnNpb246IDkgKi9cblxuXG5pbXBvcnQgKiBhcyBDbGlwYm9hcmQgZnJvbSAnLi9jbGlwYm9hcmQnO1xuaW1wb3J0ICogYXMgQXBwbGljYXRpb24gZnJvbSAnLi9hcHBsaWNhdGlvbic7XG5pbXBvcnQgKiBhcyBMb2cgZnJvbSAnLi9sb2cnO1xuaW1wb3J0ICogYXMgU2NyZWVucyBmcm9tICcuL3NjcmVlbnMnO1xuaW1wb3J0IHtQbHVnaW4sIENhbGwsIGNhbGxFcnJvckNhbGxiYWNrLCBjYWxsQ2FsbGJhY2t9IGZyb20gXCIuL2NhbGxzXCI7XG5pbXBvcnQge25ld1dpbmRvd30gZnJvbSBcIi4vd2luZG93XCI7XG5pbXBvcnQge2Rpc3BhdGNoV2FpbHNFdmVudCwgRW1pdCwgT2ZmLCBPZmZBbGwsIE9uLCBPbmNlLCBPbk11bHRpcGxlfSBmcm9tIFwiLi9ldmVudHNcIjtcbmltcG9ydCB7ZGlhbG9nQ2FsbGJhY2ssIGRpYWxvZ0Vycm9yQ2FsbGJhY2ssIEVycm9yLCBJbmZvLCBPcGVuRmlsZSwgUXVlc3Rpb24sIFNhdmVGaWxlLCBXYXJuaW5nLH0gZnJvbSBcIi4vZGlhbG9nc1wiO1xuaW1wb3J0IHtlbmFibGVDb250ZXh0TWVudXN9IGZyb20gXCIuL2NvbnRleHRtZW51XCI7XG5pbXBvcnQge3JlbG9hZFdNTH0gZnJvbSBcIi4vd21sXCI7XG5cbndpbmRvdy53YWlscyA9IHtcbiAgICAuLi5uZXdSdW50aW1lKG51bGwpLFxufTtcblxuLy8gSW50ZXJuYWwgd2FpbHMgZW5kcG9pbnRzXG53aW5kb3cuX3dhaWxzID0ge1xuICAgIGRpYWxvZ0NhbGxiYWNrLFxuICAgIGRpYWxvZ0Vycm9yQ2FsbGJhY2ssXG4gICAgZGlzcGF0Y2hXYWlsc0V2ZW50LFxuICAgIGNhbGxDYWxsYmFjayxcbiAgICBjYWxsRXJyb3JDYWxsYmFjayxcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiBuZXdSdW50aW1lKHdpbmRvd05hbWUpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBDbGlwYm9hcmQ6IHtcbiAgICAgICAgICAgIC4uLkNsaXBib2FyZFxuICAgICAgICB9LFxuICAgICAgICBBcHBsaWNhdGlvbjoge1xuICAgICAgICAgICAgLi4uQXBwbGljYXRpb24sXG4gICAgICAgICAgICBHZXRXaW5kb3dCeU5hbWUod2luZG93TmFtZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBuZXdSdW50aW1lKHdpbmRvd05hbWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBMb2csXG4gICAgICAgIFNjcmVlbnMsXG4gICAgICAgIENhbGwsXG4gICAgICAgIFBsdWdpbixcbiAgICAgICAgV01MOiB7XG4gICAgICAgICAgICBSZWxvYWQ6IHJlbG9hZFdNTCxcbiAgICAgICAgfSxcbiAgICAgICAgRGlhbG9nOiB7XG4gICAgICAgICAgICBJbmZvLFxuICAgICAgICAgICAgV2FybmluZyxcbiAgICAgICAgICAgIEVycm9yLFxuICAgICAgICAgICAgUXVlc3Rpb24sXG4gICAgICAgICAgICBPcGVuRmlsZSxcbiAgICAgICAgICAgIFNhdmVGaWxlLFxuICAgICAgICB9LFxuICAgICAgICBFdmVudHM6IHtcbiAgICAgICAgICAgIEVtaXQsXG4gICAgICAgICAgICBPbixcbiAgICAgICAgICAgIE9uY2UsXG4gICAgICAgICAgICBPbk11bHRpcGxlLFxuICAgICAgICAgICAgT2ZmLFxuICAgICAgICAgICAgT2ZmQWxsLFxuICAgICAgICB9LFxuICAgICAgICBXaW5kb3c6IG5ld1dpbmRvdyh3aW5kb3dOYW1lKSxcbiAgICB9O1xufVxuXG5pZiAoREVCVUcpIHtcbiAgICBjb25zb2xlLmxvZyhcIldhaWxzIHYzLjAuMCBEZWJ1ZyBNb2RlIEVuYWJsZWRcIik7XG59XG5cbmVuYWJsZUNvbnRleHRNZW51cyh0cnVlKTtcblxuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcihcIkRPTUNvbnRlbnRMb2FkZWRcIiwgZnVuY3Rpb24oZXZlbnQpIHtcbiAgICByZWxvYWRXTUwoKTtcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTs7O0FDWUEsTUFBTSxhQUFhLE9BQU8sU0FBUyxTQUFTO0FBRTVDLFdBQVMsWUFBWSxRQUFRLFlBQVksTUFBTTtBQUMzQyxRQUFJLE1BQU0sSUFBSSxJQUFJLFVBQVU7QUFDNUIsUUFBSSxhQUFhLE9BQU8sVUFBVSxNQUFNO0FBQ3hDLFFBQUksTUFBTTtBQUNOLFVBQUksYUFBYSxPQUFPLFFBQVEsS0FBSyxVQUFVLElBQUksQ0FBQztBQUFBLElBQ3hEO0FBQ0EsUUFBSSxlQUFlO0FBQUEsTUFDZixTQUFTLENBQUM7QUFBQSxJQUNkO0FBQ0EsUUFBSSxZQUFZO0FBQ1osbUJBQWEsUUFBUSxxQkFBcUIsSUFBSTtBQUFBLElBQ2xEO0FBQ0EsV0FBTyxJQUFJLFFBQVEsQ0FBQyxTQUFTLFdBQVc7QUFDcEMsWUFBTSxLQUFLLFlBQVksRUFDbEIsS0FBSyxjQUFZO0FBQ2QsWUFBSSxTQUFTLElBQUk7QUFFYixjQUFJLFNBQVMsUUFBUSxJQUFJLGNBQWMsS0FBSyxTQUFTLFFBQVEsSUFBSSxjQUFjLEVBQUUsUUFBUSxrQkFBa0IsTUFBTSxJQUFJO0FBQ2pILG1CQUFPLFNBQVMsS0FBSztBQUFBLFVBQ3pCLE9BQU87QUFDSCxtQkFBTyxTQUFTLEtBQUs7QUFBQSxVQUN6QjtBQUFBLFFBQ0o7QUFDQSxlQUFPLE1BQU0sU0FBUyxVQUFVLENBQUM7QUFBQSxNQUNyQyxDQUFDLEVBQ0EsS0FBSyxVQUFRLFFBQVEsSUFBSSxDQUFDLEVBQzFCLE1BQU0sV0FBUyxPQUFPLEtBQUssQ0FBQztBQUFBLElBQ3JDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxpQkFBaUIsUUFBUSxZQUFZO0FBQ2pELFdBQU8sU0FBVSxRQUFRLE9BQUssTUFBTTtBQUNoQyxhQUFPLFlBQVksU0FBUyxNQUFNLFFBQVEsWUFBWSxJQUFJO0FBQUEsSUFDOUQ7QUFBQSxFQUNKOzs7QURsQ0EsTUFBSSxPQUFPLGlCQUFpQixXQUFXO0FBS2hDLFdBQVMsUUFBUSxNQUFNO0FBQzFCLFNBQUssS0FBSyxXQUFXLEVBQUMsS0FBSSxDQUFDO0FBQUEsRUFDL0I7QUFNTyxXQUFTLE9BQU87QUFDbkIsV0FBTyxLQUFLLE1BQU07QUFBQSxFQUN0Qjs7O0FFN0JBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQWNBLE1BQUlBLFFBQU8saUJBQWlCLGFBQWE7QUFLbEMsV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCO0FBS08sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCO0FBTU8sV0FBUyxPQUFPO0FBQ25CLFNBQUtBLE1BQUssTUFBTTtBQUFBLEVBQ3BCOzs7QUNwQ0E7QUFBQTtBQUFBO0FBQUE7QUFjQSxNQUFJQyxRQUFPLGlCQUFpQixLQUFLO0FBTTFCLFdBQVMsSUFBSSxTQUFTO0FBQ3pCLFdBQU9BLE1BQUssT0FBTyxPQUFPO0FBQUEsRUFDOUI7OztBQ3RCQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFrQkEsTUFBSUMsUUFBTyxpQkFBaUIsU0FBUztBQU05QixXQUFTLFNBQVM7QUFDckIsV0FBT0EsTUFBSyxRQUFRO0FBQUEsRUFDeEI7QUFNTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxZQUFZO0FBQUEsRUFDNUI7QUFPTyxXQUFTLGFBQWE7QUFDekIsV0FBT0EsTUFBSyxZQUFZO0FBQUEsRUFDNUI7OztBQzNDQSxNQUFJLGNBQ0Y7QUFXSyxNQUFJLFNBQVMsQ0FBQyxPQUFPLE9BQU87QUFDakMsUUFBSSxLQUFLO0FBQ1QsUUFBSSxJQUFJO0FBQ1IsV0FBTyxLQUFLO0FBQ1YsWUFBTSxZQUFhLEtBQUssT0FBTyxJQUFJLEtBQU0sQ0FBQztBQUFBLElBQzVDO0FBQ0EsV0FBTztBQUFBLEVBQ1Q7OztBQ0hBLE1BQUlDLFFBQU8saUJBQWlCLE1BQU07QUFFbEMsTUFBSSxnQkFBZ0Isb0JBQUksSUFBSTtBQUU1QixXQUFTLGFBQWE7QUFDbEIsUUFBSTtBQUNKLE9BQUc7QUFDQyxlQUFTLE9BQU87QUFBQSxJQUNwQixTQUFTLGNBQWMsSUFBSSxNQUFNO0FBQ2pDLFdBQU87QUFBQSxFQUNYO0FBRU8sV0FBUyxhQUFhLElBQUksTUFBTSxRQUFRO0FBQzNDLFFBQUksSUFBSSxjQUFjLElBQUksRUFBRTtBQUM1QixRQUFJLEdBQUc7QUFDSCxVQUFJLFFBQVE7QUFDUixVQUFFLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLE1BQzlCLE9BQU87QUFDSCxVQUFFLFFBQVEsSUFBSTtBQUFBLE1BQ2xCO0FBQ0Esb0JBQWMsT0FBTyxFQUFFO0FBQUEsSUFDM0I7QUFBQSxFQUNKO0FBRU8sV0FBUyxrQkFBa0IsSUFBSSxTQUFTO0FBQzNDLFFBQUksSUFBSSxjQUFjLElBQUksRUFBRTtBQUM1QixRQUFJLEdBQUc7QUFDSCxRQUFFLE9BQU8sT0FBTztBQUNoQixvQkFBYyxPQUFPLEVBQUU7QUFBQSxJQUMzQjtBQUFBLEVBQ0o7QUFFQSxXQUFTLFlBQVksTUFBTSxTQUFTO0FBQ2hDLFdBQU8sSUFBSSxRQUFRLENBQUMsU0FBUyxXQUFXO0FBQ3BDLFVBQUksS0FBSyxXQUFXO0FBQ3BCLGdCQUFVLFdBQVcsQ0FBQztBQUN0QixjQUFRLFNBQVMsSUFBSTtBQUNyQixvQkFBYyxJQUFJLElBQUksRUFBQyxTQUFTLE9BQU0sQ0FBQztBQUN2QyxNQUFBQSxNQUFLLE1BQU0sT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVO0FBQ2pDLGVBQU8sS0FBSztBQUNaLHNCQUFjLE9BQU8sRUFBRTtBQUFBLE1BQzNCLENBQUM7QUFBQSxJQUNMLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxLQUFLLFNBQVM7QUFDMUIsV0FBTyxZQUFZLFFBQVEsT0FBTztBQUFBLEVBQ3RDO0FBU08sV0FBUyxPQUFPLFlBQVksZUFBZSxNQUFNO0FBQ3BELFdBQU8sWUFBWSxRQUFRO0FBQUEsTUFDdkIsYUFBYTtBQUFBLE1BQ2IsWUFBWTtBQUFBLE1BQ1o7QUFBQSxNQUNBO0FBQUEsSUFDSixDQUFDO0FBQUEsRUFDTDs7O0FDM0RPLFdBQVMsVUFBVSxZQUFZO0FBQ2xDLFFBQUlDLFFBQU8saUJBQWlCLFVBQVUsVUFBVTtBQUNoRCxXQUFPO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFlSCxRQUFRLE1BQU0sS0FBS0EsTUFBSyxRQUFRO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU1oQyxVQUFVLENBQUMsVUFBVSxLQUFLQSxNQUFLLFlBQVksRUFBQyxNQUFLLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUtsRCxZQUFZLE1BQU0sS0FBS0EsTUFBSyxZQUFZO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLeEMsY0FBYyxNQUFNLEtBQUtBLE1BQUssY0FBYztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU81QyxTQUFTLENBQUMsT0FBTyxXQUFXQSxNQUFLLFdBQVcsRUFBQyxPQUFNLE9BQU0sQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNMUQsTUFBTSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxNQUFNO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU9uQyxZQUFZLENBQUMsT0FBTyxXQUFXLEtBQUtBLE1BQUssY0FBYyxFQUFDLE9BQU0sT0FBTSxDQUFDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BT3JFLFlBQVksQ0FBQyxPQUFPLFdBQVcsS0FBS0EsTUFBSyxjQUFjLEVBQUMsT0FBTSxPQUFNLENBQUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BTXJFLGdCQUFnQixDQUFDLFVBQVUsS0FBS0EsTUFBSyxrQkFBa0IsRUFBQyxhQUFZLE1BQUssQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQU8xRSxhQUFhLENBQUMsR0FBRyxNQUFNQSxNQUFLLGVBQWUsRUFBQyxHQUFFLEVBQUMsQ0FBQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNaEQsVUFBVSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxVQUFVO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFNM0MsUUFBUSxNQUFNO0FBQUUsZUFBT0EsTUFBSyxRQUFRO0FBQUEsTUFBRztBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS3ZDLE1BQU0sTUFBTSxLQUFLQSxNQUFLLE1BQU07QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUs1QixVQUFVLE1BQU0sS0FBS0EsTUFBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEMsTUFBTSxNQUFNLEtBQUtBLE1BQUssTUFBTTtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BSzVCLE9BQU8sTUFBTSxLQUFLQSxNQUFLLE9BQU87QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUs5QixnQkFBZ0IsTUFBTSxLQUFLQSxNQUFLLGdCQUFnQjtBQUFBO0FBQUE7QUFBQTtBQUFBLE1BS2hELFlBQVksTUFBTSxLQUFLQSxNQUFLLFlBQVk7QUFBQTtBQUFBO0FBQUE7QUFBQSxNQUt4QyxVQUFVLE1BQU0sS0FBS0EsTUFBSyxVQUFVO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFLcEMsWUFBWSxNQUFNLEtBQUtBLE1BQUssWUFBWTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsTUFTeEMscUJBQXFCLENBQUMsR0FBRyxHQUFHLEdBQUcsTUFBTSxLQUFLQSxNQUFLLHVCQUF1QixFQUFDLEdBQUcsR0FBRyxHQUFHLEVBQUMsQ0FBQztBQUFBLElBQ3RGO0FBQUEsRUFDSjs7O0FDMUlBLE1BQUlDLFFBQU8saUJBQWlCLFFBQVE7QUFPcEMsTUFBTSxXQUFOLE1BQWU7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLElBUVgsWUFBWSxXQUFXLFVBQVUsY0FBYztBQUMzQyxXQUFLLFlBQVk7QUFFakIsV0FBSyxlQUFlLGdCQUFnQjtBQUdwQyxXQUFLLFdBQVcsQ0FBQyxTQUFTO0FBQ3RCLGlCQUFTLElBQUk7QUFFYixZQUFJLEtBQUssaUJBQWlCLElBQUk7QUFDMUIsaUJBQU87QUFBQSxRQUNYO0FBRUEsYUFBSyxnQkFBZ0I7QUFDckIsZUFBTyxLQUFLLGlCQUFpQjtBQUFBLE1BQ2pDO0FBQUEsSUFDSjtBQUFBLEVBQ0o7QUFVTyxNQUFNLGFBQU4sTUFBaUI7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQU9wQixZQUFZLE1BQU0sT0FBTyxNQUFNO0FBQzNCLFdBQUssT0FBTztBQUNaLFdBQUssT0FBTztBQUFBLElBQ2hCO0FBQUEsRUFDSjtBQUVPLE1BQU0saUJBQWlCLG9CQUFJLElBQUk7QUFXL0IsV0FBUyxXQUFXLFdBQVcsVUFBVSxjQUFjO0FBQzFELFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxLQUFLLENBQUM7QUFDbEQsVUFBTSxlQUFlLElBQUksU0FBUyxXQUFXLFVBQVUsWUFBWTtBQUNuRSxjQUFVLEtBQUssWUFBWTtBQUMzQixtQkFBZSxJQUFJLFdBQVcsU0FBUztBQUN2QyxXQUFPLE1BQU0sWUFBWSxZQUFZO0FBQUEsRUFDekM7QUFVTyxXQUFTLEdBQUcsV0FBVyxVQUFVO0FBQ3BDLFdBQU8sV0FBVyxXQUFXLFVBQVUsRUFBRTtBQUFBLEVBQzdDO0FBVU8sV0FBUyxLQUFLLFdBQVcsVUFBVTtBQUN0QyxXQUFPLFdBQVcsV0FBVyxVQUFVLENBQUM7QUFBQSxFQUM1QztBQU9BLFdBQVMsWUFBWSxVQUFVO0FBQzNCLFVBQU0sWUFBWSxTQUFTO0FBRTNCLFFBQUksWUFBWSxlQUFlLElBQUksU0FBUyxFQUFFLE9BQU8sT0FBSyxNQUFNLFFBQVE7QUFDeEUsUUFBSSxVQUFVLFdBQVcsR0FBRztBQUN4QixxQkFBZSxPQUFPLFNBQVM7QUFBQSxJQUNuQyxPQUFPO0FBQ0gscUJBQWUsSUFBSSxXQUFXLFNBQVM7QUFBQSxJQUMzQztBQUFBLEVBQ0o7QUFRTyxXQUFTLG1CQUFtQixPQUFPO0FBQ3RDLFlBQVEsSUFBSSx1QkFBdUIsRUFBQyxNQUFLLENBQUM7QUFDMUMsUUFBSSxZQUFZLGVBQWUsSUFBSSxNQUFNLElBQUk7QUFDN0MsUUFBSSxXQUFXO0FBRVgsVUFBSSxXQUFXLENBQUM7QUFDaEIsZ0JBQVUsUUFBUSxjQUFZO0FBQzFCLFlBQUksU0FBUyxTQUFTLFNBQVMsS0FBSztBQUNwQyxZQUFJLFFBQVE7QUFDUixtQkFBUyxLQUFLLFFBQVE7QUFBQSxRQUMxQjtBQUFBLE1BQ0osQ0FBQztBQUVELFVBQUksU0FBUyxTQUFTLEdBQUc7QUFDckIsb0JBQVksVUFBVSxPQUFPLE9BQUssQ0FBQyxTQUFTLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZELFlBQUksVUFBVSxXQUFXLEdBQUc7QUFDeEIseUJBQWUsT0FBTyxNQUFNLElBQUk7QUFBQSxRQUNwQyxPQUFPO0FBQ0gseUJBQWUsSUFBSSxNQUFNLE1BQU0sU0FBUztBQUFBLFFBQzVDO0FBQUEsTUFDSjtBQUFBLElBQ0o7QUFBQSxFQUNKO0FBV08sV0FBUyxJQUFJLGNBQWMsc0JBQXNCO0FBQ3BELFFBQUksaUJBQWlCLENBQUMsV0FBVyxHQUFHLG9CQUFvQjtBQUN4RCxtQkFBZSxRQUFRLENBQUFDLGVBQWE7QUFDaEMscUJBQWUsT0FBT0EsVUFBUztBQUFBLElBQ25DLENBQUM7QUFBQSxFQUNMO0FBT08sV0FBUyxTQUFTO0FBQ3JCLG1CQUFlLE1BQU07QUFBQSxFQUN6QjtBQU1PLFdBQVMsS0FBSyxPQUFPO0FBQ3hCLFNBQUtELE1BQUssUUFBUSxLQUFLO0FBQUEsRUFDM0I7OztBQzNLQSxNQUFJRSxRQUFPLGlCQUFpQixRQUFRO0FBRXBDLE1BQUksa0JBQWtCLG9CQUFJLElBQUk7QUFFOUIsV0FBU0MsY0FBYTtBQUNsQixRQUFJO0FBQ0osT0FBRztBQUNDLGVBQVMsT0FBTztBQUFBLElBQ3BCLFNBQVMsZ0JBQWdCLElBQUksTUFBTTtBQUNuQyxXQUFPO0FBQUEsRUFDWDtBQUVPLFdBQVMsZUFBZSxJQUFJLE1BQU0sUUFBUTtBQUM3QyxRQUFJLElBQUksZ0JBQWdCLElBQUksRUFBRTtBQUM5QixRQUFJLEdBQUc7QUFDSCxVQUFJLFFBQVE7QUFDUixVQUFFLFFBQVEsS0FBSyxNQUFNLElBQUksQ0FBQztBQUFBLE1BQzlCLE9BQU87QUFDSCxVQUFFLFFBQVEsSUFBSTtBQUFBLE1BQ2xCO0FBQ0Esc0JBQWdCLE9BQU8sRUFBRTtBQUFBLElBQzdCO0FBQUEsRUFDSjtBQUNPLFdBQVMsb0JBQW9CLElBQUksU0FBUztBQUM3QyxRQUFJLElBQUksZ0JBQWdCLElBQUksRUFBRTtBQUM5QixRQUFJLEdBQUc7QUFDSCxRQUFFLE9BQU8sT0FBTztBQUNoQixzQkFBZ0IsT0FBTyxFQUFFO0FBQUEsSUFDN0I7QUFBQSxFQUNKO0FBRUEsV0FBUyxPQUFPLE1BQU0sU0FBUztBQUMzQixXQUFPLElBQUksUUFBUSxDQUFDLFNBQVMsV0FBVztBQUNwQyxVQUFJLEtBQUtBLFlBQVc7QUFDcEIsZ0JBQVUsV0FBVyxDQUFDO0FBQ3RCLGNBQVEsV0FBVyxJQUFJO0FBQ3ZCLHNCQUFnQixJQUFJLElBQUksRUFBQyxTQUFTLE9BQU0sQ0FBQztBQUN6QyxNQUFBRCxNQUFLLE1BQU0sT0FBTyxFQUFFLE1BQU0sQ0FBQyxVQUFVO0FBQ2pDLGVBQU8sS0FBSztBQUNaLHdCQUFnQixPQUFPLEVBQUU7QUFBQSxNQUM3QixDQUFDO0FBQUEsSUFDTCxDQUFDO0FBQUEsRUFDTDtBQVFPLFdBQVMsS0FBSyxTQUFTO0FBQzFCLFdBQU8sT0FBTyxRQUFRLE9BQU87QUFBQSxFQUNqQztBQU9PLFdBQVMsUUFBUSxTQUFTO0FBQzdCLFdBQU8sT0FBTyxXQUFXLE9BQU87QUFBQSxFQUNwQztBQU9PLFdBQVNFLE9BQU0sU0FBUztBQUMzQixXQUFPLE9BQU8sU0FBUyxPQUFPO0FBQUEsRUFDbEM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7QUFPTyxXQUFTLFNBQVMsU0FBUztBQUM5QixXQUFPLE9BQU8sWUFBWSxPQUFPO0FBQUEsRUFDckM7OztBQ3JIQSxNQUFJQyxRQUFPLGlCQUFpQixhQUFhO0FBRXpDLFdBQVMsZ0JBQWdCLElBQUksR0FBRyxHQUFHLE1BQU07QUFDckMsV0FBT0EsTUFBSyxtQkFBbUIsRUFBQyxJQUFJLEdBQUcsR0FBRyxLQUFJLENBQUM7QUFBQSxFQUNuRDtBQUVPLFdBQVMsbUJBQW1CLFNBQVM7QUFDeEMsUUFBSSxTQUFTO0FBQ1QsYUFBTyxpQkFBaUIsZUFBZSxrQkFBa0I7QUFBQSxJQUM3RCxPQUFPO0FBQ0gsYUFBTyxvQkFBb0IsZUFBZSxrQkFBa0I7QUFBQSxJQUNoRTtBQUFBLEVBQ0o7QUFFQSxXQUFTLG1CQUFtQixPQUFPO0FBQy9CLHVCQUFtQixNQUFNLFFBQVEsS0FBSztBQUFBLEVBQzFDO0FBRUEsV0FBUyxtQkFBbUIsU0FBUyxPQUFPO0FBQ3hDLFFBQUksS0FBSyxRQUFRLGFBQWEsa0JBQWtCO0FBQ2hELFFBQUksSUFBSTtBQUNKLFlBQU0sZUFBZTtBQUNyQixzQkFBZ0IsSUFBSSxNQUFNLFNBQVMsTUFBTSxTQUFTLFFBQVEsYUFBYSx1QkFBdUIsQ0FBQztBQUFBLElBQ25HLE9BQU87QUFDSCxVQUFJLFNBQVMsUUFBUTtBQUNyQixVQUFJLFFBQVE7QUFDUiwyQkFBbUIsUUFBUSxLQUFLO0FBQUEsTUFDcEM7QUFBQSxJQUNKO0FBQUEsRUFDSjs7O0FDM0JBLFdBQVMsVUFBVSxXQUFXLE9BQUssTUFBTTtBQUNyQyxRQUFJLFFBQVEsSUFBSSxXQUFXLFdBQVcsSUFBSTtBQUMxQyxTQUFLLEtBQUs7QUFBQSxFQUNkO0FBRUEsV0FBUyx1QkFBdUI7QUFDNUIsVUFBTSxXQUFXLFNBQVMsaUJBQWlCLGtCQUFrQjtBQUM3RCxhQUFTLFFBQVEsU0FBVSxTQUFTO0FBQ2hDLFlBQU0sWUFBWSxRQUFRLGFBQWEsZ0JBQWdCO0FBQ3ZELFlBQU0sVUFBVSxRQUFRLGFBQWEsa0JBQWtCO0FBQ3ZELFlBQU0sVUFBVSxRQUFRLGFBQWEsa0JBQWtCLEtBQUs7QUFFNUQsVUFBSSxXQUFXLFdBQVk7QUFDdkIsWUFBSSxTQUFTO0FBQ1QsbUJBQVMsRUFBQyxPQUFPLFdBQVcsU0FBUSxTQUFTLFNBQVEsQ0FBQyxFQUFDLE9BQU0sTUFBSyxHQUFFLEVBQUMsT0FBTSxNQUFNLFdBQVUsS0FBSSxDQUFDLEVBQUMsQ0FBQyxFQUFFLEtBQUssU0FBVSxRQUFRO0FBQ3ZILGdCQUFJLFdBQVcsTUFBTTtBQUNqQix3QkFBVSxTQUFTO0FBQUEsWUFDdkI7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSxrQkFBVSxTQUFTO0FBQUEsTUFDdkI7QUFHQSxjQUFRLG9CQUFvQixTQUFTLFFBQVE7QUFHN0MsY0FBUSxpQkFBaUIsU0FBUyxRQUFRO0FBQUEsSUFDOUMsQ0FBQztBQUFBLEVBQ0w7QUFFQSxXQUFTLGlCQUFpQixRQUFRO0FBQzlCLFFBQUksTUFBTSxPQUFPLE1BQU0sTUFBTSxRQUFXO0FBQ3BDLGNBQVEsSUFBSSxtQkFBbUIsU0FBUyxZQUFZO0FBQUEsSUFDeEQ7QUFDQSxVQUFNLE9BQU8sTUFBTSxFQUFFO0FBQUEsRUFDekI7QUFFQSxXQUFTLHdCQUF3QjtBQUM3QixVQUFNLFdBQVcsU0FBUyxpQkFBaUIsbUJBQW1CO0FBQzlELGFBQVMsUUFBUSxTQUFVLFNBQVM7QUFDaEMsWUFBTSxlQUFlLFFBQVEsYUFBYSxpQkFBaUI7QUFDM0QsWUFBTSxVQUFVLFFBQVEsYUFBYSxrQkFBa0I7QUFDdkQsWUFBTSxVQUFVLFFBQVEsYUFBYSxrQkFBa0IsS0FBSztBQUU1RCxVQUFJLFdBQVcsV0FBWTtBQUN2QixZQUFJLFNBQVM7QUFDVCxtQkFBUyxFQUFDLE9BQU8sV0FBVyxTQUFRLFNBQVMsU0FBUSxDQUFDLEVBQUMsT0FBTSxNQUFLLEdBQUUsRUFBQyxPQUFNLE1BQU0sV0FBVSxLQUFJLENBQUMsRUFBQyxDQUFDLEVBQUUsS0FBSyxTQUFVLFFBQVE7QUFDdkgsZ0JBQUksV0FBVyxNQUFNO0FBQ2pCLCtCQUFpQixZQUFZO0FBQUEsWUFDakM7QUFBQSxVQUNKLENBQUM7QUFDRDtBQUFBLFFBQ0o7QUFDQSx5QkFBaUIsWUFBWTtBQUFBLE1BQ2pDO0FBR0EsY0FBUSxvQkFBb0IsU0FBUyxRQUFRO0FBRzdDLGNBQVEsaUJBQWlCLFNBQVMsUUFBUTtBQUFBLElBQzlDLENBQUM7QUFBQSxFQUNMO0FBRU8sV0FBUyxZQUFZO0FBQ3hCLHlCQUFxQjtBQUNyQiwwQkFBc0I7QUFBQSxFQUMxQjs7O0FDbERBLFNBQU8sUUFBUTtBQUFBLElBQ1gsR0FBRyxXQUFXLElBQUk7QUFBQSxFQUN0QjtBQUdBLFNBQU8sU0FBUztBQUFBLElBQ1o7QUFBQSxJQUNBO0FBQUEsSUFDQTtBQUFBLElBQ0E7QUFBQSxJQUNBO0FBQUEsRUFDSjtBQUVPLFdBQVMsV0FBVyxZQUFZO0FBQ25DLFdBQU87QUFBQSxNQUNILFdBQVc7QUFBQSxRQUNQLEdBQUc7QUFBQSxNQUNQO0FBQUEsTUFDQSxhQUFhO0FBQUEsUUFDVCxHQUFHO0FBQUEsUUFDSCxnQkFBZ0JDLGFBQVk7QUFDeEIsaUJBQU8sV0FBV0EsV0FBVTtBQUFBLFFBQ2hDO0FBQUEsTUFDSjtBQUFBLE1BQ0E7QUFBQSxNQUNBO0FBQUEsTUFDQTtBQUFBLE1BQ0E7QUFBQSxNQUNBLEtBQUs7QUFBQSxRQUNELFFBQVE7QUFBQSxNQUNaO0FBQUEsTUFDQSxRQUFRO0FBQUEsUUFDSjtBQUFBLFFBQ0E7QUFBQSxRQUNBLE9BQUFDO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsTUFDSjtBQUFBLE1BQ0EsUUFBUTtBQUFBLFFBQ0o7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLE1BQ0o7QUFBQSxNQUNBLFFBQVEsVUFBVSxVQUFVO0FBQUEsSUFDaEM7QUFBQSxFQUNKO0FBRUEsTUFBSSxNQUFPO0FBQ1AsWUFBUSxJQUFJLGlDQUFpQztBQUFBLEVBQ2pEO0FBRUEscUJBQW1CLElBQUk7QUFFdkIsV0FBUyxpQkFBaUIsb0JBQW9CLFNBQVMsT0FBTztBQUMxRCxjQUFVO0FBQUEsRUFDZCxDQUFDOyIsCiAgIm5hbWVzIjogWyJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJjYWxsIiwgImNhbGwiLCAiY2FsbCIsICJldmVudE5hbWUiLCAiY2FsbCIsICJnZW5lcmF0ZUlEIiwgIkVycm9yIiwgImNhbGwiLCAid2luZG93TmFtZSIsICJFcnJvciJdCn0K diff --git a/v3/internal/runtime/runtime_debug_linux.go b/v3/internal/runtime/runtime_debug_linux.go new file mode 100644 index 000000000..c29408392 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_linux.go @@ -0,0 +1,8 @@ +//go:build linux && !production + +package runtime + +import _ "embed" + +//go:embed runtime_debug_desktop_linux.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_debug_windows.go b/v3/internal/runtime/runtime_debug_windows.go new file mode 100644 index 000000000..09a3a9198 --- /dev/null +++ b/v3/internal/runtime/runtime_debug_windows.go @@ -0,0 +1,8 @@ +//go:build windows && !production + +package runtime + +import _ "embed" + +//go:embed runtime_debug_desktop_windows.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_production_darwin.go b/v3/internal/runtime/runtime_production_darwin.go new file mode 100644 index 000000000..be2411d9e --- /dev/null +++ b/v3/internal/runtime/runtime_production_darwin.go @@ -0,0 +1,8 @@ +//go:build darwin && production + +package runtime + +import _ "embed" + +//go:embed runtime_production_desktop_darwin.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_production_desktop_darwin.js b/v3/internal/runtime/runtime_production_desktop_darwin.js new file mode 100644 index 000000000..be519faa5 --- /dev/null +++ b/v3/internal/runtime/runtime_production_desktop_darwin.js @@ -0,0 +1 @@ +(()=>{var Z=Object.defineProperty;var p=(t,e)=>{for(var n in e)Z(t,n,{get:e[n],enumerable:!0})};var v={};p(v,{SetText:()=>te,Text:()=>ne});var $=window.location.origin+"/wails/runtime";function ee(t,e,n){let i=new URL($);i.searchParams.append("method",t),n&&i.searchParams.append("args",JSON.stringify(n));let o={headers:{}};return e&&(o.headers["x-wails-window-name"]=e),new Promise((l,f)=>{fetch(i,o).then(a=>{if(a.ok)return a.headers.get("Content-Type")&&a.headers.get("Content-Type").indexOf("application/json")!==-1?a.json():a.text();f(Error(a.statusText))}).then(a=>l(a)).catch(a=>f(a))})}function r(t,e){return function(n,i=null){return ee(t+"."+n,e,i)}}var k=r("clipboard");function te(t){k("SetText",{text:t})}function ne(){return k("Text")}var S={};p(S,{Hide:()=>ie,Quit:()=>re,Show:()=>oe});var C=r("application");function ie(){C("Hide")}function oe(){C("Show")}function re(){C("Quit")}var M={};p(M,{Log:()=>ae});var le=r("log");function ae(t){return le("Log",t)}var E={};p(E,{GetAll:()=>ue,GetCurrent:()=>se,GetPrimary:()=>ce});var b=r("screens");function ue(){return b("GetAll")}function ce(){return b("GetPrimary")}function se(){return b("GetCurrent")}var fe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var w=(t=21)=>{let e="",n=t;for(;n--;)e+=fe[Math.random()*64|0];return e};var de=r("call"),c=new Map;function me(){let t;do t=w();while(c.has(t));return t}function A(t,e,n){let i=c.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),c.delete(t))}function W(t,e){let n=c.get(t);n&&(n.reject(e),c.delete(t))}function R(t,e){return new Promise((n,i)=>{let o=me();e=e||{},e["call-id"]=o,c.set(o,{resolve:n,reject:i}),de(t,e).catch(l=>{i(l),c.delete(o)})})}function T(t){return R("Call",t)}function P(t,e,...n){return R("Call",{packageName:"wails-plugins",structName:t,methodName:e,args:n})}function y(t){let e=r("window",t);return{Center:()=>void e("Center"),SetTitle:n=>void e("SetTitle",{title:n}),Fullscreen:()=>void e("Fullscreen"),UnFullscreen:()=>void e("UnFullscreen"),SetSize:(n,i)=>e("SetSize",{width:n,height:i}),Size:()=>e("Size"),SetMaxSize:(n,i)=>void e("SetMaxSize",{width:n,height:i}),SetMinSize:(n,i)=>void e("SetMinSize",{width:n,height:i}),SetAlwaysOnTop:n=>void e("SetAlwaysOnTop",{alwaysOnTop:n}),SetPosition:(n,i)=>e("SetPosition",{x:n,y:i}),Position:()=>e("Position"),Screen:()=>e("Screen"),Hide:()=>void e("Hide"),Maximise:()=>void e("Maximise"),Show:()=>void e("Show"),Close:()=>void e("Close"),ToggleMaximise:()=>void e("ToggleMaximise"),UnMaximise:()=>void e("UnMaximise"),Minimise:()=>void e("Minimise"),UnMinimise:()=>void e("UnMinimise"),SetBackgroundColour:(n,i,o,l)=>void e("SetBackgroundColour",{r:n,g:i,b:o,a:l})}}var pe=r("events"),L=class{constructor(e,n,i){this.eventName=e,this.maxCallbacks=i||-1,this.Callback=o=>(n(o),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},g=class{constructor(e,n=null){this.name=e,this.data=n}},u=new Map;function x(t,e,n){let i=u.get(t)||[],o=new L(t,e,n);return i.push(o),u.set(t,i),()=>we(o)}function N(t,e){return x(t,e,-1)}function F(t,e){return x(t,e,1)}function we(t){let e=t.eventName,n=u.get(e).filter(i=>i!==t);n.length===0?u.delete(e):u.set(e,n)}function D(t){console.log("dispatching event: ",{event:t});let e=u.get(t.name);if(e){let n=[];e.forEach(i=>{i.Callback(t)&&n.push(i)}),n.length>0&&(e=e.filter(i=>!n.includes(i)),e.length===0?u.delete(t.name):u.set(t.name,e))}}function U(t,...e){[t,...e].forEach(i=>{u.delete(i)})}function z(){u.clear()}function h(t){pe("Emit",t)}var ge=r("dialog"),s=new Map;function xe(){let t;do t=w();while(s.has(t));return t}function G(t,e,n){let i=s.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),s.delete(t))}function B(t,e){let n=s.get(t);n&&(n.reject(e),s.delete(t))}function d(t,e){return new Promise((n,i)=>{let o=xe();e=e||{},e["dialog-id"]=o,s.set(o,{resolve:n,reject:i}),ge(t,e).catch(l=>{i(l),s.delete(o)})})}function I(t){return d("Info",t)}function Q(t){return d("Warning",t)}function H(t){return d("Error",t)}function m(t){return d("Question",t)}function J(t){return d("OpenFile",t)}function Y(t){return d("SaveFile",t)}var he=r("contextmenu");function ve(t,e,n,i){return he("OpenContextMenu",{id:t,x:e,y:n,data:i})}function j(t){t?window.addEventListener("contextmenu",q):window.removeEventListener("contextmenu",q)}function q(t){X(t.target,t)}function X(t,e){let n=t.getAttribute("data-contextmenu");if(n)e.preventDefault(),ve(n,e.clientX,e.clientY,t.getAttribute("data-contextmenu-data"));else{let i=t.parentElement;i&&X(i,e)}}function _(t,e=null){let n=new g(t,e);h(n)}function Ce(){document.querySelectorAll("[data-wml-event]").forEach(function(e){let n=e.getAttribute("data-wml-event"),i=e.getAttribute("data-wml-confirm"),o=e.getAttribute("data-wml-trigger")||"click",l=function(){if(i){m({Title:"Confirm",Message:i,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(function(f){f!=="No"&&_(n)});return}_(n)};e.removeEventListener(o,l),e.addEventListener(o,l)})}function K(t){wails.Window[t]===void 0&&console.log("Window method "+t+" not found"),wails.Window[t]()}function Se(){document.querySelectorAll("[data-wml-window]").forEach(function(e){let n=e.getAttribute("data-wml-window"),i=e.getAttribute("data-wml-confirm"),o=e.getAttribute("data-wml-trigger")||"click",l=function(){if(i){m({Title:"Confirm",Message:i,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(function(f){f!=="No"&&K(n)});return}K(n)};e.removeEventListener(o,l),e.addEventListener(o,l)})}function O(){Ce(),Se()}window.wails={...V(null)};window._wails={dialogCallback:G,dialogErrorCallback:B,dispatchWailsEvent:D,callCallback:A,callErrorCallback:W};function V(t){return{Clipboard:{...v},Application:{...S,GetWindowByName(e){return V(e)}},Log:M,Screens:E,Call:T,Plugin:P,WML:{Reload:O},Dialog:{Info:I,Warning:Q,Error:H,Question:m,OpenFile:J,SaveFile:Y},Events:{Emit:h,On:N,Once:F,OnMultiple:x,Off:U,OffAll:z},Window:y(t)}}console.log("Wails v3.0.0 Debug Mode Enabled");j(!0);document.addEventListener("DOMContentLoaded",function(t){O()});})(); diff --git a/v3/internal/runtime/runtime_production_desktop_linux.js b/v3/internal/runtime/runtime_production_desktop_linux.js new file mode 100644 index 000000000..be519faa5 --- /dev/null +++ b/v3/internal/runtime/runtime_production_desktop_linux.js @@ -0,0 +1 @@ +(()=>{var Z=Object.defineProperty;var p=(t,e)=>{for(var n in e)Z(t,n,{get:e[n],enumerable:!0})};var v={};p(v,{SetText:()=>te,Text:()=>ne});var $=window.location.origin+"/wails/runtime";function ee(t,e,n){let i=new URL($);i.searchParams.append("method",t),n&&i.searchParams.append("args",JSON.stringify(n));let o={headers:{}};return e&&(o.headers["x-wails-window-name"]=e),new Promise((l,f)=>{fetch(i,o).then(a=>{if(a.ok)return a.headers.get("Content-Type")&&a.headers.get("Content-Type").indexOf("application/json")!==-1?a.json():a.text();f(Error(a.statusText))}).then(a=>l(a)).catch(a=>f(a))})}function r(t,e){return function(n,i=null){return ee(t+"."+n,e,i)}}var k=r("clipboard");function te(t){k("SetText",{text:t})}function ne(){return k("Text")}var S={};p(S,{Hide:()=>ie,Quit:()=>re,Show:()=>oe});var C=r("application");function ie(){C("Hide")}function oe(){C("Show")}function re(){C("Quit")}var M={};p(M,{Log:()=>ae});var le=r("log");function ae(t){return le("Log",t)}var E={};p(E,{GetAll:()=>ue,GetCurrent:()=>se,GetPrimary:()=>ce});var b=r("screens");function ue(){return b("GetAll")}function ce(){return b("GetPrimary")}function se(){return b("GetCurrent")}var fe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var w=(t=21)=>{let e="",n=t;for(;n--;)e+=fe[Math.random()*64|0];return e};var de=r("call"),c=new Map;function me(){let t;do t=w();while(c.has(t));return t}function A(t,e,n){let i=c.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),c.delete(t))}function W(t,e){let n=c.get(t);n&&(n.reject(e),c.delete(t))}function R(t,e){return new Promise((n,i)=>{let o=me();e=e||{},e["call-id"]=o,c.set(o,{resolve:n,reject:i}),de(t,e).catch(l=>{i(l),c.delete(o)})})}function T(t){return R("Call",t)}function P(t,e,...n){return R("Call",{packageName:"wails-plugins",structName:t,methodName:e,args:n})}function y(t){let e=r("window",t);return{Center:()=>void e("Center"),SetTitle:n=>void e("SetTitle",{title:n}),Fullscreen:()=>void e("Fullscreen"),UnFullscreen:()=>void e("UnFullscreen"),SetSize:(n,i)=>e("SetSize",{width:n,height:i}),Size:()=>e("Size"),SetMaxSize:(n,i)=>void e("SetMaxSize",{width:n,height:i}),SetMinSize:(n,i)=>void e("SetMinSize",{width:n,height:i}),SetAlwaysOnTop:n=>void e("SetAlwaysOnTop",{alwaysOnTop:n}),SetPosition:(n,i)=>e("SetPosition",{x:n,y:i}),Position:()=>e("Position"),Screen:()=>e("Screen"),Hide:()=>void e("Hide"),Maximise:()=>void e("Maximise"),Show:()=>void e("Show"),Close:()=>void e("Close"),ToggleMaximise:()=>void e("ToggleMaximise"),UnMaximise:()=>void e("UnMaximise"),Minimise:()=>void e("Minimise"),UnMinimise:()=>void e("UnMinimise"),SetBackgroundColour:(n,i,o,l)=>void e("SetBackgroundColour",{r:n,g:i,b:o,a:l})}}var pe=r("events"),L=class{constructor(e,n,i){this.eventName=e,this.maxCallbacks=i||-1,this.Callback=o=>(n(o),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},g=class{constructor(e,n=null){this.name=e,this.data=n}},u=new Map;function x(t,e,n){let i=u.get(t)||[],o=new L(t,e,n);return i.push(o),u.set(t,i),()=>we(o)}function N(t,e){return x(t,e,-1)}function F(t,e){return x(t,e,1)}function we(t){let e=t.eventName,n=u.get(e).filter(i=>i!==t);n.length===0?u.delete(e):u.set(e,n)}function D(t){console.log("dispatching event: ",{event:t});let e=u.get(t.name);if(e){let n=[];e.forEach(i=>{i.Callback(t)&&n.push(i)}),n.length>0&&(e=e.filter(i=>!n.includes(i)),e.length===0?u.delete(t.name):u.set(t.name,e))}}function U(t,...e){[t,...e].forEach(i=>{u.delete(i)})}function z(){u.clear()}function h(t){pe("Emit",t)}var ge=r("dialog"),s=new Map;function xe(){let t;do t=w();while(s.has(t));return t}function G(t,e,n){let i=s.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),s.delete(t))}function B(t,e){let n=s.get(t);n&&(n.reject(e),s.delete(t))}function d(t,e){return new Promise((n,i)=>{let o=xe();e=e||{},e["dialog-id"]=o,s.set(o,{resolve:n,reject:i}),ge(t,e).catch(l=>{i(l),s.delete(o)})})}function I(t){return d("Info",t)}function Q(t){return d("Warning",t)}function H(t){return d("Error",t)}function m(t){return d("Question",t)}function J(t){return d("OpenFile",t)}function Y(t){return d("SaveFile",t)}var he=r("contextmenu");function ve(t,e,n,i){return he("OpenContextMenu",{id:t,x:e,y:n,data:i})}function j(t){t?window.addEventListener("contextmenu",q):window.removeEventListener("contextmenu",q)}function q(t){X(t.target,t)}function X(t,e){let n=t.getAttribute("data-contextmenu");if(n)e.preventDefault(),ve(n,e.clientX,e.clientY,t.getAttribute("data-contextmenu-data"));else{let i=t.parentElement;i&&X(i,e)}}function _(t,e=null){let n=new g(t,e);h(n)}function Ce(){document.querySelectorAll("[data-wml-event]").forEach(function(e){let n=e.getAttribute("data-wml-event"),i=e.getAttribute("data-wml-confirm"),o=e.getAttribute("data-wml-trigger")||"click",l=function(){if(i){m({Title:"Confirm",Message:i,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(function(f){f!=="No"&&_(n)});return}_(n)};e.removeEventListener(o,l),e.addEventListener(o,l)})}function K(t){wails.Window[t]===void 0&&console.log("Window method "+t+" not found"),wails.Window[t]()}function Se(){document.querySelectorAll("[data-wml-window]").forEach(function(e){let n=e.getAttribute("data-wml-window"),i=e.getAttribute("data-wml-confirm"),o=e.getAttribute("data-wml-trigger")||"click",l=function(){if(i){m({Title:"Confirm",Message:i,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(function(f){f!=="No"&&K(n)});return}K(n)};e.removeEventListener(o,l),e.addEventListener(o,l)})}function O(){Ce(),Se()}window.wails={...V(null)};window._wails={dialogCallback:G,dialogErrorCallback:B,dispatchWailsEvent:D,callCallback:A,callErrorCallback:W};function V(t){return{Clipboard:{...v},Application:{...S,GetWindowByName(e){return V(e)}},Log:M,Screens:E,Call:T,Plugin:P,WML:{Reload:O},Dialog:{Info:I,Warning:Q,Error:H,Question:m,OpenFile:J,SaveFile:Y},Events:{Emit:h,On:N,Once:F,OnMultiple:x,Off:U,OffAll:z},Window:y(t)}}console.log("Wails v3.0.0 Debug Mode Enabled");j(!0);document.addEventListener("DOMContentLoaded",function(t){O()});})(); diff --git a/v3/internal/runtime/runtime_production_desktop_windows.js b/v3/internal/runtime/runtime_production_desktop_windows.js new file mode 100644 index 000000000..be519faa5 --- /dev/null +++ b/v3/internal/runtime/runtime_production_desktop_windows.js @@ -0,0 +1 @@ +(()=>{var Z=Object.defineProperty;var p=(t,e)=>{for(var n in e)Z(t,n,{get:e[n],enumerable:!0})};var v={};p(v,{SetText:()=>te,Text:()=>ne});var $=window.location.origin+"/wails/runtime";function ee(t,e,n){let i=new URL($);i.searchParams.append("method",t),n&&i.searchParams.append("args",JSON.stringify(n));let o={headers:{}};return e&&(o.headers["x-wails-window-name"]=e),new Promise((l,f)=>{fetch(i,o).then(a=>{if(a.ok)return a.headers.get("Content-Type")&&a.headers.get("Content-Type").indexOf("application/json")!==-1?a.json():a.text();f(Error(a.statusText))}).then(a=>l(a)).catch(a=>f(a))})}function r(t,e){return function(n,i=null){return ee(t+"."+n,e,i)}}var k=r("clipboard");function te(t){k("SetText",{text:t})}function ne(){return k("Text")}var S={};p(S,{Hide:()=>ie,Quit:()=>re,Show:()=>oe});var C=r("application");function ie(){C("Hide")}function oe(){C("Show")}function re(){C("Quit")}var M={};p(M,{Log:()=>ae});var le=r("log");function ae(t){return le("Log",t)}var E={};p(E,{GetAll:()=>ue,GetCurrent:()=>se,GetPrimary:()=>ce});var b=r("screens");function ue(){return b("GetAll")}function ce(){return b("GetPrimary")}function se(){return b("GetCurrent")}var fe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";var w=(t=21)=>{let e="",n=t;for(;n--;)e+=fe[Math.random()*64|0];return e};var de=r("call"),c=new Map;function me(){let t;do t=w();while(c.has(t));return t}function A(t,e,n){let i=c.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),c.delete(t))}function W(t,e){let n=c.get(t);n&&(n.reject(e),c.delete(t))}function R(t,e){return new Promise((n,i)=>{let o=me();e=e||{},e["call-id"]=o,c.set(o,{resolve:n,reject:i}),de(t,e).catch(l=>{i(l),c.delete(o)})})}function T(t){return R("Call",t)}function P(t,e,...n){return R("Call",{packageName:"wails-plugins",structName:t,methodName:e,args:n})}function y(t){let e=r("window",t);return{Center:()=>void e("Center"),SetTitle:n=>void e("SetTitle",{title:n}),Fullscreen:()=>void e("Fullscreen"),UnFullscreen:()=>void e("UnFullscreen"),SetSize:(n,i)=>e("SetSize",{width:n,height:i}),Size:()=>e("Size"),SetMaxSize:(n,i)=>void e("SetMaxSize",{width:n,height:i}),SetMinSize:(n,i)=>void e("SetMinSize",{width:n,height:i}),SetAlwaysOnTop:n=>void e("SetAlwaysOnTop",{alwaysOnTop:n}),SetPosition:(n,i)=>e("SetPosition",{x:n,y:i}),Position:()=>e("Position"),Screen:()=>e("Screen"),Hide:()=>void e("Hide"),Maximise:()=>void e("Maximise"),Show:()=>void e("Show"),Close:()=>void e("Close"),ToggleMaximise:()=>void e("ToggleMaximise"),UnMaximise:()=>void e("UnMaximise"),Minimise:()=>void e("Minimise"),UnMinimise:()=>void e("UnMinimise"),SetBackgroundColour:(n,i,o,l)=>void e("SetBackgroundColour",{r:n,g:i,b:o,a:l})}}var pe=r("events"),L=class{constructor(e,n,i){this.eventName=e,this.maxCallbacks=i||-1,this.Callback=o=>(n(o),this.maxCallbacks===-1?!1:(this.maxCallbacks-=1,this.maxCallbacks===0))}},g=class{constructor(e,n=null){this.name=e,this.data=n}},u=new Map;function x(t,e,n){let i=u.get(t)||[],o=new L(t,e,n);return i.push(o),u.set(t,i),()=>we(o)}function N(t,e){return x(t,e,-1)}function F(t,e){return x(t,e,1)}function we(t){let e=t.eventName,n=u.get(e).filter(i=>i!==t);n.length===0?u.delete(e):u.set(e,n)}function D(t){console.log("dispatching event: ",{event:t});let e=u.get(t.name);if(e){let n=[];e.forEach(i=>{i.Callback(t)&&n.push(i)}),n.length>0&&(e=e.filter(i=>!n.includes(i)),e.length===0?u.delete(t.name):u.set(t.name,e))}}function U(t,...e){[t,...e].forEach(i=>{u.delete(i)})}function z(){u.clear()}function h(t){pe("Emit",t)}var ge=r("dialog"),s=new Map;function xe(){let t;do t=w();while(s.has(t));return t}function G(t,e,n){let i=s.get(t);i&&(n?i.resolve(JSON.parse(e)):i.resolve(e),s.delete(t))}function B(t,e){let n=s.get(t);n&&(n.reject(e),s.delete(t))}function d(t,e){return new Promise((n,i)=>{let o=xe();e=e||{},e["dialog-id"]=o,s.set(o,{resolve:n,reject:i}),ge(t,e).catch(l=>{i(l),s.delete(o)})})}function I(t){return d("Info",t)}function Q(t){return d("Warning",t)}function H(t){return d("Error",t)}function m(t){return d("Question",t)}function J(t){return d("OpenFile",t)}function Y(t){return d("SaveFile",t)}var he=r("contextmenu");function ve(t,e,n,i){return he("OpenContextMenu",{id:t,x:e,y:n,data:i})}function j(t){t?window.addEventListener("contextmenu",q):window.removeEventListener("contextmenu",q)}function q(t){X(t.target,t)}function X(t,e){let n=t.getAttribute("data-contextmenu");if(n)e.preventDefault(),ve(n,e.clientX,e.clientY,t.getAttribute("data-contextmenu-data"));else{let i=t.parentElement;i&&X(i,e)}}function _(t,e=null){let n=new g(t,e);h(n)}function Ce(){document.querySelectorAll("[data-wml-event]").forEach(function(e){let n=e.getAttribute("data-wml-event"),i=e.getAttribute("data-wml-confirm"),o=e.getAttribute("data-wml-trigger")||"click",l=function(){if(i){m({Title:"Confirm",Message:i,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(function(f){f!=="No"&&_(n)});return}_(n)};e.removeEventListener(o,l),e.addEventListener(o,l)})}function K(t){wails.Window[t]===void 0&&console.log("Window method "+t+" not found"),wails.Window[t]()}function Se(){document.querySelectorAll("[data-wml-window]").forEach(function(e){let n=e.getAttribute("data-wml-window"),i=e.getAttribute("data-wml-confirm"),o=e.getAttribute("data-wml-trigger")||"click",l=function(){if(i){m({Title:"Confirm",Message:i,Buttons:[{Label:"Yes"},{Label:"No",IsDefault:!0}]}).then(function(f){f!=="No"&&K(n)});return}K(n)};e.removeEventListener(o,l),e.addEventListener(o,l)})}function O(){Ce(),Se()}window.wails={...V(null)};window._wails={dialogCallback:G,dialogErrorCallback:B,dispatchWailsEvent:D,callCallback:A,callErrorCallback:W};function V(t){return{Clipboard:{...v},Application:{...S,GetWindowByName(e){return V(e)}},Log:M,Screens:E,Call:T,Plugin:P,WML:{Reload:O},Dialog:{Info:I,Warning:Q,Error:H,Question:m,OpenFile:J,SaveFile:Y},Events:{Emit:h,On:N,Once:F,OnMultiple:x,Off:U,OffAll:z},Window:y(t)}}console.log("Wails v3.0.0 Debug Mode Enabled");j(!0);document.addEventListener("DOMContentLoaded",function(t){O()});})(); diff --git a/v3/internal/runtime/runtime_production_linux.go b/v3/internal/runtime/runtime_production_linux.go new file mode 100644 index 000000000..d7c2bd1ef --- /dev/null +++ b/v3/internal/runtime/runtime_production_linux.go @@ -0,0 +1,8 @@ +//go:build linux && production + +package runtime + +import _ "embed" + +//go:embed runtime_production_desktop_linux.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/runtime_production_windows.go b/v3/internal/runtime/runtime_production_windows.go new file mode 100644 index 000000000..8ab6d6199 --- /dev/null +++ b/v3/internal/runtime/runtime_production_windows.go @@ -0,0 +1,8 @@ +//go:build windows && production + +package runtime + +import _ "embed" + +//go:embed runtime_production_desktop_windows.js +var DesktopRuntime []byte diff --git a/v3/internal/runtime/vite.config.ts b/v3/internal/runtime/vite.config.ts new file mode 100644 index 000000000..eb0831c65 --- /dev/null +++ b/v3/internal/runtime/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'happy-dom', + }, +}) \ No newline at end of file diff --git a/v3/internal/templates/_base/default/Taskfile.tmpl.yml b/v3/internal/templates/_base/default/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/_base/default/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/_base/default/build/Info.dev.plist.tmpl b/v3/internal/templates/_base/default/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/_base/default/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/_base/default/build/Info.plist.tmpl b/v3/internal/templates/_base/default/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/_base/default/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/_base/default/build/appicon.png b/v3/internal/templates/_base/default/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/_base/default/build/appicon.png differ diff --git a/v3/internal/templates/_base/default/build/icons.icns b/v3/internal/templates/_base/default/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/_base/default/build/icons.icns differ diff --git a/v3/internal/templates/_base/default/frontend/public/wails.png b/v3/internal/templates/_base/default/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/_base/default/frontend/public/wails.png differ diff --git a/v3/internal/templates/_base/default/go.mod.tmpl b/v3/internal/templates/_base/default/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/_base/default/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/_base/default/go.sum.tmpl b/v3/internal/templates/_base/default/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/_base/default/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/_base/default/main.go.tmpl b/v3/internal/templates/_base/default/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/_base/default/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/_base/lit-ts/frontend/index.html b/v3/internal/templates/_base/lit-ts/frontend/index.html new file mode 100644 index 000000000..b4898b214 --- /dev/null +++ b/v3/internal/templates/_base/lit-ts/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + TS + + + + + +

Wails + Lit

+
+ + diff --git a/v3/internal/templates/_base/lit/frontend/index.html b/v3/internal/templates/_base/lit/frontend/index.html new file mode 100644 index 000000000..0f030a2f4 --- /dev/null +++ b/v3/internal/templates/_base/lit/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + Wails + Lit + + + + + +

Wails + Lit

+
+ + diff --git a/v3/internal/templates/_base/preact-ts/frontend/index.html b/v3/internal/templates/_base/preact-ts/frontend/index.html new file mode 100644 index 000000000..c467c421b --- /dev/null +++ b/v3/internal/templates/_base/preact-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + TS + + +
+ + + diff --git a/v3/internal/templates/_base/preact/frontend/index.html b/v3/internal/templates/_base/preact/frontend/index.html new file mode 100644 index 000000000..a3c698def --- /dev/null +++ b/v3/internal/templates/_base/preact/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + + +
+ + + diff --git a/v3/internal/templates/_base/react-swc-ts/frontend/index.html b/v3/internal/templates/_base/react-swc-ts/frontend/index.html new file mode 100644 index 000000000..44b5c452b --- /dev/null +++ b/v3/internal/templates/_base/react-swc-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + TS + + +
+ + + diff --git a/v3/internal/templates/_base/react-swc/frontend/index.html b/v3/internal/templates/_base/react-swc/frontend/index.html new file mode 100644 index 000000000..cc495265c --- /dev/null +++ b/v3/internal/templates/_base/react-swc/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + + +
+ + + diff --git a/v3/internal/templates/_base/react-ts/frontend/index.html b/v3/internal/templates/_base/react-ts/frontend/index.html new file mode 100644 index 000000000..44b5c452b --- /dev/null +++ b/v3/internal/templates/_base/react-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + TS + + +
+ + + diff --git a/v3/internal/templates/_base/react/frontend/index.html b/v3/internal/templates/_base/react/frontend/index.html new file mode 100644 index 000000000..cc495265c --- /dev/null +++ b/v3/internal/templates/_base/react/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + React + + +
+ + + diff --git a/v3/internal/templates/_base/svelte-ts/frontend/index.html b/v3/internal/templates/_base/svelte-ts/frontend/index.html new file mode 100644 index 000000000..2d5c0f2de --- /dev/null +++ b/v3/internal/templates/_base/svelte-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + TS + + +
+ + + diff --git a/v3/internal/templates/_base/svelte/frontend/README.md b/v3/internal/templates/_base/svelte/frontend/README.md new file mode 100644 index 000000000..d0097d9d9 --- /dev/null +++ b/v3/internal/templates/_base/svelte/frontend/README.md @@ -0,0 +1 @@ +# Wails + Svelte diff --git a/v3/internal/templates/_base/svelte/frontend/index.html b/v3/internal/templates/_base/svelte/frontend/index.html new file mode 100644 index 000000000..d12bc9069 --- /dev/null +++ b/v3/internal/templates/_base/svelte/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Svelte + + +
+ + + diff --git a/v3/internal/templates/_base/svelte/frontend/src/App.svelte b/v3/internal/templates/_base/svelte/frontend/src/App.svelte new file mode 100644 index 000000000..c677e3838 --- /dev/null +++ b/v3/internal/templates/_base/svelte/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Wails + Svelte

+ +
+ +
+ +

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

+ +

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

+
+ + diff --git a/v3/internal/templates/_base/vanilla-ts/frontend/index.html b/v3/internal/templates/_base/vanilla-ts/frontend/index.html new file mode 100644 index 000000000..8c692073a --- /dev/null +++ b/v3/internal/templates/_base/vanilla-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + TS + + +
+ + + diff --git a/v3/internal/templates/_base/vanilla/frontend/index.html b/v3/internal/templates/_base/vanilla/frontend/index.html new file mode 100644 index 000000000..c4eb3cc44 --- /dev/null +++ b/v3/internal/templates/_base/vanilla/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails App + + +
+ + + diff --git a/v3/internal/templates/_base/vue-ts/frontend/index.html b/v3/internal/templates/_base/vue-ts/frontend/index.html new file mode 100644 index 000000000..28870032c --- /dev/null +++ b/v3/internal/templates/_base/vue-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Vue + TS + + +
+ + + diff --git a/v3/internal/templates/_base/vue/frontend/index.html b/v3/internal/templates/_base/vue/frontend/index.html new file mode 100644 index 000000000..5057cc3c1 --- /dev/null +++ b/v3/internal/templates/_base/vue/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Vue + + +
+ + + diff --git a/v3/internal/templates/lit-ts/Taskfile.tmpl.yml b/v3/internal/templates/lit-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/lit-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/lit-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/lit-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/lit-ts/build/Info.plist.tmpl b/v3/internal/templates/lit-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/lit-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/lit-ts/build/appicon.png b/v3/internal/templates/lit-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/lit-ts/build/appicon.png differ diff --git a/v3/internal/templates/lit-ts/build/icons.icns b/v3/internal/templates/lit-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/lit-ts/build/icons.icns differ 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..ca539730d --- /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..42a86bd4b --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "dist/my-element.es.js", + "exports": { + ".": "./dist/my-element.es.js" + }, + "types": "types/my-element.d.ts", + "files": [ + "dist", + "types" + ], + "scripts": { + "dev": "vite", + "build": "tsc && vite build" + }, + "dependencies": { + "lit": "^2.4.1" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ 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/assets/lit.svg b/v3/internal/templates/lit-ts/frontend/src/assets/lit.svg new file mode 100644 index 000000000..4a9c1fe66 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/assets/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/lit-ts/frontend/src/index.css b/v3/internal/templates/lit-ts/frontend/src/index.css new file mode 100644 index 000000000..d39ac2e34 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/index.css @@ -0,0 +1,40 @@ +: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; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } +} 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..9f1c76e89 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/src/my-element.ts @@ -0,0 +1,125 @@ +import { LitElement, css, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import litLogo from './assets/lit.svg' + +/** + * An example element. + * + * @slot - This element has a slot + * @csspart button - The button + */ +@customElement('my-element') +export class MyElement extends LitElement { + /** + * Copy for the read the docs hint. + */ + @property() + docsHint = 'Click on the Wails and Lit logos to learn more' + + /** + * The number of times the button has been clicked. + */ + @property({ type: Number }) + count = 0 + + render() { + return html` + + +
+ +
+

${this.docsHint}

+ ` + } + + private _onClick() { + this.count++ + } + + static styles = css` + :host { + 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 #646cffaa); + } + .logo.lit:hover { + filter: drop-shadow(0 0 2em #325cffaa); + } + + .card { + padding: 2em; + } + + .read-the-docs { + color: #888; + } + + h1 { + font-size: 3.2em; + line-height: 1.1; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + a:hover { + color: #535bf2; + } + + 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) { + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + } + ` +} + +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..b080b2b2c --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./types", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "Node", + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "useDefineForClassFields": false, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/v3/internal/templates/lit-ts/frontend/tsconfig.node.json b/v3/internal/templates/lit-ts/frontend/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/v3/internal/templates/lit-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/lit-ts/frontend/vite.config.ts b/v3/internal/templates/lit-ts/frontend/vite.config.ts new file mode 100644 index 000000000..fe69491e3 --- /dev/null +++ b/v3/internal/templates/lit-ts/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: 'src/my-element.ts', + formats: ['es'], + }, + rollupOptions: { + external: /^lit/, + }, + }, +}) diff --git a/v3/internal/templates/lit-ts/go.mod.tmpl b/v3/internal/templates/lit-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/lit-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/lit-ts/go.sum.tmpl b/v3/internal/templates/lit-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/lit-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/lit-ts/main.go.tmpl b/v3/internal/templates/lit-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/lit-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/lit/Taskfile.tmpl.yml b/v3/internal/templates/lit/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/lit/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/lit/build/Info.dev.plist.tmpl b/v3/internal/templates/lit/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/lit/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/lit/build/Info.plist.tmpl b/v3/internal/templates/lit/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/lit/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/lit/build/appicon.png b/v3/internal/templates/lit/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/lit/build/appicon.png differ diff --git a/v3/internal/templates/lit/build/icons.icns b/v3/internal/templates/lit/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/lit/build/icons.icns differ 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..9da997ad7 --- /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..1c4979def --- /dev/null +++ b/v3/internal/templates/lit/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "main": "dist/my-element.es.js", + "exports": { + ".": "./dist/my-element.es.js" + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "lit": "^2.4.1" + }, + "devDependencies": { + "vite": "^4.0.0" + } +} \ 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/assets/lit.svg b/v3/internal/templates/lit/frontend/src/assets/lit.svg new file mode 100644 index 000000000..4a9c1fe66 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/assets/lit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/lit/frontend/src/index.css b/v3/internal/templates/lit/frontend/src/index.css new file mode 100644 index 000000000..b52d4c639 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/index.css @@ -0,0 +1,31 @@ +: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%; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } +} 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..515f8fa58 --- /dev/null +++ b/v3/internal/templates/lit/frontend/src/my-element.js @@ -0,0 +1,129 @@ +import { LitElement, css, html } from 'lit' +import litLogo from './assets/lit.svg' + +/** + * An example element. + * + * @slot - This element has a slot + * @csspart button - The button + */ +export class MyElement extends LitElement { + static get properties() { + return { + /** + * Copy for the read the docs hint. + */ + docsHint: { type: String }, + + /** + * The number of times the button has been clicked. + */ + count: { type: Number }, + } + } + + constructor() { + super() + this.docsHint = 'Click on the Wails and Lit logos to learn more' + this.count = 0 + } + + render() { + return html` + + +
+ +
+

${this.docsHint}

+ ` + } + + _onClick() { + this.count++ + } + + static get styles() { + return css` + :host { + 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 #646cffaa); + } + .logo.lit:hover { + filter: drop-shadow(0 0 2em #325cffaa); + } + + .card { + padding: 2em; + } + + .read-the-docs { + color: #888; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + a:hover { + color: #535bf2; + } + + h1 { + font-size: 3.2em; + line-height: 1.1; + } + + 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) { + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + } + ` + } +} + +window.customElements.define('my-element', MyElement) diff --git a/v3/internal/templates/lit/frontend/vite.config.js b/v3/internal/templates/lit/frontend/vite.config.js new file mode 100644 index 000000000..3847c1f38 --- /dev/null +++ b/v3/internal/templates/lit/frontend/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: 'src/my-element.js', + formats: ['es'], + }, + rollupOptions: { + external: /^lit/, + }, + }, +}) diff --git a/v3/internal/templates/lit/go.mod.tmpl b/v3/internal/templates/lit/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/lit/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/lit/go.sum.tmpl b/v3/internal/templates/lit/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/lit/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/lit/main.go.tmpl b/v3/internal/templates/lit/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/lit/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/preact-ts/Taskfile.tmpl.yml b/v3/internal/templates/preact-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/preact-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/preact-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/preact-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/preact-ts/build/Info.plist.tmpl b/v3/internal/templates/preact-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/preact-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/preact-ts/build/appicon.png b/v3/internal/templates/preact-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/preact-ts/build/appicon.png differ diff --git a/v3/internal/templates/preact-ts/build/icons.icns b/v3/internal/templates/preact-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/preact-ts/build/icons.icns differ 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..9270ebd4e --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + Preact + TS + + +
+ + + 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..cab9db654 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "preact": "^10.11.3" + }, + "devDependencies": { + "@preact/preset-vite": "^2.4.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ 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.css b/v3/internal/templates/preact-ts/frontend/src/app.css new file mode 100644 index 000000000..088ed3ace --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/app.css @@ -0,0 +1,25 @@ +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.preact:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} 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..ccde5b9f3 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/app.tsx @@ -0,0 +1,32 @@ +import { useState } from 'preact/hooks' +import preactLogo from './assets/preact.svg' +import './app.css' + +export function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + Preact

+
+ +

+ Edit src/app.tsx and save to test HMR +

+
+

+ Click on the Vite and Preact logos to learn more +

+ + ) +} diff --git a/v3/internal/templates/preact-ts/frontend/src/assets/preact.svg b/v3/internal/templates/preact-ts/frontend/src/assets/preact.svg new file mode 100644 index 000000000..908f17def --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/assets/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/preact-ts/frontend/src/index.css b/v3/internal/templates/preact-ts/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/index.css @@ -0,0 +1,70 @@ +: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; +} + +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/internal/templates/preact-ts/frontend/src/main.tsx b/v3/internal/templates/preact-ts/frontend/src/main.tsx new file mode 100644 index 000000000..e0ce3e998 --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/src/main.tsx @@ -0,0 +1,5 @@ +import { render } from 'preact' +import { App } from './app' +import './index.css' + +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..9c1b1e0aa --- /dev/null +++ b/v3/internal/templates/preact-ts/frontend/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" + }, + "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..9d31e2aed --- /dev/null +++ b/v3/internal/templates/preact-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/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/go.mod.tmpl b/v3/internal/templates/preact-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/preact-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/preact-ts/go.sum.tmpl b/v3/internal/templates/preact-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/preact-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/preact-ts/main.go.tmpl b/v3/internal/templates/preact-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/preact-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/preact/Taskfile.tmpl.yml b/v3/internal/templates/preact/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/preact/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/preact/build/Info.dev.plist.tmpl b/v3/internal/templates/preact/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/preact/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/preact/build/Info.plist.tmpl b/v3/internal/templates/preact/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/preact/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/preact/build/appicon.png b/v3/internal/templates/preact/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/preact/build/appicon.png differ diff --git a/v3/internal/templates/preact/build/icons.icns b/v3/internal/templates/preact/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/preact/build/icons.icns differ 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..139931e70 --- /dev/null +++ b/v3/internal/templates/preact/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..f44069b31 --- /dev/null +++ b/v3/internal/templates/preact/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "preact": "^10.11.3" + }, + "devDependencies": { + "@preact/preset-vite": "^2.4.0", + "vite": "^4.0.0" + } +} \ 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.css b/v3/internal/templates/preact/frontend/src/app.css new file mode 100644 index 000000000..088ed3ace --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/app.css @@ -0,0 +1,25 @@ +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.preact:hover { + filter: drop-shadow(0 0 2em #673ab8aa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} 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..2679f4886 --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/app.jsx @@ -0,0 +1,32 @@ +import { useState } from 'preact/hooks' +import preactLogo from './assets/preact.svg' +import './app.css' + +export function App() { + const [count, setCount] = useState(0) + + return ( + <> + +

Vite + Preact

+
+ +

+ Edit src/app.jsx and save to test HMR +

+
+

+ Click on the Vite and Preact logos to learn more +

+ + ) +} diff --git a/v3/internal/templates/preact/frontend/src/assets/preact.svg b/v3/internal/templates/preact/frontend/src/assets/preact.svg new file mode 100644 index 000000000..908f17def --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/assets/preact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/preact/frontend/src/index.css b/v3/internal/templates/preact/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/index.css @@ -0,0 +1,70 @@ +: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; +} + +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/internal/templates/preact/frontend/src/main.jsx b/v3/internal/templates/preact/frontend/src/main.jsx new file mode 100644 index 000000000..be3fbce92 --- /dev/null +++ b/v3/internal/templates/preact/frontend/src/main.jsx @@ -0,0 +1,5 @@ +import { render } from 'preact' +import { App } from './app' +import './index.css' + +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/go.mod.tmpl b/v3/internal/templates/preact/go.mod.tmpl new file mode 100644 index 000000000..5fbc49867 --- /dev/null +++ b/v3/internal/templates/preact/go.mod.tmpl @@ -0,0 +1,17 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +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 + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/preact/go.sum.tmpl b/v3/internal/templates/preact/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/preact/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/preact/main.go.tmpl b/v3/internal/templates/preact/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/preact/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml b/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/react-swc-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react-swc-ts/build/Info.plist.tmpl b/v3/internal/templates/react-swc-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react-swc-ts/build/appicon.png b/v3/internal/templates/react-swc-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react-swc-ts/build/appicon.png differ diff --git a/v3/internal/templates/react-swc-ts/build/icons.icns b/v3/internal/templates/react-swc-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react-swc-ts/build/icons.icns differ 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..28868c572 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..26dc1bca8 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react-swc": "^3.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file 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.css b/v3/internal/templates/react-swc-ts/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + 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 #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} 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..cd201360b --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/App.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react-swc-ts/frontend/src/assets/react.svg b/v3/internal/templates/react-swc-ts/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-swc-ts/frontend/src/index.css b/v3/internal/templates/react-swc-ts/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/index.css @@ -0,0 +1,70 @@ +: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; +} + +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/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..791f139e2 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root') 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..3d0a51a86 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "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..9d31e2aed --- /dev/null +++ b/v3/internal/templates/react-swc-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/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/go.mod.tmpl b/v3/internal/templates/react-swc-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/react-swc-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/react-swc-ts/go.sum.tmpl b/v3/internal/templates/react-swc-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react-swc-ts/main.go.tmpl b/v3/internal/templates/react-swc-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/react-swc-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react-swc/Taskfile.tmpl.yml b/v3/internal/templates/react-swc/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/react-swc/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/react-swc/build/Info.dev.plist.tmpl b/v3/internal/templates/react-swc/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react-swc/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react-swc/build/Info.plist.tmpl b/v3/internal/templates/react-swc/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react-swc/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react-swc/build/appicon.png b/v3/internal/templates/react-swc/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react-swc/build/appicon.png differ diff --git a/v3/internal/templates/react-swc/build/icons.icns b/v3/internal/templates/react-swc/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react-swc/build/icons.icns differ 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..fe5530dd4 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..6583b9c4b --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react-swc": "^3.0.0", + "vite": "^4.0.0" + } +} \ No newline at end of file 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.css b/v3/internal/templates/react-swc/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + 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 #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} 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..ef0adc0d5 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/App.jsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.jsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react-swc/frontend/src/assets/react.svg b/v3/internal/templates/react-swc/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-swc/frontend/src/index.css b/v3/internal/templates/react-swc/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/index.css @@ -0,0 +1,70 @@ +: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; +} + +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/internal/templates/react-swc/frontend/src/main.jsx b/v3/internal/templates/react-swc/frontend/src/main.jsx new file mode 100644 index 000000000..5cc599199 --- /dev/null +++ b/v3/internal/templates/react-swc/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/v3/internal/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/go.mod.tmpl b/v3/internal/templates/react-swc/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/react-swc/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/react-swc/go.sum.tmpl b/v3/internal/templates/react-swc/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react-swc/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react-swc/main.go.tmpl b/v3/internal/templates/react-swc/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/react-swc/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react-ts/Taskfile.tmpl.yml b/v3/internal/templates/react-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/react-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/react-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/react-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react-ts/build/Info.plist.tmpl b/v3/internal/templates/react-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react-ts/build/appicon.png b/v3/internal/templates/react-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react-ts/build/appicon.png differ diff --git a/v3/internal/templates/react-ts/build/icons.icns b/v3/internal/templates/react-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react-ts/build/icons.icns differ 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..28868c572 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..e846a02c1 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^3.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ No newline at end of file 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.css b/v3/internal/templates/react-ts/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + 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 #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} 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..cd201360b --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/App.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react-ts/frontend/src/assets/react.svg b/v3/internal/templates/react-ts/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react-ts/frontend/src/index.css b/v3/internal/templates/react-ts/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/index.css @@ -0,0 +1,70 @@ +: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; +} + +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/internal/templates/react-ts/frontend/src/main.tsx b/v3/internal/templates/react-ts/frontend/src/main.tsx new file mode 100644 index 000000000..791f139e2 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root') 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..3d0a51a86 --- /dev/null +++ b/v3/internal/templates/react-ts/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "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..9d31e2aed --- /dev/null +++ b/v3/internal/templates/react-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/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/go.mod.tmpl b/v3/internal/templates/react-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/react-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/react-ts/go.sum.tmpl b/v3/internal/templates/react-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react-ts/main.go.tmpl b/v3/internal/templates/react-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/react-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/react/Taskfile.tmpl.yml b/v3/internal/templates/react/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/react/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/react/build/Info.dev.plist.tmpl b/v3/internal/templates/react/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/react/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react/build/Info.plist.tmpl b/v3/internal/templates/react/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/react/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/react/build/appicon.png b/v3/internal/templates/react/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/react/build/appicon.png differ diff --git a/v3/internal/templates/react/build/icons.icns b/v3/internal/templates/react/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/react/build/icons.icns differ 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..fe5530dd4 --- /dev/null +++ b/v3/internal/templates/react/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..2371d17c8 --- /dev/null +++ b/v3/internal/templates/react/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^3.0.0", + "vite": "^4.0.0" + } +} \ No newline at end of file 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.css b/v3/internal/templates/react/frontend/src/App.css new file mode 100644 index 000000000..2c5e2ef5c --- /dev/null +++ b/v3/internal/templates/react/frontend/src/App.css @@ -0,0 +1,41 @@ +#root { + 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 #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} 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..ef0adc0d5 --- /dev/null +++ b/v3/internal/templates/react/frontend/src/App.jsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( +
+ +

Vite + React

+
+ +

+ Edit src/App.jsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ) +} + +export default App diff --git a/v3/internal/templates/react/frontend/src/assets/react.svg b/v3/internal/templates/react/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/v3/internal/templates/react/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/react/frontend/src/index.css b/v3/internal/templates/react/frontend/src/index.css new file mode 100644 index 000000000..917888c1d --- /dev/null +++ b/v3/internal/templates/react/frontend/src/index.css @@ -0,0 +1,70 @@ +: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; +} + +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/internal/templates/react/frontend/src/main.jsx b/v3/internal/templates/react/frontend/src/main.jsx new file mode 100644 index 000000000..5cc599199 --- /dev/null +++ b/v3/internal/templates/react/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/v3/internal/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/go.mod.tmpl b/v3/internal/templates/react/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/react/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/react/go.sum.tmpl b/v3/internal/templates/react/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/react/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/react/main.go.tmpl b/v3/internal/templates/react/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/react/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml b/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/svelte-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/svelte-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/svelte-ts/build/Info.plist.tmpl b/v3/internal/templates/svelte-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/svelte-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/svelte-ts/build/appicon.png b/v3/internal/templates/svelte-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/svelte-ts/build/appicon.png differ diff --git a/v3/internal/templates/svelte-ts/build/icons.icns b/v3/internal/templates/svelte-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/svelte-ts/build/icons.icns differ 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/README.md b/v3/internal/templates/svelte-ts/frontend/README.md new file mode 100644 index 000000000..d7cb0df20 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/README.md @@ -0,0 +1,75 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript +in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + +[Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also +powered by Vite. Deploy anywhere with its serverless-first approach and adapt to +various platforms, with out of the box support for TypeScript, SCSS, and Less, +and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some + users. +- It is first and foremost a framework that just happens to use Vite under the + hood, not a Vite app. + +This template contains as little as possible to get started with Vite + +TypeScript + Svelte, while taking into account the developer experience with +regards to HMR and intellisense. It demonstrates capabilities on par with the +other `create-vite` templates and is a good starting point for beginners dipping +their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by +SvelteKit, the template has been structured similarly to SvelteKit so that it is +easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or +`tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed +in the configuration. Using triple-slash references keeps the default TypeScript +setting of accepting type information from the entire workspace, while also +adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file +allows VS Code to prompt the user to install the recommended extension upon +opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the +project, it does not prevent the use of JavaScript syntax in `.svelte` files. In +addition, it would force `checkJs: false`, bringing the worst of both worlds: +not being able to guarantee the entire codebase is TypeScript, and also having +worse typechecking for the existing JavaScript. In addition, there are valid use +cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by +default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often +surprising behavior. You can read the details +[here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider +creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from "svelte/store"; +export default writable(0); +``` 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..a7eaac78d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..222683e0d --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0", + "@tsconfig/svelte": "^3.0.0", + "svelte": "^3.54.0", + "svelte-check": "^2.10.0", + "tslib": "^2.4.1", + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ 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..1e9dc0394 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/App.svelte @@ -0,0 +1,45 @@ + + +
+ +

Vite + Svelte

+ +
+ +
+ +

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

+ +

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

+
+ + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/src/app.css b/v3/internal/templates/svelte-ts/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/internal/templates/svelte-ts/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/internal/templates/svelte-ts/frontend/src/assets/svelte.svg b/v3/internal/templates/svelte-ts/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/svelte-ts/frontend/src/lib/Counter.svelte b/v3/internal/templates/svelte-ts/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..979b4dfc9 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + 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..8a909a15a --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/src/main.ts @@ -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/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..7a2b764a6 --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/svelte.config.js @@ -0,0 +1,7 @@ +import { wailsPreprocess } from '@sveltejs/wails-plugin-svelte' + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: wailsPreprocess(), +} 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..c4e1c5fe6 --- /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/**/*.d.ts", "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..65dbdb96a --- /dev/null +++ b/v3/internal/templates/svelte-ts/frontend/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node" + }, + "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/go.mod.tmpl b/v3/internal/templates/svelte-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/svelte-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/svelte-ts/go.sum.tmpl b/v3/internal/templates/svelte-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/svelte-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/svelte-ts/main.go.tmpl b/v3/internal/templates/svelte-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/svelte-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/svelte/Taskfile.tmpl.yml b/v3/internal/templates/svelte/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/svelte/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/svelte/build/Info.dev.plist.tmpl b/v3/internal/templates/svelte/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/svelte/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/svelte/build/Info.plist.tmpl b/v3/internal/templates/svelte/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/svelte/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/svelte/build/appicon.png b/v3/internal/templates/svelte/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/svelte/build/appicon.png differ diff --git a/v3/internal/templates/svelte/build/icons.icns b/v3/internal/templates/svelte/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/svelte/build/icons.icns differ 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..d0097d9d9 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/README.md @@ -0,0 +1 @@ +# Wails + Svelte diff --git a/v3/internal/templates/svelte/frontend/index.html b/v3/internal/templates/svelte/frontend/index.html new file mode 100644 index 000000000..1ea50f904 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 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..2e166feea --- /dev/null +++ b/v3/internal/templates/svelte/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.0.0" + } +} \ 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..539c395dd --- /dev/null +++ b/v3/internal/templates/svelte/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/internal/templates/svelte/frontend/src/app.css b/v3/internal/templates/svelte/frontend/src/app.css new file mode 100644 index 000000000..bcc7233dd --- /dev/null +++ b/v3/internal/templates/svelte/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/internal/templates/svelte/frontend/src/assets/svelte.svg b/v3/internal/templates/svelte/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/svelte/frontend/src/lib/Counter.svelte b/v3/internal/templates/svelte/frontend/src/lib/Counter.svelte new file mode 100644 index 000000000..e45f90310 --- /dev/null +++ b/v3/internal/templates/svelte/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + 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..8a909a15a --- /dev/null +++ b/v3/internal/templates/svelte/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/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/go.mod.tmpl b/v3/internal/templates/svelte/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/svelte/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/svelte/go.sum.tmpl b/v3/internal/templates/svelte/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/svelte/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/svelte/main.go.tmpl b/v3/internal/templates/svelte/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/svelte/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/templates.go b/v3/internal/templates/templates.go new file mode 100644 index 000000000..e0da5a48b --- /dev/null +++ b/v3/internal/templates/templates.go @@ -0,0 +1,177 @@ +package templates + +import ( + "embed" + "fmt" + "github.com/wailsapp/wails/v3/internal/debug" + "io/fs" + "os" + + "github.com/wailsapp/wails/v3/internal/flags" + + "github.com/leaanthony/gosod" + + "github.com/samber/lo" +) + +//go:embed lit +var lit embed.FS + +//go:embed lit-ts +var litTS embed.FS + +//go:embed vue +var vue embed.FS + +//go:embed vue-ts +var vueTS embed.FS + +//go:embed react +var react embed.FS + +//go:embed react-ts +var reactTS embed.FS + +//go:embed react-swc +var reactSWC embed.FS + +//go:embed react-swc-ts +var reactSWCTS embed.FS + +//go:embed svelte +var svelte embed.FS + +//go:embed svelte-ts +var svelteTS embed.FS + +//go:embed preact +var preact embed.FS + +//go:embed preact-ts +var preactTS embed.FS + +//go:embed vanilla +var vanilla embed.FS + +//go:embed vanilla-ts +var vanillaTS embed.FS + +type TemplateData struct { + Name string + Description string + FS embed.FS +} + +var defaultTemplates = []TemplateData{ + { + Name: "lit", + Description: "Template using Lit Web Components: https://lit.dev", + FS: lit, + }, + { + Name: "lit-ts", + Description: "Template using Lit Web Components (TypeScript) : https://lit.dev", + FS: litTS, + }, + { + Name: "vue", + Description: "Template using Vue: https://vuejs.org", + FS: vue, + }, + { + Name: "vue-ts", + Description: "Template using Vue (TypeScript): https://vuejs.org", + FS: vueTS, + }, + { + Name: "react", + Description: "Template using React: https://reactjs.org", + FS: react, + }, + { + Name: "react-ts", + Description: "Template using React (TypeScript): https://reactjs.org", + FS: reactTS, + }, + { + Name: "react-swc", + Description: "Template using React with SWC: https://reactjs.org & https://swc.rs", + FS: reactSWC, + }, + { + Name: "react-swc-ts", + Description: "Template using React with SWC (TypeScript): https://reactjs.org & https://swc.rs", + FS: reactSWCTS, + }, + { + Name: "svelte", + Description: "Template using Svelte: https://svelte.dev", + FS: svelte, + }, + { + Name: "svelte-ts", + Description: "Template using Svelte (TypeScript): https://svelte.dev", + FS: svelteTS, + }, + { + Name: "preact", + Description: "Template using Preact: https://preactjs.com", + FS: preact, + }, + { + Name: "preact-ts", + Description: "Template using Preact (TypeScript): https://preactjs.com", + FS: preactTS, + }, + { + Name: "vanilla", + Description: "Template using Vanilla JS", + FS: vanilla, + }, + { + Name: "vanilla-ts", + Description: "Template using Vanilla JS (TypeScript)", + FS: vanillaTS, + }, +} + +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 +} + +func Install(options *flags.Init) error { + + templateData := TemplateOptions{ + options, + debug.LocalModulePath, + } + template, found := lo.Find(defaultTemplates, func(template TemplateData) bool { + return template.Name == options.TemplateName + }) + if !found { + return fmt.Errorf("template '%s' not found", options.TemplateName) + } + + if options.ProjectDir == "." || options.ProjectDir == "" { + templateData.ProjectDir = lo.Must(os.Getwd()) + } + templateData.ProjectDir = fmt.Sprintf("%s/%s", options.ProjectDir, options.ProjectName) + fmt.Printf("Installing template '%s' into '%s'\n", options.TemplateName, options.ProjectDir) + tfs, err := fs.Sub(template.FS, options.TemplateName) + if err != nil { + return err + } + + return gosod.New(tfs).Extract(options.ProjectDir, templateData) +} diff --git a/v3/internal/templates/templates_test.go b/v3/internal/templates/templates_test.go new file mode 100644 index 000000000..25fa790b5 --- /dev/null +++ b/v3/internal/templates/templates_test.go @@ -0,0 +1,34 @@ +package templates + +import ( + "testing" + + "github.com/wailsapp/wails/v3/internal/flags" +) + +func TestInstall(t *testing.T) { + type args struct { + } + tests := []struct { + name string + options *flags.Init + wantErr bool + }{ + { + name: "should install template", + options: &flags.Init{ + ProjectName: "test", + TemplateName: "svelte", + Quiet: false, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Install(tt.options); (err != nil) != tt.wantErr { + t.Errorf("Install() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml b/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/vanilla-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/vanilla-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vanilla-ts/build/Info.plist.tmpl b/v3/internal/templates/vanilla-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vanilla-ts/build/appicon.png b/v3/internal/templates/vanilla-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vanilla-ts/build/appicon.png differ diff --git a/v3/internal/templates/vanilla-ts/build/icons.icns b/v3/internal/templates/vanilla-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vanilla-ts/build/icons.icns differ 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..3da9b4918 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails + TS + + +
+ + + 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..fddd59a6c --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/package.json @@ -0,0 +1,15 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^4.0.0" + } +} \ 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/counter.ts b/v3/internal/templates/vanilla-ts/frontend/src/counter.ts new file mode 100644 index 000000000..09e5afd2d --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/counter.ts @@ -0,0 +1,9 @@ +export function setupCounter(element: HTMLButtonElement) { + let counter = 0 + const setCounter = (count: number) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} 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..b386148ad --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/main.ts @@ -0,0 +1,23 @@ +import './style.css' +import typescriptLogo from './typescript.svg' +import { setupCounter } from './counter' + +document.querySelector('#app')!.innerHTML = ` +
+ + + + + + +

Wails + TypeScript

+
+ +
+

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

+
+` + +setupCounter(document.querySelector('#counter')!) diff --git a/v3/internal/templates/vanilla-ts/frontend/src/style.css b/v3/internal/templates/vanilla-ts/frontend/src/style.css new file mode 100644 index 000000000..ac37d84b9 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/style.css @@ -0,0 +1,97 @@ +: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; +} + +#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 #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #3178c6aa); +} + +.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/internal/templates/vanilla-ts/frontend/src/typescript.svg b/v3/internal/templates/vanilla-ts/frontend/src/typescript.svg new file mode 100644 index 000000000..d91c910cc --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/src/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file 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..eac16d14a --- /dev/null +++ b/v3/internal/templates/vanilla-ts/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "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": true, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/v3/internal/templates/vanilla-ts/go.mod.tmpl b/v3/internal/templates/vanilla-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/vanilla-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/vanilla-ts/go.sum.tmpl b/v3/internal/templates/vanilla-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vanilla-ts/main.go.tmpl b/v3/internal/templates/vanilla-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/vanilla-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vanilla/Taskfile.tmpl.yml b/v3/internal/templates/vanilla/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/vanilla/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/vanilla/build/Info.dev.plist.tmpl b/v3/internal/templates/vanilla/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vanilla/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vanilla/build/Info.plist.tmpl b/v3/internal/templates/vanilla/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vanilla/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vanilla/build/appicon.png b/v3/internal/templates/vanilla/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vanilla/build/appicon.png differ diff --git a/v3/internal/templates/vanilla/build/icons.icns b/v3/internal/templates/vanilla/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vanilla/build/icons.icns differ 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/counter.js b/v3/internal/templates/vanilla/frontend/counter.js new file mode 100644 index 000000000..881e2d7ad --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/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/internal/templates/vanilla/frontend/index.html b/v3/internal/templates/vanilla/frontend/index.html new file mode 100644 index 000000000..a13d62487 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Wails App + + +
+ + + diff --git a/v3/internal/templates/vanilla/frontend/javascript.svg b/v3/internal/templates/vanilla/frontend/javascript.svg new file mode 100644 index 000000000..f9abb2b72 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/main.js b/v3/internal/templates/vanilla/frontend/main.js new file mode 100644 index 000000000..5a926e5b8 --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/main.js @@ -0,0 +1,23 @@ +import './style.css' +import javascriptLogo from './javascript.svg' +import { setupCounter } from './counter.js' + +document.querySelector('#app').innerHTML = ` +
+ + + + + + +

Hello Wails!

+
+ +
+

+ Click on the Wails logo to learn more +

+
+` + +setupCounter(document.querySelector('#counter')) diff --git a/v3/internal/templates/vanilla/frontend/package.json b/v3/internal/templates/vanilla/frontend/package.json new file mode 100644 index 000000000..63288c9ef --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/package.json @@ -0,0 +1,14 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^4.0.0" + } +} \ No newline at end of file diff --git a/v3/internal/templates/vanilla/frontend/public/wails.png b/v3/internal/templates/vanilla/frontend/public/wails.png new file mode 100644 index 000000000..8bdf42483 Binary files /dev/null and b/v3/internal/templates/vanilla/frontend/public/wails.png differ diff --git a/v3/internal/templates/vanilla/frontend/style.css b/v3/internal/templates/vanilla/frontend/style.css new file mode 100644 index 000000000..12320801d --- /dev/null +++ b/v3/internal/templates/vanilla/frontend/style.css @@ -0,0 +1,97 @@ +: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; +} + +#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 #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/internal/templates/vanilla/go.mod.tmpl b/v3/internal/templates/vanilla/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/vanilla/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/vanilla/go.sum.tmpl b/v3/internal/templates/vanilla/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vanilla/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vanilla/main.go.tmpl b/v3/internal/templates/vanilla/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/vanilla/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vue-ts/Taskfile.tmpl.yml b/v3/internal/templates/vue-ts/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/vue-ts/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/vue-ts/build/Info.dev.plist.tmpl b/v3/internal/templates/vue-ts/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vue-ts/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vue-ts/build/Info.plist.tmpl b/v3/internal/templates/vue-ts/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vue-ts/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vue-ts/build/appicon.png b/v3/internal/templates/vue-ts/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vue-ts/build/appicon.png differ diff --git a/v3/internal/templates/vue-ts/build/icons.icns b/v3/internal/templates/vue-ts/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vue-ts/build/icons.icns differ 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..d89213460 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/README.md @@ -0,0 +1,32 @@ +# 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..129f6aef7 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/package.json @@ -0,0 +1,20 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "typescript": "^4.9.3", + "vite": "^4.0.0", + "vue-tsc": "^1.0.11" + } +} \ 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..fb679f1d5 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/App.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/v3/internal/templates/vue-ts/frontend/src/assets/vue.svg b/v3/internal/templates/vue-ts/frontend/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file 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..523091033 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/components/HelloWorld.vue @@ -0,0 +1,38 @@ + + + + + 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..2425c0f74 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue-ts/frontend/src/style.css b/v3/internal/templates/vue-ts/frontend/src/style.css new file mode 100644 index 000000000..0192f9aac --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/src/style.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; +} + +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; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} 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..b557c4047 --- /dev/null +++ b/v3/internal/templates/vue-ts/frontend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "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/go.mod.tmpl b/v3/internal/templates/vue-ts/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/vue-ts/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/vue-ts/go.sum.tmpl b/v3/internal/templates/vue-ts/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vue-ts/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vue-ts/main.go.tmpl b/v3/internal/templates/vue-ts/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/vue-ts/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/internal/templates/vue/Taskfile.tmpl.yml b/v3/internal/templates/vue/Taskfile.tmpl.yml new file mode 100644 index 000000000..5e8c5f4aa --- /dev/null +++ b/v3/internal/templates/vue/Taskfile.tmpl.yml @@ -0,0 +1,85 @@ +version: '3' + +vars: + APP_NAME: "{{.ProjectName}}" + +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 + deps: + - install-frontend-deps + cmds: + - npm run build + + build: + summary: Builds the application + cmds: + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o build/bin/{{ "{{.APP_NAME}}" }} main.go + - task: post-build + env: + CGO_CFLAGS: "-mmacosx-version-min=10.13" + CGO_LDFLAGS: "-mmacosx-version-min=10.13" + + generate-icons: + summary: Generates Windows `.ico` and Mac `.icns` files from an image + dir: build + cmds: + # Generates both .ico and .icns files + - wails generate 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" + vars: + ARCH: $GOARCH + + + 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 \ No newline at end of file diff --git a/v3/internal/templates/vue/build/Info.dev.plist.tmpl b/v3/internal/templates/vue/build/Info.dev.plist.tmpl new file mode 100644 index 000000000..7efa134f4 --- /dev/null +++ b/v3/internal/templates/vue/build/Info.dev.plist.tmpl @@ -0,0 +1,32 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vue/build/Info.plist.tmpl b/v3/internal/templates/vue/build/Info.plist.tmpl new file mode 100644 index 000000000..6bfa8c316 --- /dev/null +++ b/v3/internal/templates/vue/build/Info.plist.tmpl @@ -0,0 +1,27 @@ + + + + CFBundlePackageType + APPL + CFBundleName + My Product Name + CFBundleExecutable + {{.ProjectName}} + CFBundleIdentifier + com.wails.{{.ProjectName}} + 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/internal/templates/vue/build/appicon.png b/v3/internal/templates/vue/build/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/templates/vue/build/appicon.png differ diff --git a/v3/internal/templates/vue/build/icons.icns b/v3/internal/templates/vue/build/icons.icns new file mode 100644 index 000000000..1b5bd4c86 Binary files /dev/null and b/v3/internal/templates/vue/build/icons.icns differ 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/README.md b/v3/internal/templates/vue/frontend/README.md new file mode 100644 index 000000000..d19f91afd --- /dev/null +++ b/v3/internal/templates/vue/frontend/README.md @@ -0,0 +1,13 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The +template uses Vue 3 ` + + diff --git a/v3/internal/templates/vue/frontend/package.json b/v3/internal/templates/vue/frontend/package.json new file mode 100644 index 000000000..b779f58b4 --- /dev/null +++ b/v3/internal/templates/vue/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "vite": "^4.0.0" + } +} \ 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..3f9b55cb7 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/App.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/v3/internal/templates/vue/frontend/src/assets/vue.svg b/v3/internal/templates/vue/frontend/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file 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..d3c3f15cd --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/components/HelloWorld.vue @@ -0,0 +1,40 @@ + + + + + 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..2425c0f74 --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/main.js @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/v3/internal/templates/vue/frontend/src/style.css b/v3/internal/templates/vue/frontend/src/style.css new file mode 100644 index 000000000..a566a347d --- /dev/null +++ b/v3/internal/templates/vue/frontend/src/style.css @@ -0,0 +1,90 @@ +: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; +} + +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; +} + +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; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} 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/go.mod.tmpl b/v3/internal/templates/vue/go.mod.tmpl new file mode 100644 index 000000000..3c878c9ab --- /dev/null +++ b/v3/internal/templates/vue/go.mod.tmpl @@ -0,0 +1,21 @@ +module changeme + +go 1.19 + +require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 + +require ( + github.com/json-iterator/go v1.1.12 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/samber/lo v1.37.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect + golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect + golang.org/x/net v0.7.0 // indirect +) +{{if gt (len .LocalModulePath) 0}} +replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3 +replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2 +{{end}} diff --git a/v3/internal/templates/vue/go.sum.tmpl b/v3/internal/templates/vue/go.sum.tmpl new file mode 100644 index 000000000..c06e0dbc6 --- /dev/null +++ b/v3/internal/templates/vue/go.sum.tmpl @@ -0,0 +1,33 @@ +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= +github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= +golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/v3/internal/templates/vue/main.go.tmpl b/v3/internal/templates/vue/main.go.tmpl new file mode 100644 index 000000000..1bc6a4868 --- /dev/null +++ b/v3/internal/templates/vue/main.go.tmpl @@ -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: "{{.ProjectName}}", + Description: "A demo of using raw HTML & CSS", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + // Create window + app.NewWebviewWindowWithOptions(&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: "/", + Assets: application.AssetOptions{ + FS: assets, + }, + }) + + err := app.Run() + + if err != nil { + log.Fatal(err) + } +} diff --git a/v3/pkg/application/TODO.md b/v3/pkg/application/TODO.md new file mode 100644 index 000000000..c96ee2d79 --- /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/app_delegate.h b/v3/pkg/application/app_delegate.h new file mode 100644 index 000000000..50ff8b6e9 --- /dev/null +++ b/v3/pkg/application/app_delegate.h @@ -0,0 +1,12 @@ +//go:build darwin + +#ifndef appdelegate_h +#define appdelegate_h + +#import + +@interface AppDelegate : NSObject +@property bool shouldTerminateWhenLastWindowClosed; +@end + +#endif diff --git a/v3/pkg/application/app_delegate.m b/v3/pkg/application/app_delegate.m new file mode 100644 index 000000000..d20a48fda --- /dev/null +++ b/v3/pkg/application/app_delegate.m @@ -0,0 +1,137 @@ +//go:build darwin +#import "app_delegate.h" +#import "../events/events.h" +extern bool hasListeners(unsigned int); +@implementation AppDelegate +- (void)dealloc +{ + [super dealloc]; +} +// Create the applicationShouldTerminateAfterLastWindowClosed: method +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return self.shouldTerminateWhenLastWindowClosed; +} +// GENERATED EVENTS START +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidBecomeActive) ) { + processApplicationEvent(EventApplicationDidBecomeActive); + } +} + +- (void)applicationDidChangeBackingProperties:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeBackingProperties) ) { + processApplicationEvent(EventApplicationDidChangeBackingProperties); + } +} + +- (void)applicationDidChangeEffectiveAppearance:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeEffectiveAppearance) ) { + processApplicationEvent(EventApplicationDidChangeEffectiveAppearance); + } +} + +- (void)applicationDidChangeIcon:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeIcon) ) { + processApplicationEvent(EventApplicationDidChangeIcon); + } +} + +- (void)applicationDidChangeOcclusionState:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeOcclusionState) ) { + processApplicationEvent(EventApplicationDidChangeOcclusionState); + } +} + +- (void)applicationDidChangeScreenParameters:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeScreenParameters) ) { + processApplicationEvent(EventApplicationDidChangeScreenParameters); + } +} + +- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarFrame) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarFrame); + } +} + +- (void)applicationDidChangeStatusBarOrientation:(NSNotification *)notification { + if( hasListeners(EventApplicationDidChangeStatusBarOrientation) ) { + processApplicationEvent(EventApplicationDidChangeStatusBarOrientation); + } +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationDidFinishLaunching) ) { + processApplicationEvent(EventApplicationDidFinishLaunching); + } +} + +- (void)applicationDidHide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidHide) ) { + processApplicationEvent(EventApplicationDidHide); + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationDidResignActive) ) { + processApplicationEvent(EventApplicationDidResignActive); + } +} + +- (void)applicationDidUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUnhide) ) { + processApplicationEvent(EventApplicationDidUnhide); + } +} + +- (void)applicationDidUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationDidUpdate) ) { + processApplicationEvent(EventApplicationDidUpdate); + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillBecomeActive) ) { + processApplicationEvent(EventApplicationWillBecomeActive); + } +} + +- (void)applicationWillFinishLaunching:(NSNotification *)notification { + if( hasListeners(EventApplicationWillFinishLaunching) ) { + processApplicationEvent(EventApplicationWillFinishLaunching); + } +} + +- (void)applicationWillHide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillHide) ) { + processApplicationEvent(EventApplicationWillHide); + } +} + +- (void)applicationWillResignActive:(NSNotification *)notification { + if( hasListeners(EventApplicationWillResignActive) ) { + processApplicationEvent(EventApplicationWillResignActive); + } +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillTerminate) ) { + processApplicationEvent(EventApplicationWillTerminate); + } +} + +- (void)applicationWillUnhide:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUnhide) ) { + processApplicationEvent(EventApplicationWillUnhide); + } +} + +- (void)applicationWillUpdate:(NSNotification *)notification { + if( hasListeners(EventApplicationWillUpdate) ) { + processApplicationEvent(EventApplicationWillUpdate); + } +} + +// GENERATED EVENTS END +@end diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go new file mode 100644 index 000000000..b2667bf00 --- /dev/null +++ b/v3/pkg/application/application.go @@ -0,0 +1,601 @@ +package application + +import "C" +import ( + "log" + "net/http" + "os" + "runtime" + "strconv" + "sync" + + "github.com/wailsapp/wails/v2/pkg/assetserver" + "github.com/wailsapp/wails/v2/pkg/assetserver/webview" + assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver" + + wailsruntime "github.com/wailsapp/wails/v3/internal/runtime" + "github.com/wailsapp/wails/v3/pkg/events" + "github.com/wailsapp/wails/v3/pkg/logger" +) + +var globalApplication *App + +func init() { + runtime.LockOSThread() +} + +func New(appOptions Options) *App { + if globalApplication != nil { + return globalApplication + } + + mergeApplicationDefaults(&appOptions) + + result := &App{ + options: appOptions, + applicationEventListeners: make(map[uint][]func()), + systemTrays: make(map[uint]*SystemTray), + log: logger.New(appOptions.Logger.CustomLoggers...), + contextMenus: make(map[string]*Menu), + pid: os.Getpid(), + } + globalApplication = result + + if !appOptions.Logger.Silent { + result.log.AddOutput(&logger.Console{}) + } + + result.Events = NewWailsEventProcessor(result.dispatchEventToWindows) + + opts := assetserveroptions.Options{ + Assets: appOptions.Assets.FS, + Handler: appOptions.Assets.Handler, + Middleware: assetserveroptions.Middleware(appOptions.Assets.Middleware), + } + + // TODO ServingFrom disk? + srv, err := assetserver.NewAssetServer("", opts, false, nil, wailsruntime.RuntimeAssetsBundle) + if err != nil { + result.fatal(err.Error()) + } + + srv.UseRuntimeHandler(NewMessageProcessor()) + result.assets = srv + + result.bindings, err = NewBindings(appOptions.Bind) + if err != nil { + println("Fatal error in application initialisation: ", err.Error()) + os.Exit(1) + } + + result.plugins = NewPluginManager(appOptions.Plugins, srv) + err = result.plugins.Init() + if err != nil { + result.Quit() + os.Exit(1) + } + + err = result.bindings.AddPlugins(appOptions.Plugins) + if err != nil { + println("Fatal error in application initialisation: ", err.Error()) + os.Exit(1) + } + + 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.Icon == nil { + o.Icon = DefaultApplicationIcon + } + +} + +type platformApp interface { + run() error + destroy() + setApplicationMenu(menu *Menu) + name() string + getCurrentWindowID() uint + showAboutDialog(name string, description string, icon []byte) + setIcon(icon []byte) + on(id uint) + dispatchOnMainThread(id uint) + hide() + show() +} + +// Messages sent from javascript get routed here +type windowMessage struct { + windowId uint + message string +} + +var windowMessageBuffer = make(chan *windowMessage) + +type dragAndDropMessage struct { + windowId uint + filenames []string +} + +var windowDragAndDropBuffer = make(chan *dragAndDropMessage) + +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 +} + +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) + +type App struct { + options Options + applicationEventListeners map[uint][]func() + applicationEventListenersLock sync.RWMutex + + // Windows + windows map[uint]*WebviewWindow + windowsLock sync.Mutex + + // System Trays + systemTrays map[uint]*SystemTray + systemTraysLock sync.Mutex + systemTrayID uint + systemTrayIDLock sync.RWMutex + + // MenuItems + menuItems map[uint]*MenuItem + menuItemsLock sync.Mutex + + // Running + running bool + bindings *Bindings + plugins *PluginManager + + // platform app + impl platformApp + + // The main application menu + ApplicationMenu *Menu + + clipboard *Clipboard + Events *EventProcessor + log *logger.Logger + + contextMenus map[string]*Menu + contextMenusLock sync.Mutex + + assets *assetserver.AssetServer + + // Hooks + windowCreatedCallbacks []func(window *WebviewWindow) + pid int +} + +func (a *App) getSystemTrayID() uint { + a.systemTrayIDLock.Lock() + defer a.systemTrayIDLock.Unlock() + a.systemTrayID++ + return a.systemTrayID +} + +func (a *App) getWindowForID(id uint) *WebviewWindow { + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + return a.windows[id] +} + +func (a *App) deleteWindowByID(id uint) { + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + delete(a.windows, id) +} + +func (a *App) On(eventType events.ApplicationEventType, callback func()) { + eventID := uint(eventType) + a.applicationEventListenersLock.Lock() + defer a.applicationEventListenersLock.Unlock() + a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], callback) + if a.impl != nil { + go a.impl.on(eventID) + } +} +func (a *App) NewWebviewWindow() *WebviewWindow { + return a.NewWebviewWindowWithOptions(&WebviewWindowOptions{}) +} + +func (a *App) GetPID() int { + return a.pid +} + +func (a *App) info(message string, args ...any) { + a.Log(&logger.Message{ + Level: "INFO", + Message: message, + Data: args, + Sender: "Wails", + }) +} + +func (a *App) fatal(message string, args ...any) { + msg := "************** FATAL **************\n" + msg += message + msg += "***********************************\n" + + a.Log(&logger.Message{ + Level: "FATAL", + Message: msg, + Data: args, + Sender: "Wails", + }) + + a.log.Flush() + os.Exit(1) +} + +func (a *App) error(message string, args ...any) { + a.Log(&logger.Message{ + Level: "ERROR", + Message: message, + Data: args, + Sender: "Wails", + }) +} + +func (a *App) NewWebviewWindowWithOptions(windowOptions *WebviewWindowOptions) *WebviewWindow { + // Ensure we have sane defaults + if windowOptions == nil { + windowOptions = WebviewWindowDefaults + } + newWindow := NewWindow(windowOptions) + id := newWindow.id + if a.windows == nil { + a.windows = make(map[uint]*WebviewWindow) + } + a.windowsLock.Lock() + a.windows[id] = newWindow + a.windowsLock.Unlock() + + // Call hooks + for _, hook := range a.windowCreatedCallbacks { + hook(newWindow) + } + + if a.running { + newWindow.run() + } + + return newWindow +} + +func (a *App) NewSystemTray() *SystemTray { + id := a.getSystemTrayID() + newSystemTray := NewSystemTray(id) + a.systemTraysLock.Lock() + a.systemTrays[id] = newSystemTray + a.systemTraysLock.Unlock() + + if a.running { + newSystemTray.Run() + } + return newSystemTray +} + +func (a *App) Run() error { + a.info("Starting application") + a.impl = newPlatformApp(a) + + a.running = true + go func() { + for { + event := <-applicationEvents + a.handleApplicationEvent(event) + } + }() + go func() { + for { + event := <-windowEvents + a.handleWindowEvent(event) + } + }() + go func() { + for { + request := <-webviewRequests + a.handleWebViewRequest(request) + } + }() + go func() { + for { + event := <-windowMessageBuffer + a.handleWindowMessage(event) + } + }() + go func() { + for { + dragAndDropMessage := <-windowDragAndDropBuffer + a.handleDragAndDropMessage(dragAndDropMessage) + } + }() + + go func() { + for { + menuItemID := <-menuItemClicked + a.handleMenuItemClicked(menuItemID) + } + }() + + // run windows + for _, window := range a.windows { + go window.run() + } + + // run system trays + for _, systray := range a.systemTrays { + go systray.Run() + } + + // set the application menu + a.impl.setApplicationMenu(a.ApplicationMenu) + + // set the application Icon + a.impl.setIcon(a.options.Icon) + + err := a.impl.run() + if err != nil { + return err + } + + a.plugins.Shutdown() + + return nil +} + +func (a *App) handleApplicationEvent(event uint) { + a.applicationEventListenersLock.RLock() + listeners, ok := a.applicationEventListeners[event] + a.applicationEventListenersLock.RUnlock() + if !ok { + return + } + for _, listener := range listeners { + go listener() + } +} + +func (a *App) handleDragAndDropMessage(event *dragAndDropMessage) { + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.handleDragAndDropMessage(event) +} + +func (a *App) handleWindowMessage(event *windowMessage) { + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.handleMessage(event.message) +} + +func (a *App) handleWebViewRequest(request *webViewAssetRequest) { + // Get window from window map + url, _ := request.URL() + a.info("Window: '%s', Request: %s", request.windowName, url) + a.assets.ServeWebViewRequest(request) +} + +func (a *App) handleWindowEvent(event *WindowEvent) { + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.WindowID] + a.windowsLock.Unlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.WindowID) + return + } + window.handleWindowEvent(event.EventID) +} + +func (a *App) handleMenuItemClicked(menuItemID uint) { + menuItem := getMenuItemByID(menuItemID) + if menuItem == nil { + log.Printf("MenuItem #%d not found", menuItemID) + return + } + menuItem.handleClick() +} + +func (a *App) CurrentWindow() *WebviewWindow { + if a.impl == nil { + return nil + } + id := a.impl.getCurrentWindowID() + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + return a.windows[id] +} + +func (a *App) Quit() { + var wg sync.WaitGroup + wg.Add(2) + go func() { + a.windowsLock.Lock() + for _, window := range a.windows { + window.Destroy() + } + a.windowsLock.Unlock() + wg.Done() + }() + go func() { + a.systemTraysLock.Lock() + for _, systray := range a.systemTrays { + systray.Destroy() + } + a.systemTraysLock.Unlock() + wg.Done() + }() + wg.Wait() + if a.impl != nil { + a.impl.destroy() + } +} + +func (a *App) SetMenu(menu *Menu) { + a.ApplicationMenu = menu + if a.impl != nil { + a.impl.setApplicationMenu(menu) + } +} +func (a *App) ShowAboutDialog() { + if a.impl != nil { + a.impl.showAboutDialog(a.options.Name, a.options.Description, a.options.Icon) + } +} + +func (a *App) InfoDialog() *MessageDialog { + return newMessageDialog(InfoDialog) +} + +func (a *App) QuestionDialog() *MessageDialog { + return newMessageDialog(QuestionDialog) +} + +func (a *App) WarningDialog() *MessageDialog { + return newMessageDialog(WarningDialog) +} + +func (a *App) ErrorDialog() *MessageDialog { + return newMessageDialog(ErrorDialog) +} + +func (a *App) OpenDirectoryDialog() *MessageDialog { + return newMessageDialog(OpenDirectoryDialog) +} + +func (a *App) OpenFileDialog() *OpenFileDialog { + return newOpenFileDialog() +} + +func (a *App) SaveFileDialog() *SaveFileDialog { + return newSaveFileDialog() +} + +func (a *App) GetPrimaryScreen() (*Screen, error) { + return getPrimaryScreen() +} + +func (a *App) GetScreens() ([]*Screen, error) { + return getScreens() +} + +func (a *App) Clipboard() *Clipboard { + if a.clipboard == nil { + a.clipboard = newClipboard() + } + return a.clipboard +} + +func (a *App) dispatchOnMainThread(fn func()) { + mainThreadFunctionStoreLock.Lock() + id := generateFunctionStoreID() + mainThreadFunctionStore[id] = fn + mainThreadFunctionStoreLock.Unlock() + // Call platform specific dispatch function + a.impl.dispatchOnMainThread(id) +} + +func (a *App) OpenFileDialogWithOptions(options *OpenFileDialogOptions) *OpenFileDialog { + result := a.OpenFileDialog() + result.SetOptions(options) + return result +} + +func (a *App) SaveFileDialogWithOptions(s *SaveFileDialogOptions) *SaveFileDialog { + result := a.SaveFileDialog() + result.SetOptions(s) + return result +} + +func (a *App) dispatchEventToWindows(event *WailsEvent) { + for _, window := range a.windows { + window.dispatchWailsEvent(event) + } +} + +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) Log(message *logger.Message) { + a.log.Log(message) +} + +func (a *App) RegisterContextMenu(name string, menu *Menu) { + a.contextMenusLock.Lock() + defer a.contextMenusLock.Unlock() + a.contextMenus[name] = menu +} + +func (a *App) getContextMenu(name string) (*Menu, bool) { + a.contextMenusLock.Lock() + defer a.contextMenusLock.Unlock() + menu, ok := a.contextMenus[name] + return menu, ok + +} + +func (a *App) OnWindowCreation(callback func(window *WebviewWindow)) { + a.windowCreatedCallbacks = append(a.windowCreatedCallbacks, callback) +} + +func (a *App) GetWindowByName(name string) *WebviewWindow { + a.windowsLock.Lock() + defer a.windowsLock.Unlock() + for _, window := range a.windows { + if window.Name() == name { + return window + } + } + return nil +} diff --git a/v3/pkg/application/application.h b/v3/pkg/application/application.h new file mode 100644 index 000000000..e67384397 --- /dev/null +++ b/v3/pkg/application/application.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.go b/v3/pkg/application/application_darwin.go new file mode 100644 index 000000000..b299c1f67 --- /dev/null +++ b/v3/pkg/application/application_darwin.go @@ -0,0 +1,258 @@ +//go:build darwin + +package application + +/* + +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "application.h" +#include "app_delegate.h" +#include "webview_window.h" +#include + +extern void registerListener(unsigned int event); + +#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; + }]; +} + +static void setApplicationShouldTerminateAfterLastWindowClosed(bool shouldTerminate) { + // Get the NSApp delegate + AppDelegate *appDelegate = (AppDelegate*)[NSApp delegate]; + // Set the applicationShouldTerminateAfterLastWindowClosed boolean + appDelegate.shouldTerminateWhenLastWindowClosed = shouldTerminate; +} + +static void setActivationPolicy(int policy) { + [NSApp setActivationPolicy:policy]; +} + +static void activateIgnoringOtherApps() { + [NSApp activateIgnoringOtherApps:YES]; +} + +static void run(void) { + @autoreleasepool { + [NSApp run]; + [appDelegate release]; + } +} + +// Destroy application +static void destroyApp(void) { + [NSApp terminate:nil]; +} + +// Set the application menu +static void setApplicationMenu(void *menu) { + NSMenu *nsMenu = (__bridge NSMenu *)menu; + [NSApp setMainMenu:menu]; +} + +// Get the application name +static char* getAppName(void) { + NSString *appName = [NSRunningApplication currentApplication].localizedName; + if( appName == nil ) { + appName = [[NSProcessInfo processInfo] processName]; + } + return strdup([appName UTF8String]); +} + +// get the current window ID +static unsigned int getCurrentWindowID(void) { + NSWindow *window = [NSApp keyWindow]; + // Get the window delegate + WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)[window delegate]; + return delegate.windowId; +} + +// Set the application icon +static void setApplicationIcon(void *icon, int length) { + // On main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSImage *image = [[NSImage alloc] initWithData:[NSData dataWithBytes:icon length:length]]; + [NSApp setApplicationIconImage:image]; + }); +} + +// Hide the application +static void hide(void) { + [NSApp hide:nil]; +} + +// Show the application +static void show(void) { + [NSApp unhide:nil]; +} + +*/ +import "C" +import ( + "unsafe" + + "github.com/wailsapp/wails/v2/pkg/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type macosApp struct { + applicationMenu unsafe.Pointer + parent *App +} + +func (m *macosApp) 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 { + // Add a hook to the ApplicationDidFinishLaunching event + m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() { + C.setApplicationShouldTerminateAfterLastWindowClosed(C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed)) + C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy)) + C.activateIgnoringOtherApps() + }) + // setup event listeners + for eventID := range m.parent.applicationEventListeners { + m.on(eventID) + } + C.run() + return nil +} + +func (m *macosApp) destroy() { + C.destroyApp() +} + +func newPlatformApp(app *App) *macosApp { + C.init() + return &macosApp{ + parent: app, + } +} + +//export processApplicationEvent +func processApplicationEvent(eventID C.uint) { + applicationEvents <- uint(eventID) +} + +//export processWindowEvent +func processWindowEvent(windowID C.uint, eventID C.uint) { + windowEvents <- &WindowEvent{ + WindowID: uint(windowID), + EventID: uint(eventID), + } +} + +//export processMessage +func processMessage(windowID C.uint, message *C.char) { + windowMessageBuffer <- &windowMessage{ + windowId: uint(windowID), + message: C.GoString(message), + } +} + +//export processURLRequest +func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { + webviewRequests <- &webViewAssetRequest{ + Request: webview.NewRequest(wkUrlSchemeTask), + windowId: uint(windowID), + windowName: globalApplication.getWindowForID(uint(windowID)).Name(), + } +} + +//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) +} + +func setIcon(icon []byte) { + if icon == nil { + return + } + C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) +} diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go new file mode 100644 index 000000000..9fe604c70 --- /dev/null +++ b/v3/pkg/application/bindings.go @@ -0,0 +1,315 @@ +package application + +import ( + "fmt" + "reflect" + "runtime" + "strings" + + "github.com/samber/lo" +) + +type CallOptions struct { + PackageName string `json:"packageName"` + StructName string `json:"structName"` + MethodName string `json:"methodName"` + Args []any `json:"args"` +} + +type PluginCallOptions struct { + Name string `json:"name"` + Args []any `json:"args"` +} + +var reservedPluginMethods = []string{ + "Name", + "Init", + "Shutdown", + "Exported", +} + +// 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 { + Name string `json:"name"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` + Comments string `json:"comments,omitempty"` + Method reflect.Value `json:"-"` + PackageName string + StructName string + PackagePath string +} + +type Bindings struct { + boundMethods map[string]map[string]map[string]*BoundMethod +} + +func NewBindings(bindings []any) (*Bindings, error) { + b := &Bindings{ + boundMethods: make(map[string]map[string]map[string]*BoundMethod), + } + for _, binding := range bindings { + err := b.Add(binding) + if err != nil { + return nil, err + } + } + return b, nil +} + +// Add the given struct methods to the Bindings +func (b *Bindings) Add(structPtr interface{}) error { + + methods, err := b.getMethods(structPtr) + if err != nil { + return fmt.Errorf("cannot bind value to app: %s", err.Error()) + } + + for _, method := range methods { + packageName := method.PackageName + structName := method.StructName + methodName := method.Name + + // Add it as a regular method + if _, ok := b.boundMethods[packageName]; !ok { + b.boundMethods[packageName] = make(map[string]map[string]*BoundMethod) + } + if _, ok := b.boundMethods[packageName][structName]; !ok { + b.boundMethods[packageName][structName] = make(map[string]*BoundMethod) + } + b.boundMethods[packageName][structName][methodName] = method + } + return nil +} + +func (b *Bindings) AddPlugins(plugins map[string]Plugin) error { + for pluginID, plugin := range plugins { + methods, err := b.getMethods(plugin) + if err != nil { + return fmt.Errorf("cannot add plugin '%s' to app: %s", pluginID, err.Error()) + } + + exportedMethods := plugin.CallableByJS() + + for _, method := range methods { + // Do not expose reserved methods + if lo.Contains(reservedPluginMethods, method.Name) { + continue + } + // Do not expose methods that are not in the exported list + if !lo.Contains(exportedMethods, method.Name) { + continue + } + packageName := "wails-plugins" + structName := pluginID + methodName := method.Name + + // Add it as a regular method + if _, ok := b.boundMethods[packageName]; !ok { + b.boundMethods[packageName] = make(map[string]map[string]*BoundMethod) + } + if _, ok := b.boundMethods[packageName][structName]; !ok { + b.boundMethods[packageName][structName] = make(map[string]*BoundMethod) + } + b.boundMethods[packageName][structName][methodName] = method + globalApplication.info("Added %s plugin method: %s", structName, methodName) + } + } + return nil +} + +func (b *Bindings) Get(options *CallOptions) *BoundMethod { + _, ok := b.boundMethods[options.PackageName] + if !ok { + return nil + } + _, ok = b.boundMethods[options.PackageName][options.StructName] + if !ok { + return nil + } + method, ok := b.boundMethods[options.PackageName][options.StructName][options.MethodName] + if !ok { + return nil + } + return method +} + +func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { + + // Create result placeholder + var result []*BoundMethod + + // Check type + if !isStructPtr(value) { + + if isStruct(value) { + name := reflect.ValueOf(value).Type().Name() + return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name) + } + + if isFunction(value) { + name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name() + return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct", name) + } + + return nil, fmt.Errorf("not a pointer to a struct") + } + + // Process Struct + structType := reflect.TypeOf(value) + structValue := reflect.ValueOf(value) + structTypeString := structType.String() + baseName := structTypeString[1:] + + // Process Methods + for i := 0; i < structType.NumMethod(); i++ { + methodDef := structType.Method(i) + methodName := methodDef.Name + packageName, structName, _ := strings.Cut(baseName, ".") + method := structValue.MethodByName(methodName) + packagePath, _ := lo.Coalesce(structType.PkgPath(), "main") + + // Create new method + boundMethod := &BoundMethod{ + Name: methodName, + PackageName: packageName, + PackagePath: packagePath, + StructName: structName, + 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) + 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 +} + +// Call will attempt to call this bound method with the given args +func (b *BoundMethod) Call(args []interface{}) (interface{}, error) { + // Check inputs + expectedInputLength := len(b.Inputs) + actualInputLength := len(args) + + // If the method is variadic, we need to check the minimum number of inputs + if b.Method.Type().IsVariadic() { + if actualInputLength < expectedInputLength-1 { + return nil, fmt.Errorf("%s takes at least %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength) + } + } else { + if expectedInputLength != actualInputLength { + return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength) + } + } + + /** Convert inputs to reflect values **/ + + // Create slice for the input arguments to the method call + callArgs := make([]reflect.Value, actualInputLength) + + // Iterate over given arguments + for index, arg := range args { + // Save the converted argument + if arg == nil { + callArgs[index] = reflect.Zero(b.Inputs[index].ReflectType) + continue + } + callArgs[index] = reflect.ValueOf(arg) + } + + // Do the call + callResults := b.Method.Call(callArgs) + + //** Check results **// + var returnValue interface{} + var err error + + switch len(b.Outputs) { + case 1: + // Loop over results and determine if the result + // is an error or not + for _, result := range callResults { + interfac := result.Interface() + temp, ok := interfac.(error) + if ok { + err = temp + } else { + returnValue = interfac + } + } + case 2: + returnValue = callResults[0].Interface() + if temp, ok := callResults[1].Interface().(error); ok { + err = temp + } + } + + return returnValue, err +} + +// isStructPtr returns true if the value given is a +// pointer to a struct +func isStructPtr(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Ptr && + reflect.ValueOf(value).Elem().Kind() == reflect.Struct +} + +// isFunction returns true if the given value is a function +func isFunction(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Func +} + +// isStructPtr returns true if the value given is a struct +func isStruct(value interface{}) bool { + return reflect.ValueOf(value).Kind() == reflect.Struct +} diff --git a/v3/pkg/application/clipboard.go b/v3/pkg/application/clipboard.go new file mode 100644 index 000000000..e823ace92 --- /dev/null +++ b/v3/pkg/application/clipboard.go @@ -0,0 +1,26 @@ +package application + +import "C" + +type clipboardImpl interface { + setText(text string) bool + text() string +} + +type Clipboard struct { + impl clipboardImpl +} + +func newClipboard() *Clipboard { + return &Clipboard{ + impl: newClipboardImpl(), + } +} + +func (c *Clipboard) SetText(text string) bool { + return c.impl.setText(text) +} + +func (c *Clipboard) Text() string { + return c.impl.text() +} diff --git a/v3/pkg/application/clipboard_darwin.go b/v3/pkg/application/clipboard_darwin.go new file mode 100644 index 000000000..a46ff66d5 --- /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 { + clipboardLock.RLock() + defer clipboardLock.RUnlock() + clipboardText := C.getClipboardText() + result := C.GoString(clipboardText) + return result +} + +func newClipboardImpl() *macosClipboard { + return &macosClipboard{} +} diff --git a/v3/pkg/application/context.go b/v3/pkg/application/context.go new file mode 100644 index 000000000..56b213350 --- /dev/null +++ b/v3/pkg/application/context.go @@ -0,0 +1,54 @@ +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() any { + return c.data[contextMenuData] +} + +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_window_event.go b/v3/pkg/application/context_window_event.go new file mode 100644 index 000000000..f9895fbd2 --- /dev/null +++ b/v3/pkg/application/context_window_event.go @@ -0,0 +1,35 @@ +package application + +var blankWindowEventContext = &WindowEventContext{} + +const ( + // FilesDropped is the event name for when files are dropped on the window + 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/dialogs.go b/v3/pkg/application/dialogs.go new file mode 100644 index 000000000..d7e8d6671 --- /dev/null +++ b/v3/pkg/application/dialogs.go @@ -0,0 +1,442 @@ +package application + +import "C" +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 ( + InfoDialog DialogType = iota + QuestionDialog + WarningDialog + ErrorDialog + OpenDirectoryDialog +) + +type Button struct { + Label string + IsCancel bool + IsDefault bool + callback func() +} + +func (b *Button) OnClick(callback func()) { + b.callback = callback +} + +type messageDialogImpl interface { + show() +} + +type MessageDialogOptions struct { + DialogType DialogType + Title string + Message string + Buttons []*Button + Icon []byte +} + +type MessageDialog struct { + MessageDialogOptions + + // platform independent + impl messageDialogImpl +} + +var defaultTitles = map[DialogType]string{ + InfoDialog: "Information", + QuestionDialog: "Question", + WarningDialog: "Warning", + ErrorDialog: "Error", +} + +func newMessageDialog(dialogType DialogType) *MessageDialog { + return &MessageDialog{ + MessageDialogOptions: MessageDialogOptions{ + DialogType: dialogType, + Title: defaultTitles[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) + } + 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) 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() ([]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 + + Title string + Message string + ButtonText string + Directory string +} + +type OpenFileDialog 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 *OpenFileDialog) CanChooseFiles(canChooseFiles bool) *OpenFileDialog { + d.canChooseFiles = canChooseFiles + return d +} + +func (d *OpenFileDialog) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialog { + d.canChooseDirectories = canChooseDirectories + return d +} + +func (d *OpenFileDialog) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialog { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *OpenFileDialog) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialog { + d.allowsOtherFileTypes = allowsOtherFileTypes + return d +} + +func (d *OpenFileDialog) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialog { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *OpenFileDialog) HideExtension(hideExtension bool) *OpenFileDialog { + d.hideExtension = hideExtension + return d +} + +func (d *OpenFileDialog) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialog { + d.treatsFilePackagesAsDirectories = treatsFilePackagesAsDirectories + return d +} + +func (d *OpenFileDialog) AttachToWindow(window *WebviewWindow) *OpenFileDialog { + d.window = window + return d +} + +func (d *OpenFileDialog) ResolvesAliases(resolvesAliases bool) *OpenFileDialog { + d.resolvesAliases = resolvesAliases + return d +} + +func (d *OpenFileDialog) SetTitle(title string) *OpenFileDialog { + d.title = title + return d +} + +func (d *OpenFileDialog) PromptForSingleSelection() (string, error) { + d.allowsMultipleSelection = false + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + selection, err := d.impl.show() + var result string + if len(selection) > 0 { + result = selection[0] + } + + 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 *OpenFileDialog) AddFilter(displayName, pattern string) *OpenFileDialog { + d.filters = append(d.filters, FileFilter{ + DisplayName: strings.TrimSpace(displayName), + Pattern: strings.TrimSpace(pattern), + }) + return d +} + +func (d *OpenFileDialog) PromptForMultipleSelection() ([]string, error) { + d.allowsMultipleSelection = true + if d.impl == nil { + d.impl = newOpenFileDialogImpl(d) + } + return d.impl.show() +} + +func (d *OpenFileDialog) SetMessage(message string) *OpenFileDialog { + d.message = message + return d +} + +func (d *OpenFileDialog) SetButtonText(text string) *OpenFileDialog { + d.buttonText = text + return d +} + +func (d *OpenFileDialog) SetDirectory(directory string) *OpenFileDialog { + d.directory = directory + return d +} + +func (d *OpenFileDialog) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialog { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *OpenFileDialog) 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 +} + +func newOpenFileDialog() *OpenFileDialog { + return &OpenFileDialog{ + id: getDialogID(), + canChooseDirectories: false, + canChooseFiles: true, + canCreateDirectories: true, + resolvesAliases: false, + } +} + +func newSaveFileDialog() *SaveFileDialog { + return &SaveFileDialog{ + id: getDialogID(), + canCreateDirectories: true, + } +} + +type SaveFileDialogOptions struct { + CanCreateDirectories bool + ShowHiddenFiles bool + CanSelectHiddenExtension bool + AllowOtherFileTypes bool + HideExtension bool + TreatsFilePackagesAsDirectories bool + Message string + Directory string + Filename string + ButtonText string +} + +type SaveFileDialog struct { + id uint + canCreateDirectories bool + showHiddenFiles bool + canSelectHiddenExtension bool + allowOtherFileTypes bool + hideExtension bool + treatsFilePackagesAsDirectories bool + message string + directory string + filename string + buttonText string + + window *WebviewWindow + + impl saveFileDialogImpl +} + +type saveFileDialogImpl interface { + show() (string, error) +} + +func (d *SaveFileDialog) SetOptions(options *SaveFileDialogOptions) { + 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 +} + +func (d *SaveFileDialog) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialog { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *SaveFileDialog) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialog { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *SaveFileDialog) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialog { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *SaveFileDialog) SetMessage(message string) *SaveFileDialog { + d.message = message + return d +} + +func (d *SaveFileDialog) SetDirectory(directory string) *SaveFileDialog { + d.directory = directory + return d +} + +func (d *SaveFileDialog) AttachToWindow(window *WebviewWindow) *SaveFileDialog { + d.window = window + return d +} + +func (d *SaveFileDialog) PromptForSingleSelection() (string, error) { + if d.impl == nil { + d.impl = newSaveFileDialogImpl(d) + } + return d.impl.show() +} + +func (d *SaveFileDialog) SetButtonText(text string) *SaveFileDialog { + d.buttonText = text + return d +} + +func (d *SaveFileDialog) SetFilename(filename string) *SaveFileDialog { + d.filename = filename + return d +} + +func (d *SaveFileDialog) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialog { + d.allowOtherFileTypes = allowOtherFileTypes + return d +} + +func (d *SaveFileDialog) HideExtension(hideExtension bool) *SaveFileDialog { + d.hideExtension = hideExtension + return d +} + +func (d *SaveFileDialog) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialog { + 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..ca27e7da6 --- /dev/null +++ b/v3/pkg/application/dialogs_darwin.go @@ -0,0 +1,520 @@ +//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_delegate.h" + +extern void openFileDialogCallback(uint id, char* path); +extern void openFileDialogCallbackEnd(uint id); +extern void saveFileDialogCallback(uint id, char* path); + +static void showAboutBox(char* title, char *message, void *icon, int length) { + + // run on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + 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; + +} + +// Run the dialog +static int dialogRunModal(void *dialog) { + NSAlert *alert = (__bridge NSAlert *)dialog; + long response = [alert runModal]; + int result; + + if( response == NSAlertFirstButtonReturn ) { + result = 0; + } + else if( response == NSAlertSecondButtonReturn ) { + result = 1; + } + else if( response == NSAlertThirdButtonReturn ) { + result = 2; + } else { + result = 3; + } + return result; +} + +// 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 + dispatch_async(dispatch_get_main_queue(), ^{ + + 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 (@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]; + } + + // 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) { + + // run on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + 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]; + const char *path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } else { + [panel beginWithCompletionHandler:^(NSInteger result) { + const char *path = NULL; + if (result == NSModalResponseOK) { + NSURL *url = [panel URL]; + const char *path = [[url path] UTF8String]; + } + saveFileDialogCallback(dialogID, (char *)path); + }]; + } + }); +} + +*/ +import "C" +import ( + "strings" + "unsafe" +) + +const NSAlertStyleWarning = C.int(0) +const NSAlertStyleInformational = C.int(1) +const NSAlertStyleCritical = C.int(2) + +var alertTypeMap = map[DialogType]C.int{ + WarningDialog: NSAlertStyleWarning, + InfoDialog: NSAlertStyleInformational, + ErrorDialog: NSAlertStyleCritical, + QuestionDialog: NSAlertStyleInformational, +} + +func (m *macosApp) showAboutDialog(title string, message string, icon []byte) { + var iconData unsafe.Pointer + if icon != nil { + iconData = unsafe.Pointer(&icon[0]) + } + 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() { + globalApplication.dispatchOnMainThread(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 == ErrorDialog { + iconData = unsafe.Pointer(&globalApplication.options.Icon[0]) + iconLength = C.int(len(globalApplication.options.Icon)) + } + } + + 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++ + } + + buttonPressed := int(C.dialogRunModal(m.nsDialog)) + if len(m.dialog.Buttons) > buttonPressed { + button := reversedButtons[buttonPressed] + if button.callback != nil { + button.callback() + } + } + }) + +} + +func newDialogImpl(d *MessageDialog) *macosDialog { + return &macosDialog{ + dialog: d, + } +} + +type macosOpenFileDialog struct { + dialog *OpenFileDialog +} + +func newOpenFileDialogImpl(d *OpenFileDialog) *macosOpenFileDialog { + return &macosOpenFileDialog{ + dialog: d, + } +} + +func toCString(s string) *C.char { + if s == "" { + return nil + } + return C.CString(s) +} + +func (m *macosOpenFileDialog) show() ([]string, error) { + openFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + nsWindow = m.dialog.window.impl.(*macosWebviewWindow).nsWindow + } + + // 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) + var result []string + for filename := range openFileResponses[m.dialog.id] { + result = append(result, filename) + } + return result, 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 *SaveFileDialog +} + +func newSaveFileDialogImpl(d *SaveFileDialog) *macosSaveFileDialog { + return &macosSaveFileDialog{ + dialog: d, + } +} + +func (m *macosSaveFileDialog) show() (string, error) { + saveFileResponses[m.dialog.id] = make(chan string) + nsWindow := unsafe.Pointer(nil) + if m.dialog.window != nil { + // get NSWindow from window + nsWindow = m.dialog.window.impl.(*macosWebviewWindow).nsWindow + } + 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_delegate.h b/v3/pkg/application/dialogs_delegate.h new file mode 100644 index 000000000..07657f8b9 --- /dev/null +++ b/v3/pkg/application/dialogs_delegate.h @@ -0,0 +1,14 @@ +//go:build darwin + +#ifndef _DIALOGS_DELEGATE_H_ +#define _DIALOGS_DELEGATE_H_ + +#import +#import + +// create an NSOpenPanel delegate to handle the callback +@interface OpenPanelDelegate : NSObject +@property (nonatomic, strong) NSArray *allowedExtensions; +@end + +#endif \ No newline at end of file diff --git a/v3/pkg/application/dialogs_delegate.m b/v3/pkg/application/dialogs_delegate.m new file mode 100644 index 000000000..67f34e2eb --- /dev/null +++ b/v3/pkg/application/dialogs_delegate.m @@ -0,0 +1,36 @@ +//go:build darwin + +#import "dialogs_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 (self.allowedExtensions == nil) { + return YES; + } + NSString *extension = url.pathExtension; + if (extension == nil) { + return NO; + } + if ([extension isEqualToString:@""]) { + return NO; + } + if ([self.allowedExtensions containsObject:extension]) { + return YES; + } + return NO; +} + +@end + + + + diff --git a/v3/pkg/application/errors.go b/v3/pkg/application/errors.go new file mode 100644 index 000000000..747142328 --- /dev/null +++ b/v3/pkg/application/errors.go @@ -0,0 +1,16 @@ +package application + +import ( + "fmt" + "os" +) + +func Fatal(message string, args ...interface{}) { + println("*********************** FATAL ***********************") + println("There has been a catastrophic failure in your application.") + println("Please report this error at https://github.com/wailsapp/wails/issues") + println("******************** Error Details ******************") + println(fmt.Sprintf(message, args...)) + println("*********************** FATAL ***********************") + os.Exit(1) +} diff --git a/v3/pkg/application/events.go b/v3/pkg/application/events.go new file mode 100644 index 000000000..41ac872ff --- /dev/null +++ b/v3/pkg/application/events.go @@ -0,0 +1,160 @@ +package application + +import ( + "encoding/json" + "sync" + + "github.com/samber/lo" +) + +var applicationEvents = make(chan uint) + +type WindowEvent struct { + WindowID uint + EventID uint +} + +var windowEvents = make(chan *WindowEvent) + +var menuItemClicked = make(chan uint) + +type WailsEvent struct { + Name string `json:"name"` + Data any `json:"data"` + Sender string `json:"sender"` +} + +func (e WailsEvent) ToJSON() string { + marshal, err := json.Marshal(&e) + if err != nil { + // TODO: Fatal error? log? + return "" + } + return string(marshal) +} + +// 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(*WailsEvent) // 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(*WailsEvent) +} + +func NewWailsEventProcessor(dispatchEventToWindows func(*WailsEvent)) *EventProcessor { + return &EventProcessor{ + listeners: make(map[string][]*eventListener), + dispatchEventToWindows: dispatchEventToWindows, + } +} + +// On is the equivalent of Javascript's `addEventListener` +func (e *EventProcessor) On(eventName string, callback func(event *WailsEvent)) func() { + return e.registerListener(eventName, callback, -1) +} + +// OnMultiple is the same as `On` but will unregister after `count` events +func (e *EventProcessor) OnMultiple(eventName string, callback func(event *WailsEvent), counter int) func() { + return e.registerListener(eventName, callback, counter) +} + +// Once is the same as `On` but will unregister after the first event +func (e *EventProcessor) Once(eventName string, callback func(event *WailsEvent)) func() { + return e.registerListener(eventName, callback, 1) +} + +// Emit sends an event to all listeners +func (e *EventProcessor) Emit(thisEvent *WailsEvent) { + if thisEvent == nil { + return + } + go e.dispatchEventToListeners(thisEvent) + go 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(*WailsEvent), 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 + }) + } +} + +// 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 *WailsEvent) { + + 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 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_test.go b/v3/pkg/application/events_test.go new file mode 100644 index 000000000..fe921e4fd --- /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.WailsEvent +} + +func (m *mockNotifier) dispatchEventToWindows(event *application.WailsEvent) { + m.Events = append(m.Events, event) +} + +func (m *mockNotifier) Reset() { + m.Events = []*application.WailsEvent{} +} + +func Test_EventsOn(t *testing.T) { + i := is.New(t) + notifier := &mockNotifier{} + eventProcessor := application.NewWailsEventProcessor(notifier.dispatchEventToWindows) + + // Test On + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.On(eventName, func(event *application.WailsEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.WailsEvent{ + 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 On + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(1) + unregisterFn := eventProcessor.Once(eventName, func(event *application.WailsEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(1, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.WailsEvent{ + 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 On + eventName := "test" + counter := 0 + var wg sync.WaitGroup + wg.Add(2) + unregisterFn := eventProcessor.OnMultiple(eventName, func(event *application.WailsEvent) { + // This is called in a goroutine + counter++ + wg.Done() + }, 2) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + wg.Wait() + i.Equal(2, counter) + + // Unregister + notifier.Reset() + unregisterFn() + counter = 0 + eventProcessor.Emit(&application.WailsEvent{ + Name: "test", + Data: "test payload", + }) + i.Equal(0, counter) + +} diff --git a/v3/pkg/application/icons.go b/v3/pkg/application/icons.go new file mode 100644 index 000000000..13ef1f389 --- /dev/null +++ b/v3/pkg/application/icons.go @@ -0,0 +1,8 @@ +package application + +var DefaultApplicationIcon = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 64, 0, 0, 0, 64, 8, 3, 0, 0, 0, 157, 183, 129, 236, 0, 0, 1, 2, 80, 76, 84, 69, 0, 0, 0, 255, 255, 255, 20, 20, 20, 14, 14, 14, 255, 255, 255, 9, 9, 9, 103, 103, 103, 255, 255, 255, 255, 255, 255, 185, 185, 185, 255, 255, 255, 16, 16, 16, 27, 27, 27, 2, 2, 2, 23, 23, 23, 62, 62, 62, 105, 105, 105, 12, 12, 12, 21, 21, 21, 36, 36, 36, 41, 41, 41, 51, 51, 51, 55, 55, 55, 78, 78, 78, 146, 146, 146, 226, 226, 226, 0, 0, 0, 225, 225, 225, 79, 79, 79, 17, 17, 17, 76, 76, 76, 221, 221, 221, 101, 101, 101, 64, 64, 64, 223, 223, 223, 217, 217, 217, 133, 133, 133, 104, 104, 104, 94, 94, 94, 6, 6, 6, 202, 202, 202, 198, 198, 198, 110, 110, 110, 97, 97, 97, 70, 70, 70, 55, 55, 55, 13, 13, 13, 211, 211, 211, 188, 188, 188, 176, 176, 176, 117, 117, 117, 91, 91, 91, 73, 73, 73, 34, 35, 34, 27, 27, 27, 21, 21, 21, 173, 173, 173, 166, 166, 166, 161, 162, 162, 150, 150, 150, 142, 143, 143, 136, 136, 136, 119, 119, 119, 90, 90, 90, 87, 87, 87, 50, 50, 50, 44, 44, 45, 41, 41, 41, 32, 32, 32, 4, 4, 4, 214, 214, 214, 178, 178, 178, 158, 158, 158, 153, 153, 153, 148, 148, 148, 123, 123, 123, 82, 82, 82, 67, 67, 67, 66, 66, 66, 208, 208, 208, 192, 192, 192, 190, 190, 190, 184, 184, 184, 127, 127, 127, 126, 126, 126, 60, 60, 61, 204, 47, 21, 237, 0, 0, 0, 26, 116, 82, 78, 83, 0, 12, 195, 213, 16, 227, 64, 19, 14, 29, 9, 198, 164, 248, 176, 96, 59, 213, 184, 145, 130, 110, 106, 78, 37, 20, 109, 186, 18, 188, 0, 0, 2, 46, 73, 68, 65, 84, 88, 195, 237, 150, 123, 83, 26, 49, 20, 197, 89, 107, 91, 10, 84, 251, 126, 220, 128, 91, 150, 151, 96, 65, 64, 121, 169, 80, 68, 169, 90, 31, 125, 233, 247, 255, 42, 206, 238, 222, 51, 73, 112, 38, 201, 50, 227, 140, 227, 120, 254, 217, 61, 155, 156, 223, 228, 113, 179, 147, 212, 147, 30, 153, 50, 158, 187, 50, 119, 227, 95, 63, 174, 173, 184, 43, 247, 217, 91, 200, 191, 202, 82, 50, 189, 79, 235, 195, 207, 81, 82, 173, 235, 3, 120, 157, 24, 176, 162, 77, 226, 13, 37, 214, 243, 85, 21, 240, 98, 9, 192, 203, 251, 1, 52, 243, 172, 89, 232, 10, 108, 250, 161, 233, 161, 169, 104, 2, 236, 11, 214, 255, 208, 109, 177, 201, 135, 230, 0, 77, 101, 3, 64, 246, 234, 134, 110, 196, 102, 35, 52, 155, 104, 106, 154, 0, 101, 244, 170, 132, 51, 240, 21, 192, 17, 90, 234, 100, 2, 156, 163, 91, 149, 136, 242, 66, 1, 124, 131, 153, 24, 1, 93, 116, 155, 98, 65, 0, 216, 131, 57, 50, 2, 26, 74, 166, 246, 19, 102, 87, 49, 109, 50, 1, 228, 64, 139, 68, 69, 161, 0, 154, 120, 111, 152, 1, 59, 202, 82, 151, 37, 64, 206, 45, 232, 155, 1, 23, 200, 204, 148, 125, 19, 223, 137, 78, 248, 117, 72, 102, 192, 6, 50, 61, 58, 20, 10, 160, 16, 96, 119, 44, 128, 75, 100, 250, 52, 81, 1, 0, 251, 3, 11, 224, 6, 153, 22, 181, 85, 192, 152, 223, 246, 200, 2, 192, 184, 131, 121, 47, 122, 198, 235, 112, 73, 117, 254, 126, 97, 3, 160, 96, 75, 84, 137, 158, 241, 78, 92, 29, 227, 115, 203, 6, 64, 207, 109, 26, 70, 3, 248, 17, 23, 69, 149, 63, 255, 34, 27, 160, 143, 19, 51, 240, 163, 1, 48, 224, 12, 139, 97, 5, 20, 80, 176, 211, 56, 25, 3, 174, 182, 121, 92, 53, 43, 160, 197, 251, 221, 57, 141, 2, 243, 184, 50, 113, 66, 198, 100, 5, 212, 124, 6, 148, 226, 41, 199, 128, 33, 234, 219, 14, 160, 18, 23, 12, 31, 201, 170, 226, 68, 125, 238, 0, 248, 45, 164, 74, 45, 6, 176, 206, 201, 14, 224, 138, 65, 217, 233, 128, 153, 11, 224, 143, 18, 216, 225, 255, 50, 235, 47, 185, 0, 254, 201, 64, 48, 208, 1, 13, 39, 64, 71, 6, 58, 164, 1, 130, 99, 39, 192, 72, 38, 42, 252, 139, 99, 157, 144, 19, 224, 84, 38, 122, 58, 96, 203, 13, 176, 47, 160, 54, 105, 0, 127, 224, 6, 56, 16, 208, 68, 7, 140, 200, 13, 48, 22, 208, 161, 14, 152, 58, 2, 174, 17, 216, 140, 108, 69, 86, 165, 35, 160, 139, 68, 89, 7, 156, 145, 35, 160, 129, 68, 81, 7, 236, 186, 2, 106, 5, 214, 162, 189, 11, 120, 88, 151, 172, 229, 239, 137, 203, 223, 84, 179, 158, 10, 240, 214, 18, 3, 62, 165, 52, 125, 121, 155, 48, 159, 75, 47, 222, 247, 63, 100, 159, 185, 235, 221, 58, 231, 85, 121, 233, 85, 87, 165, 51, 169, 39, 61, 36, 221, 2, 115, 72, 10, 51, 166, 156, 57, 80, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} +var DefaultMacTemplateIcon = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 64, 0, 0, 0, 64, 8, 3, 0, 0, 0, 157, 183, 129, 236, 0, 0, 0, 135, 80, 76, 84, 69, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 106, 145, 255, 17, 0, 0, 0, 45, 116, 82, 78, 83, 0, 225, 11, 16, 14, 7, 26, 18, 79, 65, 23, 221, 100, 91, 176, 74, 217, 118, 95, 34, 20, 211, 201, 133, 104, 69, 149, 110, 53, 189, 167, 161, 125, 86, 76, 45, 198, 154, 143, 136, 57, 41, 185, 203, 192, 69, 76, 133, 225, 0, 0, 1, 243, 73, 68, 65, 84, 88, 195, 237, 214, 109, 111, 130, 48, 16, 7, 240, 107, 41, 34, 48, 31, 65, 116, 62, 59, 231, 220, 220, 190, 255, 231, 155, 216, 187, 28, 213, 164, 61, 98, 150, 152, 197, 255, 27, 57, 219, 254, 44, 133, 34, 240, 204, 63, 139, 110, 145, 248, 118, 120, 146, 69, 70, 158, 40, 211, 224, 166, 212, 208, 46, 165, 113, 167, 159, 64, 219, 100, 158, 74, 20, 227, 204, 185, 211, 30, 136, 163, 123, 129, 228, 111, 128, 101, 23, 179, 175, 171, 8, 139, 75, 115, 65, 77, 169, 15, 152, 43, 204, 182, 174, 198, 88, 116, 235, 226, 131, 154, 166, 30, 128, 123, 141, 234, 106, 141, 197, 91, 93, 124, 83, 211, 210, 7, 76, 169, 215, 172, 62, 131, 188, 1, 124, 81, 203, 187, 119, 13, 182, 212, 109, 124, 46, 186, 84, 12, 206, 197, 11, 207, 205, 7, 140, 168, 219, 142, 22, 132, 128, 13, 21, 95, 94, 96, 161, 120, 214, 241, 164, 1, 196, 67, 60, 62, 121, 47, 35, 79, 52, 5, 72, 85, 3, 248, 164, 227, 133, 31, 232, 53, 150, 122, 202, 64, 227, 220, 58, 126, 96, 71, 253, 246, 0, 149, 162, 188, 2, 244, 241, 176, 15, 126, 96, 64, 99, 10, 216, 43, 6, 248, 138, 142, 3, 64, 202, 51, 29, 53, 1, 130, 243, 210, 15, 240, 90, 25, 88, 53, 129, 35, 30, 109, 130, 155, 137, 198, 64, 113, 249, 168, 16, 120, 167, 93, 225, 7, 248, 134, 29, 194, 204, 110, 28, 123, 77, 11, 250, 90, 135, 0, 234, 57, 177, 203, 94, 245, 44, 64, 87, 119, 14, 33, 160, 67, 59, 166, 204, 47, 19, 64, 224, 64, 139, 17, 4, 18, 236, 185, 218, 217, 145, 8, 76, 112, 94, 113, 16, 208, 116, 191, 28, 236, 0, 11, 208, 14, 57, 66, 16, 136, 115, 4, 134, 246, 148, 45, 64, 15, 150, 207, 48, 0, 184, 233, 172, 243, 134, 0, 170, 21, 8, 128, 137, 226, 12, 13, 62, 21, 49, 91, 9, 80, 41, 206, 6, 92, 96, 47, 1, 126, 20, 167, 231, 2, 43, 144, 0, 39, 30, 144, 103, 46, 176, 16, 1, 125, 30, 208, 199, 39, 20, 165, 16, 1, 107, 30, 48, 35, 128, 60, 17, 176, 105, 252, 162, 11, 188, 200, 128, 185, 187, 102, 12, 228, 153, 12, 248, 80, 148, 145, 11, 172, 65, 6, 28, 21, 101, 233, 2, 187, 48, 192, 255, 109, 124, 223, 206, 248, 174, 148, 1, 252, 40, 157, 186, 192, 1, 132, 192, 130, 70, 164, 46, 48, 144, 2, 113, 132, 185, 46, 111, 129, 199, 122, 201, 202, 238, 124, 79, 188, 255, 77, 85, 71, 173, 129, 236, 206, 183, 245, 196, 192, 149, 80, 26, 45, 143, 201, 204, 237, 162, 104, 19, 73, 99, 52, 60, 243, 72, 249, 5, 251, 207, 25, 192, 218, 106, 27, 249, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} +var WailsLogoBlack = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 128, 0, 0, 0, 128, 8, 3, 0, 0, 0, 244, 224, 145, 249, 0, 0, 3, 0, 80, 76, 84, 69, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 254, 254, 254, 8, 1, 1, 227, 49, 49, 255, 255, 255, 5, 5, 5, 0, 0, 0, 6, 1, 1, 203, 44, 44, 227, 50, 50, 35, 6, 6, 160, 22, 29, 244, 53, 53, 150, 17, 26, 234, 51, 51, 36, 6, 6, 7, 1, 1, 59, 10, 12, 255, 255, 255, 6, 5, 5, 13, 2, 2, 23, 4, 4, 212, 212, 212, 15, 2, 2, 234, 51, 51, 188, 35, 38, 171, 27, 33, 78, 17, 17, 240, 52, 52, 231, 231, 231, 51, 8, 10, 26, 26, 26, 76, 76, 76, 163, 163, 163, 171, 30, 34, 218, 42, 45, 210, 42, 44, 214, 45, 46, 23, 23, 23, 13, 13, 13, 195, 41, 42, 225, 49, 49, 135, 25, 28, 42, 7, 8, 66, 13, 14, 148, 29, 31, 111, 23, 23, 154, 154, 154, 225, 48, 48, 204, 39, 42, 198, 38, 41, 146, 28, 30, 131, 12, 21, 105, 17, 20, 186, 35, 38, 139, 139, 139, 88, 12, 16, 48, 7, 9, 186, 30, 36, 5, 5, 5, 219, 47, 47, 183, 31, 36, 35, 35, 35, 247, 246, 246, 209, 44, 45, 253, 252, 252, 116, 116, 116, 118, 23, 24, 11, 2, 2, 77, 14, 15, 126, 23, 25, 33, 5, 6, 253, 55, 55, 128, 5, 17, 55, 9, 10, 242, 242, 242, 145, 15, 24, 27, 4, 5, 238, 238, 238, 225, 225, 225, 179, 29, 35, 179, 179, 179, 20, 3, 3, 171, 34, 36, 40, 40, 40, 166, 33, 35, 89, 18, 18, 235, 235, 235, 207, 207, 207, 93, 93, 93, 43, 8, 9, 53, 8, 10, 230, 46, 48, 196, 33, 38, 115, 4, 16, 194, 194, 194, 60, 60, 60, 140, 18, 25, 170, 170, 170, 103, 103, 103, 160, 34, 34, 99, 20, 21, 135, 28, 29, 72, 14, 15, 146, 146, 146, 148, 30, 31, 206, 36, 40, 12, 12, 12, 64, 10, 12, 22, 3, 4, 191, 34, 38, 163, 23, 30, 163, 30, 33, 72, 11, 13, 217, 47, 47, 47, 47, 47, 153, 22, 28, 216, 216, 216, 156, 28, 32, 221, 221, 221, 51, 51, 51, 96, 17, 19, 178, 36, 38, 104, 17, 20, 38, 4, 6, 141, 26, 29, 222, 47, 48, 68, 68, 68, 131, 131, 131, 87, 87, 87, 114, 13, 19, 202, 202, 202, 24, 3, 4, 229, 229, 229, 189, 189, 189, 148, 148, 148, 91, 8, 15, 100, 17, 20, 125, 10, 19, 203, 40, 43, 145, 20, 27, 62, 62, 62, 191, 39, 41, 104, 20, 22, 194, 194, 194, 173, 173, 173, 85, 15, 17, 203, 203, 203, 206, 19, 21, 198, 198, 198, 159, 159, 159, 163, 24, 30, 126, 126, 126, 212, 44, 45, 108, 108, 108, 248, 248, 248, 134, 25, 27, 129, 23, 26, 141, 26, 29, 186, 116, 121, 64, 8, 12, 99, 16, 19, 250, 234, 235, 227, 80, 80, 227, 201, 204, 224, 103, 104, 35, 35, 35, 243, 187, 187, 163, 73, 80, 114, 114, 114, 183, 183, 183, 10, 0, 1, 255, 238, 238, 146, 40, 50, 212, 71, 73, 230, 148, 149, 0, 0, 0, 203, 53, 56, 86, 14, 17, 0, 0, 0, 227, 50, 50, 255, 255, 255, 5, 1, 1, 224, 48, 49, 216, 45, 46, 221, 47, 48, 218, 46, 47, 214, 44, 46, 210, 43, 45, 4, 4, 4, 2, 2, 2, 205, 41, 43, 225, 49, 49, 194, 36, 40, 212, 44, 45, 222, 48, 48, 184, 32, 36, 209, 42, 44, 219, 47, 47, 198, 37, 41, 177, 29, 34, 188, 34, 38, 192, 35, 39, 202, 39, 42, 204, 40, 42, 200, 39, 41, 251, 251, 251, 187, 33, 37, 180, 30, 35, 172, 27, 33, 182, 31, 36, 218, 48, 48, 247, 54, 54, 236, 51, 52, 229, 50, 50, 207, 41, 43, 190, 34, 38, 175, 28, 33, 129, 8, 19, 8, 8, 8, 196, 36, 40, 169, 25, 31, 245, 245, 245, 154, 34, 34, 164, 24, 30, 154, 19, 27, 133, 9, 20, 231, 50, 50, 167, 24, 31, 135, 11, 21, 120, 5, 17, 240, 52, 52, 125, 7, 18, 170, 26, 32, 141, 14, 23, 137, 12, 22, 214, 41, 44, 157, 20, 28, 145, 15, 24, 190, 31, 37, 211, 42, 44, 223, 43, 46, 203, 36, 40, 148, 31, 32, 173, 26, 32, 162, 22, 29, 199, 35, 40, 220, 43, 46, 210, 37, 40, 183, 27, 34, 153, 33, 33, 124, 0, 8, 252, 54, 55, 29, 193, 86, 184, 0, 0, 0, 182, 116, 82, 78, 83, 254, 251, 225, 249, 254, 254, 254, 249, 244, 246, 247, 245, 249, 248, 254, 254, 254, 254, 241, 251, 241, 246, 246, 246, 241, 239, 252, 250, 247, 247, 247, 251, 247, 243, 242, 241, 235, 230, 254, 250, 249, 248, 246, 244, 243, 242, 242, 241, 240, 240, 237, 252, 250, 247, 245, 244, 243, 241, 240, 233, 252, 251, 250, 249, 248, 248, 247, 245, 244, 243, 242, 242, 241, 239, 230, 251, 250, 249, 248, 247, 246, 245, 245, 245, 244, 244, 243, 242, 242, 242, 239, 237, 235, 235, 233, 254, 251, 251, 245, 245, 244, 243, 242, 240, 238, 236, 236, 235, 234, 253, 253, 252, 249, 248, 248, 246, 246, 245, 245, 245, 243, 243, 242, 240, 240, 239, 254, 254, 253, 250, 247, 246, 242, 241, 238, 234, 231, 231, 249, 249, 248, 248, 241, 239, 236, 235, 235, 234, 230, 226, 253, 252, 247, 246, 239, 236, 236, 235, 234, 229, 224, 220, 234, 224, 223, 246, 246, 243, 240, 239, 234, 234, 224, 223, 211, 248, 247, 244, 239, 229, 215, 214, 81, 124, 11, 18, 0, 0, 15, 238, 73, 68, 65, 84, 120, 218, 236, 152, 107, 72, 83, 97, 24, 199, 223, 211, 216, 106, 43, 117, 46, 73, 209, 173, 240, 131, 115, 160, 203, 162, 108, 134, 137, 146, 161, 89, 138, 129, 40, 245, 65, 81, 8, 180, 40, 74, 51, 36, 232, 250, 33, 250, 208, 141, 130, 8, 34, 232, 126, 161, 160, 123, 59, 109, 78, 151, 59, 187, 232, 212, 57, 156, 171, 188, 49, 97, 115, 154, 121, 77, 69, 187, 16, 61, 239, 57, 59, 30, 187, 88, 218, 205, 47, 253, 81, 16, 207, 129, 255, 239, 125, 222, 231, 242, 190, 7, 205, 81, 19, 234, 217, 212, 127, 128, 255, 0, 255, 1, 254, 3, 252, 7, 248, 71, 0, 104, 118, 1, 16, 31, 126, 103, 17, 64, 163, 81, 7, 75, 166, 66, 248, 251, 0, 176, 124, 34, 102, 7, 0, 168, 103, 7, 0, 150, 191, 104, 91, 244, 226, 41, 2, 240, 215, 1, 144, 6, 150, 31, 181, 49, 5, 169, 53, 234, 217, 0, 128, 85, 103, 239, 8, 123, 185, 143, 167, 230, 171, 103, 1, 0, 85, 169, 137, 245, 201, 1, 61, 81, 217, 106, 62, 154, 5, 0, 240, 12, 137, 8, 120, 229, 77, 140, 81, 107, 102, 167, 15, 8, 139, 162, 95, 82, 100, 216, 114, 2, 88, 254, 53, 0, 68, 31, 73, 14, 108, 12, 52, 147, 175, 10, 133, 120, 3, 48, 67, 213, 191, 2, 64, 8, 220, 68, 203, 23, 188, 212, 106, 201, 87, 243, 179, 185, 4, 252, 123, 0, 8, 49, 63, 85, 88, 154, 42, 88, 126, 208, 153, 141, 222, 26, 146, 244, 42, 194, 241, 83, 2, 17, 66, 132, 178, 119, 31, 202, 205, 221, 13, 239, 253, 62, 0, 226, 60, 17, 237, 249, 21, 76, 112, 90, 216, 75, 221, 10, 146, 36, 155, 183, 137, 114, 243, 50, 182, 159, 59, 158, 178, 163, 108, 79, 68, 97, 218, 245, 188, 96, 244, 7, 34, 128, 144, 70, 163, 249, 42, 232, 4, 15, 241, 230, 45, 12, 61, 22, 114, 72, 146, 23, 127, 102, 99, 243, 11, 18, 251, 39, 151, 149, 100, 37, 189, 29, 29, 144, 239, 148, 238, 75, 217, 181, 155, 248, 115, 91, 128, 64, 60, 33, 18, 166, 134, 134, 30, 243, 59, 148, 23, 180, 107, 125, 252, 241, 19, 105, 39, 79, 158, 206, 28, 186, 112, 113, 73, 143, 121, 5, 246, 23, 40, 100, 181, 111, 223, 214, 39, 200, 148, 251, 195, 131, 121, 4, 194, 131, 1, 253, 9, 0, 209, 134, 9, 207, 163, 5, 5, 42, 85, 103, 102, 230, 144, 88, 236, 1, 13, 122, 196, 171, 19, 189, 100, 13, 137, 69, 81, 249, 31, 19, 100, 167, 227, 130, 66, 132, 216, 67, 195, 231, 220, 127, 19, 96, 81, 124, 122, 65, 122, 122, 123, 123, 39, 86, 183, 79, 253, 253, 253, 125, 125, 253, 251, 78, 45, 32, 205, 102, 198, 63, 240, 212, 233, 184, 24, 63, 17, 161, 198, 238, 26, 244, 39, 199, 241, 238, 130, 55, 5, 175, 105, 189, 121, 243, 6, 147, 116, 131, 121, 223, 80, 95, 230, 234, 196, 102, 202, 76, 9, 40, 240, 215, 6, 22, 30, 19, 17, 104, 10, 243, 223, 173, 130, 77, 39, 95, 23, 48, 246, 237, 96, 15, 254, 24, 160, 255, 194, 41, 1, 44, 95, 75, 81, 20, 246, 143, 74, 85, 131, 176, 249, 95, 232, 3, 40, 60, 29, 251, 115, 0, 96, 159, 185, 250, 8, 105, 214, 105, 181, 0, 32, 32, 169, 230, 232, 60, 110, 233, 127, 161, 17, 17, 165, 221, 92, 0, 112, 4, 186, 47, 36, 11, 180, 58, 179, 150, 6, 32, 5, 100, 216, 89, 110, 4, 252, 149, 78, 184, 240, 120, 39, 11, 0, 9, 216, 9, 203, 167, 106, 106, 124, 0, 2, 129, 246, 213, 14, 33, 156, 65, 126, 21, 224, 167, 232, 56, 178, 217, 71, 59, 95, 183, 99, 125, 250, 212, 174, 186, 144, 76, 213, 84, 232, 116, 58, 179, 153, 142, 128, 182, 71, 26, 12, 254, 51, 7, 64, 124, 62, 193, 19, 206, 35, 112, 251, 6, 253, 120, 19, 36, 39, 219, 95, 127, 250, 244, 41, 253, 193, 213, 171, 79, 228, 84, 57, 172, 31, 0, 24, 127, 60, 2, 52, 232, 87, 0, 54, 169, 182, 166, 93, 186, 124, 244, 224, 193, 184, 179, 215, 175, 199, 239, 42, 45, 141, 217, 126, 45, 239, 208, 33, 63, 137, 100, 195, 177, 208, 208, 80, 209, 162, 212, 121, 72, 13, 112, 60, 30, 82, 19, 185, 233, 111, 148, 119, 175, 222, 90, 119, 231, 230, 200, 139, 242, 138, 10, 6, 0, 139, 92, 80, 132, 112, 6, 254, 194, 22, 160, 12, 255, 177, 241, 142, 177, 177, 177, 218, 90, 199, 199, 143, 31, 45, 9, 9, 197, 197, 197, 78, 75, 130, 116, 245, 153, 51, 189, 189, 178, 156, 136, 136, 136, 195, 135, 15, 43, 183, 93, 186, 28, 119, 240, 68, 220, 221, 91, 55, 230, 62, 187, 127, 251, 252, 7, 5, 248, 87, 112, 17, 104, 142, 141, 11, 23, 209, 37, 56, 243, 28, 64, 126, 153, 227, 98, 104, 169, 131, 254, 32, 183, 187, 171, 163, 183, 169, 13, 96, 156, 150, 186, 214, 122, 151, 235, 221, 104, 67, 203, 240, 192, 192, 128, 252, 253, 251, 145, 17, 106, 228, 230, 141, 103, 107, 31, 37, 159, 127, 145, 88, 201, 1, 128, 200, 72, 177, 71, 166, 138, 73, 5, 123, 254, 47, 148, 225, 26, 79, 151, 103, 124, 124, 220, 221, 213, 213, 1, 238, 77, 109, 224, 95, 235, 112, 56, 19, 18, 156, 22, 139, 165, 14, 4, 40, 245, 70, 163, 203, 101, 181, 157, 127, 120, 7, 150, 159, 88, 89, 89, 14, 0, 19, 57, 72, 30, 41, 233, 22, 123, 198, 146, 246, 109, 145, 16, 184, 13, 206, 16, 0, 8, 6, 59, 60, 254, 227, 110, 32, 96, 0, 176, 63, 152, 55, 54, 214, 181, 50, 214, 86, 171, 221, 214, 208, 96, 48, 24, 244, 45, 59, 63, 84, 42, 76, 12, 0, 36, 33, 179, 3, 130, 172, 118, 177, 103, 208, 191, 203, 146, 159, 149, 178, 148, 7, 81, 208, 160, 153, 1, 32, 76, 224, 198, 225, 159, 12, 192, 16, 176, 0, 118, 155, 13, 8, 90, 244, 38, 147, 105, 216, 4, 254, 44, 0, 16, 144, 177, 233, 125, 98, 241, 160, 187, 183, 169, 214, 245, 33, 118, 85, 204, 34, 64, 224, 207, 100, 22, 32, 32, 240, 239, 29, 252, 14, 64, 29, 7, 0, 254, 24, 160, 90, 175, 215, 211, 1, 96, 139, 192, 12, 9, 160, 234, 28, 26, 242, 248, 187, 155, 106, 157, 150, 86, 195, 123, 69, 206, 150, 84, 60, 138, 103, 116, 59, 6, 130, 49, 127, 102, 7, 32, 5, 113, 14, 50, 91, 80, 199, 248, 51, 0, 6, 12, 80, 173, 55, 49, 59, 48, 1, 144, 88, 246, 166, 175, 79, 76, 3, 88, 234, 140, 46, 123, 165, 96, 65, 212, 242, 16, 2, 16, 102, 52, 142, 215, 184, 219, 152, 44, 156, 148, 134, 190, 60, 0, 6, 216, 130, 9, 0, 253, 23, 0, 218, 21, 178, 130, 238, 254, 33, 0, 232, 162, 1, 108, 134, 106, 147, 206, 251, 106, 175, 146, 78, 6, 52, 109, 0, 32, 216, 179, 58, 43, 43, 43, 41, 33, 161, 177, 241, 109, 125, 126, 126, 254, 187, 119, 163, 163, 59, 119, 182, 128, 101, 245, 240, 176, 205, 138, 183, 224, 123, 17, 16, 72, 11, 218, 89, 0, 135, 165, 213, 101, 51, 232, 203, 43, 106, 4, 129, 61, 11, 10, 99, 132, 184, 49, 76, 23, 0, 169, 67, 54, 108, 200, 205, 13, 15, 207, 88, 179, 102, 77, 198, 185, 167, 231, 206, 197, 239, 223, 127, 226, 32, 116, 200, 43, 87, 174, 236, 136, 173, 182, 114, 57, 48, 25, 128, 74, 62, 249, 186, 179, 187, 111, 72, 60, 56, 222, 209, 230, 176, 212, 27, 49, 64, 141, 25, 247, 166, 158, 176, 213, 235, 1, 65, 141, 190, 5, 224, 205, 232, 32, 192, 227, 17, 68, 248, 202, 1, 43, 222, 2, 122, 7, 38, 85, 161, 54, 81, 166, 242, 1, 184, 59, 218, 156, 141, 70, 123, 67, 181, 169, 66, 167, 37, 177, 154, 123, 2, 78, 21, 225, 230, 132, 166, 57, 142, 159, 35, 172, 137, 171, 78, 149, 79, 106, 172, 140, 149, 3, 118, 22, 160, 210, 7, 160, 171, 209, 173, 144, 14, 150, 168, 218, 251, 251, 134, 60, 80, 133, 181, 206, 198, 122, 91, 165, 110, 133, 192, 27, 248, 106, 9, 86, 96, 96, 192, 198, 232, 109, 146, 175, 139, 238, 30, 63, 56, 52, 116, 33, 136, 199, 227, 137, 68, 11, 69, 172, 126, 120, 156, 0, 130, 114, 187, 1, 50, 2, 0, 184, 54, 160, 141, 245, 119, 119, 148, 165, 247, 251, 170, 208, 146, 180, 45, 38, 99, 87, 233, 226, 73, 42, 93, 124, 98, 123, 234, 215, 0, 126, 215, 226, 227, 142, 170, 84, 91, 105, 157, 62, 189, 108, 95, 225, 230, 205, 57, 57, 7, 138, 160, 114, 126, 72, 32, 183, 227, 28, 228, 0, 42, 180, 145, 37, 227, 80, 55, 101, 157, 108, 17, 180, 22, 175, 10, 18, 254, 236, 176, 129, 30, 171, 121, 68, 176, 36, 232, 248, 81, 149, 184, 107, 236, 227, 91, 16, 20, 154, 181, 97, 88, 113, 246, 199, 4, 187, 26, 228, 54, 253, 36, 128, 10, 115, 98, 150, 127, 71, 71, 83, 147, 44, 179, 143, 173, 66, 251, 251, 176, 168, 229, 18, 4, 61, 128, 147, 102, 138, 171, 25, 33, 20, 102, 47, 45, 221, 159, 86, 38, 75, 106, 117, 185, 92, 198, 122, 107, 228, 89, 248, 176, 242, 35, 2, 133, 220, 62, 169, 17, 234, 142, 72, 253, 59, 232, 190, 37, 203, 20, 15, 194, 159, 184, 8, 140, 182, 145, 192, 249, 105, 208, 3, 166, 60, 29, 48, 85, 192, 94, 245, 16, 1, 20, 219, 227, 148, 155, 165, 43, 203, 223, 143, 8, 224, 203, 194, 243, 41, 174, 101, 176, 20, 226, 92, 244, 136, 193, 196, 1, 196, 186, 25, 127, 135, 83, 70, 23, 129, 163, 177, 222, 218, 208, 98, 208, 55, 7, 68, 47, 163, 123, 0, 31, 253, 168, 19, 34, 196, 81, 240, 130, 253, 130, 138, 148, 133, 81, 209, 151, 133, 220, 134, 177, 239, 240, 225, 53, 166, 20, 136, 107, 23, 203, 171, 77, 62, 0, 93, 164, 172, 11, 252, 233, 174, 157, 80, 226, 113, 247, 182, 57, 49, 64, 53, 174, 144, 192, 128, 176, 156, 248, 96, 14, 97, 170, 50, 228, 40, 64, 4, 33, 10, 9, 143, 63, 6, 0, 236, 191, 153, 39, 204, 51, 158, 40, 91, 18, 180, 126, 255, 153, 247, 44, 64, 141, 34, 169, 11, 183, 108, 122, 110, 183, 38, 149, 184, 155, 218, 156, 117, 70, 12, 0, 109, 192, 108, 246, 6, 44, 129, 129, 128, 48, 194, 148, 0, 223, 82, 128, 24, 227, 42, 182, 7, 17, 194, 96, 73, 120, 204, 150, 229, 202, 85, 57, 210, 83, 43, 19, 229, 250, 106, 182, 13, 188, 40, 238, 98, 166, 22, 30, 23, 245, 198, 164, 146, 94, 60, 9, 236, 6, 26, 0, 174, 73, 230, 230, 158, 37, 201, 202, 92, 176, 226, 79, 243, 251, 192, 36, 10, 130, 71, 164, 238, 206, 13, 138, 143, 75, 91, 181, 39, 75, 26, 187, 50, 114, 120, 0, 107, 88, 15, 98, 27, 97, 108, 47, 187, 124, 60, 173, 92, 249, 73, 62, 0, 61, 13, 0, 162, 204, 100, 79, 64, 244, 62, 92, 150, 252, 233, 127, 33, 65, 161, 75, 243, 74, 139, 142, 42, 203, 54, 103, 73, 243, 173, 54, 251, 232, 168, 29, 132, 207, 35, 204, 56, 100, 59, 113, 69, 164, 108, 204, 193, 46, 31, 138, 216, 106, 139, 58, 0, 57, 232, 178, 1, 64, 141, 153, 34, 105, 9, 180, 100, 96, 79, 216, 129, 245, 244, 76, 154, 238, 205, 40, 116, 235, 78, 7, 238, 12, 173, 117, 220, 97, 208, 200, 29, 71, 216, 78, 172, 72, 106, 114, 56, 28, 190, 229, 27, 1, 208, 16, 123, 101, 207, 187, 58, 43, 51, 138, 40, 48, 247, 33, 80, 176, 19, 81, 69, 208, 11, 171, 208, 180, 0, 128, 224, 176, 177, 201, 193, 28, 136, 184, 3, 25, 119, 28, 193, 4, 176, 3, 197, 240, 142, 147, 243, 135, 199, 166, 148, 120, 104, 38, 13, 45, 166, 114, 157, 153, 244, 122, 155, 89, 145, 164, 183, 39, 96, 111, 202, 38, 52, 237, 187, 97, 72, 132, 177, 141, 61, 15, 77, 6, 96, 8, 32, 194, 90, 193, 138, 149, 109, 181, 240, 10, 216, 51, 143, 225, 97, 131, 60, 39, 68, 41, 55, 54, 84, 3, 0, 117, 68, 145, 184, 224, 11, 133, 45, 129, 153, 132, 166, 5, 128, 212, 104, 67, 132, 171, 118, 2, 192, 200, 0, 128, 108, 116, 130, 235, 20, 210, 194, 229, 65, 251, 243, 157, 206, 198, 137, 240, 211, 209, 49, 69, 6, 101, 23, 15, 219, 241, 59, 212, 197, 203, 75, 119, 111, 242, 155, 172, 77, 126, 25, 211, 4, 192, 95, 60, 55, 68, 188, 171, 229, 142, 196, 32, 60, 229, 7, 228, 242, 200, 216, 28, 229, 150, 165, 248, 27, 136, 48, 101, 212, 210, 200, 44, 31, 236, 153, 228, 88, 145, 198, 91, 127, 164, 133, 174, 66, 111, 216, 234, 45, 162, 95, 190, 158, 35, 13, 38, 248, 204, 174, 185, 252, 52, 17, 196, 113, 124, 38, 174, 84, 164, 166, 41, 143, 182, 1, 12, 175, 42, 65, 16, 75, 193, 70, 56, 152, 240, 8, 66, 72, 8, 120, 144, 34, 68, 14, 36, 42, 207, 152, 16, 12, 1, 73, 56, 233, 149, 40, 49, 49, 106, 248, 47, 102, 183, 11, 145, 214, 202, 74, 137, 138, 104, 210, 199, 5, 131, 135, 30, 122, 241, 32, 112, 49, 6, 127, 211, 97, 186, 45, 22, 221, 88, 123, 210, 111, 160, 205, 46, 179, 251, 253, 252, 126, 251, 155, 157, 71, 248, 72, 167, 196, 180, 18, 97, 142, 3, 75, 163, 242, 139, 179, 142, 154, 92, 203, 49, 122, 37, 188, 27, 201, 73, 219, 183, 215, 44, 252, 88, 223, 80, 102, 205, 216, 182, 3, 0, 10, 236, 86, 24, 58, 38, 219, 171, 161, 49, 29, 138, 92, 76, 90, 139, 80, 37, 128, 36, 188, 93, 31, 91, 43, 175, 131, 13, 191, 222, 106, 51, 70, 204, 28, 193, 55, 66, 119, 38, 190, 190, 97, 181, 17, 157, 166, 65, 207, 240, 149, 151, 144, 174, 74, 121, 53, 218, 9, 148, 229, 96, 160, 202, 118, 3, 140, 84, 103, 141, 0, 156, 96, 247, 195, 167, 221, 177, 186, 66, 231, 249, 123, 185, 93, 153, 204, 28, 162, 81, 111, 150, 93, 248, 229, 213, 59, 150, 126, 218, 47, 64, 25, 173, 152, 12, 235, 125, 62, 217, 43, 130, 106, 253, 198, 64, 129, 179, 205, 196, 7, 4, 237, 0, 156, 160, 238, 180, 173, 166, 173, 218, 124, 140, 5, 206, 205, 213, 90, 205, 173, 92, 123, 79, 195, 135, 236, 179, 57, 170, 56, 107, 34, 184, 61, 178, 228, 5, 0, 134, 16, 10, 20, 213, 223, 182, 16, 226, 1, 4, 205, 0, 188, 18, 31, 92, 224, 129, 67, 4, 220, 92, 149, 139, 228, 79, 236, 172, 179, 240, 95, 176, 85, 146, 119, 178, 132, 16, 115, 125, 196, 47, 114, 201, 203, 33, 67, 81, 86, 107, 54, 31, 16, 180, 3, 0, 1, 179, 57, 122, 215, 43, 103, 238, 236, 198, 75, 30, 62, 216, 43, 203, 74, 248, 18, 180, 45, 209, 135, 21, 49, 14, 65, 132, 215, 144, 35, 151, 47, 82, 52, 3, 0, 1, 247, 78, 174, 227, 142, 12, 88, 43, 67, 245, 113, 127, 89, 86, 140, 215, 192, 7, 215, 116, 200, 34, 23, 27, 147, 130, 17, 189, 115, 152, 141, 73, 26, 0, 180, 109, 91, 229, 56, 6, 54, 192, 159, 61, 125, 31, 91, 164, 203, 161, 201, 108, 226, 33, 57, 206, 125, 127, 72, 100, 226, 3, 130, 49, 2, 111, 134, 19, 108, 76, 210, 12, 240, 27, 127, 218, 249, 249, 211, 87, 162, 59, 133, 94, 111, 193, 67, 106, 80, 93, 101, 240, 139, 25, 92, 94, 250, 171, 200, 161, 96, 81, 22, 157, 160, 16, 148, 58, 0, 98, 254, 137, 225, 103, 208, 72, 141, 78, 12, 41, 192, 189, 29, 145, 112, 240, 144, 140, 198, 160, 97, 95, 111, 131, 9, 10, 74, 21, 0, 252, 109, 3, 60, 253, 44, 124, 176, 167, 242, 134, 154, 187, 8, 130, 252, 12, 85, 85, 102, 37, 213, 228, 179, 69, 75, 170, 25, 240, 80, 127, 216, 40, 137, 79, 63, 175, 55, 177, 160, 247, 96, 70, 117, 148, 112, 126, 126, 138, 0, 136, 249, 179, 244, 243, 167, 207, 229, 13, 223, 210, 176, 238, 77, 5, 0, 185, 96, 24, 26, 88, 75, 168, 190, 248, 122, 15, 53, 91, 216, 172, 250, 23, 74, 9, 32, 234, 175, 86, 191, 204, 253, 99, 210, 183, 241, 213, 93, 58, 50, 64, 227, 31, 13, 175, 172, 44, 169, 213, 39, 30, 82, 216, 129, 9, 74, 19, 128, 234, 207, 58, 95, 50, 127, 49, 148, 117, 37, 109, 0, 224, 111, 26, 53, 174, 174, 194, 184, 167, 246, 189, 159, 0, 244, 195, 196, 149, 182, 71, 144, 185, 88, 11, 239, 127, 190, 75, 229, 79, 170, 218, 33, 236, 65, 105, 2, 176, 156, 233, 8, 211, 55, 92, 36, 98, 160, 10, 36, 213, 126, 179, 153, 184, 210, 3, 128, 135, 39, 246, 230, 246, 162, 250, 78, 245, 57, 185, 246, 78, 165, 11, 0, 209, 159, 212, 245, 239, 252, 47, 217, 127, 128, 63, 1, 64, 160, 216, 167, 27, 190, 18, 254, 148, 112, 204, 26, 36, 30, 242, 99, 245, 148, 91, 109, 163, 61, 3, 232, 111, 197, 233, 38, 84, 110, 164, 29, 0, 97, 16, 251, 66, 236, 211, 67, 98, 194, 84, 241, 55, 75, 60, 102, 237, 19, 227, 192, 166, 113, 19, 189, 159, 102, 128, 138, 17, 171, 125, 164, 135, 148, 89, 173, 35, 165, 164, 127, 193, 110, 191, 137, 98, 87, 247, 88, 237, 118, 235, 211, 113, 226, 142, 53, 110, 128, 51, 119, 137, 231, 192, 13, 247, 47, 140, 88, 251, 85, 55, 184, 114, 222, 218, 55, 213, 247, 164, 236, 50, 65, 90, 1, 26, 243, 36, 65, 55, 136, 30, 233, 164, 150, 25, 60, 37, 8, 186, 6, 196, 19, 136, 26, 116, 146, 32, 52, 245, 144, 45, 126, 147, 153, 22, 104, 92, 198, 50, 12, 24, 231, 238, 11, 146, 206, 142, 1, 144, 19, 61, 207, 147, 64, 66, 119, 41, 217, 210, 12, 80, 44, 9, 197, 141, 21, 157, 210, 118, 119, 79, 69, 147, 180, 121, 85, 189, 182, 98, 90, 218, 222, 150, 174, 207, 147, 45, 78, 84, 166, 219, 20, 166, 27, 161, 1, 3, 104, 156, 146, 58, 117, 253, 60, 99, 112, 98, 48, 15, 174, 0, 130, 199, 38, 237, 0, 63, 168, 45, 99, 21, 213, 129, 40, 12, 79, 112, 96, 226, 237, 21, 210, 5, 99, 163, 229, 150, 22, 194, 138, 105, 5, 69, 16, 162, 85, 36, 54, 194, 150, 34, 110, 167, 178, 168, 213, 194, 46, 139, 203, 178, 246, 11, 187, 237, 240, 151, 201, 35, 204, 11, 92, 200, 155, 220, 99, 226, 36, 123, 183, 138, 183, 187, 163, 112, 156, 113, 78, 230, 227, 255, 231, 76, 166, 118, 34, 128, 209, 154, 200, 123, 31, 142, 0, 134, 250, 110, 17, 73, 234, 42, 142, 222, 58, 87, 192, 37, 128, 113, 83, 247, 217, 61, 1, 119, 215, 50, 210, 9, 198, 156, 43, 197, 173, 163, 56, 150, 101, 88, 24, 192, 3, 6, 139, 13, 143, 209, 31, 181, 0, 49, 203, 82, 141, 0, 92, 112, 116, 28, 166, 21, 48, 230, 136, 49, 44, 103, 0, 91, 194, 182, 111, 117, 66, 36, 219, 67, 174, 224, 151, 217, 84, 171, 82, 4, 224, 238, 5, 216, 57, 71, 156, 181, 246, 1, 175, 38, 67, 45, 112, 15, 202, 59, 161, 242, 158, 1, 220, 237, 17, 243, 86, 230, 185, 65, 130, 192, 106, 230, 0, 165, 128, 0, 6, 77, 201, 88, 84, 188, 12, 127, 19, 128, 53, 17, 226, 4, 251, 211, 6, 130, 220, 1, 87, 32, 174, 123, 168, 184, 122, 87, 178, 198, 18, 138, 207, 152, 190, 123, 148, 110, 56, 9, 82, 210, 0, 52, 62, 19, 32, 9, 170, 122, 164, 152, 2, 71, 240, 151, 3, 236, 39, 120, 207, 10, 149, 109, 38, 64, 213, 7, 6, 19, 114, 217, 101, 122, 133, 81, 7, 188, 235, 200, 76, 144, 33, 9, 50, 215, 130, 36, 101, 241, 68, 132, 252, 201, 148, 97, 113, 128, 182, 15, 238, 173, 48, 108, 225, 180, 67, 162, 95, 190, 163, 17, 80, 221, 137, 155, 242, 37, 149, 173, 207, 158, 47, 242, 42, 89, 34, 22, 239, 185, 221, 4, 90, 179, 160, 64, 46, 133, 172, 48, 192, 47, 130, 174, 8, 229, 186, 16, 21, 160, 174, 39, 69, 70, 139, 67, 109, 107, 4, 80, 215, 34, 147, 194, 228, 121, 67, 3, 176, 69, 15, 74, 57, 236, 219, 98, 68, 48, 64, 178, 13, 194, 194, 0, 213, 61, 20, 135, 125, 239, 10, 10, 194, 201, 78, 185, 198, 138, 192, 190, 158, 5, 240, 42, 53, 0, 213, 10, 39, 207, 53, 128, 83, 1, 149, 206, 247, 131, 153, 80, 182, 156, 6, 239, 105, 78, 81, 0, 227, 245, 108, 155, 111, 110, 132, 82, 176, 218, 23, 128, 136, 109, 5, 20, 0, 206, 243, 186, 107, 7, 228, 121, 114, 240, 229, 130, 172, 22, 237, 154, 113, 97, 110, 126, 58, 70, 121, 134, 43, 1, 228, 1, 28, 124, 99, 28, 206, 10, 80, 137, 233, 51, 37, 224, 160, 207, 249, 251, 160, 79, 158, 38, 25, 44, 222, 178, 11, 184, 177, 225, 10, 162, 223, 163, 255, 163, 36, 167, 102, 139, 253, 190, 3, 142, 101, 245, 10, 11, 216, 1, 49, 239, 79, 217, 129, 199, 113, 247, 81, 11, 32, 111, 7, 232, 197, 65, 107, 168, 98, 248, 119, 23, 128, 91, 15, 113, 103, 155, 21, 65, 187, 142, 158, 82, 128, 95, 74, 1, 216, 180, 15, 106, 49, 184, 126, 157, 20, 3, 184, 1, 176, 50, 101, 64, 193, 42, 167, 0, 209, 217, 110, 192, 54, 229, 26, 20, 244, 219, 97, 45, 128, 238, 52, 3, 104, 12, 144, 180, 186, 145, 102, 177, 73, 5, 49, 7, 196, 254, 170, 50, 100, 239, 187, 177, 245, 198, 34, 119, 247, 96, 205, 168, 155, 218, 89, 157, 83, 247, 77, 202, 233, 114, 108, 125, 125, 164, 0, 236, 145, 102, 62, 55, 50, 123, 107, 251, 213, 195, 120, 60, 182, 38, 68, 156, 42, 244, 106, 119, 85, 199, 114, 245, 250, 5, 1, 218, 166, 105, 150, 210, 144, 207, 48, 204, 164, 203, 74, 151, 152, 106, 78, 191, 203, 9, 99, 62, 231, 239, 172, 198, 98, 49, 106, 50, 90, 255, 95, 175, 100, 236, 231, 168, 142, 133, 90, 116, 9, 161, 44, 14, 160, 111, 145, 58, 100, 45, 76, 186, 58, 230, 51, 191, 63, 60, 76, 27, 251, 49, 240, 223, 93, 203, 255, 12, 59, 24, 117, 192, 168, 3, 70, 29, 48, 234, 128, 81, 7, 140, 58, 96, 212, 1, 84, 7, 0, 13, 207, 230, 190, 92, 185, 148, 184, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} +var WailsLogoBlackTransparent = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 128, 0, 0, 0, 128, 8, 3, 0, 0, 0, 244, 224, 145, 249, 0, 0, 2, 250, 80, 76, 84, 69, 0, 0, 0, 227, 50, 50, 36, 32, 32, 201, 41, 45, 29, 29, 29, 227, 50, 49, 153, 23, 31, 225, 45, 46, 30, 30, 30, 198, 33, 33, 226, 47, 47, 227, 50, 50, 30, 30, 30, 226, 50, 50, 206, 41, 43, 30, 30, 30, 183, 31, 36, 185, 32, 37, 30, 30, 30, 228, 50, 50, 30, 30, 30, 30, 30, 30, 219, 47, 47, 30, 30, 30, 228, 50, 50, 28, 28, 28, 192, 35, 38, 173, 27, 33, 187, 33, 37, 227, 50, 50, 228, 50, 50, 30, 30, 30, 205, 40, 43, 215, 44, 45, 208, 42, 44, 178, 29, 35, 29, 29, 29, 183, 31, 36, 29, 29, 29, 33, 33, 33, 197, 37, 40, 210, 43, 44, 197, 37, 40, 30, 30, 30, 29, 29, 29, 29, 29, 29, 226, 49, 49, 225, 49, 49, 220, 47, 47, 30, 30, 30, 29, 29, 29, 30, 30, 30, 29, 29, 29, 192, 36, 39, 29, 29, 29, 167, 24, 31, 212, 43, 45, 206, 41, 43, 29, 29, 29, 30, 30, 30, 222, 48, 48, 30, 30, 30, 163, 24, 27, 119, 5, 16, 204, 40, 43, 196, 37, 40, 178, 29, 35, 177, 29, 35, 227, 50, 50, 164, 23, 30, 226, 49, 49, 217, 45, 47, 162, 23, 30, 172, 27, 33, 228, 50, 50, 227, 49, 49, 200, 38, 40, 30, 30, 30, 29, 29, 29, 29, 29, 29, 31, 31, 31, 150, 18, 26, 166, 24, 31, 227, 50, 49, 178, 30, 34, 228, 50, 50, 190, 34, 38, 30, 30, 30, 205, 40, 43, 30, 30, 30, 207, 41, 43, 176, 28, 34, 226, 49, 49, 195, 37, 40, 30, 30, 30, 199, 38, 41, 30, 30, 30, 127, 8, 19, 28, 28, 28, 210, 34, 36, 214, 45, 46, 200, 38, 41, 131, 11, 21, 190, 34, 38, 227, 49, 49, 190, 34, 38, 191, 34, 39, 192, 35, 39, 186, 32, 37, 226, 49, 50, 30, 30, 30, 216, 46, 46, 171, 27, 32, 199, 37, 41, 146, 16, 26, 141, 14, 25, 211, 47, 48, 120, 0, 3, 136, 15, 25, 158, 21, 29, 203, 39, 42, 218, 46, 47, 208, 42, 43, 156, 20, 28, 227, 49, 49, 191, 34, 39, 172, 27, 33, 184, 31, 36, 30, 30, 30, 204, 40, 42, 208, 42, 44, 120, 5, 17, 207, 41, 44, 204, 41, 44, 186, 32, 37, 148, 17, 25, 227, 50, 49, 224, 48, 49, 227, 49, 49, 151, 18, 26, 228, 50, 50, 125, 7, 18, 156, 20, 28, 130, 4, 17, 152, 19, 27, 177, 29, 34, 151, 18, 26, 229, 50, 51, 204, 40, 43, 189, 34, 38, 213, 44, 45, 227, 49, 49, 126, 8, 19, 145, 16, 24, 130, 9, 20, 162, 23, 29, 232, 52, 51, 254, 253, 253, 219, 75, 77, 218, 46, 46, 196, 37, 40, 184, 32, 36, 211, 43, 44, 152, 19, 27, 201, 39, 41, 165, 24, 30, 175, 28, 34, 123, 7, 18, 182, 31, 35, 170, 26, 32, 212, 43, 45, 125, 7, 18, 172, 27, 33, 247, 217, 217, 127, 8, 19, 141, 14, 23, 198, 37, 40, 118, 4, 16, 220, 196, 199, 155, 61, 69, 226, 108, 109, 233, 151, 151, 188, 111, 117, 178, 86, 92, 130, 9, 20, 119, 5, 16, 122, 6, 18, 138, 13, 23, 238, 223, 225, 197, 142, 146, 237, 173, 174, 139, 19, 28, 220, 47, 48, 31, 31, 31, 227, 50, 50, 215, 45, 46, 219, 46, 47, 224, 48, 49, 213, 44, 45, 221, 47, 48, 209, 42, 44, 33, 33, 33, 222, 48, 48, 246, 54, 54, 207, 41, 43, 205, 40, 43, 192, 34, 38, 217, 45, 46, 226, 49, 49, 211, 42, 44, 200, 38, 41, 194, 36, 40, 190, 33, 38, 232, 51, 51, 203, 40, 42, 188, 33, 37, 186, 32, 37, 229, 50, 50, 197, 37, 40, 184, 32, 36, 131, 10, 21, 180, 29, 35, 169, 25, 32, 178, 29, 34, 199, 38, 41, 182, 31, 35, 123, 6, 17, 236, 51, 51, 174, 27, 33, 172, 26, 32, 164, 23, 30, 139, 13, 23, 128, 7, 18, 136, 11, 21, 242, 52, 53, 176, 28, 34, 202, 38, 41, 167, 24, 31, 143, 14, 24, 153, 18, 27, 148, 16, 25, 238, 52, 52, 223, 45, 47, 161, 22, 30, 219, 43, 46, 206, 38, 42, 199, 34, 39, 215, 41, 44, 158, 21, 28, 195, 35, 39, 183, 30, 35, 160, 21, 29, 250, 55, 55, 253, 55, 55, 105, 176, 83, 104, 0, 0, 0, 193, 116, 82, 78, 83, 0, 254, 5, 5, 8, 250, 7, 11, 252, 8, 27, 194, 246, 168, 18, 249, 251, 29, 241, 54, 233, 212, 23, 221, 170, 20, 252, 252, 249, 246, 225, 186, 177, 33, 252, 250, 56, 50, 38, 14, 253, 248, 248, 175, 156, 149, 89, 65, 251, 204, 194, 162, 127, 43, 25, 252, 251, 249, 226, 168, 124, 92, 22, 253, 247, 231, 208, 90, 83, 82, 38, 249, 248, 247, 234, 147, 146, 142, 114, 85, 31, 246, 239, 238, 229, 207, 201, 181, 160, 135, 119, 102, 98, 81, 74, 71, 66, 52, 44, 254, 251, 249, 246, 245, 229, 191, 180, 136, 126, 110, 107, 76, 74, 59, 37, 26, 10, 253, 252, 245, 241, 168, 168, 168, 159, 157, 136, 112, 99, 95, 88, 247, 243, 227, 221, 220, 214, 202, 187, 181, 133, 105, 48, 18, 15, 239, 233, 219, 215, 211, 198, 180, 151, 90, 74, 63, 47, 250, 245, 244, 239, 238, 233, 207, 197, 194, 191, 173, 171, 150, 139, 123, 119, 243, 211, 196, 108, 88, 250, 244, 243, 238, 235, 232, 231, 229, 190, 139, 246, 242, 239, 236, 219, 38, 121, 14, 209, 0, 0, 13, 162, 73, 68, 65, 84, 120, 218, 236, 152, 87, 76, 147, 97, 20, 134, 91, 177, 24, 52, 154, 18, 19, 98, 4, 13, 208, 144, 80, 46, 132, 27, 34, 77, 8, 171, 64, 41, 112, 131, 130, 33, 160, 33, 70, 89, 130, 1, 65, 209, 168, 64, 84, 196, 129, 162, 56, 113, 239, 189, 247, 30, 233, 78, 41, 180, 20, 126, 104, 75, 203, 42, 171, 12, 129, 150, 173, 38, 158, 239, 231, 47, 85, 131, 8, 13, 200, 133, 188, 225, 134, 64, 250, 62, 223, 251, 157, 239, 156, 3, 164, 25, 205, 104, 70, 51, 154, 209, 140, 254, 19, 217, 4, 146, 166, 85, 254, 33, 14, 164, 105, 148, 77, 200, 82, 127, 210, 52, 202, 63, 66, 123, 136, 52, 125, 10, 12, 89, 82, 180, 136, 68, 178, 32, 77, 131, 144, 233, 234, 253, 88, 81, 196, 220, 233, 241, 7, 157, 159, 79, 209, 98, 193, 41, 211, 226, 111, 69, 34, 205, 217, 184, 180, 27, 227, 112, 60, 73, 211, 36, 215, 211, 156, 110, 50, 5, 91, 132, 96, 44, 44, 44, 44, 173, 172, 172, 44, 73, 255, 72, 112, 124, 155, 67, 9, 221, 24, 133, 220, 186, 223, 129, 180, 153, 244, 111, 4, 135, 28, 22, 254, 246, 230, 41, 225, 248, 100, 236, 248, 249, 145, 31, 219, 184, 111, 138, 137, 217, 100, 61, 37, 158, 191, 23, 153, 67, 200, 74, 56, 62, 133, 163, 36, 71, 230, 167, 50, 163, 179, 147, 147, 110, 39, 174, 90, 149, 152, 25, 181, 198, 125, 193, 164, 24, 207, 154, 53, 90, 93, 91, 30, 73, 79, 203, 63, 119, 246, 204, 153, 168, 121, 152, 22, 142, 207, 225, 40, 233, 225, 126, 225, 5, 154, 242, 190, 190, 202, 220, 13, 201, 107, 156, 108, 38, 251, 57, 32, 203, 181, 96, 121, 248, 240, 189, 119, 133, 59, 242, 30, 61, 186, 182, 239, 216, 177, 240, 189, 75, 180, 24, 178, 39, 43, 47, 209, 106, 6, 251, 250, 20, 225, 96, 238, 126, 116, 18, 125, 143, 60, 121, 114, 248, 222, 189, 194, 194, 91, 183, 222, 62, 124, 248, 240, 245, 235, 184, 184, 12, 93, 243, 151, 47, 93, 93, 157, 157, 93, 157, 84, 46, 134, 201, 40, 28, 228, 175, 28, 28, 210, 132, 223, 204, 98, 66, 238, 147, 171, 163, 183, 234, 247, 120, 123, 215, 215, 183, 180, 180, 168, 213, 106, 29, 168, 25, 244, 5, 105, 197, 54, 10, 25, 132, 252, 57, 152, 239, 141, 59, 209, 78, 94, 164, 41, 208, 218, 55, 45, 224, 15, 26, 97, 192, 1, 154, 195, 174, 92, 34, 115, 248, 124, 25, 242, 39, 119, 207, 207, 79, 159, 178, 22, 120, 214, 27, 101, 96, 2, 192, 9, 208, 241, 101, 124, 144, 12, 252, 41, 221, 17, 54, 164, 41, 212, 225, 250, 250, 61, 166, 4, 192, 94, 167, 11, 187, 194, 226, 243, 197, 200, 95, 38, 227, 80, 176, 149, 174, 83, 58, 3, 45, 11, 213, 120, 21, 32, 0, 93, 70, 179, 78, 157, 177, 98, 155, 152, 47, 68, 254, 124, 228, 175, 84, 110, 52, 243, 131, 9, 89, 224, 26, 227, 4, 233, 111, 213, 40, 130, 22, 117, 70, 134, 26, 66, 8, 139, 103, 241, 133, 60, 49, 1, 64, 161, 104, 119, 78, 110, 195, 3, 89, 90, 90, 25, 101, 9, 223, 64, 33, 234, 226, 32, 253, 150, 45, 91, 182, 196, 125, 90, 225, 227, 38, 230, 241, 132, 4, 0, 153, 223, 122, 192, 198, 188, 252, 189, 242, 189, 188, 142, 28, 29, 223, 212, 58, 187, 7, 153, 111, 201, 120, 246, 242, 229, 94, 15, 49, 79, 202, 19, 10, 137, 43, 224, 99, 199, 93, 205, 188, 255, 180, 188, 125, 121, 121, 121, 59, 64, 119, 238, 100, 101, 101, 221, 191, 159, 29, 29, 205, 100, 6, 164, 166, 158, 219, 228, 228, 228, 238, 238, 96, 109, 237, 181, 96, 142, 149, 5, 81, 136, 123, 212, 207, 94, 125, 120, 250, 224, 197, 69, 55, 30, 87, 202, 35, 0, 64, 28, 178, 39, 196, 102, 105, 86, 216, 103, 194, 26, 7, 154, 154, 58, 144, 170, 65, 182, 182, 108, 118, 65, 65, 193, 149, 240, 240, 220, 220, 220, 147, 39, 97, 160, 36, 110, 216, 96, 191, 61, 50, 50, 52, 52, 212, 51, 244, 213, 211, 247, 23, 46, 60, 120, 206, 234, 151, 130, 191, 9, 128, 172, 116, 201, 59, 227, 133, 79, 101, 51, 20, 112, 172, 122, 235, 215, 175, 134, 166, 38, 0, 104, 108, 104, 107, 171, 209, 212, 13, 85, 41, 42, 203, 75, 74, 6, 7, 85, 125, 114, 121, 123, 123, 69, 49, 72, 34, 145, 136, 122, 47, 62, 184, 112, 225, 197, 197, 117, 235, 4, 92, 174, 212, 148, 128, 76, 73, 15, 235, 218, 122, 227, 126, 26, 170, 105, 51, 9, 12, 6, 67, 211, 114, 144, 45, 18, 141, 6, 16, 212, 42, 5, 80, 0, 70, 89, 153, 170, 182, 86, 14, 20, 18, 81, 233, 186, 139, 31, 159, 227, 246, 92, 34, 1, 188, 8, 57, 110, 65, 186, 78, 67, 245, 114, 191, 44, 39, 146, 89, 10, 216, 218, 24, 4, 9, 64, 4, 144, 193, 112, 8, 154, 186, 42, 72, 1, 0, 22, 47, 46, 43, 115, 118, 118, 150, 203, 43, 42, 42, 138, 29, 139, 123, 233, 224, 47, 16, 152, 0, 144, 63, 133, 173, 238, 234, 28, 104, 106, 24, 170, 203, 205, 140, 49, 155, 0, 1, 116, 252, 2, 96, 74, 0, 34, 0, 0, 252, 26, 214, 137, 192, 255, 215, 4, 148, 241, 234, 102, 4, 80, 221, 86, 53, 168, 242, 181, 223, 61, 199, 44, 130, 134, 32, 34, 129, 134, 134, 182, 26, 184, 1, 34, 1, 240, 71, 87, 128, 238, 0, 7, 16, 137, 68, 165, 35, 0, 66, 28, 128, 195, 200, 80, 127, 233, 234, 252, 218, 212, 88, 83, 87, 89, 214, 91, 154, 115, 106, 227, 92, 51, 8, 130, 218, 130, 140, 53, 64, 3, 81, 145, 236, 236, 22, 35, 193, 13, 212, 202, 127, 7, 144, 26, 1, 56, 172, 176, 250, 102, 2, 160, 170, 178, 76, 94, 204, 21, 178, 14, 120, 158, 55, 131, 64, 131, 178, 135, 240, 135, 134, 134, 80, 250, 40, 126, 120, 8, 144, 63, 4, 128, 106, 96, 4, 192, 84, 2, 32, 25, 101, 133, 183, 26, 7, 232, 0, 128, 242, 50, 185, 68, 196, 21, 43, 101, 199, 67, 22, 78, 148, 128, 185, 2, 196, 102, 179, 109, 109, 209, 67, 172, 82, 160, 103, 168, 130, 55, 216, 43, 1, 79, 73, 236, 31, 0, 196, 100, 59, 239, 22, 29, 2, 48, 116, 52, 104, 20, 37, 170, 118, 73, 41, 151, 39, 86, 106, 177, 132, 249, 254, 115, 240, 166, 62, 110, 130, 252, 212, 115, 169, 169, 169, 1, 1, 76, 38, 51, 58, 58, 58, 59, 59, 59, 57, 57, 57, 41, 41, 41, 20, 20, 233, 219, 31, 11, 254, 163, 0, 112, 114, 226, 188, 213, 8, 96, 192, 80, 13, 0, 139, 93, 124, 24, 116, 150, 155, 219, 146, 37, 176, 162, 5, 47, 90, 182, 224, 15, 43, 174, 229, 108, 203, 95, 101, 5, 115, 103, 172, 201, 180, 230, 114, 143, 15, 74, 64, 244, 27, 0, 159, 197, 142, 171, 71, 0, 248, 35, 168, 171, 108, 239, 239, 151, 234, 101, 24, 214, 221, 173, 213, 118, 23, 21, 97, 17, 135, 198, 127, 19, 99, 55, 177, 53, 219, 122, 124, 140, 1, 24, 1, 208, 48, 182, 27, 8, 138, 131, 43, 232, 26, 6, 176, 243, 245, 101, 8, 4, 44, 55, 10, 18, 74, 1, 163, 140, 130, 112, 36, 109, 45, 40, 45, 45, 221, 203, 203, 43, 221, 218, 164, 209, 8, 208, 13, 154, 8, 160, 13, 16, 1, 16, 9, 240, 93, 12, 77, 213, 91, 227, 116, 198, 71, 16, 191, 225, 113, 76, 204, 238, 213, 203, 140, 242, 247, 95, 182, 122, 217, 66, 139, 223, 1, 214, 62, 185, 87, 120, 235, 209, 62, 208, 181, 107, 55, 110, 220, 188, 121, 115, 21, 232, 250, 245, 168, 177, 23, 170, 221, 219, 244, 62, 224, 255, 51, 0, 159, 190, 220, 208, 81, 109, 27, 166, 30, 121, 4, 190, 246, 187, 108, 198, 49, 0, 143, 166, 175, 61, 123, 248, 221, 142, 107, 199, 6, 26, 135, 160, 216, 203, 203, 203, 161, 122, 69, 62, 127, 35, 200, 209, 231, 252, 2, 32, 102, 209, 12, 168, 115, 217, 134, 53, 119, 118, 26, 112, 0, 185, 132, 113, 10, 122, 192, 248, 118, 67, 171, 244, 252, 212, 232, 172, 29, 126, 5, 154, 193, 246, 222, 222, 10, 121, 113, 206, 223, 8, 124, 244, 116, 32, 32, 0, 164, 66, 183, 120, 3, 222, 185, 53, 236, 48, 226, 17, 148, 171, 42, 4, 122, 222, 231, 16, 87, 132, 48, 206, 209, 188, 32, 237, 28, 51, 57, 51, 209, 215, 69, 212, 163, 228, 221, 29, 251, 223, 47, 187, 24, 64, 48, 146, 128, 56, 118, 121, 117, 3, 248, 215, 212, 81, 217, 93, 80, 131, 168, 13, 56, 87, 56, 10, 120, 74, 14, 244, 128, 137, 253, 133, 62, 199, 218, 105, 205, 227, 80, 251, 189, 87, 61, 73, 99, 42, 229, 106, 143, 135, 17, 64, 76, 103, 119, 192, 220, 64, 99, 67, 97, 23, 212, 137, 191, 194, 18, 85, 133, 72, 32, 21, 202, 180, 216, 202, 131, 171, 109, 38, 188, 27, 45, 8, 76, 217, 109, 243, 199, 164, 28, 54, 165, 236, 138, 58, 209, 227, 65, 12, 67, 225, 122, 26, 225, 143, 150, 23, 187, 32, 67, 35, 0, 148, 213, 226, 0, 124, 50, 7, 211, 46, 57, 224, 25, 72, 50, 95, 38, 95, 167, 152, 93, 81, 73, 145, 246, 215, 79, 236, 189, 202, 112, 164, 27, 219, 0, 207, 45, 190, 3, 236, 9, 255, 146, 50, 234, 114, 124, 20, 213, 22, 227, 0, 28, 50, 66, 32, 239, 135, 129, 96, 166, 142, 90, 59, 197, 48, 179, 147, 50, 55, 36, 158, 244, 189, 236, 226, 220, 94, 12, 213, 95, 10, 222, 165, 4, 128, 148, 23, 107, 219, 48, 108, 143, 251, 171, 92, 168, 141, 26, 52, 138, 138, 69, 92, 30, 0, 112, 0, 129, 140, 105, 149, 9, 59, 83, 44, 38, 184, 165, 7, 48, 163, 179, 238, 220, 78, 244, 203, 189, 18, 111, 7, 31, 44, 71, 59, 161, 28, 223, 135, 24, 142, 142, 142, 198, 87, 200, 163, 211, 8, 127, 124, 101, 112, 86, 181, 95, 14, 71, 143, 0, 7, 16, 203, 0, 0, 103, 80, 66, 49, 156, 94, 141, 15, 132, 241, 114, 88, 223, 86, 208, 106, 170, 240, 85, 8, 215, 240, 58, 0, 4, 196, 44, 68, 1, 172, 103, 185, 121, 80, 27, 145, 61, 238, 143, 22, 22, 121, 241, 182, 204, 240, 190, 197, 42, 57, 62, 11, 101, 208, 135, 71, 186, 177, 114, 229, 193, 9, 45, 40, 78, 126, 125, 26, 90, 93, 29, 218, 70, 236, 42, 137, 149, 148, 88, 71, 192, 223, 145, 78, 95, 47, 224, 201, 120, 241, 13, 104, 103, 82, 24, 127, 1, 240, 74, 67, 239, 15, 170, 98, 43, 36, 2, 174, 80, 172, 108, 253, 86, 212, 218, 10, 95, 184, 190, 125, 111, 93, 26, 226, 58, 254, 61, 205, 221, 175, 132, 70, 197, 239, 214, 180, 17, 226, 254, 12, 6, 221, 81, 196, 147, 41, 149, 250, 75, 46, 192, 104, 58, 62, 242, 103, 72, 79, 185, 223, 134, 153, 45, 2, 0, 254, 202, 136, 136, 165, 193, 9, 75, 71, 148, 16, 156, 48, 207, 51, 112, 34, 25, 208, 168, 196, 78, 108, 220, 72, 99, 99, 125, 24, 162, 30, 189, 76, 95, 122, 245, 84, 100, 84, 74, 114, 165, 130, 10, 246, 68, 252, 40, 28, 71, 1, 221, 213, 250, 114, 41, 3, 111, 3, 193, 59, 253, 3, 29, 206, 187, 186, 46, 52, 201, 213, 213, 97, 66, 183, 64, 165, 42, 140, 43, 169, 179, 139, 139, 139, 92, 210, 175, 215, 11, 114, 78, 216, 223, 221, 181, 201, 1, 122, 156, 85, 102, 159, 194, 174, 196, 120, 124, 84, 28, 30, 30, 208, 70, 31, 247, 139, 28, 185, 168, 13, 4, 31, 220, 56, 138, 223, 4, 9, 126, 208, 102, 254, 46, 9, 132, 97, 28, 127, 95, 228, 56, 50, 12, 34, 184, 41, 67, 27, 29, 202, 37, 112, 144, 72, 8, 7, 215, 168, 73, 104, 17, 34, 155, 130, 38, 49, 154, 4, 149, 134, 64, 205, 177, 165, 169, 63, 227, 245, 39, 94, 200, 157, 118, 130, 173, 110, 210, 162, 120, 184, 40, 244, 220, 251, 88, 167, 114, 147, 220, 125, 254, 129, 239, 243, 189, 247, 121, 223, 231, 199, 225, 80, 112, 19, 248, 214, 167, 147, 105, 47, 243, 14, 198, 205, 213, 211, 105, 82, 55, 6, 6, 60, 28, 158, 155, 137, 198, 171, 72, 124, 227, 16, 228, 96, 141, 105, 35, 150, 53, 11, 194, 70, 17, 252, 236, 3, 95, 221, 158, 170, 234, 129, 162, 239, 14, 141, 47, 225, 191, 82, 3, 104, 31, 245, 225, 102, 190, 29, 147, 147, 140, 146, 128, 75, 64, 41, 132, 160, 29, 164, 121, 65, 216, 218, 48, 2, 189, 221, 209, 245, 110, 228, 246, 241, 201, 122, 215, 121, 31, 153, 6, 58, 61, 227, 243, 99, 125, 132, 238, 32, 12, 5, 179, 218, 10, 226, 194, 200, 120, 3, 98, 101, 94, 16, 92, 27, 69, 80, 106, 151, 146, 96, 124, 23, 76, 88, 115, 29, 81, 207, 254, 237, 131, 126, 176, 159, 119, 17, 215, 199, 40, 88, 91, 188, 66, 16, 194, 192, 187, 29, 222, 52, 25, 78, 76, 227, 214, 236, 22, 161, 71, 227, 250, 216, 32, 182, 148, 11, 120, 249, 221, 185, 161, 151, 33, 148, 23, 4, 108, 10, 29, 64, 242, 53, 209, 62, 232, 47, 134, 68, 190, 165, 58, 14, 14, 4, 70, 25, 194, 67, 96, 133, 52, 228, 163, 253, 250, 147, 207, 195, 191, 207, 143, 83, 178, 160, 149, 9, 16, 150, 153, 128, 242, 102, 65, 128, 9, 65, 36, 182, 114, 202, 245, 85, 44, 140, 184, 170, 82, 104, 63, 22, 53, 10, 120, 121, 238, 165, 43, 8, 76, 211, 176, 32, 216, 198, 142, 161, 175, 130, 125, 208, 71, 251, 138, 2, 94, 113, 83, 184, 23, 147, 189, 84, 88, 163, 175, 97, 131, 98, 155, 126, 147, 235, 163, 125, 220, 84, 66, 0, 240, 183, 200, 32, 62, 156, 13, 229, 53, 134, 242, 108, 46, 95, 120, 206, 37, 27, 86, 136, 168, 95, 95, 181, 111, 64, 251, 7, 126, 2, 136, 207, 217, 252, 182, 21, 151, 57, 79, 92, 178, 65, 63, 85, 85, 67, 120, 250, 11, 125, 243, 222, 197, 137, 193, 131, 223, 109, 133, 36, 185, 163, 81, 209, 6, 253, 70, 29, 245, 77, 251, 8, 156, 129, 135, 56, 137, 169, 111, 97, 31, 207, 160, 32, 17, 39, 65, 253, 74, 168, 178, 148, 125, 108, 9, 202, 132, 35, 226, 44, 47, 169, 49, 234, 155, 246, 87, 2, 208, 210, 196, 81, 196, 95, 1, 237, 79, 129, 122, 31, 110, 63, 74, 28, 116, 240, 49, 208, 16, 148, 68, 0, 237, 7, 91, 15, 45, 251, 48, 192, 33, 89, 90, 78, 156, 115, 70, 28, 135, 216, 15, 178, 30, 108, 63, 166, 3, 110, 122, 51, 208, 12, 40, 42, 188, 122, 12, 172, 226, 31, 127, 252, 248, 10, 216, 230, 198, 1, 254, 100, 240, 209, 44, 8, 226, 21, 42, 33, 128, 23, 9, 72, 192, 0, 136, 13, 130, 53, 110, 52, 115, 0, 59, 31, 39, 59, 17, 128, 153, 97, 20, 140, 2, 0, 113, 100, 172, 194, 48, 8, 69, 209, 71, 112, 232, 80, 40, 136, 82, 156, 178, 136, 25, 170, 67, 99, 112, 104, 201, 39, 164, 205, 154, 37, 148, 124, 64, 191, 33, 191, 246, 70, 255, 170, 134, 130, 74, 233, 144, 118, 201, 25, 174, 92, 148, 235, 125, 188, 191, 41, 2, 239, 35, 153, 252, 46, 247, 240, 197, 127, 198, 17, 66, 10, 216, 142, 34, 234, 102, 8, 39, 126, 170, 224, 58, 109, 58, 5, 163, 213, 67, 15, 173, 53, 122, 134, 136, 210, 218, 232, 70, 164, 52, 49, 88, 99, 100, 26, 183, 189, 118, 186, 133, 156, 209, 214, 231, 82, 223, 9, 172, 70, 81, 68, 42, 225, 228, 145, 77, 80, 123, 196, 91, 250, 175, 9, 22, 47, 10, 98, 156, 220, 35, 238, 31, 241, 65, 85, 250, 3, 14, 249, 184, 79, 138, 11, 220, 193, 234, 10, 61, 67, 164, 83, 21, 148, 41, 65, 23, 141, 121, 130, 47, 5, 152, 76, 97, 99, 240, 199, 84, 168, 231, 161, 192, 156, 237, 95, 82, 191, 67, 244, 158, 147, 245, 59, 112, 47, 118, 171, 29, 215, 65, 24, 8, 90, 145, 11, 23, 150, 44, 161, 32, 139, 138, 6, 133, 2, 40, 248, 40, 5, 136, 35, 240, 233, 80, 154, 40, 202, 1, 56, 67, 174, 54, 165, 111, 245, 214, 122, 47, 54, 148, 238, 223, 40, 10, 90, 203, 222, 157, 29, 47, 35, 180, 109, 242, 101, 43, 173, 54, 127, 113, 144, 19, 22, 215, 151, 39, 176, 83, 193, 38, 119, 113, 22, 65, 69, 78, 16, 194, 195, 64, 200, 52, 149, 105, 192, 16, 36, 177, 109, 170, 51, 17, 122, 250, 135, 248, 248, 163, 173, 17, 66, 33, 90, 60, 129, 201, 72, 12, 62, 249, 19, 150, 188, 63, 112, 25, 44, 65, 158, 63, 23, 10, 66, 8, 196, 89, 67, 153, 117, 182, 25, 232, 187, 203, 119, 187, 66, 196, 87, 68, 163, 35, 144, 23, 70, 30, 239, 124, 135, 64, 157, 83, 232, 41, 43, 232, 132, 5, 33, 175, 129, 122, 148, 232, 161, 71, 13, 180, 254, 66, 119, 32, 170, 52, 228, 238, 42, 220, 107, 40, 236, 204, 225, 44, 8, 225, 3, 72, 108, 57, 11, 1, 111, 12, 234, 9, 186, 128, 158, 20, 228, 226, 210, 241, 20, 208, 11, 169, 208, 185, 165, 183, 29, 216, 39, 133, 78, 144, 8, 211, 145, 64, 82, 24, 8, 12, 151, 16, 39, 226, 169, 17, 186, 198, 240, 192, 181, 6, 226, 196, 79, 180, 4, 218, 155, 192, 65, 243, 23, 128, 254, 205, 184, 19, 196, 72, 236, 39, 27, 92, 83, 16, 186, 32, 239, 44, 140, 144, 66, 204, 179, 81, 18, 168, 152, 195, 3, 80, 227, 10, 90, 115, 4, 62, 246, 206, 19, 198, 189, 32, 74, 45, 54, 244, 12, 222, 49, 4, 226, 60, 64, 2, 154, 92, 1, 244, 217, 108, 132, 128, 124, 186, 76, 119, 202, 36, 135, 18, 64, 235, 8, 116, 70, 161, 96, 182, 162, 23, 228, 206, 206, 24, 1, 107, 105, 1, 86, 216, 90, 2, 169, 77, 78, 237, 113, 71, 96, 1, 20, 0, 1, 20, 20, 126, 185, 202, 95, 61, 188, 32, 241, 107, 125, 187, 153, 152, 199, 11, 155, 45, 171, 32, 2, 165, 37, 208, 177, 201, 42, 240, 56, 242, 2, 96, 64, 72, 249, 95, 186, 164, 54, 226, 236, 212, 10, 50, 214, 69, 238, 140, 17, 195, 208, 67, 32, 192, 8, 9, 19, 164, 117, 187, 10, 82, 69, 153, 19, 224, 166, 33, 101, 53, 183, 56, 88, 223, 218, 67, 29, 108, 137, 151, 38, 34, 210, 104, 104, 255, 215, 24, 41, 84, 64, 23, 82, 159, 85, 134, 188, 251, 98, 59, 54, 181, 151, 183, 163, 85, 205, 217, 66, 143, 235, 250, 87, 243, 69, 129, 202, 252, 75, 160, 233, 8, 253, 202, 239, 169, 209, 252, 170, 54, 228, 65, 4, 198, 102, 219, 118, 186, 208, 166, 72, 71, 63, 154, 29, 133, 51, 53, 149, 110, 105, 249, 53, 71, 27, 84, 137, 39, 80, 54, 197, 182, 21, 155, 243, 221, 91, 21, 95, 163, 190, 153, 67, 172, 208, 127, 67, 217, 7, 243, 224, 126, 149, 159, 119, 158, 247, 156, 151, 242, 228, 158, 112, 246, 143, 127, 252, 108, 20, 140, 130, 81, 48, 10, 70, 193, 40, 24, 5, 88, 1, 0, 48, 20, 121, 13, 171, 25, 166, 148, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} +var WailsLogoWhiteTransparent = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 128, 0, 0, 0, 128, 8, 3, 0, 0, 0, 244, 224, 145, 249, 0, 0, 2, 244, 80, 76, 84, 69, 0, 0, 0, 190, 37, 40, 145, 21, 31, 222, 46, 46, 208, 42, 44, 228, 50, 50, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 227, 50, 49, 227, 50, 50, 207, 38, 39, 224, 48, 49, 255, 255, 255, 227, 48, 48, 227, 50, 50, 255, 255, 255, 214, 44, 45, 202, 40, 43, 210, 42, 44, 174, 27, 33, 206, 41, 43, 255, 255, 255, 186, 33, 37, 255, 255, 255, 186, 32, 37, 134, 12, 22, 183, 31, 36, 150, 19, 25, 228, 50, 50, 255, 255, 255, 187, 34, 37, 255, 255, 255, 142, 14, 23, 196, 37, 40, 169, 25, 31, 220, 47, 47, 215, 45, 46, 227, 50, 50, 203, 40, 42, 198, 37, 40, 255, 255, 255, 255, 255, 255, 255, 255, 255, 120, 5, 16, 227, 50, 49, 203, 40, 42, 255, 255, 255, 255, 255, 255, 161, 22, 29, 227, 49, 49, 189, 33, 37, 255, 255, 255, 192, 35, 39, 255, 255, 255, 225, 49, 49, 255, 255, 255, 255, 255, 255, 167, 24, 31, 255, 255, 255, 255, 255, 255, 188, 33, 38, 179, 30, 35, 227, 50, 50, 225, 48, 48, 151, 18, 26, 205, 40, 43, 228, 50, 50, 192, 35, 38, 184, 32, 36, 255, 255, 255, 255, 255, 255, 218, 46, 47, 224, 48, 49, 228, 49, 49, 221, 46, 48, 255, 255, 255, 130, 11, 22, 196, 36, 40, 227, 50, 50, 191, 34, 39, 255, 255, 255, 227, 50, 50, 176, 28, 34, 176, 29, 33, 148, 17, 25, 255, 255, 255, 121, 6, 17, 228, 50, 50, 255, 255, 255, 188, 33, 38, 255, 255, 255, 255, 255, 255, 221, 47, 48, 226, 49, 49, 206, 40, 43, 255, 255, 255, 191, 34, 38, 255, 255, 255, 151, 18, 26, 227, 50, 50, 195, 37, 40, 174, 28, 33, 216, 46, 46, 146, 16, 26, 255, 255, 255, 210, 34, 36, 180, 30, 35, 150, 18, 26, 227, 50, 50, 211, 43, 45, 227, 49, 49, 195, 36, 40, 206, 41, 43, 255, 255, 255, 186, 32, 37, 225, 48, 49, 208, 42, 44, 156, 20, 28, 195, 37, 39, 125, 7, 18, 196, 37, 40, 149, 16, 27, 119, 0, 3, 217, 46, 47, 203, 39, 42, 203, 41, 43, 149, 17, 26, 178, 29, 34, 207, 41, 44, 255, 255, 255, 167, 25, 31, 164, 23, 30, 205, 40, 43, 227, 49, 49, 198, 37, 39, 228, 50, 50, 216, 45, 46, 175, 28, 33, 127, 8, 19, 255, 255, 255, 206, 41, 43, 255, 255, 255, 255, 255, 255, 186, 33, 37, 177, 29, 34, 155, 20, 27, 255, 255, 255, 255, 255, 255, 182, 31, 35, 224, 48, 48, 211, 43, 45, 131, 10, 20, 186, 32, 37, 227, 49, 49, 173, 27, 33, 126, 8, 19, 255, 255, 255, 221, 47, 48, 116, 4, 16, 255, 255, 255, 255, 255, 255, 255, 255, 255, 234, 53, 51, 127, 8, 19, 139, 13, 23, 227, 50, 49, 133, 10, 21, 124, 6, 18, 255, 255, 255, 255, 255, 255, 234, 216, 218, 172, 81, 88, 146, 16, 25, 214, 44, 46, 178, 30, 34, 168, 24, 31, 122, 6, 18, 166, 24, 31, 255, 255, 255, 216, 67, 69, 251, 216, 216, 226, 108, 109, 233, 151, 151, 188, 111, 117, 142, 17, 26, 165, 24, 31, 199, 38, 41, 214, 184, 187, 219, 87, 90, 224, 79, 80, 197, 142, 146, 149, 48, 57, 237, 173, 174, 227, 50, 50, 255, 255, 255, 223, 48, 48, 215, 45, 46, 221, 47, 48, 218, 46, 47, 213, 44, 45, 211, 43, 45, 203, 39, 42, 193, 35, 39, 219, 46, 47, 191, 34, 38, 206, 41, 43, 188, 33, 38, 246, 54, 54, 183, 31, 36, 199, 38, 41, 186, 32, 37, 209, 42, 44, 173, 27, 33, 179, 29, 35, 197, 37, 40, 176, 28, 34, 229, 50, 50, 181, 30, 35, 233, 51, 51, 170, 26, 32, 168, 24, 31, 201, 38, 41, 164, 23, 30, 236, 52, 52, 124, 6, 17, 131, 11, 21, 242, 53, 53, 195, 35, 39, 161, 22, 29, 222, 45, 47, 147, 16, 25, 137, 12, 22, 128, 8, 19, 140, 13, 23, 153, 18, 26, 219, 43, 46, 157, 20, 28, 205, 39, 42, 200, 35, 40, 231, 50, 50, 207, 39, 42, 215, 41, 44, 252, 55, 55, 238, 52, 52, 217, 45, 46, 212, 40, 44, 132, 6, 18, 235, 51, 51, 226, 46, 48, 239, 50, 51, 194, 134, 147, 194, 0, 0, 0, 195, 116, 82, 78, 83, 0, 5, 7, 11, 254, 253, 7, 251, 9, 4, 250, 169, 8, 253, 248, 27, 194, 244, 22, 17, 249, 249, 246, 233, 251, 164, 29, 254, 249, 24, 226, 222, 45, 19, 254, 252, 252, 251, 250, 246, 249, 248, 179, 56, 38, 254, 237, 176, 156, 149, 247, 87, 53, 24, 247, 241, 203, 185, 173, 243, 213, 208, 208, 90, 55, 32, 246, 239, 221, 196, 134, 127, 93, 68, 62, 51, 38, 13, 243, 233, 211, 181, 132, 102, 102, 65, 48, 44, 247, 232, 226, 221, 202, 190, 168, 120, 120, 113, 110, 107, 89, 82, 81, 79, 76, 37, 29, 254, 231, 182, 181, 165, 160, 154, 145, 143, 125, 94, 88, 79, 73, 54, 33, 15, 254, 245, 229, 217, 216, 206, 198, 196, 193, 170, 159, 145, 139, 133, 130, 118, 111, 100, 96, 88, 84, 240, 239, 236, 218, 194, 191, 189, 178, 169, 166, 151, 151, 151, 138, 111, 93, 78, 72, 67, 41, 221, 201, 173, 134, 74, 64, 33, 244, 237, 225, 224, 224, 209, 187, 139, 120, 245, 244, 243, 238, 235, 234, 228, 167, 254, 245, 245, 242, 240, 239, 163, 91, 109, 90, 0, 0, 13, 94, 73, 68, 65, 84, 120, 218, 236, 152, 105, 72, 147, 113, 28, 199, 159, 212, 17, 44, 87, 177, 182, 33, 186, 137, 168, 51, 247, 66, 3, 19, 19, 4, 205, 19, 52, 209, 36, 21, 195, 23, 133, 88, 41, 105, 165, 216, 97, 167, 69, 183, 18, 84, 208, 29, 245, 162, 19, 186, 163, 251, 238, 105, 30, 59, 156, 186, 195, 185, 205, 99, 78, 55, 231, 60, 166, 166, 89, 175, 250, 253, 159, 103, 143, 51, 203, 114, 166, 249, 34, 63, 136, 40, 27, 251, 126, 254, 191, 231, 247, 255, 255, 254, 12, 155, 101, 150, 89, 102, 153, 101, 150, 255, 4, 102, 34, 54, 163, 44, 61, 18, 137, 205, 32, 204, 139, 158, 75, 177, 25, 100, 93, 168, 254, 24, 54, 115, 36, 94, 92, 92, 59, 15, 195, 230, 96, 51, 0, 10, 93, 181, 82, 87, 187, 50, 114, 102, 242, 129, 21, 243, 232, 122, 93, 96, 206, 140, 228, 187, 98, 152, 243, 149, 24, 125, 31, 142, 175, 193, 102, 136, 132, 77, 248, 32, 78, 215, 205, 67, 50, 115, 230, 204, 113, 114, 117, 117, 117, 194, 254, 17, 176, 124, 230, 49, 207, 65, 29, 157, 214, 22, 19, 137, 57, 99, 255, 6, 88, 36, 9, 177, 247, 22, 106, 244, 56, 29, 215, 197, 92, 27, 121, 153, 233, 149, 182, 99, 71, 176, 251, 180, 100, 142, 109, 178, 200, 35, 129, 131, 125, 116, 26, 174, 161, 109, 78, 223, 16, 145, 154, 85, 20, 119, 225, 76, 120, 120, 74, 193, 218, 245, 94, 139, 166, 40, 248, 87, 125, 237, 180, 139, 151, 155, 158, 182, 61, 59, 251, 202, 66, 157, 30, 167, 225, 56, 174, 225, 60, 120, 152, 119, 74, 91, 109, 181, 214, 100, 166, 20, 173, 247, 98, 78, 233, 118, 160, 34, 139, 111, 103, 151, 149, 221, 186, 117, 249, 124, 126, 254, 141, 146, 146, 123, 47, 95, 237, 93, 172, 239, 163, 161, 124, 218, 30, 118, 125, 187, 213, 170, 200, 75, 137, 67, 75, 159, 58, 118, 93, 189, 122, 181, 236, 214, 229, 203, 231, 31, 61, 186, 113, 253, 250, 245, 164, 164, 36, 15, 179, 197, 187, 163, 163, 163, 171, 171, 163, 139, 91, 222, 167, 131, 229, 3, 26, 141, 164, 174, 62, 239, 76, 92, 4, 25, 62, 149, 2, 231, 91, 51, 2, 2, 2, 90, 91, 59, 59, 13, 134, 97, 15, 15, 179, 217, 108, 177, 88, 188, 189, 59, 188, 189, 195, 162, 225, 209, 51, 112, 2, 93, 242, 193, 75, 169, 193, 60, 108, 26, 40, 78, 26, 38, 4, 64, 1, 57, 120, 32, 7, 16, 176, 68, 157, 220, 67, 195, 133, 66, 28, 65, 211, 207, 75, 231, 77, 219, 17, 120, 59, 0, 213, 128, 18, 48, 160, 124, 139, 25, 150, 207, 128, 120, 82, 128, 174, 15, 101, 98, 211, 72, 89, 107, 107, 70, 235, 40, 1, 139, 217, 3, 150, 207, 16, 10, 25, 164, 0, 93, 23, 152, 48, 173, 51, 208, 233, 242, 112, 64, 6, 228, 35, 1, 244, 0, 12, 222, 97, 28, 134, 176, 162, 66, 72, 128, 211, 52, 154, 43, 147, 252, 96, 27, 115, 72, 176, 113, 225, 61, 26, 70, 37, 64, 241, 195, 195, 195, 134, 168, 147, 126, 194, 10, 233, 72, 62, 93, 191, 101, 106, 15, 60, 192, 201, 201, 149, 194, 9, 254, 193, 138, 175, 155, 51, 160, 250, 157, 187, 119, 239, 78, 122, 25, 198, 225, 11, 43, 237, 249, 140, 182, 80, 230, 228, 234, 207, 75, 231, 241, 22, 237, 154, 216, 212, 186, 157, 49, 12, 225, 1, 73, 119, 95, 188, 216, 199, 98, 84, 66, 190, 237, 9, 224, 12, 93, 76, 252, 36, 159, 127, 110, 126, 73, 126, 126, 254, 33, 224, 210, 165, 199, 143, 227, 74, 75, 75, 83, 83, 35, 34, 54, 108, 216, 145, 150, 22, 28, 236, 229, 181, 211, 221, 157, 185, 200, 217, 213, 246, 217, 87, 51, 12, 31, 238, 190, 191, 115, 231, 249, 59, 190, 180, 124, 200, 46, 192, 192, 105, 107, 160, 108, 78, 147, 42, 118, 118, 148, 170, 167, 165, 165, 165, 9, 104, 68, 176, 217, 236, 83, 167, 78, 229, 229, 229, 61, 200, 204, 12, 15, 15, 143, 141, 141, 77, 73, 89, 242, 250, 245, 225, 130, 194, 194, 227, 71, 239, 190, 185, 51, 119, 254, 211, 103, 123, 6, 134, 32, 127, 84, 5, 52, 130, 252, 108, 30, 57, 149, 29, 103, 251, 233, 166, 32, 163, 209, 8, 14, 96, 160, 106, 104, 168, 175, 215, 214, 41, 20, 53, 213, 213, 146, 246, 118, 181, 85, 222, 220, 44, 22, 247, 246, 202, 100, 253, 253, 50, 151, 254, 39, 79, 63, 45, 123, 254, 100, 227, 198, 170, 242, 242, 202, 81, 2, 26, 86, 84, 71, 208, 193, 210, 92, 212, 211, 127, 99, 224, 139, 80, 177, 217, 224, 0, 18, 117, 10, 183, 26, 164, 33, 105, 87, 203, 229, 200, 66, 230, 34, 218, 248, 246, 227, 51, 34, 158, 20, 160, 10, 192, 15, 50, 119, 25, 155, 124, 247, 199, 5, 99, 192, 100, 12, 26, 131, 32, 31, 149, 0, 106, 0, 69, 104, 128, 124, 16, 168, 1, 1, 137, 68, 226, 227, 227, 67, 24, 136, 101, 80, 7, 214, 198, 47, 85, 85, 200, 0, 242, 109, 2, 56, 141, 109, 232, 232, 234, 105, 81, 105, 235, 50, 11, 118, 76, 210, 64, 21, 68, 246, 1, 41, 0, 5, 64, 21, 80, 80, 21, 80, 83, 2, 46, 202, 141, 74, 17, 228, 255, 32, 160, 9, 241, 176, 32, 1, 223, 134, 186, 118, 117, 242, 146, 109, 206, 147, 49, 8, 66, 6, 68, 62, 41, 96, 175, 0, 249, 8, 40, 1, 23, 165, 136, 18, 160, 90, 0, 231, 120, 27, 188, 59, 186, 140, 45, 141, 13, 117, 53, 237, 189, 34, 206, 217, 43, 11, 28, 55, 200, 14, 106, 8, 242, 69, 176, 17, 245, 92, 192, 13, 64, 2, 62, 192, 79, 2, 246, 30, 196, 249, 81, 173, 150, 17, 1, 137, 92, 86, 110, 226, 135, 30, 191, 54, 9, 3, 45, 90, 58, 85, 124, 98, 245, 228, 242, 209, 3, 240, 25, 71, 0, 12, 232, 97, 1, 6, 16, 232, 49, 54, 169, 234, 161, 2, 114, 153, 75, 185, 80, 35, 140, 57, 146, 224, 176, 193, 171, 48, 0, 150, 175, 106, 176, 237, 67, 8, 183, 54, 139, 123, 251, 251, 149, 95, 250, 253, 199, 171, 0, 206, 13, 232, 244, 48, 219, 4, 20, 213, 106, 177, 76, 84, 46, 101, 104, 244, 186, 213, 91, 214, 57, 19, 135, 250, 132, 13, 210, 211, 210, 210, 54, 0, 17, 17, 17, 169, 169, 169, 89, 89, 165, 69, 69, 69, 113, 113, 71, 143, 22, 22, 20, 28, 222, 59, 224, 111, 107, 194, 49, 2, 56, 39, 41, 192, 128, 122, 16, 9, 104, 21, 33, 2, 127, 14, 203, 143, 207, 95, 188, 152, 142, 227, 171, 111, 110, 93, 52, 206, 233, 231, 52, 22, 87, 152, 59, 191, 155, 76, 235, 163, 187, 253, 33, 159, 18, 176, 111, 2, 62, 59, 169, 213, 96, 70, 2, 208, 2, 218, 154, 230, 47, 3, 67, 38, 188, 79, 167, 31, 212, 235, 7, 107, 107, 117, 43, 143, 173, 112, 100, 48, 99, 227, 131, 12, 144, 0, 202, 167, 4, 42, 164, 66, 134, 91, 79, 80, 82, 39, 37, 80, 231, 150, 156, 236, 47, 170, 242, 227, 51, 232, 0, 170, 130, 142, 30, 250, 179, 194, 174, 220, 226, 226, 226, 244, 220, 92, 30, 194, 221, 14, 24, 252, 98, 241, 163, 12, 56, 35, 45, 64, 85, 128, 33, 48, 182, 52, 69, 121, 152, 169, 77, 16, 146, 146, 21, 31, 191, 109, 235, 214, 165, 54, 214, 45, 221, 186, 106, 235, 242, 177, 93, 192, 43, 190, 90, 118, 249, 252, 141, 146, 123, 247, 74, 74, 30, 30, 4, 206, 156, 137, 5, 14, 172, 253, 253, 133, 106, 91, 180, 137, 51, 70, 64, 200, 242, 53, 54, 53, 250, 70, 121, 128, 0, 180, 0, 236, 194, 228, 37, 39, 152, 19, 24, 128, 187, 120, 233, 183, 203, 110, 157, 191, 113, 186, 71, 85, 87, 77, 32, 81, 139, 149, 254, 127, 50, 224, 152, 56, 74, 165, 253, 32, 172, 20, 250, 177, 141, 232, 236, 102, 71, 89, 186, 200, 93, 88, 45, 239, 231, 28, 56, 142, 106, 62, 145, 222, 119, 229, 165, 111, 79, 125, 124, 232, 225, 41, 173, 4, 198, 157, 88, 46, 227, 252, 217, 128, 53, 170, 2, 21, 252, 16, 34, 191, 65, 203, 142, 178, 239, 194, 42, 83, 229, 253, 139, 241, 72, 97, 130, 163, 121, 81, 110, 90, 68, 209, 133, 216, 76, 129, 178, 187, 79, 122, 252, 247, 95, 191, 156, 0, 3, 74, 0, 122, 80, 208, 210, 164, 82, 17, 167, 70, 88, 71, 15, 252, 173, 69, 2, 46, 34, 169, 6, 247, 156, 7, 103, 128, 35, 56, 187, 7, 175, 207, 42, 60, 119, 63, 26, 12, 126, 71, 78, 116, 55, 139, 18, 168, 96, 177, 33, 159, 24, 156, 10, 183, 176, 46, 98, 23, 74, 144, 192, 80, 5, 254, 85, 23, 184, 105, 21, 211, 225, 187, 17, 51, 49, 103, 27, 115, 220, 74, 237, 12, 206, 57, 177, 118, 95, 55, 85, 129, 10, 63, 46, 228, 19, 115, 27, 14, 77, 110, 144, 145, 156, 4, 98, 151, 170, 202, 10, 6, 142, 235, 244, 139, 67, 215, 36, 98, 127, 15, 228, 238, 56, 177, 246, 232, 225, 37, 7, 246, 237, 141, 230, 176, 88, 34, 234, 28, 226, 135, 64, 219, 147, 249, 48, 53, 124, 184, 190, 141, 245, 132, 128, 18, 4, 132, 56, 78, 3, 5, 124, 229, 145, 229, 147, 206, 117, 247, 74, 139, 200, 138, 43, 72, 137, 13, 207, 76, 22, 200, 197, 50, 232, 126, 18, 242, 28, 26, 146, 10, 124, 81, 62, 117, 121, 83, 251, 112, 209, 38, 128, 89, 168, 44, 39, 4, 144, 66, 159, 94, 227, 185, 37, 199, 193, 187, 50, 15, 190, 222, 136, 187, 4, 223, 110, 100, 158, 60, 233, 86, 221, 174, 38, 174, 132, 205, 232, 151, 191, 204, 197, 126, 12, 72, 89, 108, 85, 61, 181, 124, 137, 68, 173, 22, 39, 231, 161, 30, 108, 70, 163, 8, 70, 52, 9, 77, 163, 135, 102, 32, 7, 194, 68, 61, 220, 15, 41, 216, 245, 104, 85, 36, 232, 66, 6, 128, 6, 53, 11, 161, 0, 126, 126, 124, 63, 46, 149, 15, 239, 1, 77, 121, 111, 244, 133, 7, 214, 16, 155, 0, 14, 231, 48, 157, 58, 141, 53, 129, 103, 87, 57, 114, 65, 9, 222, 111, 37, 238, 4, 110, 192, 152, 251, 144, 11, 192, 98, 249, 85, 73, 241, 202, 16, 21, 188, 135, 92, 62, 188, 1, 189, 44, 42, 44, 109, 87, 11, 160, 7, 203, 43, 24, 154, 182, 207, 181, 109, 109, 240, 67, 240, 249, 115, 155, 231, 197, 248, 137, 111, 75, 175, 253, 18, 46, 151, 188, 16, 218, 111, 132, 98, 192, 159, 195, 97, 137, 164, 223, 250, 52, 166, 61, 130, 122, 120, 7, 185, 124, 9, 121, 95, 148, 13, 29, 72, 188, 48, 64, 10, 8, 3, 87, 134, 198, 172, 94, 237, 105, 35, 38, 102, 117, 160, 231, 194, 53, 137, 14, 24, 180, 107, 145, 129, 77, 0, 229, 203, 253, 1, 101, 183, 233, 155, 73, 116, 255, 192, 225, 181, 57, 69, 53, 10, 238, 200, 242, 229, 196, 211, 169, 98, 197, 239, 12, 249, 226, 79, 108, 130, 192, 45, 235, 174, 69, 174, 72, 72, 88, 110, 99, 197, 138, 229, 9, 9, 145, 152, 3, 6, 86, 46, 81, 1, 178, 7, 4, 2, 129, 92, 57, 96, 50, 85, 113, 246, 157, 59, 122, 34, 33, 210, 21, 78, 241, 75, 86, 133, 27, 188, 72, 45, 31, 53, 167, 180, 16, 203, 26, 80, 178, 208, 38, 160, 5, 110, 186, 242, 139, 60, 199, 12, 190, 179, 103, 126, 47, 77, 133, 97, 28, 127, 198, 233, 44, 221, 60, 169, 103, 146, 205, 11, 115, 196, 92, 178, 161, 103, 168, 115, 133, 25, 130, 165, 230, 194, 95, 23, 33, 253, 16, 10, 111, 118, 161, 150, 4, 34, 164, 23, 65, 81, 32, 94, 133, 222, 86, 130, 23, 221, 246, 31, 236, 40, 153, 118, 177, 139, 32, 14, 131, 24, 76, 118, 37, 75, 157, 12, 245, 166, 231, 153, 239, 122, 183, 154, 180, 214, 14, 122, 225, 231, 226, 236, 98, 135, 243, 121, 190, 103, 239, 251, 62, 239, 57, 187, 144, 44, 192, 227, 185, 244, 45, 182, 179, 185, 179, 54, 77, 193, 107, 100, 96, 200, 253, 219, 30, 30, 159, 252, 181, 81, 175, 0, 13, 81, 44, 0, 39, 65, 104, 23, 215, 128, 91, 172, 33, 228, 85, 65, 219, 143, 228, 32, 220, 88, 75, 36, 98, 117, 51, 13, 24, 124, 74, 128, 116, 166, 218, 18, 158, 84, 252, 51, 56, 55, 113, 102, 62, 235, 6, 223, 244, 126, 45, 205, 66, 17, 75, 216, 170, 30, 31, 33, 125, 126, 37, 52, 181, 197, 214, 191, 111, 199, 54, 122, 111, 60, 126, 56, 89, 99, 201, 118, 70, 239, 78, 93, 42, 254, 106, 242, 81, 113, 255, 53, 54, 204, 79, 203, 118, 90, 5, 68, 145, 214, 0, 214, 16, 12, 249, 85, 176, 126, 183, 255, 230, 245, 166, 170, 35, 3, 76, 78, 39, 46, 51, 127, 178, 57, 218, 181, 121, 3, 24, 158, 28, 216, 37, 182, 10, 5, 180, 112, 216, 81, 54, 220, 158, 239, 56, 248, 219, 155, 206, 170, 153, 205, 243, 164, 39, 255, 225, 115, 106, 125, 15, 64, 169, 55, 226, 8, 48, 68, 106, 8, 38, 218, 151, 234, 129, 220, 240, 149, 173, 140, 232, 39, 189, 36, 105, 195, 0, 208, 109, 15, 155, 2, 12, 214, 147, 58, 104, 131, 82, 120, 255, 38, 234, 41, 62, 243, 47, 75, 166, 208, 40, 32, 195, 65, 212, 166, 67, 15, 41, 75, 141, 130, 14, 126, 212, 179, 219, 191, 76, 72, 218, 208, 34, 53, 210, 209, 184, 35, 163, 0, 17, 119, 231, 33, 106, 8, 5, 247, 243, 248, 8, 253, 228, 218, 32, 32, 61, 67, 65, 135, 136, 152, 232, 64, 31, 216, 149, 68, 45, 132, 27, 148, 69, 40, 16, 22, 244, 211, 230, 144, 199, 63, 140, 26, 90, 2, 98, 48, 18, 143, 4, 127, 35, 18, 140, 199, 131, 245, 230, 17, 25, 254, 27, 3, 249, 63, 87, 172, 102, 196, 103, 237, 191, 186, 29, 16, 225, 85, 223, 124, 89, 54, 188, 94, 243, 160, 92, 8, 255, 23, 242, 163, 62, 211, 79, 99, 239, 41, 16, 37, 237, 165, 217, 144, 229, 82, 159, 79, 40, 128, 63, 154, 242, 51, 61, 39, 100, 6, 125, 33, 255, 88, 116, 181, 34, 51, 62, 71, 235, 144, 65, 47, 184, 127, 165, 118, 37, 109, 242, 5, 50, 48, 53, 130, 174, 112, 63, 143, 159, 193, 214, 56, 232, 138, 64, 254, 172, 241, 25, 90, 159, 5, 56, 250, 248, 217, 75, 106, 73, 146, 184, 151, 47, 253, 14, 61, 255, 56, 47, 121, 129, 126, 54, 250, 146, 126, 241, 207, 2, 180, 151, 160, 27, 83, 11, 145, 61, 109, 111, 47, 188, 123, 16, 65, 130, 217, 137, 87, 91, 116, 187, 5, 221, 11, 11, 99, 15, 24, 230, 163, 88, 26, 247, 233, 86, 128, 96, 41, 17, 114, 192, 0, 167, 156, 114, 82, 40, 54, 22, 25, 139, 225, 248, 40, 134, 99, 199, 230, 183, 253, 83, 33, 205, 19, 138, 123, 194, 5, 239, 172, 202, 128, 31, 230, 172, 110, 101, 150, 127, 231, 82, 20, 183, 210, 105, 3, 35, 48, 108, 3, 86, 183, 219, 201, 227, 118, 89, 39, 148, 46, 72, 3, 175, 243, 230, 222, 125, 101, 206, 8, 57, 227, 106, 81, 213, 114, 39, 92, 85, 213, 202, 59, 240, 92, 85, 213, 143, 220, 215, 169, 34, 23, 93, 80, 4, 12, 103, 57, 158, 252, 254, 215, 9, 182, 43, 234, 57, 117, 0, 210, 120, 212, 162, 18, 173, 126, 200, 185, 4, 127, 37, 169, 111, 211, 209, 101, 43, 167, 35, 191, 126, 171, 138, 84, 58, 121, 1, 111, 169, 32, 126, 194, 135, 86, 44, 96, 22, 56, 206, 22, 245, 172, 138, 92, 43, 202, 189, 128, 230, 159, 237, 85, 49, 143, 178, 64, 20, 220, 138, 93, 155, 77, 182, 34, 84, 132, 22, 45, 160, 32, 6, 162, 181, 52, 52, 54, 38, 70, 10, 66, 103, 235, 79, 176, 190, 156, 133, 182, 151, 124, 94, 167, 237, 253, 132, 247, 195, 190, 217, 83, 246, 97, 114, 133, 244, 76, 162, 102, 55, 207, 157, 121, 51, 111, 33, 36, 218, 206, 110, 150, 233, 126, 197, 119, 33, 0, 166, 195, 246, 55, 11, 104, 65, 120, 172, 132, 199, 124, 218, 220, 122, 121, 175, 137, 164, 174, 115, 147, 15, 24, 130, 12, 109, 158, 103, 43, 50, 144, 1, 203, 229, 7, 107, 15, 72, 74, 73, 243, 127, 44, 224, 11, 101, 129, 64, 69, 223, 16, 230, 242, 10, 24, 112, 84, 213, 245, 52, 100, 104, 151, 112, 108, 122, 193, 201, 225, 52, 199, 121, 153, 59, 111, 230, 147, 92, 248, 100, 74, 71, 168, 10, 148, 125, 113, 119, 45, 248, 118, 21, 150, 44, 89, 211, 57, 18, 131, 80, 237, 32, 160, 68, 255, 20, 150, 72, 35, 224, 11, 221, 18, 205, 211, 144, 76, 235, 24, 178, 37, 8, 90, 1, 176, 33, 69, 223, 237, 15, 66, 65, 60, 76, 129, 186, 64, 192, 129, 194, 152, 194, 131, 38, 205, 230, 41, 235, 199, 39, 92, 88, 57, 134, 141, 79, 72, 196, 25, 82, 117, 134, 176, 157, 5, 193, 148, 224, 189, 252, 153, 71, 134, 75, 42, 214, 228, 195, 139, 69, 36, 250, 87, 46, 152, 73, 2, 69, 135, 111, 155, 249, 70, 40, 54, 196, 244, 13, 129, 178, 123, 77, 192, 106, 208, 179, 51, 38, 105, 164, 108, 26, 210, 96, 76, 69, 127, 162, 117, 185, 39, 236, 185, 30, 75, 155, 121, 36, 20, 27, 162, 97, 153, 234, 43, 216, 44, 81, 179, 192, 92, 188, 47, 192, 78, 46, 109, 167, 13, 97, 226, 245, 149, 111, 7, 78, 210, 65, 66, 176, 193, 9, 88, 217, 132, 45, 13, 27, 178, 205, 196, 43, 62, 177, 233, 179, 75, 111, 32, 176, 2, 114, 123, 56, 218, 227, 255, 157, 108, 154, 246, 195, 148, 30, 134, 156, 253, 176, 35, 39, 233, 124, 219, 111, 186, 117, 213, 148, 19, 209, 88, 85, 131, 4, 36, 150, 100, 37, 14, 214, 129, 117, 95, 87, 135, 218, 123, 10, 136, 118, 40, 253, 97, 119, 215, 208, 99, 22, 231, 184, 35, 219, 47, 168, 40, 182, 54, 2, 53, 32, 2, 48, 27, 242, 111, 34, 37, 163, 231, 83, 126, 69, 132, 100, 76, 218, 216, 158, 47, 213, 179, 159, 123, 72, 122, 126, 114, 9, 120, 9, 205, 33, 154, 234, 215, 7, 177, 70, 59, 67, 248, 193, 140, 225, 159, 252, 118, 188, 123, 25, 65, 10, 213, 35, 209, 189, 80, 46, 115, 61, 229, 75, 112, 166, 95, 36, 28, 255, 3, 129, 18, 67, 80, 30, 227, 184, 69, 160, 199, 56, 47, 121, 156, 127, 176, 108, 208, 84, 29, 231, 73, 246, 108, 218, 46, 82, 44, 58, 1, 201, 165, 136, 227, 56, 63, 57, 215, 210, 165, 239, 111, 235, 38, 18, 131, 48, 81, 202, 155, 216, 31, 207, 19, 12, 79, 121, 234, 177, 171, 186, 109, 94, 112, 13, 128, 50, 135, 42, 202, 34, 37, 70, 140, 24, 49, 98, 196, 136, 17, 35, 70, 252, 137, 255, 156, 118, 125, 151, 11, 217, 82, 191, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} +var WailsLogoWhite = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 128, 0, 0, 0, 128, 8, 3, 0, 0, 0, 244, 224, 145, 249, 0, 0, 3, 0, 80, 76, 84, 69, 255, 255, 255, 255, 255, 255, 252, 252, 252, 26, 26, 26, 255, 255, 255, 250, 250, 250, 255, 254, 254, 228, 57, 57, 161, 22, 30, 225, 39, 40, 255, 254, 254, 230, 72, 72, 2, 2, 2, 14, 14, 14, 253, 247, 247, 173, 11, 16, 183, 32, 37, 227, 49, 49, 51, 51, 51, 252, 243, 243, 249, 249, 249, 160, 5, 12, 251, 224, 224, 253, 246, 246, 42, 42, 42, 185, 36, 41, 207, 32, 34, 254, 249, 249, 31, 31, 31, 215, 47, 49, 221, 28, 28, 228, 228, 228, 209, 42, 44, 243, 196, 197, 198, 19, 22, 204, 18, 20, 236, 236, 236, 252, 235, 236, 73, 73, 73, 248, 248, 248, 244, 244, 244, 60, 60, 60, 227, 53, 53, 202, 202, 202, 129, 129, 129, 239, 215, 217, 113, 113, 113, 213, 36, 37, 151, 2, 9, 227, 44, 44, 246, 186, 186, 230, 63, 63, 45, 45, 45, 197, 67, 71, 174, 174, 174, 66, 66, 66, 178, 18, 24, 108, 0, 0, 252, 239, 239, 228, 189, 191, 82, 82, 82, 232, 82, 82, 242, 189, 190, 193, 30, 34, 184, 23, 28, 198, 37, 41, 195, 49, 53, 192, 37, 40, 250, 220, 221, 94, 94, 94, 162, 23, 30, 221, 102, 104, 242, 163, 163, 122, 122, 122, 231, 114, 115, 219, 54, 55, 121, 0, 6, 220, 47, 48, 72, 72, 72, 5, 5, 5, 239, 207, 208, 216, 99, 101, 148, 17, 25, 37, 37, 37, 237, 122, 122, 235, 106, 107, 245, 245, 245, 117, 2, 13, 250, 230, 230, 181, 34, 39, 216, 216, 216, 22, 22, 22, 248, 229, 230, 222, 163, 165, 238, 152, 152, 223, 223, 223, 174, 54, 60, 213, 213, 213, 225, 131, 132, 236, 181, 182, 216, 139, 141, 245, 235, 236, 172, 27, 33, 130, 9, 20, 163, 7, 14, 152, 152, 152, 254, 253, 253, 110, 110, 110, 248, 204, 204, 184, 184, 184, 164, 164, 164, 103, 103, 103, 225, 187, 189, 209, 65, 68, 239, 133, 133, 235, 167, 168, 235, 200, 201, 185, 44, 49, 51, 51, 51, 221, 151, 153, 221, 37, 37, 244, 181, 182, 208, 52, 55, 142, 34, 44, 189, 58, 62, 249, 217, 217, 209, 104, 107, 155, 66, 74, 245, 223, 224, 158, 158, 158, 221, 107, 108, 232, 185, 186, 222, 139, 141, 176, 22, 27, 196, 196, 196, 207, 34, 36, 17, 17, 17, 221, 84, 85, 191, 191, 191, 119, 119, 119, 249, 210, 210, 239, 145, 145, 243, 175, 175, 194, 107, 112, 233, 93, 93, 201, 81, 85, 242, 228, 230, 199, 147, 151, 221, 120, 122, 178, 25, 30, 239, 204, 205, 229, 150, 151, 233, 142, 143, 216, 170, 173, 161, 41, 49, 136, 136, 136, 216, 178, 181, 211, 117, 121, 173, 99, 105, 37, 37, 37, 187, 29, 33, 145, 145, 145, 84, 84, 84, 101, 101, 101, 212, 96, 99, 140, 140, 140, 223, 69, 70, 151, 27, 36, 215, 85, 87, 169, 57, 63, 89, 89, 89, 204, 114, 119, 187, 96, 101, 203, 26, 28, 172, 78, 84, 127, 15, 26, 190, 127, 132, 200, 83, 86, 200, 54, 57, 183, 122, 129, 192, 192, 192, 238, 223, 224, 255, 255, 255, 255, 254, 254, 254, 253, 253, 226, 49, 49, 228, 50, 50, 218, 46, 47, 216, 45, 46, 222, 47, 48, 220, 47, 47, 254, 250, 250, 212, 43, 45, 224, 48, 49, 214, 44, 46, 254, 248, 248, 208, 42, 44, 255, 252, 252, 251, 250, 250, 228, 52, 52, 204, 40, 43, 197, 37, 40, 200, 39, 42, 186, 32, 37, 194, 36, 40, 33, 33, 33, 191, 35, 38, 189, 34, 38, 180, 30, 35, 210, 42, 44, 30, 30, 30, 129, 10, 20, 173, 27, 33, 23, 23, 23, 207, 40, 43, 225, 31, 31, 228, 44, 44, 10, 10, 10, 202, 39, 42, 183, 31, 36, 170, 26, 32, 165, 24, 30, 134, 12, 22, 124, 7, 18, 236, 116, 116, 228, 48, 47, 19, 19, 19, 223, 50, 50, 176, 28, 34, 37, 37, 37, 138, 13, 23, 178, 29, 35, 204, 45, 47, 194, 40, 44, 227, 38, 38, 143, 14, 23, 150, 17, 25, 168, 25, 31, 156, 20, 28, 208, 47, 49, 229, 56, 56, 175, 33, 39, 212, 47, 49, 166, 31, 38, 189, 39, 43, 202, 28, 30, 189, 17, 21, 181, 12, 17, 196, 25, 29, 200, 45, 48, 199, 32, 35, 189, 24, 28, 132, 0, 6, 224, 55, 55, 213, 27, 29, 147, 22, 30, 49, 94, 55, 29, 0, 0, 0, 182, 116, 82, 78, 83, 251, 225, 254, 254, 248, 245, 246, 254, 254, 253, 250, 244, 254, 254, 248, 253, 254, 249, 245, 246, 254, 254, 248, 254, 254, 254, 254, 248, 248, 254, 253, 246, 246, 244, 254, 253, 245, 243, 237, 252, 246, 244, 244, 241, 240, 236, 233, 254, 254, 249, 247, 246, 245, 242, 238, 238, 254, 253, 250, 249, 245, 244, 240, 254, 254, 248, 247, 245, 243, 243, 243, 242, 240, 235, 254, 254, 254, 250, 249, 249, 248, 248, 247, 246, 242, 241, 252, 252, 248, 248, 246, 246, 244, 244, 244, 243, 243, 240, 240, 238, 237, 249, 247, 247, 246, 244, 243, 243, 242, 242, 242, 242, 240, 240, 239, 238, 232, 232, 230, 227, 250, 246, 245, 243, 241, 240, 240, 240, 239, 235, 235, 253, 251, 249, 248, 247, 246, 245, 244, 244, 243, 242, 241, 241, 241, 241, 240, 240, 240, 239, 238, 238, 234, 233, 246, 244, 243, 242, 237, 236, 236, 234, 231, 230, 223, 247, 243, 241, 240, 229, 225, 224, 249, 246, 242, 241, 233, 233, 222, 246, 228, 205, 116, 60, 49, 81, 0, 0, 16, 106, 73, 68, 65, 84, 120, 218, 236, 152, 107, 72, 83, 97, 24, 199, 79, 99, 45, 87, 224, 22, 203, 196, 133, 177, 21, 131, 16, 196, 76, 6, 102, 152, 87, 76, 42, 161, 164, 48, 161, 68, 76, 45, 162, 15, 154, 69, 217, 69, 165, 18, 9, 42, 75, 211, 50, 42, 250, 16, 93, 232, 94, 116, 222, 179, 153, 206, 53, 215, 230, 20, 83, 54, 84, 212, 37, 50, 209, 121, 75, 179, 214, 197, 174, 207, 251, 158, 169, 179, 102, 185, 210, 250, 80, 255, 47, 162, 135, 115, 254, 191, 247, 121, 159, 247, 121, 158, 87, 106, 6, 77, 209, 127, 83, 255, 1, 254, 3, 252, 7, 248, 15, 240, 207, 0, 32, 231, 127, 254, 99, 0, 218, 191, 11, 128, 84, 19, 133, 224, 207, 0, 168, 24, 90, 20, 233, 252, 209, 159, 0, 64, 26, 154, 146, 110, 136, 164, 145, 211, 24, 76, 63, 0, 44, 223, 125, 151, 79, 230, 4, 123, 48, 253, 0, 120, 249, 193, 11, 19, 16, 173, 117, 250, 120, 186, 1, 24, 154, 150, 109, 240, 111, 92, 205, 5, 16, 167, 154, 102, 0, 45, 77, 101, 6, 244, 244, 4, 203, 38, 244, 159, 70, 0, 146, 116, 155, 54, 242, 155, 218, 125, 164, 19, 251, 79, 31, 0, 98, 16, 205, 141, 246, 105, 212, 41, 252, 19, 40, 154, 113, 164, 114, 212, 180, 1, 32, 176, 140, 60, 181, 176, 125, 80, 201, 223, 136, 19, 128, 97, 24, 45, 136, 249, 214, 127, 154, 0, 16, 164, 252, 130, 132, 185, 61, 131, 58, 5, 127, 142, 140, 246, 164, 39, 210, 20, 2, 32, 134, 195, 209, 170, 158, 170, 176, 96, 157, 104, 103, 112, 163, 114, 150, 66, 217, 30, 112, 28, 63, 164, 104, 138, 75, 115, 100, 243, 35, 139, 139, 101, 104, 170, 0, 16, 195, 112, 84, 156, 167, 79, 53, 154, 121, 26, 45, 195, 161, 29, 21, 116, 194, 31, 47, 95, 169, 208, 37, 165, 236, 78, 59, 178, 101, 239, 209, 139, 247, 79, 203, 31, 156, 190, 184, 119, 119, 16, 188, 249, 219, 0, 104, 222, 188, 121, 42, 237, 120, 79, 88, 34, 119, 166, 40, 197, 215, 119, 93, 90, 73, 70, 240, 66, 88, 62, 72, 41, 73, 140, 47, 202, 13, 20, 8, 4, 111, 189, 228, 23, 143, 108, 34, 86, 232, 183, 35, 128, 236, 249, 204, 229, 209, 59, 82, 82, 10, 118, 175, 88, 81, 82, 88, 120, 239, 210, 173, 59, 55, 18, 207, 95, 139, 253, 18, 117, 121, 31, 89, 190, 82, 169, 91, 19, 223, 42, 8, 20, 228, 134, 159, 78, 150, 202, 184, 216, 71, 245, 148, 161, 127, 123, 11, 16, 45, 42, 185, 80, 82, 248, 240, 225, 165, 236, 236, 236, 3, 7, 14, 220, 221, 102, 108, 91, 251, 145, 213, 210, 222, 222, 162, 152, 193, 193, 79, 74, 236, 63, 168, 14, 244, 16, 196, 203, 47, 110, 205, 225, 17, 115, 79, 13, 174, 199, 83, 1, 144, 146, 189, 60, 110, 57, 209, 90, 144, 209, 104, 236, 236, 236, 236, 253, 66, 20, 181, 127, 214, 160, 197, 162, 192, 210, 241, 35, 18, 47, 110, 89, 231, 78, 190, 175, 113, 52, 255, 253, 45, 240, 189, 187, 188, 13, 100, 4, 61, 39, 234, 196, 8, 189, 189, 177, 151, 99, 148, 138, 50, 53, 184, 67, 252, 155, 54, 250, 138, 200, 183, 85, 26, 21, 222, 181, 169, 4, 160, 211, 226, 226, 218, 226, 218, 28, 32, 176, 127, 103, 148, 68, 167, 44, 179, 168, 73, 250, 233, 218, 131, 69, 184, 29, 145, 83, 9, 154, 74, 0, 32, 64, 133, 16, 130, 241, 0, 157, 120, 249, 101, 101, 106, 53, 1, 208, 41, 125, 118, 210, 90, 21, 155, 114, 83, 14, 0, 181, 157, 186, 180, 220, 56, 206, 255, 121, 148, 68, 161, 46, 47, 83, 91, 0, 0, 251, 251, 71, 147, 22, 224, 50, 0, 114, 34, 167, 49, 88, 146, 77, 8, 70, 178, 0, 150, 175, 46, 47, 135, 0, 128, 192, 95, 199, 223, 192, 163, 85, 180, 203, 0, 32, 39, 52, 204, 119, 66, 140, 22, 39, 162, 145, 4, 224, 227, 71, 227, 54, 88, 126, 121, 41, 248, 19, 2, 124, 0, 32, 1, 52, 244, 47, 0, 120, 6, 205, 230, 241, 240, 169, 69, 20, 232, 199, 111, 94, 136, 91, 219, 134, 79, 225, 129, 75, 55, 31, 236, 179, 60, 46, 45, 135, 8, 176, 254, 150, 166, 128, 157, 224, 239, 50, 0, 196, 122, 93, 226, 249, 27, 55, 110, 220, 202, 191, 149, 159, 127, 243, 102, 242, 22, 80, 73, 73, 218, 58, 144, 175, 111, 78, 80, 80, 144, 72, 36, 218, 129, 48, 28, 151, 139, 104, 170, 48, 110, 237, 129, 236, 123, 190, 51, 87, 6, 243, 75, 31, 151, 150, 130, 255, 200, 22, 204, 141, 70, 180, 74, 133, 92, 4, 32, 4, 23, 250, 150, 190, 240, 88, 138, 229, 193, 106, 251, 118, 65, 96, 96, 110, 110, 81, 209, 229, 240, 240, 240, 131, 242, 7, 242, 245, 235, 215, 175, 58, 124, 248, 230, 209, 132, 132, 115, 249, 23, 224, 160, 7, 173, 90, 252, 217, 27, 252, 9, 0, 155, 132, 202, 176, 59, 23, 220, 113, 245, 97, 92, 4, 192, 105, 187, 34, 170, 239, 149, 219, 43, 86, 47, 177, 188, 132, 93, 117, 194, 150, 234, 103, 207, 66, 67, 63, 124, 248, 16, 242, 238, 13, 104, 17, 209, 226, 171, 50, 90, 116, 86, 178, 216, 219, 251, 201, 24, 0, 72, 41, 137, 253, 184, 52, 113, 139, 12, 1, 130, 214, 197, 28, 128, 228, 90, 225, 182, 244, 149, 27, 200, 108, 238, 239, 111, 109, 53, 153, 76, 29, 0, 0, 242, 122, 13, 234, 238, 238, 126, 79, 100, 181, 90, 7, 6, 108, 233, 91, 87, 45, 246, 91, 243, 228, 201, 99, 135, 8, 88, 148, 49, 81, 207, 221, 220, 60, 60, 14, 38, 231, 80, 128, 160, 114, 237, 20, 48, 140, 157, 192, 12, 0, 173, 24, 160, 163, 163, 163, 174, 174, 174, 165, 165, 186, 26, 162, 80, 91, 91, 83, 83, 83, 85, 101, 48, 52, 55, 55, 235, 27, 6, 26, 242, 196, 222, 67, 21, 4, 96, 36, 7, 45, 138, 185, 69, 198, 190, 190, 23, 175, 94, 6, 10, 34, 206, 172, 227, 98, 4, 151, 142, 33, 7, 19, 120, 188, 34, 0, 253, 44, 64, 215, 40, 64, 45, 6, 168, 98, 1, 244, 250, 134, 202, 202, 138, 161, 138, 138, 10, 240, 119, 216, 2, 101, 196, 182, 94, 0, 48, 183, 190, 244, 250, 240, 38, 108, 213, 202, 29, 24, 193, 5, 0, 216, 5, 66, 224, 16, 129, 46, 236, 223, 50, 22, 0, 2, 160, 39, 0, 149, 245, 224, 207, 2, 148, 179, 0, 10, 201, 182, 231, 189, 189, 47, 220, 204, 173, 29, 194, 215, 221, 195, 182, 188, 99, 25, 34, 220, 143, 92, 40, 68, 90, 198, 78, 96, 79, 1, 66, 64, 0, 48, 129, 115, 0, 54, 0, 24, 32, 38, 182, 173, 179, 183, 15, 3, 116, 9, 159, 213, 188, 183, 214, 251, 249, 93, 77, 144, 81, 208, 23, 38, 95, 9, 25, 66, 240, 210, 100, 122, 201, 74, 232, 5, 122, 141, 213, 77, 100, 0, 53, 55, 59, 5, 80, 207, 77, 141, 123, 206, 2, 152, 186, 90, 158, 213, 24, 244, 67, 222, 165, 124, 126, 192, 174, 67, 92, 8, 3, 154, 36, 0, 33, 136, 218, 78, 36, 24, 209, 91, 44, 56, 129, 239, 64, 19, 71, 64, 17, 17, 103, 100, 1, 250, 77, 117, 213, 181, 85, 134, 134, 250, 39, 229, 22, 69, 83, 207, 220, 141, 82, 30, 4, 23, 77, 10, 128, 212, 131, 130, 221, 32, 24, 247, 142, 128, 246, 130, 146, 147, 147, 143, 130, 146, 174, 128, 222, 15, 235, 157, 0, 0, 1, 36, 192, 120, 128, 230, 202, 138, 199, 229, 106, 139, 78, 217, 222, 227, 127, 106, 25, 32, 32, 39, 0, 92, 196, 124, 175, 137, 10, 8, 226, 114, 41, 74, 26, 102, 179, 218, 183, 160, 194, 17, 64, 29, 147, 186, 173, 141, 5, 128, 28, 36, 0, 13, 0, 80, 6, 243, 9, 168, 169, 103, 78, 180, 200, 133, 171, 25, 135, 5, 193, 34, 63, 237, 66, 248, 214, 39, 53, 216, 172, 250, 134, 6, 54, 0, 99, 91, 96, 137, 127, 225, 182, 205, 8, 254, 118, 128, 238, 225, 97, 177, 216, 111, 223, 62, 190, 93, 141, 11, 125, 118, 69, 82, 223, 220, 11, 10, 60, 11, 82, 82, 82, 68, 238, 11, 176, 220, 71, 181, 224, 7, 227, 4, 98, 104, 105, 179, 109, 128, 0, 216, 203, 0, 27, 128, 48, 55, 115, 127, 20, 156, 194, 62, 114, 10, 91, 34, 178, 182, 74, 151, 101, 102, 102, 46, 27, 83, 102, 244, 74, 247, 241, 31, 166, 30, 165, 221, 187, 4, 195, 245, 181, 107, 215, 206, 159, 79, 76, 76, 60, 136, 37, 151, 167, 167, 159, 165, 104, 213, 143, 8, 244, 64, 48, 14, 160, 84, 45, 49, 187, 65, 237, 138, 101, 1, 76, 112, 10, 195, 214, 75, 121, 78, 222, 254, 46, 7, 230, 21, 164, 21, 230, 223, 57, 31, 75, 90, 160, 32, 48, 180, 58, 36, 100, 120, 248, 246, 185, 159, 16, 88, 197, 64, 48, 6, 80, 90, 22, 147, 234, 134, 43, 87, 106, 108, 39, 201, 193, 174, 150, 218, 170, 69, 139, 210, 79, 68, 34, 90, 235, 233, 169, 81, 105, 88, 169, 38, 200, 1, 138, 203, 43, 72, 219, 155, 127, 58, 49, 62, 55, 48, 244, 67, 173, 193, 58, 144, 119, 150, 11, 4, 104, 194, 255, 62, 172, 204, 19, 15, 56, 22, 194, 152, 8, 226, 223, 209, 149, 26, 59, 122, 8, 172, 3, 126, 159, 125, 72, 13, 112, 94, 135, 88, 128, 217, 156, 167, 64, 200, 254, 198, 219, 92, 188, 37, 41, 75, 30, 30, 178, 104, 145, 205, 6, 49, 208, 210, 200, 249, 85, 216, 83, 67, 101, 220, 246, 27, 114, 0, 8, 51, 247, 219, 91, 103, 234, 216, 41, 108, 24, 90, 195, 95, 232, 179, 1, 239, 132, 6, 91, 252, 160, 14, 48, 42, 205, 8, 5, 87, 20, 185, 242, 236, 153, 235, 251, 243, 146, 120, 227, 19, 6, 105, 73, 32, 237, 195, 54, 181, 85, 226, 55, 52, 2, 80, 46, 73, 5, 127, 82, 180, 171, 189, 162, 70, 78, 161, 30, 202, 128, 229, 83, 123, 163, 255, 169, 147, 164, 33, 160, 159, 14, 36, 28, 248, 60, 34, 127, 167, 22, 108, 222, 153, 177, 153, 0, 32, 196, 1, 186, 177, 203, 21, 76, 100, 238, 178, 67, 210, 147, 209, 233, 226, 17, 128, 114, 239, 120, 51, 246, 39, 61, 163, 214, 43, 202, 108, 234, 128, 66, 12, 0, 79, 74, 203, 212, 186, 65, 101, 15, 63, 56, 90, 6, 201, 160, 153, 76, 29, 128, 117, 218, 41, 224, 5, 208, 104, 9, 231, 82, 188, 160, 156, 226, 173, 103, 147, 14, 175, 79, 223, 47, 177, 222, 22, 215, 215, 215, 219, 203, 64, 76, 132, 25, 122, 22, 241, 135, 166, 93, 19, 111, 110, 37, 157, 128, 5, 80, 40, 8, 2, 52, 4, 10, 143, 105, 147, 43, 68, 48, 120, 143, 76, 149, 20, 151, 18, 109, 46, 222, 186, 55, 41, 235, 180, 60, 60, 60, 44, 36, 4, 186, 129, 109, 88, 172, 175, 108, 0, 0, 123, 33, 12, 107, 53, 129, 125, 157, 144, 244, 108, 232, 151, 241, 173, 117, 66, 12, 80, 207, 2, 0, 130, 14, 42, 161, 207, 134, 157, 60, 140, 48, 249, 155, 81, 80, 113, 218, 150, 228, 172, 251, 242, 240, 8, 97, 40, 59, 18, 134, 132, 64, 39, 50, 232, 173, 32, 210, 9, 8, 64, 169, 36, 213, 84, 55, 186, 124, 240, 111, 142, 40, 234, 170, 126, 86, 133, 1, 236, 133, 152, 69, 128, 134, 176, 18, 16, 84, 204, 164, 0, 16, 162, 55, 201, 61, 114, 5, 48, 21, 135, 134, 182, 144, 165, 213, 116, 119, 227, 97, 128, 52, 67, 82, 8, 89, 127, 239, 120, 19, 59, 52, 217, 231, 5, 67, 243, 254, 43, 123, 66, 187, 171, 12, 99, 157, 128, 141, 130, 174, 157, 15, 201, 0, 249, 200, 160, 73, 0, 176, 4, 2, 47, 50, 146, 146, 105, 196, 97, 28, 113, 4, 120, 28, 65, 252, 171, 89, 127, 242, 88, 156, 148, 28, 24, 98, 96, 123, 161, 162, 125, 84, 10, 128, 105, 106, 12, 56, 177, 9, 193, 199, 127, 10, 64, 10, 94, 206, 30, 32, 104, 17, 58, 25, 200, 42, 65, 245, 245, 126, 160, 48, 240, 23, 218, 67, 196, 62, 181, 138, 143, 229, 100, 189, 125, 175, 39, 0, 159, 2, 102, 205, 29, 39, 255, 125, 208, 147, 224, 235, 63, 5, 32, 131, 129, 47, 16, 8, 191, 153, 8, 155, 173, 184, 23, 138, 65, 146, 171, 171, 18, 164, 201, 130, 106, 97, 245, 104, 248, 73, 159, 174, 151, 72, 55, 135, 189, 179, 146, 67, 16, 112, 238, 80, 208, 166, 249, 142, 218, 52, 255, 248, 252, 73, 94, 207, 17, 38, 120, 235, 37, 28, 157, 201, 217, 12, 24, 182, 217, 108, 121, 251, 143, 157, 201, 56, 180, 0, 222, 230, 102, 189, 245, 114, 88, 62, 222, 154, 33, 191, 36, 110, 198, 155, 134, 10, 0, 176, 180, 67, 25, 114, 119, 178, 191, 147, 2, 160, 145, 138, 16, 124, 37, 215, 108, 66, 162, 138, 194, 48, 124, 208, 169, 193, 188, 14, 168, 145, 162, 205, 24, 106, 150, 63, 76, 136, 98, 17, 52, 149, 25, 53, 9, 149, 164, 17, 41, 73, 181, 236, 7, 202, 141, 185, 136, 114, 165, 166, 82, 32, 228, 54, 132, 112, 219, 230, 156, 171, 134, 83, 212, 232, 74, 161, 64, 102, 102, 151, 138, 144, 110, 148, 161, 12, 21, 165, 247, 124, 115, 244, 202, 205, 159, 153, 46, 67, 139, 222, 129, 81, 231, 220, 123, 223, 231, 251, 238, 249, 206, 185, 231, 56, 0, 128, 195, 44, 250, 224, 50, 138, 112, 249, 208, 163, 11, 189, 158, 6, 27, 139, 12, 21, 220, 241, 238, 215, 172, 17, 190, 124, 74, 113, 30, 107, 119, 176, 154, 220, 48, 141, 67, 131, 115, 174, 178, 46, 26, 3, 50, 124, 126, 191, 143, 228, 143, 42, 3, 6, 193, 15, 60, 148, 206, 142, 159, 94, 89, 92, 57, 219, 210, 223, 211, 212, 153, 206, 4, 109, 254, 80, 77, 203, 190, 186, 184, 100, 132, 143, 142, 233, 124, 95, 125, 159, 31, 104, 201, 114, 14, 201, 34, 88, 181, 79, 6, 48, 6, 48, 154, 147, 98, 223, 33, 33, 130, 241, 51, 139, 139, 43, 231, 30, 61, 125, 237, 105, 160, 61, 55, 161, 166, 4, 149, 205, 171, 149, 185, 75, 27, 225, 203, 145, 105, 184, 185, 139, 241, 187, 89, 67, 168, 66, 89, 130, 11, 64, 40, 173, 195, 195, 72, 100, 52, 22, 49, 1, 16, 193, 137, 202, 87, 61, 77, 87, 211, 109, 212, 51, 97, 46, 76, 61, 197, 83, 185, 252, 93, 86, 198, 23, 229, 63, 156, 244, 50, 155, 179, 59, 107, 211, 0, 24, 148, 12, 163, 246, 193, 128, 156, 16, 112, 122, 134, 30, 83, 6, 136, 224, 217, 93, 10, 28, 1, 40, 115, 243, 1, 142, 254, 220, 121, 233, 79, 3, 19, 252, 167, 63, 22, 117, 98, 36, 109, 119, 173, 2, 64, 150, 191, 154, 16, 146, 187, 170, 112, 157, 12, 127, 76, 0, 176, 141, 216, 32, 235, 219, 236, 206, 59, 106, 126, 126, 54, 210, 143, 21, 194, 244, 116, 210, 69, 52, 117, 54, 7, 71, 7, 200, 31, 28, 64, 24, 197, 156, 84, 123, 223, 70, 235, 246, 24, 0, 224, 237, 135, 185, 216, 182, 84, 51, 106, 114, 85, 239, 131, 191, 90, 37, 7, 235, 108, 92, 176, 91, 174, 5, 114, 87, 227, 49, 16, 228, 34, 197, 35, 231, 36, 159, 1, 96, 77, 228, 63, 63, 175, 194, 31, 146, 246, 180, 89, 89, 86, 133, 204, 101, 215, 5, 236, 240, 222, 16, 77, 8, 115, 165, 167, 186, 247, 32, 42, 159, 2, 176, 238, 159, 55, 79, 19, 51, 236, 35, 225, 211, 86, 105, 41, 54, 138, 4, 175, 42, 154, 179, 195, 85, 105, 64, 253, 72, 74, 74, 190, 117, 152, 234, 199, 50, 128, 224, 217, 23, 164, 191, 41, 124, 244, 252, 96, 29, 3, 29, 235, 118, 5, 39, 131, 127, 106, 46, 144, 90, 219, 201, 184, 176, 10, 160, 147, 191, 42, 62, 35, 124, 249, 154, 44, 187, 33, 151, 185, 159, 30, 23, 157, 74, 222, 82, 69, 111, 123, 15, 91, 204, 128, 240, 147, 127, 228, 169, 128, 226, 143, 132, 79, 0, 131, 165, 221, 224, 67, 146, 247, 238, 219, 90, 54, 223, 73, 7, 1, 88, 244, 15, 27, 189, 143, 194, 7, 0, 4, 2, 170, 3, 188, 118, 144, 197, 91, 224, 35, 127, 115, 239, 83, 37, 135, 123, 128, 127, 217, 233, 240, 0, 4, 137, 171, 119, 37, 106, 177, 146, 1, 129, 241, 175, 53, 47, 28, 254, 16, 89, 155, 170, 222, 39, 147, 15, 17, 72, 234, 19, 48, 238, 40, 75, 0, 228, 239, 12, 111, 238, 125, 235, 195, 46, 49, 32, 5, 181, 116, 237, 56, 1, 224, 254, 183, 170, 77, 82, 216, 147, 191, 52, 133, 12, 128, 228, 116, 28, 22, 31, 0, 21, 191, 211, 136, 31, 0, 102, 77, 166, 122, 184, 47, 62, 0, 66, 240, 189, 61, 89, 213, 213, 206, 106, 104, 21, 178, 111, 169, 209, 199, 76, 196, 5, 0, 254, 13, 253, 121, 107, 82, 46, 82, 96, 107, 185, 202, 210, 185, 47, 46, 0, 88, 26, 183, 95, 90, 215, 183, 237, 85, 123, 32, 62, 0, 144, 30, 53, 107, 188, 170, 224, 95, 3, 8, 93, 236, 46, 93, 236, 226, 255, 31, 125, 157, 47, 118, 0, 161, 67, 72, 32, 189, 235, 82, 220, 144, 78, 141, 230, 131, 77, 237, 166, 203, 141, 140, 140, 232, 127, 155, 1, 17, 221, 199, 98, 251, 63, 213, 87, 60, 18, 244, 24, 0, 24, 196, 133, 216, 120, 23, 194, 184, 50, 163, 70, 211, 7, 155, 28, 229, 241, 155, 9, 224, 203, 246, 28, 61, 194, 240, 91, 148, 0, 130, 231, 123, 221, 110, 239, 77, 222, 225, 118, 151, 228, 240, 138, 62, 183, 251, 161, 106, 2, 72, 35, 154, 220, 111, 174, 243, 17, 174, 148, 95, 114, 207, 139, 131, 117, 101, 153, 80, 209, 231, 117, 87, 192, 205, 240, 47, 190, 87, 112, 185, 224, 197, 3, 236, 251, 69, 7, 160, 243, 198, 122, 45, 84, 95, 44, 206, 107, 41, 245, 183, 217, 113, 45, 116, 165, 68, 36, 40, 0, 81, 162, 133, 82, 180, 107, 55, 249, 117, 5, 203, 111, 183, 105, 41, 109, 29, 17, 32, 161, 243, 163, 5, 218, 132, 230, 101, 60, 97, 253, 98, 236, 121, 189, 150, 50, 53, 17, 42, 207, 225, 251, 163, 205, 64, 78, 90, 226, 88, 90, 78, 126, 121, 98, 102, 121, 99, 254, 181, 196, 153, 52, 121, 174, 74, 78, 225, 68, 230, 204, 88, 90, 49, 63, 178, 14, 208, 145, 56, 51, 86, 168, 14, 16, 56, 183, 112, 42, 115, 172, 66, 168, 104, 1, 94, 124, 48, 148, 57, 51, 49, 245, 245, 50, 178, 22, 21, 0, 162, 248, 77, 125, 249, 242, 168, 15, 131, 113, 124, 161, 12, 113, 89, 205, 114, 98, 98, 9, 134, 240, 39, 16, 212, 178, 153, 217, 137, 51, 32, 72, 78, 128, 32, 72, 32, 128, 195, 99, 200, 229, 120, 9, 8, 2, 23, 12, 250, 210, 238, 21, 224, 106, 154, 84, 204, 54, 217, 251, 248, 61, 219, 122, 27, 151, 51, 76, 254, 38, 104, 186, 246, 233, 243, 201, 247, 249, 67, 87, 63, 73, 228, 140, 247, 67, 134, 78, 211, 79, 44, 80, 160, 145, 80, 45, 173, 62, 16, 227, 194, 218, 231, 0, 116, 55, 140, 69, 179, 157, 3, 116, 71, 146, 217, 123, 18, 230, 2, 244, 34, 198, 184, 231, 79, 252, 26, 169, 60, 27, 130, 198, 86, 34, 115, 118, 53, 44, 225, 204, 142, 17, 183, 215, 138, 61, 164, 218, 38, 98, 156, 33, 107, 149, 75, 242, 242, 102, 88, 114, 83, 37, 119, 197, 179, 250, 224, 220, 156, 22, 17, 104, 129, 5, 242, 107, 90, 87, 169, 242, 84, 8, 222, 7, 17, 218, 126, 251, 0, 96, 118, 131, 136, 155, 245, 60, 194, 83, 71, 178, 237, 9, 217, 59, 90, 161, 106, 111, 96, 196, 162, 7, 49, 87, 37, 49, 7, 65, 6, 109, 48, 80, 0, 247, 4, 160, 211, 78, 173, 73, 9, 0, 62, 112, 109, 113, 18, 230, 206, 148, 124, 169, 229, 193, 217, 97, 100, 245, 77, 105, 207, 193, 161, 74, 10, 15, 51, 190, 166, 176, 86, 8, 18, 84, 115, 128, 144, 172, 49, 138, 133, 175, 131, 1, 125, 30, 192, 7, 128, 94, 100, 94, 132, 217, 143, 133, 189, 2, 99, 165, 39, 44, 152, 46, 68, 25, 0, 212, 113, 227, 145, 228, 163, 21, 56, 204, 5, 177, 148, 32, 202, 166, 125, 49, 24, 147, 23, 149, 1, 207, 1, 128, 31, 102, 118, 112, 208, 195, 167, 45, 79, 244, 11, 243, 140, 150, 124, 51, 155, 160, 201, 27, 156, 167, 138, 224, 131, 67, 190, 100, 57, 73, 19, 65, 162, 24, 237, 30, 194, 93, 73, 94, 49, 14, 80, 180, 4, 192, 37, 98, 246, 132, 205, 231, 134, 109, 115, 209, 215, 8, 85, 41, 216, 195, 136, 185, 13, 112, 217, 207, 68, 166, 132, 174, 135, 76, 12, 26, 170, 8, 82, 65, 152, 13, 130, 132, 121, 75, 190, 67, 101, 226, 52, 13, 238, 79, 3, 44, 110, 80, 58, 144, 128, 115, 35, 102, 220, 254, 206, 107, 176, 1, 39, 217, 95, 95, 136, 139, 37, 249, 1, 56, 66, 14, 66, 204, 21, 0, 253, 156, 32, 238, 76, 139, 127, 3, 154, 120, 112, 49, 66, 206, 12, 246, 208, 39, 1, 180, 101, 34, 154, 191, 184, 2, 0, 26, 180, 242, 8, 184, 72, 48, 46, 49, 172, 5, 53, 5, 240, 186, 137, 98, 113, 208, 96, 86, 8, 210, 153, 181, 234, 144, 114, 89, 99, 60, 175, 180, 218, 90, 42, 128, 39, 21, 160, 164, 103, 48, 132, 174, 218, 97, 8, 66, 244, 126, 34, 64, 94, 54, 17, 231, 82, 34, 206, 147, 206, 211, 74, 61, 182, 7, 80, 101, 103, 104, 84, 10, 253, 104, 128, 102, 142, 227, 67, 222, 164, 204, 117, 115, 24, 220, 70, 192, 221, 209, 201, 189, 4, 0, 182, 184, 179, 39, 7, 108, 197, 163, 46, 4, 63, 19, 96, 106, 66, 27, 222, 28, 3, 22, 115, 255, 93, 181, 66, 232, 218, 177, 229, 230, 69, 240, 186, 132, 190, 27, 51, 233, 87, 73, 37, 107, 140, 142, 16, 152, 199, 92, 30, 75, 84, 1, 165, 7, 67, 70, 157, 5, 89, 194, 224, 169, 250, 9, 41, 189, 26, 24, 155, 11, 178, 55, 112, 100, 214, 19, 0, 0, 219, 15, 163, 200, 238, 22, 69, 208, 49, 4, 198, 50, 90, 106, 10, 192, 29, 226, 152, 35, 140, 47, 122, 41, 128, 221, 182, 217, 60, 83, 58, 223, 54, 189, 53, 248, 206, 34, 160, 191, 193, 244, 76, 72, 215, 107, 122, 183, 105, 18, 81, 10, 19, 120, 247, 213, 200, 195, 91, 191, 117, 154, 240, 120, 46, 81, 31, 231, 245, 131, 105, 179, 145, 55, 7, 255, 101, 110, 68, 53, 93, 215, 171, 217, 160, 50, 0, 126, 43, 122, 58, 13, 95, 146, 177, 242, 176, 115, 145, 219, 165, 123, 210, 109, 69, 66, 55, 198, 227, 113, 27, 138, 166, 252, 149, 44, 252, 53, 16, 250, 119, 181, 40, 182, 63, 123, 232, 239, 67, 232, 157, 148, 2, 80, 183, 72, 53, 168, 135, 86, 138, 183, 247, 199, 157, 149, 7, 191, 201, 218, 47, 171, 48, 155, 255, 103, 215, 242, 127, 195, 1, 70, 29, 48, 234, 128, 81, 7, 140, 58, 96, 212, 1, 163, 14, 24, 117, 0, 213, 1, 0, 87, 162, 212, 25, 139, 217, 220, 95, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130} diff --git a/v3/pkg/application/keys.go b/v3/pkg/application/keys.go new file mode 100644 index 000000000..d7d1beaf7 --- /dev/null +++ b/v3/pkg/application/keys.go @@ -0,0 +1,172 @@ +package application + +import ( + "fmt" + "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 +) + +var modifierMap = map[string]modifier{ + "cmdorctrl": CmdOrCtrlKey, + "cmd": CmdOrCtrlKey, + "command": CmdOrCtrlKey, + "ctrl": CmdOrCtrlKey, + "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 +} + +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 = 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/mainthread.go b/v3/pkg/application/mainthread.go new file mode 100644 index 000000000..f0ae2803b --- /dev/null +++ b/v3/pkg/application/mainthread.go @@ -0,0 +1,21 @@ +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") + } + } +} diff --git a/v3/pkg/application/mainthread_darwin.go b/v3/pkg/application/mainthread_darwin.go new file mode 100644 index 000000000..65ca398ad --- /dev/null +++ b/v3/pkg/application/mainthread_darwin.go @@ -0,0 +1,37 @@ +//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); + }); +} + +*/ +import "C" + +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/menu.go b/v3/pkg/application/menu.go new file mode 100644 index 000000000..80cf213dd --- /dev/null +++ b/v3/pkg/application/menu.go @@ -0,0 +1,109 @@ +package application + +type menuImpl interface { + update() +} + +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 := newMenuItemSeperator() + 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() +} + +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) + m.items = append(m.items, result) + return m +} + +func (m *Menu) processRadioGroups() { + var radioGroup []*MenuItem + for _, item := range m.items { + if item.itemType == submenu { + item.submenu.processRadioGroups() + continue + } + if item.itemType == radio { + radioGroup = append(radioGroup, item) + } else { + if len(radioGroup) > 0 { + for _, item := range radioGroup { + item.radioGroupMembers = radioGroup + } + radioGroup = nil + } + } + } + if len(radioGroup) > 0 { + for _, item := range radioGroup { + item.radioGroupMembers = radioGroup + } + } +} + +func (m *Menu) SetLabel(label string) { + m.label = label +} + +func (m *Menu) setContextData(data *ContextMenuData) { + for _, item := range m.items { + item.setContextData(data) + } +} + +func (a *App) NewMenu() *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_darwin.go b/v3/pkg/application/menu_darwin.go new file mode 100644 index 000000000..b14be232a --- /dev/null +++ b/v3/pkg/application/menu_darwin.go @@ -0,0 +1,105 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.10 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#include "menuitem.h" + +extern void setMenuItemChecked(void*, unsigned int, bool); + +// 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() { + 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 + C.addMenuItem(parent, menuItem.nsMenuItem) + case separator: + C.addMenuSeparator(parent) + } + + } +} diff --git a/v3/pkg/application/menuitem.go b/v3/pkg/application/menuitem.go new file mode 100644 index 000000000..da943156b --- /dev/null +++ b/v3/pkg/application/menuitem.go @@ -0,0 +1,284 @@ +package application + +import ( + "os" + "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] +} + +type menuItemImpl interface { + setTooltip(s string) + setLabel(s string) + setDisabled(disabled bool) + setChecked(checked bool) + setAccelerator(accelerator *accelerator) +} + +type MenuItem struct { + id uint + label string + tooltip string + disabled bool + checked bool + 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 newMenuItemSeperator() *MenuItem { + result := &MenuItem{ + 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 { + switch role { + case AppMenu: + return newAppMenu() + case EditMenu: + return newEditMenu() + case FileMenu: + return newFileMenu() + case ViewMenu: + return newViewMenu() + case ServicesMenu: + return newServicesMenu() + case SpeechMenu: + return newSpeechMenu() + case WindowMenu: + return newWindowMenu() + case HelpMenu: + return newHelpMenu() + case Hide: + return newHideMenuItem() + case HideOthers: + return newHideOthersMenuItem() + case UnHide: + return newUnhideMenuItem() + case Undo: + return newUndoMenuItem() + case Redo: + return newRedoMenuItem() + case Cut: + return newCutMenuItem() + case Copy: + return newCopyMenuItem() + case Paste: + return newPasteMenuItem() + case PasteAndMatchStyle: + return newPasteAndMatchStyleMenuItem() + case SelectAll: + return newSelectAllMenuItem() + case Delete: + return newDeleteMenuItem() + case Quit: + return newQuitMenuItem() + case Close: + return newCloseMenuItem() + case About: + return newAboutMenuItem() + case Reload: + return newReloadMenuItem() + case ForceReload: + return newForceReloadMenuItem() + case ToggleFullscreen: + return newToggleFullscreenMenuItem() + case ToggleDevTools: + return newToggleDevToolsMenuItem() + case ResetZoom: + return newZoomResetMenuItem() + case ZoomIn: + return newZoomInMenuItem() + case ZoomOut: + return newZoomOutMenuItem() + case Minimize: + return newMinimizeMenuItem() + case Zoom: + return newZoomMenuItem() + + default: + println("No support for role:", role) + os.Exit(1) + } + return nil +} + +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) + m.impl.setChecked(m.checked) + } + if m.itemType == radio { + for _, member := range m.radioGroupMembers { + member.checked = false + member.impl.setChecked(false) + } + m.checked = true + ctx.withChecked(true) + m.impl.setChecked(true) + } + if m.callback != nil { + go m.callback(ctx) + } +} + +func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem { + accelerator, err := parseAccelerator(shortcut) + if err != nil { + println("ERROR: invalid accelerator:", err.Error()) + return m + } + m.accelerator = accelerator + if m.impl != nil { + m.impl.setAccelerator(accelerator) + } + return m +} + +func (m *MenuItem) SetTooltip(s string) *MenuItem { + m.tooltip = s + if m.impl != nil { + m.impl.setTooltip(s) + } + 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) SetChecked(checked bool) *MenuItem { + m.checked = checked + if m.impl != nil { + m.impl.setChecked(m.checked) + } + return m +} + +func (m *MenuItem) Checked() bool { + return m.checked +} + +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) + } +} diff --git a/v3/pkg/application/menuitem.h b/v3/pkg/application/menuitem.h new file mode 100644 index 000000000..91fce726e --- /dev/null +++ b/v3/pkg/application/menuitem.h @@ -0,0 +1,18 @@ + +#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.m b/v3/pkg/application/menuitem.m new file mode 100644 index 000000000..f875d0de5 --- /dev/null +++ b/v3/pkg/application/menuitem.m @@ -0,0 +1,13 @@ +//go:build darwin + +#import + +#import "menuitem.h" + +@implementation MenuItem + +- (void) handleClick { + processMenuItemClick(self.menuItemID); +} + +@end diff --git a/v3/pkg/application/menuitem_darwin.go b/v3/pkg/application/menuitem_darwin.go new file mode 100644 index 000000000..daee2a521 --- /dev/null +++ b/v3/pkg/application/menuitem_darwin.go @@ -0,0 +1,620 @@ +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "Cocoa/Cocoa.h" +#include "menuitem.h" +#include "application.h" + +#define unicode(input) [NSString stringWithFormat:@"%C", input] + +// Create menu item +void* newMenuItem(unsigned int menuItemID, char *label, bool disabled, char* tooltip) { + MenuItem *menuItem = [MenuItem new]; + + // Label + menuItem.title = [NSString stringWithUTF8String:label]; + + if( disabled ) { + [menuItem setTarget:nil]; + } else { + [menuItem setTarget:menuItem]; + } + menuItem.menuItemID = menuItemID; + menuItem.action = @selector(handleClick); + 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) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.title = [NSString stringWithUTF8String: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 tooltip +void setMenuItemTooltip(void* nsMenuItem, char *tooltip) { + MenuItem *menuItem = (MenuItem *)nsMenuItem; + menuItem.toolTip = [NSString stringWithUTF8String:tooltip]; +} + +// Check menu item +void setMenuItemChecked(void* nsMenuItem, bool checked) { + 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(0x001c); + } + if( [key isEqualToString:@"right"] ) { + return unicode(0x001d); + } + if( [key isEqualToString:@"up"] ) { + return unicode(0x001e); + } + if( [key isEqualToString:@"down"] ) { + return unicode(0x001f); + } + 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]); +} + +// Call paste selector to paste text +static void paste(void) { + [NSApp sendAction:@selector(paste:) to:nil from:nil]; +} + +// Call copy selector to copy text +static void copy(void) { + [NSApp sendAction:@selector(copy:) to:nil from:nil]; +} + +// Call cut selector to cut text +static void cut(void) { + [NSApp sendAction:@selector(cut:) to:nil from:nil]; +} + +// Call selectAll selector to select all text +static void selectAll(void) { + [NSApp sendAction:@selector(selectAll:) to:nil from:nil]; +} + +// Call delete selector to delete text +static void delete(void) { + [NSApp sendAction:@selector(delete:) to:nil from:nil]; +} + +// Call undo selector to undo text +static void undo(void) { + [NSApp sendAction:@selector(undo:) to:nil from:nil]; +} + +// Call redo selector to redo text +static void redo(void) { + [NSApp sendAction:@selector(redo:) to:nil from:nil]; +} + +// Call startSpeaking selector to start speaking text +static void startSpeaking(void) { + [NSApp sendAction:@selector(startSpeaking:) to:nil from:nil]; +} + +// Call stopSpeaking selector to stop speaking text +static void stopSpeaking(void) { + [NSApp sendAction:@selector(stopSpeaking:) to:nil from:nil]; +} + +static void pasteAndMatchStyle(void) { + [NSApp sendAction:@selector(pasteAndMatchStyle:) to:nil from:nil]; +} + +static void hideApplication(void) { + [[NSApplication sharedApplication] hide:nil]; +} + +// hideOthers hides all other applications +static void hideOthers(void) { + [[NSApplication sharedApplication] hideOtherApplications:nil]; +} + +// showAll shows all hidden applications +static void showAll(void) { + [[NSApplication sharedApplication] unhideAllApplications:nil]; +} + +*/ +import "C" +import ( + "runtime" + "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) 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 newMenuItemImpl(item *MenuItem) *macosMenuItem { + result := &macosMenuItem{ + menuItem: item, + } + + switch item.itemType { + case text, checkbox, submenu, radio: + result.nsMenuItem = unsafe.Pointer(C.newMenuItem(C.uint(item.id), C.CString(item.label), C.bool(item.disabled), C.CString(item.tooltip))) + if item.itemType == checkbox || item.itemType == radio { + C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked)) + } + if item.accelerator != nil { + result.setAccelerator(item.accelerator) + } + default: + panic("WTF") + } + 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 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.ShowAboutDialog() + }) +} + +func newCloseMenuItem() *MenuItem { + return newMenuItem("Close"). + SetAccelerator("CmdOrCtrl+w"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Close() + } + }) +} + +func newReloadMenuItem() *MenuItem { + return newMenuItem("Reload"). + SetAccelerator("CmdOrCtrl+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Reload() + } + }) +} + +func newForceReloadMenuItem() *MenuItem { + return newMenuItem("Force Reload"). + SetAccelerator("CmdOrCtrl+Shift+r"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ForceReload() + } + }) +} + +func newToggleFullscreenMenuItem() *MenuItem { + result := newMenuItem("Toggle Full Screen"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ToggleFullscreen() + } + }) + if runtime.GOOS == "darwin" { + result.SetAccelerator("Ctrl+Command+F") + } else { + result.SetAccelerator("F11") + } + return result +} + +func newToggleDevToolsMenuItem() *MenuItem { + return newMenuItem("Toggle Developer Tools"). + SetAccelerator("Alt+Command+I"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ToggleDevTools() + } + }) +} + +func newZoomResetMenuItem() *MenuItem { + // reset zoom menu item + return newMenuItem("Actual Size"). + SetAccelerator("CmdOrCtrl+0"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomReset() + } + }) +} + +func newZoomInMenuItem() *MenuItem { + return newMenuItem("Zoom In"). + SetAccelerator("CmdOrCtrl+plus"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomIn() + } + }) +} + +func newZoomOutMenuItem() *MenuItem { + return newMenuItem("Zoom Out"). + SetAccelerator("CmdOrCtrl+-"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.ZoomOut() + } + }) +} + +func newMinimizeMenuItem() *MenuItem { + return newMenuItem("Minimize"). + SetAccelerator("CmdOrCtrl+M"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Minimize() + } + }) +} + +func newZoomMenuItem() *MenuItem { + return newMenuItem("Zoom"). + OnClick(func(ctx *Context) { + currentWindow := globalApplication.CurrentWindow() + if currentWindow != nil { + currentWindow.Zoom() + } + }) +} diff --git a/v3/pkg/application/messageprocessor.go b/v3/pkg/application/messageprocessor.go new file mode 100644 index 000000000..21fd80f5c --- /dev/null +++ b/v3/pkg/application/messageprocessor.go @@ -0,0 +1,141 @@ +package application + +import ( + "fmt" + "net/http" + "strconv" + "strings" + + jsoniter "github.com/json-iterator/go" +) + +// 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. + +type MessageProcessor struct { + pluginManager *PluginManager +} + +func NewMessageProcessor() *MessageProcessor { + return &MessageProcessor{} +} + +func (m *MessageProcessor) httpError(rw http.ResponseWriter, message string, args ...any) { + m.Error(message, args...) + rw.WriteHeader(http.StatusBadRequest) + rw.Write([]byte(fmt.Sprintf(message, args...))) +} + +func (m *MessageProcessor) getTargetWindow(r *http.Request) *WebviewWindow { + windowName := r.Header.Get(webViewRequestHeaderWindowName) + if windowName != "" { + return globalApplication.GetWindowByName(windowName) + } + windowID := r.Header.Get(webViewRequestHeaderWindowId) + if windowID == "" { + return nil + } + wID, err := strconv.ParseUint(windowID, 10, 64) + if err != nil { + m.Error("Window ID '%s' not parsable: %s", windowID, err) + return nil + } + targetWindow := globalApplication.getWindowForID(uint(wID)) + if targetWindow == nil { + m.Error("Window ID %d not found", wID) + return nil + } + return targetWindow +} + +func (m *MessageProcessor) HandleRuntimeCall(rw http.ResponseWriter, r *http.Request) { + // Read "method" from query string + method := r.URL.Query().Get("method") + if method == "" { + m.httpError(rw, "No method specified") + return + } + splitMethod := strings.Split(method, ".") + if len(splitMethod) != 2 { + m.httpError(rw, "Invalid method format") + return + } + // Get the object + object := splitMethod[0] + // Get the method + method = splitMethod[1] + + params := QueryParams(r.URL.Query()) + + targetWindow := m.getTargetWindow(r) + if targetWindow == nil { + m.Error("No valid window found") + return + } + + switch object { + case "window": + m.processWindowMethod(method, rw, r, targetWindow, params) + case "clipboard": + m.processClipboardMethod(method, rw, r, targetWindow, params) + case "dialog": + m.processDialogMethod(method, rw, r, targetWindow, params) + case "events": + m.processEventsMethod(method, rw, r, targetWindow, params) + case "application": + m.processApplicationMethod(method, rw, r, targetWindow, params) + case "log": + m.processLogMethod(method, rw, r, targetWindow, params) + case "contextmenu": + m.processContextMenuMethod(method, rw, r, targetWindow, params) + case "screens": + m.processScreensMethod(method, rw, r, targetWindow, params) + case "call": + m.processCallMethod(method, rw, r, targetWindow, params) + default: + m.httpError(rw, "Unknown runtime call: %s", object) + } + +} + +func (m *MessageProcessor) Error(message string, args ...any) { + fmt.Printf("[MessageProcessor] Error: "+message, args...) +} + +func (m *MessageProcessor) Info(message string, args ...any) { + fmt.Printf("[MessageProcessor] 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 = jsoniter.Marshal(data) + if err != nil { + m.Error("Unable to convert data to JSON. Please report this to the Wails team! Error: %s", err) + return + } + } + _, err = rw.Write(jsonPayload) + if err != nil { + m.Error("Unable to write json payload. Please report this to the Wails team! Error: %s", 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: %s", 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..4eaf9e9b1 --- /dev/null +++ b/v3/pkg/application/messageprocessor_application.go @@ -0,0 +1,23 @@ +package application + +import ( + "net/http" +) + +func (m *MessageProcessor) processApplicationMethod(method string, rw http.ResponseWriter, r *http.Request, window *WebviewWindow, params QueryParams) { + + switch method { + case "Quit": + globalApplication.Quit() + m.ok(rw) + case "Hide": + globalApplication.Hide() + m.ok(rw) + case "Show": + globalApplication.Show() + m.ok(rw) + default: + m.httpError(rw, "Unknown event method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go new file mode 100644 index 000000000..ddbddd37f --- /dev/null +++ b/v3/pkg/application/messageprocessor_call.go @@ -0,0 +1,65 @@ +package application + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +func (m *MessageProcessor) callErrorCallback(window *WebviewWindow, message string, callID *string, err error) { + errorMsg := fmt.Sprintf(message, err) + m.Error(errorMsg) + msg := "_wails.callErrorCallback('" + *callID + "', " + strconv.Quote(errorMsg) + ");" + window.ExecJS(msg) +} + +func (m *MessageProcessor) callCallback(window *WebviewWindow, callID *string, result string, isJSON bool) { + msg := fmt.Sprintf("_wails.callCallback('%s', %s, %v);", *callID, strconv.Quote(result), isJSON) + window.ExecJS(msg) +} + +func (m *MessageProcessor) processCallMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) { + args, err := params.Args() + if err != nil { + m.httpError(rw, "Unable to parse arguments: %s", err) + return + } + callID := args.String("call-id") + if callID == nil { + m.Error("call-id is required") + return + } + switch method { + case "Call": + var options CallOptions + err := params.ToStruct(&options) + if err != nil { + m.callErrorCallback(window, "Error parsing call options: %s", callID, err) + return + } + bindings := globalApplication.bindings.Get(&options) + if bindings == nil { + m.callErrorCallback(window, "Error getting binding for method: %s", callID, fmt.Errorf("'%s' not found", options.MethodName)) + return + } + go func() { + result, err := bindings.Call(options.Args) + if err != nil { + m.callErrorCallback(window, "Error calling method: %s", callID, err) + return + } + // convert result to json + jsonResult, err := json.Marshal(result) + if err != nil { + m.callErrorCallback(window, "Error converting result to json: %s", callID, err) + return + } + m.callCallback(window, callID, string(jsonResult), true) + }() + m.ok(rw) + default: + m.httpError(rw, "Unknown dialog method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_clipboard.go b/v3/pkg/application/messageprocessor_clipboard.go new file mode 100644 index 000000000..0600b9c3d --- /dev/null +++ b/v3/pkg/application/messageprocessor_clipboard.go @@ -0,0 +1,25 @@ +package application + +import ( + "net/http" +) + +func (m *MessageProcessor) processClipboardMethod(method string, rw http.ResponseWriter, _ *http.Request, _ *WebviewWindow, params QueryParams) { + + switch method { + case "SetText": + title := params.String("text") + if title == nil { + m.Error("SetText: text is required") + return + } + globalApplication.Clipboard().SetText(*title) + m.ok(rw) + case "Text": + text := globalApplication.Clipboard().Text() + m.text(rw, text) + default: + m.httpError(rw, "Unknown clipboard method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_contextmenu.go b/v3/pkg/application/messageprocessor_contextmenu.go new file mode 100644 index 000000000..d0fe57c98 --- /dev/null +++ b/v3/pkg/application/messageprocessor_contextmenu.go @@ -0,0 +1,30 @@ +package application + +import ( + "net/http" +) + +type ContextMenuData struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Data any `json:"data"` +} + +func (m *MessageProcessor) processContextMenuMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) { + + switch method { + case "OpenContextMenu": + var data ContextMenuData + err := params.ToStruct(&data) + if err != nil { + m.httpError(rw, "error parsing contextmenu message: %s", err.Error()) + return + } + window.openContextMenu(&data) + m.ok(rw) + default: + m.httpError(rw, "Unknown clipboard method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_dialog.go b/v3/pkg/application/messageprocessor_dialog.go new file mode 100644 index 000000000..84f519a43 --- /dev/null +++ b/v3/pkg/application/messageprocessor_dialog.go @@ -0,0 +1,128 @@ +package application + +import ( + "encoding/json" + "fmt" + "net/http" + "runtime" + "strconv" +) + +func (m *MessageProcessor) dialogErrorCallback(window *WebviewWindow, message string, dialogID *string, err error) { + errorMsg := fmt.Sprintf(message, err) + m.Error(errorMsg) + msg := "_wails.dialogErrorCallback('" + *dialogID + "', " + strconv.Quote(errorMsg) + ");" + window.ExecJS(msg) +} + +func (m *MessageProcessor) dialogCallback(window *WebviewWindow, dialogID *string, result string, isJSON bool) { + msg := fmt.Sprintf("_wails.dialogCallback('%s', %s, %v);", *dialogID, strconv.Quote(result), isJSON) + window.ExecJS(msg) +} + +func (m *MessageProcessor) processDialogMethod(method string, rw http.ResponseWriter, r *http.Request, window *WebviewWindow, params QueryParams) { + + args, err := params.Args() + if err != nil { + m.httpError(rw, "Unable to parse arguments: %s", err) + return + } + dialogID := args.String("dialog-id") + if dialogID == nil { + m.Error("dialog-id is required") + return + } + switch method { + case "Info", "Warning", "Error", "Question": + var options MessageDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.dialogErrorCallback(window, "Error parsing dialog options: %s", dialogID, 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 "Info": + dialog = globalApplication.InfoDialog() + case "Warning": + dialog = globalApplication.WarningDialog() + case "Error": + dialog = globalApplication.ErrorDialog() + case "Question": + dialog = globalApplication.QuestionDialog() + } + // TODO: Add support for attaching Message dialogs to windows + 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) + case "OpenFile": + var options OpenFileDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.httpError(rw, "Error parsing dialog options: %s", err.Error()) + return + } + dialog := globalApplication.OpenFileDialogWithOptions(&options) + + go func() { + if options.AllowsMultipleSelection { + files, err := dialog.PromptForMultipleSelection() + if err != nil { + m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err) + return + } else { + result, err := json.Marshal(files) + if err != nil { + m.dialogErrorCallback(window, "Error marshalling files: %s", dialogID, err) + return + } + m.dialogCallback(window, dialogID, string(result), true) + } + } else { + file, err := dialog.PromptForSingleSelection() + if err != nil { + m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err) + return + } + m.dialogCallback(window, dialogID, file, false) + } + }() + m.ok(rw) + case "SaveFile": + var options SaveFileDialogOptions + err := params.ToStruct(&options) + if err != nil { + m.httpError(rw, "Error parsing dialog options: %s", err.Error()) + return + } + dialog := globalApplication.SaveFileDialogWithOptions(&options) + + go func() { + file, err := dialog.PromptForSingleSelection() + if err != nil { + m.dialogErrorCallback(window, "Error getting selection: %s", dialogID, err) + return + } + m.dialogCallback(window, dialogID, file, false) + }() + m.ok(rw) + + default: + m.httpError(rw, "Unknown dialog method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_events.go b/v3/pkg/application/messageprocessor_events.go new file mode 100644 index 000000000..439452a36 --- /dev/null +++ b/v3/pkg/application/messageprocessor_events.go @@ -0,0 +1,28 @@ +package application + +import ( + "net/http" +) + +func (m *MessageProcessor) processEventsMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) { + + switch method { + case "Emit": + var event WailsEvent + err := params.ToStruct(&event) + if err != nil { + m.httpError(rw, "Error parsing event: %s", err) + return + } + if event.Name == "" { + m.httpError(rw, "Event name must be specified") + return + } + event.Sender = window.Name() + globalApplication.Events.Emit(&event) + m.ok(rw) + default: + m.httpError(rw, "Unknown event method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_log.go b/v3/pkg/application/messageprocessor_log.go new file mode 100644 index 000000000..3352a980f --- /dev/null +++ b/v3/pkg/application/messageprocessor_log.go @@ -0,0 +1,25 @@ +package application + +import ( + "net/http" + + "github.com/wailsapp/wails/v3/pkg/logger" +) + +func (m *MessageProcessor) processLogMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) { + switch method { + case "Log": + var msg logger.Message + err := params.ToStruct(&msg) + if err != nil { + m.httpError(rw, "error parsing log message: %s", err.Error()) + return + } + msg.Sender = window.Name() + globalApplication.Log(&msg) + m.ok(rw) + default: + m.httpError(rw, "Unknown log method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_params.go b/v3/pkg/application/messageprocessor_params.go new file mode 100644 index 000000000..39d741ce2 --- /dev/null +++ b/v3/pkg/application/messageprocessor_params.go @@ -0,0 +1,190 @@ +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 { + result := val.(int) + return &result + } + return nil +} + +func (a *Args) UInt8(s string) *uint8 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + result := val.(uint8) + return &result + } + return nil +} +func (a *Args) UInt(s string) *uint { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + result := val.(uint) + return &result + } + return nil +} + +func (a *Args) Float64(s string) *float64 { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + result := val.(float64) + return &result + } + return nil +} + +func (a *Args) Bool(s string) *bool { + if a == nil { + return nil + } + if val := a.data[s]; val != nil { + result := val.(bool) + 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..22a32a175 --- /dev/null +++ b/v3/pkg/application/messageprocessor_screens.go @@ -0,0 +1,35 @@ +package application + +import ( + "net/http" +) + +func (m *MessageProcessor) processScreensMethod(method string, rw http.ResponseWriter, _ *http.Request, _ *WebviewWindow, _ QueryParams) { + + switch method { + case "GetAll": + screens, err := globalApplication.GetScreens() + if err != nil { + m.Error("GetAll: %s", err.Error()) + return + } + m.json(rw, screens) + case "GetPrimary": + screen, err := globalApplication.GetPrimaryScreen() + if err != nil { + m.Error("GetPrimary: %s", err.Error()) + return + } + m.json(rw, screen) + case "GetCurrent": + screen, err := globalApplication.CurrentWindow().GetScreen() + if err != nil { + m.Error("GetCurrent: %s", err.Error()) + return + } + m.json(rw, screen) + default: + m.httpError(rw, "Unknown clipboard method: %s", method) + } + +} diff --git a/v3/pkg/application/messageprocessor_window.go b/v3/pkg/application/messageprocessor_window.go new file mode 100644 index 000000000..cf36b9c31 --- /dev/null +++ b/v3/pkg/application/messageprocessor_window.go @@ -0,0 +1,189 @@ +package application + +import ( + "net/http" +) + +func (m *MessageProcessor) processWindowMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) { + + args, err := params.Args() + if err != nil { + m.httpError(rw, "Unable to parse arguments: %s", err) + return + } + + switch method { + case "SetTitle": + title := args.String("title") + if title == nil { + m.Error("SetTitle: title is required") + return + } + window.SetTitle(*title) + m.ok(rw) + case "SetSize": + width := args.Int("width") + height := args.Int("height") + if width == nil || height == nil { + m.Error("Invalid SetSize Message") + return + } + window.SetSize(*width, *height) + m.ok(rw) + case "SetPosition": + x := args.Int("x") + y := args.Int("y") + if x == nil || y == nil { + m.Error("Invalid SetPosition Message") + return + } + window.SetPosition(*x, *y) + m.ok(rw) + case "Fullscreen": + window.Fullscreen() + m.ok(rw) + case "UnFullscreen": + window.UnFullscreen() + m.ok(rw) + case "Minimise": + window.Minimize() + m.ok(rw) + case "UnMinimise": + window.UnMinimise() + m.ok(rw) + case "Maximise": + window.Maximise() + m.ok(rw) + case "UnMaximise": + window.UnMaximise() + m.ok(rw) + case "Show": + window.Show() + m.ok(rw) + case "Hide": + window.Hide() + m.ok(rw) + case "Close": + window.Close() + m.ok(rw) + case "Center": + window.Center() + m.ok(rw) + case "Size": + width, height := window.Size() + m.json(rw, map[string]interface{}{ + "width": width, + "height": height, + }) + case "Position": + x, y := window.Position() + m.json(rw, map[string]interface{}{ + "x": x, + "y": y, + }) + case "SetBackgroundColour": + r := args.UInt8("r") + if r == nil { + m.Error("Invalid SetBackgroundColour Message: 'r' value required") + return + } + g := args.UInt8("g") + if g == nil { + m.Error("Invalid SetBackgroundColour Message: 'g' value required") + return + } + b := args.UInt8("b") + if b == nil { + m.Error("Invalid SetBackgroundColour Message: 'b' value required") + return + } + a := args.UInt8("a") + if a == nil { + m.Error("Invalid SetBackgroundColour Message: 'a' value required") + return + } + window.SetBackgroundColour(&RGBA{ + Red: *r, + Green: *g, + Blue: *b, + Alpha: *a, + }) + m.ok(rw) + case "SetAlwaysOnTop": + alwaysOnTop := args.Bool("alwaysOnTop") + if alwaysOnTop == nil { + m.Error("Invalid SetAlwaysOnTop Message: 'alwaysOnTop' value required") + return + } + window.SetAlwaysOnTop(*alwaysOnTop) + m.ok(rw) + case "SetResizable": + resizable := args.Bool("resizable") + if resizable == nil { + m.Error("Invalid SetResizable Message: 'resizable' value required") + return + } + window.SetResizable(*resizable) + m.ok(rw) + case "SetMinSize": + width := args.Int("width") + height := args.Int("height") + if width == nil || height == nil { + m.Error("Invalid SetMinSize Message") + return + } + window.SetMinSize(*width, *height) + m.ok(rw) + case "SetMaxSize": + width := args.Int("width") + height := args.Int("height") + if width == nil || height == nil { + m.Error("Invalid SetMaxSize Message") + return + } + window.SetMaxSize(*width, *height) + m.ok(rw) + case "Width": + width := window.Width() + m.json(rw, map[string]interface{}{ + "width": width, + }) + case "Height": + height := window.Height() + m.json(rw, map[string]interface{}{ + "height": height, + }) + case "ZoomIn": + window.ZoomIn() + m.ok(rw) + case "ZoomOut": + window.ZoomOut() + m.ok(rw) + case "ZoomReset": + window.ZoomReset() + m.ok(rw) + case "GetZoom": + zoomLevel := window.GetZoom() + m.json(rw, map[string]interface{}{ + "zoomLevel": zoomLevel, + }) + case "Screen": + screen, err := window.GetScreen() + if err != nil { + m.httpError(rw, err.Error()) + return + } + m.json(rw, screen) + case "SetZoom": + zoomLevel := args.Float64("zoomLevel") + if zoomLevel == nil { + m.Error("Invalid SetZoom Message: invalid 'zoomLevel' value") + return + } + window.SetZoom(*zoomLevel) + m.ok(rw) + default: + m.httpError(rw, "Unknown window method: %s", method) + } + +} diff --git a/v3/pkg/application/options_application.go b/v3/pkg/application/options_application.go new file mode 100644 index 000000000..0a768a0b7 --- /dev/null +++ b/v3/pkg/application/options_application.go @@ -0,0 +1,66 @@ +package application + +import ( + "io/fs" + "net/http" + + "github.com/wailsapp/wails/v3/pkg/logger" +) + +type Options struct { + Name string + Description string + Icon []byte + Mac MacOptions + Bind []any + Logger struct { + Silent bool + CustomLoggers []logger.Output + } + Assets AssetOptions + Plugins map[string]Plugin +} + +// AssetOptions defines the configuration of the AssetServer. +type AssetOptions struct { + // FS defines the static assets to be used. A GET request is first tried to be served from this FS. If the FS returns + // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET + // request from it. + // + // If set to nil, all GET requests will be forwarded to Handler. + FS fs.FS + + // Handler will be called for every GET request that can't be served from FS, due to `os.ErrNotExist`. Furthermore all + // non GET requests will always be served from this Handler. + // + // If not defined, the result is the following in cases where the Handler would have been called: + // GET request: `http.StatusNotFound` + // Other request: `http.StatusMethodNotAllowed` + 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. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware +} + +// 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/pkg/application/options_mac.go b/v3/pkg/application/options_mac.go new file mode 100644 index 000000000..7ad1c5f0f --- /dev/null +++ b/v3/pkg/application/options_mac.go @@ -0,0 +1,130 @@ +package application + +type ActivationPolicy int + +const ( + 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 +) + +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 +} + +type MacBackdrop int + +const ( + MacBackdropNormal MacBackdrop = iota + MacBackdropTransparent + MacBackdropTranslucent +) + +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 +type MacWindow struct { + Backdrop MacBackdrop + TitleBar MacTitleBar + Appearance MacAppearanceType + InvisibleTitleBarHeight int +} + +// MacTitleBar contains options for the Mac titlebar +type MacTitleBar struct { + AppearsTransparent bool + Hide bool + HideTitle bool + FullSizeContent bool + UseToolbar bool + HideToolbarSeparator bool + 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" +) diff --git a/v3/pkg/application/options_webview_window.go b/v3/pkg/application/options_webview_window.go new file mode 100644 index 000000000..99e40d054 --- /dev/null +++ b/v3/pkg/application/options_webview_window.go @@ -0,0 +1,49 @@ +package application + +type WindowState int + +const ( + WindowStateNormal WindowState = iota + WindowStateMinimised + WindowStateMaximised + WindowStateFullscreen +) + +type WebviewWindowOptions struct { + Name string + Title string + Width, Height int + AlwaysOnTop bool + URL string + DisableResize bool + Frameless bool + MinWidth int + MinHeight int + MaxWidth int + MaxHeight int + StartState WindowState + Mac MacWindow + BackgroundColour *RGBA + HTML string + JS string + CSS string + X int + Y int + HideOnClose bool + FullscreenButtonEnabled bool + Hidden bool + EnableFraudulentWebsiteWarnings bool + Zoom float64 + EnableDragAndDrop bool +} + +var WebviewWindowDefaults = &WebviewWindowOptions{ + Title: "", + Width: 800, + Height: 600, + URL: "", +} + +type RGBA struct { + Red, Green, Blue, Alpha uint8 +} diff --git a/v3/pkg/application/plugins.go b/v3/pkg/application/plugins.go new file mode 100644 index 000000000..90ade7358 --- /dev/null +++ b/v3/pkg/application/plugins.go @@ -0,0 +1,50 @@ +package application + +import "github.com/wailsapp/wails/v2/pkg/assetserver" + +type Plugin interface { + Name() string + Init(app *App) error + Shutdown() + CallableByJS() []string + InjectJS() string +} + +type PluginManager struct { + plugins map[string]Plugin + assetServer *assetserver.AssetServer + initialisedPlugins []Plugin +} + +func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager { + result := &PluginManager{ + plugins: plugins, + assetServer: assetServer, + } + return result +} + +func (p *PluginManager) Init() error { + for _, plugin := range p.plugins { + err := plugin.Init(globalApplication) + if err != nil { + globalApplication.error("Plugin '%s' failed to initialise: %s", plugin.Name(), err.Error()) + p.Shutdown() + return err + } + p.initialisedPlugins = append(p.initialisedPlugins, plugin) + injectJS := plugin.InjectJS() + if injectJS != "" { + p.assetServer.AddPluginScript(plugin.Name(), injectJS) + } + globalApplication.info("Plugin '%s' initialised", plugin.Name()) + } + return nil +} + +func (p *PluginManager) Shutdown() { + for _, plugin := range p.initialisedPlugins { + plugin.Shutdown() + globalApplication.info("Plugin '%s' shutdown", plugin.Name()) + } +} diff --git a/v3/pkg/application/roles.go b/v3/pkg/application/roles.go new file mode 100644 index 000000000..0aec3bd48 --- /dev/null +++ b/v3/pkg/application/roles.go @@ -0,0 +1,148 @@ +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 + 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 + Close Role = iota + Reload Role = iota + ForceReload Role = iota + ToggleDevTools Role = iota + ResetZoom Role = iota + ZoomIn Role = iota + ZoomOut Role = iota + ToggleFullscreen Role = iota + + Minimize Role = iota + Zoom Role = iota + //Front Role = iota + //WindowRole Role = iota + + //QuitRole Role = + //TogglefullscreenRole Role = "togglefullscreen" + //ViewMenuRole Role = "viewMenu" + //WindowMenuRole Role = "windowMenu" + + //FrontRole Role = "front" + //ZoomRole Role = "zoom" + //WindowSubMenuRole Role = "windowSubMenu" + //HelpSubMenuRole Role = "helpSubMenu" + //SeparatorItemRole Role = "separatorItem" +) + +func newFileMenu() *MenuItem { + fileMenu := NewMenu() + if runtime.GOOS == "darwin" { + fileMenu.AddRole(Close) + } else { + fileMenu.AddRole(Quit) + } + subMenu := newSubMenuItem("File") + subMenu.submenu = fileMenu + return subMenu +} + +func newViewMenu() *MenuItem { + viewMenu := NewMenu() + viewMenu.AddRole(Reload) + viewMenu.AddRole(ForceReload) + viewMenu.AddRole(ToggleDevTools) + 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 { + 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(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(Minimize) + menu.AddRole(Zoom) + subMenu := newSubMenuItem("Window") + subMenu.submenu = menu + return subMenu +} + +func newHelpMenu() *MenuItem { + menu := NewMenu() + menu.Add("Learn More").OnClick(func(ctx *Context) { + globalApplication.CurrentWindow().SetURL("https://wails.io") + }) + subMenu := newSubMenuItem("Help") + subMenu.submenu = menu + return subMenu +} diff --git a/v3/pkg/application/screen.go b/v3/pkg/application/screen.go new file mode 100644 index 000000000..b3712c9df --- /dev/null +++ b/v3/pkg/application/screen.go @@ -0,0 +1,26 @@ +package application + +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + Scale float32 // The scale factor of the display + 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 + WorkArea Rect // The work area of the display + 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 Size struct { + Width int + Height int +} diff --git a/v3/pkg/application/screen_darwin.go b/v3/pkg/application/screen_darwin.go new file mode 100644 index 000000000..d45753f7b --- /dev/null +++ b/v3/pkg/application/screen_darwin.go @@ -0,0 +1,151 @@ +//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 scale; + double rotation; + bool isPrimary; +} Screen; + + +int GetNumScreens(){ + return [[NSScreen screens] count]; +} + +Screen processScreen(NSScreen* screen){ + Screen returnScreen; + returnScreen.scale = 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( @available(macOS 10.15, *) ){ + returnScreen.name = [screen.localizedName UTF8String]; + } + + 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); +} + +*/ +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), + }, + WorkArea: Rect{ + X: int(screen.w_x), + Y: int(screen.w_y), + Height: int(screen.w_height), + Width: int(screen.w_width), + }, + Scale: float32(screen.scale), + ID: C.GoString(screen.id), + Name: C.GoString(screen.name), + IsPrimary: bool(screen.isPrimary), + Rotation: float32(screen.rotation), + } +} + +func getPrimaryScreen() (*Screen, error) { + cScreen := C.GetPrimaryScreen() + return cScreenToScreen(cScreen), nil +} + +func 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 +} diff --git a/v3/pkg/application/systemtray.go b/v3/pkg/application/systemtray.go new file mode 100644 index 000000000..23ba6f286 --- /dev/null +++ b/v3/pkg/application/systemtray.go @@ -0,0 +1,106 @@ +package application + +type IconPosition int + +const ( + NSImageNone = iota + NSImageOnly + NSImageLeft + NSImageRight + NSImageBelow + NSImageAbove + NSImageOverlaps + NSImageLeading + NSImageTrailing +) + +type systemTrayImpl interface { + setLabel(label string) + run() + setIcon(icon []byte) + setMenu(menu *Menu) + setIconPosition(position int) + setTemplateIcon(icon []byte) + destroy() +} + +type SystemTray struct { + id uint + label string + icon []byte + iconPosition int + + // Platform specific implementation + impl systemTrayImpl + menu *Menu + isTemplateIcon bool +} + +func NewSystemTray(id uint) *SystemTray { + return &SystemTray{ + id: id, + label: "", + iconPosition: NSImageLeading, + } +} + +func (s *SystemTray) SetLabel(label string) { + if s.impl == nil { + s.label = label + return + } + s.impl.setLabel(label) +} + +func (s *SystemTray) Label() string { + return s.label +} + +func (s *SystemTray) Run() { + s.impl = newSystemTrayImpl(s) + s.impl.run() +} + +func (s *SystemTray) SetIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + } else { + s.impl.setIcon(icon) + } + return s +} + +func (s *SystemTray) SetMenu(menu *Menu) *SystemTray { + if s.impl == nil { + s.menu = menu + } else { + s.impl.setMenu(menu) + } + return s +} + +func (s *SystemTray) SetIconPosition(iconPosition int) *SystemTray { + if s.impl == nil { + s.iconPosition = iconPosition + } else { + s.impl.setIconPosition(iconPosition) + } + return s +} + +func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray { + if s.impl == nil { + s.icon = icon + s.isTemplateIcon = true + } else { + s.impl.setTemplateIcon(icon) + } + return s +} + +func (s *SystemTray) Destroy() { + if s.impl == nil { + return + } + s.impl.destroy() +} diff --git a/v3/pkg/application/systemtray_darwin.go b/v3/pkg/application/systemtray_darwin.go new file mode 100644 index 000000000..f5a464341 --- /dev/null +++ b/v3/pkg/application/systemtray_darwin.go @@ -0,0 +1,161 @@ +//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.h" + +// Create a new system tray +void* systemTrayNew() { + NSStatusItem *statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain]; + 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); + }); +} + +// 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; + }); +} + +// Add menu to system tray +void systemTraySetMenu(void* nsStatusItem, void* nsMenu) { + // Set the menu on the main thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem; + NSMenu *menu = (NSMenu *)nsMenu; + statusItem.menu = menu; + }); +} + +// 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]; + }); +} + +*/ +import "C" +import ( + "unsafe" +) + +type macosSystemTray struct { + id uint + label string + icon []byte + menu *Menu + + nsStatusItem unsafe.Pointer + nsImage unsafe.Pointer + nsMenu unsafe.Pointer + iconPosition int + isTemplateIcon bool +} + +func (s *macosSystemTray) setIconPosition(position int) { + s.iconPosition = position +} + +func (s *macosSystemTray) setMenu(menu *Menu) { + s.menu = menu +} + +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()) + if s.label != "" { + C.systemTraySetLabel(s.nsStatusItem, C.CString(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 + C.systemTraySetMenu(s.nsStatusItem, s.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) 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 newSystemTrayImpl(s *SystemTray) systemTrayImpl { + return &macosSystemTray{ + id: s.id, + label: s.label, + icon: s.icon, + menu: s.menu, + iconPosition: s.iconPosition, + isTemplateIcon: s.isTemplateIcon, + } +} + +func (s *macosSystemTray) setLabel(label string) { + s.label = label + C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) +} + +func (s *macosSystemTray) destroy() { + // Remove the status item from the status bar and its associated menu + C.systemTrayDestroy(s.nsStatusItem) +} diff --git a/v3/pkg/application/webview_drag.h b/v3/pkg/application/webview_drag.h new file mode 100644 index 000000000..aca00d970 --- /dev/null +++ b/v3/pkg/application/webview_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_drag.m b/v3/pkg/application/webview_drag.m new file mode 100644 index 000000000..057e68d95 --- /dev/null +++ b/v3/pkg/application/webview_drag.m @@ -0,0 +1,60 @@ +//go:build darwin + +#import +#import +#import "webview_drag.h" + +#import "../events/events.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.go b/v3/pkg/application/webview_window.go new file mode 100644 index 000000000..e9899fe82 --- /dev/null +++ b/v3/pkg/application/webview_window.go @@ -0,0 +1,659 @@ +package application + +import ( + "fmt" + "sync" + "time" + + "github.com/wailsapp/wails/v3/pkg/logger" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +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) + restore() + setBackgroundColour(color *RGBA) + run() + center() + size() (int, int) + width() int + height() int + position() (int, int) + destroy() + reload() + forceReload() + toggleDevTools() + zoomReset() + zoomIn() + zoomOut() + getZoom() float64 + setZoom(zoom float64) + close() + zoom() + setHTML(html string) + setPosition(x int, y int) + on(eventID uint) + minimise() + unminimise() + maximise() + unmaximise() + fullscreen() + unfullscreen() + isMinimised() bool + isMaximised() bool + isFullscreen() bool + disableSizeConstraints() + setFullscreenButtonEnabled(enabled bool) + show() + hide() + getScreen() (*Screen, error) + setFrameless(bool) + openContextMenu(menu *Menu, data *ContextMenuData) + } +) + +type WebviewWindow struct { + options *WebviewWindowOptions + impl webviewWindowImpl + implLock sync.RWMutex + id uint + + eventListeners map[uint][]func(ctx *WindowEventContext) + eventListenersLock sync.RWMutex + + contextMenus map[string]*Menu + contextMenusLock sync.RWMutex +} + +var windowID uint +var windowIDLock sync.RWMutex + +func getWindowID() uint { + windowIDLock.Lock() + defer windowIDLock.Unlock() + windowID++ + return windowID +} + +func NewWindow(options *WebviewWindowOptions) *WebviewWindow { + if options.Width == 0 { + options.Width = 800 + } + if options.Height == 0 { + options.Height = 600 + } + if options.URL == "" { + options.URL = "/" + } + + result := &WebviewWindow{ + id: getWindowID(), + options: options, + eventListeners: make(map[uint][]func(ctx *WindowEventContext)), + contextMenus: make(map[string]*Menu), + } + + return result +} + +func (w *WebviewWindow) SetTitle(title string) *WebviewWindow { + w.implLock.RLock() + defer w.implLock.RUnlock() + w.options.Title = title + if w.impl != nil { + w.impl.setTitle(title) + } + return w +} + +func (w *WebviewWindow) Name() string { + return w.options.Name +} + +func (w *WebviewWindow) SetSize(width, height int) *WebviewWindow { + // 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 { + w.impl.setSize(width, height) + } + return w +} + +func (w *WebviewWindow) run() { + if w.impl != nil { + return + } + w.implLock.Lock() + w.impl = newWindowImpl(w) + w.implLock.Unlock() + w.impl.run() +} + +func (w *WebviewWindow) SetAlwaysOnTop(b bool) *WebviewWindow { + w.options.AlwaysOnTop = b + if w.impl == nil { + w.impl.setAlwaysOnTop(b) + } + return w +} + +func (w *WebviewWindow) Show() *WebviewWindow { + if globalApplication.impl == nil { + return w + } + if w.impl == nil { + w.run() + return w + } + w.impl.show() + return w +} +func (w *WebviewWindow) Hide() *WebviewWindow { + w.options.Hidden = true + if w.impl != nil { + w.impl.hide() + } + return w +} + +func (w *WebviewWindow) SetURL(s string) *WebviewWindow { + w.options.URL = s + if w.impl != nil { + w.impl.setURL(s) + } + return w +} + +func (w *WebviewWindow) SetZoom(magnification float64) *WebviewWindow { + w.options.Zoom = magnification + if w.impl != nil { + w.impl.setZoom(magnification) + } + return w +} + +func (w *WebviewWindow) GetZoom() float64 { + if w.impl != nil { + return w.impl.getZoom() + } + return 1 +} + +func (w *WebviewWindow) SetResizable(b bool) *WebviewWindow { + w.options.DisableResize = !b + if w.impl != nil { + w.impl.setResizable(b) + } + return w +} + +func (w *WebviewWindow) Resizable() bool { + return !w.options.DisableResize +} + +func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) *WebviewWindow { + 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 { + w.impl.setSize(newWidth, newHeight) + } + w.impl.setMinSize(minWidth, minHeight) + } + return w +} + +func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) *WebviewWindow { + 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 { + w.impl.setSize(newWidth, newHeight) + } + w.impl.setMaxSize(maxWidth, maxHeight) + } + return w +} + +func (w *WebviewWindow) ExecJS(js string) { + if w.impl == nil { + return + } + w.impl.execJS(js) +} + +func (w *WebviewWindow) Fullscreen() *WebviewWindow { + if w.impl == nil { + w.options.StartState = WindowStateFullscreen + return w + } + if !w.IsFullscreen() { + w.disableSizeConstraints() + w.impl.fullscreen() + } + return w +} + +func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) *WebviewWindow { + w.options.FullscreenButtonEnabled = enabled + if w.impl != nil { + w.impl.setFullscreenButtonEnabled(enabled) + } + return w +} + +// IsMinimised returns true if the window is minimised +func (w *WebviewWindow) IsMinimised() bool { + if w.impl == nil { + return false + } + return w.impl.isMinimised() +} + +// IsMaximised returns true if the window is maximised +func (w *WebviewWindow) IsMaximised() bool { + if w.impl == nil { + return false + } + return w.impl.isMaximised() +} + +// Size returns the size of the window +func (w *WebviewWindow) Size() (width int, height int) { + if w.impl == nil { + return 0, 0 + } + return w.impl.size() +} + +// IsFullscreen returns true if the window is fullscreen +func (w *WebviewWindow) IsFullscreen() bool { + w.implLock.RLock() + defer w.implLock.RUnlock() + if w.impl == nil { + return false + } + return w.impl.isFullscreen() +} + +func (w *WebviewWindow) SetBackgroundColour(colour *RGBA) *WebviewWindow { + w.options.BackgroundColour = colour + if w.impl != nil { + w.impl.setBackgroundColour(colour) + } + return w +} + +func (w *WebviewWindow) handleMessage(message string) { + w.info(message) + // Check for special messages + if message == "test" { + w.SetTitle("Hello World") + } + w.info("ProcessMessage from front end:", message) + +} + +func (w *WebviewWindow) Center() { + if w.impl == nil { + return + } + w.impl.center() +} + +func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *WindowEventContext)) { + eventID := uint(eventType) + w.eventListenersLock.Lock() + defer w.eventListenersLock.Unlock() + w.eventListeners[eventID] = append(w.eventListeners[eventID], callback) + if w.impl != nil { + w.impl.on(eventID) + } +} + +func (w *WebviewWindow) handleWindowEvent(id uint) { + w.eventListenersLock.RLock() + for _, callback := range w.eventListeners[id] { + go callback(blankWindowEventContext) + } + w.eventListenersLock.RUnlock() +} + +func (w *WebviewWindow) Width() int { + if w.impl == nil { + return 0 + } + return w.impl.width() +} + +func (w *WebviewWindow) Height() int { + if w.impl == nil { + return 0 + } + return w.impl.height() +} + +func (w *WebviewWindow) Position() (int, int) { + w.implLock.RLock() + defer w.implLock.RUnlock() + if w.impl == nil { + return 0, 0 + } + return w.impl.position() +} + +func (w *WebviewWindow) Destroy() { + if w.impl == nil { + return + } + w.impl.destroy() +} + +func (w *WebviewWindow) Reload() { + if w.impl == nil { + return + } + w.impl.reload() +} + +func (w *WebviewWindow) ForceReload() { + if w.impl == nil { + return + } + w.impl.forceReload() +} + +func (w *WebviewWindow) ToggleFullscreen() { + if w.impl == nil { + return + } + if w.IsFullscreen() { + w.UnFullscreen() + } else { + w.Fullscreen() + } +} + +func (w *WebviewWindow) ToggleDevTools() { + if w.impl == nil { + return + } + w.impl.toggleDevTools() +} + +func (w *WebviewWindow) ZoomReset() *WebviewWindow { + if w.impl != nil { + w.impl.zoomReset() + } + return w + +} + +func (w *WebviewWindow) ZoomIn() { + if w.impl == nil { + return + } + w.impl.zoomIn() +} + +func (w *WebviewWindow) ZoomOut() { + if w.impl == nil { + return + } + w.impl.zoomOut() +} + +func (w *WebviewWindow) Close() { + if w.impl == nil { + return + } + w.impl.close() +} + +func (w *WebviewWindow) Minimize() { + if w.impl == nil { + return + } + w.impl.minimise() +} + +func (w *WebviewWindow) Zoom() { + if w.impl == nil { + return + } + w.impl.zoom() +} + +func (w *WebviewWindow) SetHTML(html string) *WebviewWindow { + w.options.HTML = html + if w.impl != nil { + w.impl.setHTML(html) + } + return w +} + +func (w *WebviewWindow) SetPosition(x, y int) *WebviewWindow { + w.options.X = x + w.options.Y = y + if w.impl != nil { + w.impl.setPosition(x, y) + } + return w +} + +func (w *WebviewWindow) Minimise() *WebviewWindow { + if w.impl == nil { + w.options.StartState = WindowStateMinimised + return w + } + if !w.IsMinimised() { + w.impl.minimise() + } + return w +} + +func (w *WebviewWindow) Maximise() *WebviewWindow { + if w.impl == nil { + w.options.StartState = WindowStateMaximised + return w + } + if !w.IsMaximised() { + w.disableSizeConstraints() + w.impl.maximise() + } + return w +} + +func (w *WebviewWindow) UnMinimise() { + if w.impl == nil { + return + } + w.impl.unminimise() +} + +func (w *WebviewWindow) UnMaximise() { + if w.impl == nil { + return + } + w.enableSizeConstraints() + w.impl.unmaximise() +} + +func (w *WebviewWindow) UnFullscreen() { + if w.impl == nil { + return + } + w.enableSizeConstraints() + w.impl.unfullscreen() +} + +func (w *WebviewWindow) Restore() { + if w.impl == nil { + return + } + if w.IsMinimised() { + w.UnMinimise() + } else if w.IsMaximised() { + w.UnMaximise() + } else if w.IsFullscreen() { + w.UnFullscreen() + } +} + +func (w *WebviewWindow) disableSizeConstraints() { + if w.impl == nil { + return + } + w.impl.setMinSize(0, 0) + w.impl.setMaxSize(0, 0) +} + +func (w *WebviewWindow) enableSizeConstraints() { + if w.impl == nil { + return + } + w.SetMinSize(w.options.MinWidth, w.options.MinHeight) + w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight) +} + +func (w *WebviewWindow) GetScreen() (*Screen, error) { + if w.impl == nil { + return nil, nil + } + return w.impl.getScreen() +} + +func (w *WebviewWindow) SetFrameless(frameless bool) *WebviewWindow { + w.options.Frameless = frameless + if w.impl != nil { + w.impl.setFrameless(frameless) + } + return w +} + +func (w *WebviewWindow) dispatchWailsEvent(event *WailsEvent) { + msg := fmt.Sprintf("_wails.dispatchWailsEvent(%s);", event.ToJSON()) + w.ExecJS(msg) +} + +func (w *WebviewWindow) info(message string, args ...any) { + + globalApplication.Log(&logger.Message{ + Level: "INFO", + Message: message, + Data: args, + Sender: w.Name(), + Time: time.Now(), + }) +} +func (w *WebviewWindow) error(message string, args ...any) { + + globalApplication.Log(&logger.Message{ + Level: "ERROR", + Message: message, + Data: args, + Sender: w.Name(), + Time: time.Now(), + }) +} + +func (w *WebviewWindow) handleDragAndDropMessage(event *dragAndDropMessage) { + ctx := newWindowEventContext() + ctx.setDroppedFiles(event.filenames) + for _, listener := range w.eventListeners[uint(events.FilesDropped)] { + listener(ctx) + } +} + +func (w *WebviewWindow) openContextMenu(data *ContextMenuData) { + menu, ok := w.contextMenus[data.Id] + if !ok { + // try application level context menu + menu, ok = globalApplication.getContextMenu(data.Id) + if !ok { + w.error("No context menu found for id: %s", data.Id) + return + } + } + menu.setContextData(data) + if w.impl == nil { + return + } + w.impl.openContextMenu(menu, data) +} + +func (w *WebviewWindow) RegisterContextMenu(name string, menu *Menu) { + w.contextMenusLock.Lock() + defer w.contextMenusLock.Unlock() + w.contextMenus[name] = menu +} diff --git a/v3/pkg/application/webview_window.h b/v3/pkg/application/webview_window.h new file mode 100644 index 000000000..280ec934c --- /dev/null +++ b/v3/pkg/application/webview_window.h @@ -0,0 +1,35 @@ +//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 bool hideOnClose; +@property unsigned int windowId; +@property (retain) NSEvent* leftMouseEvent; +@property unsigned int invisibleTitleBarHeight; +@property NSWindowStyleMask previousStyleMask; // Used to restore the window style mask when using frameless + +- (void)handleLeftMouseUp:(NSWindow *)window; +- (void)handleLeftMouseDown:(NSEvent*)event; + +@end + + +#endif /* WebviewWindowDelegate_h */ diff --git a/v3/pkg/application/webview_window.m b/v3/pkg/application/webview_window.m new file mode 100644 index 000000000..70636fc47 --- /dev/null +++ b/v3/pkg/application/webview_window.m @@ -0,0 +1,585 @@ +//go:build darwin +#import +#import +#import "webview_window.h" +#import "../events/events.h" +extern void processMessage(unsigned int, const char*); +extern void processURLRequest(unsigned int, void *); +extern bool hasListeners(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 { +} +- (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]; +} +@end +@implementation WebviewWindowDelegate +- (BOOL)windowShouldClose:(NSWindow *)sender { + if( self.hideOnClose ) { + [sender orderOut:nil]; + return false; + } + return true; +} +- (void) dealloc { + // Makes sure to remove the retained properties so the reference counter of the retains are decreased + self.leftMouseEvent = nil; + [super dealloc]; +} +// Handle script messages from the external bridge +- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message { + NSString *m = message.body; + /* + // TODO: Check for drag + if ( [m isEqualToString:@"drag"] ) { + if( [self IsFullScreen] ) { + return; + } + if( self.mouseEvent != nil ) { + [self.mainWindow performWindowDragWithEvent:self.mouseEvent]; + } + return; + } + */ + 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]; + } + } +} +// GENERATED EVENTS START +- (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)windowDidChangeOcclusionState:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeOcclusionState) ) { + processWindowEvent(self.windowId, EventWindowDidChangeOcclusionState); + } +} + +- (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)windowDidChangeVisibility:(NSNotification *)notification { + if( hasListeners(EventWindowDidChangeVisibility) ) { + processWindowEvent(self.windowId, EventWindowDidChangeVisibility); + } +} + +- (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)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 { + if( hasListeners(EventWindowDidOrderOffScreen) ) { + processWindowEvent(self.windowId, EventWindowDidOrderOffScreen); + } +} + +- (void)windowDidOrderOnScreen:(NSNotification *)notification { + 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)windowDidUnfocus:(NSNotification *)notification { + if( hasListeners(EventWindowDidUnfocus) ) { + processWindowEvent(self.windowId, EventWindowDidUnfocus); + } +} + +- (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)windowDidUpdateVisibility:(NSNotification *)notification { + if( hasListeners(EventWindowDidUpdateVisibility) ) { + processWindowEvent(self.windowId, EventWindowDidUpdateVisibility); + } +} + +- (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 { + 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 { + if( hasListeners(EventWindowWillOrderOffScreen) ) { + processWindowEvent(self.windowId, EventWindowWillOrderOffScreen); + } +} + +- (void)windowWillOrderOnScreen:(NSNotification *)notification { + 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)webView:(WKWebView *)webview didStartProvisionalNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidStartProvisionalNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidStartProvisionalNavigation); + } +} + +- (void)webView:(WKWebView *)webview didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidReceiveServerRedirectForProvisionalNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidReceiveServerRedirectForProvisionalNavigation); + } +} + +- (void)webView:(WKWebView *)webview didFinishNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidFinishNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidFinishNavigation); + } +} + +- (void)webView:(WKWebView *)webview didCommitNavigation:(WKNavigation *)navigation { + if( hasListeners(EventWebViewDidCommitNavigation) ) { + processWindowEvent(self.windowId, EventWebViewDidCommitNavigation); + } +} + +// GENERATED EVENTS END +@end diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go new file mode 100644 index 000000000..c8728f2e9 --- /dev/null +++ b/v3/pkg/application/webview_window_darwin.go @@ -0,0 +1,1178 @@ +//go:build darwin + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework WebKit + +#include "application.h" +#include "webview_window.h" +#include +#include "Cocoa/Cocoa.h" +#import +#import +#import "webview_drag.h" + + +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, bool hideOnClose) { + 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]; + + // 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]; + + config.suppressesIncrementalRendering = true; + config.applicationNameForUserAgent = @"wails.io"; + [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; + if (@available(macOS 10.15, *)) { + config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; + } + + // 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]; + + delegate.hideOnClose = hideOnClose; + + 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) { + // On main thread + dispatch_async(dispatch_get_main_queue(), ^{ + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window setOpaque:NO]; + [window setBackgroundColor:[NSColor clearColor]]; + }); +} + +void windowSetInvisibleTitleBar(void* nsWindow, unsigned int height) { + // On main thread + dispatch_async(dispatch_get_main_queue(), ^{ + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Get delegate + WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate]; + // Set height + delegate.invisibleTitleBarHeight = height; + }); +} + + +// Set the title of the NSWindow +void windowSetTitle(void* nsWindow, char* title) { + // Set window title on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + 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 + dispatch_async(dispatch_get_main_queue(), ^{ + 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 + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)nsWindow setLevel:alwaysOnTop ? NSStatusWindowLevel : NSNormalWindowLevel]; + }); +} + +// Load URL in NSWindow +void navigationLoadURL(void* nsWindow, char* url) { + // Load URL on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + 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 + dispatch_async(dispatch_get_main_queue(), ^{ + 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 + dispatch_async(dispatch_get_main_queue(), ^{ + 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 + dispatch_async(dispatch_get_main_queue(), ^{ + 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]; + }); +} + +// Enable NSWindow devtools +void windowEnableDevTools(void* nsWindow) { + // Enable devtools on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Enable devtools in webview + [window.webView.configuration.preferences setValue:@YES forKey:@"developerExtrasEnabled"]; + }); +} + +// windowZoomReset +void windowZoomReset(void* nsWindow) { + // Reset zoom on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Reset zoom + [window.webView setMagnification:1.0]; + }); +} + +// windowZoomSet +void windowZoomSet(void* nsWindow, double zoom) { + // Reset zoom on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Reset zoom + [window.webView setMagnification:zoom]; + }); +} + +// windowZoomGet +float windowZoomGet(void* nsWindow) { + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Get zoom + return [window.webView magnification]; +} + +// windowZoomIn +void windowZoomIn(void* nsWindow) { + // Zoom in on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Zoom in + [window.webView setMagnification:window.webView.magnification + 0.05]; + }); +} + +// windowZoomOut +void windowZoomOut(void* nsWindow) { + // Zoom out on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + 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 +void windowSetPosition(void* nsWindow, int x, int y) { + // Set window position on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)nsWindow setFrameOrigin:NSMakePoint(x, y)]; + }); +} + +// Execute JS in NSWindow +void windowExecJS(void* nsWindow, const char* js) { + // Execute JS on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window.webView evaluateJavaScript:[NSString stringWithUTF8String:js] completionHandler:nil]; + free((void*)js); + }); +} + +// Make NSWindow backdrop translucent +void windowSetTranslucent(void* nsWindow) { + // Set window transparent on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + + // 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) { + // Set webview transparent on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + 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) { + // Set webview background color on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + 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) { + // Set window background color on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // Get window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // Set window background color + [window 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]; +} + +// 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) { + // Set window normal on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // 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) { + // Disable fullscreen button on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // Get window + WebviewWindow* window = (WebviewWindow*)nsWindow; + NSButton *fullscreenButton = [window standardWindowButton:NSWindowZoomButton]; + fullscreenButton.enabled = enabled; + }); +} + +// Set the titlebar style +void windowSetTitleBarAppearsTransparent(void* nsWindow, bool transparent) { + // Set window titlebar style on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + if( transparent ) { + [(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:true]; + } else { + [(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:false]; + } + }); +} + +// Set window fullsize content view +void windowSetFullSizeContent(void* nsWindow, bool fullSize) { + // Set window fullsize content view on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + 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) { + // Set window titlebar hidden on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + 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) { + // Set window titlebar hidden on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + if( hideTitle ) { + [(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleHidden]; + } else { + [(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleVisible]; + } + }); +} + +// Set Window use toolbar +void windowSetUseToolbar(void* nsWindow, bool useToolbar, int toolbarStyle) { + // Set window use toolbar on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + if( useToolbar ) { + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"]; + [toolbar autorelease]; + [window setToolbar:toolbar]; + + // If macos 11 or higher, set toolbar style + if (@available(macOS 11.0, *)) { + [window setToolbarStyle:toolbarStyle]; + } + + } else { + [window setToolbar:nil]; + } + }); +} + +// Set window toolbar style +void windowSetToolbarStyle(void* nsWindow, int style) { + // use @available to check if the function is available + // if not, return + if (@available(macOS 11.0, *)) { + // Set window toolbar style on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // get toolbar + NSToolbar* toolbar = [window toolbar]; + // set toolbar style + [toolbar setShowsBaselineSeparator:style]; + }); + } +} + +// Set Hide Toolbar Separator +void windowSetHideToolbarSeparator(void* nsWindow, bool hideSeparator) { + // Set window hide toolbar separator on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // get toolbar + NSToolbar* toolbar = [window toolbar]; + // Return if toolbar nil + if( toolbar == nil ) { + return; + } + if( hideSeparator ) { + [toolbar setShowsBaselineSeparator:false]; + } else { + [toolbar setShowsBaselineSeparator:true]; + } + }); +} + +// Set Window appearance type +void windowSetAppearanceTypeByName(void* nsWindow, const char *appearanceName) { + // Set window appearance type on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // set window appearance type by name + // Convert appearance name to NSString + NSString* appearanceNameString = [NSString stringWithUTF8String:appearanceName]; + // Set appearance + [window setAppearance:[NSAppearance appearanceNamed:appearanceNameString]]; + + free((void*)appearanceName); + }); +} + +// Center window on current monitor +void windowCenter(void* nsWindow) { + // Center window on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + [window center]; + }); +} + +// Get the current size of the window +void windowGetSize(void* nsWindow, int* width, int* height) { + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // get window frame + NSRect frame = [window frame]; + // set width and height + *width = frame.size.width; + *height = frame.size.height; +} + +// Get window width +int windowGetWidth(void* nsWindow) { + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // get window frame + NSRect frame = [window frame]; + // return width + return frame.size.width; +} + +// Get window height +int windowGetHeight(void* nsWindow) { + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // get window frame + NSRect frame = [window frame]; + // return height + return frame.size.height; +} + +// Get window position +void windowGetPosition(void* nsWindow, int* x, int* y) { + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // get window frame + NSRect frame = [window frame]; + // set x and y + *x = frame.origin.x; + *y = frame.origin.y; +} + +// Destroy window +void windowDestroy(void* nsWindow) { + // Destroy window on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* window = (WebviewWindow*)nsWindow; + // close window + [window close]; + }); +} + + +// windowClose closes the current window +static void windowClose(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // close window + [(WebviewWindow*)window close]; + }); +} + +// windowZoom +static void windowZoom(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // zoom window + [(WebviewWindow*)window zoom:nil]; + }); +} + +// webviewRenderHTML renders the given HTML +static void windowRenderHTML(void *window, const char *html) { + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + 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) { + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + 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) { + dispatch_async(dispatch_get_main_queue(), ^{ + // minimize window + [(WebviewWindow*)window miniaturize:nil]; + }); +} + +// zoom maximizes the window to the screen dimensions +static void windowMaximise(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // maximize window + [(WebviewWindow*)window zoom:nil]; + }); +} + +static bool isFullScreen(void *window) { + // get main window + WebviewWindow* nsWindow = (WebviewWindow*)window; + long mask = [nsWindow styleMask]; + return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen; +} + +// windowSetFullScreen +static void windowSetFullScreen(void *window, bool fullscreen) { + if (isFullScreen(window)) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + WebviewWindow* nsWindow = (WebviewWindow*)window; + windowSetMaxSize(nsWindow, 0, 0); + windowSetMinSize(nsWindow, 0, 0); + [nsWindow toggleFullScreen:nil]; + }); +} + +// windowUnminimise +static void windowUnminimise(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // unminimize window + [(WebviewWindow*)window deminiaturize:nil]; + }); +} + +// windowUnmaximise +static void windowUnmaximise(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // unmaximize window + [(WebviewWindow*)window zoom:nil]; + }); +} + +static void windowDisableSizeConstraints(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* nsWindow = (WebviewWindow*)window; + // disable size constraints + [nsWindow setContentMinSize:CGSizeZero]; + [nsWindow setContentMaxSize:CGSizeZero]; + }); +} + +static void windowShow(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + // show window + [(WebviewWindow*)window makeKeyAndOrderFront:nil]; + }); +} + +static void windowHide(void *window) { + dispatch_async(dispatch_get_main_queue(), ^{ + [(WebviewWindow*)window orderOut:nil]; + }); +} + +// windowShowMenu opens an NSMenu at the given coordinates +static void windowShowMenu(void *window, void *menu, int x, int y) { + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* nsWindow = (WebviewWindow*)window; + // get menu + NSMenu* nsMenu = (NSMenu*)menu; + // get webview + WKWebView* webView = nsWindow.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) { + dispatch_async(dispatch_get_main_queue(), ^{ + // get main window + WebviewWindow* nsWindow = (WebviewWindow*)window; + // set the window style to be frameless + if (frameless) { + [nsWindow setStyleMask:([nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView)]; + } else { + [nsWindow setStyleMask:([nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView)]; + } + }); +} + +*/ +import "C" +import ( + "net/url" + "sync" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +var showDevTools = func(window unsafe.Pointer) {} + +type macosWebviewWindow struct { + nsWindow unsafe.Pointer + parent *WebviewWindow +} + +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) getScreen() (*Screen, error) { + return getScreenForWindow(w) +} + +func (w *macosWebviewWindow) show() { + C.windowShow(w.nsWindow) +} + +func (w *macosWebviewWindow) hide() { + 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() { + C.windowClose(w.nsWindow) + if !w.parent.options.HideOnClose { + globalApplication.deleteWindowByID(w.parent.id) + } +} + +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) toggleDevTools() { + showDevTools(w.nsWindow) +} + +func (w *macosWebviewWindow) reload() { + //TODO: Implement + println("reload called on WebviewWindow", w.parent.id) +} + +func (w *macosWebviewWindow) forceReload() { + //TODO: Implement + println("forceReload called on WebviewWindow", 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) 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) execJS(js string) { + C.windowExecJS(w.nsWindow, C.CString(js)) +} + +func (w *macosWebviewWindow) setURL(uri string) { + if uri != "" { + url, err := url.Parse(uri) + if err == nil && url.Scheme == "" && url.Host == "" { + // TODO handle this in a central location, the scheme and host might be platform dependant. + url.Scheme = "wails" + url.Host = "wails" + uri = url.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, + } + 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) 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) enableDevTools() { + C.windowEnableDevTools(w.nsWindow) +} + +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) setPosition(x, y int) { + C.windowSetPosition(w.nsWindow, C.int(x), C.int(y)) +} + +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 (w *macosWebviewWindow) run() { + for eventId := range w.parent.eventListeners { + w.on(eventId) + } + globalApplication.dispatchOnMainThread(func() { + w.nsWindow = C.windowNew(C.uint(w.parent.id), + C.int(w.parent.options.Width), + C.int(w.parent.options.Height), + C.bool(w.parent.options.EnableFraudulentWebsiteWarnings), + C.bool(w.parent.options.Frameless), + C.bool(w.parent.options.EnableDragAndDrop), + C.bool(w.parent.options.HideOnClose), + ) + w.setTitle(w.parent.options.Title) + w.setAlwaysOnTop(w.parent.options.AlwaysOnTop) + w.setResizable(!w.parent.options.DisableResize) + if w.parent.options.MinWidth != 0 || w.parent.options.MinHeight != 0 { + w.setMinSize(w.parent.options.MinWidth, w.parent.options.MinHeight) + } + if w.parent.options.MaxWidth != 0 || w.parent.options.MaxHeight != 0 { + w.setMaxSize(w.parent.options.MaxWidth, w.parent.options.MaxHeight) + } + //w.setZoom(w.parent.options.Zoom) + w.enableDevTools() + w.setBackgroundColour(w.parent.options.BackgroundColour) + + macOptions := w.parent.options.Mac + switch macOptions.Backdrop { + case MacBackdropTransparent: + C.windowSetTransparent(w.nsWindow) + C.webviewSetTransparent(w.nsWindow) + case MacBackdropTranslucent: + C.windowSetTranslucent(w.nsWindow) + C.webviewSetTransparent(w.nsWindow) + } + + 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)) + if titleBarOptions.UseToolbar { + C.windowSetUseToolbar(w.nsWindow, C.bool(titleBarOptions.UseToolbar), C.int(titleBarOptions.ToolbarStyle)) + } + 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() + + } + C.windowCenter(w.nsWindow) + + if w.parent.options.URL != "" { + w.setURL(w.parent.options.URL) + } + // We need to wait for the HTML to load before we can execute the javascript + w.parent.On(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEventContext) { + if w.parent.options.JS != "" { + w.execJS(w.parent.options.JS) + } + if w.parent.options.CSS != "" { + C.windowInjectCSS(w.nsWindow, C.CString(w.parent.options.CSS)) + } + }) + + w.parent.On(events.Mac.WindowWillClose, func(_ *WindowEventContext) { + globalApplication.deleteWindowByID(w.parent.id) + }) + + if w.parent.options.HTML != "" { + w.setHTML(w.parent.options.HTML) + } + if w.parent.options.Hidden == false { + C.windowShow(w.nsWindow) + } + }) +} + +func (w *macosWebviewWindow) setBackgroundColour(colour *RGBA) { + if colour == nil { + return + } + C.windowSetBackgroundColour(w.nsWindow, C.int(colour.Red), C.int(colour.Green), C.int(colour.Blue), C.int(colour.Alpha)) +} + +func (w *macosWebviewWindow) position() (int, int) { + var x, y C.int + var wg sync.WaitGroup + wg.Add(1) + go globalApplication.dispatchOnMainThread(func() { + C.windowGetPosition(w.nsWindow, &x, &y) + wg.Done() + }) + wg.Wait() + return int(x), int(y) +} + +func (w *macosWebviewWindow) destroy() { + 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) +} diff --git a/v3/pkg/application/webview_window_devtools.go b/v3/pkg/application/webview_window_devtools.go new file mode 100644 index 000000000..7dce43c52 --- /dev/null +++ b/v3/pkg/application/webview_window_devtools.go @@ -0,0 +1,38 @@ +//go:build darwin && !production + +package application + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa + +#import + +#include "webview_window.h" + +@interface _WKInspector : NSObject +- (void)show; +- (void)detach; +@end + +@interface WKWebView () +- (_WKInspector *)_inspector; +@end + +void showDevTools(void *window) { + // get main window + WebviewWindow* nsWindow = (WebviewWindow*)window; + dispatch_async(dispatch_get_main_queue(), ^{ + [nsWindow.webView._inspector show]; + }); +} + +*/ +import "C" +import "unsafe" + +func init() { + showDevTools = func(window unsafe.Pointer) { + C.showDevTools(window) + } +} diff --git a/v3/pkg/events/events.go b/v3/pkg/events/events.go new file mode 100644 index 000000000..f26f53b2e --- /dev/null +++ b/v3/pkg/events/events.go @@ -0,0 +1,262 @@ +package events + +type ApplicationEventType uint +type WindowEventType uint + +const ( + FilesDropped WindowEventType = iota +) + +var Mac = newMacEvents() + +type macEvents struct { + ApplicationDidBecomeActive ApplicationEventType + ApplicationDidChangeBackingProperties ApplicationEventType + ApplicationDidChangeEffectiveAppearance ApplicationEventType + ApplicationDidChangeIcon ApplicationEventType + ApplicationDidChangeOcclusionState ApplicationEventType + ApplicationDidChangeScreenParameters ApplicationEventType + ApplicationDidChangeStatusBarFrame ApplicationEventType + ApplicationDidChangeStatusBarOrientation ApplicationEventType + ApplicationDidFinishLaunching ApplicationEventType + ApplicationDidHide ApplicationEventType + ApplicationDidResignActive ApplicationEventType + ApplicationDidUnhide ApplicationEventType + ApplicationDidUpdate ApplicationEventType + ApplicationWillBecomeActive ApplicationEventType + ApplicationWillFinishLaunching ApplicationEventType + ApplicationWillHide ApplicationEventType + ApplicationWillResignActive ApplicationEventType + ApplicationWillTerminate ApplicationEventType + ApplicationWillUnhide ApplicationEventType + ApplicationWillUpdate ApplicationEventType + 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 + WindowDidChangeVisibility 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 + WindowDidUnfocus WindowEventType + WindowDidUpdate WindowEventType + WindowDidUpdateAlpha WindowEventType + WindowDidUpdateCollectionBehavior WindowEventType + WindowDidUpdateCollectionProperties WindowEventType + WindowDidUpdateShadow WindowEventType + WindowDidUpdateTitle WindowEventType + WindowDidUpdateToolbar WindowEventType + WindowDidUpdateVisibility 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 + MenuWillOpen ApplicationEventType + MenuDidOpen ApplicationEventType + MenuDidClose ApplicationEventType + MenuWillSendAction ApplicationEventType + MenuDidSendAction ApplicationEventType + MenuWillHighlightItem ApplicationEventType + MenuDidHighlightItem ApplicationEventType + MenuWillDisplayItem ApplicationEventType + MenuDidDisplayItem ApplicationEventType + MenuWillAddItem ApplicationEventType + MenuDidAddItem ApplicationEventType + MenuWillRemoveItem ApplicationEventType + MenuDidRemoveItem ApplicationEventType + MenuWillBeginTracking ApplicationEventType + MenuDidBeginTracking ApplicationEventType + MenuWillEndTracking ApplicationEventType + MenuDidEndTracking ApplicationEventType + MenuWillUpdate ApplicationEventType + MenuDidUpdate ApplicationEventType + MenuWillPopUp ApplicationEventType + MenuDidPopUp ApplicationEventType + MenuWillSendActionToItem ApplicationEventType + MenuDidSendActionToItem ApplicationEventType + WebViewDidStartProvisionalNavigation WindowEventType + WebViewDidReceiveServerRedirectForProvisionalNavigation WindowEventType + WebViewDidFinishNavigation WindowEventType + WebViewDidCommitNavigation WindowEventType + WindowFileDraggingEntered WindowEventType + WindowFileDraggingPerformed WindowEventType + WindowFileDraggingExited WindowEventType +} + +func newMacEvents() macEvents { + return macEvents{ + ApplicationDidBecomeActive: 1024, + ApplicationDidChangeBackingProperties: 1025, + ApplicationDidChangeEffectiveAppearance: 1026, + ApplicationDidChangeIcon: 1027, + ApplicationDidChangeOcclusionState: 1028, + ApplicationDidChangeScreenParameters: 1029, + ApplicationDidChangeStatusBarFrame: 1030, + ApplicationDidChangeStatusBarOrientation: 1031, + ApplicationDidFinishLaunching: 1032, + ApplicationDidHide: 1033, + ApplicationDidResignActive: 1034, + ApplicationDidUnhide: 1035, + ApplicationDidUpdate: 1036, + ApplicationWillBecomeActive: 1037, + ApplicationWillFinishLaunching: 1038, + ApplicationWillHide: 1039, + ApplicationWillResignActive: 1040, + ApplicationWillTerminate: 1041, + ApplicationWillUnhide: 1042, + ApplicationWillUpdate: 1043, + WindowDidBecomeKey: 1044, + WindowDidBecomeMain: 1045, + WindowDidBeginSheet: 1046, + WindowDidChangeAlpha: 1047, + WindowDidChangeBackingLocation: 1048, + WindowDidChangeBackingProperties: 1049, + WindowDidChangeCollectionBehavior: 1050, + WindowDidChangeEffectiveAppearance: 1051, + WindowDidChangeOcclusionState: 1052, + WindowDidChangeOrderingMode: 1053, + WindowDidChangeScreen: 1054, + WindowDidChangeScreenParameters: 1055, + WindowDidChangeScreenProfile: 1056, + WindowDidChangeScreenSpace: 1057, + WindowDidChangeScreenSpaceProperties: 1058, + WindowDidChangeSharingType: 1059, + WindowDidChangeSpace: 1060, + WindowDidChangeSpaceOrderingMode: 1061, + WindowDidChangeTitle: 1062, + WindowDidChangeToolbar: 1063, + WindowDidChangeVisibility: 1064, + WindowDidDeminiaturize: 1065, + WindowDidEndSheet: 1066, + WindowDidEnterFullScreen: 1067, + WindowDidEnterVersionBrowser: 1068, + WindowDidExitFullScreen: 1069, + WindowDidExitVersionBrowser: 1070, + WindowDidExpose: 1071, + WindowDidFocus: 1072, + WindowDidMiniaturize: 1073, + WindowDidMove: 1074, + WindowDidOrderOffScreen: 1075, + WindowDidOrderOnScreen: 1076, + WindowDidResignKey: 1077, + WindowDidResignMain: 1078, + WindowDidResize: 1079, + WindowDidUnfocus: 1080, + WindowDidUpdate: 1081, + WindowDidUpdateAlpha: 1082, + WindowDidUpdateCollectionBehavior: 1083, + WindowDidUpdateCollectionProperties: 1084, + WindowDidUpdateShadow: 1085, + WindowDidUpdateTitle: 1086, + WindowDidUpdateToolbar: 1087, + WindowDidUpdateVisibility: 1088, + WindowWillBecomeKey: 1089, + WindowWillBecomeMain: 1090, + WindowWillBeginSheet: 1091, + WindowWillChangeOrderingMode: 1092, + WindowWillClose: 1093, + WindowWillDeminiaturize: 1094, + WindowWillEnterFullScreen: 1095, + WindowWillEnterVersionBrowser: 1096, + WindowWillExitFullScreen: 1097, + WindowWillExitVersionBrowser: 1098, + WindowWillFocus: 1099, + WindowWillMiniaturize: 1100, + WindowWillMove: 1101, + WindowWillOrderOffScreen: 1102, + WindowWillOrderOnScreen: 1103, + WindowWillResignMain: 1104, + WindowWillResize: 1105, + WindowWillUnfocus: 1106, + WindowWillUpdate: 1107, + WindowWillUpdateAlpha: 1108, + WindowWillUpdateCollectionBehavior: 1109, + WindowWillUpdateCollectionProperties: 1110, + WindowWillUpdateShadow: 1111, + WindowWillUpdateTitle: 1112, + WindowWillUpdateToolbar: 1113, + WindowWillUpdateVisibility: 1114, + WindowWillUseStandardFrame: 1115, + MenuWillOpen: 1116, + MenuDidOpen: 1117, + MenuDidClose: 1118, + MenuWillSendAction: 1119, + MenuDidSendAction: 1120, + MenuWillHighlightItem: 1121, + MenuDidHighlightItem: 1122, + MenuWillDisplayItem: 1123, + MenuDidDisplayItem: 1124, + MenuWillAddItem: 1125, + MenuDidAddItem: 1126, + MenuWillRemoveItem: 1127, + MenuDidRemoveItem: 1128, + MenuWillBeginTracking: 1129, + MenuDidBeginTracking: 1130, + MenuWillEndTracking: 1131, + MenuDidEndTracking: 1132, + MenuWillUpdate: 1133, + MenuDidUpdate: 1134, + MenuWillPopUp: 1135, + MenuDidPopUp: 1136, + MenuWillSendActionToItem: 1137, + MenuDidSendActionToItem: 1138, + WebViewDidStartProvisionalNavigation: 1139, + WebViewDidReceiveServerRedirectForProvisionalNavigation: 1140, + WebViewDidFinishNavigation: 1141, + WebViewDidCommitNavigation: 1142, + WindowFileDraggingEntered: 1143, + WindowFileDraggingPerformed: 1144, + WindowFileDraggingExited: 1145, + } +} diff --git a/v3/pkg/events/events.h b/v3/pkg/events/events.h new file mode 100644 index 000000000..67cbdbef5 --- /dev/null +++ b/v3/pkg/events/events.h @@ -0,0 +1,135 @@ +//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int); +extern void processWindowEvent(unsigned int, unsigned int); + +#define EventApplicationDidBecomeActive 1024 +#define EventApplicationDidChangeBackingProperties 1025 +#define EventApplicationDidChangeEffectiveAppearance 1026 +#define EventApplicationDidChangeIcon 1027 +#define EventApplicationDidChangeOcclusionState 1028 +#define EventApplicationDidChangeScreenParameters 1029 +#define EventApplicationDidChangeStatusBarFrame 1030 +#define EventApplicationDidChangeStatusBarOrientation 1031 +#define EventApplicationDidFinishLaunching 1032 +#define EventApplicationDidHide 1033 +#define EventApplicationDidResignActive 1034 +#define EventApplicationDidUnhide 1035 +#define EventApplicationDidUpdate 1036 +#define EventApplicationWillBecomeActive 1037 +#define EventApplicationWillFinishLaunching 1038 +#define EventApplicationWillHide 1039 +#define EventApplicationWillResignActive 1040 +#define EventApplicationWillTerminate 1041 +#define EventApplicationWillUnhide 1042 +#define EventApplicationWillUpdate 1043 +#define EventWindowDidBecomeKey 1044 +#define EventWindowDidBecomeMain 1045 +#define EventWindowDidBeginSheet 1046 +#define EventWindowDidChangeAlpha 1047 +#define EventWindowDidChangeBackingLocation 1048 +#define EventWindowDidChangeBackingProperties 1049 +#define EventWindowDidChangeCollectionBehavior 1050 +#define EventWindowDidChangeEffectiveAppearance 1051 +#define EventWindowDidChangeOcclusionState 1052 +#define EventWindowDidChangeOrderingMode 1053 +#define EventWindowDidChangeScreen 1054 +#define EventWindowDidChangeScreenParameters 1055 +#define EventWindowDidChangeScreenProfile 1056 +#define EventWindowDidChangeScreenSpace 1057 +#define EventWindowDidChangeScreenSpaceProperties 1058 +#define EventWindowDidChangeSharingType 1059 +#define EventWindowDidChangeSpace 1060 +#define EventWindowDidChangeSpaceOrderingMode 1061 +#define EventWindowDidChangeTitle 1062 +#define EventWindowDidChangeToolbar 1063 +#define EventWindowDidChangeVisibility 1064 +#define EventWindowDidDeminiaturize 1065 +#define EventWindowDidEndSheet 1066 +#define EventWindowDidEnterFullScreen 1067 +#define EventWindowDidEnterVersionBrowser 1068 +#define EventWindowDidExitFullScreen 1069 +#define EventWindowDidExitVersionBrowser 1070 +#define EventWindowDidExpose 1071 +#define EventWindowDidFocus 1072 +#define EventWindowDidMiniaturize 1073 +#define EventWindowDidMove 1074 +#define EventWindowDidOrderOffScreen 1075 +#define EventWindowDidOrderOnScreen 1076 +#define EventWindowDidResignKey 1077 +#define EventWindowDidResignMain 1078 +#define EventWindowDidResize 1079 +#define EventWindowDidUnfocus 1080 +#define EventWindowDidUpdate 1081 +#define EventWindowDidUpdateAlpha 1082 +#define EventWindowDidUpdateCollectionBehavior 1083 +#define EventWindowDidUpdateCollectionProperties 1084 +#define EventWindowDidUpdateShadow 1085 +#define EventWindowDidUpdateTitle 1086 +#define EventWindowDidUpdateToolbar 1087 +#define EventWindowDidUpdateVisibility 1088 +#define EventWindowWillBecomeKey 1089 +#define EventWindowWillBecomeMain 1090 +#define EventWindowWillBeginSheet 1091 +#define EventWindowWillChangeOrderingMode 1092 +#define EventWindowWillClose 1093 +#define EventWindowWillDeminiaturize 1094 +#define EventWindowWillEnterFullScreen 1095 +#define EventWindowWillEnterVersionBrowser 1096 +#define EventWindowWillExitFullScreen 1097 +#define EventWindowWillExitVersionBrowser 1098 +#define EventWindowWillFocus 1099 +#define EventWindowWillMiniaturize 1100 +#define EventWindowWillMove 1101 +#define EventWindowWillOrderOffScreen 1102 +#define EventWindowWillOrderOnScreen 1103 +#define EventWindowWillResignMain 1104 +#define EventWindowWillResize 1105 +#define EventWindowWillUnfocus 1106 +#define EventWindowWillUpdate 1107 +#define EventWindowWillUpdateAlpha 1108 +#define EventWindowWillUpdateCollectionBehavior 1109 +#define EventWindowWillUpdateCollectionProperties 1110 +#define EventWindowWillUpdateShadow 1111 +#define EventWindowWillUpdateTitle 1112 +#define EventWindowWillUpdateToolbar 1113 +#define EventWindowWillUpdateVisibility 1114 +#define EventWindowWillUseStandardFrame 1115 +#define EventMenuWillOpen 1116 +#define EventMenuDidOpen 1117 +#define EventMenuDidClose 1118 +#define EventMenuWillSendAction 1119 +#define EventMenuDidSendAction 1120 +#define EventMenuWillHighlightItem 1121 +#define EventMenuDidHighlightItem 1122 +#define EventMenuWillDisplayItem 1123 +#define EventMenuDidDisplayItem 1124 +#define EventMenuWillAddItem 1125 +#define EventMenuDidAddItem 1126 +#define EventMenuWillRemoveItem 1127 +#define EventMenuDidRemoveItem 1128 +#define EventMenuWillBeginTracking 1129 +#define EventMenuDidBeginTracking 1130 +#define EventMenuWillEndTracking 1131 +#define EventMenuDidEndTracking 1132 +#define EventMenuWillUpdate 1133 +#define EventMenuDidUpdate 1134 +#define EventMenuWillPopUp 1135 +#define EventMenuDidPopUp 1136 +#define EventMenuWillSendActionToItem 1137 +#define EventMenuDidSendActionToItem 1138 +#define EventWebViewDidStartProvisionalNavigation 1139 +#define EventWebViewDidReceiveServerRedirectForProvisionalNavigation 1140 +#define EventWebViewDidFinishNavigation 1141 +#define EventWebViewDidCommitNavigation 1142 +#define EventWindowFileDraggingEntered 1143 +#define EventWindowFileDraggingPerformed 1144 +#define EventWindowFileDraggingExited 1145 + +#define MAX_EVENTS 1146 + + +#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..dbbe98db4 --- /dev/null +++ b/v3/pkg/events/events.txt @@ -0,0 +1,123 @@ +mac:ApplicationDidBecomeActive +mac:ApplicationDidChangeBackingProperties +mac:ApplicationDidChangeEffectiveAppearance +mac:ApplicationDidChangeIcon +mac:ApplicationDidChangeOcclusionState +mac:ApplicationDidChangeScreenParameters +mac:ApplicationDidChangeStatusBarFrame +mac:ApplicationDidChangeStatusBarOrientation +mac:ApplicationDidFinishLaunching +mac:ApplicationDidHide +mac:ApplicationDidResignActive +mac:ApplicationDidUnhide +mac:ApplicationDidUpdate +mac:ApplicationWillBecomeActive +mac:ApplicationWillFinishLaunching +mac:ApplicationWillHide +mac:ApplicationWillResignActive +mac:ApplicationWillTerminate +mac:ApplicationWillUnhide +mac:ApplicationWillUpdate +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:WindowDidChangeVisibility +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:WindowDidUnfocus +mac:WindowDidUpdate +mac:WindowDidUpdateAlpha +mac:WindowDidUpdateCollectionBehavior +mac:WindowDidUpdateCollectionProperties +mac:WindowDidUpdateShadow +mac:WindowDidUpdateTitle +mac:WindowDidUpdateToolbar +mac:WindowDidUpdateVisibility +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:MenuWillOpen +mac:MenuDidOpen +mac:MenuDidClose +mac:MenuWillSendAction +mac:MenuDidSendAction +mac:MenuWillHighlightItem +mac:MenuDidHighlightItem +mac:MenuWillDisplayItem +mac:MenuDidDisplayItem +mac:MenuWillAddItem +mac:MenuDidAddItem +mac:MenuWillRemoveItem +mac:MenuDidRemoveItem +mac:MenuWillBeginTracking +mac:MenuDidBeginTracking +mac:MenuWillEndTracking +mac:MenuDidEndTracking +mac:MenuWillUpdate +mac:MenuDidUpdate +mac:MenuWillPopUp +mac:MenuDidPopUp +mac:MenuWillSendActionToItem +mac:MenuDidSendActionToItem +mac:WebViewDidStartProvisionalNavigation +mac:WebViewDidReceiveServerRedirectForProvisionalNavigation +mac:WebViewDidFinishNavigation +mac:WebViewDidCommitNavigation +mac:WindowFileDraggingEntered +mac:WindowFileDraggingPerformed +mac:WindowFileDraggingExited + diff --git a/v3/pkg/events/events_darwin.go b/v3/pkg/events/events_darwin.go new file mode 100644 index 000000000..12a7c3af4 --- /dev/null +++ b/v3/pkg/events/events_darwin.go @@ -0,0 +1,26 @@ +//go:build darwin + +package events + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Cocoa -mmacosx-version-min=10.13 + +#include "events.h" +#include +#include + +#include "events.h" + +bool hasListener[MAX_EVENTS] = {false}; + +void registerListener(unsigned int event) { + hasListener[event] = true; +} + +bool hasListeners(unsigned int event) { + return hasListener[event]; +} + +*/ +import "C" diff --git a/v3/pkg/logger/log.go b/v3/pkg/logger/log.go new file mode 100644 index 000000000..75024d49d --- /dev/null +++ b/v3/pkg/logger/log.go @@ -0,0 +1,35 @@ +package logger + +import ( + "fmt" +) + +type Logger struct { + output []Output +} + +func New(outputs ...Output) *Logger { + result := &Logger{} + if outputs != nil { + result.output = outputs + } + return result +} + +func (l *Logger) AddOutput(output Output) { + l.output = append(l.output, output) +} + +func (l *Logger) Log(message *Message) { + for _, o := range l.output { + go o.Log(message) + } +} + +func (l *Logger) Flush() { + for _, o := range l.output { + if err := o.Flush(); err != nil { + fmt.Printf("Error flushing '%s' Logger: %s\n", o.Name(), err.Error()) + } + } +} diff --git a/v3/pkg/logger/log_console.go b/v3/pkg/logger/log_console.go new file mode 100644 index 000000000..abaafcb36 --- /dev/null +++ b/v3/pkg/logger/log_console.go @@ -0,0 +1,27 @@ +package logger + +import "fmt" + +type Console struct{} + +func (l *Console) Name() string { + return "Console" +} + +func (l *Console) Log(message *Message) { + msg := fmt.Sprintf(message.Message+"\n", message.Data...) + level := "" + if message.Level != "" { + level = fmt.Sprintf("[%s] ", message.Level) + } + sender := "" + if message.Sender != "" { + sender = fmt.Sprintf("%s: ", message.Sender) + } + + fmt.Printf("%s%s%s", level, sender, msg) +} + +func (l *Console) Flush() error { + return nil +} diff --git a/v3/pkg/logger/message.go b/v3/pkg/logger/message.go new file mode 100644 index 000000000..a60ac8965 --- /dev/null +++ b/v3/pkg/logger/message.go @@ -0,0 +1,11 @@ +package logger + +import "time" + +type Message struct { + Level string `json:"log"` + Message string `json:"message"` + Data []any `json:"data,omitempty"` + Sender string `json:"-"` + Time time.Time `json:"-"` +} diff --git a/v3/pkg/logger/output.go b/v3/pkg/logger/output.go new file mode 100644 index 000000000..65b359c70 --- /dev/null +++ b/v3/pkg/logger/output.go @@ -0,0 +1,7 @@ +package logger + +type Output interface { + Name() string + Log(message *Message) + Flush() error +} 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/plugins/browser/README.md b/v3/plugins/browser/README.md new file mode 100644 index 000000000..11f930984 --- /dev/null +++ b/v3/plugins/browser/README.md @@ -0,0 +1,53 @@ +# Browser Plugin + +This plugin provides the ability to open a URL or local file in the default +browser. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/browser" +) + +func main() { + browserPlugin := browser.NewPlugin() + app := application.New(application.Options{ + // ... + Plugins: map[string]application.Plugin{ + "browser": browserPlugin, + }, + }) +``` + +## Usage + +### Go + +You can call the methods exported by the plugin directly: + +```go + browserPlugin.OpenURL("https://www.google.com") + // or + browserPlugin.OpenFile("/path/to/file") +``` + +### Javascript + +You can call the methods from the frontend using the Plugin method: + +```js +wails.Plugin("browser", "OpenURL", "https://www.google.com"); +// or +wails.Plugin("browser", "OpenFile", "/path/to/file"); +``` + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails +[Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/browser/plugin.go b/v3/plugins/browser/plugin.go new file mode 100644 index 000000000..85925cd70 --- /dev/null +++ b/v3/plugins/browser/plugin.go @@ -0,0 +1,48 @@ +package browser + +import ( + "github.com/pkg/browser" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Plugin struct{} + +func NewPlugin() *Plugin { + return &Plugin{} +} + +func (p *Plugin) Shutdown() {} + +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/browser" +} + +func (p *Plugin) Init(_ *application.App) error { + return nil +} + +func (p *Plugin) CallableByJS() []string { + return []string{ + "OpenURL", + "OpenFile", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- + +func (p *Plugin) OpenURL(url string) error { + return browser.OpenURL(url) +} + +func (p *Plugin) OpenFile(path string) error { + return browser.OpenFile(path) +} diff --git a/v3/plugins/browser/plugin.js b/v3/plugins/browser/plugin.js new file mode 100644 index 000000000..d2f304fa3 --- /dev/null +++ b/v3/plugins/browser/plugin.js @@ -0,0 +1,25 @@ + +/** + * Opens the given URL in the default browser. + * @param url {string} - The URL to open. + * @returns {Promise} + */ +function OpenURL(url) { + return wails.Plugin("browser", "OpenURL", url); +} + +/** + * Opens the given filename in the default browser. + * @param filename {string} - The file to open. + * @returns {Promise} + */ +function OpenFile(filename) { + return wails.Plugin("browser", "OpenFile", filename); +} + +export default { + Browser: { + OpenURL, + OpenFile, + } +}; diff --git a/v3/plugins/browser/plugin.toml b/v3/plugins/browser/plugin.toml new file mode 100644 index 000000000..47e547389 --- /dev/null +++ b/v3/plugins/browser/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "browser" plugin. + +Name = "browser" +Description = "Opens URLs and files in the default browser." +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +Repository = "https://github.com/wailsapp/wails/v3/plugins/browser" +License = "MIT" + + diff --git a/v3/plugins/kvstore/README.md b/v3/plugins/kvstore/README.md new file mode 100644 index 000000000..39bb93134 --- /dev/null +++ b/v3/plugins/kvstore/README.md @@ -0,0 +1,65 @@ +# KVStore Plugin + +This plugin provides a simple key/value store for your Wails applications. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/plugins/kvstore" +) + +func main() { + kvstorePlugin := kvstore.NewPlugin(&kvstore.Config{ + Filename: "myapp.db", + }) + app := application.New(application.Options{ + // ... + Plugins: map[string]application.Plugin{ + "kvstore": kvstorePlugin, + }, + }) + +``` + +## Usage + +### Go + +You can call the methods exported by the plugin directly: + +```go + err := kvstore.Set("url", "https://www.google.com") + if err != nil { + // handle error + } + url := kvstore.Get("url").(string) + + // If you have not enables AutoSave, you will need to call Save() to persist the changes + err = kvstore.Save() + if err != nil { + // handle error + } +``` + +### Javascript + +You can call the methods from the frontend using the Plugin method: + +```js +wails.Plugin("kvstore", "Set", "url", "https://www.google.com"); +wails.Plugin("kvstore", "Get", "url").then((url) => {}); + +// or +wails.Plugin("browser", "OpenFile", "/path/to/file"); +``` + +## Support + +If you find a bug in this plugin, please raise a ticket on the Wails +[Issue Tracker](https://github.com/wailsapp/wails/issues). diff --git a/v3/plugins/kvstore/kvstore.go b/v3/plugins/kvstore/kvstore.go new file mode 100644 index 000000000..2fb9d49f2 --- /dev/null +++ b/v3/plugins/kvstore/kvstore.go @@ -0,0 +1,171 @@ +package kvstore + +import ( + "encoding/json" + "io" + "os" + "sync" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/logger" +) + +type KeyValueStore struct { + config *Config + filename string + data map[string]any + unsaved bool + lock sync.RWMutex + app *application.App +} + +func (kvs *KeyValueStore) InjectJS() string { + return "" +} + +type Config struct { + Filename string + AutoSave bool +} + +type Plugin struct{} + +func NewPlugin(config *Config) *KeyValueStore { + return &KeyValueStore{ + config: config, + data: make(map[string]any), + } +} + +// Shutdown will save the store to disk if there are unsaved changes. +func (kvs *KeyValueStore) Shutdown() { + if kvs.unsaved { + err := kvs.Save() + if err != nil { + println("Error saving store: " + err.Error()) + } + } +} + +// Name returns the name of the plugin. +func (kvs *KeyValueStore) Name() string { + return "github.com/wailsapp/wails/v3/plugins/kvstore" +} + +// Init is called when the plugin is loaded. It is passed the application.App +// instance. This is where you should do any setup. +func (kvs *KeyValueStore) Init(app *application.App) error { + kvs.app = app + err := kvs.open(kvs.config.Filename) + if err != nil { + return err + } + + return nil +} + +func (kvs *KeyValueStore) CallableByJS() []string { + return []string{ + "Set", + "Get", + "Save", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} + +// ---------------- Plugin Methods ---------------- + +func (kvs *KeyValueStore) open(filename string) (err error) { + kvs.filename = filename + kvs.data = make(map[string]any) + + file, err := os.Open(filename) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer func() { + err2 := file.Close() + if err2 != nil { + if err == nil { + err = err2 + } else { + kvs.app.Log(&logger.Message{ + Level: "error", + Message: err.Error(), + }) + } + } + }() + + bytes, err := io.ReadAll(file) + if err != nil { + return err + } + + if len(bytes) > 0 { + if err := json.Unmarshal(bytes, &kvs.data); err != nil { + return err + } + } + + return nil +} + +// Save saves the store to disk +func (kvs *KeyValueStore) Save() error { + kvs.lock.Lock() + defer kvs.lock.Unlock() + + bytes, err := json.Marshal(kvs.data) + if err != nil { + return err + } + + err = os.WriteFile(kvs.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 *KeyValueStore) 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 *KeyValueStore) Set(key string, value any) error { + kvs.lock.Lock() + if value == nil { + delete(kvs.data, key) + } else { + kvs.data[key] = value + } + kvs.lock.Unlock() + if kvs.config.AutoSave { + err := kvs.Save() + if err != nil { + return err + } + kvs.unsaved = false + } else { + kvs.unsaved = true + } + return nil +} diff --git a/v3/plugins/kvstore/plugin.js b/v3/plugins/kvstore/plugin.js new file mode 100644 index 000000000..1b2faec49 --- /dev/null +++ b/v3/plugins/kvstore/plugin.js @@ -0,0 +1,31 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * Get the value of a key. + * @param key {string} - The store key. + * @returns {Promise} - The value of the key. + */ +export function Get(key) { + return wails.Plugin("kvstore", "Get", key); +} + +/** + * Set the value of a key. + @param key {string} - The store key. + @param value {any} - The value to set. + * @returns {Promise} + */ +export function Set(key, value) { + return wails.Plugin("kvstore", "Set", key, value); +} + + +/** + * Save the database to disk. + * @returns {Promise} + */ +export function Save() { + return wails.Plugin("kvstore", "Save"); +} diff --git a/v3/plugins/kvstore/plugin.toml b/v3/plugins/kvstore/plugin.toml new file mode 100644 index 000000000..cebd76c8a --- /dev/null +++ b/v3/plugins/kvstore/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "kvstore" plugin. + +Name = "kvstore" +Description = "A Simple Key/Value Store for Wails Applications" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +Repository = "https://github.com/wailsapp/wails/v3/plugins/kvstore" +License = "MIT" + + diff --git a/v3/plugins/log/README.md b/v3/plugins/log/README.md new file mode 100644 index 000000000..529bed8fb --- /dev/null +++ b/v3/plugins/log/README.md @@ -0,0 +1,39 @@ +# log Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "log": log.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js +wails.Plugin("log", "All", "hello world").then((result) => console.log(result)); +``` + +This method returns a struct with the following fields: + +```typescript +interface Hashes { + MD5: string; + SHA1: string; + SHA256: string; +} +``` + +A TypeScript definition file is provided for this interface. + +## Support + +If you find a bug in this plugin, please raise a ticket +[here](https://github.com/plugin/repository). Please do not contact the Wails +team for support. diff --git a/v3/plugins/log/plugin.go b/v3/plugins/log/plugin.go new file mode 100644 index 000000000..704c842f9 --- /dev/null +++ b/v3/plugins/log/plugin.go @@ -0,0 +1,152 @@ +package log + +import ( + _ "embed" + "fmt" + "io" + "os" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed plugin.js +var pluginJS string + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type LogLevel = float64 + +const ( + Trace LogLevel = iota + 1 + Debug + Info + Warning + Error + Fatal +) + +type Config struct { + // Where the logs are written to. Defaults to os.Stderr + // If you want to write to a file, use os.OpenFile() + // e.g. os.OpenFile("mylog.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + // Closes the writer when the app shuts down + Writer io.WriteCloser + + // The initial log level. Defaults to Debug + Level LogLevel + + // Disables the log level prefixes + DisablePrefix bool + + // Handles errors that occur when writing to the log + ErrorHandler func(err error) +} + +type Plugin struct { + config *Config + app *application.App + level LogLevel +} + +func NewPluginWithConfig(config *Config) *Plugin { + if config.Level == 0 { + config.Level = Debug + } + if config.Writer == nil { + config.Writer = os.Stderr + } + return &Plugin{ + config: config, + level: config.Level, + } +} + +func NewPlugin() *Plugin { + return NewPluginWithConfig(&Config{}) +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() { + p.config.Writer.Close() +} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/log" +} + +func (p *Plugin) Init(app *application.App) error { + p.app = app + return nil +} + +// CallableByJS returns a list of methods that can be called from the frontend +func (p *Plugin) CallableByJS() []string { + return []string{ + "Trace", + "Debug", + "Info", + "Warning", + "Error", + "Fatal", + "SetLevel", + } +} + +func (p *Plugin) InjectJS() string { + return pluginJS +} + +// ---------------- Plugin Methods ---------------- +// Plugin 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 *Plugin) write(prefix string, level LogLevel, message string, args ...any) { + if level >= p.level { + if !p.config.DisablePrefix { + message = prefix + " " + message + } + _, err := fmt.Fprintln(p.config.Writer, fmt.Sprintf(message, args...)) + if err != nil && p.config.ErrorHandler != nil { + p.config.ErrorHandler(err) + } + } +} + +func (p *Plugin) Trace(message string, args ...any) { + p.write("[Trace]", Trace, message, args...) +} + +func (p *Plugin) Debug(message string, args ...any) { + p.write("[Debug]", Debug, message, args...) +} + +func (p *Plugin) Info(message string, args ...any) { + p.write("[Info]", Info, message, args...) +} + +func (p *Plugin) Warning(message string, args ...any) { + p.write("[Warning]", Warning, message, args...) +} + +func (p *Plugin) Error(message string, args ...any) { + p.write("[Error]", Error, message, args...) +} + +func (p *Plugin) Fatal(message string, args ...any) { + p.write("[FATAL]", Fatal, message, args...) +} + +func (p *Plugin) SetLevel(newLevel LogLevel) { + if newLevel == 0 { + newLevel = Debug + } + p.level = newLevel +} diff --git a/v3/plugins/log/plugin.js b/v3/plugins/log/plugin.js new file mode 100644 index 000000000..16811d5d1 --- /dev/null +++ b/v3/plugins/log/plugin.js @@ -0,0 +1,98 @@ +// plugin.js +// This file should contain helper functions for the that can be used by the frontend. +// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions. + +/** + * Log at the Trace level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Trace(input, ...args) { + return wails.Plugin("log", "Trace", input, ...args); +} + +/** + * Log at the Debug level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ + +function Debug(input, ...args) { + return wails.Plugin("log", "Debug", input, ...args); +} + +/** + * Log at the Info level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Info(input, ...args) { + return wails.Plugin("log", "Info", input, ...args); +} + +/** + * Log at the Warning level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Warning(input, ...args) { + return wails.Plugin("log", "Warning", input, ...args); +} + +/** + * Log at the Error level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Error(input, ...args) { + return wails.Plugin("log", "Error", input, ...args); +} + +/** + * Log at the Fatal level. + * @param input {string} - The message in printf format. + * @param args {...any} - The arguments for the log message. + * @returns {Promise} + */ +function Fatal(input, ...args) { + return wails.Plugin("log", "Fatal", input, ...args); +} + +/** + * SetLevel sets the logging level + * @param level {Level} The log level to set + * @returns {Promise} + */ +function SetLevel(level) { + return wails.Plugin("log", "SetLevel", level); +} + +/** + * Log Level. + * @readonly + * @enum {number} + */ +let Level = { + Trace: 1, + Debug: 2, + Info: 3, + Warning: 4, + Error: 5, + Fatal: 6, +}; + +window.Logger = { + Trace, + Debug, + Info, + Warning, + Error, + Fatal, + SetLevel, + Level, +} diff --git a/v3/plugins/log/plugin.toml b/v3/plugins/log/plugin.toml new file mode 100644 index 000000000..0315468df --- /dev/null +++ b/v3/plugins/log/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "log" plugin. + +Name = "log" +Description = "A basic logger" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +Repository = "https://github.com/wailsapp/wails/v3/plugins/log" +License = "MIT" + + diff --git a/v3/plugins/single_instance/README.md b/v3/plugins/single_instance/README.md new file mode 100644 index 000000000..91d355182 --- /dev/null +++ b/v3/plugins/single_instance/README.md @@ -0,0 +1,35 @@ +# single-instance Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "single_instance": single_instance.NewPlugin(&single_instance.Config{ + // When true, the original app will be activated when a second instance is launched + ActivateAppOnSubsequentLaunch: true, + } + }, +``` + +## Usage + +This plugin prevents the launch of multiple copies of your application. If you +set `ActivateAppOnSubsequentLaunch` to true the original app will be activated +when a second instance is launched. + +## Support + +If you find a bug in this plugin, please raise a ticket +[here](https://github.com/plugin/repository). Please do not contact the Wails +team for support. + +## Credit + +This plugin contains modified code from the awesome +[go-singleinstance](https://github.com/allan-simon/go-singleinstance) module (c) +2015 Allan Simon. Original license file has been renamed +`go-singleinstance.LICENSE` and is available [here](./singleinstance_LICENSE). diff --git a/v3/plugins/single_instance/go-singleinstance.LICENSE b/v3/plugins/single_instance/go-singleinstance.LICENSE new file mode 100644 index 000000000..67bb9ea50 --- /dev/null +++ b/v3/plugins/single_instance/go-singleinstance.LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Allan Simon + +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/plugins/single_instance/lock.go b/v3/plugins/single_instance/lock.go new file mode 100644 index 000000000..ba2758769 --- /dev/null +++ b/v3/plugins/single_instance/lock.go @@ -0,0 +1,16 @@ +package single_instance + +import ( + "os" + "strconv" +) + +func GetLockFilePid(filename string) (pid int, err error) { + contents, err := os.ReadFile(filename) + if err != nil { + return + } + + pid, err = strconv.Atoi(string(contents)) + return +} diff --git a/v3/plugins/single_instance/lock_posix.go b/v3/plugins/single_instance/lock_posix.go new file mode 100644 index 000000000..06139d52b --- /dev/null +++ b/v3/plugins/single_instance/lock_posix.go @@ -0,0 +1,38 @@ +//go:build !windows + +package single_instance + +import ( + "os" + "strconv" + "syscall" +) + +// 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, PID int) (*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 + } + + // Write PID to lock file + contents := strconv.Itoa(PID) + if err := file.Truncate(0); err != nil { + file.Close() + return nil, err + } + if _, err := file.WriteString(contents); err != nil { + file.Close() + return nil, err + } + + return file, nil +} diff --git a/v3/plugins/single_instance/lock_windows.go b/v3/plugins/single_instance/lock_windows.go new file mode 100644 index 000000000..67aabe685 --- /dev/null +++ b/v3/plugins/single_instance/lock_windows.go @@ -0,0 +1,35 @@ +//go:build windows + +package single_instance + +import ( + "os" + "strconv" +) + +// 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, PID int) (*os.File, error) { + if _, err := os.Stat(filename); err == nil { + // If the file exists, we first try to remove it + if err = os.Remove(filename); err != nil { + return nil, err + } + } else if !os.IsNotExist(err) { + return nil, err + } + + file, err := os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if err != nil { + return nil, err + } + + // Write PID to lock file + _, err = file.WriteString(strconv.Itoa(PID)) + if err != nil { + return nil, err + } + + return file, nil +} diff --git a/v3/plugins/single_instance/plugin.go b/v3/plugins/single_instance/plugin.go new file mode 100644 index 000000000..af52ea133 --- /dev/null +++ b/v3/plugins/single_instance/plugin.go @@ -0,0 +1,83 @@ +package single_instance + +import ( + "fmt" + "github.com/wailsapp/wails/v3/pkg/application" + "os" + "path/filepath" +) + +type Config struct { + // Add any configuration options here + LockFileName string + LockFilePath string + ActivateAppOnSubsequentLaunch bool +} + +type Plugin struct { + config *Config + app *application.App + lockfile *os.File +} + +func (p *Plugin) CallableByJS() []string { + return []string{} +} + +func (p *Plugin) InjectJS() string { + return "" +} + +func NewPlugin(config *Config) *Plugin { + if config.LockFilePath == "" { + // Use the system default temp directory + config.LockFilePath = os.TempDir() + } + if config.LockFileName == "" { + // Use the executable name + config.LockFileName = filepath.Base(os.Args[0]) + ".lock" + } + return &Plugin{ + config: config, + } +} + +// Shutdown is called when the app is shutting down +func (p *Plugin) Shutdown() { + p.lockfile.Close() +} + +// Name returns the name of the plugin. +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/single-instance" +} + +// Init 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. +func (p *Plugin) Init(app *application.App) error { + p.app = app + + var err error + lockfileName := p.config.LockFilePath + "/" + p.config.LockFileName + p.lockfile, err = CreateLockFile(lockfileName, p.app.GetPID()) + if err != nil { + if p.config.ActivateAppOnSubsequentLaunch { + pid, err := GetLockFilePid(lockfileName) + if err != nil { + return err + } + err = p.activeInstance(pid) + if err != nil { + return err + } + } + return fmt.Errorf("another instance of this application is already running") + } + return nil +} + +// Exported returns a list of exported methods that can be called from the frontend +func (p *Plugin) Exported() []string { + return []string{} +} diff --git a/v3/plugins/single_instance/plugin.toml b/v3/plugins/single_instance/plugin.toml new file mode 100644 index 000000000..6682e4493 --- /dev/null +++ b/v3/plugins/single_instance/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "single-instance" plugin. + +Name = "single-instance" +Description = "Allows only a single instance of your application to run" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io" +Repository = "https://github.com/wailsapp/wails/v3/plugins/single-instance +License = "MIT" + + diff --git a/v3/plugins/single_instance/plugin_darwin.go b/v3/plugins/single_instance/plugin_darwin.go new file mode 100644 index 000000000..c4f47f715 --- /dev/null +++ b/v3/plugins/single_instance/plugin_darwin.go @@ -0,0 +1,24 @@ +//go:build darwin + +package single_instance + +/* +#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c +#cgo LDFLAGS: -framework Cocoa -framework AppKit -mmacosx-version-min=10.13 + +#import + +void activateApplicationWithProcessID(int pid) { + NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + if (app != nil) { + [app unhide]; + [app activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; + } +} +*/ +import "C" + +func (p *Plugin) activeInstance(pid int) error { + C.activateApplicationWithProcessID(C.int(pid)) + return nil +} diff --git a/v3/plugins/sqlite/plugin.go b/v3/plugins/sqlite/plugin.go new file mode 100644 index 000000000..95c5ef52a --- /dev/null +++ b/v3/plugins/sqlite/plugin.go @@ -0,0 +1,173 @@ +package sqlite + +import ( + "database/sql" + _ "embed" + "errors" + "fmt" + "github.com/wailsapp/wails/v3/pkg/application" + _ "modernc.org/sqlite" + "strings" +) + +//go:embed sqlite_close.js +var closejs string + +//go:embed sqlite_open.js +var openjs string + +//go:embed sqlite_execute_select.js +var executeselectjs string + +// ---------------- Plugin Setup ---------------- +// This is the main plugin struct. It can be named anything you like. +// It must implement the application.Plugin interface. +// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops. + +type Config struct { + // Add any configuration options here + CanOpenFromJS bool + CanCloseFromJS bool + DBFile string +} + +type Plugin struct { + config *Config + app *application.App + conn *sql.DB + callableMethods []string + js string +} + +func NewPlugin(config *Config) *Plugin { + return &Plugin{ + config: config, + } +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() { + if p.conn != nil { + p.conn.Close() + } +} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/sqlite" +} + +// Init 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. +func (p *Plugin) Init(app *application.App) error { + p.app = app + p.callableMethods = []string{"Execute", "Select"} + p.js = executeselectjs + if p.config.CanOpenFromJS { + p.callableMethods = append(p.callableMethods, "Open") + p.js += openjs + } + if p.config.CanCloseFromJS { + p.callableMethods = append(p.callableMethods, "Close") + p.js += closejs + } + if p.config.DBFile == "" { + return errors.New(`no database file specified. Please set DBFile in the config to either a filename or use ":memory:" to use an in-memory database`) + } + _, err := p.Open(p.config.DBFile) + if err != nil { + return err + } + + p.js += fmt.Sprintf("\nwindow.sqlite = { %s };", strings.Join(p.callableMethods, ", ")) + return nil +} + +// CallableByJS returns a list of exported methods that can be called from the frontend +func (p *Plugin) CallableByJS() []string { + return p.callableMethods +} + +func (p *Plugin) InjectJS() string { + return p.js +} + +// ---------------- Plugin Methods ---------------- +// Plugin 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. +// Any methods that you want to be callable from the frontend must be returned by the +// Exported() method above. +// See https://golang.org/pkg/encoding/json/#Marshal for more information. + +func (p *Plugin) Open(dbPath string) (string, error) { + var err error + p.conn, err = sql.Open("sqlite", dbPath) + if err != nil { + return "", err + } + return "Database connection opened", nil +} + +func (p *Plugin) Execute(query string, args ...any) error { + if p.conn == nil { + return errors.New("no open database connection") + } + + _, err := p.conn.Exec(query, args...) + if err != nil { + return err + } + return nil +} + +func (p *Plugin) Select(query string, args ...any) ([]map[string]any, error) { + if p.conn == nil { + return nil, errors.New("no open database connection") + } + + rows, err := p.conn.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + columns, err := rows.Columns() + var results []map[string]any + for rows.Next() { + values := make([]any, len(columns)) + pointers := make([]any, len(columns)) + + 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 +} + +func (p *Plugin) Close() error { + if p.conn == nil { + return errors.New("no open database connection") + } + + err := p.conn.Close() + if err != nil { + return err + } + p.conn = nil + return nil +} diff --git a/v3/plugins/sqlite/plugin.toml b/v3/plugins/sqlite/plugin.toml new file mode 100644 index 000000000..7e53e00d6 --- /dev/null +++ b/v3/plugins/sqlite/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "sqlite" plugin. + +Name = "sqlite" +Description = "Provides easy access to SQLite DBs" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io/plugins/sqlite" +Repository = "https://github.com/wailsapp/wails/v3/plugins/sqlite" +License = "MIT" + + diff --git a/v3/plugins/sqlite/sqlite_close.js b/v3/plugins/sqlite/sqlite_close.js new file mode 100644 index 000000000..1bf476dec --- /dev/null +++ b/v3/plugins/sqlite/sqlite_close.js @@ -0,0 +1,8 @@ + +/** + * Close a sqlite DB. + * @returns {Promise} + */ +function Close() { + return wails.Plugin("sqlite", "Close"); +} diff --git a/v3/plugins/sqlite/sqlite_execute_select.js b/v3/plugins/sqlite/sqlite_execute_select.js new file mode 100644 index 000000000..0d20cb552 --- /dev/null +++ b/v3/plugins/sqlite/sqlite_execute_select.js @@ -0,0 +1,20 @@ + +/** + * Execute a SQL statement. + * @param statement {string} - SQL statement to execute. + @param args {...any} - Arguments to pass to the statement. + * @returns {Promise} + */ +function Execute(statement, ...args) { + return wails.Plugin("sqlite", "Execute", statement, ...args); +} + +/** + * Perform a select query. + * @param statement {string} - Select SQL statement. + * @param args {...any} - Arguments to pass to the statement. + * @returns {Promise} + */ +function Select(statement, ...args) { + return wails.Plugin("sqlite", "Select", statement, ...args); +} diff --git a/v3/plugins/sqlite/sqlite_open.js b/v3/plugins/sqlite/sqlite_open.js new file mode 100644 index 000000000..22526210f --- /dev/null +++ b/v3/plugins/sqlite/sqlite_open.js @@ -0,0 +1,8 @@ +/** + * Open a sqlite DB. + * @param filename {string} - file to open. + * @returns {Promise} + */ +function Open(filename) { + return wails.Plugin("sqlite", "Open", filename); +} diff --git a/v3/plugins/start_at_login/README.md b/v3/plugins/start_at_login/README.md new file mode 100644 index 000000000..e790f1526 --- /dev/null +++ b/v3/plugins/start_at_login/README.md @@ -0,0 +1,40 @@ +# start_at_login Plugin + +This example plugin provides a way to generate hashes of strings. + +## Installation + +Add the plugin to the `Plugins` option in the Applications options: + +```go + Plugins: map[string]application.Plugin{ + "start_at_login": start_at_login.NewPlugin(), + }, +``` + +## Usage + +You can then call the methods from the frontend: + +```js +wails + .Plugin("start_at_login", "StartAtLogin", true) + .then((result) => console.log(result)); +wails + .Plugin("start_at_login", "IsStartAtLogin") + .then((result) => console.log(result)); +``` + +To use this from Go, create a new instance of the plugin, then call the methods +on that: + +```go + start_at_login := start_at_login.NewPlugin() + start_at_login.StartAtLogin(true) +``` + +## Support + +If you find a bug in this plugin, please raise a ticket +[here](https://github.com/plugin/repository). Please do not contact the Wails +team for support. diff --git a/v3/plugins/start_at_login/plugin.go b/v3/plugins/start_at_login/plugin.go new file mode 100644 index 000000000..44cbae6c6 --- /dev/null +++ b/v3/plugins/start_at_login/plugin.go @@ -0,0 +1,45 @@ +package start_at_login + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +type Plugin struct { + app *application.App + disabled bool +} + +func NewPlugin() *Plugin { + return &Plugin{} +} + +// Shutdown is called when the app is shutting down +// You can use this to clean up any resources you have allocated +func (p *Plugin) Shutdown() {} + +// Name returns the name of the plugin. +// You should use the go module format e.g. github.com/myuser/myplugin +func (p *Plugin) Name() string { + return "github.com/wailsapp/wails/v3/plugins/start_at_login" +} + +func (p *Plugin) Init(app *application.App) error { + p.app = app + // OS specific initialiser + err := p.init() + if err != nil { + return err + } + return nil +} + +func (p *Plugin) CallableByJS() []string { + return []string{ + "StartAtLogin", + "IsStartAtLogin", + } +} + +func (p *Plugin) InjectJS() string { + return "" +} diff --git a/v3/plugins/start_at_login/plugin.toml b/v3/plugins/start_at_login/plugin.toml new file mode 100644 index 000000000..fa359f46b --- /dev/null +++ b/v3/plugins/start_at_login/plugin.toml @@ -0,0 +1,11 @@ +# This is the plugin definition file for the "start_at_login" plugin. + +Name = "start_at_login" +Description = "Plugin to control whether the application should start at login" +Author = "Lea Anthony" +Version = "v1.0.0" +Website = "https://wails.io/plugins/start_at_login" +Repository = "https://github.com/wailsapp/wails/v3/plugins/start_at_login" +License = "MIT" + + diff --git a/v3/plugins/start_at_login/plugin_darwin.go b/v3/plugins/start_at_login/plugin_darwin.go new file mode 100644 index 000000000..f3d88390c --- /dev/null +++ b/v3/plugins/start_at_login/plugin_darwin.go @@ -0,0 +1,80 @@ +//go:build darwin + +package start_at_login + +import ( + "fmt" + "github.com/pkg/errors" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/logger" + "github.com/wailsapp/wails/v3/pkg/mac" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func (p *Plugin) init() error { + bundleID := mac.GetBundleID() + if bundleID == "" { + p.app.Log(&logger.Message{ + Level: "INFO", + Message: "Application is not in bundle. StartAtLogin will not work.", + }) + p.disabled = true + } + return nil +} + +func (p *Plugin) StartAtLogin(enabled bool) error { + if p.disabled { + return nil + } + exe, err := os.Executable() + if err != nil { + return errors.Wrap(err, "Error running os.Executable:") + } + binName := filepath.Base(exe) + if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) { + return fmt.Errorf("app needs to be running as package.app file to start at login") + } + appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName) + var command string + if enabled { + command = fmt.Sprintf("tell application \"System Events\" to make login item at end with properties {name: \"%s\",path:\"%s\", hidden:false}", binName, appPath) + } else { + command = fmt.Sprintf("tell application \"System Events\" to delete login item \"%s\"", binName) + } + + cmd := exec.Command("osascript", "-e", command) + _, err = cmd.CombinedOutput() + if err != nil { + return err + } + return nil +} + +func (p *Plugin) IsStartAtLogin() (bool, error) { + if p.disabled { + return false, nil + } + exe, err := os.Executable() + if err != nil { + return false, err + } + binName := filepath.Base(exe) + if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) { + return false, fmt.Errorf("app needs to be running as package.app file to start at login") + } + appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName) + appName := strings.TrimSuffix(filepath.Base(appPath), ".app") + cmd := exec.Command("osascript", "-e", `tell application "System Events" to get the name of every login item`) + results, err := cmd.CombinedOutput() + if err != nil { + return false, err + } + resultsString := strings.TrimSpace(string(results)) + startupApps := strings.Split(resultsString, ", ") + result := lo.Contains(startupApps, appName) + return result, nil +} diff --git a/v3/plugins/start_at_login/plugin_linux.go b/v3/plugins/start_at_login/plugin_linux.go new file mode 100644 index 000000000..22d1b244d --- /dev/null +++ b/v3/plugins/start_at_login/plugin_linux.go @@ -0,0 +1,8 @@ +//go:build linux + +package start_at_login + +func (p *Plugin) init() error { + // TBD + return nil +} diff --git a/v3/plugins/start_at_login/plugin_window.go b/v3/plugins/start_at_login/plugin_window.go new file mode 100644 index 000000000..7018077ac --- /dev/null +++ b/v3/plugins/start_at_login/plugin_window.go @@ -0,0 +1,8 @@ +//go:build windows + +package start_at_login + +func (p *Plugin) init() error { + // TBD + return nil +} diff --git a/v3/scripts/validate-changelog.go b/v3/scripts/validate-changelog.go deleted file mode 100644 index 659285a20..000000000 --- a/v3/scripts/validate-changelog.go +++ /dev/null @@ -1,270 +0,0 @@ -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..1949fb8f9 --- /dev/null +++ b/v3/tasks/Taskfile.yml @@ -0,0 +1,7 @@ +version: '3' + +tasks: + generate: + dir: ./events + cmds: + - go run generate.go 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..eafa27f89 --- /dev/null +++ b/v3/tasks/events/generate.go @@ -0,0 +1,222 @@ +package main + +import ( + "bytes" + "os" + "strconv" + "strings" +) + +var eventsGo = `package events + +type ApplicationEventType uint +type WindowEventType uint + +const ( + FilesDropped WindowEventType = iota +) + +var Mac = newMacEvents() + +type macEvents struct { +$$MACEVENTSDECL} + +func newMacEvents() macEvents { + return macEvents{ +$$MACEVENTSVALUES } +} +` + +var eventsH = `//go:build darwin + +#ifndef _events_h +#define _events_h + +extern void processApplicationEvent(unsigned int); +extern void processWindowEvent(unsigned int, unsigned int); + +$$CHEADEREVENTS + +#endif` + +func main() { + + eventNames, err := os.ReadFile("../../pkg/events/events.txt") + if err != nil { + panic(err) + } + + macEventsDecl := bytes.NewBufferString("") + macEventsValues := bytes.NewBufferString("") + cHeaderEvents := bytes.NewBufferString("") + windowDelegateEvents := bytes.NewBufferString("") + applicationDelegateEvents := bytes.NewBufferString("") + webviewDelegateEvents := bytes.NewBufferString("") + + var id 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 = event[:len(event)-1] + ignoreEvent = true + } + + // 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 "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") + cHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n") + 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 + `); + } +} + +`) + } + + } + } + + cHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(id-1) + "\n") + + // Save the eventsGo template substituting the values and decls + templateToWrite := strings.ReplaceAll(eventsGo, "$$MACEVENTSDECL", macEventsDecl.String()) + templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSVALUES", macEventsValues.String()) + err = os.WriteFile("../../pkg/events/events.go", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Save the eventsH template substituting the values and decls + templateToWrite = strings.ReplaceAll(eventsH, "$$CHEADEREVENTS", cHeaderEvents.String()) + err = os.WriteFile("../../pkg/events/events.h", []byte(templateToWrite), 0644) + if err != nil { + panic(err) + } + + // Load the window_delegate.m file + windowDelegate, err := os.ReadFile("../../pkg/application/webview_window.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.m", buffer.Bytes(), 0755) + if err != nil { + panic(err) + } + + // Load the app_delegate.m file + appDelegate, err := os.ReadFile("../../pkg/application/app_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/app_delegate.m", buffer.Bytes(), 0755) + if err != nil { + panic(err) + } + +} diff --git a/v3/tasks/png2bytes/png2bytes.go b/v3/tasks/png2bytes/png2bytes.go new file mode 100644 index 000000000..b51a472b6 --- /dev/null +++ b/v3/tasks/png2bytes/png2bytes.go @@ -0,0 +1,38 @@ +package main + +import ( + "bytes" + "os" + "strconv" +) + +func main() { + + if len(os.Args) != 2 { + println("Please provide a filename") + os.Exit(1) + } + + data, err := os.ReadFile(os.Args[1]) + if err != nil { + println("Error reading file:", err.Error()) + os.Exit(1) + } + + var buffer bytes.Buffer + buffer.WriteString("var image = []byte{") + // Iterate over the bytes and print them out in decimal + for _, b := range data { + // convert byte to decimal + buffer.WriteString(strconv.Itoa(int(b)) + ", ") + } + buffer.WriteString("}\n") + + // write to file + err = os.WriteFile(os.Args[1]+".go", buffer.Bytes(), 0644) + if err != nil { + println("Error writing file:", err.Error()) + os.Exit(1) + } + +} 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/website/.gitattributes b/website/.gitattributes deleted file mode 100644 index 65ac44a9e..000000000 --- a/website/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Mark all files as documentation so it gets excluded from github language statistics -# https://github.com/github-linguist/linguist/blob/master/docs/overrides.md#documentation -** linguist-documentation diff --git a/website/README.md b/website/README.md index 5194c28d2..79623c5e6 100644 --- a/website/README.md +++ b/website/README.md @@ -1,6 +1,7 @@ # Website -This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. +This website is built using [Docusaurus 2](https://docusaurus.io/), a modern +static website generator. ### Installation @@ -20,13 +21,16 @@ Other languages: npm run start -- --locale ``` -language - The language code configured in the i18n field in the docusaurus.config.js file. +language - The language code configured in the i18n field in the +docusaurus.config.js file. -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. +This command starts a local development server and opens up a browser window. +Most changes are reflected live without having to restart the server. ### Translate -After the English source file is updated, run the following command to submit the source file to Crowdin: +After the English source file is updated, run the following command to submit +the source file to Crowdin: ``` npm run crowdin push -- -b @@ -40,9 +44,12 @@ Run the following command to pull the translated files in crowdin to the local: npm run crowdin pull -- -b -l ``` -languageCode - **Note** that this refers to the language code in the crowdin project. +languageCode - **Note** that this refers to the language code in the crowdin +project. -The recommended practice is to update the English source file locally, then translate the file in crowdin, and finally pull the translated file to the local. +The recommended practice is to update the English source file locally, then +translate the file in crowdin, and finally pull the translated file to the +local. ### Build @@ -50,4 +57,5 @@ The recommended practice is to update the English source file locally, then tran $ yarn build ``` -This command generates static content into the `build` directory and can be served using any static contents hosting service. +This command generates static content into the `build` directory and can be +served using any static contents hosting service. diff --git a/website/Taskfile.yml b/website/Taskfile.yml index dbb09105d..53b38857a 100644 --- a/website/Taskfile.yml +++ b/website/Taskfile.yml @@ -8,7 +8,7 @@ tasks: aliases: [i] cmds: - corepack enable - - corepack prepare pnpm@8.3.1 --activate + - corepack prepare pnpm@8.2.0 --activate - pnpm install sources: - package.json diff --git a/website/blog/2021-09-27-v2-beta1-release-notes.mdx b/website/blog/2021-09-27-v2-beta1-release-notes.mdx index 62466176f..f9682cbb2 100644 --- a/website/blog/2021-09-27-v2-beta1-release-notes.mdx +++ b/website/blog/2021-09-27-v2-beta1-release-notes.mdx @@ -16,34 +16,44 @@ tags: [wails, v2]
``` -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 +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. +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. +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... +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. +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 @@ -58,16 +68,19 @@ possible, but also being open to someone taking those projects and building on t
``` -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! +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. +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. +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! @@ -84,18 +97,21 @@ Gophers rejoice! The single binary dream lives on!
``` -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 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 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. +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. ```mdx-code-block
@@ -110,9 +126,11 @@ but we hope to support other IDEs such as Goland soon. ### 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. +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... @@ -120,8 +138,9 @@ 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. +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 @@ -136,27 +155,34 @@ They don't even need to be in the top directory - Wails will just work it out fo
``` -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. +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 +- 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 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 +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! +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 @@ -171,45 +197,63 @@ auto-imported when hitting tab in an auto-generated module wrapping your Go code
``` -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. +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! +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. +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. +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: +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. +- [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 drive the project in many ways behind the scenes. +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! +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! +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! +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/website/blog/2021-11-08-v2-beta2-release-notes.mdx b/website/blog/2021-11-08-v2-beta2-release-notes.mdx index 2f8751c9c..b4efe204d 100644 --- a/website/blog/2021-11-08-v2-beta2-release-notes.mdx +++ b/website/blog/2021-11-08-v2-beta2-release-notes.mdx @@ -16,16 +16,19 @@ tags: [wails, v2]
``` -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. +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! +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: +So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the +Windows Beta :wink: ### New Features @@ -40,18 +43,21 @@ So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the Windo
``` -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 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 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: +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 @@ -60,9 +66,11 @@ In addition to the normal application options, Wails v2 for Mac also brings some ### 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. +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... @@ -70,32 +78,40 @@ 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. +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. +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 +- 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 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 +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! +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 @@ -110,21 +126,24 @@ auto-imported when hitting tab in an auto-generated module wrapping your Go code
``` -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. +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! +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: +Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the +Wails project now supports M1 native builds: ```mdx-code-block
@@ -165,7 +184,8 @@ Oh, I almost forgot.... you can also do `darwin/universal`.... :wink: ### Cross Compilation to Windows -Because Wails v2 for Windows is pure Go, you can target Windows builds without docker. +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. ```mdx-code-block
@@ -180,22 +200,28 @@ Because Wails v2 for Windows is pure Go, you can target Windows builds without d ### 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. +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. +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. +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! +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! +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/website/blog/2022-02-22-v2-beta3-release-notes.mdx b/website/blog/2022-02-22-v2-beta3-release-notes.mdx index 1471ec1d1..b617f788d 100644 --- a/website/blog/2022-02-22-v2-beta3-release-notes.mdx +++ b/website/blog/2022-02-22-v2-beta3-release-notes.mdx @@ -16,9 +16,11 @@ tags: [wails, v2]
``` -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: +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 @@ -33,20 +35,24 @@ is very different from those first experiments. So without further ado, let's go
``` -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 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 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 -`` tag with a local src path. Want to use a cool font? Copy it in and add the path to it in your CSS. +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... @@ -54,32 +60,40 @@ 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. +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. +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 +- 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 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 +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! +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 @@ -94,20 +108,24 @@ auto-imported when hitting tab in an auto-generated module wrapping your Go code
``` -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. +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! +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. +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. ```mdx-code-block
@@ -122,21 +140,25 @@ Because Wails v2 for Windows is pure Go, you can target Windows builds without d ### 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. +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! +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 -drive the project in many ways behind the scenes. +Finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors) 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! +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! +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/website/blog/2022-09-22-v2-release-notes.mdx b/website/blog/2022-09-22-v2-release-notes.mdx index bcf972450..8bea63338 100644 --- a/website/blog/2022-09-22-v2-release-notes.mdx +++ b/website/blog/2022-09-22-v2-release-notes.mdx @@ -17,80 +17,189 @@ tags: [wails, v2] # 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. +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. +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. +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). +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: +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](/docs/reference/options#theme) + [custom theming](/docs/reference/options#customtheme) on Windows. +- Webview2 component for Windows that supports modern web standards and + debugging capabilities. +- [Dark / Light theme](/docs/reference/options#theme) + + [custom theming](/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](/docs/guides/application-development#application-menu) and [dialogs](/docs/reference/runtime/dialog). -- Native window translucency effects for [Windows](/docs/reference/options#windowistranslucent) and [macOS](/docs/reference/options#windowistranslucent-1). Support for Mica & Acrylic backdrops. -- Easily generate an [NSIS installer](/docs/guides/windows-installer) for Windows deployments. -- A rich [runtime library](/docs/reference/runtime/intro) providing utility methods for window manipulation, eventing, dialogs, menus and logging. -- Support for [obfuscating](/docs/guides/obfuscated) your application using [garble](https://github.com/burrowers/garble). +- 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](/docs/guides/application-development#application-menu) and + [dialogs](/docs/reference/runtime/dialog). +- Native window translucency effects for + [Windows](/docs/reference/options#windowistranslucent) and + [macOS](/docs/reference/options#windowistranslucent-1). Support for Mica & + Acrylic backdrops. +- Easily generate an [NSIS installer](/docs/guides/windows-installer) for + Windows deployments. +- A rich [runtime library](/docs/reference/runtime/intro) providing utility + methods for window manipulation, eventing, dialogs, menus and logging. +- Support for [obfuscating](/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](/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. +- Automatic TypeScript generation of Go structs. More info + [here](/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. +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! +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! +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! +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. +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. +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). +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. +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. +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! +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. +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. +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. +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! +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/website/blog/2023-01-17-v3-roadmap.mdx b/website/blog/2023-01-17-v3-roadmap.mdx index adc53e36e..be3b4dd35 100644 --- a/website/blog/2023-01-17-v3-roadmap.mdx +++ b/website/blog/2023-01-17-v3-roadmap.mdx @@ -17,12 +17,13 @@ tags: [wails, v3] # 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. +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: +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 @@ -31,21 +32,22 @@ including: - Creating of NSIS Installer - Obfuscated builds -Right now, Wails v2 provides powerful tooling for creating rich, cross-platform desktop -applications. +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. +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. +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. +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 @@ -62,14 +64,16 @@ 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 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: +The Runtime API provides a lot of utility functions for the developer. This +includes: - Window management - Dialogs @@ -77,13 +81,13 @@ The Runtime API provides a lot of utility functions for the developer. This incl - 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. +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. +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 @@ -101,10 +105,10 @@ func main() { } ``` -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: +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{}) @@ -112,8 +116,9 @@ 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): +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() { @@ -134,107 +139,125 @@ func main() { ### 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. +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 was 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. +There was 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. +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: +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 +- 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 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) + - 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. +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. +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. +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. +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. +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. +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. +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 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. +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. +- 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. +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. +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! +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. \ No newline at end of file +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/website/blog/2025-03-16-security-incident-response.mdx b/website/blog/2025-03-16-security-incident-response.mdx deleted file mode 100644 index e9903c570..000000000 --- a/website/blog/2025-03-16-security-incident-response.mdx +++ /dev/null @@ -1,89 +0,0 @@ ---- -slug: security-incident-response-march-2025 -title: Proactive Security Response - GitHub Actions Supply Chain Attack -authors: [leaanthony] -tags: [wails, security] ---- - -
- Security Shield -
-
- -:::note TL;DR -**Good news! Wails was NOT affected by this security incident.** Our thorough investigation confirmed that no secrets were leaked, and the Wails codebase and releases remain completely secure. We've already taken proactive measures to further strengthen our security posture. -::: - -## Introduction - -On 15th March 2025 (AEST), the Wails team was alerted to a security incident involving the `tj-actions/changed-files` GitHub Action. This widely-used action (with over 23,000 repositories depending on it) was compromised in a supply chain attack. While this action was used in some of our CI/CD workflows, we're pleased to confirm that Wails remained fully protected throughout. - -This post shares the details of the incident, our response, and the additional safeguards we've implemented to ensure the continued security of the Wails project. - -## Incident Details - -The security company StepSecurity [reported](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised) that the `tj-actions/changed-files` GitHub Action was compromised beginning around 9:00 AM March 14th, 2025 Pacific Time (4:00 PM UTC). - -In this attack, malicious code was injected into the action that attempted to dump CI/CD secrets from GitHub Actions runner processes into public logs. The attackers modified the action's code and retroactively updated multiple version tags to reference the malicious commit. - -## Our Proactive Assessment - -Upon learning this, we immediately launched a comprehensive assessment of our systems: - -1. We identified the following Wails workflows that were using the action: - - For Wails v2: `pr-v2.yml` and `upload-source-documents.yml` - - For Wails v3: `pr-v3.yml`, `publish-npm.yml`, and `upload-source-documents.yml` - -2. Our security team conducted a thorough review of all workflow logs for the affected actions during the time period of the compromise. - -3. We're happy to confirm that **no secrets were leaked** in any of our workflow logs, and the Wails codebase remained completely secure. - -## Action Taken - -We took immediate steps to address this situation: - -1. We swiftly replaced all instances of the affected `tj-actions/changed-files` action with the secure alternative `step-security/changed-files` provided by StepSecurity. - -2. As an extra precautionary measure, we temporarily removed all secrets from our GitHub Actions workflows. - -## What This Means for You - -We want to reassure our community that: - -1. The Wails codebase was never compromised in any way. -2. No malicious code was introduced into any Wails releases. -3. This situation only potentially affected our CI/CD pipeline, not the actual Wails source code or releases. -4. No sensitive information or secrets were exposed during this time. - -**In short: All Wails releases remain secure and trustworthy, and no action is required on your part.** - -## Strengthening Our Security Posture - -To minimise exposure to similar potential incidents in the future, we're enhancing our security practices by: - -1. Implementing stricter version pinning for all third-party actions used in our workflows, preferably pinning to specific commit hashes rather than version tags. - -2. Establishing a regular security review process for our CI/CD pipelines and dependencies. - -3. Exploring the use of additional security tools like StepSecurity's Harden-Runner to provide enhanced protection for our GitHub Actions workflows. - -4. Developing a more comprehensive security incident response plan to ensure we can respond quickly and effectively to any future security concerns. - -It's worth noting that the Wails project already employs several security tools as part of our development process: - -- **Semgrep**: We use Semgrep for static code analysis to identify potential security vulnerabilities and code quality issues. -- **Snyk**: We employ Snyk to continuously monitor our dependencies for known vulnerabilities and receive alerts when security patches are needed. - -These existing security measures, combined with our enhanced preventative steps, demonstrate our ongoing commitment to maintaining the security and integrity of the Wails project. - -## Moving Forward - -The security of the Wails project and the trust of our community are our highest priorities. We remain committed to transparency and will continue to promptly address any security concerns that arise. - -We would like to thank StepSecurity for their quick response in identifying this issue and providing a secure alternative action. - -If you have any questions or concerns about this, please don't hesitate to reach out to us on [GitHub](https://github.com/wailsapp/wails) or [Discord](https://discord.gg/JDdSxwjhGf). We're always here to help. diff --git a/website/bun.lockb b/website/bun.lockb deleted file mode 100644 index 63ed1b159..000000000 Binary files a/website/bun.lockb and /dev/null differ diff --git a/website/docs/community/links.mdx b/website/docs/community/links.mdx index fe8b51dd0..1f8ea0c99 100644 --- a/website/docs/community/links.mdx +++ b/website/docs/community/links.mdx @@ -4,12 +4,13 @@ sidebar_position: 2 # Links -This page serves as a list for community related links. Please submit a PR (click `Edit this page` at the bottom) -to submit links. +This page serves as a list for community related links. Please submit a PR +(click `Edit this page` at the bottom) to submit links. ## Awesome Wails -The [definitive list](https://github.com/wailsapp/awesome-wails) of links related to Wails. +The [definitive list](https://github.com/wailsapp/awesome-wails) of links +related to Wails. ## Support Channels @@ -20,7 +21,8 @@ The [definitive list](https://github.com/wailsapp/awesome-wails) of links relate ## Social Media - [Twitter](https://twitter.com/wailsapp) -- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) - Group number: 1067173054 +- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) - + Group number: 1067173054 ## Other Tutorials and Articles diff --git a/website/docs/community/showcase/bulletinboard.mdx b/website/docs/community/showcase/bulletinboard.mdx index 37be75135..54a1253fe 100644 --- a/website/docs/community/showcase/bulletinboard.mdx +++ b/website/docs/community/showcase/bulletinboard.mdx @@ -7,4 +7,13 @@

``` -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. There 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). +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. There 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/website/docs/community/showcase/cfntracker.mdx b/website/docs/community/showcase/cfntracker.mdx deleted file mode 100644 index 8fab23b75..000000000 --- a/website/docs/community/showcase/cfntracker.mdx +++ /dev/null @@ -1,39 +0,0 @@ -# CFN Tracker - -```mdx-code-block -

- -
-

-``` - -[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/website/docs/community/showcase/clustta.mdx b/website/docs/community/showcase/clustta.mdx deleted file mode 100644 index 7da3195df..000000000 --- a/website/docs/community/showcase/clustta.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Clustta -description: File manager and project management tool for creative professionals. -slug: /community/showcase/clustta -image: /img/showcase/clustta.png ---- - -
- Clustta screenshot -
- -[Clustta](https://clustta.com) is a file manager and project management tool designed for creative professionals. Built with Wails, it simplifies file management, collaboration, and version control for creative workflows. - -## Features - -- **File Management**: Track all projects and files with easy access even months after completion. -- **Version Control**: Save unlimited revisions with descriptive notes, without duplicating files. -- **Collaboration**: Share files or entire projects securely through simple user tags with fine-grained permissions. -- **Recovery**: Restore corrupted files from saved checkpoints if your software crashes. -- **Templates**: Quick start with preset project and task templates. -- **Kanban Boards**: Visual task tracking to keep tasks organized. -- **Dependencies**: Track and version project resources and dependencies. -- **Checkpoints**: Create memorable milestones and explore alternate creative directions non-destructively. -- **Search & Filters**: Powerful instant search with metadata filtering (task types, tags, status, file extensions). -- **Workspaces**: Save search and filter combinations for easy access to specific task sets. -- **Integrations**: Connect with creative software packages and production management tools like Blender and Kitsu. -- **Self-Hosting**: Host private instances for teams or studios on your own servers. diff --git a/website/docs/community/showcase/emailit.mdx b/website/docs/community/showcase/emailit.mdx index c1817b70f..ea23f79dd 100644 --- a/website/docs/community/showcase/emailit.mdx +++ b/website/docs/community/showcase/emailit.mdx @@ -7,4 +7,11 @@

``` -[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. +[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/website/docs/community/showcase/encrypteasy.mdx b/website/docs/community/showcase/encrypteasy.mdx index 7504950ea..a071515ac 100644 --- a/website/docs/community/showcase/encrypteasy.mdx +++ b/website/docs/community/showcase/encrypteasy.mdx @@ -7,6 +7,13 @@

``` -**[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.** +**[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. +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/website/docs/community/showcase/espstudio.mdx b/website/docs/community/showcase/espstudio.mdx deleted file mode 100644 index 44db858f9..000000000 --- a/website/docs/community/showcase/espstudio.mdx +++ /dev/null @@ -1,13 +0,0 @@ -# ESP Studio - -```mdx-code-block -

- -
-

-``` - -[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/website/docs/community/showcase/filehound.mdx b/website/docs/community/showcase/filehound.mdx index 8c541f482..99c53fa71 100644 --- a/website/docs/community/showcase/filehound.mdx +++ b/website/docs/community/showcase/filehound.mdx @@ -7,18 +7,16 @@

``` -[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. +[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. +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 +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 +Frontend with: Vue 2.6.11 Vuex 3.4.0 TypeScript Tailwind 1.9.6 diff --git a/website/docs/community/showcase/gamestacker.mdx b/website/docs/community/showcase/gamestacker.mdx deleted file mode 100644 index 46245e146..000000000 --- a/website/docs/community/showcase/gamestacker.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: GameStacker -description: A modern, console-like interface that unifies your game libraries. -slug: /community/showcase/gamestacker -image: /img/showcase/gamestacker.webp ---- - -
- GameStacker main dashboard -
- -[GameStacker](https://gamestacker.io) is a modern, elegant console-like interface that unifies your entire game collection into one beautiful dashboard. - -## Features - -- **Unified Library**: Automatically detects and imports games from external libraries (such as Steam, LaunchBox, and RetroBat), bringing your modern and retro collections into a single, cohesive interface. -- **True Console Experience**: Built for controllers with responsive navigation, UI sound effects, and a dedicated in-game "Guide" overlay to control music and manage your session without alt-tabbing. -- **Achievement Integration**: Tracks your progress across supported services, allowing you to view unlocks and Gamerscore directly from the dashboard. -- **Multi-Profile Support**: Create unique profiles with custom avatars and specific color themes for a personalized experience. diff --git a/website/docs/community/showcase/grpcmd-gui.mdx b/website/docs/community/showcase/grpcmd-gui.mdx deleted file mode 100644 index 891350290..000000000 --- a/website/docs/community/showcase/grpcmd-gui.mdx +++ /dev/null @@ -1,10 +0,0 @@ -# grpcmd-gui - -```mdx-code-block -

- -
-

-``` - -[grpcmd-gui](https://grpc.md/gui) is a modern cross-platform desktop app and API client for gRPC development and testing. diff --git a/website/docs/community/showcase/hiposter.mdx b/website/docs/community/showcase/hiposter.mdx index c0f9052c3..b4ad84d7f 100644 --- a/website/docs/community/showcase/hiposter.mdx +++ b/website/docs/community/showcase/hiposter.mdx @@ -7,4 +7,5 @@

``` -[hiposter](https://github.com/obity/hiposter) is a simple and efficient http API testing client tool. Based on Wails, Go and sveltejs. \ No newline at end of file +[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/website/docs/community/showcase/kafka-king.mdx b/website/docs/community/showcase/kafka-king.mdx deleted file mode 100644 index 0ba78a6ad..000000000 --- a/website/docs/community/showcase/kafka-king.mdx +++ /dev/null @@ -1,22 +0,0 @@ -# Kafka-King - -```mdx-code-block -

- -
-

-``` - -[Kafka-King](https://github.com/Bronya0/Kafka-King) is a kafka GUI client that supports various systems and is compact and easy to use. -This is made of Wails+vue3 - -# Kafka-King function list -- [x] View the cluster node list, support dynamic configuration of broker and topic configuration items -- [x] Supports consumer clients, consumes the specified topic, size, and timeout according to the specified group, and displays the message information in various dimensions in a table -- [x] Supports PLAIN, SSL, SASL, kerberos, sasl_plaintext, etc. etc. -- [x] Create topics (support batches), delete topics, specify replicas, partitions -- [x] Support statistics of the total number of messages, total number of submissions, and backlog for each topic based on consumer groups -- [x] Support viewing topics Detailed information (offset) of the partition, and support adding additional partitions -- [x] Support simulated producers, batch sending messages, specify headers, partitions -- [x] Health check -- [x] Support viewing consumer groups , Consumer- …… diff --git a/website/docs/community/showcase/marasi.mdx b/website/docs/community/showcase/marasi.mdx deleted file mode 100644 index 5ff137523..000000000 --- a/website/docs/community/showcase/marasi.mdx +++ /dev/null @@ -1,22 +0,0 @@ -# Marasi - -```mdx-code-block -

- -
-

-``` - -[Marasi](https://marasi.app/) is an open source application security testing proxy, it lets you intercept, inspect, modify, and extend requests as they flow through your applications. Read more about it on the [blog](https://marasi.app/blog/2025/introducing_marasi/). - -## Features - -- Desktop GUI Interface: Cross-platform desktop application built with Wails -- HTTP/HTTPS Proxy: TLS-capable proxy server with certificate management -- Request/Response Interception: Modify traffic in real-time with an intuitive interface -- Lua Extensions: Scriptable proxy behavior with built-in extensions -- Project Management: SQLite-based storage for all proxy data (requests, responses, metadata) -- Launchpad: Replay and modify HTTP requests -- Scope Management: Filter traffic with inclusion/exclusion rules -- Waypoints: Override hostnames for request routing -- Chrome Integration: Auto-configure Chrome with proxy settings \ No newline at end of file diff --git a/website/docs/community/showcase/mchat.mdx b/website/docs/community/showcase/mchat.mdx deleted file mode 100644 index aa535a6f8..000000000 --- a/website/docs/community/showcase/mchat.mdx +++ /dev/null @@ -1,10 +0,0 @@ -# Mchat - -```mdx-code-block -

- -
-

-``` - -[Official page](https://marcio199226.github.io/mchat-site/public/) Fully anonymous end2end encrypted chat. diff --git a/website/docs/community/showcase/minecraftupdater.mdx b/website/docs/community/showcase/minecraftupdater.mdx index 2f6c7c72b..26126c1de 100644 --- a/website/docs/community/showcase/minecraftupdater.mdx +++ b/website/docs/community/showcase/minecraftupdater.mdx @@ -11,4 +11,7 @@

``` -[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. +[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/website/docs/community/showcase/minesweeper-xp.mdx b/website/docs/community/showcase/minesweeper-xp.mdx deleted file mode 100644 index f127a005f..000000000 --- a/website/docs/community/showcase/minesweeper-xp.mdx +++ /dev/null @@ -1,10 +0,0 @@ -# Minesweeper XP - -```mdx-code-block -

- -
-

-``` - -[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/website/docs/community/showcase/modalfilemanager.mdx b/website/docs/community/showcase/modalfilemanager.mdx index bcd212396..fbc8cbc72 100644 --- a/website/docs/community/showcase/modalfilemanager.mdx +++ b/website/docs/community/showcase/modalfilemanager.mdx @@ -9,6 +9,18 @@

``` -[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. +[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. +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/website/docs/community/showcase/mollywallet.mdx b/website/docs/community/showcase/mollywallet.mdx index 5d846d06d..8b72895d9 100644 --- a/website/docs/community/showcase/mollywallet.mdx +++ b/website/docs/community/showcase/mollywallet.mdx @@ -7,4 +7,6 @@

``` -[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. +[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/website/docs/community/showcase/october.mdx b/website/docs/community/showcase/october.mdx index 66d634dc5..78a108b54 100644 --- a/website/docs/community/showcase/october.mdx +++ b/website/docs/community/showcase/october.mdx @@ -7,8 +7,13 @@

``` -[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). +[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/)! +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. +In contrast, the author's previous attempts with Electron quickly bloated to +several hundred megabytes. diff --git a/website/docs/community/showcase/optimus.mdx b/website/docs/community/showcase/optimus.mdx index 4f87479d6..cd1eb2300 100644 --- a/website/docs/community/showcase/optimus.mdx +++ b/website/docs/community/showcase/optimus.mdx @@ -7,4 +7,6 @@

``` -[Optimus](https://github.com/splode/optimus) is a desktop image optimization application. It supports conversion and compression between WebP, JPEG, and PNG image formats. +[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/website/docs/community/showcase/portfall.mdx b/website/docs/community/showcase/portfall.mdx index 03e740f4c..421504239 100644 --- a/website/docs/community/showcase/portfall.mdx +++ b/website/docs/community/showcase/portfall.mdx @@ -7,4 +7,5 @@

``` -[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s port-forwarding portal for easy access to all your cluster UIs +[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s +port-forwarding portal for easy access to all your cluster UIs diff --git a/website/docs/community/showcase/resizem.mdx b/website/docs/community/showcase/resizem.mdx deleted file mode 100644 index 27f168f48..000000000 --- a/website/docs/community/showcase/resizem.mdx +++ /dev/null @@ -1,10 +0,0 @@ -# Resizem - -```mdx-code-block -

- -
-

-``` - -[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/website/docs/community/showcase/restic-browser.mdx b/website/docs/community/showcase/restic-browser.mdx index 3646384ec..a7729426a 100644 --- a/website/docs/community/showcase/restic-browser.mdx +++ b/website/docs/community/showcase/restic-browser.mdx @@ -9,4 +9,6 @@

``` -[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. +[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/website/docs/community/showcase/riftshare.mdx b/website/docs/community/showcase/riftshare.mdx index 9928b4785..a729f9574 100644 --- a/website/docs/community/showcase/riftshare.mdx +++ b/website/docs/community/showcase/riftshare.mdx @@ -7,15 +7,20 @@

``` -Easy, Secure, and Free file sharing for everyone. Learn more at [Riftshare.app](https://riftshare.app) +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.) +- 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 +- 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/website/docs/community/showcase/scriptbar.mdx b/website/docs/community/showcase/scriptbar.mdx index 3e41eb32a..93df3b306 100644 --- a/website/docs/community/showcase/scriptbar.mdx +++ b/website/docs/community/showcase/scriptbar.mdx @@ -7,4 +7,11 @@

``` -[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. +[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/website/docs/community/showcase/snippetexpander.mdx b/website/docs/community/showcase/snippetexpander.mdx deleted file mode 100644 index 1f9fb6157..000000000 --- a/website/docs/community/showcase/snippetexpander.mdx +++ /dev/null @@ -1,27 +0,0 @@ -# Snippet Expander - -```mdx-code-block -

- -
- Screenshot of Snippet Expander's Select Snippet window -

-

- -
- Screenshot of Snippet Expander's Add Snippet screen -

-

- -
- 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/website/docs/community/showcase/surge.mdx b/website/docs/community/showcase/surge.mdx index c3b3fb4c0..5a54d6522 100644 --- a/website/docs/community/showcase/surge.mdx +++ b/website/docs/community/showcase/surge.mdx @@ -7,4 +7,6 @@

``` -[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. +[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/website/docs/community/showcase/tinyrdm.mdx b/website/docs/community/showcase/tinyrdm.mdx deleted file mode 100644 index e3124bab7..000000000 --- a/website/docs/community/showcase/tinyrdm.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# Tiny RDM - -```mdx-code-block -

- - -
-

-``` - -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/website/docs/community/showcase/upbeat.mdx b/website/docs/community/showcase/upbeat.mdx deleted file mode 100644 index 2f85b6cce..000000000 --- a/website/docs/community/showcase/upbeat.mdx +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: UpBeat -description: An RSS/Atom Feed Reader that filters out negative news with locally run ML models. -slug: /community/showcase/upbeat -image: /img/showcase/upbeat.png ---- - -
- UpBeat screenshot -
- -[UpBeat](https://upbeat.mitchelltechnologies.co.uk) is An RSS/Atom Feed Reader for macOS that filters out negative news with locally running ML models. - -## Features - -- **Local ML**: UpBeat runs a transformer-based sentiment analysis model directly on your Mac's hardware. No waiting for a Cloud GPU to spin up before you can decide what you want to read -- **Apple Neural Engine Enabled**: UpBeat runs its ML models directly on the Neural Engine of Macs. That means you get super-fast inference, but without burning through your battery or electricity bill. -- **Widely Compatible**: Works with the vast majority of RSS + Atom versions. diff --git a/website/docs/community/showcase/wailsterm.mdx b/website/docs/community/showcase/wailsterm.mdx deleted file mode 100644 index 9924dace5..000000000 --- a/website/docs/community/showcase/wailsterm.mdx +++ /dev/null @@ -1,10 +0,0 @@ -# WailsTerm - -```mdx-code-block -

- -
-

-``` - -[WailsTerm](https://github.com/rlshukhov/wailsterm) is a simple translucent terminal app powered by Wails and Xterm.js. diff --git a/website/docs/community/showcase/wally.mdx b/website/docs/community/showcase/wally.mdx index 7408aa585..9e7aae67d 100644 --- a/website/docs/community/showcase/wally.mdx +++ b/website/docs/community/showcase/wally.mdx @@ -7,4 +7,7 @@

``` -[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. +[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/website/docs/community/showcase/warmine.mdx b/website/docs/community/showcase/warmine.mdx index 46b10b5b1..7c073e061 100644 --- a/website/docs/community/showcase/warmine.mdx +++ b/website/docs/community/showcase/warmine.mdx @@ -1,4 +1,4 @@ -# Minecraft launcher for WarMine +# Minecraft launcher for WarMine ```mdx-code-block

@@ -12,8 +12,13 @@

``` -[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. +[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. +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. +Frontend is written in Svelte, whole launcher fits in 9MB and supports Windows +7-11. diff --git a/website/docs/community/showcase/ytd.mdx b/website/docs/community/showcase/ytd.mdx index 5db428f72..00ab16d44 100644 --- a/website/docs/community/showcase/ytd.mdx +++ b/website/docs/community/showcase/ytd.mdx @@ -7,4 +7,7 @@

``` -[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. +[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/website/docs/community/templates.mdx b/website/docs/community/templates.mdx index 3b020b60b..15696d5d9 100644 --- a/website/docs/community/templates.mdx +++ b/website/docs/community/templates.mdx @@ -4,79 +4,94 @@ sidebar_position: 1 # Templates -This page serves as a list for community supported templates. Please submit a PR (click `Edit this page` at the bottom) -to include your templates. To build your own template, please see the [Templates](../guides/templates.mdx) guide. +This page serves as a list for community supported templates. Please submit a PR +(click `Edit this page` at the bottom) to include your templates. To build your +own template, please see the [Templates](../guides/templates.mdx) guide. -To use these templates, run `wails init -n "Your Project Name" -t [the link below[@version]]` +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. +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` +Example: +`wails init -n "Your Project Name" -t https://github.com/misitebao/wails-template-vue` :::warning Attention -**The Wails project does not maintain, is not responsible nor liable for 3rd party templates!** +**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. +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-primevue-sakai](https://github.com/TekWizely/wails-template-primevue-sakai) - Wails starter using [PrimeVue's Sakai Application Template](https://sakai.primevue.org) (Vite, Vue, PrimeVue, TailwindCSS, Vue Router, Themes, Dark Mode, UI Components, and more) -- [wails-template-tdesign-js](https://github.com/tongque0/wails-template-tdesign-js) - Wails template based on TDesign UI (a Vue 3 UI library by Tencent), using Vite, Pinia, Vue Router, ESLint, and Prettier. +- [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-vite-vue-ts](https://github.com/codydbentley/wails-vite-vue-ts) - Vue 3 + TypeScript with Vite (and instructions to add features) +- [wails-vite-vue-the-works](https://github.com/codydbentley/wails-vite-vue-the-works) - + Vue 3 TypeScript with Vite, Vuex, Vue Router, Sass, and ESLint + Prettier +- [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) ## 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 +- [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-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - A template for React + TypeScript + Vite + TailwindCSS -- [Wails-vite-ts-tailwindcss-shadcn-template-2025](https://github.com/darkb0ts/Wails-vite-ts-tailwindcss-shadcn-template-2025) - A template for React + TypeScript + Vite -- [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 -- [wails-nextjs-tailwind-template](https://github.com/kairo913/wails-nextjs-tailwind-template) - A template using Next.js and Typescript with TailwindCSS +- [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-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - + A template for React + TypeScript + Vite + TailwindCSS ## 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-ts-tailwind-template](https://github.com/xvertile/wails-vite-svelte-tailwind-template) - A template using Wails, Svelte, Vite, TypeScript, and TailwindCSS v3 -- [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-sveltekit-less-prettier-eslint](https://github.com/Alex6357/wails-template-sveltekit-less-prettier-eslint) - A template using SvelteKit with less, Prettier and ESlint -- [wails-template-svelte-ts-less-prettier-eslint-vite](https://github.com/Alex6357/wails-template-svelte-ts-less-prettier-eslint-vite) - A template using Svelte5 + TypeScript + less + Prettier + ESlint + Vite +- [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-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) - + A template using SvelteKit ## 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 +- [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-tailwind-daisyui-template](https://github.com/ltcovalt/wails-htmx-tailwind-daisyui-template) - HTMX template using Tailwind CSS + daisyUI for styling and the Go standard library for routing and HTML templating -- [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 +- [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. ## 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. +- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A + template with nothing but just basic JavaScript, HTML, and CSS diff --git a/website/docs/gettingstarted/building.mdx b/website/docs/gettingstarted/building.mdx index c4c16ea48..a8624b1cb 100644 --- a/website/docs/gettingstarted/building.mdx +++ b/website/docs/gettingstarted/building.mdx @@ -4,12 +4,8 @@ sidebar_position: 6 # Compiling your Project -From the project directory, run `wails build`. -This will compile your project and save the production-ready binary in the `build/bin` directory. - -:::info Linux -If you are using a Linux distribution that does not have webkit2gtk-4.0 (such as Ubuntu 24.04), you will need to add `-tags webkit2_41`. -::: +From the project directory, run `wails build`. This will compile your project +and save the production-ready binary in the `build/bin` directory. If you run the binary, you should see the default application: @@ -24,4 +20,5 @@ If you run the binary, you should see the default application:
``` -For more details on compilation options, please refer to the [CLI Reference](../reference/cli.mdx#build). +For more details on compilation options, please refer to the +[CLI Reference](../reference/cli.mdx#build). diff --git a/website/docs/gettingstarted/development.mdx b/website/docs/gettingstarted/development.mdx index 034be31d6..91800fff9 100644 --- a/website/docs/gettingstarted/development.mdx +++ b/website/docs/gettingstarted/development.mdx @@ -4,13 +4,18 @@ sidebar_position: 5 # Developing your Application -You can run your application in development mode by running `wails dev` from your project directory. This will do the following things: +You can run your application in development mode by running `wails dev` from +your project directory. This will do the following things: - Build your application and run it - Bind your Go code to the frontend so it can be called from JavaScript -- Using the power of [Vite](https://vitejs.dev/), will watch for modifications in your Go files and rebuild/re-run on change -- Sets up a [webserver](http://localhost:34115) that will serve your application over a browser. This allows you to use your favourite browser extensions. You can even call your Go code from the console +- Using the power of [Vite](https://vitejs.dev/), will watch for modifications + in your Go files and rebuild/re-run on change +- Sets up a [webserver](http://localhost:34115) that will serve your application + over a browser. This allows you to use your favourite browser extensions. You + can even call your Go code from the console -To get started, run `wails dev` in the project directory. More information on this can be found [here](../reference/cli.mdx#dev). +To get started, run `wails dev` in the project directory. More information on +this can be found [here](../reference/cli.mdx#dev). Coming soon: Tutorial diff --git a/website/docs/gettingstarted/firstproject.mdx b/website/docs/gettingstarted/firstproject.mdx index 5cf4dff58..3ba976b30 100644 --- a/website/docs/gettingstarted/firstproject.mdx +++ b/website/docs/gettingstarted/firstproject.mdx @@ -6,7 +6,8 @@ sidebar_position: 2 ## Project Generation -Now that the CLI is installed, you can generate a new project by using the `wails init` command. +Now that the CLI is installed, you can generate a new project by using the +`wails init` command. Pick your favourite framework: @@ -90,10 +91,11 @@ If you would rather use TypeScript:

-There are also [community templates](../community/templates.mdx) available that offer different capabilities and frameworks. +There are also [community templates](../community/templates.mdx) available that +offer different capabilities and frameworks. -To see the other options available, you can run `wails init -help`. -More details can be found in the [CLI Reference](../reference/cli.mdx#init). +To see the other options available, you can run `wails init -help`. More details +can be found in the [CLI Reference](../reference/cli.mdx#init). ## Project Layout @@ -124,7 +126,12 @@ Wails projects have the following layout: - `/go.mod` - Go module file - `/go.sum` - Go module checksum file -The `frontend` directory has nothing specific to Wails and can be any frontend project of your choosing. +The `frontend` directory has nothing specific to Wails and can be any frontend +project of your choosing. -The `build` directory is used during the build process. These files may be updated to customise your builds. If -files are removed from the build directory, default versions will be regenerated. +The `build` directory is used during the build process. These files may be +updated to customise your builds. If files are removed from the build directory, +default versions will be regenerated. + +The default module name in `go.mod` is "changeme". You should change this to +something more appropriate. diff --git a/website/docs/gettingstarted/installation.mdx b/website/docs/gettingstarted/installation.mdx index 6189c6d83..36e3108ee 100644 --- a/website/docs/gettingstarted/installation.mdx +++ b/website/docs/gettingstarted/installation.mdx @@ -7,7 +7,7 @@ sidebar_position: 1 ## Supported Platforms - Windows 10/11 AMD64/ARM64 -- MacOS 10.15+ AMD64 for development, MacOS 10.13+ for release +- MacOS 10.13+ AMD64 - MacOS 11.0+ ARM64 - Linux AMD64/ARM64 @@ -15,21 +15,25 @@ sidebar_position: 1 Wails has a number of common dependencies that are required before installation: -- Go 1.21+ (macOS 15+ requires Go 1.23.3+) +- Go 1.18+ - NPM (Node 15+) ### Go 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: +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: `echo $PATH | grep go/bin` ### NPM -Download NPM 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. +Download NPM 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. @@ -58,14 +62,6 @@ import TabItem from "@theme/TabItem"; Linux requires the standard gcc build tools plus libgtk3 and libwebkit. Rather than list a ton of commands for different distros, Wails can try to determine what the installation commands are for your specific distribution. Run wails doctor after installation to be shown how to install the dependencies. If your distro/package manager is not supported, please consult the Add Linux Distro guide. -
Note:
- If you are using latest Linux version (example: Ubuntu 24.04) and it is not supporting libwebkit2gtk-4.0-dev, then you might encounter an issue in wails doctor: libwebkit not found. To resolve this issue you can install libwebkit2gtk-4.1-dev and during your build use the tag -tags webkit2_41. -

- After installing Wails via Go, ensure you run the following commands to update your PATH: -
- export PATH=$PATH:$(go env GOPATH)/bin -
- source ~/.bashrc or source ~/.zshrc
``` @@ -73,11 +69,13 @@ import TabItem from "@theme/TabItem"; ## Optional Dependencies - [UPX](https://upx.github.io/) for compressing your applications. -- [NSIS](https://wails.io/docs/guides/windows-installer/) for generating Windows installers. +- [NSIS](https://wails.io/docs/guides/windows-installer/) for generating Windows + installers. ## Installing Wails -Run `go install github.com/wailsapp/wails/v2/cmd/wails@latest` to install the Wails CLI. +Run `go install github.com/wailsapp/wails/v2/cmd/wails@latest` to install the +Wails CLI. Note: If you get an error similar to this: @@ -93,11 +91,15 @@ go version ## System Check -Running `wails 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. +Running `wails 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 `wails` command appears to be missing? -If your system is reporting that the `wails` command is missing, make sure you have followed the Go installation guide -correctly. Normally, it means that the `go/bin` directory in your User's home directory is not in the `PATH` environment -variable. You will also normally need to close and reopen any open command prompts so that changes to the environment -made by the installer are reflected at the command prompt. +If your system is reporting that the `wails` command is missing, make sure you +have followed the Go installation guide correctly. Normally, it means that the +`go/bin` directory in your User's home directory is not in the `PATH` +environment variable. You will also normally need to close and reopen any open +command prompts so that changes to the environment made by the installer are +reflected at the command prompt. diff --git a/website/docs/guides/angular.mdx b/website/docs/guides/angular.mdx index 92eec68d5..423ef0e07 100644 --- a/website/docs/guides/angular.mdx +++ b/website/docs/guides/angular.mdx @@ -1,14 +1,16 @@ # Angular -Whilst Wails does not have an Angular template, it is possible to use Angular with Wails. +Whilst Wails does not have an Angular template, it is possible to use Angular +with Wails. ## Dev Mode -To get dev mode working with Angular, you need to add the following to your `wails.json`: +To get dev mode working with Angular, you need to add the following to your +`wails.json`: ```json "frontend:build": "npx ng build", "frontend:install": "npm install", "frontend:dev:watcher": "npx ng serve", "frontend:dev:serverUrl": "http://localhost:4200", -``` \ No newline at end of file +``` diff --git a/website/docs/guides/application-development.mdx b/website/docs/guides/application-development.mdx index adefa4b04..c7ae404b3 100644 --- a/website/docs/guides/application-development.mdx +++ b/website/docs/guides/application-development.mdx @@ -1,19 +1,18 @@ # Application Development -There are no hard and fast rules for developing applications with Wails, but there are some basic guidelines. +There are no hard and fast rules for developing applications with Wails, but +there are some basic guidelines. ## Application Setup -The pattern used by the default templates are that `main.go` is used for configuring and running the application, whilst -`app.go` is used for defining the application logic. +The pattern used by the default templates are that `main.go` is used for +configuring and running the application, whilst `app.go` is used for defining +the application logic. -The `app.go` file will define a struct that has 2 methods which act as hooks into the main application: +The `app.go` file will define a struct that has 2 methods which act as hooks +into the main application: ```go title="app.go" -import ( - "context" -) - type App struct { ctx context.Context } @@ -30,18 +29,23 @@ func (a *App) shutdown(ctx context.Context) { } ``` -- The startup method is called as soon as Wails allocates the resources it needs and is a good place for creating resources, - setting up event listeners and anything else the application needs at startup. - It is given a [`context.Context`](https://pkg.go.dev/context) which is usually saved in a struct field. This context is needed for calling the - [runtime](../reference/runtime/intro.mdx). If this method returns an error, the application will terminate. - In dev mode, the error will be output to the console. +- The startup method is called as soon as Wails allocates the resources it needs + and is a good place for creating resources, setting up event listeners and + anything else the application needs at startup. It is given a + `context.Context` which is usually saved in a struct field. This context is + needed for calling the [runtime](../reference/runtime/intro.mdx). If this + method returns an error, the application will terminate. In dev mode, the + error will be output to the console. -- The shutdown method will be called by Wails right at the end of the shutdown process. This is a good place to deallocate - memory and perform any shutdown tasks. +- The shutdown method will be called by Wails right at the end of the shutdown + process. This is a good place to deallocate memory and perform any shutdown + tasks. -The `main.go` file generally consists of a single call to `wails.Run()`, which accepts the application configuration. -The pattern used by the templates is that before the call to `wails.Run()`, an instance of the struct we defined in -`app.go` is created and saved in a variable called `app`. This configuration is where we add our callbacks: +The `main.go` file generally consists of a single call to `wails.Run()`, which +accepts the application configuration. The pattern used by the templates is that +before the call to `wails.Run()`, an instance of the struct we defined in +`app.go` is created and saved in a variable called `app`. This configuration is +where we add our callbacks: ```go {3,9,10} title="main.go" func main() { @@ -59,21 +63,19 @@ func main() { log.Fatal(err) } } + ``` -More information on application lifecycle hooks can be found [here](../howdoesitwork.mdx#application-lifecycle-callbacks). +More information on application lifecycle hooks can be found +[here](../howdoesitwork.mdx#application-lifecycle-callbacks). ## Binding Methods -It is likely that you will want to call Go methods from the frontend. This is normally done by adding public methods to -the already defined struct in `app.go`: - -```go {3,21-23} title="app.go" -import ( - "context" - "fmt" -) +It is likely that you will want to call Go methods from the frontend. This is +normally done by adding public methods to the already defined struct in +`app.go`: +```go {16-18} title="app.go" type App struct { ctx context.Context } @@ -90,11 +92,12 @@ func (a *App) shutdown(ctx context.Context) { } func (a *App) Greet(name string) string { - return fmt.Sprintf("Hello %s!", name) + return fmt.Sprintf("Hello %s!", name) } ``` -In the main application configuration, the `Bind` key is where we can tell Wails what we want to bind: +In the main application configuration, the `Bind` key is where we can tell Wails +what we want to bind: ```go {11-13} title="main.go" func main() { @@ -107,23 +110,26 @@ func main() { Height: 600, OnStartup: app.startup, OnShutdown: app.shutdown, - Bind: []interface{}{ - app, - }, + Bind: []interface{}{ + app, + }, }) if err != nil { log.Fatal(err) } } + ``` -This will bind all public methods in our `App` struct (it will never bind the startup and shutdown methods). +This will bind all public methods in our `App` struct (it will never bind the +startup and shutdown methods). ### Dealing with context when binding multiple structs -If you want to bind methods for multiple structs but want each struct to keep a reference to the context so that you -can use the runtime functions, a good pattern is to pass the context from the `OnStartup` method to your struct instances -: +If you want to bind methods for multiple structs but want each struct to keep a +reference to the context so that you can use the runtime functions, a good +pattern is to pass the context from the `OnStartup` method to your struct +instances : ```go func main() { @@ -140,10 +146,10 @@ func main() { otherStruct.SetContext(ctx) }, OnShutdown: app.shutdown, - Bind: []interface{}{ - app, + Bind: []interface{}{ + app, otherStruct - }, + }, }) if err != nil { log.Fatal(err) @@ -151,71 +157,15 @@ func main() { } ``` -Also you might want to use Enums in your structs and have models for them on frontend. -In that case you should create array that will contain all possible enum values, instrument enum type and bind it to the app: - -```go {16-18} title="app.go" -type Weekday string - -const ( - Sunday Weekday = "Sunday" - Monday Weekday = "Monday" - Tuesday Weekday = "Tuesday" - Wednesday Weekday = "Wednesday" - Thursday Weekday = "Thursday" - Friday Weekday = "Friday" - Saturday Weekday = "Saturday" -) - -var AllWeekdays = []struct { - Value Weekday - TSName string -}{ - {Sunday, "SUNDAY"}, - {Monday, "MONDAY"}, - {Tuesday, "TUESDAY"}, - {Wednesday, "WEDNESDAY"}, - {Thursday, "THURSDAY"}, - {Friday, "FRIDAY"}, - {Saturday, "SATURDAY"}, -} -``` - -In the main application configuration, the `EnumBind` key is where we can tell Wails what we want to bind enums as well: - -```go {11-13} title="main.go" -func main() { - - app := NewApp() - - err := wails.Run(&options.App{ - Title: "My App", - Width: 800, - Height: 600, - OnStartup: app.startup, - OnShutdown: app.shutdown, - Bind: []interface{}{ - app, - }, - EnumBind: []interface{}{ - AllWeekdays, - }, - }) - if err != nil { - log.Fatal(err) - } -} -``` - -This will add missing enums to your `model.ts` file. - -More information on Binding can be found [here](../howdoesitwork.mdx#method-binding). +More information on Binding can be found +[here](../howdoesitwork.mdx#method-binding). ## Application Menu -Wails supports adding a menu to your application. This is done by passing a [Menu](../reference/menus.mdx#menu) struct -to application config. It's common to use a method that returns a Menu, and even more common for that to be a method on -the `App` struct used for the lifecycle hooks. +Wails supports adding a menu to your application. This is done by passing a +[Menu](../reference/menus.mdx#menu) struct to application config. It's common to +use a method that returns a Menu, and even more common for that to be a method +on the `App` struct used for the lifecycle hooks. ```go {11} title="main.go" func main() { @@ -229,89 +179,76 @@ func main() { OnStartup: app.startup, OnShutdown: app.shutdown, Menu: app.menu(), - Bind: []interface{}{ - app, - }, + Bind: []interface{}{ + app, + }, }) if err != nil { log.Fatal(err) } } + ``` ## Assets -The great thing about the way Wails v2 handles assets is that it doesn't! The only thing you need to give Wails is an -`embed.FS`. How you get to that is entirely up to you. You can use vanilla html/css/js files like the vanilla template. -You could have some complicated build system, it doesn't matter. +The great thing about the way Wails v2 handles assets is that it doesn't! The +only thing you need to give Wails is an `embed.FS`. How you get to that is +entirely up to you. You can use vanilla html/css/js files like the vanilla +template. You could have some complicated build system, it doesn't matter. -When `wails build` is run, it will check the `wails.json` project file at the project root. There are 2 keys in the -project file that are read: +When `wails build` is run, it will check the `wails.json` project file at the +project root. There are 2 keys in the project file that are read: - "frontend:install" - "frontend:build" -The first, if given, will be executed in the `frontend` directory to install the node modules. -The second, if given, will be executed in the `frontend` directory to build the frontend project. +The first, if given, will be executed in the `frontend` directory to install the +node modules. The second, if given, will be executed in the `frontend` directory +to build the frontend project. -If these 2 keys aren't given, then Wails does absolutely nothing with the frontend. It is only expecting that `embed.FS`. +If these 2 keys aren't given, then Wails does absolutely nothing with the +frontend. It is only expecting that `embed.FS`. ### AssetsHandler -A Wails v2 app can optionally define a `http.Handler` in the `options.App`, which allows hooking into the AssetServer to -create files on the fly or process POST/PUT requests. -GET requests are always first handled by the `assets` FS. If the FS doesn't find the requested file the request will be -forwarded to the `http.Handler` for serving. Any requests other than GET will be directly processed by the `AssetsHandler` -if specified. -It's also possible to only use the `AssetsHandler` by specifying `nil` as the `Assets` option. +A Wails v2 app can optionally define a `http.Handler` in the `options.App`, +which allows hooking into the AssetServer to create files on the fly or process +POST/PUT requests. GET requests are always first handled by the `assets` FS. If +the FS doesn't find the requested file the request will be forwarded to the +`http.Handler` for serving. Any requests other than GET will be directly +processed by the `AssetsHandler` if specified. It's also possible to only use +the `AssetsHandler` by specifiy `nil` as the `Assets` option. ## Built in Dev Server -Running `wails dev` will start the built in dev server which will start a file watcher in your project directory. By -default, if any file changes, wails checks if it was an application file (default: `.go`, configurable with `-e` flag). -If it was, then it will rebuild your application and relaunch it. If the changed file was in the assets, -it will issue a reload after a short amount of time. +Running `wails dev` will start the built in dev server which will start a file +watcher in your project directory. By default, if any file changes, wails checks +if it was an application file (default: `.go`, configurable with `-e` flag). If +it was, then it will rebuild your application and relaunch it. If the changed +file was in the assets, it will issue a reload after a short amount of time. -The dev server uses a technique called "debouncing" which means it doesn't reload straight away, -as there may be multiple files changed in a short amount of time. When a trigger occurs, it waits for a set amount of time -before issuing a reload. If another trigger happens, it resets to the wait time again. By default this value is `100ms`. -If this value doesn't work for your project, it can be configured using the `-debounce` flag. If used, this value will -be saved to your project config and become the default. +The dev server uses a technique called "debouncing" which means it doesn't +reload straight away, as there may be multiple files changed in a short amount +of time. When a trigger occurs, it waits for a set amount of time before issuing +a reload. If another trigger happens, it resets to the wait time again. By +default this value is `100ms`. If this value doesn't work for your project, it +can be configured using the `-debounce` flag. If used, this value will be saved +to your project config and become the default. ## External Dev Server -Some frameworks come with their own live-reloading server, however they will not be able to take advantage of the Wails -Go bindings. In this scenario, it is best to run a watcher script that rebuilds the project into the build directory, which -Wails will be watching. For an example, see the default svelte template that uses [rollup](https://rollupjs.org/guide/en/). - -### Create React App - -The process for a Create-React-App project is slightly more complicated. In order to support live frontend reloading the following configuration -needs to be added to your `wails.json`: - -```json - "frontend:dev:watcher": "yarn start", - "frontend:dev:serverUrl": "http://localhost:3000", -``` - -The `frontend:dev:watcher` command will start the Create-React-App development server (hosted on port `3000` typically). The `frontend:dev:serverUrl` command then -instructs Wails to serve assets from the development server when loading the frontend rather than from the build folder. In addition to the above, the -`index.html` needs to be updated with the following: - -```html - - - - - -``` - -This is required as the watcher command that rebuilds the frontend prevents Wails from injecting the required scripts. This circumvents that issue by ensuring -the scripts are always injected. With this configuration, `wails dev` can be run which will appropriately build the frontend and backend with hot-reloading enabled. -Additionally, when accessing the application from a browser the React developer tools can now be used on a non-minified version of the application for straightforward -debugging. Finally, for faster builds, `wails dev -s` can be run to skip the default building of the frontend by Wails as this is an unnecessary step. +Some frameworks come with their own live-reloading server, however they will not +be able to take advantage of the Wails Go bindings. In this scenario, it is best +to run a watcher script that rebuilds the project into the build directory, +which Wails will be watching. For an example, see the default svelte template +that uses [rollup](https://rollupjs.org/guide/en/). For +[create-react-app](https://create-react-app.dev/), it's possible to use +[this script](https://gist.github.com/int128/e0cdec598c5b3db728ff35758abdbafd) +to achieve a similar result. ## Go Module -The default Wails templates generate a `go.mod` file that contains the module name "changeme". You should change this -to something more appropriate after project generation. +The default Wails templates generate a `go.mod` file that contains the module +name "changeme". You should change this to something more appropriate after +project generation. diff --git a/website/docs/guides/crossplatform-build.mdx b/website/docs/guides/crossplatform-build.mdx deleted file mode 100644 index f6fbc0f06..000000000 --- a/website/docs/guides/crossplatform-build.mdx +++ /dev/null @@ -1,65 +0,0 @@ -# Crossplatform build with Github Actions - -To build a Wails project for all the available platforms, you need to create an application build for each operating system. One effective method to achieve this is by utilizing GitHub Actions. - -An action that facilitates building a Wails app is available at: -https://github.com/dAppServer/wails-build-action - -In case the existing action doesn't fulfill your requirements, you can select only the necessary steps from the source: -https://github.com/dAppServer/wails-build-action/blob/main/action.yml - -Below is a comprehensive example that demonstrates building an app upon the creation of a new Git tag and subsequently uploading it to the Actions artifacts: - -```yaml -name: Wails build - -on: - push: - tags: - # Match any new tag - - '*' - -env: - # Necessary for most environments as build failure can occur due to OOM issues - NODE_OPTIONS: "--max-old-space-size=4096" - -jobs: - build: - strategy: - # Failure in one platform build won't impact the others - fail-fast: false - matrix: - build: - - name: 'App' - platform: 'linux/amd64' - os: 'ubuntu-latest' - - name: 'App' - platform: 'windows/amd64' - os: 'windows-latest' - - name: 'App' - platform: 'darwin/universal' - os: 'macos-latest' - - runs-on: ${{ matrix.build.os }} - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Build wails - uses: dAppServer/wails-build-action@v2.2 - id: build - with: - build-name: ${{ matrix.build.name }} - build-platform: ${{ matrix.build.platform }} - package: false - go-version: '1.20' -``` - -This example offers opportunities for various enhancements, including: -- Caching dependencies -- Code signing -- Uploading to platforms like S3, Supabase, etc. -- Injecting secrets as environment variables -- Utilizing environment variables as build variables (such as version variable extracted from the current Git tag) diff --git a/website/docs/guides/custom-protocol-schemes.mdx b/website/docs/guides/custom-protocol-schemes.mdx deleted file mode 100644 index 216fb7100..000000000 --- a/website/docs/guides/custom-protocol-schemes.mdx +++ /dev/null @@ -1,207 +0,0 @@ -# Custom Protocol Scheme association - -Custom Protocols feature allows you to associate specific custom protocol with your app so that when users open links with this protocol, -your app is launched to handle them. This can be particularly useful to connect your desktop app with your web app. -In this guide, we'll walk through the steps to implement custom protocols in Wails app. - - -## Set Up Custom Protocol Schemes Association: -To set up custom protocol, you need to modify your application's wails.json file. -In "info" section add a "protocols" section specifying the protocols your app should be associated with. - -For example: -```json -{ - "info": { - "protocols": [ - { - "scheme": "myapp", - "description": "My App Protocol", - "role": "Editor" - } - ] - } -} -``` - -| Property | Description | -|:------------|:--------------------------------------------------------------------------------------| -| scheme | Custom Protocol scheme. e.g. myapp | -| description | Windows-only. The description. | -| role | macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole. | - -## Platform Specifics: - -### macOS -When you open custom protocol with your app, the system will launch your app and call the `OnUrlOpen` function in your Wails app. Example: -```go title="main.go" -func main() { - // Create application with options - err := wails.Run(&options.App{ - Title: "wails-open-file", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - Mac: &mac.Options{ - OnUrlOpen: func(url string) { println(url) }, - }, - Bind: []interface{}{ - app, - }, - }) - - if err != nil { - println("Error:", err.Error()) - } -} -``` - -If you want to handle universal links as well, follow this [guide](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) to add required entitlements, add required keys to Info.plist and configure `apple-app-site-association` on your website. - -Here is example for Info.plist: -```xml -NSUserActivityTypes - - NSUserActivityTypeBrowsingWeb - -``` - -And for entitlements.plist -```xml -com.apple.developer.associated-domains - - applinks:myawesomeapp.com - -``` - - -### Windows -On Windows Custom Protocol Schemes is supported only with NSIS installer. During installation, the installer will create a -registry entry for your schemes. When you open url with your app, new instance of app is launched and url is passed -as argument to your app. To handle this you should parse command line arguments in your app. Example: -```go title="main.go" -func main() { - argsWithoutProg := os.Args[1:] - - if len(argsWithoutProg) != 0 { - println("launchArgs", argsWithoutProg) - } -} -``` - -You also can enable single instance lock for your app. In this case, when you open url with your app, new instance of app is not launched -and arguments are passed to already running instance. Check single instance lock guide for details. Example: -```go title="main.go" -func main() { - // Create application with options - err := wails.Run(&options.App{ - Title: "wails-open-file", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - SingleInstanceLock: &options.SingleInstanceLock{ - UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", - OnSecondInstanceLaunch: app.onSecondInstanceLaunch, - }, - Bind: []interface{}{ - app, - }, - }) -} -``` - -### Linux -Currently, Wails doesn't support bundling for Linux. So, you need to create file associations manually. -For example if you distribute your app as a .deb package, you can create file associations by adding required files in you bundle. -You can use [nfpm](https://nfpm.goreleaser.com/) to create .deb package for your app. - -1. Create a .desktop file for your app and specify file associations there (note that `%u` is important in Exec). Example: -```ini -[Desktop Entry] -Categories=Office -Exec=/usr/bin/wails-open-file %u -Icon=wails-open-file.png -Name=wails-open-file -Terminal=false -Type=Application -MimeType=x-scheme-handler/myapp; -``` - -2. Prepare postInstall/postRemove scripts for your package. Example: -```sh -# reload desktop database to load app in list of available -update-desktop-database /usr/share/applications -``` -3. Configure nfpm to use your scripts and files. Example: -```yaml -name: "wails-open-file" -arch: "arm64" -platform: "linux" -version: "1.0.0" -section: "default" -priority: "extra" -maintainer: "FooBarCorp " -description: "Sample Package" -vendor: "FooBarCorp" -homepage: "http://example.com" -license: "MIT" -contents: -- src: ../bin/wails-open-file - dst: /usr/bin/wails-open-file -- src: ./main.desktop - dst: /usr/share/applications/wails-open-file.desktop -- src: ../appicon.svg - dst: /usr/share/icons/hicolor/scalable/apps/wails-open-file.svg -# copy icons to Yaru theme as well. For some reason Ubuntu didn't pick up fileicons from hicolor theme -- src: ../appicon.svg - dst: /usr/share/icons/Yaru/scalable/apps/wails-open-file.svg -scripts: - postinstall: ./postInstall.sh - postremove: ./postRemove.sh -``` -6. Build your .deb package using nfpm: -```sh -nfpm pkg --packager deb --target . -``` -7. Now when your package is installed, your app will be associated with custom protocol scheme. When you open url with your app, -new instance of app is launched and file path is passed as argument to your app. -To handle this you should parse command line arguments in your app. Example: -```go title="main.go" -func main() { - argsWithoutProg := os.Args[1:] - - if len(argsWithoutProg) != 0 { - println("launchArgs", argsWithoutProg) - } -} -``` - -You also can enable single instance lock for your app. In this case, when you open url with your app, new instance of app is not launched -and arguments are passed to already running instance. Check single instance lock guide for details. Example: -```go title="main.go" -func main() { - // Create application with options - err := wails.Run(&options.App{ - Title: "wails-open-file", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - SingleInstanceLock: &options.SingleInstanceLock{ - UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", - OnSecondInstanceLaunch: app.onSecondInstanceLaunch, - }, - Bind: []interface{}{ - app, - }, - }) -} -``` diff --git a/website/docs/guides/dynamic-assets.mdx b/website/docs/guides/dynamic-assets.mdx index 8d1debcef..2eb79f00a 100644 --- a/website/docs/guides/dynamic-assets.mdx +++ b/website/docs/guides/dynamic-assets.mdx @@ -1,24 +1,18 @@ # Dynamic Assets -:::info +If you want to load or generate assets for your frontend dynamically, you can +achieve that using the [AssetsHandler](../reference/options#assetshandler) +option. The AssetsHandler is a generic `http.Handler` which will be called for +any non GET request on the assets server and for GET requests which can not be +served from the bundled assets because the file is not found. -This does not work with vite v5.0.0+ and wails v2 due to changes in vite. -Changes are planned in v3 to support similar functionality under vite v5.0.0+. -If you need this feature, stay with vite v4.0.0+. -See [issue 3240](https://github.com/wailsapp/wails/issues/3240) for details - -::: - -If you want to load or generate assets for your frontend dynamically, you can achieve that using the -[AssetsHandler](../reference/options#assetshandler) option. The AssetsHandler is a generic `http.Handler` which will -be called for any non GET request on the assets server and for GET requests which can not be served from the -bundled assets because the file is not found. - -By installing a custom AssetsHandler, you can serve your own assets using a custom asset server. +By installing a custom AssetsHandler, you can serve your own assets using a +custom asset server. ## Example -In our example project, we will create a simple assets handler which will load files off disk: +In our example project, we will create a simple assets handler which will load +files off disk: ```go title=main.go {17-36,49} package main @@ -84,7 +78,8 @@ func main() { } ``` -When we run the application in dev mode using `wails dev`, we will see the following output: +When we run the application in dev mode using `wails dev`, we will see the +following output: ``` DEB | [ExternalAssetHandler] Loading 'http://localhost:3001/favicon.ico' @@ -92,18 +87,19 @@ DEB | [ExternalAssetHandler] Loading 'http://localhost:3001/favicon.ico' failed, Requesting file: favicon.ico ``` -As you can see, the assets handler is called when the default assets server is unable to serve -the `favicon.ico` file. +As you can see, the assets handler is called when the default assets server is +unable to serve the `favicon.ico` file. -If you right click the main application and select "inspect" to bring up the devtools, you can test -this feature out by typing the following into the console: +If you right click the main application and select "inspect" to bring up the +devtools, you can test this feature out by typing the following into the +console: ``` let response = await fetch('does-not-exist.txt'); ``` -This will generate an error in the devtools. We can see that the error is what we expect, returned by -our custom assets handler: +This will generate an error in the devtools. We can see that the error is what +we expect, returned by our custom assets handler: ```mdx-code-block

@@ -121,8 +117,8 @@ However, if we request `go.mod`, we will see the following output:

``` -This technique can be used to load images directly into the page. If we updated our default vanilla template and -replaced the logo image: +This technique can be used to load images directly into the page. If we updated +our default vanilla template and replaced the logo image: ```html @@ -147,7 +143,7 @@ Then we would see the following: :::warning -Exposing your filesystem in this way is a security risk. It is recommended that you properly manage access -to your filesystem. +Exposing your filesystem in this way is a security risk. It is recommended that +you properly manage access to your filesystem. ::: diff --git a/website/docs/guides/file-association.mdx b/website/docs/guides/file-association.mdx deleted file mode 100644 index 71bbff37e..000000000 --- a/website/docs/guides/file-association.mdx +++ /dev/null @@ -1,228 +0,0 @@ -# File Association - -File association feature allows you to associate specific file types with your app so that when users open those files, -your app is launched to handle them. This can be particularly useful for text editors, image viewers, or any application -that works with specific file formats. In this guide, we'll walk through the steps to implement file association in Wails app. - - -## Set Up File Association: -To set up file association, you need to modify your application's wails.json file. -In "info" section add a "fileAssociations" section specifying the file types your app should be associated with. - -For example: -```json -{ - "info": { - "fileAssociations": [ - { - "ext": "wails", - "name": "Wails", - "description": "Wails Application File", - "iconName": "wailsFileIcon", - "role": "Editor" - }, - { - "ext": "jpg", - "name": "JPEG", - "description": "Image File", - "iconName": "jpegFileIcon", - "role": "Editor" - } - ] - } -} -``` - -| Property | Description | -|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------| -| ext | The extension (minus the leading period). e.g. png | -| name | The name. e.g. PNG File | -| iconName | The icon name without extension. Icons should be located in build folder. Proper icons will be generated from .png file for both macOS and Windows | -| description | Windows-only. The description. It is displayed on the `Type` column on Windows Explorer. | -| role | macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole. | - -## Platform Specifics: - -### macOS -When you open file (or files) with your app, the system will launch your app and call the `OnFileOpen` function in your Wails app. Example: -```go title="main.go" -func main() { - // Create application with options - err := wails.Run(&options.App{ - Title: "wails-open-file", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - Mac: &mac.Options{ - OnFileOpen: func(filePaths []string) { println(filestring) }, - }, - Bind: []interface{}{ - app, - }, - }) - - if err != nil { - println("Error:", err.Error()) - } -} -``` - - -### Windows -On Windows file association is supported only with NSIS installer. During installation, the installer will create a -registry entry for your file associations. When you open file with your app, new instance of app is launched and file path is passed -as argument to your app. To handle this you should parse command line arguments in your app. Example: -```go title="main.go" -func main() { - argsWithoutProg := os.Args[1:] - - if len(argsWithoutProg) != 0 { - println("launchArgs", argsWithoutProg) - } -} -``` - -You also can enable single instance lock for your app. In this case, when you open file with your app, new instance of app is not launched -and arguments are passed to already running instance. Check single instance lock guide for details. Example: -```go title="main.go" -func main() { - // Create application with options - err := wails.Run(&options.App{ - Title: "wails-open-file", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - SingleInstanceLock: &options.SingleInstanceLock{ - UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", - OnSecondInstanceLaunch: app.onSecondInstanceLaunch, - }, - Bind: []interface{}{ - app, - }, - }) -} -``` - -### Linux -Currently, Wails doesn't support bundling for Linux. So, you need to create file associations manually. -For example if you distribute your app as a .deb package, you can create file associations by adding required files in you bundle. -You can use [nfpm](https://nfpm.goreleaser.com/) to create .deb package for your app. - -1. Create a .desktop file for your app and specify file associations there. Example: -```ini -[Desktop Entry] -Categories=Office -Exec=/usr/bin/wails-open-file %u -Icon=wails-open-file.png -Name=wails-open-file -Terminal=false -Type=Application -MimeType=application/x-wails;application/x-test -``` - -2. Create mime types file. Example: -```xml - - - - Wails Application File - - - -``` - -3. Create icons for your file types. SVG icons are recommended. -4. Prepare postInstall/postRemove scripts for your package. Example: -```sh -# reload mime types to register file associations -update-mime-database /usr/share/mime -# reload desktop database to load app in list of available -update-desktop-database /usr/share/applications -# update icons -update-icon-caches /usr/share/icons/* -``` -5. Configure nfpm to use your scripts and files. Example: -```yaml -name: "wails-open-file" -arch: "arm64" -platform: "linux" -version: "1.0.0" -section: "default" -priority: "extra" -maintainer: "FooBarCorp " -description: "Sample Package" -vendor: "FooBarCorp" -homepage: "http://example.com" -license: "MIT" -contents: -- src: ../bin/wails-open-file - dst: /usr/bin/wails-open-file -- src: ./main.desktop - dst: /usr/share/applications/wails-open-file.desktop -- src: ./application-wails-mime.xml - dst: /usr/share/mime/packages/application-x-wails.xml -- src: ./application-test-mime.xml - dst: /usr/share/mime/packages/application-x-test.xml -- src: ../appicon.svg - dst: /usr/share/icons/hicolor/scalable/apps/wails-open-file.svg -- src: ../wailsFileIcon.svg - dst: /usr/share/icons/hicolor/scalable/mimetypes/application-x-wails.svg -- src: ../testFileIcon.svg - dst: /usr/share/icons/hicolor/scalable/mimetypes/application-x-test.svg -# copy icons to Yaru theme as well. For some reason Ubuntu didn't pick up fileicons from hicolor theme -- src: ../appicon.svg - dst: /usr/share/icons/Yaru/scalable/apps/wails-open-file.svg -- src: ../wailsFileIcon.svg - dst: /usr/share/icons/Yaru/scalable/mimetypes/application-x-wails.svg -- src: ../testFileIcon.svg - dst: /usr/share/icons/Yaru/scalable/mimetypes/application-x-test.svg -scripts: - postinstall: ./postInstall.sh - postremove: ./postRemove.sh -``` -6. Build your .deb package using nfpm: -```sh -nfpm pkg --packager deb --target . -``` -7. Now when your package is installed, your app will be associated with specified file types. When you open file with your app, -new instance of app is launched and file path is passed as argument to your app. -To handle this you should parse command line arguments in your app. Example: -```go title="main.go" -func main() { - argsWithoutProg := os.Args[1:] - - if len(argsWithoutProg) != 0 { - println("launchArgs", argsWithoutProg) - } -} -``` - -You also can enable single instance lock for your app. In this case, when you open file with your app, new instance of app is not launched -and arguments are passed to already running instance. Check single instance lock guide for details. Example: -```go title="main.go" -func main() { - // Create application with options - err := wails.Run(&options.App{ - Title: "wails-open-file", - Width: 1024, - Height: 768, - AssetServer: &assetserver.Options{ - Assets: assets, - }, - BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, - SingleInstanceLock: &options.SingleInstanceLock{ - UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", - OnSecondInstanceLaunch: app.onSecondInstanceLaunch, - }, - Bind: []interface{}{ - app, - }, - }) -} -``` diff --git a/website/docs/guides/frameless.mdx b/website/docs/guides/frameless.mdx index 07d8d2d25..3f5c8354a 100644 --- a/website/docs/guides/frameless.mdx +++ b/website/docs/guides/frameless.mdx @@ -1,11 +1,14 @@ # Frameless Applications -Wails supports application that have no frames. This can be achieved by using the [frameless](../reference/options.mdx#frameless) -field in [Application Options](../reference/options.mdx#application-options). +Wails supports application that have no frames. This can be achieved by using +the [frameless](../reference/options.mdx#frameless) field in +[Application Options](../reference/options.mdx#application-options). -Wails offers a simple solution for dragging the window: Any HTML element that has the CSS style `--wails-draggable:drag` will -act as a "drag handle". This property applies to all child elements. If you need to indicate that a nested element -should not drag, then use the attribute '--wails-draggable:no-drag' on that element. +Wails offers a simple solution for dragging the window: Any HTML element that +has the CSS style `--wails-draggable:drag` will act as a "drag handle". This +property applies to all child elements. If you need to indicate that a nested +element should not drag, then use the attribute '--wails-draggable:no-drag' on +that element. ```html @@ -26,8 +29,9 @@ should not drag, then use the attribute '--wails-draggable:no-drag' on that elem ``` -For some projects, using a CSS variable may not be possible due to dynamic styling. In this case, you can use the -`CSSDragProperty` and `CSSDragValue` application options to define a property and value that will be used to indicate +For some projects, using a CSS variable may not be possible due to dynamic +styling. In this case, you can use the `CSSDragProperty` and `CSSDragValue` +application options to define a property and value that will be used to indicate draggable regions: ```go title=main.go @@ -87,6 +91,7 @@ func main() { :::info Fullscreen -If you allow your application to go fullscreen, this drag functionality will be disabled. +If you allow your application to go fullscreen, this drag functionality will be +disabled. ::: diff --git a/website/docs/guides/frontend.mdx b/website/docs/guides/frontend.mdx index 2c3c78e42..68b5c1620 100644 --- a/website/docs/guides/frontend.mdx +++ b/website/docs/guides/frontend.mdx @@ -2,8 +2,9 @@ ## Script Injection -When Wails serves your `index.html`, by default, it will inject 2 script entries into the `` tag to load `/wails/ipc.js` -and `/wails/runtime.js`. These files install the bindings and runtime respectively. +When Wails serves your `index.html`, by default, it will inject 2 script entries +into the `` tag to load `/wails/ipc.js` and `/wails/runtime.js`. These +files install the bindings and runtime respectively. The code below shows where these are injected by default: @@ -31,7 +32,8 @@ The code below shows where these are injected by default: ### Overriding Default Script Injection -To provide more flexibility to developers, there is a meta tag that may be used to customise this behaviour: +To provide more flexibility to developers, there is a meta tag that may be used +to customise this behaviour: ```html @@ -45,7 +47,7 @@ The options are as follows: | noautoinjectipc | Disable the autoinjection of `/wails/ipc.js` | | noautoinject | Disable all autoinjection of scripts | -Multiple options may be used provided they are comma separated. +Multiple options may be used provided they are comma seperated. This code is perfectly valid and operates the same as the autoinjection version: diff --git a/website/docs/guides/ides.mdx b/website/docs/guides/ides.mdx index 5fe5a7bb9..00956a0ce 100644 --- a/website/docs/guides/ides.mdx +++ b/website/docs/guides/ides.mdx @@ -1,9 +1,10 @@ # IDEs -Wails aims to provide a great development experience. To that aim, we now support generating IDE specific configuration -to provide smoother project setup. +Wails aims to provide a great development experience. To that aim, we now +support generating IDE specific configuration to provide smoother project setup. -Currently, we support [Visual Studio Code](https://code.visualstudio.com/) and [Goland](https://www.jetbrains.com/go/). +Currently, we support [Visual Studio Code](https://code.visualstudio.com/) but +aim to support other IDEs such as Goland. ## Visual Studio Code @@ -16,10 +17,13 @@ Currently, we support [Visual Studio Code](https://code.visualstudio.com/) and [

``` -When generating a project using the `-ide vscode` flags, IDE files will be created alongside the other project files. -These files are placed into the `.vscode` directory and provide the correct configuration for debugging your application. +When generating a project using the `-ide vscode` flags, IDE files will be +created alongside the other project files. These files are placed into the +`.vscode` directory and provide the correct configuration for debugging your +application. -The 2 files generated are `tasks.json` and `launch.json`. Below are the files generated for the default vanilla project: +The 2 files generated are `tasks.json` and `launch.json`. Below are the files +generated for the default vanilla project: ```json title="tasks.json" { @@ -66,8 +70,9 @@ The 2 files generated are `tasks.json` and `launch.json`. Below are the files ge ### Configuring the install and build steps -The `tasks.json` file is simple for the default project as there is no `npm install` or `npm run build` step needed. -For projects that have a frontend build step, such as the svelte template, we would need to edit `tasks.json` to +The `tasks.json` file is simple for the default project as there is no +`npm install` or `npm run build` step needed. For projects that have a frontend +build step, such as the svelte template, we would need to edit `tasks.json` to add the install and build steps: ```json title="tasks.json" @@ -126,6 +131,7 @@ add the install and build steps: :::info Future Enhancement -In the future, we hope to generate a `tasks.json` that includes the install and build steps automatically. +In the future, we hope to generate a `tasks.json` that includes the install and +build steps automatically. ::: diff --git a/website/docs/guides/linux-distro-support.mdx b/website/docs/guides/linux-distro-support.mdx index b64ed0c03..e6b8325ab 100644 --- a/website/docs/guides/linux-distro-support.mdx +++ b/website/docs/guides/linux-distro-support.mdx @@ -2,9 +2,10 @@ ## Overview -Wails offers Linux support but providing installation instructions for all available distributions is an impossible task. -Instead, Wails tries to determine if the packages you need to develop applications are available via your system's package -manager. Currently, we support the following package managers: +Wails offers Linux support but providing installation instructions for all +available distributions is an impossible task. Instead, Wails tries to determine +if the packages you need to develop applications are available via your system's +package manager. Currently, we support the following package managers: - apt - dnf @@ -16,11 +17,13 @@ manager. Currently, we support the following package managers: ## Adding package names -There may be circumstances where your distro uses one of the supported package managers but the package name -is different. For example, you may use an Ubuntu derivative, but the package name for gtk may be different. Wails -attempts to find the correct package by iterating through a list of package names. -The list of packages are stored in the packagemanager specific file in the `v2/internal/system/packagemanager` -directory. In our example, this would be `v2/internal/system/packagemanager/apt.go`. +There may be circumstances where your distro uses one of the supported package +managers but the package name is different. For example, you may use an Ubuntu +derivative, but the package name for gtk may be different. Wails attempts to +find the correct package by iterating through a list of package names. The list +of packages are stored in the packagemanager specific file in the +`v2/internal/system/packagemanager` directory. In our example, this would be +`v2/internal/system/packagemanager/apt.go`. In this file, the list of packages are defined by the `Packages()` method: @@ -49,8 +52,8 @@ func (a *Apt) Packages() packagemap { } ``` -Let's assume that in our linux distro, `libgtk-3` is packaged under the name `lib-gtk3-dev`. -We could add support for this by adding the following line: +Let's assume that in our linux distro, `libgtk-3` is packaged under the name +`lib-gtk3-dev`. We could add support for this by adding the following line: ```go {5} func (a *Apt) Packages() packagemap { @@ -82,8 +85,10 @@ func (a *Apt) Packages() packagemap { To add a new package manager, perform the following steps: -- Create a new file in `v2/internal/system/packagemanager` called `.go`, where `` is the name of the package manager. -- Define a struct that conforms to the package manager interface defined in `pm.go`: +- Create a new file in `v2/internal/system/packagemanager` called `.go`, + where `` is the name of the package manager. +- Define a struct that conforms to the package manager interface defined in + `pm.go`: ```go type PackageManager interface { @@ -96,15 +101,19 @@ type PackageManager interface { ``` - `Name()` should return the name of the package manager -- `Packages()` should return a `packagemap`, that provides candidate filenames for dependencies +- `Packages()` should return a `packagemap`, that provides candidate filenames + for dependencies - `PackageInstalled()` should return `true` if the given package is installed -- `PackageAvailable()` should return `true` if the given package is not installed but available for installation -- `InstallCommand()` should return the exact command to install the given package name +- `PackageAvailable()` should return `true` if the given package is not + installed but available for installation +- `InstallCommand()` should return the exact command to install the given + package name Take a look at the other package managers code to get an idea how this works. :::info Remember -If you add support for a new package manager, don't forget to also update this page! +If you add support for a new package manager, don't forget to also update this +page! ::: diff --git a/website/docs/guides/linux.mdx b/website/docs/guides/linux.mdx index 2cfc2e62a..611fc7da5 100644 --- a/website/docs/guides/linux.mdx +++ b/website/docs/guides/linux.mdx @@ -1,11 +1,13 @@ # Linux -This page has miscellaneous guides related to developing Wails applications for Linux. +This page has miscellaneous guides related to developing Wails applications for +Linux. ## Video tag doesn't fire "ended" event -When using a video tag, the "ended" event is not fired when the video is finished playing. This is a bug -in WebkitGTK, however you can use the following workaround to fix it: +When using a video tag, the "ended" event is not fired when the video is +finished playing. This is a bug in WebkitGTK, however you can use the following +workaround to fix it: ```js videoTag.addEventListener("timeupdate", (event) => { @@ -18,109 +20,3 @@ videoTag.addEventListener("timeupdate", (event) => { Source: [Lyimmi](https://github.com/Lyimmi) on the [discussions board](https://github.com/wailsapp/wails/issues/1729#issuecomment-1212291275) - -## GStreamer error when using Audio or Video elements - -If you are seeing the following error when including `