-
- 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/generate-sponsor-image.yml b/.github/workflows/generate-sponsor-image.yml
deleted file mode 100644
index 56548ab43..000000000
--- a/.github/workflows/generate-sponsor-image.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Generate Sponsor Image
-
-on:
- workflow_dispatch:
- schedule:
- - cron: "0 0 * * *"
-
-jobs:
- update-sponsors:
- name: Update Sponsors
- runs-on: ubuntu-latest
- if: github.repository == 'wailsapp/wails'
- steps:
- - uses: actions/checkout@v3
-
- - name: Set Node
- uses: actions/setup-node@v2
- with:
- node-version: 20.x
-
- - name: Update Sponsors
- run: cd scripts/sponsors && chmod 755 ./generate-sponsor-image.sh && ./generate-sponsor-image.sh
- env:
- SPONSORKIT_GITHUB_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
- SPONSORKIT_GITHUB_LOGIN: wailsapp
-
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v6
- 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]
- 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/latest-pre.yml b/.github/workflows/latest-pre.yml
new file mode 100644
index 000000000..d77f9792f
--- /dev/null
+++ b/.github/workflows/latest-pre.yml
@@ -0,0 +1,32 @@
+name: latest pre-release
+on:
+ push:
+ tags:
+ - '**-pre**'
+jobs:
+
+ build:
+ name: Test Build Latest Pre-Release
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ steps:
+
+ - name: Set up Go 1.13
+ uses: actions/setup-go@v1
+ with:
+ go-version: 1.13
+ id: go
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v1
+
+ - name: Get dependencies
+ run: |
+ go get -v -d ./...
+ - name: Build
+ run: go build -v ./cmd/wails
+
+ - name: Test
+ run: ./wails version
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..0247e85c2
--- /dev/null
+++ b/.github/workflows/pr.yml
@@ -0,0 +1,32 @@
+name: pr
+on:
+ pull_request:
+ branches:
+ - develop
+jobs:
+
+ build:
+ name: Test Build PR
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ steps:
+
+ - name: Set up Go 1.13
+ uses: actions/setup-go@v1
+ with:
+ go-version: 1.13
+ id: go
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v1
+
+ - name: Get dependencies
+ run: |
+ go get -v -d ./...
+ - name: Build
+ run: go build -v ./cmd/wails
+
+ - name: Test
+ run: ./wails version
\ No newline at end of file
diff --git a/.github/workflows/projects.yml b/.github/workflows/projects.yml
deleted file mode 100644
index 3b81c64e7..000000000
--- a/.github/workflows/projects.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-name: Update Project
-
-on:
- project_card:
- types: [ moved ]
-
-jobs:
- projectcardautolabel_job:
- runs-on: ubuntu-latest
- if: github.repository == 'wailsapp/wails'
- steps:
- - name: Run ProjectCard AutoLabel
- id: runprojectcardautolabel
- uses: Matticusau/projectcard-autolabel@v1.0.0
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- autolabel-config: '[{"column": "TODO", "add_labels":["TODO"], "remove_labels":["In Progress", "Ready For Testing"]},{"column":"In progress", "add_labels":["In Progress"], "remove_labels":["TODO", "Ready For Testing"]},{"column":"In review", "add_labels":["Ready For Testing"], "remove_labels":["TODO", "In Progress"]}, {"column":"Done", "add_labels":["Done"], "remove_labels":["TODO", "In Progress", "Ready For Testing"]}]'
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..feddbcce5
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,34 @@
+name: release
+on:
+ push:
+ branches:
+ - master
+ tags:
+ - '!**pre**'
+jobs:
+
+ build:
+ name: Test Build Latest Release
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ steps:
+
+ - name: Set up Go 1.13
+ uses: actions/setup-go@v1
+ with:
+ go-version: 1.13
+ id: go
+
+ - name: Check out code into the Go module directory
+ uses: actions/checkout@v1
+
+ - name: Get dependencies
+ run: |
+ go get -v -d ./...
+ - name: Build
+ run: go build -v ./cmd/wails
+
+ - name: Test
+ run: ./wails version
\ No newline at end of file
diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml
deleted file mode 100644
index 2c97b628b..000000000
--- a/.github/workflows/runtime.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-name: Runtime
-on:
- push:
- branches:
- - v2-alpha
- paths:
- - 'v2/internal/frontend/runtime/**'
-jobs:
- rebuild-runtime:
- name: Rebuild the runtime
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-node@v2
- with:
- node-version: 14.17.6
- cache: 'npm'
- cache-dependency-path: v2/internal/frontend/runtime/package-lock.json
- - run: npm install
- working-directory: v2/internal/frontend/runtime
- - run: npm run build
- working-directory: v2/internal/frontend/runtime
-
- - name: Commit changes
- uses: devops-infra/action-commit-push@master
- with:
- github_token: "${{ secrets.GITHUB_TOKEN }}"
- commit_prefix: "[AUTO]"
- commit_message: "The runtime was rebuilt"
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
deleted file mode 100644
index 0aa06f11e..000000000
--- a/.github/workflows/sync-translated-documents.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Sync Translations
-
-on:
- workflow_dispatch:
- schedule:
- - cron: "0 0 * * *"
-
-jobs:
- sync-translated-documents:
- 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: 20.x
-
- - name: Install Task
- uses: arduino/setup-task@v1
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Sync Translated Documents
- env:
- CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- working-directory: ./website
- run: task crowdin:pull
-
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v4
- with:
- commit-message: "docs: sync translations"
- title: "docs: sync translations"
- body: "- [x] Sync translated documents"
- branch: chore/sync-translations
- labels: translation
- delete-branch: true
- draft: true
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
deleted file mode 100644
index 69d6c3e48..000000000
--- a/.github/workflows/upload-source-documents.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Upload Source Documents
-
-on:
- push:
- branches: [master]
- workflow_dispatch:
-
-jobs:
- push_files_to_crowdin:
- name: Push files to Crowdin
- if: github.repository == 'wailsapp/wails'
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Verify Changed files
- id: changed-files
- uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
- with:
- files: |
- website/**/*.mdx
- website/**/*.md
- website/**/*.json
-
- - name: Setup Nodejs
- uses: actions/setup-node@v2
- with:
- node-version: 20.x
-
- - name: Setup Task
- uses: arduino/setup-task@v1
- with:
- version: 3.x
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Upload source documents
- if: steps.changed-files.outputs.any_changed == 'true'
- env:
- CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- working-directory: ./website
- run: task crowdin:push
diff --git a/.gitignore b/.gitignore
index e7888b44a..87db40c4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,15 +27,3 @@ v2/pkg/parser/testproject/frontend/wails
v2/test/kitchensink/frontend/public
v2/test/kitchensink/build/darwin/desktop/kitchensink
v2/test/kitchensink/frontend/package.json.md5
-.idea/
-v2/cmd/wails/internal/commands/initialise/templates/testtemplates/
-.env
-/website/static/img/.cache.json
-
-/v3/.task
-/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/.goreleaser.yml b/.goreleaser.yml
new file mode 100644
index 000000000..a5769985b
--- /dev/null
+++ b/.goreleaser.yml
@@ -0,0 +1,34 @@
+# This is an example goreleaser.yaml file with some sane defaults.
+# Make sure to check the documentation at http://goreleaser.com
+
+builds:
+- env:
+ - CGO_ENABLED=0
+ goos:
+ - windows
+ - linux
+ - darwin
+ goarch:
+ - 386
+ - amd64
+ ignore:
+ - goos: darwin
+ goarch: 386
+ main: ./cmd/wails/main.go
+archive:
+ replacements:
+ darwin: Darwin
+ linux: Linux
+ windows: Windows
+ 386: i386
+ amd64: x86_64
+checksum:
+ name_template: 'checksums.txt'
+snapshot:
+ name_template: "{{ .Tag }}-next"
+changelog:
+ sort: asc
+ filters:
+ exclude:
+ - '^docs:'
+ - '^test:'
diff --git a/.hound.yml b/.hound.yml
new file mode 100644
index 000000000..c74b4a197
--- /dev/null
+++ b/.hound.yml
@@ -0,0 +1,6 @@
+jshint:
+ config_file: .jshintrc
+eslint:
+ enabled: true
+ config_file: .eslintrc
+ ignore_file: .eslintignore
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 000000000..53b202cb9
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
deleted file mode 100644
index 52b962c55..000000000
--- a/.prettierignore
+++ /dev/null
@@ -1,3 +0,0 @@
-website
-v2
-v3
\ No newline at end of file
diff --git a/.prettierrc.yml b/.prettierrc.yml
deleted file mode 100644
index 685d8b6e7..000000000
--- a/.prettierrc.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-overrides:
- - files:
- - "**/*.md"
- options:
- printWidth: 80
- proseWrap: always
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/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000..51158b5ba
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,48 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Test cmd package",
+ "type": "go",
+ "request": "launch",
+ "mode": "test",
+ "program": "${workspaceFolder}/cmd/"
+ },
+ {
+ "name": "Wails Init",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "${workspaceFolder}/cmd/wails/main.go",
+ "env": {},
+ "cwd": "/tmp",
+ "args": [
+ "init",
+ "-name",
+ "runtime",
+ "-dir",
+ "runtime",
+ "-output",
+ "runtime",
+ "-template",
+ "vuebasic"
+ ]
+ },
+ {
+ "name": "Wails Update Pre",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "${workspaceFolder}/cmd/wails/main.go",
+ "env": {},
+ "cwd": "/tmp",
+ "args": [
+ "update",
+ "-pre"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..da2d84ba6
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,8 @@
+{
+ "go.formatTool": "goimports",
+ "eslint.alwaysShowStatus": true,
+ "files.associations": {
+ "__locale": "c",
+ "ios": "c"
+ }
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62b09b25f..e74282965 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1 +1,34 @@
-The current changelog may be found at: https://wails.io/changelog/
+
+2019-07-20 **v0.17.6-pre**
+* Significant refactor of runtime
+* Removed wailsbridge file - now a unified approach taken
+* Fixed React on Windows - Thanks [Florian Didran](https://github.com/fdidron)!
+
+2019-06-18 **v0.16.0**
+* React template FTW! - Thanks [admin_3.exe](https://github.com/bh90210)!
+* Updated contributors
+* Arch Linux detection without lsb-release
+* Removed deprecated methods for dealing with JS/CSS in the backend
+
+2019-05-29 **v0.14.11-pre**
+* Windows fix for spinner
+
+2019-05-29 **v0.14.10-pre**
+* Windows fix for Vuetify
+
+2019-05-29 **v0.14.9-pre**
+* Vuetify project template 🎉
+
+2019-05-29 **v0.14.8-pre**
+* Updated Ubuntu npm install command
+
+2019-05-22 **v0.14.7-pre**
+* New projects are built automatically when initialised
+* Go 1.12 is now a minimum requirement
+
+2019-05-21 **v0.14.6-pre**
+* Hotfix for module dependency issue
+
+2019-05-20 **v0.14.5-pre**
+* Added developer tooling - New Template Generator
+* Documentation fixes - Thanks [admin_3.exe](https://github.com/bh90210)!
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index aa53c412a..000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1 +0,0 @@
-The current Contribution Guidelines can be found at: https://wails.io/community-guide
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index a7b5a60ab..7dc5b1c89 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,2 +1,43 @@
+# Contributors
-The latest contributors list may be found at: https://wails.io/credits#contributors
\ No newline at end of file
+Wails is what it is because of the time and effort given by these great people. A huge thank you to each and every one!
+
+ * [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot)
+ * [Qais Patankar](https://github.com/qaisjp)
+ * [Anthony Lee](https://github.com/alee792)
+ * [Adrian Lanzafame](https://github.com/lanzafame)
+ * [Mattn](https://github.com/mattn)
+ * [0xflotus](https://github.com/0xflotus)
+ * [Michael D Henderson](https://github.com/mdhender)
+ * [fred2104](https://github.com/fishfishfish2104)
+ * [intelwalk](https://github.com/intelwalk)
+ * [Mark Stenglein](https://github.com/ocelotsloth)
+ * [admin_3.exe](https://github.com/bh90210)
+ * [iceleo-com](https://github.com/iceleo-com)
+ * [fallendusk](https://github.com/fallendusk)
+ * [Nikolai Zimmermann](https://github.com/Chronophylos)
+ * [Toyam Cox](https://github.com/Vaelatern)
+ * [Robin Eklind](https://github.com/mewmew)
+ * [Kris Raney](https://github.com/kraney)
+ * [Jack Mordaunt](https://github.com/JackMordaunt)
+ * [Michael Hipp](https://github.com/MichaelHipp)
+ * [Travis McLane](https://github.com/tmclane)
+ * [Reuben Thomas-Davis](https://github.com/Rested)
+ * [Jarek](https://github.com/Jarek-SRT)
+ * [Konez2k](https://github.com/konez2k)
+ * [msms](https://github.com/sayuthisobri)
+ * [dedo1911](https://github.com/dedo1911)
+ * [Florian Didron](https://github.com/fdidron)
+ * [Christopher Murphy](https://github.com/Splode)
+ * [Zámbó, Levente](https://github.com/Lyimmi)
+ * [artem](https://github.com/Unix4ever)
+ * [Tim Kipp](https://github.com/timkippdev)
+ * [Dmitry Gomzyakov](https://github.com/kyoto44)
+ * [Arthur Wiebe](https://github.com/artooro)
+ * [Ilgıt Yıldırım](https://github.com/ilgityildirim)
+ * [Altynbek](https://github.com/gelleson)
+ * [Kyle](https://github.com/kmuchmore)
+ * [Balakrishna Prasad Ganne](https://github.com/aayush420)
+ * [Charaf Rezrazi](https://github.com/Rezrazi)
+ * [misitebao](https://github.com/misitebao)
+ * [Elie Grenon](https://github.com/DrunkenPoney)
\ No newline at end of file
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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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
-
-
-
-
-
-
-
-
-
-## 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
-
-[](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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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
-
-[](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
-
-[](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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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
-
-[](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
-
-[](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
deleted file mode 100644
index ffd9f8103..000000000
--- a/README.ja.md
+++ /dev/null
@@ -1,152 +0,0 @@
-Wails
-
-
-
-
-
-
- GoとWebの技術を用いてデスクトップアプリケーションを構築します。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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)
-
-
-
-
-
-## 目次
-
-- [目次](#目次)
-- [はじめに](#はじめに)
-- [特徴](#特徴)
- - [ロードマップ](#ロードマップ)
-- [始め方](#始め方)
-- [スポンサー](#スポンサー)
-- [FAQ](#faq)
-- [スター数の推移](#スター数の推移)
-- [コントリビューター](#コントリビューター)
-- [ライセンス](#ライセンス)
-- [インスピレーション](#インスピレーション)
-
-
-## はじめに
-
-Go プログラムにウェブインタフェースを提供する従来の方法は内蔵のウェブサーバを経由するものですが、 Wails では異なるアプローチを提供します。
-Wails では Go のコードとウェブフロントエンドを単一のバイナリにまとめる機能を提供します。
-また、プロジェクトの作成、コンパイル、ビルドを行うためのツールが提供されています。あなたがすべきことは創造性を発揮することです!
-
-## 特徴
-
-- バックエンドには Go を利用しています
-- 使い慣れたフロントエンド技術を利用して UI を構築できます
-- あらかじめ用意されたテンプレートを利用することで、リッチなフロントエンドを備えた Go プログラムを素早く作成できます
-- JavaScript から Go のメソッドを簡単に呼び出すことができます
-- あなたの書いた Go の構造体やメソットに応じた TypeScript の定義が自動生成されます
-- ネイティブのダイアログとメニューが利用できます
-- ネイティブなダーク/ライトモードをサポートします
-- モダンな半透明や「frosted window」エフェクトをサポートしています
-- Go と JavaScript 間で統一されたイベント・システムを備えています
-- プロジェクトを素早く生成して構築する強力な cli ツールを用意しています
-- マルチプラットフォームに対応しています
-- ネイティブなレンダリングエンジンを使用しています - _つまりブラウザを埋め込んでいるわけではありません!_
-
-### ロードマップ
-
-プロジェクトのロードマップは[こちら](https://github.com/wailsapp/wails/discussions/1484)になります。
-機能拡張のリクエストを出す前にご覧ください。
-
-## 始め方
-
-インストール方法は[公式サイト](https://wails.io/docs/gettingstarted/installation)に掲載されています。
-
-## スポンサー
-
-このプロジェクトは、以下の方々・企業によって支えられています。
-
-
-## FAQ
-
-- Electron の代替品になりますか?
-
- それはあなたの求める要件によります。Wails は Go プログラマーが簡単に軽量のデスクトップアプリケーションを作成したり、既存のアプリケーションにフロントエンドを追加できるように設計されています。
- Wails v2 ではメニューやダイアログといったネイティブな要素を提供するようになったため、軽量な Electron の代替となりつつあります。
-
-- このプロジェクトは誰に向けたものですか?
-
- HTML/JS/CSS のフロントエンド技術をアプリケーションにバンドルさせることで、サーバーを作成してブラウザ経由で表示させることなくアプリケーションを利用したい Go プログラマにおすすめです。
-
-- 名前の由来を教えて下さい
-
- WebView を見たとき、私はこう思いました。
- 「私が本当に欲しいのは、WebView アプリを構築するためのツールであり、Ruby に対する Rails のようなものである」と。
- そのため、最初は言葉遊びのつもりでした(Webview on Rails)。
- また、私の[出身国](https://en.wikipedia.org/wiki/Wales)の英語名と同音異義語でもあります。そしてこの名前が定着しました。
-
-## スター数の推移
-
-[](https://star-history.com/#wailsapp/wails&Date)
-
-## コントリビューター
-
-貢献してくれた方のリストが大きくなりすぎて、readme に入りきらなくなりました!
-このプロジェクトに貢献してくれた素晴らしい方々のページは[こちら](https://wails.io/credits#contributors)です。
-
-## ライセンス
-
-[](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.ko.md b/README.ko.md
deleted file mode 100644
index 075e04229..000000000
--- a/README.ko.md
+++ /dev/null
@@ -1,155 +0,0 @@
-Wails
-
-
-
-
-
-
- Go & Web 기술을 사용하여 데스크탑 애플리케이션을 빌드하세요.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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)
-
-
-
-
-
-## 목차
-
-- [목차](#목차)
-- [소개](#소개)
-- [기능](#기능)
- - [로드맵](#로드맵)
-- [시작하기](#시작하기)
-- [스폰서](#스폰서)
-- [FAQ](#faq)
-- [Stargazers 성장 추세](#stargazers-성장-추세)
-- [기여자](#기여자)
-- [라이센스](#라이센스)
-- [영감](#영감)
-
-## 소개
-
-Go 프로그램에 웹 인터페이스를 제공하는 전통적인 방법은 내장 웹 서버를 이용하는 것입니다.
-Wails는 다르게 접근합니다: Go 코드와 웹 프론트엔드를 단일 바이너리로 래핑하는 기능을 제공합니다.
-프로젝트 생성, 컴파일 및 번들링을 처리하여 이를 쉽게 수행할 수 있도록 도구가 제공됩니다.
-창의력을 발휘하기만 하면 됩니다!
-
-## 기능
-
-- 백엔드에 표준 Go 사용
-- 이미 익숙한 프론트엔드 기술을 사용하여 UI 구축
-- 사전 구축된 템플릿을 사용하여 Go 프로그램을 위한 풍부한 프론트엔드를 빠르게 생성
-- Javascript에서 Go 메서드를 쉽게 호출
-- Go 구조체 및 메서드에 대한 자동 생성된 Typescript 정의
-- 기본 대화 및 메뉴
-- 네이티브 다크/라이트 모드 지원
-- 최신 반투명도 및 "반투명 창" 효과 지원
-- Go와 Javascript 간의 통합 이벤트 시스템
-- 프로젝트를 빠르게 생성하고 구축하는 강력한 CLI 도구
-- 멀티플랫폼
-- 기본 렌더링 엔진 사용 - _내장 브라우저 없음_!
-
-### 로드맵
-
-프로젝트 로드맵은 [여기](https://github.com/wailsapp/wails/discussions/1484)에서
-확인할 수 있습니다. 개선 요청을 하기 전에 이것을 참조하십시오.
-
-## 시작하기
-
-설치 지침은
-[공식 웹사이트](https://wails.io/docs/gettingstarted/installation)에 있습니다.
-
-## 스폰서
-
-이 프로젝트는 친절한 사람들 / 회사들이 지원합니다.
-
-
-## FAQ
-
-- 이것은 Electron의 대안인가요?
-
- 요구 사항에 따라 다릅니다. Go 프로그래머가 쉽게 가벼운 데스크톱 애플리케이션을
- 만들거나 기존 애플리케이션에 프론트엔드를 추가할 수 있도록 설계되었습니다.
- Wails는 메뉴 및 대화 상자와 같은 기본 요소를 제공하므로 가벼운 Electron 대안으로
- 간주될 수 있습니다.
-
-- 이 프로젝트는 누구를 대상으로 하나요?
-
- 서버를 생성하고 이를 보기 위해 브라우저를 열 필요 없이 HTML/JS/CSS 프런트엔드를
- 애플리케이션과 함께 묶고자 하는 프로그래머를 대상으로 합니다.
-
-- Wails 이름의 의미는 무엇인가요?
-
- WebView를 보았을 때 저는 "내가 정말로 원하는 것은 WebView 앱을 구축하기 위한
- 도구를 사용하는거야. 마치 Ruby on Rails 처럼 말이야."라고 생각했습니다.
- 그래서 처음에는 말장난(Webview on Rails)이었습니다.
- [국가](https://en.wikipedia.org/wiki/Wales)에 대한 영어 이름의 동음이의어이기도 하여 정했습니다.
-
-## Stargazers 성장 추세
-
-[](https://star-history.com/#wailsapp/wails&Date)
-
-## 기여자
-
-기여자 목록이 추가 정보에 비해 너무 커지고 있습니다! 이 프로젝트에 기여한 모든 놀라운 사람들은
-[여기](https://wails.io/credits#contributors)에 자신의 페이지를 가지고 있습니다.
-
-## 라이센스
-
-[](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.md b/README.md
index 5ab9309b4..5c279e02e 100644
--- a/README.md
+++ b/README.md
@@ -1,159 +1,161 @@
-
+
-
- Build desktop applications using Go & Web Technologies.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ A framework for building desktop applications using Go & Web Technologies.
+
+
+
+
+
+
+
+
+
-
-
-
+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!
-[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)
-
-
-
-
-
-## Table of Contents
-
-- [Table of Contents](#table-of-contents)
-- [Introduction](#introduction)
-- [Features](#features)
- - [Roadmap](#roadmap)
-- [Getting Started](#getting-started)
-- [Sponsors](#sponsors)
-- [FAQ](#faq)
-- [Stargazers over time](#stargazers-over-time)
-- [Contributors](#contributors)
-- [License](#license)
-- [Inspiration](#inspiration)
-
-## 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 official docs can be found at [https://wails.app](https://wails.app).
## Features
-- Use standard Go for the backend
-- Use any frontend technology you are already familiar with to build your UI
-- Quickly create rich frontends for your Go programs using pre-built templates
-- Easily call Go methods from Javascript
-- Auto-generated Typescript definitions for your Go structs and methods
-- Native Dialogs & Menus
-- Native Dark / Light mode support
-- Supports modern translucency and "frosted window" effects
-- Unified eventing system between Go and Javascript
-- Powerful cli tool to quickly generate and build your projects
+- Use standard Go libraries/frameworks for the backend
+- Use any frontend technology to build your UI
+- Quickly create Vue, Vuetify or React frontends for your Go programs
+- Expose Go methods/functions to the frontend via a single bind command
+- Uses native rendering engines - no embedded browser
+- Shared events system
+- Native file dialogs
+- Powerful cli tool
- Multiplatform
-- Uses native rendering engines - _no embedded browser_!
-### Roadmap
-The project roadmap may be found [here](https://github.com/wailsapp/wails/discussions/1484). Please consult
-it before creating an enhancement request.
+## Installation
-## Getting Started
+Wails uses cgo to bind to the native rendering engines so a number of platform dependent libraries are needed as well as an installation of Go. The basic requirements are:
-The installation instructions are on the [official website](https://wails.io/docs/gettingstarted/installation).
+- Go 1.13
+- npm
-## Sponsors
+### MacOS
-This project is supported by these kind people / companies:
-
+Make sure you have the xcode command line tools installed. This can be done by running:
-## Powered By
+`xcode-select --install`
-[](https://jb.gg/OpenSource)
+### Linux
+
+#### Debian/Ubuntu
+
+`sudo apt install libgtk-3-dev libwebkit2gtk-4.0-dev`
+
+_Debian: 8, 9, 10_
+
+_Ubuntu: 16.04, 18.04, 19.04_
+
+_Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_, Pop!_OS
+
+#### Arch Linux / ArchLabs / Ctlos Linux
+
+`sudo pacman -S webkit2gtk gtk3`
+
+_Also succesfully test on: Manjaro & ArcoLinux_
+
+#### Centos
+
+`sudo yum install webkitgtk3-devel gtk3-devel`
+
+_CentOS 6, 7_
+
+#### Fedora
+
+`sudo yum install webkit2gtk3-devel gtk3-devel`
+
+_Fedora 29, 30_
+
+#### VoidLinux & VoidLinux-musl
+
+`xbps-install gtk+3-devel webkit2gtk-devel`
+
+#### Gentoo
+
+`sudo emerge gtk+:3 webkit-gtk`
+
+### Windows
+
+Windows requires gcc and related tooling. The recommended download is from [http://tdm-gcc.tdragon.net/download](http://tdm-gcc.tdragon.net/download). Once this is installed, you are good to go.
+
+## Installation
+
+**Ensure Go modules are enabled: GO111MODULE=on and go/bin is in your PATH variable.**
+
+Installation is as simple as running the following command:
+
+
+go get -u github.com/wailsapp/wails/cmd/wails
+
+
+## Next Steps
+
+It is recommended at this stage to read the comprehensive documentation at [https://wails.app](https://wails.app).
## FAQ
-- Is this an alternative to Electron?
+ * 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. Whilst Wails does not currently offer hooks into native elements such as menus, this may change in the future.
-- Who is this project aimed at?
+ * 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?
+ * 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
+## Shoulders of Giants
-
-
-
-
-
-
-
+Without the following people, this project would never have existed:
-## Contributors
+ * [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - His support and feedback has been immense. More patience than you can throw a stick at (Not long now Dustin!).
+ * [Serge Zaitsev](https://github.com/zserge) - Creator of [Webview](https://github.com/zserge/webview) which Wails uses for the windowing.
-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).
+And without [these people](CONTRIBUTORS.md), it wouldn't be what it is today. A huge thank you to each and every one of you!
-## License
+Special Mentions:
-[](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large)
-
-## Inspiration
+ * [Byron](https://github.com/bh90210) - At times, Byron has single handedly kept this project alive. Without his incredible input, we never would have got to v1.
This project was mainly coded to the following albums:
-- [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)
+ * [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)
+
+## Licensing
+
+[](https://app.fossa.io/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large)
+
+## Special Thanks
+
+
+ A *huge* thanks to Pace for sponsoring the project and helping the efforts to get Wails ported to Apple Silicon!
+ If you are looking for a Project Management tool that's powerful but quick and easy to use, check them out!
+
+
+
+ A special thank you to JetBrains for donating licenses to us!
+ Please click the logo to let them know your appreciation!
+
+
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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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
-
-[](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
-
-[](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 технологии
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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://star-history.com/#wailsapp/wails&Date)
-
-## Контрибьюторы
-
-Список участников слишком велик для README! У всех замечательных людей, которые внесли свой вклад в этот
-проект, есть своя [страничка](https://wails.io/credits#contributors).
-
-## Лицензия
-
-[](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.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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
-
-
-
-
-
-
-
-
-
-## 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
-
-[](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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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
-
-
-
-
-
-
-
-
-
-## 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
-
-[](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
deleted file mode 100644
index 4c09d0c45..000000000
--- a/README.zh-Hans.md
+++ /dev/null
@@ -1,144 +0,0 @@
-Wails
-
-
-
-
-
-
- 使用 Go 和 Web 技术构建桌面应用程序。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[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)
-
-
-
-
-
-## 内容目录
-
-- [内容目录](#内容目录)
-- [项目介绍](#项目介绍)
-- [功能](#功能)
- - [路线图](#路线图)
-- [快速入门](#快速入门)
-- [赞助商](#赞助商)
-- [常见问题](#常见问题)
-- [星星增长趋势](#星星增长趋势)
-- [贡献者](#贡献者)
-- [许可证](#许可证)
-- [灵感](#灵感)
-
-## 项目介绍
-
-为 Go 程序提供 Web 界面的传统方法是通过内置 Web 服务器。Wails 提供了一种不同的方法:它提供了将 Go 代码和 Web
-前端一起打包成单个二进制文件的能力。通过提供的工具,可以很轻松的完成项目的创建、编译和打包。你所要做的就是发挥创造力!
-
-## 功能
-
-- 后端使用标准 Go
-- 使用您已经熟悉的任何前端技术来构建您的 UI
-- 使用内置模板为您的 Go 程序快速创建丰富的前端
-- 从 Javascript 轻松调用 Go 方法
-- 为您的 Go 结构体和方法自动生成 Typescript 声明
-- 原生对话框和菜单
-- 支持现代半透明和“磨砂窗”效果
-- Go 和 Javascript 之间统一的事件系统
-- 强大的命令行工具,可快速生成和构建您的项目
-- 跨平台
-- 使用原生渲染引擎 - _没有嵌入浏览器_!
-
-### 路线图
-
-项目路线图可在 [此处](https://github.com/wailsapp/wails/discussions/1484) 找到。在提出增强请求之前请查阅此内容。
-
-## 快速入门
-
-使用说明在 [官网](https://wails.io/zh-Hans/docs/gettingstarted/installation/)。
-
-## 赞助商
-
-这个项目由以下这些人或者公司支持:
-
-
-
-## 常见问题
-
-- 它是 Electron 的替代品吗?
-
- 取决于您的要求。它旨在使 Go 程序员可以轻松制作轻量级桌面应用程序或在其现有应用程序中添加前端。尽管 Wails 当前不提供对诸如菜单之类的原生元素的钩子,但将来可能会改变。
-
-- 这个项目针对的是哪些人?
-
- 希望将 HTML / JS / CSS 前端与其应用程序捆绑在一起的程序员,而不是借助创建服务并打开浏览器进行查看的方式。
-
-- 名字怎么来的?
-
- 当我看到 WebView 时,我想"我真正想要的是围绕构建 WebView 应用程序工作,有点像 Rails 对于 Ruby"。因此,最初它是一个文字游戏(Webview on
- Rails)。碰巧也是我来自的 [国家](https://en.wikipedia.org/wiki/Wales) 的英文名字的同音。所以就是它了。
-
-## 星星增长趋势
-
-[](https://star-history.com/#wailsapp/wails&Date)
-
-## 贡献者
-
-贡献者列表对于 README 文件来说太大了!所有为这个项目做出贡献的了不起的人在[这里](https://wails.io/credits#contributors)都有自己的页面。
-
-## 许可证
-
-[](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/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/Taskfile.yaml b/Taskfile.yaml
deleted file mode 100644
index 7cc165825..000000000
--- a/Taskfile.yaml
+++ /dev/null
@@ -1,47 +0,0 @@
-# https://taskfile.dev
-
-version: "3"
-
-includes:
- website:
- taskfile: website
- dir: website
-
- v2:
- taskfile: v2
- dir: v2
- optional: true
- v3:
- taskfile: v3
- dir: v3
- optional: true
-
-tasks:
- contributors:check:
- cmds:
- - npx -y all-contributors-cli check
-
- contributors:update:
- cmds:
- - go run v3/tasks/contribs/main.go
-
- contributors:build:
- cmds:
- - npx -y all-contributors-cli generate
-
- format:md:
- cmds:
- - npx prettier --write "**/*.md"
-
- format:
- cmds:
- - task: format:md
-
- format-all-md:
- cmds:
- - task: format:md
- - task: website:format:md
- - task: v2:format:md
- # - task: v2:website:format
- - task: v3:format:md
- # - task: v3:website:format:md
diff --git a/app.go b/app.go
new file mode 100644
index 000000000..95a8a7e82
--- /dev/null
+++ b/app.go
@@ -0,0 +1,174 @@
+package wails
+
+import (
+ "os"
+ "syscall"
+
+ "github.com/syossan27/tebata"
+ "github.com/wailsapp/wails/cmd"
+ "github.com/wailsapp/wails/lib/binding"
+ "github.com/wailsapp/wails/lib/event"
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/ipc"
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/renderer"
+ wailsruntime "github.com/wailsapp/wails/runtime"
+)
+
+// -------------------------------- Compile time Flags ------------------------------
+
+// BuildMode indicates what mode we are in
+var BuildMode = cmd.BuildModeProd
+
+// Runtime is the Go Runtime struct
+type Runtime = wailsruntime.Runtime
+
+// Store is a state store used for syncing with
+// the front end
+type Store = wailsruntime.Store
+
+// CustomLogger is a specialised logger
+type CustomLogger = logger.CustomLogger
+
+// ----------------------------------------------------------------------------------
+
+// App defines the main application struct
+type App struct {
+ config *AppConfig // The Application configuration object
+ cli *cmd.Cli // In debug mode, we have a cli
+ renderer interfaces.Renderer // The renderer is what we will render the app to
+ logLevel string // The log level of the app
+ ipc interfaces.IPCManager // Handles the IPC calls
+ log *logger.CustomLogger // Logger
+ bindingManager interfaces.BindingManager // Handles binding of Go code to renderer
+ eventManager interfaces.EventManager // Handles all the events
+ runtime interfaces.Runtime // The runtime object for registered structs
+}
+
+// CreateApp creates the application window with the given configuration
+// If none given, the defaults are used
+func CreateApp(optionalConfig ...*AppConfig) *App {
+ var userConfig *AppConfig
+ if len(optionalConfig) > 0 {
+ userConfig = optionalConfig[0]
+ }
+
+ result := &App{
+ logLevel: "debug",
+ renderer: renderer.NewWebView(),
+ ipc: ipc.NewManager(),
+ bindingManager: binding.NewManager(),
+ eventManager: event.NewManager(),
+ log: logger.NewCustomLogger("App"),
+ }
+
+ appconfig, err := newConfig(userConfig)
+ if err != nil {
+ result.log.Fatalf("Cannot use custom HTML: %s", err.Error())
+ }
+ result.config = appconfig
+
+ // Set up the CLI if not in release mode
+ if BuildMode != cmd.BuildModeProd {
+ result.cli = result.setupCli()
+ } else {
+ // Disable Inspector in release mode
+ result.config.DisableInspector = true
+ }
+
+ // Platform specific init
+ platformInit()
+
+ return result
+}
+
+// Run the app
+func (a *App) Run() error {
+
+ if BuildMode != cmd.BuildModeProd {
+ return a.cli.Run()
+ }
+
+ a.logLevel = "error"
+ err := a.start()
+ if err != nil {
+ a.log.Error(err.Error())
+ }
+ return err
+}
+
+func (a *App) start() error {
+
+ // Set the log level
+ logger.SetLogLevel(a.logLevel)
+
+ // Log starup
+ a.log.Info("Starting")
+
+ // Check if we are to run in bridge mode
+ if BuildMode == cmd.BuildModeBridge {
+ a.renderer = renderer.NewBridge()
+ }
+
+ // Initialise the renderer
+ err := a.renderer.Initialise(a.config, a.ipc, a.eventManager)
+ if err != nil {
+ return err
+ }
+
+ // Start signal handler
+ t := tebata.New(os.Interrupt, os.Kill, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
+ t.Reserve(func() {
+ a.log.Debug("SIGNAL CAUGHT! Starting Shutdown")
+ a.renderer.Close()
+ })
+
+ // Start event manager and give it our renderer
+ a.eventManager.Start(a.renderer)
+
+ // Start the IPC Manager and give it the event manager and binding manager
+ a.ipc.Start(a.eventManager, a.bindingManager)
+
+ // Create the runtime
+ a.runtime = wailsruntime.NewRuntime(a.eventManager, a.renderer)
+
+ // Start binding manager and give it our renderer
+ err = a.bindingManager.Start(a.renderer, a.runtime)
+ if err != nil {
+ return err
+ }
+
+ // Defer the shutdown
+ defer a.shutdown()
+
+ // Run the renderer
+ err = a.renderer.Run()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// shutdown the app
+func (a *App) shutdown() {
+ // Make sure this is only called once
+ a.log.Debug("Shutting down")
+
+ // Shutdown Binding Manager
+ a.bindingManager.Shutdown()
+
+ // Shutdown IPC Manager
+ a.ipc.Shutdown()
+
+ // Shutdown Event Manager
+ a.eventManager.Shutdown()
+
+ a.log.Debug("Cleanly Shutdown")
+}
+
+// Bind allows the user to bind the given object
+// with the application
+func (a *App) Bind(object interface{}) {
+ a.bindingManager.Bind(object)
+}
diff --git a/app_other.go b/app_other.go
new file mode 100644
index 000000000..5d63c4c6c
--- /dev/null
+++ b/app_other.go
@@ -0,0 +1,7 @@
+// +build linux darwin !windows
+
+package wails
+
+func platformInit() {
+
+}
diff --git a/app_windows.go b/app_windows.go
new file mode 100644
index 000000000..6386d4131
--- /dev/null
+++ b/app_windows.go
@@ -0,0 +1,27 @@
+// +build windows !linux !darwin
+
+package wails
+
+import (
+ "fmt"
+ "log"
+ "syscall"
+)
+
+func platformInit() {
+ err := SetProcessDPIAware()
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
+}
+
+// SetProcessDPIAware via user32.dll
+// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiaware
+// Also, thanks Jack Mordaunt! https://github.com/wailsapp/wails/issues/293
+func SetProcessDPIAware() error {
+ status, r, err := syscall.NewLazyDLL("user32.dll").NewProc("SetProcessDPIAware").Call()
+ if status == 0 {
+ return fmt.Errorf("exit status %d: %v %v", status, r, err)
+ }
+ return nil
+}
diff --git a/assets/images/jetbrains-grayscale.png b/assets/images/jetbrains-grayscale.png
deleted file mode 100644
index 513642dda..000000000
Binary files a/assets/images/jetbrains-grayscale.png and /dev/null differ
diff --git a/assets/images/logo-universal.png b/assets/images/logo-universal.png
deleted file mode 100644
index f60b2f43f..000000000
Binary files a/assets/images/logo-universal.png and /dev/null differ
diff --git a/assets/images/logo_cropped.png b/assets/images/logo_cropped.png
deleted file mode 100644
index 380c3d7fb..000000000
Binary files a/assets/images/logo_cropped.png and /dev/null differ
diff --git a/assets/images/pace.jpeg b/assets/images/pace.jpeg
deleted file mode 100644
index 38db20c0a..000000000
Binary files a/assets/images/pace.jpeg and /dev/null differ
diff --git a/assets/images/sponsors/bronze-sponsor.png b/assets/images/sponsors/bronze-sponsor.png
deleted file mode 100644
index 0eeb61e0a..000000000
Binary files a/assets/images/sponsors/bronze-sponsor.png and /dev/null differ
diff --git a/assets/images/sponsors/silver-sponsor.png b/assets/images/sponsors/silver-sponsor.png
deleted file mode 100644
index e81da100c..000000000
Binary files a/assets/images/sponsors/silver-sponsor.png and /dev/null differ
diff --git a/cli.go b/cli.go
new file mode 100644
index 000000000..1a26f68d9
--- /dev/null
+++ b/cli.go
@@ -0,0 +1,27 @@
+package wails
+
+import (
+ "github.com/wailsapp/wails/cmd"
+)
+
+// setupCli creates a new cli handler for the application
+func (app *App) setupCli() *cmd.Cli {
+
+ // Create a new cli
+ result := cmd.NewCli(app.config.Title, "Debug build")
+ result.Version(cmd.Version)
+
+ // Setup cli to handle loglevel
+ result.
+ StringFlag("loglevel", "Sets the log level [debug|info|error|panic|fatal]. Default debug", &app.logLevel).
+ Action(app.start)
+
+ // Banner
+ result.PreRun(func(cli *cmd.Cli) error {
+ log := cmd.NewLogger()
+ log.YellowUnderline(app.config.Title + " - Debug Build")
+ return nil
+ })
+
+ return result
+}
diff --git a/cmd/build.go b/cmd/build.go
new file mode 100644
index 000000000..945fbfc78
--- /dev/null
+++ b/cmd/build.go
@@ -0,0 +1,10 @@
+package cmd
+
+const (
+ // BuildModeProd indicates we are building for prod mode
+ BuildModeProd = "prod"
+ // BuildModeDebug indicates we are building for debug mode
+ BuildModeDebug = "debug"
+ // BuildModeBridge indicates we are building for bridge mode
+ BuildModeBridge = "bridge"
+)
diff --git a/cmd/cli.go b/cmd/cli.go
new file mode 100644
index 000000000..d52a44513
--- /dev/null
+++ b/cmd/cli.go
@@ -0,0 +1,285 @@
+package cmd
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+)
+
+// NewCli - Creates a new Cli application object
+func NewCli(name, description string) *Cli {
+ result := &Cli{}
+ result.rootCommand = NewCommand(name, description, result, "")
+ result.log = NewLogger()
+ return result
+}
+
+// Cli - The main application object
+type Cli struct {
+ rootCommand *Command
+ defaultCommand *Command
+ preRunCommand func(*Cli) error
+ log *Logger
+}
+
+// Version - Set the Application version string
+func (c *Cli) Version(version string) {
+ c.rootCommand.AppVersion = version
+}
+
+// PrintHelp - Prints the application's help
+func (c *Cli) PrintHelp() {
+ c.rootCommand.PrintHelp()
+}
+
+// Run - Runs the application with the given arguments
+func (c *Cli) Run(args ...string) error {
+ if c.preRunCommand != nil {
+ err := c.preRunCommand(c)
+ if err != nil {
+ return err
+ }
+ }
+ if len(args) == 0 {
+ args = os.Args[1:]
+ }
+ return c.rootCommand.Run(args)
+}
+
+// DefaultCommand - Sets the given command as the command to run when
+// no other commands given
+func (c *Cli) DefaultCommand(defaultCommand *Command) *Cli {
+ c.defaultCommand = defaultCommand
+ return c
+}
+
+// Command - Adds a command to the application
+func (c *Cli) Command(name, description string) *Command {
+ return c.rootCommand.Command(name, description)
+}
+
+// PreRun - Calls the given function before running the specific command
+func (c *Cli) PreRun(callback func(*Cli) error) {
+ c.preRunCommand = callback
+}
+
+// BoolFlag - Adds a boolean flag to the root command
+func (c *Cli) BoolFlag(name, description string, variable *bool) *Command {
+ c.rootCommand.BoolFlag(name, description, variable)
+ return c.rootCommand
+}
+
+// StringFlag - Adds a string flag to the root command
+func (c *Cli) StringFlag(name, description string, variable *string) *Command {
+ c.rootCommand.StringFlag(name, description, variable)
+ return c.rootCommand
+}
+
+// Action represents a function that gets calls when the command is called by
+// the user
+type Action func() error
+
+// Command represents a command that may be run by the user
+type Command struct {
+ Name string
+ CommandPath string
+ Shortdescription string
+ Longdescription string
+ AppVersion string
+ SubCommands []*Command
+ SubCommandsMap map[string]*Command
+ longestSubcommand int
+ ActionCallback Action
+ App *Cli
+ Flags *flag.FlagSet
+ flagCount int
+ log *Logger
+ helpFlag bool
+ hidden bool
+}
+
+// NewCommand creates a new Command
+func NewCommand(name string, description string, app *Cli, parentCommandPath string) *Command {
+ result := &Command{
+ Name: name,
+ Shortdescription: description,
+ SubCommandsMap: make(map[string]*Command),
+ App: app,
+ log: NewLogger(),
+ hidden: false,
+ }
+
+ // Set up command path
+ if parentCommandPath != "" {
+ result.CommandPath += parentCommandPath + " "
+ }
+ result.CommandPath += name
+
+ // Set up flag set
+ result.Flags = flag.NewFlagSet(result.CommandPath, flag.ContinueOnError)
+ result.BoolFlag("help", "Get help on the '"+result.CommandPath+"' command.", &result.helpFlag)
+
+ // result.Flags.Usage = result.PrintHelp
+
+ return result
+}
+
+// parseFlags parses the given flags
+func (c *Command) parseFlags(args []string) error {
+ // Parse flags
+ tmp := os.Stderr
+ os.Stderr = nil
+ err := c.Flags.Parse(args)
+ os.Stderr = tmp
+ if err != nil {
+ fmt.Printf("Error: %s\n\n", err.Error())
+ c.PrintHelp()
+ }
+ return err
+}
+
+// Run - Runs the Command with the given arguments
+func (c *Command) Run(args []string) error {
+
+ // If we have arguments, process them
+ if len(args) > 0 {
+ // Check for subcommand
+ subcommand := c.SubCommandsMap[args[0]]
+ if subcommand != nil {
+ return subcommand.Run(args[1:])
+ }
+
+ // Parse flags
+ err := c.parseFlags(args)
+ if err != nil {
+ fmt.Printf("Error: %s\n\n", err.Error())
+ c.PrintHelp()
+ return err
+ }
+
+ // Help takes precedence
+ if c.helpFlag {
+ c.PrintHelp()
+ return nil
+ }
+ }
+
+ // Do we have an action?
+ if c.ActionCallback != nil {
+ return c.ActionCallback()
+ }
+
+ // If we haven't specified a subcommand
+ // check for an app level default command
+ if c.App.defaultCommand != nil {
+ // Prevent recursion!
+ if c.App.defaultCommand != c {
+ // only run default command if no args passed
+ if len(args) == 0 {
+ return c.App.defaultCommand.Run(args)
+ }
+ }
+ }
+
+ // Nothing left we can do
+ c.PrintHelp()
+
+ return nil
+}
+
+// Action - Define an action from this command
+func (c *Command) Action(callback Action) *Command {
+ c.ActionCallback = callback
+ return c
+}
+
+// PrintHelp - Output the help text for this command
+func (c *Command) PrintHelp() {
+ c.log.PrintBanner()
+
+ commandTitle := c.CommandPath
+ if c.Shortdescription != "" {
+ commandTitle += " - " + c.Shortdescription
+ }
+ // Ignore root command
+ if c.CommandPath != c.Name {
+ c.log.Yellow(commandTitle)
+ }
+ if c.Longdescription != "" {
+ fmt.Println()
+ fmt.Println(c.Longdescription + "\n")
+ }
+ if len(c.SubCommands) > 0 {
+ c.log.White("Available commands:")
+ fmt.Println("")
+ for _, subcommand := range c.SubCommands {
+ if subcommand.isHidden() {
+ continue
+ }
+ spacer := strings.Repeat(" ", 3+c.longestSubcommand-len(subcommand.Name))
+ isDefault := ""
+ if subcommand.isDefaultCommand() {
+ isDefault = "[default]"
+ }
+ fmt.Printf(" %s%s%s %s\n", subcommand.Name, spacer, subcommand.Shortdescription, isDefault)
+ }
+ fmt.Println("")
+ }
+ if c.flagCount > 0 {
+ c.log.White("Flags:")
+ fmt.Println()
+ c.Flags.SetOutput(os.Stdout)
+ c.Flags.PrintDefaults()
+ c.Flags.SetOutput(os.Stderr)
+
+ }
+ fmt.Println()
+}
+
+// isDefaultCommand returns true if called on the default command
+func (c *Command) isDefaultCommand() bool {
+ return c.App.defaultCommand == c
+}
+
+// isHidden returns true if the command is a hidden command
+func (c *Command) isHidden() bool {
+ return c.hidden
+}
+
+// Hidden hides the command from the Help system
+func (c *Command) Hidden() {
+ c.hidden = true
+}
+
+// Command - Defines a subcommand
+func (c *Command) Command(name, description string) *Command {
+ result := NewCommand(name, description, c.App, c.CommandPath)
+ result.log = c.log
+ c.SubCommands = append(c.SubCommands, result)
+ c.SubCommandsMap[name] = result
+ if len(name) > c.longestSubcommand {
+ c.longestSubcommand = len(name)
+ }
+ return result
+}
+
+// BoolFlag - Adds a boolean flag to the command
+func (c *Command) BoolFlag(name, description string, variable *bool) *Command {
+ c.Flags.BoolVar(variable, name, *variable, description)
+ c.flagCount++
+ return c
+}
+
+// StringFlag - Adds a string flag to the command
+func (c *Command) StringFlag(name, description string, variable *string) *Command {
+ c.Flags.StringVar(variable, name, *variable, description)
+ c.flagCount++
+ return c
+}
+
+// LongDescription - Sets the long description for the command
+func (c *Command) LongDescription(Longdescription string) *Command {
+ c.Longdescription = Longdescription
+ return c
+}
diff --git a/cmd/cmd-mewn.go b/cmd/cmd-mewn.go
new file mode 100644
index 000000000..7bb0a198c
--- /dev/null
+++ b/cmd/cmd-mewn.go
@@ -0,0 +1,10 @@
+package cmd
+
+// Autogenerated by Mewn - Do not alter
+
+import "github.com/leaanthony/mewn"
+
+func init() {
+ mewn.AddAsset(".", "../runtime/assets/bridge.js", "1f8b08000000000000ffac396973e2ba969fe157e8f5ad2ec825c1ecebcdad318610920061cbc29b5719611f6c115b3696c04077fefb946c03269d74f79d9a749358d2d9755623fd19472f28f879091f5ec2a7f877f4dddf9090f4f2f2825e922f67fed38b7f227d170702f67fa4f0e945122792f82f7082eda4a07686e2df5f5e24f1f9ef9797f317c9ff2730108a8f0d4026d10dee81f88de62eb6c0b3dd5734b75de4c1ecc224af80b0e3b078523d437780914cb961d32dca65b2d50bc7050694c7ff94e2d29f68c10c423902b60697119bd65009fd29c5e3f3155539b1292294f0e419fa168f49126ab844d301d9b305a83c1ef308d56c2fed6162b25970742920632ea836a5a0f2fe1a5c136f6b88ae4cf33c7a322616b83594cf64c4b6c726c3bb1a4a78ac26490994422169d356b110236dd88c536c014aa1442d5fc8668b52c03121d0439ac4a6238e391cd9a9369d13bd86bebdf95c60c66cf5157804009be60cabafc71d3b10f97adcbdabc563b158e22f8dac916a62c62ebff8aa5e1c94b80881bffcfd1b4017aa4d3950fe7bc09c7013befcfd288e43bbff256964fdf75f33f7b7089836d608d599432805f7cbdfefb089f639aa058c613de0ce09d57dd7125602aa856422bf1311ab29a35160b4f427b4bf393623e2a26a73b201adce6da796a99b30e7b54cdd231a376ad94ce66bddf0bd3b78169c75d75e51ade6ea339ccc9c8b7fe9d2597d6e537e31c71631b7358629bb60e092795d23cc115e476d0af5dd05a11a6c6a55ffe7ed33c1f6b7f3cdc19ab05b2d977136289f7136750e1b7e814da2d39a0a94831b0a9acb80553fe8e382893959c341f40258f599ed6ae05eb858232b56cb8255b7b0ab135a2b7e4578c56d94896877a1daa6edd6fe98cfe7f599bdb96006d66caf967536487c02819c4d1483585887daca35931ae6b8e62f2587eaf51966502a9c9387467fe8656edbba2dcbb2dc1b4d8cd644976559a988f54a91bb6271efcce4b2d8585e35ba0fad89fc0f7f5a0d79f0d1f37e2d3e4dd9eb8a47b1b7ffbbff89ae1559ef88cf7b988fe005dc3f95f5aa3178bc5516a5c020f9e1686c76e53bd259368dd7764366decde23ed3dedc977ac3c26355daf1c5b0d9351e27c54cbbaa17e1fa693d9b569f5a9dea663a56975333874dedd62e16079eadaca7137d6708c2ac7a336c5d4d465de899008fcb853c3126998682aff31d4ebceaee765ccda776ed4c6e709b352ca9787d955aeca637a5f9004aeaf6a9955ae8d5a7b25df536abadd9e9cc0be375ee219f9af5ddd6139d57346f20dd0e3aed662f278d5285bef5a03c3cdd4923b541e764b6990c979dc6f65e32fabd56c5ad8c53b366d71c542bb723b3fc5c31760b6754596797bc695456c579e576e0b49d39bdbf3796edca0dd87a2f5fbe5e5601f7729ba1d1f164dca66b8f55aeb22363b3bc2ddab31cf79e49f64aee360bee137be654916e5a6438330beac4551e0dda55ee8657603dc1ee69362b5607b7854779d4675689e61eb3a982d218e76e2cbe1b94e6cc9db557b3a1d4bb9a6939bdf89c2d5753454f6ab7aef12dbb5e3c2bc0cb8aee3e4df27c83dd9e527edef41f6e786751905ac35d579bce776a3e6b38a98ada2c41bb73a76377fa5c5d4db339436699fc7ade6856956c39ab8f8a7d6b57eeabeb8d7c37e8d0c2aa321c361a0d9591eae363ceb3fb55e2dce9f39df36ceaba9e9f0eb5617fa2546ec7e5c1dd72997facde368a8d9976f5a017aab9eaabe639a5d4124f6957517a0d6d91ea6d7bd9c74aa538dc4af25dbe59900733f6f050a0b642ba9252c9bf6e9f9795cc93d9949f07dcc5669f3477f650be366dfe68569639be583f9b7d5dd5b37265d1929d294e2999d2b8a1c9bbdc609df156d96c47da4dfbdb9b41a9e5b56197bbcfcd2af79d819b353c8fca53266f3cb930919ffbdba7c9a62b6d9c12bf9669a13db1eedbddea3057b11f36ba92016937b8b9e9949a5b633bcdbae5d56e38301cf5b1bc29afc7f871b4bb1db54b189c72f9d5b5c72d333fa96457a3073c1973bdd5e9cd950a389bd4a070551d398e79ddae145aaf25ab30949755697cf7da6fb89b2dac346d6a5cafdb1d27fbf434bfdae6a7f935f77a33f3b64d9a347593aae6d69e2737ae756351bea63b7394bd2ebe360a5579bad6daca954e1af7cb5275397fba77f3db546a4bccab4cbf5718566058668eccee9c69b5d85bca8f458e6759b5b07e9d5a256c025ea846d77b1e6b9dbbc160ecb572fd8742363bed561f77bcb9b85f9ad99c8a1ba4a4379dc9add5d40a93ec534f1b6ca685bbced3a0288dddc27db953e5cfea95dbbccd0759ccbc1abf8e56034b51cea209d8050730af513b7c8a9e1d4a4350353e2f3d7eadffe61733467650cb81f53970589c23e0d974fe6708a78dc037c726429a0b5803e52ca891613d4b17c1da97af6011d4af5aba001662b64934c45d4c99835da07c5fddc2b2956f95ca2d05fd0100feaf77c5af98f95ac794587e3f578b0885b20c9984027611a173d16fc2be4a8a12593f146421d1db7fbdc2d6ef79198ad0f8261a856fbe6c73dbb56aae2dfac064be94d1403f7b7bf37b14d3d66be8d0d8264343fabd6d4c74b7c04c42f98546189e99704145d917828963d5a6cc36216dda7a52ac6389af2af22d8ec2cef7ab8a44e31a52157d2af2b9c66289480383fec03893c964ea28b01af2ab3d3a3595a8f228137e44c5477b1b8846a08e8e578f32e9b20bd6878c32996af5578c42067b86bf6624f89cd5e3b1d85b3cf6568fbfc5e39284640d3b1c3434776d4b8c206b021eba40dcc0f495a11d0357877f45470a313e28a3515265cc37ff1abb084cb0d025d26c756501e569d505cca165825825138c6f4d4808d60230cd80cb9cbb64b6e2904cf0ad03897394109d9aa432e6c391394a06b002756400f0e0aedf6da655c6c6b0e1e812a98cd5e3b1370426832328761ca09a6210534bbe134fe0f56c0d7c4d04d3b7401903b01655c65f7fff7edcd08187aab1c6768cf51eb62099106089b37f67fe538fc7c4f3096b21cbd9dee48acf9e218ca8ad012214710350d3b68e660e24f4a50be2f5dcb731503ede3a708e88761ecc1082f7390afbdfc37d689f5f4684ccded0440b6caba58940245a600b7174e0b187386c088bef9f23f01141043d91b5c43426a083a3003650eaf47684382ef0954b91b637d50838432bc7375098138f4662c0574e38a5260fbacf6c6d1b555facebc1911ff3c13c264c2b843ada59c09da38446d6c21b83841c8ea942b277b827aafd384ba72393683deecfdf1d3f72a29a2065348ac78e11f5391965343a78cf88633720139d2f235611e7c1e661f61f094b9d1831766a3c21e307ec0f4337baf467ecfa8750d1b7023f057cff6a217a4dcb15b8db1198a072db1569fad3313411f7b3d8e782ec5f25a04b719d2cdc052d11dec4c8b03d766a8da3f50cdb1bbe133330e3ef281464a67438c30afe33d3565f13bed3fbbcaf89069ff1368806ff9fbc457b10612d6b9ac8394c7589c311b7f75927ed9f0ec1b2d7c010e188cc1113c435e46051ad39b88830c4dd15a423e2624d1bf9b49201c973e4fa34028945bcb19fd5041fc70fad18fb594958e0353e0516db4a904dd065a88f3839c9d827b985f90e7e5413f96d14c5e6bee4b3a0fef916e977e3313f9945d589b17490b3fce00f4e8eb483921ade30a69a290c4955db225447c7203afa278b5ebb8fa004679fdfb7e85e12cade97c5fd856f7502b37cec3ce2443501bb1da1f01a9b1f259968fcfa183fcb04699baaa6cd44700582370f11f61ba8fb0e6b8fdc0dd69f607e10cfd1607e6ff1a3a18f51ffa3a18ff2fec2d6cd48ea08dce3c4e0bf972eff6f692a16fb380fd58fef4b936747038cddadf087f0641fdaa1b408d6e06e513e93b1184a6a30c72b93a3353657701684be6260aa03e20661c1febe25b130a128fa863878791c4d02bf34e5bbe2c0801f5cf1d8ce87312682eee7660dec1a82fff20ac0438f301bf9eb0f09b3c9f0ce37ea2f1dd776801ebc5639fafb2f11c1756da1f851d97d4689c520cdb8ed742c0b348239dcbbb683757fc64a865285201f9f38ae3f0736832b3d1cfca663c662fb6e6b8e4d06c1d69bffe74d24b4f38fda9ad3af00f62ef82ec2c2a03e1dd2440112e527f4240f10d1a9ed025a3084a926daf770b808d2638c7984abc661d44b6b98e37f67fe73a016743f2699b9d8dda28b10f95ffef7020c50c24bf86fb38f552a4a28cd4ca242327b16140641af677be150e8ae282716889a27c654d0cec383b9edfa71e102d6b6c8377e88ec6b107e1db18f3f3f7ab0e3c4dfdd49bae54fefe93e4d066d662de09238473f44c4a7c929d07e2804498417ff49f4ecc53a78dd67247b3627f3ada858d8714c127c7993f88957ed299f700de1df7c3f0a169f31bcf3d50e2f7218183de4377301bfd60f37c3c93c94871dee97fed6fd9efb6dcb07441b848a61f9406e1690f32788e0085da20f498614840a5f422a35f405a5f678673f5cf84bba075e089adc43fd2892824dd3cfda07a1d4a3507b633731c73f95ec1d67657f495102ef988745a1f603fa9dada75b2281251313fa4a6d8f1ede91881e0db9a0025983568bbe3e3944ea695f14cc2db3addf620bf3469ae0743a1dffb4e4f9d8e9745ac418e61caca07b0d01e3d182f8167f370725a3debf1f85f60365e08b620223fcc06884d7e09fee313f9e34f6b17e7980ab47d50c87b2d8c930762a5f47703d09ce7712fbe096adad4c48c3c6b15d2efa6981568fff6f000000ffffc3c33335cf1e0000")
+ mewn.AddAsset(".", "../runtime/js/runtime/init.js", "1f8b08000000000000ff548ccd4ec3301084cfec53cc3189680c1c90a04282231247242e488b9b6e89a96347b64b404ddf1dd96d0ff867359a99fd5443601c0f9f049f14cd988ba1a098195c715d149744cd39c8dd0f7552ac72a2f2cb3b47bbcab41a3433abfcdf992f59959b37007aed05d67cf669923cb1097a90c9872d363e6092d5c29aad408f63a4aaabf1221a4f2ef5defde2e6eafa6e310689e212358a5483afd81b9720f15b4234dedde3168d22524d4368f0ec4c32da9a2811a917bc696323c2ce253308a1741e471df480fd66e7ba64bc3ba0d3d6ae74b7a58c3adb85559da31a7bba988c5bfba99d32b4e5f67f614907a2c1af77565af9197d48110f05b2a4bf000000ffffe38437ed8a010000")
+}
diff --git a/cmd/fs.go b/cmd/fs.go
new file mode 100644
index 000000000..0c39e2aea
--- /dev/null
+++ b/cmd/fs.go
@@ -0,0 +1,251 @@
+package cmd
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/leaanthony/slicer"
+)
+
+// FSHelper - Wrapper struct for File System utility commands
+type FSHelper struct {
+}
+
+// NewFSHelper - Returns a new FSHelper
+func NewFSHelper() *FSHelper {
+ result := &FSHelper{}
+ return result
+}
+
+// DirExists - Returns true if the given path resolves to a directory on the filesystem
+func (fs *FSHelper) DirExists(path string) bool {
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return false
+ }
+
+ return fi.Mode().IsDir()
+}
+
+// FileExists returns a boolean value indicating whether
+// the given file exists
+func (fs *FSHelper) FileExists(path string) bool {
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return false
+ }
+
+ return fi.Mode().IsRegular()
+}
+
+// FindFile returns the first occurrence of match inside path.
+func (fs *FSHelper) FindFile(path, match string) (string, error) {
+ files, err := ioutil.ReadDir(path)
+ if err != nil {
+ return "", err
+ }
+
+ for _, f := range files {
+ if !f.IsDir() && strings.Contains(f.Name(), match) {
+ return f.Name(), nil
+ }
+ }
+
+ return "", fmt.Errorf("file not found")
+}
+
+// CreateFile creates a file at the given filename location with the contents
+// set to the given data. It will create intermediary directories if needed.
+func (fs *FSHelper) CreateFile(filename string, data []byte) error {
+ // Ensure directory exists
+ fs.MkDirs(filepath.Dir(filename))
+ return ioutil.WriteFile(filename, data, 0644)
+}
+
+// MkDirs creates the given nested directories.
+// Returns error on failure
+func (fs *FSHelper) MkDirs(fullPath string, mode ...os.FileMode) error {
+ var perms os.FileMode
+ perms = 0700
+ if len(mode) == 1 {
+ perms = mode[0]
+ }
+ return os.MkdirAll(fullPath, perms)
+}
+
+// CopyFile from source to target
+func (fs *FSHelper) CopyFile(source, target string) error {
+ s, err := os.Open(source)
+ if err != nil {
+ return err
+ }
+ defer s.Close()
+ d, err := os.Create(target)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(d, s); err != nil {
+ d.Close()
+ return err
+ }
+ return d.Close()
+}
+
+// Cwd returns the current working directory
+// Aborts on Failure
+func (fs *FSHelper) Cwd() string {
+ cwd, err := os.Getwd()
+ if err != nil {
+ log.Fatal("Unable to get working directory!")
+ }
+ return cwd
+}
+
+// RemoveFile removes the given filename
+func (fs *FSHelper) RemoveFile(filename string) error {
+ return os.Remove(filename)
+}
+
+// RemoveFiles removes the given filenames
+func (fs *FSHelper) RemoveFiles(files []string, continueOnError bool) error {
+ for _, filename := range files {
+ err := os.Remove(filename)
+ if err != nil && !continueOnError {
+ return err
+ }
+ }
+ return nil
+}
+
+// Dir holds information about a directory
+type Dir struct {
+ localPath string
+ fullPath string
+}
+
+// Directory creates a new Dir struct with the given directory path
+func (fs *FSHelper) Directory(dir string) (*Dir, error) {
+ fullPath, err := filepath.Abs(dir)
+ return &Dir{fullPath: fullPath}, err
+}
+
+// LocalDir creates a new Dir struct based on a path relative to the caller
+func (fs *FSHelper) LocalDir(dir string) (*Dir, error) {
+ _, filename, _, _ := runtime.Caller(1)
+ fullPath, err := filepath.Abs(filepath.Join(path.Dir(filename), dir))
+ return &Dir{
+ localPath: dir,
+ fullPath: fullPath,
+ }, err
+}
+
+// LoadRelativeFile loads the given file relative to the caller's directory
+func (fs *FSHelper) LoadRelativeFile(relativePath string) ([]byte, error) {
+ _, filename, _, _ := runtime.Caller(0)
+ fullPath, err := filepath.Abs(filepath.Join(path.Dir(filename), relativePath))
+ if err != nil {
+ return nil, err
+ }
+ return ioutil.ReadFile(fullPath)
+}
+
+// GetSubdirs will return a list of FQPs to subdirectories in the given directory
+func (d *Dir) GetSubdirs() (map[string]string, error) {
+
+ // Read in the directory information
+ fileInfo, err := ioutil.ReadDir(d.fullPath)
+ if err != nil {
+ return nil, err
+ }
+
+ // Allocate space for the list
+ subdirs := make(map[string]string)
+
+ // Pull out the directories and store in the map as
+ // map["directoryName"] = "path/to/directoryName"
+ for _, file := range fileInfo {
+ if file.IsDir() {
+ subdirs[file.Name()] = filepath.Join(d.fullPath, file.Name())
+ }
+ }
+ return subdirs, nil
+}
+
+// GetAllFilenames returns all filename in and below this directory
+func (d *Dir) GetAllFilenames() (*slicer.StringSlicer, error) {
+ result := slicer.String()
+ err := filepath.Walk(d.fullPath, func(dir string, info os.FileInfo, err error) error {
+ if dir == d.fullPath {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+
+ // Don't copy template metadata
+ result.Add(dir)
+
+ return nil
+ })
+ return result, err
+}
+
+// MkDir creates the given directory.
+// Returns error on failure
+func (fs *FSHelper) MkDir(dir string) error {
+ return os.Mkdir(dir, 0700)
+}
+
+// SaveAsJSON saves the JSON representation of the given data to the given filename
+func (fs *FSHelper) SaveAsJSON(data interface{}, filename string) error {
+
+ var buf bytes.Buffer
+ e := json.NewEncoder(&buf)
+ e.SetEscapeHTML(false)
+ e.SetIndent("", " ")
+ e.Encode(data)
+
+ err := ioutil.WriteFile(filename, buf.Bytes(), 0755)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// LoadAsString will attempt to load the given file and return
+// its contents as a string
+func (fs *FSHelper) LoadAsString(filename string) (string, error) {
+ bytes, err := fs.LoadAsBytes(filename)
+ return string(bytes), err
+}
+
+// LoadAsBytes returns the contents of the file as a byte slice
+func (fs *FSHelper) LoadAsBytes(filename string) ([]byte, error) {
+ return ioutil.ReadFile(filename)
+}
+
+// FileMD5 returns the md5sum of the given file
+func (fs *FSHelper) FileMD5(filename string) (string, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ h := md5.New()
+ if _, err := io.Copy(h, f); err != nil {
+ return "", err
+ }
+
+ return fmt.Sprintf("%x", h.Sum(nil)), nil
+}
diff --git a/cmd/github.go b/cmd/github.go
new file mode 100644
index 000000000..6258ed2f8
--- /dev/null
+++ b/cmd/github.go
@@ -0,0 +1,108 @@
+package cmd
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "sort"
+)
+
+// GitHubHelper is a utility class for interacting with GitHub
+type GitHubHelper struct {
+}
+
+// NewGitHubHelper returns a new GitHub Helper
+func NewGitHubHelper() *GitHubHelper {
+ return &GitHubHelper{}
+}
+
+// GetVersionTags gets the list of tags on the Wails repo
+// It returns a list of sorted tags in descending order
+func (g *GitHubHelper) GetVersionTags() ([]*SemanticVersion, error) {
+
+ result := []*SemanticVersion{}
+ var err error
+
+ resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags")
+ if err != nil {
+ return result, err
+ }
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return result, err
+ }
+
+ data := []map[string]interface{}{}
+ err = json.Unmarshal(body, &data)
+ if err != nil {
+ return result, err
+ }
+
+ // Convert tag data to Version structs
+ for _, tag := range data {
+ version := tag["name"].(string)
+ semver, err := NewSemanticVersion(version)
+ if err != nil {
+ return result, err
+ }
+ result = append(result, semver)
+ }
+
+ // Reverse Sort
+ sort.Sort(sort.Reverse(SemverCollection(result)))
+
+ return result, err
+}
+
+// GetLatestStableRelease gets the latest stable release on GitHub
+func (g *GitHubHelper) GetLatestStableRelease() (result *SemanticVersion, err error) {
+
+ tags, err := g.GetVersionTags()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, tag := range tags {
+ if tag.IsRelease() {
+ return tag, nil
+ }
+ }
+
+ return nil, fmt.Errorf("no release tag found")
+}
+
+// GetLatestPreRelease gets the latest prerelease on GitHub
+func (g *GitHubHelper) GetLatestPreRelease() (result *SemanticVersion, err error) {
+
+ tags, err := g.GetVersionTags()
+ if err != nil {
+ return nil, err
+ }
+
+ for _, tag := range tags {
+ if tag.IsPreRelease() {
+ return tag, nil
+ }
+ }
+
+ return nil, fmt.Errorf("no prerelease tag found")
+}
+
+// IsValidTag returns true if the given string is a valid tag
+func (g *GitHubHelper) IsValidTag(tagVersion string) (bool, error) {
+ if tagVersion[0] == 'v' {
+ tagVersion = tagVersion[1:]
+ }
+ tags, err := g.GetVersionTags()
+ if err != nil {
+ return false, err
+ }
+
+ for _, tag := range tags {
+ if tag.String() == tagVersion {
+ return true, nil
+ }
+ }
+ return false, nil
+}
diff --git a/cmd/gomod.go b/cmd/gomod.go
new file mode 100644
index 000000000..a9ca7d93c
--- /dev/null
+++ b/cmd/gomod.go
@@ -0,0 +1,78 @@
+package cmd
+
+import (
+ "fmt"
+ "path/filepath"
+ "regexp"
+
+ "github.com/Masterminds/semver"
+)
+
+func GetWailsVersion() (*semver.Version, error) {
+ var FS = NewFSHelper()
+ var result *semver.Version
+
+ // Load file
+ var err error
+ goModFile, err := filepath.Abs(filepath.Join(".", "go.mod"))
+ if err != nil {
+ return nil, fmt.Errorf("Unable to load go.mod at %s", goModFile)
+ }
+ goMod, err := FS.LoadAsString(goModFile)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to load go.mod")
+ }
+
+ // Find wails version
+ versionRegexp := regexp.MustCompile(`.*github.com/wailsapp/wails.*(v\d+.\d+.\d+(?:-pre\d+)?)`)
+ versions := versionRegexp.FindStringSubmatch(goMod)
+
+ if len(versions) != 2 {
+ return nil, fmt.Errorf("Unable to determine Wails version")
+ }
+
+ version := versions[1]
+ result, err = semver.NewVersion(version)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse Wails version: %s", version)
+ }
+ return result, nil
+
+}
+
+func GetCurrentVersion() (*semver.Version, error) {
+ result, err := semver.NewVersion(Version)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse Wails version: %s", Version)
+ }
+ return result, nil
+}
+
+func GoModOutOfSync() (bool, error) {
+ gomodversion, err := GetWailsVersion()
+ if err != nil {
+ return true, err
+ }
+ currentVersion, err := GetCurrentVersion()
+ if err != nil {
+ return true, err
+ }
+ result := !currentVersion.Equal(gomodversion)
+ return result, nil
+}
+
+func UpdateGoModVersion() error {
+ currentVersion, err := GetCurrentVersion()
+ if err != nil {
+ return err
+ }
+ currentVersionString := currentVersion.String()
+
+ requireLine := "-require=github.com/wailsapp/wails@v" + currentVersionString
+
+ // Issue: go mod edit -require=github.com/wailsapp/wails@1.0.2-pre5
+ helper := NewProgramHelper()
+ command := []string{"go", "mod", "edit", requireLine}
+ return helper.RunCommandArray(command)
+
+}
diff --git a/cmd/helpers.go b/cmd/helpers.go
new file mode 100644
index 000000000..6b93b4561
--- /dev/null
+++ b/cmd/helpers.go
@@ -0,0 +1,597 @@
+package cmd
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/leaanthony/mewn"
+ "github.com/leaanthony/mewn/lib"
+ "github.com/leaanthony/slicer"
+ "github.com/leaanthony/spinner"
+)
+
+const xgoVersion = "1.0.1"
+
+var fs = NewFSHelper()
+
+// ValidateFrontendConfig checks if the frontend config is valid
+func ValidateFrontendConfig(projectOptions *ProjectOptions) error {
+ if projectOptions.FrontEnd.Dir == "" {
+ return fmt.Errorf("Frontend directory not set in project.json")
+ }
+ if projectOptions.FrontEnd.Build == "" {
+ return fmt.Errorf("Frontend build command not set in project.json")
+ }
+ if projectOptions.FrontEnd.Install == "" {
+ return fmt.Errorf("Frontend install command not set in project.json")
+ }
+ if projectOptions.FrontEnd.Bridge == "" {
+ return fmt.Errorf("Frontend bridge config not set in project.json")
+ }
+
+ return nil
+}
+
+// InstallGoDependencies will run go get in the current directory
+func InstallGoDependencies(verbose bool) error {
+ var depSpinner *spinner.Spinner
+ if !verbose {
+ depSpinner = spinner.New("Ensuring Dependencies are up to date...")
+ depSpinner.SetSpinSpeed(50)
+ depSpinner.Start()
+ }
+ err := NewProgramHelper(verbose).RunCommand("go get")
+ if err != nil {
+ if !verbose {
+ depSpinner.Error()
+ }
+ return err
+ }
+ if !verbose {
+ depSpinner.Success()
+ }
+ return nil
+}
+
+// EmbedAssets will embed the built frontend assets via mewn.
+func EmbedAssets() ([]string, error) {
+ mewnFiles := lib.GetMewnFiles([]string{}, false)
+
+ referencedAssets, err := lib.GetReferencedAssets(mewnFiles)
+ if err != nil {
+ return []string{}, err
+ }
+
+ targetFiles := []string{}
+
+ for _, referencedAsset := range referencedAssets {
+ packfileData, err := lib.GeneratePackFileString(referencedAsset, false)
+ if err != nil {
+ return []string{}, err
+ }
+ targetFile := filepath.Join(referencedAsset.BaseDir, referencedAsset.PackageName+"-mewn.go")
+ targetFiles = append(targetFiles, targetFile)
+ ioutil.WriteFile(targetFile, []byte(packfileData), 0644)
+ }
+
+ return targetFiles, nil
+}
+
+func InitializeCrossCompilation(verbose bool) error {
+ // Check Docker
+ if err := CheckIfInstalled("docker"); err != nil {
+ return err
+ }
+
+ var packSpinner *spinner.Spinner
+ msg := fmt.Sprintf("Pulling wailsapp/xgo:%s docker image... (may take a while)", xgoVersion)
+ if !verbose {
+ packSpinner = spinner.New(msg)
+ packSpinner.SetSpinSpeed(50)
+ packSpinner.Start()
+ } else {
+ println(msg)
+ }
+
+ err := NewProgramHelper(verbose).RunCommandArray([]string{"docker",
+ "pull", fmt.Sprintf("wailsapp/xgo:%s", xgoVersion)})
+
+ if err != nil {
+ if packSpinner != nil {
+ packSpinner.Error()
+ }
+ return err
+ }
+ if packSpinner != nil {
+ packSpinner.Success()
+ }
+
+ return nil
+}
+
+// BuildDocker builds the project using the cross compiling wailsapp/xgo: container
+func BuildDocker(binaryName string, buildMode string, projectOptions *ProjectOptions) error {
+ var packSpinner *spinner.Spinner
+ if buildMode == BuildModeBridge {
+ return fmt.Errorf("you cant serve the application in cross-compilation")
+ }
+
+ // Check build directory
+ buildDirectory := filepath.Join(fs.Cwd(), "build")
+ if !fs.DirExists(buildDirectory) {
+ fs.MkDir(buildDirectory)
+ }
+
+ buildCommand := slicer.String()
+ userid := 1000
+ user, _ := user.Current()
+ if i, err := strconv.Atoi(user.Uid); err == nil {
+ userid = i
+ }
+ for _, arg := range []string{
+ "docker",
+ "run",
+ "--rm",
+ "-v", fmt.Sprintf("%s:/build", filepath.Join(fs.Cwd(), "build")),
+ "-v", fmt.Sprintf("%s:/source", fs.Cwd()),
+ "-e", fmt.Sprintf("LOCAL_USER_ID=%v", userid),
+ "-e", fmt.Sprintf("FLAG_TAGS=%s", projectOptions.Tags),
+ "-e", fmt.Sprintf("FLAG_LDFLAGS=%s", ldFlags(projectOptions, buildMode)),
+ "-e", "FLAG_V=false",
+ "-e", "FLAG_X=false",
+ "-e", "FLAG_RACE=false",
+ "-e", "FLAG_BUILDMODE=default",
+ "-e", "FLAG_TRIMPATH=false",
+ "-e", fmt.Sprintf("TARGETS=%s/%s", projectOptions.Platform, projectOptions.Architecture),
+ "-e", "GOPROXY=",
+ "-e", "GO111MODULE=on",
+ } {
+ buildCommand.Add(arg)
+ }
+
+ if projectOptions.GoPath != "" {
+ buildCommand.Add("-v")
+ buildCommand.Add(fmt.Sprintf("%s:/go", projectOptions.GoPath))
+ }
+
+ buildCommand.Add(fmt.Sprintf("wailsapp/xgo:%s", xgoVersion))
+ buildCommand.Add(".")
+
+ compileMessage := fmt.Sprintf(
+ "Packing + Compiling project for %s/%s using docker image wailsapp/xgo:%s",
+ projectOptions.Platform, projectOptions.Architecture, xgoVersion)
+
+ if buildMode == BuildModeDebug {
+ compileMessage += " (Debug Mode)"
+ }
+
+ if !projectOptions.Verbose {
+ packSpinner = spinner.New(compileMessage + "...")
+ packSpinner.SetSpinSpeed(50)
+ packSpinner.Start()
+ } else {
+ println(compileMessage)
+ }
+
+ err := NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
+ if err != nil {
+ if packSpinner != nil {
+ packSpinner.Error()
+ }
+ return err
+ }
+ if packSpinner != nil {
+ packSpinner.Success()
+ }
+
+ return nil
+}
+
+// BuildNative builds on the target platform itself.
+func BuildNative(binaryName string, forceRebuild bool, buildMode string, projectOptions *ProjectOptions) error {
+
+ // Check Mewn is installed
+ if err := CheckMewn(projectOptions.Verbose); err != nil {
+ return err
+ }
+
+ if err := CheckWindres(); err != nil {
+ return err
+ }
+
+ compileMessage := "Packing + Compiling project"
+
+ if buildMode == BuildModeDebug {
+ compileMessage += " (Debug Mode)"
+ }
+
+ var packSpinner *spinner.Spinner
+ if !projectOptions.Verbose {
+ packSpinner = spinner.New(compileMessage + "...")
+ packSpinner.SetSpinSpeed(50)
+ packSpinner.Start()
+ } else {
+ println(compileMessage)
+ }
+
+ buildCommand := slicer.String()
+ buildCommand.Add("go")
+
+ buildCommand.Add("build")
+
+ if binaryName != "" {
+ // Alter binary name based on OS
+ switch projectOptions.Platform {
+ case "windows":
+ if !strings.HasSuffix(binaryName, ".exe") {
+ binaryName += ".exe"
+ }
+ default:
+ if strings.HasSuffix(binaryName, ".exe") {
+ binaryName = strings.TrimSuffix(binaryName, ".exe")
+ }
+ }
+ buildCommand.Add("-o", filepath.Join("build", binaryName))
+ }
+
+ // If we are forcing a rebuild
+ if forceRebuild {
+ buildCommand.Add("-a")
+ }
+
+ buildCommand.AddSlice([]string{"-ldflags", ldFlags(projectOptions, buildMode)})
+
+ if projectOptions.Tags != "" {
+ buildCommand.AddSlice([]string{"--tags", projectOptions.Tags})
+ }
+
+ if projectOptions.Verbose {
+ fmt.Printf("Command: %v\n", buildCommand.AsSlice())
+ }
+
+ err := NewProgramHelper(projectOptions.Verbose).RunCommandArray(buildCommand.AsSlice())
+ if err != nil {
+ if packSpinner != nil {
+ packSpinner.Error()
+ }
+ return err
+ }
+ if packSpinner != nil {
+ packSpinner.Success()
+ }
+
+ return nil
+}
+
+// BuildApplication will attempt to build the project based on the given inputs
+func BuildApplication(binaryName string, forceRebuild bool, buildMode string, packageApp bool, projectOptions *ProjectOptions) error {
+ var err error
+
+ // embed resources
+ targetFiles, err := EmbedAssets()
+ if err != nil {
+ return err
+ }
+
+ if projectOptions.CrossCompile {
+ if err := InitializeCrossCompilation(projectOptions.Verbose); err != nil {
+ return err
+ }
+ }
+
+ helper := NewPackageHelper(projectOptions.Platform)
+
+ // Generate windows resources
+ if projectOptions.Platform == "windows" {
+ if err := helper.PackageWindows(projectOptions, false); err != nil {
+ return err
+ }
+ }
+
+ // cleanup temporary embedded assets
+ defer func() {
+ for _, filename := range targetFiles {
+ if err := os.Remove(filename); err != nil {
+ fmt.Println(err)
+ }
+ }
+ // Removed by popular demand
+ // TODO: Potentially add a flag to cleanup
+ // if projectOptions.Platform == "windows" {
+ // helper.CleanWindows(projectOptions)
+ // }
+ }()
+
+ if projectOptions.CrossCompile {
+ err = BuildDocker(binaryName, buildMode, projectOptions)
+ } else {
+ err = BuildNative(binaryName, forceRebuild, buildMode, projectOptions)
+ }
+ if err != nil {
+ return err
+ }
+
+ if packageApp {
+ err = PackageApplication(projectOptions)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// PackageApplication will attempt to package the application in a platform dependent way
+func PackageApplication(projectOptions *ProjectOptions) error {
+ var packageSpinner *spinner.Spinner
+ if projectOptions.Verbose {
+ packageSpinner = spinner.New("Packaging application...")
+ packageSpinner.SetSpinSpeed(50)
+ packageSpinner.Start()
+ }
+
+ err := NewPackageHelper(projectOptions.Platform).Package(projectOptions)
+ if err != nil {
+ if packageSpinner != nil {
+ packageSpinner.Error()
+ }
+ return err
+ }
+ if packageSpinner != nil {
+ packageSpinner.Success()
+ }
+ return nil
+}
+
+// BuildFrontend runs the given build command
+func BuildFrontend(projectOptions *ProjectOptions) error {
+ var buildFESpinner *spinner.Spinner
+ if !projectOptions.Verbose {
+ buildFESpinner = spinner.New("Building frontend...")
+ buildFESpinner.SetSpinSpeed(50)
+ buildFESpinner.Start()
+ } else {
+ println("Building frontend...")
+ }
+ err := NewProgramHelper(projectOptions.Verbose).RunCommand(projectOptions.FrontEnd.Build)
+ if err != nil {
+ if buildFESpinner != nil {
+ buildFESpinner.Error()
+ }
+ return err
+ }
+ if buildFESpinner != nil {
+ buildFESpinner.Success()
+ }
+ return nil
+}
+
+// CheckMewn checks if mewn is installed and if not, attempts to fetch it
+func CheckMewn(verbose bool) (err error) {
+ programHelper := NewProgramHelper(verbose)
+ if !programHelper.IsInstalled("mewn") {
+ var buildSpinner *spinner.Spinner
+ if !verbose {
+ buildSpinner = spinner.New()
+ buildSpinner.SetSpinSpeed(50)
+ buildSpinner.Start("Installing Mewn asset packer...")
+ }
+ err := programHelper.InstallGoPackage("github.com/leaanthony/mewn/cmd/mewn")
+ if err != nil {
+ if buildSpinner != nil {
+ buildSpinner.Error()
+ }
+ return err
+ }
+ if buildSpinner != nil {
+ buildSpinner.Success()
+ }
+ }
+ return nil
+}
+
+// CheckWindres checks if Windres is installed and if not, aborts
+func CheckWindres() (err error) {
+ if runtime.GOOS != "windows" { // FIXME: Handle windows cross-compile for windows!
+ return nil
+ }
+ programHelper := NewProgramHelper()
+ if !programHelper.IsInstalled("windres") {
+ return fmt.Errorf("windres not installed. It comes by default with mingw. Ensure you have installed mingw correctly")
+ }
+ return nil
+}
+
+// CheckIfInstalled returns if application is installed
+func CheckIfInstalled(application string) (err error) {
+ programHelper := NewProgramHelper()
+ if !programHelper.IsInstalled(application) {
+ return fmt.Errorf("%s not installed. Ensure you have installed %s correctly", application, application)
+ }
+ return nil
+}
+
+// InstallFrontendDeps attempts to install the frontend dependencies based on the given options
+func InstallFrontendDeps(projectDir string, projectOptions *ProjectOptions, forceRebuild bool, caller string) error {
+
+ // Install frontend deps
+ err := os.Chdir(projectOptions.FrontEnd.Dir)
+ if err != nil {
+ return err
+ }
+
+ // Check if frontend deps have been updated
+ var feSpinner *spinner.Spinner
+ if !projectOptions.Verbose {
+ feSpinner = spinner.New("Ensuring frontend dependencies are up to date (This may take a while)")
+ feSpinner.SetSpinSpeed(50)
+ feSpinner.Start()
+ } else {
+ println("Ensuring frontend dependencies are up to date (This may take a while)")
+ }
+
+ requiresNPMInstall := true
+
+ // Read in package.json MD5
+ fs := NewFSHelper()
+ packageJSONMD5, err := fs.FileMD5("package.json")
+ if err != nil {
+ return err
+ }
+
+ const md5sumFile = "package.json.md5"
+
+ // If node_modules does not exist, force a rebuild.
+ nodeModulesPath, err := filepath.Abs(filepath.Join(".", "node_modules"))
+ if err != nil {
+ return err
+ }
+ if !fs.DirExists(nodeModulesPath) {
+ forceRebuild = true
+ }
+
+ // If we aren't forcing the install and the md5sum file exists
+ if !forceRebuild && fs.FileExists(md5sumFile) {
+ // Yes - read contents
+ savedMD5sum, err := fs.LoadAsString(md5sumFile)
+ // File exists
+ if err == nil {
+ // Compare md5
+ if savedMD5sum == packageJSONMD5 {
+ // Same - no need for reinstall
+ requiresNPMInstall = false
+ if feSpinner != nil {
+ feSpinner.Success("Skipped frontend dependencies (-f to force rebuild)")
+ } else {
+ println("Skipped frontend dependencies (-f to force rebuild)")
+ }
+ }
+ }
+ }
+
+ // Md5 sum package.json
+ // Different? Build
+ if requiresNPMInstall || forceRebuild {
+ // Install dependencies
+ err = NewProgramHelper(projectOptions.Verbose).RunCommand(projectOptions.FrontEnd.Install)
+ if err != nil {
+ if feSpinner != nil {
+ feSpinner.Error()
+ }
+ return err
+ }
+ if feSpinner != nil {
+ feSpinner.Success()
+ }
+
+ // Update md5sum file
+ ioutil.WriteFile(md5sumFile, []byte(packageJSONMD5), 0644)
+ }
+
+ // Install the runtime
+ err = InstallRuntime(caller, projectDir, projectOptions)
+ if err != nil {
+ return err
+ }
+
+ // Build frontend
+ err = BuildFrontend(projectOptions)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// InstallRuntime installs the correct runtime for the type of build
+func InstallRuntime(caller string, projectDir string, projectOptions *ProjectOptions) error {
+ if caller == "build" {
+ return InstallProdRuntime(projectDir, projectOptions)
+ }
+
+ return InstallBridge(projectDir, projectOptions)
+}
+
+// InstallBridge installs the relevant bridge javascript library
+func InstallBridge(projectDir string, projectOptions *ProjectOptions) error {
+ bridgeFileData := mewn.String("../runtime/assets/bridge.js")
+ bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, "node_modules", "@wailsapp", "runtime", "init.js")
+ err := fs.CreateFile(bridgeFileTarget, []byte(bridgeFileData))
+ return err
+}
+
+// InstallProdRuntime installs the production runtime
+func InstallProdRuntime(projectDir string, projectOptions *ProjectOptions) error {
+ prodInit := mewn.String("../runtime/js/runtime/init.js")
+ bridgeFileTarget := filepath.Join(projectDir, projectOptions.FrontEnd.Dir, "node_modules", "@wailsapp", "runtime", "init.js")
+ err := fs.CreateFile(bridgeFileTarget, []byte(prodInit))
+ return err
+}
+
+// ServeProject attempts to serve up the current project so that it may be connected to
+// via the Wails bridge
+func ServeProject(projectOptions *ProjectOptions, logger *Logger) error {
+ go func() {
+ time.Sleep(2 * time.Second)
+ if projectOptions.Platform == "windows" {
+ logger.Yellow("*** Please note: Windows builds use mshtml which is only compatible with IE11. We strongly recommend only using IE11 when running 'wails serve'! For more information, please read https://wails.app/guides/windows/ ***")
+ }
+ logger.Green(">>>>> To connect, you will need to run '" + projectOptions.FrontEnd.Serve + "' in the '" + projectOptions.FrontEnd.Dir + "' directory <<<<<")
+ }()
+ location, err := filepath.Abs(filepath.Join("build", projectOptions.BinaryName))
+ if err != nil {
+ return err
+ }
+
+ logger.Yellow("Serving Application: " + location)
+ cmd := exec.Command(location)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func ldFlags(po *ProjectOptions, buildMode string) string {
+ // Setup ld flags
+ ldflags := "-w -s "
+ if buildMode == BuildModeDebug {
+ ldflags = ""
+ }
+
+ // Add windows flags
+ if po.Platform == "windows" && buildMode == BuildModeProd {
+ ldflags += "-H windowsgui "
+ }
+
+ if po.UseFirebug {
+ ldflags += "-X github.com/wailsapp/wails/lib/renderer.UseFirebug=true "
+ }
+
+ ldflags += "-X github.com/wailsapp/wails.BuildMode=" + buildMode
+
+ // Add additional ldflags passed in via the `ldflags` cli flag
+ if len(po.LdFlags) > 0 {
+ ldflags += " " + po.LdFlags
+ }
+
+ // If we wish to generate typescript
+ if po.typescriptDefsFilename != "" {
+ cwd, err := os.Getwd()
+ if err == nil {
+ filename := filepath.Join(cwd, po.FrontEnd.Dir, po.typescriptDefsFilename)
+ ldflags += " -X github.com/wailsapp/wails/lib/binding.typescriptDefinitionFilename=" + filename
+ }
+ }
+ return ldflags
+}
diff --git a/cmd/linux.go b/cmd/linux.go
new file mode 100644
index 000000000..4d5b91ca0
--- /dev/null
+++ b/cmd/linux.go
@@ -0,0 +1,301 @@
+package cmd
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "runtime"
+ "strings"
+
+ "github.com/pkg/browser"
+)
+
+// LinuxDistribution is of type int
+type LinuxDistribution int
+
+const (
+ // Unknown is the catch-all distro
+ Unknown LinuxDistribution = iota
+ // Debian distribution
+ Debian
+ // Ubuntu distribution
+ Ubuntu
+ // Arch linux distribution
+ Arch
+ // CentOS linux distribution
+ CentOS
+ // Fedora linux distribution
+ Fedora
+ // Gentoo distribution
+ Gentoo
+ // Zorin distribution
+ Zorin
+ // Parrot distribution
+ Parrot
+ // Linuxmint distribution
+ Linuxmint
+ // VoidLinux distribution
+ VoidLinux
+ // Elementary distribution
+ Elementary
+ // Kali distribution
+ Kali
+ // Neon distribution
+ Neon
+ // ArcoLinux distribution
+ ArcoLinux
+ // Manjaro distribution
+ Manjaro
+ // ManjaroARM distribution
+ ManjaroARM
+ // Deepin distribution
+ Deepin
+ // Raspbian distribution
+ Raspbian
+ // Tumbleweed (OpenSUSE) distribution
+ Tumbleweed
+ // Leap (OpenSUSE) distribution
+ Leap
+ // ArchLabs distribution
+ ArchLabs
+ // PopOS distribution
+ PopOS
+ // Solus distribution
+ Solus
+ // Ctlos Linux distribution
+ Ctlos
+ // EndeavourOS linux distribution
+ EndeavourOS
+)
+
+// DistroInfo contains all the information relating to a linux distribution
+type DistroInfo struct {
+ Distribution LinuxDistribution
+ Name string
+ ID string
+ Description string
+ Release string
+}
+
+// GetLinuxDistroInfo returns information about the running linux distribution
+func GetLinuxDistroInfo() *DistroInfo {
+ result := &DistroInfo{
+ Distribution: Unknown,
+ ID: "unknown",
+ Name: "Unknown",
+ }
+ _, err := os.Stat("/etc/os-release")
+ if !os.IsNotExist(err) {
+ osRelease, _ := ioutil.ReadFile("/etc/os-release")
+ result = parseOsRelease(string(osRelease))
+ }
+ return result
+}
+
+// parseOsRelease parses the given os-release data and returns
+// a DistroInfo struct with the details
+func parseOsRelease(osRelease string) *DistroInfo {
+ result := &DistroInfo{Distribution: Unknown}
+
+ // Default value
+ osID := "unknown"
+ osNAME := "Unknown"
+ version := ""
+
+ // Split into lines
+ lines := strings.Split(osRelease, "\n")
+ // Iterate lines
+ for _, line := range lines {
+ // Split each line by the equals char
+ splitLine := strings.SplitN(line, "=", 2)
+ // Check we have
+ if len(splitLine) != 2 {
+ continue
+ }
+ switch splitLine[0] {
+ case "ID":
+ osID = strings.ToLower(strings.Trim(splitLine[1], "\""))
+ case "NAME":
+ osNAME = strings.Trim(splitLine[1], "\"")
+ case "VERSION_ID":
+ version = strings.Trim(splitLine[1], "\"")
+ }
+ }
+
+ // Check distro name against list of distros
+ switch osID {
+ case "fedora":
+ result.Distribution = Fedora
+ case "centos":
+ result.Distribution = CentOS
+ case "arch":
+ result.Distribution = Arch
+ case "archlabs":
+ result.Distribution = ArchLabs
+ case "ctlos":
+ result.Distribution = Ctlos
+ case "debian":
+ result.Distribution = Debian
+ case "ubuntu":
+ result.Distribution = Ubuntu
+ case "gentoo":
+ result.Distribution = Gentoo
+ case "zorin":
+ result.Distribution = Zorin
+ case "parrot":
+ result.Distribution = Parrot
+ case "linuxmint":
+ result.Distribution = Linuxmint
+ case "void":
+ result.Distribution = VoidLinux
+ case "elementary":
+ result.Distribution = Elementary
+ case "kali":
+ result.Distribution = Kali
+ case "neon":
+ result.Distribution = Neon
+ case "arcolinux":
+ result.Distribution = ArcoLinux
+ case "manjaro":
+ result.Distribution = Manjaro
+ case "manjaro-arm":
+ result.Distribution = ManjaroARM
+ case "deepin":
+ result.Distribution = Deepin
+ case "raspbian":
+ result.Distribution = Raspbian
+ case "opensuse-tumbleweed":
+ result.Distribution = Tumbleweed
+ case "opensuse-leap":
+ result.Distribution = Leap
+ case "pop":
+ result.Distribution = PopOS
+ case "solus":
+ result.Distribution = Solus
+ case "endeavouros":
+ result.Distribution = EndeavourOS
+ default:
+ result.Distribution = Unknown
+ }
+
+ result.Name = osNAME
+ result.ID = osID
+ result.Release = version
+
+ return result
+}
+
+// CheckPkgInstalled is all functions that use local programs to see if a package is installed
+type CheckPkgInstalled func(string) (bool, error)
+
+// EqueryInstalled uses equery to see if a package is installed
+func EqueryInstalled(packageName string) (bool, error) {
+ program := NewProgramHelper()
+ equery := program.FindProgram("equery")
+ if equery == nil {
+ return false, fmt.Errorf("cannont check dependencies: equery not found")
+ }
+ _, _, exitCode, _ := equery.Run("l", packageName)
+ return exitCode == 0, nil
+}
+
+// DpkgInstalled uses dpkg to see if a package is installed
+func DpkgInstalled(packageName string) (bool, error) {
+ program := NewProgramHelper()
+ dpkg := program.FindProgram("dpkg")
+ if dpkg == nil {
+ return false, fmt.Errorf("cannot check dependencies: dpkg not found")
+ }
+ _, _, exitCode, _ := dpkg.Run("-L", packageName)
+ return exitCode == 0, nil
+}
+
+// EOpkgInstalled uses dpkg to see if a package is installed
+func EOpkgInstalled(packageName string) (bool, error) {
+ program := NewProgramHelper()
+ eopkg := program.FindProgram("eopkg")
+ if eopkg == nil {
+ return false, fmt.Errorf("cannot check dependencies: eopkg not found")
+ }
+ stdout, _, _, _ := eopkg.Run("info", packageName)
+ return strings.HasPrefix(stdout, "Installed"), nil
+}
+
+// PacmanInstalled uses pacman to see if a package is installed.
+func PacmanInstalled(packageName string) (bool, error) {
+ program := NewProgramHelper()
+ pacman := program.FindProgram("pacman")
+ if pacman == nil {
+ return false, fmt.Errorf("cannot check dependencies: pacman not found")
+ }
+ _, _, exitCode, _ := pacman.Run("-Qs", packageName)
+ return exitCode == 0, nil
+}
+
+// XbpsInstalled uses pacman to see if a package is installed.
+func XbpsInstalled(packageName string) (bool, error) {
+ program := NewProgramHelper()
+ xbpsQuery := program.FindProgram("xbps-query")
+ if xbpsQuery == nil {
+ return false, fmt.Errorf("cannot check dependencies: xbps-query not found")
+ }
+ _, _, exitCode, _ := xbpsQuery.Run("-S", packageName)
+ return exitCode == 0, nil
+}
+
+// RpmInstalled uses rpm to see if a package is installed
+func RpmInstalled(packageName string) (bool, error) {
+ program := NewProgramHelper()
+ rpm := program.FindProgram("rpm")
+ if rpm == nil {
+ return false, fmt.Errorf("cannot check dependencies: rpm not found")
+ }
+ _, _, exitCode, _ := rpm.Run("--query", packageName)
+ return exitCode == 0, nil
+}
+
+// RequestSupportForDistribution promts the user to submit a request to support their
+// currently unsupported distribution
+func RequestSupportForDistribution(distroInfo *DistroInfo) error {
+ var logger = NewLogger()
+ defaultError := fmt.Errorf("unable to check libraries on distribution '%s'", distroInfo.Name)
+
+ logger.Yellow("Distribution '%s' is not currently supported, but we would love to!", distroInfo.Name)
+ q := fmt.Sprintf("Would you like to submit a request to support distribution '%s'?", distroInfo.Name)
+ result := Prompt(q, "yes")
+ if strings.ToLower(result) != "yes" {
+ return defaultError
+ }
+
+ title := fmt.Sprintf("Support Distribution '%s'", distroInfo.Name)
+
+ var str strings.Builder
+
+ gomodule, exists := os.LookupEnv("GO111MODULE")
+ if !exists {
+ gomodule = "(Not Set)"
+ }
+
+ str.WriteString("\n| Name | Value |\n| ----- | ----- |\n")
+ str.WriteString(fmt.Sprintf("| Wails Version | %s |\n", Version))
+ str.WriteString(fmt.Sprintf("| Go Version | %s |\n", runtime.Version()))
+ str.WriteString(fmt.Sprintf("| Platform | %s |\n", runtime.GOOS))
+ str.WriteString(fmt.Sprintf("| Arch | %s |\n", runtime.GOARCH))
+ str.WriteString(fmt.Sprintf("| GO111MODULE | %s |\n", gomodule))
+ str.WriteString(fmt.Sprintf("| Distribution ID | %s |\n", distroInfo.ID))
+ str.WriteString(fmt.Sprintf("| Distribution Name | %s |\n", distroInfo.Name))
+ str.WriteString(fmt.Sprintf("| Distribution Version | %s |\n", distroInfo.Release))
+
+ body := fmt.Sprintf("**Description**\nDistribution '%s' is currently unsupported.\n\n**Further Information**\n\n%s\n\n*Please add any extra information here, EG: libraries that are needed to make the distribution work, or commands to install them*", distroInfo.ID, str.String())
+ fullURL := "https://github.com/wailsapp/wails/issues/new?"
+ params := "title=" + title + "&body=" + body
+
+ fmt.Println("Opening browser to file request.")
+ browser.OpenURL(fullURL + url.PathEscape(params))
+ result = Prompt("We have a guide for adding support for your distribution. Would you like to view it?", "yes")
+ if strings.ToLower(result) == "yes" {
+ browser.OpenURL("https://wails.app/guides/distro/")
+ }
+ return nil
+}
diff --git a/cmd/linux_test.go b/cmd/linux_test.go
new file mode 100644
index 000000000..73f7eec43
--- /dev/null
+++ b/cmd/linux_test.go
@@ -0,0 +1,46 @@
+package cmd
+
+import "testing"
+
+func TestUbuntuDetection(t *testing.T) {
+ osrelease := `
+NAME="Ubuntu"
+VERSION="18.04.2 LTS (Bionic Beaver)"
+ID=ubuntu
+ID_LIKE=debian
+PRETTY_NAME="Ubuntu 18.04.2 LTS"
+VERSION_ID="18.04"
+HOME_URL="https://www.ubuntu.com/"
+SUPPORT_URL="https://help.ubuntu.com/"
+BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
+PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
+VERSION_CODENAME=bionic
+UBUNTU_CODENAME=bionic
+`
+
+ result := parseOsRelease(osrelease)
+ if result.Distribution != Ubuntu {
+ t.Errorf("expected 'Ubuntu' ID but got '%d'", result.Distribution)
+ }
+}
+
+func TestTumbleweedDetection(t *testing.T) {
+ osrelease := `
+NAME="openSUSE Tumbleweed"
+# VERSION="20200414"
+ID="opensuse-tumbleweed"
+ID_LIKE="opensuse suse"
+VERSION_ID="20200414"
+PRETTY_NAME="openSUSE Tumbleweed"
+ANSI_COLOR="0;32"
+CPE_NAME="cpe:/o:opensuse:tumbleweed:20200414"
+BUG_REPORT_URL="https://bugs.opensuse.org"
+HOME_URL="https://www.opensuse.org/"
+LOGO="distributor-logo"
+`
+
+ result := parseOsRelease(osrelease)
+ if result.Distribution != Tumbleweed {
+ t.Errorf("expected 'Tumbleweed' ID but got '%d'", result.Distribution)
+ }
+}
diff --git a/cmd/linuxdb.go b/cmd/linuxdb.go
new file mode 100644
index 000000000..fbea6b686
--- /dev/null
+++ b/cmd/linuxdb.go
@@ -0,0 +1,93 @@
+package cmd
+
+import (
+ "log"
+
+ "gopkg.in/yaml.v3"
+)
+
+// LinuxDB is the database for linux distribution data.
+type LinuxDB struct {
+ Distributions map[string]*Distribution `yaml:"distributions"`
+}
+
+// Distribution holds the os-release ID and a map of releases.
+type Distribution struct {
+ ID string `yaml:"id"`
+ Releases map[string]*Release `yaml:"releases"`
+}
+
+// GetRelease attempts to return the specific Release information
+// for the given release name. If there is no specific match, the
+// default release data is returned.
+func (d *Distribution) GetRelease(version string) *Release {
+ result := d.Releases[version]
+ if result == nil {
+ result = d.Releases["default"]
+ }
+ return result
+}
+
+// Release holds the name and version of the release as given by
+// os-release. Programs is a slice of dependant programs required
+// to be present on the local installation for Wails to function.
+// Libraries is a slice of libraries that must be present for Wails
+// applications to compile.
+type Release struct {
+ Name string `yaml:"name"`
+ Version string `yaml:"version"`
+ GccVersionCommand string `yaml:"gccversioncommand"`
+ Programs []*Prerequisite `yaml:"programs"`
+ Libraries []*Prerequisite `yaml:"libraries"`
+}
+
+// Prerequisite is a simple struct containing a program/library name
+// plus the distribution specific help text indicating how to install
+// it.
+type Prerequisite struct {
+ Name string `yaml:"name"`
+ Help string `yaml:"help,omitempty"`
+}
+
+// Load will load the given filename from disk and attempt to
+// import the data into the LinuxDB.
+func (l *LinuxDB) Load(filename string) error {
+ if fs.FileExists(filename) {
+ data, err := fs.LoadAsBytes(filename)
+ if err != nil {
+ return err
+ }
+ return l.ImportData(data)
+ }
+ return nil
+}
+
+// ImportData will unmarshal the given YAML formatted data
+// into the LinuxDB
+func (l *LinuxDB) ImportData(data []byte) error {
+ return yaml.Unmarshal(data, l)
+}
+
+// GetDistro returns the Distribution information for the
+// given distribution name. If the distribution is not supported,
+// nil is returned.
+func (l *LinuxDB) GetDistro(distro string) *Distribution {
+ return l.Distributions[distro]
+}
+
+// NewLinuxDB creates a new LinuxDB instance from the bundled
+// linuxdb.yaml file.
+func NewLinuxDB() *LinuxDB {
+ data, err := fs.LoadRelativeFile("./linuxdb.yaml")
+ if err != nil {
+ log.Fatal("Could not load linuxdb.yaml")
+ }
+ result := LinuxDB{
+ Distributions: make(map[string]*Distribution),
+ }
+ err = result.ImportData(data)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return &result
+}
diff --git a/cmd/linuxdb.yaml b/cmd/linuxdb.yaml
new file mode 100644
index 000000000..616ccd0f6
--- /dev/null
+++ b/cmd/linuxdb.yaml
@@ -0,0 +1,309 @@
+---
+distributions:
+ debian:
+ id: debian
+ releases:
+ default:
+ name: Debian
+ version: default
+ gccversioncommand: &gccdumpversion -dumpversion
+ programs: &debiandefaultprograms
+ - name: gcc
+ help: Please install with `sudo apt-get install build-essential` and try again
+ - name: pkg-config
+ help: Please install with `sudo apt-get install pkg-config` and try again
+ - name: npm
+ help: Please install with `curl -sL https://deb.nodesource.com/setup_12.x | sudo bash - && sudo apt-get install -y nodejs` and try again
+ libraries: &debiandefaultlibraries
+ - name: libgtk-3-dev
+ help: Please install with `sudo apt-get install libgtk-3-dev` and try again
+ - name: libwebkit2gtk-4.0-dev
+ help: Please install with `sudo apt-get install libwebkit2gtk-4.0-dev` and try again
+ ubuntu:
+ id: ubuntu
+ releases:
+ default:
+ version: default
+ name: Ubuntu
+ gccversioncommand: &gccdumpfullversion -dumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ pop:
+ id: pop
+ releases:
+ default:
+ version: default
+ name: Pop!_OS
+ gccversioncommand: &gccdumpfullversion -dumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ kali:
+ id: kali
+ releases:
+ default:
+ version: default
+ name: Kali GNU/Linux
+ gccversioncommand: *gccdumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ parrot:
+ id: parrot
+ releases:
+ default:
+ version: default
+ name: Parrot GNU/Linux
+ gccversioncommand: *gccdumpversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ zorin:
+ id: zorin
+ releases:
+ default:
+ version: default
+ name: Zorin
+ gccversioncommand: *gccdumpversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ linuxmint:
+ id: linuxmint
+ releases:
+ default:
+ version: default
+ name: Linux Mint
+ gccversioncommand: *gccdumpversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ elementary:
+ id: elementary
+ releases:
+ default:
+ version: default
+ name: elementary OS
+ gccversioncommand: *gccdumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ neon:
+ id: neon
+ releases:
+ default:
+ version: default
+ name: KDE neon
+ gccversioncommand: *gccdumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ deepin:
+ id: deepin
+ releases:
+ default:
+ version: default
+ name: Deepin
+ gccversioncommand: *gccdumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ void:
+ id: void
+ releases:
+ default:
+ version: default
+ name: VoidLinux
+ gccversioncommand: *gccdumpversion
+ programs:
+ - name: gcc
+ help: Please install with `xbps-install base-devel` and try again
+ - name: pkg-config
+ help: Please install with `xbps-install pkg-config` and try again
+ - name: npm
+ help: Please install with `xbps-install nodejs` and try again
+ libraries:
+ - name: gtk+3-devel
+ help: Please install with `xbps-install gtk+3-devel` and try again
+ - name: webkit2gtk-devel
+ help: Please install with `xbps-install webkit2gtk-devel` and try again
+ centos:
+ id: centos
+ releases:
+ default:
+ version: default
+ name: CentOS Linux
+ gccversioncommand: *gccdumpversion
+ programs:
+ - name: gcc
+ help: Please install with `sudo yum install gcc-c++ make` and try again
+ - name: pkg-config
+ help: Please install with `sudo yum install pkgconf-pkg-config` and try again
+ - name: npm
+ help: Please install with `sudo yum install epel-release && sudo yum install nodejs` and try again
+ libraries:
+ - name: gtk3-devel
+ help: Please install with `sudo yum install gtk3-devel` and try again
+ - name: webkitgtk3-devel
+ help: Please install with `sudo yum install webkitgtk3-devel` and try again
+ fedora:
+ id: fedora
+ releases:
+ default:
+ version: default
+ name: Fedora
+ gccversioncommand: *gccdumpfullversion
+ programs:
+ - name: gcc
+ help: Please install with `sudo yum install gcc-c++ make` and try again
+ - name: pkg-config
+ help: Please install with `sudo yum install pkgconf-pkg-config` and try again
+ - name: npm
+ help: Please install `sudo yum install nodejs` and try again
+ libraries:
+ - name: gtk3-devel
+ help: Please install with `sudo yum install gtk3-devel` and try again
+ - name: webkit2gtk3-devel
+ help: Please install with `sudo yum install webkit2gtk3-devel` and try again
+ arch:
+ id: arch
+ releases:
+ default:
+ version: default
+ name: Arch Linux
+ gccversioncommand: *gccdumpversion
+ programs: &archdefaultprograms
+ - name: gcc
+ help: Please install with `sudo pacman -S gcc` and try again
+ - name: pkgconf
+ help: Please install with `sudo pacman -S pkgconf` and try again
+ - name: npm
+ help: Please install with `sudo pacman -S npm` and try again
+ libraries: &archdefaultlibraries
+ - name: gtk3
+ help: Please install with `sudo pacman -S gtk3` and try again
+ - name: webkit2gtk
+ help: Please install with `sudo pacman -S webkit2gtk` and try again
+ arcolinux:
+ id: arcolinux
+ releases:
+ default:
+ version: default
+ name: ArcoLinux
+ gccversioncommand: *gccdumpversion
+ programs: *archdefaultprograms
+ libraries: *archdefaultlibraries
+ archlabs:
+ id: archlabs
+ releases:
+ default:
+ version: default
+ name: ArchLabs
+ gccversioncommand: *gccdumpversion
+ programs: *archdefaultprograms
+ libraries: *archdefaultlibraries
+ ctlos:
+ id: ctlos
+ releases:
+ default:
+ version: default
+ name: Ctlos Linux
+ gccversioncommand: *gccdumpversion
+ programs: *archdefaultprograms
+ libraries: *archdefaultlibraries
+ endeavouros:
+ id: endeavouros
+ releases:
+ default:
+ version: default
+ name: EndeavourOS
+ gccversioncommand: *gccdumpversion
+ programs: *archdefaultprograms
+ libraries: *archdefaultlibraries
+ manjaro:
+ id: manjaro
+ releases:
+ default:
+ version: default
+ name: Manjaro Linux
+ gccversioncommand: *gccdumpversion
+ programs: *archdefaultprograms
+ libraries: *archdefaultlibraries
+ manjaro-arm:
+ id: manjaro-arm
+ releases:
+ default:
+ version: default
+ name: Manjaro-ARM
+ gccversioncommand: *gccdumpversion
+ programs: *archdefaultprograms
+ libraries: *archdefaultlibraries
+ gentoo:
+ id: gentoo
+ releases:
+ default:
+ version: default
+ name: Gentoo
+ gccversioncommand: *gccdumpversion
+ programs:
+ - name: gcc
+ help: Please install using your system's package manager
+ - name: pkg-config
+ help: Please install using your system's package manager
+ - name: npm
+ help: Please install using your system's package manager
+ libraries:
+ - name: gtk+:3
+ help: Please install with `sudo emerge gtk+:3` and try again
+ - name: webkit-gtk
+ help: Please install with `sudo emerge webkit-gtk` and try again
+
+ raspbian:
+ id: raspbian
+ releases:
+ default:
+ version: default
+ name: Raspbian
+ gccversioncommand: *gccdumpfullversion
+ programs: *debiandefaultprograms
+ libraries: *debiandefaultlibraries
+ solus:
+ id: solus
+ releases:
+ default:
+ version: default
+ name: Solus
+ gccversioncommand: *gccdumpfullversion
+ programs: &solusdefaultprograms
+ - name: gcc
+ help: Please install with `sudo eopkg it -c system.devel` and try again
+ - name: pkg-config
+ help: Please install with `sudo eopkg it -c system.devel` and try again
+ - name: npm
+ help: Please install with `sudo eopkg it nodejs` and try again
+ libraries: &solusdefaultlibraries
+ - name: libgtk-3-devel
+ help: Please install with `sudo eopkg it libgtk-3-devel` and try again
+ - name: libwebkit-gtk-devel
+ help: Please install with `sudo eopkg it libwebkit-gtk-devel` and try again
+
+ opensuse-tumbleweed:
+ id: opensuse-tumbleweed
+ releases:
+ default:
+ version: default
+ name: openSUSE Tumbleweed
+ gccversioncommand: *gccdumpfullversion
+ programs: &opensusedefaultprograms
+ - name: gcc
+ help: Please install with `sudo zypper in gcc-c++` and try again
+ - name: pkg-config
+ help: Please install with `sudo zypper in pkgconf-pkg-config` and try again
+ - name: npm
+ help: Please install `sudo zypper in nodejs` and try again
+ libraries: &opensusedefaultlibraries
+ - name: gtk3-devel
+ help: Please install with `sudo zypper in gtk3-devel` and try again
+ - name: webkit2gtk3-devel
+ help: Please install with `sudo zypper in webkit2gtk3-devel` and try again
+ opensuse-leap:
+ id: opensuse-leap
+ releases:
+ default:
+ version: default
+ name: openSUSE Leap
+ gccversioncommand: *gccdumpfullversion
+ programs: *opensusedefaultprograms
+ libraries: *opensusedefaultlibraries
diff --git a/cmd/linuxdb_test.go b/cmd/linuxdb_test.go
new file mode 100644
index 000000000..795a010d2
--- /dev/null
+++ b/cmd/linuxdb_test.go
@@ -0,0 +1,81 @@
+package cmd
+
+import "testing"
+
+func TestNewLinuxDB(t *testing.T) {
+ _ = NewLinuxDB()
+}
+
+func TestKnownDistro(t *testing.T) {
+ var linuxDB = NewLinuxDB()
+ result := linuxDB.GetDistro("ubuntu")
+ if result == nil {
+ t.Error("Cannot get distro 'ubuntu'")
+ }
+}
+
+func TestUnknownDistro(t *testing.T) {
+ var linuxDB = NewLinuxDB()
+ result := linuxDB.GetDistro("unknown")
+ if result != nil {
+ t.Error("Should get nil for distribution 'unknown'")
+ }
+}
+
+func TestDefaultRelease(t *testing.T) {
+ var linuxDB = NewLinuxDB()
+ result := linuxDB.GetDistro("ubuntu")
+ if result == nil {
+ t.Error("Cannot get distro 'ubuntu'")
+ }
+
+ release := result.GetRelease("default")
+ if release == nil {
+ t.Error("Cannot get release 'default' for distro 'ubuntu'")
+ }
+}
+
+func TestUnknownRelease(t *testing.T) {
+ var linuxDB = NewLinuxDB()
+ result := linuxDB.GetDistro("ubuntu")
+ if result == nil {
+ t.Error("Cannot get distro 'ubuntu'")
+ }
+
+ release := result.GetRelease("16.04")
+ if release == nil {
+ t.Error("Failed to get release 'default' for unknown release version '16.04'")
+ }
+
+ if release.Version != "default" {
+ t.Errorf("Got version '%s' instead of 'default' for unknown release version '16.04'", result.ID)
+ }
+}
+
+func TestGetPrerequisites(t *testing.T) {
+ var linuxDB = NewLinuxDB()
+ result := linuxDB.GetDistro("debian")
+ if result == nil {
+ t.Error("Cannot get distro 'debian'")
+ }
+
+ release := result.GetRelease("default")
+ if release == nil {
+ t.Error("Failed to get release 'default' for unknown release version '16.04'")
+ }
+
+ if release.Version != "default" {
+ t.Errorf("Got version '%s' instead of 'default' for unknown release version '16.04'", result.ID)
+ }
+
+ if release.Name != "Debian" {
+ t.Errorf("Got Release Name '%s' instead of 'debian' for unknown release version '16.04'", release.Name)
+ }
+
+ if len(release.Programs) != 3 {
+ t.Errorf("Expected %d programs for unknown release version '16.04'", len(release.Programs))
+ }
+ if len(release.Libraries) != 2 {
+ t.Errorf("Expected %d libraries for unknown release version '16.04'", len(release.Libraries))
+ }
+}
diff --git a/cmd/log.go b/cmd/log.go
new file mode 100644
index 000000000..416383935
--- /dev/null
+++ b/cmd/log.go
@@ -0,0 +1,130 @@
+package cmd
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/fatih/color"
+)
+
+// Logger struct
+type Logger struct {
+ errorOnly bool
+}
+
+// NewLogger creates a new logger!
+func NewLogger() *Logger {
+ return &Logger{errorOnly: false}
+}
+
+// SetErrorOnly ensures that only errors are logged out
+func (l *Logger) SetErrorOnly(errorOnly bool) {
+ l.errorOnly = errorOnly
+}
+
+// Yellow - Outputs yellow text
+func (l *Logger) Yellow(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+ color.New(color.FgHiYellow).PrintfFunc()(format+"\n", a...)
+}
+
+// Yellowf - Outputs yellow text without the newline
+func (l *Logger) Yellowf(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+
+ color.New(color.FgHiYellow).PrintfFunc()(format, a...)
+}
+
+// Green - Outputs Green text
+func (l *Logger) Green(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+
+ color.New(color.FgHiGreen).PrintfFunc()(format+"\n", a...)
+}
+
+// White - Outputs White text
+func (l *Logger) White(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+
+ color.New(color.FgHiWhite).PrintfFunc()(format+"\n", a...)
+}
+
+// WhiteUnderline - Outputs White text with underline
+func (l *Logger) WhiteUnderline(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+
+ l.White(format, a...)
+ l.White(l.underline(format))
+}
+
+// YellowUnderline - Outputs Yellow text with underline
+func (l *Logger) YellowUnderline(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+
+ l.Yellow(format, a...)
+ l.Yellow(l.underline(format))
+}
+
+// underline returns a string of a line, the length of the message given to it
+func (l *Logger) underline(message string) string {
+ if l.errorOnly {
+ return ""
+ }
+
+ return strings.Repeat("-", len(message))
+}
+
+// Red - Outputs Red text
+func (l *Logger) Red(format string, a ...interface{}) {
+ if l.errorOnly {
+ return
+ }
+
+ color.New(color.FgHiRed).PrintfFunc()(format+"\n", a...)
+}
+
+// Error - Outputs an Error message
+func (l *Logger) Error(format string, a ...interface{}) {
+ color.New(color.FgHiRed).PrintfFunc()("Error: "+format+"\n", a...)
+}
+
+// PrintSmallBanner prints a condensed banner
+func (l *Logger) PrintSmallBanner(message ...string) {
+ yellow := color.New(color.FgYellow).SprintFunc()
+ red := color.New(color.FgRed).SprintFunc()
+ msg := ""
+ if len(message) > 0 {
+ msg = " - " + message[0]
+ }
+ fmt.Printf("%s %s%s\n", yellow("Wails"), red(Version), msg)
+}
+
+// PrintBanner prints the Wails banner before running commands
+func (l *Logger) PrintBanner() error {
+ banner1 := ` _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ ` + "`" + `/ / / ___/
+| |/ |/ / /_/ / / (__ ) `
+ banner2 := `|__/|__/\__,_/_/_/____/ `
+
+ l.Yellowf(banner1)
+ l.Red(Version)
+ l.Yellowf(banner2)
+ l.Green("https://wails.app")
+ l.White("The lightweight framework for web-like apps")
+ fmt.Println()
+
+ return nil
+}
diff --git a/cmd/package.go b/cmd/package.go
new file mode 100644
index 000000000..f0ad999d3
--- /dev/null
+++ b/cmd/package.go
@@ -0,0 +1,426 @@
+package cmd
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "image"
+ "image/png"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "text/template"
+ "time"
+
+ "github.com/jackmordaunt/icns"
+ "golang.org/x/image/draw"
+)
+
+// PackageHelper helps with the 'wails package' command
+type PackageHelper struct {
+ platform string
+ fs *FSHelper
+ log *Logger
+ system *SystemHelper
+}
+
+// NewPackageHelper creates a new PackageHelper!
+func NewPackageHelper(platform string) *PackageHelper {
+ return &PackageHelper{
+ platform: platform,
+ fs: NewFSHelper(),
+ log: NewLogger(),
+ system: NewSystemHelper(),
+ }
+}
+
+type plistData struct {
+ Title string
+ Exe string
+ PackageID string
+ Version string
+ Author string
+ Date string
+}
+
+func newPlistData(title, exe, packageID, version, author string) *plistData {
+ now := time.Now().Format(time.RFC822)
+ return &plistData{
+ Title: title,
+ Exe: exe,
+ Version: version,
+ PackageID: packageID,
+ Author: author,
+ Date: now,
+ }
+}
+
+type windowsIcoHeader struct {
+ _ uint16
+ imageType uint16
+ imageCount uint16
+}
+
+type windowsIcoDescriptor struct {
+ width uint8
+ height uint8
+ colours uint8
+ _ uint8
+ planes uint16
+ bpp uint16
+ size uint32
+ offset uint32
+}
+
+type windowsIcoContainer struct {
+ Header windowsIcoDescriptor
+ Data []byte
+}
+
+func generateWindowsIcon(pngFilename string, iconfile string) error {
+ sizes := []int{256, 128, 64, 48, 32, 16}
+
+ pngfile, err := os.Open(pngFilename)
+ if err != nil {
+ return err
+ }
+ defer pngfile.Close()
+
+ pngdata, err := png.Decode(pngfile)
+ if err != nil {
+ return err
+ }
+
+ icons := []windowsIcoContainer{}
+
+ for _, size := range sizes {
+ rect := image.Rect(0, 0, int(size), int(size))
+ rawdata := image.NewRGBA(rect)
+ scale := draw.CatmullRom
+ scale.Scale(rawdata, rect, pngdata, pngdata.Bounds(), draw.Over, nil)
+
+ icondata := new(bytes.Buffer)
+ writer := bufio.NewWriter(icondata)
+ err = png.Encode(writer, rawdata)
+ if err != nil {
+ return err
+ }
+ writer.Flush()
+
+ imgSize := size
+ if imgSize >= 256 {
+ imgSize = 0
+ }
+
+ data := icondata.Bytes()
+
+ icn := windowsIcoContainer{
+ Header: windowsIcoDescriptor{
+ width: uint8(imgSize),
+ height: uint8(imgSize),
+ planes: 1,
+ bpp: 32,
+ size: uint32(len(data)),
+ },
+ Data: data,
+ }
+ icons = append(icons, icn)
+ }
+
+ outfile, err := os.Create(iconfile)
+ if err != nil {
+ return err
+ }
+ defer outfile.Close()
+
+ ico := windowsIcoHeader{
+ imageType: 1,
+ imageCount: uint16(len(sizes)),
+ }
+ err = binary.Write(outfile, binary.LittleEndian, ico)
+ if err != nil {
+ return err
+ }
+
+ offset := uint32(6 + 16*len(sizes))
+ for _, icon := range icons {
+ icon.Header.offset = offset
+ err = binary.Write(outfile, binary.LittleEndian, icon.Header)
+ if err != nil {
+ return err
+ }
+ offset += icon.Header.size
+ }
+ for _, icon := range icons {
+ _, err = outfile.Write(icon.Data)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func defaultString(val string, defaultVal string) string {
+ if val != "" {
+ return val
+ }
+ return defaultVal
+}
+
+func (b *PackageHelper) getPackageFileBaseDir() string {
+ // Calculate template base dir
+ _, filename, _, _ := runtime.Caller(1)
+ return filepath.Join(path.Dir(filename), "packages", b.platform)
+}
+
+// Package the application into a platform specific package
+func (b *PackageHelper) Package(po *ProjectOptions) error {
+ switch b.platform {
+ case "darwin":
+ return b.packageOSX(po)
+ case "windows":
+ return b.PackageWindows(po, true)
+ case "linux":
+ return b.packageLinux(po)
+ default:
+ return fmt.Errorf("platform '%s' not supported for bundling yet", b.platform)
+ }
+}
+
+func (b *PackageHelper) packageLinux(po *ProjectOptions) error {
+ return nil
+}
+
+// Package the application for OSX
+func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
+ build := path.Join(b.fs.Cwd(), "build")
+
+ system := NewSystemHelper()
+ config, err := system.LoadConfig()
+ if err != nil {
+ return err
+ }
+
+ name := defaultString(po.Name, "WailsTest")
+ exe := defaultString(po.BinaryName, name)
+ version := defaultString(po.Version, "0.1.0")
+ author := defaultString(config.Name, "Anonymous")
+ packageID := strings.Join([]string{"wails", name, version}, ".")
+ plistData := newPlistData(name, exe, packageID, version, author)
+ appname := po.Name + ".app"
+ plistFilename := path.Join(build, appname, "Contents", "Info.plist")
+ customPlist := path.Join(b.fs.Cwd(), "info.plist")
+
+ // Check binary exists
+ source := path.Join(build, exe)
+ if po.CrossCompile == true {
+ file, err := b.fs.FindFile(build, "darwin")
+ if err != nil {
+ return err
+ }
+ source = path.Join(build, file)
+ }
+
+ if !b.fs.FileExists(source) {
+ // We need to build!
+ return fmt.Errorf("Target '%s' not available. Has it been compiled yet?", source)
+ }
+ // Remove the existing package
+ os.RemoveAll(appname)
+
+ // Create directories
+ exeDir := path.Join(build, appname, "/Contents/MacOS")
+ b.fs.MkDirs(exeDir, 0755)
+ resourceDir := path.Join(build, appname, "/Contents/Resources")
+ b.fs.MkDirs(resourceDir, 0755)
+
+ // Do we have a custom plist in the project directory?
+ if !fs.FileExists(customPlist) {
+
+ // No - create a new plist from our defaults
+ tmpl := template.New("infoPlist")
+ plistFile := filepath.Join(b.getPackageFileBaseDir(), "info.plist")
+ infoPlist, err := ioutil.ReadFile(plistFile)
+ if err != nil {
+ return err
+ }
+ tmpl.Parse(string(infoPlist))
+
+ // Write the template to a buffer
+ var tpl bytes.Buffer
+ err = tmpl.Execute(&tpl, plistData)
+ if err != nil {
+ return err
+ }
+
+ // Save to the package
+ err = ioutil.WriteFile(plistFilename, tpl.Bytes(), 0644)
+ if err != nil {
+ return err
+ }
+
+ // Also write to project directory for customisation
+ err = ioutil.WriteFile(customPlist, tpl.Bytes(), 0644)
+ if err != nil {
+ return err
+ }
+ } else {
+ // Yes - we have a plist. Copy it to the package verbatim
+ err = fs.CopyFile(customPlist, plistFilename)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Copy executable
+ target := path.Join(exeDir, exe)
+ err = b.fs.CopyFile(source, target)
+ if err != nil {
+ return err
+ }
+
+ err = os.Chmod(target, 0755)
+ if err != nil {
+ return err
+ }
+ err = b.packageIconOSX(resourceDir)
+ return err
+}
+
+// CleanWindows removes any windows related files found in the directory
+func (b *PackageHelper) CleanWindows(po *ProjectOptions) {
+ pdir := b.fs.Cwd()
+ basename := strings.TrimSuffix(po.BinaryName, ".exe")
+ exts := []string{".ico", ".exe.manifest", ".rc", "-res.syso"}
+ rsrcs := []string{}
+ for _, ext := range exts {
+ rsrcs = append(rsrcs, filepath.Join(pdir, basename+ext))
+ }
+ b.fs.RemoveFiles(rsrcs, true)
+}
+
+// PackageWindows packages the application for windows platforms
+func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error {
+ outputDir := b.fs.Cwd()
+ basename := strings.TrimSuffix(po.BinaryName, ".exe")
+
+ // Copy default icon if needed
+ icon, err := b.copyIcon()
+ if err != nil {
+ return err
+ }
+
+ // Generate icon from PNG
+ err = generateWindowsIcon(icon, basename+".ico")
+ if err != nil {
+ return err
+ }
+
+ // Copy manifest
+ tgtManifestFile := filepath.Join(outputDir, basename+".exe.manifest")
+ if !b.fs.FileExists(tgtManifestFile) {
+ srcManifestfile := filepath.Join(b.getPackageFileBaseDir(), "wails.exe.manifest")
+ err := b.fs.CopyFile(srcManifestfile, tgtManifestFile)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Copy rc file
+ tgtRCFile := filepath.Join(outputDir, basename+".rc")
+ if !b.fs.FileExists(tgtRCFile) {
+ srcRCfile := filepath.Join(b.getPackageFileBaseDir(), "wails.rc")
+ rcfilebytes, err := ioutil.ReadFile(srcRCfile)
+ if err != nil {
+ return err
+ }
+ rcfiledata := strings.Replace(string(rcfilebytes), "$NAME$", basename, -1)
+ err = ioutil.WriteFile(tgtRCFile, []byte(rcfiledata), 0755)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Build syso
+ sysofile := filepath.Join(outputDir, basename+"-res.syso")
+
+ // cross-compile
+ if b.platform != runtime.GOOS {
+ args := []string{
+ "docker", "run", "--rm",
+ "-v", outputDir + ":/build",
+ "--entrypoint", "/bin/sh",
+ "wailsapp/xgo:latest",
+ "-c", "/usr/bin/x86_64-w64-mingw32-windres -o /build/" + basename + "-res.syso /build/" + basename + ".rc",
+ }
+ if err := NewProgramHelper().RunCommandArray(args); err != nil {
+ return err
+ }
+ } else {
+ batfile, err := fs.LocalDir(".")
+ if err != nil {
+ return err
+ }
+
+ windresBatFile := filepath.Join(batfile.fullPath, "windres.bat")
+ windresCommand := []string{windresBatFile, sysofile, tgtRCFile}
+ err = NewProgramHelper().RunCommandArray(windresCommand)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (b *PackageHelper) copyIcon() (string, error) {
+
+ // TODO: Read this from project.json
+ const appIconFilename = "appicon.png"
+ srcIcon := path.Join(b.fs.Cwd(), appIconFilename)
+
+ // Check if appicon.png exists
+ if !b.fs.FileExists(srcIcon) {
+
+ // Install default icon
+ iconfile := filepath.Join(b.getPackageFileBaseDir(), "icon.png")
+ iconData, err := ioutil.ReadFile(iconfile)
+ if err != nil {
+ return "", err
+ }
+ err = ioutil.WriteFile(srcIcon, iconData, 0644)
+ if err != nil {
+ return "", err
+ }
+ }
+ return srcIcon, nil
+}
+
+func (b *PackageHelper) packageIconOSX(resourceDir string) error {
+
+ srcIcon, err := b.copyIcon()
+ if err != nil {
+ return err
+ }
+ tgtBundle := path.Join(resourceDir, "iconfile.icns")
+ imageFile, err := os.Open(srcIcon)
+ if err != nil {
+ return err
+ }
+ defer imageFile.Close()
+ srcImg, _, err := image.Decode(imageFile)
+ if err != nil {
+ return err
+
+ }
+ dest, err := os.Create(tgtBundle)
+ if err != nil {
+ return err
+
+ }
+ defer dest.Close()
+ return icns.Encode(dest, srcImg)
+}
diff --git a/cmd/packages/darwin/icon.png b/cmd/packages/darwin/icon.png
new file mode 100644
index 000000000..9f22be34b
Binary files /dev/null and b/cmd/packages/darwin/icon.png differ
diff --git a/cmd/packages/darwin/info.plist b/cmd/packages/darwin/info.plist
new file mode 100644
index 000000000..c7289870f
--- /dev/null
+++ b/cmd/packages/darwin/info.plist
@@ -0,0 +1,12 @@
+
+
+ CFBundlePackageType APPL
+ CFBundleName {{.Title}}
+ CFBundleExecutable {{.Exe}}
+ CFBundleIdentifier {{.PackageID}}
+ CFBundleVersion {{.Version}}
+ CFBundleGetInfoString Built by {{.Author}} at {{.Date}} using Wails (https://wails.app)
+ CFBundleShortVersionString {{.Version}}
+ CFBundleIconFile iconfile
+ NSHighResolutionCapable true
+
\ No newline at end of file
diff --git a/cmd/packages/windows/icon.png b/cmd/packages/windows/icon.png
new file mode 100644
index 000000000..9f22be34b
Binary files /dev/null and b/cmd/packages/windows/icon.png differ
diff --git a/cmd/packages/windows/wails.exe.manifest b/cmd/packages/windows/wails.exe.manifest
new file mode 100644
index 000000000..b236d268f
--- /dev/null
+++ b/cmd/packages/windows/wails.exe.manifest
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ true/pm
+ permonitorv2,permonitor
+ true
+
+
+
\ No newline at end of file
diff --git a/cmd/packages/windows/wails.ico b/cmd/packages/windows/wails.ico
new file mode 100644
index 000000000..9b62ac5b4
Binary files /dev/null and b/cmd/packages/windows/wails.ico differ
diff --git a/cmd/packages/windows/wails.rc b/cmd/packages/windows/wails.rc
new file mode 100644
index 000000000..633bd214a
--- /dev/null
+++ b/cmd/packages/windows/wails.rc
@@ -0,0 +1,2 @@
+100 ICON "$NAME$.ico"
+110 24 "$NAME$.exe.manifest"
\ No newline at end of file
diff --git a/cmd/prerequisites.go b/cmd/prerequisites.go
new file mode 100644
index 000000000..3f88ae8ce
--- /dev/null
+++ b/cmd/prerequisites.go
@@ -0,0 +1,100 @@
+package cmd
+
+import (
+ "fmt"
+ "runtime"
+)
+
+func newPrerequisite(name, help string) *Prerequisite {
+ return &Prerequisite{Name: name, Help: help}
+}
+
+// Prerequisites is a list of things required to use Wails
+type Prerequisites []*Prerequisite
+
+// Add given prereq object to list
+func (p *Prerequisites) Add(prereq *Prerequisite) {
+ *p = append(*p, prereq)
+}
+
+// GetRequiredPrograms returns a list of programs required for the platform
+func GetRequiredPrograms() (*Prerequisites, error) {
+ switch runtime.GOOS {
+ case "darwin":
+ return getRequiredProgramsOSX(), nil
+ case "linux":
+ return getRequiredProgramsLinux(), nil
+ case "windows":
+ return getRequiredProgramsWindows(), nil
+ default:
+ return nil, fmt.Errorf("platform '%s' not supported at this time", runtime.GOOS)
+ }
+}
+
+func getRequiredProgramsOSX() *Prerequisites {
+ result := &Prerequisites{}
+ result.Add(newPrerequisite("clang", "Please install with `xcode-select --install` and try again"))
+ result.Add(newPrerequisite("npm", "Please install from https://nodejs.org/en/download/ and try again"))
+ return result
+}
+
+func getRequiredProgramsLinux() *Prerequisites {
+ result := &Prerequisites{}
+ distroInfo := GetLinuxDistroInfo()
+ if distroInfo.Distribution != Unknown {
+ var linuxDB = NewLinuxDB()
+ distro := linuxDB.GetDistro(distroInfo.ID)
+ release := distro.GetRelease(distroInfo.Release)
+ for _, program := range release.Programs {
+ result.Add(program)
+ }
+ }
+ return result
+}
+
+// TODO: Test this on Windows
+func getRequiredProgramsWindows() *Prerequisites {
+ result := &Prerequisites{}
+ result.Add(newPrerequisite("gcc", "Please install gcc from here and try again: http://tdm-gcc.tdragon.net/download. You will need to add the bin directory to your path, EG: C:\\TDM-GCC-64\\bin\\"))
+ result.Add(newPrerequisite("npm", "Please install node/npm from here and try again: https://nodejs.org/en/download/"))
+ return result
+}
+
+// GetRequiredLibraries returns a list of libraries (packages) required for the platform
+func GetRequiredLibraries() (*Prerequisites, error) {
+ switch runtime.GOOS {
+ case "darwin":
+ return getRequiredLibrariesOSX()
+ case "linux":
+ return getRequiredLibrariesLinux()
+ case "windows":
+ return getRequiredLibrariesWindows()
+ default:
+ return nil, fmt.Errorf("platform '%s' not supported at this time", runtime.GOOS)
+ }
+}
+
+func getRequiredLibrariesOSX() (*Prerequisites, error) {
+ result := &Prerequisites{}
+ return result, nil
+}
+
+func getRequiredLibrariesLinux() (*Prerequisites, error) {
+ result := &Prerequisites{}
+ // The Linux Distribution DB
+ distroInfo := GetLinuxDistroInfo()
+ if distroInfo.Distribution != Unknown {
+ var linuxDB = NewLinuxDB()
+ distro := linuxDB.GetDistro(distroInfo.ID)
+ release := distro.GetRelease(distroInfo.Release)
+ for _, library := range release.Libraries {
+ result.Add(library)
+ }
+ }
+ return result, nil
+}
+
+func getRequiredLibrariesWindows() (*Prerequisites, error) {
+ result := &Prerequisites{}
+ return result, nil
+}
diff --git a/cmd/program.go b/cmd/program.go
new file mode 100644
index 000000000..733bd51a7
--- /dev/null
+++ b/cmd/program.go
@@ -0,0 +1,162 @@
+package cmd
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "syscall"
+)
+
+// ProgramHelper - Utility functions around installed applications
+type ProgramHelper struct {
+ shell *ShellHelper
+ verbose bool
+}
+
+// NewProgramHelper - Creates a new ProgramHelper
+func NewProgramHelper(verbose ...bool) *ProgramHelper {
+ result := &ProgramHelper{
+ shell: NewShellHelper(),
+ }
+ if len(verbose) > 0 {
+ result.verbose = verbose[0]
+ if result.verbose {
+ result.shell.SetVerbose()
+ }
+ }
+ return result
+}
+
+// IsInstalled tries to determine if the given binary name is installed
+func (p *ProgramHelper) IsInstalled(programName string) bool {
+ _, err := exec.LookPath(programName)
+ return err == nil
+}
+
+// Program - A struct to define an installed application/binary
+type Program struct {
+ Name string `json:"name"`
+ Path string `json:"path"`
+ verbose bool
+}
+
+// FindProgram attempts to find the given program on the system.FindProgram
+// Returns a struct with the name and path to the program
+func (p *ProgramHelper) FindProgram(programName string) *Program {
+ path, err := exec.LookPath(programName)
+ if err != nil {
+ return nil
+ }
+ path, err = filepath.Abs(path)
+ if err != nil {
+ return nil
+ }
+ return &Program{
+ Name: programName,
+ Path: path,
+ verbose: p.verbose,
+ }
+}
+
+// GetFullPathToBinary returns the full path the the current binary
+func (p *Program) GetFullPathToBinary() (string, error) {
+ return filepath.Abs(p.Path)
+}
+
+// Run will execute the program with the given parameters
+// Returns stdout + stderr as strings and an error if one occurred
+func (p *Program) Run(vars ...string) (stdout, stderr string, exitCode int, err error) {
+ command, err := p.GetFullPathToBinary()
+ if err != nil {
+ return "", "", 1, err
+ }
+ cmd := exec.Command(command, vars...)
+ if !p.verbose {
+ var stdo, stde bytes.Buffer
+ cmd.Stdout = &stdo
+ cmd.Stderr = &stde
+ err = cmd.Run()
+ stdout = string(stdo.Bytes())
+ stderr = string(stde.Bytes())
+ } else {
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ }
+
+ // https://stackoverflow.com/questions/10385551/get-exit-code-go
+ if err != nil {
+ // try to get the exit code
+ if exitError, ok := err.(*exec.ExitError); ok {
+ ws := exitError.Sys().(syscall.WaitStatus)
+ exitCode = ws.ExitStatus()
+ } else {
+ exitCode = 1
+ if stderr == "" {
+ stderr = err.Error()
+ }
+ }
+ } else {
+ // success, exitCode should be 0 if go is ok
+ ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
+ exitCode = ws.ExitStatus()
+ }
+ return
+}
+
+// InstallGoPackage installs the given Go package
+func (p *ProgramHelper) InstallGoPackage(packageName string) error {
+ args := strings.Split("get "+packageName, " ")
+ _, stderr, err := p.shell.Run("go", args...)
+ if err != nil {
+ fmt.Println(stderr)
+ }
+ return err
+}
+
+// InstallNPMPackage installs the given npm package
+func (p *ProgramHelper) InstallNPMPackage(packageName string, save bool) error {
+ args := strings.Split("install "+packageName, " ")
+ if save {
+ args = append(args, "--save")
+ }
+ _, stderr, err := p.shell.Run("npm", args...)
+ if err != nil {
+ fmt.Println(stderr)
+ }
+ return err
+}
+
+// RunCommand runs the given command
+func (p *ProgramHelper) RunCommand(command string) error {
+ args := strings.Split(command, " ")
+ return p.RunCommandArray(args)
+}
+
+// RunCommandArray runs the command specified in the array
+func (p *ProgramHelper) RunCommandArray(args []string, dir ...string) error {
+ programCommand := args[0]
+ // TODO: Run FindProgram here and get the full path to the exe
+ program, err := exec.LookPath(programCommand)
+ if err != nil {
+ fmt.Printf("ERROR: Looks like '%s' isn't installed. Please install and try again.", programCommand)
+ return err
+ }
+
+ args = args[1:]
+ var stderr string
+ var stdout string
+ if len(dir) > 0 {
+ stdout, stderr, err = p.shell.RunInDirectory(dir[0], program, args...)
+ } else {
+ stdout, stderr, err = p.shell.Run(program, args...)
+ }
+ if err != nil {
+ fmt.Println(stderr)
+ fmt.Println(stdout)
+ }
+ return err
+}
diff --git a/cmd/project.go b/cmd/project.go
new file mode 100644
index 000000000..efe772579
--- /dev/null
+++ b/cmd/project.go
@@ -0,0 +1,407 @@
+package cmd
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strings"
+
+ "github.com/leaanthony/slicer"
+)
+
+// PackageManager indicates different package managers
+type PackageManager int
+
+const (
+ // UNKNOWN package manager
+ UNKNOWN PackageManager = iota
+ // NPM package manager
+ NPM
+ // YARN package manager
+ YARN
+)
+
+type author struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+}
+
+type frontend struct {
+ Dir string `json:"dir"`
+ Install string `json:"install"`
+ Build string `json:"build"`
+ Bridge string `json:"bridge"`
+ Serve string `json:"serve"`
+}
+
+type framework struct {
+ Name string `json:"name"`
+ BuildTag string `json:"buildtag"`
+ Options map[string]string `json:"options,omitempty"`
+}
+
+// ProjectHelper is a helper struct for managing projects
+type ProjectHelper struct {
+ log *Logger
+ system *SystemHelper
+ templates *TemplateHelper
+}
+
+// NewProjectHelper creates a new Project helper struct
+func NewProjectHelper() *ProjectHelper {
+ return &ProjectHelper{
+ log: NewLogger(),
+ system: NewSystemHelper(),
+ templates: NewTemplateHelper(),
+ }
+}
+
+// GenerateProject generates a new project using the options given
+func (ph *ProjectHelper) GenerateProject(projectOptions *ProjectOptions) error {
+
+ // Calculate project path
+ projectPath, err := filepath.Abs(projectOptions.OutputDirectory)
+ if err != nil {
+ return err
+ }
+
+ _ = projectPath
+
+ if fs.DirExists(projectPath) {
+ return fmt.Errorf("directory '%s' already exists", projectPath)
+ }
+
+ // Create project directory
+ err = fs.MkDir(projectPath)
+ if err != nil {
+ return err
+ }
+
+ // Create and save project config
+ err = projectOptions.WriteProjectConfig()
+ if err != nil {
+ return err
+ }
+
+ err = ph.templates.InstallTemplate(projectPath, projectOptions)
+ if err != nil {
+ return err
+ }
+
+ // // If we are on windows, dump a windows_resource.json
+ // if runtime.GOOS == "windows" {
+ // ph.GenerateWindowsResourceConfig(projectOptions)
+ // }
+
+ return nil
+}
+
+// // GenerateWindowsResourceConfig generates the default windows resource file
+// func (ph *ProjectHelper) GenerateWindowsResourceConfig(po *ProjectOptions) {
+
+// fmt.Println(buffer.String())
+
+// // vi.Build()
+// // vi.Walk()
+// // err := vi.WriteSyso(outPath, runtime.GOARCH)
+// }
+
+// LoadProjectConfig loads the project config from the given directory
+func (ph *ProjectHelper) LoadProjectConfig(dir string) (*ProjectOptions, error) {
+ po := ph.NewProjectOptions()
+ err := po.LoadConfig(dir)
+ return po, err
+}
+
+// NewProjectOptions creates a new default set of project options
+func (ph *ProjectHelper) NewProjectOptions() *ProjectOptions {
+ result := ProjectOptions{
+ Name: "",
+ Description: "Enter your project description",
+ Version: "0.1.0",
+ BinaryName: "",
+ system: ph.system,
+ log: ph.log,
+ templates: ph.templates,
+ Author: &author{},
+ }
+
+ // Populate system config
+ config, err := ph.system.LoadConfig()
+ if err == nil {
+ result.Author.Name = config.Name
+ result.Author.Email = config.Email
+ }
+
+ return &result
+}
+
+// ProjectOptions holds all the options available for a project
+type ProjectOptions struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Author *author `json:"author,omitempty"`
+ Version string `json:"version"`
+ OutputDirectory string `json:"-"`
+ UseDefaults bool `json:"-"`
+ Template string `json:"-"`
+ BinaryName string `json:"binaryname"`
+ FrontEnd *frontend `json:"frontend,omitempty"`
+ Tags string `json:"tags"`
+ NPMProjectName string `json:"-"`
+ system *SystemHelper
+ log *Logger
+ templates *TemplateHelper
+ selectedTemplate *TemplateDetails
+ WailsVersion string
+ typescriptDefsFilename string
+ Verbose bool `json:"-"`
+ CrossCompile bool
+ Platform string
+ Architecture string
+ LdFlags string
+ GoPath string
+ UseFirebug bool
+
+ // Supported platforms
+ Platforms []string `json:"platforms,omitempty"`
+}
+
+// PlatformSupported returns true if the template is supported
+// on the current platform
+func (po *ProjectOptions) PlatformSupported() bool {
+
+ // Default is all platforms supported
+ if len(po.Platforms) == 0 {
+ return true
+ }
+
+ // Check that the platform is in the list
+ platformsSupported := slicer.String(po.Platforms)
+ return platformsSupported.Contains(runtime.GOOS)
+}
+
+// Defaults sets the default project template
+func (po *ProjectOptions) Defaults() {
+ po.Template = "vuebasic"
+ po.WailsVersion = Version
+}
+
+// SetTypescriptDefsFilename indicates that we want to generate typescript bindings to the given file
+func (po *ProjectOptions) SetTypescriptDefsFilename(filename string) {
+ po.typescriptDefsFilename = filename
+}
+
+// GetNPMBinaryName returns the type of package manager used by the project
+func (po *ProjectOptions) GetNPMBinaryName() (PackageManager, error) {
+ if po.FrontEnd == nil {
+ return UNKNOWN, fmt.Errorf("No frontend specified in project options")
+ }
+
+ if strings.Index(po.FrontEnd.Install, "npm") > -1 {
+ return NPM, nil
+ }
+
+ if strings.Index(po.FrontEnd.Install, "yarn") > -1 {
+ return YARN, nil
+ }
+
+ return UNKNOWN, nil
+}
+
+// PromptForInputs asks the user to input project details
+func (po *ProjectOptions) PromptForInputs() error {
+
+ processProjectName(po)
+
+ processBinaryName(po)
+
+ err := processOutputDirectory(po)
+ if err != nil {
+ return err
+ }
+
+ // Process Templates
+ templateList := slicer.Interface()
+ options := slicer.String()
+ templateDetails, err := po.templates.GetTemplateDetails()
+ if err != nil {
+ return err
+ }
+
+ if po.Template != "" {
+ // Check template is valid if given
+ if templateDetails[po.Template] == nil {
+ keys := make([]string, 0, len(templateDetails))
+ for k := range templateDetails {
+ keys = append(keys, k)
+ }
+ return fmt.Errorf("invalid template name '%s'. Valid options: %s", po.Template, strings.Join(keys, ", "))
+ }
+ po.selectedTemplate = templateDetails[po.Template]
+ } else {
+
+ keys := make([]string, 0)
+ for k := range templateDetails {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ templateDetail := templateDetails[k]
+ templateList.Add(templateDetail)
+ if !templateDetail.Metadata.PlatformSupported() {
+ templateDetail.Metadata.Name = "* " + templateDetail.Metadata.Name
+ }
+ options.Add(fmt.Sprintf("%s - %s", templateDetail.Metadata.Name, templateDetail.Metadata.ShortDescription))
+ }
+
+ templateIndex := 0
+
+ if len(options.AsSlice()) > 1 {
+ templateIndex = PromptSelection("Please select a template (* means unsupported on current platform)", options.AsSlice(), 0)
+ }
+
+ if len(templateList.AsSlice()) == 0 {
+ return fmt.Errorf("aborting: no templates found")
+ }
+
+ // After selection do this....
+ po.selectedTemplate = templateList.AsSlice()[templateIndex].(*TemplateDetails)
+ }
+
+ po.selectedTemplate.Metadata.Name = strings.TrimPrefix(po.selectedTemplate.Metadata.Name, "* ")
+ if !po.selectedTemplate.Metadata.PlatformSupported() {
+ println("WARNING: This template is unsupported on this platform!")
+ }
+ fmt.Println("Template: " + po.selectedTemplate.Metadata.Name)
+
+ // Setup NPM Project name
+ po.NPMProjectName = strings.ToLower(strings.Replace(po.Name, " ", "_", -1))
+
+ // Fix template name
+ po.Template = strings.Split(po.selectedTemplate.Path, string(os.PathSeparator))[0]
+
+ // // Populate template details
+ templateMetadata := po.selectedTemplate.Metadata
+
+ err = processTemplateMetadata(templateMetadata, po)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WriteProjectConfig writes the project configuration into
+// the project directory
+func (po *ProjectOptions) WriteProjectConfig() error {
+ targetDir, err := filepath.Abs(po.OutputDirectory)
+ if err != nil {
+ return err
+ }
+
+ targetFile := filepath.Join(targetDir, "project.json")
+ filedata, err := json.MarshalIndent(po, "", " ")
+ if err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile(targetFile, filedata, 0600)
+}
+
+// LoadConfig loads the project configuration file from the
+// given directory
+func (po *ProjectOptions) LoadConfig(projectDir string) error {
+ targetFile := filepath.Join(projectDir, "project.json")
+ rawBytes, err := ioutil.ReadFile(targetFile)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(rawBytes, po)
+}
+
+func computeBinaryName(projectName string) string {
+ if projectName == "" {
+ return ""
+ }
+ var binaryNameComputed = strings.ToLower(projectName)
+ binaryNameComputed = strings.Replace(binaryNameComputed, " ", "-", -1)
+ binaryNameComputed = strings.Replace(binaryNameComputed, string(filepath.Separator), "-", -1)
+ binaryNameComputed = strings.Replace(binaryNameComputed, ":", "-", -1)
+ return binaryNameComputed
+}
+
+func processOutputDirectory(po *ProjectOptions) error {
+ // po.OutputDirectory
+ if po.OutputDirectory == "" {
+ po.OutputDirectory = PromptRequired("Project directory name", computeBinaryName(po.Name))
+ }
+ projectPath, err := filepath.Abs(po.OutputDirectory)
+ if err != nil {
+ return err
+ }
+
+ if NewFSHelper().DirExists(projectPath) {
+ return fmt.Errorf("directory '%s' already exists", projectPath)
+ }
+
+ fmt.Println("Project Directory: " + po.OutputDirectory)
+ return nil
+}
+
+func processProjectName(po *ProjectOptions) {
+ if po.Name == "" {
+ po.Name = Prompt("The name of the project", "My Project")
+ }
+ fmt.Println("Project Name: " + po.Name)
+}
+
+func processBinaryName(po *ProjectOptions) {
+ if po.BinaryName == "" {
+ var binaryNameComputed = computeBinaryName(po.Name)
+ po.BinaryName = Prompt("The output binary name", binaryNameComputed)
+ }
+ fmt.Println("Output binary Name: " + po.BinaryName)
+}
+
+func processTemplateMetadata(templateMetadata *TemplateMetadata, po *ProjectOptions) error {
+ if templateMetadata.FrontendDir != "" {
+ po.FrontEnd = &frontend{}
+ po.FrontEnd.Dir = templateMetadata.FrontendDir
+ }
+ if templateMetadata.Install != "" {
+ if po.FrontEnd == nil {
+ return fmt.Errorf("install set in template metadata but not frontenddir")
+ }
+ po.FrontEnd.Install = templateMetadata.Install
+ }
+ if templateMetadata.Build != "" {
+ if po.FrontEnd == nil {
+ return fmt.Errorf("build set in template metadata but not frontenddir")
+ }
+ po.FrontEnd.Build = templateMetadata.Build
+ }
+
+ if templateMetadata.Bridge != "" {
+ if po.FrontEnd == nil {
+ return fmt.Errorf("bridge set in template metadata but not frontenddir")
+ }
+ po.FrontEnd.Bridge = templateMetadata.Bridge
+ }
+
+ if templateMetadata.Serve != "" {
+ if po.FrontEnd == nil {
+ return fmt.Errorf("serve set in template metadata but not frontenddir")
+ }
+ po.FrontEnd.Serve = templateMetadata.Serve
+ }
+
+ // Save platforms
+ po.Platforms = templateMetadata.Platforms
+
+ return nil
+}
diff --git a/cmd/prompt.go b/cmd/prompt.go
new file mode 100644
index 000000000..dac324fe4
--- /dev/null
+++ b/cmd/prompt.go
@@ -0,0 +1,80 @@
+package cmd
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// Prompt asks the user for a value
+func Prompt(question string, defaultValue ...string) string {
+ var answer string
+
+ if len(defaultValue) > 0 {
+ answer = defaultValue[0]
+ question = fmt.Sprintf("%s (%s)", question, answer)
+ }
+ fmt.Printf(question + ": ")
+ reader := bufio.NewReader(os.Stdin)
+ input, _ := reader.ReadString('\n')
+ input = strings.TrimSpace(input)
+
+ if input != "" {
+ answer = input
+ }
+
+ return answer
+}
+
+// PromptRequired calls Prompt repeatedly until a value is given
+func PromptRequired(question string, defaultValue ...string) string {
+ for {
+ result := Prompt(question, defaultValue...)
+ if result != "" {
+ return result
+ }
+ }
+}
+
+// PromptSelection asks the user to choose an option
+func PromptSelection(question string, options []string, optionalDefaultValue ...int) int {
+
+ defaultValue := -1
+ message := "Please choose an option"
+ fmt.Println(question + ":")
+
+ if len(optionalDefaultValue) > 0 {
+ defaultValue = optionalDefaultValue[0] + 1
+ message = fmt.Sprintf("%s [%d]", message, defaultValue)
+ }
+
+ for index, option := range options {
+ fmt.Printf(" %d: %s\n", index+1, option)
+ }
+
+ selectedValue := -1
+
+ for {
+ choice := Prompt(message)
+ if choice == "" && defaultValue > -1 {
+ selectedValue = defaultValue - 1
+ break
+ }
+
+ // index
+ number, err := strconv.Atoi(choice)
+ if err == nil {
+ if number > 0 && number <= len(options) {
+ selectedValue = number - 1
+ break
+ } else {
+ continue
+ }
+ }
+
+ }
+
+ return selectedValue
+}
diff --git a/v2/internal/github/semver.go b/cmd/semver.go
similarity index 97%
rename from v2/internal/github/semver.go
rename to cmd/semver.go
index 1cf5907fa..ab9405292 100644
--- a/v2/internal/github/semver.go
+++ b/cmd/semver.go
@@ -1,4 +1,4 @@
-package github
+package cmd
import (
"fmt"
@@ -24,8 +24,8 @@ func NewSemanticVersion(version string) (*SemanticVersion, error) {
// IsRelease returns true if it's a release version
func (s *SemanticVersion) IsRelease() bool {
- // Limit to v2
- if s.Version.Major() != 2 {
+ // Limit to v1
+ if s.Version.Major() != 1 {
return false
}
return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0
@@ -34,7 +34,7 @@ func (s *SemanticVersion) IsRelease() bool {
// IsPreRelease returns true if it's a prerelease version
func (s *SemanticVersion) IsPreRelease() bool {
// Limit to v1
- if s.Version.Major() != 2 {
+ if s.Version.Major() != 1 {
return false
}
return len(s.Version.Prerelease()) > 0
diff --git a/cmd/semver_test.go b/cmd/semver_test.go
new file mode 100644
index 000000000..54261b6f7
--- /dev/null
+++ b/cmd/semver_test.go
@@ -0,0 +1,65 @@
+package cmd
+
+import (
+ "testing"
+)
+
+func TestSemanticVersion_IsPreRelease(t *testing.T) {
+ tests := []struct {
+ name string
+ version string
+ want bool
+ }{
+ {"v1.6.7-pre0", "v1.6.7-pre0", true},
+ {"v2.6.7+pre0", "v2.6.7+pre0", false},
+ {"v2.6.7", "v2.6.7", false},
+ {"v2.0.0+alpha.1", "v2.0.0+alpha.1", false},
+ {"v2.0.0-alpha.1", "v2.0.0-alpha.1", false},
+ {"v1.6.7", "v1.6.7", false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ semanticversion, err := NewSemanticVersion(tt.version)
+ if err != nil {
+ t.Errorf("Invalid semantic version: %s", semanticversion)
+ return
+ }
+ s := &SemanticVersion{
+ Version: semanticversion.Version,
+ }
+ if got := s.IsPreRelease(); got != tt.want {
+ t.Errorf("IsPreRelease() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestSemanticVersion_IsRelease(t *testing.T) {
+ tests := []struct {
+ name string
+ version string
+ want bool
+ }{
+ {"v1.6.7", "v1.6.7", true},
+ {"v2.6.7-pre0", "v2.6.7-pre0", false},
+ {"v2.6.7", "v2.6.7", false},
+ {"v2.6.7+release", "v2.6.7+release", false},
+ {"v2.0.0-alpha.1", "v2.0.0-alpha.1", false},
+ {"v1.6.7-pre0", "v1.6.7-pre0", false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ semanticversion, err := NewSemanticVersion(tt.version)
+ if err != nil {
+ t.Errorf("Invalid semantic version: %s", semanticversion)
+ return
+ }
+ s := &SemanticVersion{
+ Version: semanticversion.Version,
+ }
+ if got := s.IsRelease(); got != tt.want {
+ t.Errorf("IsRelease() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/cmd/shell.go b/cmd/shell.go
new file mode 100644
index 000000000..53c227de0
--- /dev/null
+++ b/cmd/shell.go
@@ -0,0 +1,61 @@
+package cmd
+
+import (
+ "bytes"
+ "os"
+ "os/exec"
+)
+
+// ShellHelper helps with Shell commands
+type ShellHelper struct {
+ verbose bool
+}
+
+// NewShellHelper creates a new ShellHelper!
+func NewShellHelper() *ShellHelper {
+ return &ShellHelper{}
+}
+
+// SetVerbose sets the verbose flag
+func (sh *ShellHelper) SetVerbose() {
+ sh.verbose = true
+}
+
+// Run the given command
+func (sh *ShellHelper) Run(command string, vars ...string) (stdout, stderr string, err error) {
+ cmd := exec.Command(command, vars...)
+ cmd.Env = append(os.Environ(), "GO111MODULE=on")
+ if !sh.verbose {
+ var stdo, stde bytes.Buffer
+ cmd.Stdout = &stdo
+ cmd.Stderr = &stde
+ err = cmd.Run()
+ stdout = string(stdo.Bytes())
+ stderr = string(stde.Bytes())
+ } else {
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ }
+ return
+}
+
+// RunInDirectory runs the given command in the given directory
+func (sh *ShellHelper) RunInDirectory(dir string, command string, vars ...string) (stdout, stderr string, err error) {
+ cmd := exec.Command(command, vars...)
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "GO111MODULE=on")
+ if !sh.verbose {
+ var stdo, stde bytes.Buffer
+ cmd.Stdout = &stdo
+ cmd.Stderr = &stde
+ err = cmd.Run()
+ stdout = string(stdo.Bytes())
+ stderr = string(stde.Bytes())
+ } else {
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ }
+ return
+}
diff --git a/cmd/system.go b/cmd/system.go
new file mode 100644
index 000000000..ab3c9e5dd
--- /dev/null
+++ b/cmd/system.go
@@ -0,0 +1,309 @@
+package cmd
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "time"
+)
+
+// SystemHelper - Defines everything related to the system
+type SystemHelper struct {
+ log *Logger
+ fs *FSHelper
+ configFilename string
+ homeDir string
+ wailsSystemDir string
+ wailsSystemConfig string
+}
+
+// NewSystemHelper - Creates a new System Helper
+func NewSystemHelper() *SystemHelper {
+ result := &SystemHelper{
+ fs: NewFSHelper(),
+ log: NewLogger(),
+ configFilename: "wails.json",
+ }
+ result.setSystemDirs()
+ return result
+}
+
+// Internal
+// setSystemDirs calculates the system directories it is interested in
+func (s *SystemHelper) setSystemDirs() {
+ var err error
+ s.homeDir, err = os.UserHomeDir()
+ if err != nil {
+ log.Fatal("Cannot find home directory! Please file a bug report!")
+ }
+
+ // TODO: A better config system
+ s.wailsSystemDir = filepath.Join(s.homeDir, ".wails")
+ s.wailsSystemConfig = filepath.Join(s.wailsSystemDir, s.configFilename)
+}
+
+// ConfigFileExists - Returns true if it does!
+func (s *SystemHelper) ConfigFileExists() bool {
+ return s.fs.FileExists(s.wailsSystemConfig)
+}
+
+// SystemDirExists - Returns true if it does!
+func (s *SystemHelper) systemDirExists() bool {
+ return s.fs.DirExists(s.wailsSystemDir)
+}
+
+// LoadConfig attempts to load the Wails system config
+func (s *SystemHelper) LoadConfig() (*SystemConfig, error) {
+ return NewSystemConfig(s.wailsSystemConfig)
+}
+
+// ConfigFileIsValid checks if the config file is valid
+func (s *SystemHelper) ConfigFileIsValid() bool {
+ _, err := NewSystemConfig(s.wailsSystemConfig)
+ return err == nil
+}
+
+// GetAuthor returns a formatted string of the user's name and email
+func (s *SystemHelper) GetAuthor() (string, error) {
+ var config *SystemConfig
+ config, err := s.LoadConfig()
+ if err != nil {
+ return "", err
+ }
+
+ return fmt.Sprintf("%s <%s>", config.Name, config.Email), nil
+}
+
+// BackupConfig attempts to backup the system config file
+func (s *SystemHelper) BackupConfig() (string, error) {
+ now := strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
+ backupFilename := s.wailsSystemConfig + "." + now
+ err := s.fs.CopyFile(s.wailsSystemConfig, backupFilename)
+ if err != nil {
+ return "", err
+ }
+ return backupFilename, nil
+}
+
+func (s *SystemHelper) setup() error {
+
+ systemConfig := make(map[string]string)
+
+ // Try to load current values - ignore errors
+ config, _ := s.LoadConfig()
+
+ if config.Name != "" {
+ systemConfig["name"] = PromptRequired("What is your name", config.Name)
+ } else {
+ systemConfig["name"] = PromptRequired("What is your name")
+ }
+ if config.Email != "" {
+ systemConfig["email"] = PromptRequired("What is your email address", config.Email)
+ } else {
+ systemConfig["email"] = PromptRequired("What is your email address")
+ }
+
+ // Create the directory
+ err := s.fs.MkDirs(s.wailsSystemDir)
+ if err != nil {
+ return err
+ }
+
+ // Save
+ configData, err := json.Marshal(&systemConfig)
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(s.wailsSystemConfig, configData, 0755)
+ if err != nil {
+ return err
+ }
+ fmt.Println()
+ s.log.White("Wails config saved to: " + s.wailsSystemConfig)
+ s.log.White("Feel free to customise these settings.")
+ fmt.Println()
+
+ return nil
+}
+
+const introText = `
+Wails is a lightweight framework for creating web-like desktop apps in Go.
+I'll need to ask you a few questions so I can fill in your project templates and then I will try and see if you have the correct dependencies installed. If you don't have the right tools installed, I'll try and suggest how to install them.
+`
+
+// CheckInitialised checks if the system has been set up
+// and if not, runs setup
+func (s *SystemHelper) CheckInitialised() error {
+ if !s.systemDirExists() {
+ s.log.Yellow("System not initialised. Running setup.")
+ return s.setup()
+ }
+ return nil
+}
+
+// Initialise attempts to set up the Wails system.
+// An error is returns if there is a problem
+func (s *SystemHelper) Initialise() error {
+
+ // System dir doesn't exist
+ if !s.systemDirExists() {
+ s.log.Green("Welcome to Wails!")
+ s.log.Green(introText)
+ return s.setup()
+ }
+
+ // Config doesn't exist
+ if !s.ConfigFileExists() {
+ s.log.Green("Looks like the system config is missing.")
+ s.log.Green("To get you back on track, I'll need to ask you a few things...")
+ return s.setup()
+ }
+
+ // Config exists but isn't valid.
+ if !s.ConfigFileIsValid() {
+ s.log.Green("Looks like the system config got corrupted.")
+ backupFile, err := s.BackupConfig()
+ if err != nil {
+ s.log.Green("I tried to backup your config file but got this error: %s", err.Error())
+ } else {
+ s.log.Green("Just in case you needed it, I backed up your config file here: %s", backupFile)
+ }
+ s.log.Green("To get you back on track, I'll need to ask you a few things...")
+ return s.setup()
+ }
+
+ return s.setup()
+}
+
+// SystemConfig - Defines system wode configuration data
+type SystemConfig struct {
+ Name string `json:"name"`
+ Email string `json:"email"`
+}
+
+// NewSystemConfig - Creates a new SystemConfig helper object
+func NewSystemConfig(filename string) (*SystemConfig, error) {
+ result := &SystemConfig{}
+ err := result.load(filename)
+ return result, err
+}
+
+// Save - Saves the system config to the given filename
+func (sc *SystemConfig) Save(filename string) error {
+ // Convert config to JSON string
+ theJSON, err := json.MarshalIndent(sc, "", " ")
+ if err != nil {
+ return err
+ }
+
+ // Write it out to the config file
+ return ioutil.WriteFile(filename, theJSON, 0644)
+}
+
+func (sc *SystemConfig) load(filename string) error {
+ configData, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+ // Load and unmarshall!
+ err = json.Unmarshal(configData, &sc)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// CheckDependenciesSilent checks for dependencies but
+// only outputs if there's an error
+func CheckDependenciesSilent(logger *Logger) (bool, error) {
+ logger.SetErrorOnly(true)
+ result, err := CheckDependencies(logger)
+ logger.SetErrorOnly(false)
+ return result, err
+}
+
+// CheckDependencies will look for Wails dependencies on the system
+// Errors are reported in error and the bool return value is whether
+// the dependencies are all installed.
+func CheckDependencies(logger *Logger) (bool, error) {
+
+ switch runtime.GOOS {
+ case "darwin":
+ logger.Yellow("Detected Platform: OSX")
+ case "windows":
+ logger.Yellow("Detected Platform: Windows")
+ case "linux":
+ logger.Yellow("Detected Platform: Linux")
+ default:
+ return false, fmt.Errorf("Platform %s is currently not supported", runtime.GOOS)
+ }
+
+ logger.Yellow("Checking for prerequisites...")
+ // Check we have a cgo capable environment
+
+ requiredPrograms, err := GetRequiredPrograms()
+ if err != nil {
+ return false, nil
+ }
+ errors := false
+ programHelper := NewProgramHelper()
+ for _, program := range *requiredPrograms {
+ bin := programHelper.FindProgram(program.Name)
+ if bin == nil {
+ errors = true
+ logger.Error("Program '%s' not found. %s", program.Name, program.Help)
+ } else {
+ logger.Green("Program '%s' found: %s", program.Name, bin.Path)
+ }
+ }
+
+ // Linux has library deps
+ if runtime.GOOS == "linux" {
+ // Check library prerequisites
+ requiredLibraries, err := GetRequiredLibraries()
+ if err != nil {
+ return false, err
+ }
+
+ var libraryChecker CheckPkgInstalled
+ distroInfo := GetLinuxDistroInfo()
+
+ switch distroInfo.Distribution {
+ case Ubuntu, Debian, Zorin, Parrot, Linuxmint, Elementary, Kali, Neon, Deepin, Raspbian, PopOS:
+ libraryChecker = DpkgInstalled
+ case Arch, ArcoLinux, ArchLabs, Ctlos, Manjaro, ManjaroARM, EndeavourOS:
+ libraryChecker = PacmanInstalled
+ case CentOS, Fedora, Tumbleweed, Leap:
+ libraryChecker = RpmInstalled
+ case Gentoo:
+ libraryChecker = EqueryInstalled
+ case VoidLinux:
+ libraryChecker = XbpsInstalled
+ case Solus:
+ libraryChecker = EOpkgInstalled
+ default:
+ return false, RequestSupportForDistribution(distroInfo)
+ }
+
+ for _, library := range *requiredLibraries {
+ installed, err := libraryChecker(library.Name)
+ if err != nil {
+ return false, err
+ }
+ if !installed {
+ errors = true
+ logger.Error("Library '%s' not found. %s", library.Name, library.Help)
+ } else {
+ logger.Green("Library '%s' installed.", library.Name)
+ }
+ }
+ }
+ logger.White("")
+
+ return !errors, err
+}
diff --git a/cmd/templates.go b/cmd/templates.go
new file mode 100644
index 000000000..fe1c68d24
--- /dev/null
+++ b/cmd/templates.go
@@ -0,0 +1,270 @@
+package cmd
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "text/template"
+
+ "github.com/kennygrant/sanitize"
+ "github.com/leaanthony/slicer"
+)
+
+// TemplateMetadata holds all the metadata for a Wails template
+type TemplateMetadata struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ ShortDescription string `json:"shortdescription"`
+ Description string `json:"description"`
+ Install string `json:"install"`
+ Build string `json:"build"`
+ Author string `json:"author"`
+ Created string `json:"created"`
+ FrontendDir string `json:"frontenddir"`
+ Serve string `json:"serve"`
+ Bridge string `json:"bridge"`
+ WailsDir string `json:"wailsdir"`
+ TemplateDependencies []*TemplateDependency `json:"dependencies,omitempty"`
+
+ // List of platforms that this template is supported on.
+ // No value means all platforms. A platform name is the same string
+ // as `runtime.GOOS` will return, eg: "darwin". NOTE: This is
+ // case sensitive.
+ Platforms []string `json:"platforms,omitempty"`
+}
+
+// PlatformSupported returns true if this template supports the
+// currently running platform
+func (m *TemplateMetadata) PlatformSupported() bool {
+
+ // Default is all platforms supported
+ if len(m.Platforms) == 0 {
+ return true
+ }
+
+ // Check that the platform is in the list
+ platformsSupported := slicer.String(m.Platforms)
+ return platformsSupported.Contains(runtime.GOOS)
+}
+
+// TemplateDependency defines a binary dependency for the template
+// EG: ng for angular
+type TemplateDependency struct {
+ Bin string `json:"bin"`
+ Help string `json:"help"`
+}
+
+// TemplateDetails holds information about a specific template
+type TemplateDetails struct {
+ Name string
+ Path string
+ Metadata *TemplateMetadata
+ fs *FSHelper
+}
+
+// TemplateHelper is a utility object to help with processing templates
+type TemplateHelper struct {
+ templateDir *Dir
+ fs *FSHelper
+ metadataFilename string
+}
+
+// NewTemplateHelper creates a new template helper
+func NewTemplateHelper() *TemplateHelper {
+
+ templateDir, err := fs.LocalDir("./templates")
+ if err != nil {
+ log.Fatal("Unable to find the template directory. Please reinstall Wails.")
+ }
+
+ return &TemplateHelper{
+ templateDir: templateDir,
+ metadataFilename: "template.json",
+ }
+}
+
+// IsValidTemplate returns true if the given template name resides on disk
+func (t *TemplateHelper) IsValidTemplate(templateName string) bool {
+ pathToTemplate := filepath.Join(t.templateDir.fullPath, templateName)
+ return t.fs.DirExists(pathToTemplate)
+}
+
+// SanitizeFilename sanitizes the given string to make a valid filename
+func (t *TemplateHelper) SanitizeFilename(name string) string {
+ return sanitize.Name(name)
+}
+
+// CreateNewTemplate creates a new template based on the given directory name and string
+func (t *TemplateHelper) CreateNewTemplate(dirname string, details *TemplateMetadata) (string, error) {
+
+ // Check if this template has already been created
+ if t.IsValidTemplate(dirname) {
+ return "", fmt.Errorf("cannot create template in directory '%s' - already exists", dirname)
+ }
+
+ targetDir := filepath.Join(t.templateDir.fullPath, dirname)
+ err := t.fs.MkDir(targetDir)
+ if err != nil {
+ return "", err
+ }
+ targetMetadata := filepath.Join(targetDir, t.metadataFilename)
+ err = t.fs.SaveAsJSON(details, targetMetadata)
+
+ return targetDir, err
+}
+
+// LoadMetadata loads the template's 'metadata.json' file
+func (t *TemplateHelper) LoadMetadata(dir string) (*TemplateMetadata, error) {
+ templateFile := filepath.Join(dir, t.metadataFilename)
+ result := &TemplateMetadata{}
+ if !t.fs.FileExists(templateFile) {
+ return nil, nil
+ }
+ rawJSON, err := ioutil.ReadFile(templateFile)
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(rawJSON, &result)
+ return result, err
+}
+
+// GetTemplateDetails returns a map of Template structs containing details
+// of the found templates
+func (t *TemplateHelper) GetTemplateDetails() (map[string]*TemplateDetails, error) {
+
+ // Get the subdirectory details
+ templateDirs, err := t.templateDir.GetSubdirs()
+ if err != nil {
+ return nil, err
+ }
+
+ result := make(map[string]*TemplateDetails)
+
+ for name, dir := range templateDirs {
+ result[name] = &TemplateDetails{
+ Path: dir,
+ }
+ metadata, err := t.LoadMetadata(dir)
+ if err != nil {
+ return nil, err
+ }
+
+ result[name].Metadata = metadata
+ if metadata.Name != "" {
+ result[name].Name = metadata.Name
+ } else {
+ // Ignore bad templates?
+ result[name] = nil
+ }
+ }
+
+ return result, nil
+}
+
+// GetTemplateFilenames returns all the filenames of the given template
+func (t *TemplateHelper) GetTemplateFilenames(template *TemplateDetails) (*slicer.StringSlicer, error) {
+
+ // Get the subdirectory details
+ templateDir, err := t.fs.Directory(template.Path)
+ if err != nil {
+ return nil, err
+ }
+ return templateDir.GetAllFilenames()
+}
+
+// InstallTemplate installs the template given in the project options to the
+// project path given
+func (t *TemplateHelper) InstallTemplate(projectPath string, projectOptions *ProjectOptions) error {
+
+ // Check dependencies before installing
+ dependencies := projectOptions.selectedTemplate.Metadata.TemplateDependencies
+ if dependencies != nil {
+ programHelper := NewProgramHelper()
+ logger := NewLogger()
+ errors := []string{}
+ for _, dep := range dependencies {
+ program := programHelper.FindProgram(dep.Bin)
+ if program == nil {
+ errors = append(errors, dep.Help)
+ }
+ }
+ if len(errors) > 0 {
+ mainError := "template dependencies not installed"
+ if len(errors) == 1 {
+ mainError = errors[0]
+ } else {
+ for _, error := range errors {
+ logger.Red(error)
+ }
+ }
+ return fmt.Errorf(mainError)
+ }
+ }
+
+ // Get template files
+ templateFilenames, err := t.GetTemplateFilenames(projectOptions.selectedTemplate)
+ if err != nil {
+ return err
+ }
+
+ templatePath := projectOptions.selectedTemplate.Path
+
+ // Save the version
+ projectOptions.WailsVersion = Version
+
+ templateJSONFilename := filepath.Join(templatePath, t.metadataFilename)
+
+ templateFiles := templateFilenames.Filter(func(filename string) bool {
+ filename = filepath.FromSlash(filename)
+ return strings.HasPrefix(filename, templatePath) && filename != templateJSONFilename
+ })
+
+ templateFiles.Each(func(templateFile string) {
+
+ // Setup filenames
+ relativeFilename := strings.TrimPrefix(templateFile, templatePath)[1:]
+ targetFilename, err := filepath.Abs(filepath.Join(projectOptions.OutputDirectory, relativeFilename))
+ if err != nil {
+ return
+ }
+ filedata, err := t.fs.LoadAsBytes(templateFile)
+ if err != nil {
+ return
+ }
+
+ // If file is a template, process it
+ if strings.HasSuffix(templateFile, ".template") {
+ templateData := string(filedata)
+ tmpl := template.New(templateFile)
+ tmpl.Parse(templateData)
+ var tpl bytes.Buffer
+ err = tmpl.Execute(&tpl, projectOptions)
+ if err != nil {
+ return
+ }
+
+ // Remove template suffix
+ targetFilename = strings.TrimSuffix(targetFilename, ".template")
+
+ // Set the filedata to the template result
+ filedata = tpl.Bytes()
+ }
+
+ // Normal file, just copy it
+ err = fs.CreateFile(targetFilename, filedata)
+ if err != nil {
+ return
+ }
+ })
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cmd/templates/angular-template/frontend/.editorconfig b/cmd/templates/angular-template/frontend/.editorconfig
new file mode 100644
index 000000000..e89330a61
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/.editorconfig
@@ -0,0 +1,13 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/cmd/templates/angular-template/frontend/.gitignore b/cmd/templates/angular-template/frontend/.gitignore
new file mode 100644
index 000000000..2d5d82ccd
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/.gitignore
@@ -0,0 +1,47 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+# Only exists if Bazel was run
+/bazel-out
+
+# dependencies
+/node_modules
+
+# profiling files
+chrome-profiler-events.json
+speed-measure-plugin.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
+.editorcinfig
diff --git a/cmd/templates/angular-template/frontend/README.md b/cmd/templates/angular-template/frontend/README.md
new file mode 100644
index 000000000..f5aa03f4c
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/README.md
@@ -0,0 +1,27 @@
+# MyApp
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.3.
+
+## Development server
+
+Run `npx ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `npx ng generate component component-name` to generate a new component. You can also use `npx ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `npx ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `npx ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `npx ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `npx ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/cmd/templates/angular-template/frontend/angular.json b/cmd/templates/angular-template/frontend/angular.json
new file mode 100644
index 000000000..73d12e71f
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/angular.json
@@ -0,0 +1,121 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "my-app": {
+ "projectType": "application",
+ "schematics": {},
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "ngx-build-plus:browser",
+ "options": {
+ "outputPath": "dist/my-app",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.app.json",
+ "aot": false,
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "2mb",
+ "maximumError": "5mb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "ngx-build-plus:dev-server",
+ "options": {
+ "browserTarget": "my-app:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "my-app:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "my-app:build"
+ }
+ },
+ "test": {
+ "builder": "ngx-build-plus:karma",
+ "options": {
+ "main": "src/test.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.spec.json",
+ "karmaConfig": "karma.conf.js",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "tsconfig.app.json",
+ "tsconfig.spec.json",
+ "e2e/tsconfig.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ },
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "e2e/protractor.conf.js",
+ "devServerTarget": "my-app:serve"
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "my-app:serve:production"
+ }
+ }
+ }
+ }
+ }
+ },
+ "defaultProject": "my-app"
+}
\ No newline at end of file
diff --git a/cmd/templates/angular-template/frontend/browserslist b/cmd/templates/angular-template/frontend/browserslist
new file mode 100644
index 000000000..3cb56d100
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/browserslist
@@ -0,0 +1,12 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# You can see what browsers were selected by your queries by running:
+# npx browserslist
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+IE 9-11 # For IE 9-11 support, remove 'not'.
diff --git a/cmd/templates/angular-template/frontend/e2e/protractor.conf.js b/cmd/templates/angular-template/frontend/e2e/protractor.conf.js
new file mode 100644
index 000000000..73e4e6806
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/e2e/protractor.conf.js
@@ -0,0 +1,32 @@
+// @ts-check
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+/**
+ * @type { import("protractor").Config }
+ */
+exports.config = {
+ allScriptsTimeout: 11000,
+ specs: [
+ './src/**/*.e2e-spec.ts'
+ ],
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+ directConnect: true,
+ baseUrl: 'http://localhost:4200/',
+ framework: 'jasmine',
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000,
+ print: function() {}
+ },
+ onPrepare() {
+ require('ts-node').register({
+ project: require('path').join(__dirname, './tsconfig.json')
+ });
+ jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+ }
+};
\ No newline at end of file
diff --git a/cmd/templates/angular-template/frontend/e2e/src/app.e2e-spec.ts b/cmd/templates/angular-template/frontend/e2e/src/app.e2e-spec.ts
new file mode 100644
index 000000000..3b79f7c47
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/e2e/src/app.e2e-spec.ts
@@ -0,0 +1,23 @@
+import { AppPage } from './app.po';
+import { browser, logging } from 'protractor';
+
+describe('workspace-project App', () => {
+ let page: AppPage;
+
+ beforeEach(() => {
+ page = new AppPage();
+ });
+
+ it('should display welcome message', () => {
+ page.navigateTo();
+ expect(page.getTitleText()).toEqual('Welcome to my-app!');
+ });
+
+ afterEach(async () => {
+ // Assert that there are no errors emitted from the browser
+ const logs = await browser.manage().logs().get(logging.Type.BROWSER);
+ expect(logs).not.toContain(jasmine.objectContaining({
+ level: logging.Level.SEVERE,
+ } as logging.Entry));
+ });
+});
diff --git a/cmd/templates/angular-template/frontend/e2e/src/app.po.ts b/cmd/templates/angular-template/frontend/e2e/src/app.po.ts
new file mode 100644
index 000000000..5776aa9eb
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/e2e/src/app.po.ts
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+ navigateTo() {
+ return browser.get(browser.baseUrl) as Promise;
+ }
+
+ getTitleText() {
+ return element(by.css('app-root h1')).getText() as Promise;
+ }
+}
diff --git a/cmd/templates/angular-template/frontend/e2e/tsconfig.json b/cmd/templates/angular-template/frontend/e2e/tsconfig.json
new file mode 100644
index 000000000..39b800f78
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/e2e/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/e2e",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "jasminewd2",
+ "node"
+ ]
+ }
+}
diff --git a/cmd/templates/angular-template/frontend/karma.conf.js b/cmd/templates/angular-template/frontend/karma.conf.js
new file mode 100644
index 000000000..b0d5cbe01
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/karma.conf.js
@@ -0,0 +1,32 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, './coverage/my-app'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ restartOnFileChange: true
+ });
+};
diff --git a/cmd/templates/angular-template/frontend/package.json.template b/cmd/templates/angular-template/frontend/package.json.template
new file mode 100644
index 000000000..7b2ae8278
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/package.json.template
@@ -0,0 +1,52 @@
+{
+ "name": "my-app",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "npx ng",
+ "start": "npx ng serve --poll=2000 --host=0.0.0.0",
+ "build": "npx ng build --single-bundle true --output-hashing none --prod --bundle-styles false",
+ "test": "npx ng test",
+ "lint": "npx ng lint",
+ "e2e": "npx ng e2e"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^8.0.2",
+ "@angular/cdk": "^8.0.1",
+ "@angular/common": "~8.0.1",
+ "@angular/compiler": "~8.0.1",
+ "@angular/core": "~8.0.1",
+ "@angular/forms": "~8.0.1",
+ "@angular/material": "^8.0.1",
+ "@angular/platform-browser": "~8.0.1",
+ "@angular/platform-browser-dynamic": "~8.0.1",
+ "@angular/router": "~8.0.1",
+ "@wailsapp/runtime": "^1.0.0",
+ "core-js": "^3.4.4",
+ "ngx-build-plus": "^8.0.3",
+ "rxjs": "~6.4.0",
+ "tslib": "^1.9.0",
+ "zone.js": "~0.9.1"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "~0.800.0",
+ "@angular/cli": "~8.0.3",
+ "@angular/compiler-cli": "~8.0.1",
+ "@angular/language-service": "~8.0.1",
+ "@types/node": "~8.9.4",
+ "@types/jasmine": "~3.3.8",
+ "@types/jasminewd2": "~2.0.3",
+ "codelyzer": "^5.0.0",
+ "jasmine-core": "~3.4.0",
+ "jasmine-spec-reporter": "~4.2.1",
+ "karma": "~4.1.0",
+ "karma-chrome-launcher": "~2.2.0",
+ "karma-coverage-istanbul-reporter": "~2.0.1",
+ "karma-jasmine": "~2.0.1",
+ "karma-jasmine-html-reporter": "^1.4.0",
+ "protractor": "~5.4.0",
+ "ts-node": "~7.0.0",
+ "tslint": "~5.15.0",
+ "typescript": "~3.4.3"
+ }
+}
diff --git a/cmd/templates/angular-template/frontend/src/app/app-routing.module.ts b/cmd/templates/angular-template/frontend/src/app/app-routing.module.ts
new file mode 100644
index 000000000..249a14a5c
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/app/app-routing.module.ts
@@ -0,0 +1,13 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+const routes: Routes = [];
+
+@NgModule({
+ imports: [
+ RouterModule.forRoot(routes,{useHash:true})
+ ],
+ exports: [RouterModule]
+})
+
+export class AppRoutingModule { }
diff --git a/v2/pkg/templates/generate/assets/common/frontend/dist/gitkeep b/cmd/templates/angular-template/frontend/src/app/app.component.css
similarity index 100%
rename from v2/pkg/templates/generate/assets/common/frontend/dist/gitkeep
rename to cmd/templates/angular-template/frontend/src/app/app.component.css
diff --git a/cmd/templates/angular-template/frontend/src/app/app.component.html b/cmd/templates/angular-template/frontend/src/app/app.component.html
new file mode 100644
index 000000000..4a0437517
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/app/app.component.html
@@ -0,0 +1,14 @@
+
+
+
+ Welcome to {{ title }}!
+
+
+
+
+
Hello
+
{{clickMessage}}
+
+
+
diff --git a/cmd/templates/angular-template/frontend/src/app/app.component.spec.ts b/cmd/templates/angular-template/frontend/src/app/app.component.spec.ts
new file mode 100644
index 000000000..3fe58ce0f
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/app/app.component.spec.ts
@@ -0,0 +1,35 @@
+import { TestBed, async } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ RouterTestingModule
+ ],
+ declarations: [
+ AppComponent
+ ],
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title 'my-app'`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app.title).toEqual('my-app');
+ });
+
+ it('should render title in a h1 tag', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.debugElement.nativeElement;
+ expect(compiled.querySelector('h1').textContent).toContain('Welcome to my-app!');
+ });
+});
diff --git a/cmd/templates/angular-template/frontend/src/app/app.component.ts b/cmd/templates/angular-template/frontend/src/app/app.component.ts
new file mode 100644
index 000000000..e91b91686
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/app/app.component.ts
@@ -0,0 +1,19 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: '[id="app"]',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css']
+})
+export class AppComponent {
+ title = 'my-app';
+
+ clickMessage = '';
+
+ onClickMe() {
+ // @ts-ignore
+ window.backend.basic().then(result =>
+ this.clickMessage = result
+ );
+ }
+}
diff --git a/cmd/templates/angular-template/frontend/src/app/app.module.ts b/cmd/templates/angular-template/frontend/src/app/app.module.ts
new file mode 100644
index 000000000..4c082cefe
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/app/app.module.ts
@@ -0,0 +1,20 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+
+import { APP_BASE_HREF } from '@angular/common';
+
+@NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule
+ ],
+ providers: [{provide: APP_BASE_HREF, useValue : '/' }],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/v2/pkg/templates/generate/assets/preact/frontend/dist/gitkeep b/cmd/templates/angular-template/frontend/src/assets/.gitkeep
similarity index 100%
rename from v2/pkg/templates/generate/assets/preact/frontend/dist/gitkeep
rename to cmd/templates/angular-template/frontend/src/assets/.gitkeep
diff --git a/cmd/templates/angular-template/frontend/src/environments/environment.prod.ts b/cmd/templates/angular-template/frontend/src/environments/environment.prod.ts
new file mode 100644
index 000000000..3612073bc
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true
+};
diff --git a/cmd/templates/angular-template/frontend/src/environments/environment.ts b/cmd/templates/angular-template/frontend/src/environments/environment.ts
new file mode 100644
index 000000000..7b4f817ad
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/environments/environment.ts
@@ -0,0 +1,16 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/cmd/templates/angular-template/frontend/src/favicon.ico b/cmd/templates/angular-template/frontend/src/favicon.ico
new file mode 100644
index 000000000..8081c7cea
Binary files /dev/null and b/cmd/templates/angular-template/frontend/src/favicon.ico differ
diff --git a/cmd/templates/angular-template/frontend/src/index.html.template b/cmd/templates/angular-template/frontend/src/index.html.template
new file mode 100644
index 000000000..df56c4116
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/index.html.template
@@ -0,0 +1,14 @@
+
+
+
+
+my-app
+
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/angular-template/frontend/src/main.ts b/cmd/templates/angular-template/frontend/src/main.ts
new file mode 100644
index 000000000..49f44bfbd
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/main.ts
@@ -0,0 +1,19 @@
+import 'core-js/stable';
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+import 'zone.js'
+
+import * as Wails from '@wailsapp/runtime';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+Wails.Init(() => {
+ platformBrowserDynamic().bootstrapModule(AppModule)
+ .catch(err => console.error(err));
+});
diff --git a/cmd/templates/angular-template/frontend/src/polyfills.ts b/cmd/templates/angular-template/frontend/src/polyfills.ts
new file mode 100644
index 000000000..22a5df87d
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/polyfills.ts
@@ -0,0 +1,63 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ * (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+//import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/cmd/templates/angular-template/frontend/src/styles.css b/cmd/templates/angular-template/frontend/src/styles.css
new file mode 100644
index 000000000..4cf0ed8d1
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/styles.css
@@ -0,0 +1,24 @@
+/* You can add global styles to this file, and also import other style files */
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ background-color: #282c34;
+}
+
+p {
+ color: white
+}
+
+h1 {
+ color: white
+}
+
+button {
+ background-color: white;
+ color: black;
+}
diff --git a/cmd/templates/angular-template/frontend/src/test.ts b/cmd/templates/angular-template/frontend/src/test.ts
new file mode 100644
index 000000000..16317897b
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/src/test.ts
@@ -0,0 +1,20 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/cmd/templates/angular-template/frontend/tsconfig.app.json b/cmd/templates/angular-template/frontend/tsconfig.app.json
new file mode 100644
index 000000000..31f8397ac
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/tsconfig.app.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "include": [
+ "src/**/*.ts"
+ ],
+ "exclude": [
+ "src/test.ts",
+ "src/**/*.spec.ts"
+ ]
+}
diff --git a/cmd/templates/angular-template/frontend/tsconfig.json b/cmd/templates/angular-template/frontend/tsconfig.json
new file mode 100644
index 000000000..16195ad5f
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "downlevelIteration": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "target": "es5",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2018",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/cmd/templates/angular-template/frontend/tsconfig.spec.json b/cmd/templates/angular-template/frontend/tsconfig.spec.json
new file mode 100644
index 000000000..6400fde7d
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts",
+ "src/polyfills.ts"
+ ],
+ "include": [
+ "src/**/*.spec.ts",
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/cmd/templates/angular-template/frontend/tslint.json b/cmd/templates/angular-template/frontend/tslint.json
new file mode 100644
index 000000000..188bd78d3
--- /dev/null
+++ b/cmd/templates/angular-template/frontend/tslint.json
@@ -0,0 +1,92 @@
+{
+ "extends": "tslint:recommended",
+ "rules": {
+ "array-type": false,
+ "arrow-parens": false,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "component-class-suffix": true,
+ "contextual-lifecycle": true,
+ "directive-class-suffix": true,
+ "directive-selector": [
+ true,
+ "attribute",
+ "app",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "app",
+ "kebab-case"
+ ],
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "interface-name": false,
+ "max-classes-per-file": false,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-consecutive-blank-lines": false,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-empty": false,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-switch-case-fall-through": true,
+ "no-use-before-declare": true,
+ "no-var-requires": false,
+ "object-literal-key-quotes": [
+ true,
+ "as-needed"
+ ],
+ "object-literal-sort-keys": false,
+ "ordered-imports": false,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "trailing-comma": false,
+ "no-conflicting-lifecycle": true,
+ "no-host-metadata-property": true,
+ "no-input-rename": true,
+ "no-inputs-metadata-property": true,
+ "no-output-native": true,
+ "no-output-on-prefix": true,
+ "no-output-rename": true,
+ "no-outputs-metadata-property": true,
+ "template-banana-in-box": true,
+ "template-no-negated-async": true,
+ "use-lifecycle-interface": true,
+ "use-pipe-transform-interface": true
+ },
+ "rulesDirectory": [
+ "codelyzer"
+ ]
+}
\ No newline at end of file
diff --git a/cmd/templates/angular-template/go.mod.template b/cmd/templates/angular-template/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/angular-template/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/angular-template/main.go.template b/cmd/templates/angular-template/main.go.template
new file mode 100644
index 000000000..bcba7df03
--- /dev/null
+++ b/cmd/templates/angular-template/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "World!"
+}
+
+func main() {
+
+ js := mewn.String("./frontend/dist/my-app/main.js")
+ css := mewn.String("./frontend/dist/my-app/styles.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/angular-template/template.json b/cmd/templates/angular-template/template.json
new file mode 100644
index 000000000..89ac169e4
--- /dev/null
+++ b/cmd/templates/angular-template/template.json
@@ -0,0 +1,20 @@
+{
+ "name": "Angular",
+ "version": "1.0.0",
+ "shortdescription": "Angular 8 template (Requires node 10.8+)",
+ "description": "Angular projects w/ @angular/cli - Note: in order to reach the cli use npx like this: npx ng",
+ "dependencies": [
+ {
+ "bin": "npx",
+ "help": "This template requires 'npx'. Please install with 'npm install -g npx'"
+ }
+ ],
+ "install": "npm install",
+ "build": "npx ng build --single-bundle true --output-hashing none --prod --bundle-styles false",
+ "author": "bh90210 ",
+ "created": "2019-06-15 18:23:48.666414555 +0300 EEST m=+223.934866008",
+ "frontenddir": "frontend",
+ "serve": "npx ng serve --poll=2000",
+ "bridge": "src",
+ "wailsdir": ""
+}
\ No newline at end of file
diff --git a/cmd/templates/create-react-app/.jshint b/cmd/templates/create-react-app/.jshint
new file mode 100644
index 000000000..0557edf11
--- /dev/null
+++ b/cmd/templates/create-react-app/.jshint
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
\ No newline at end of file
diff --git a/cmd/templates/create-react-app/frontend/.gitignore b/cmd/templates/create-react-app/frontend/.gitignore
new file mode 100644
index 000000000..4d29575de
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/cmd/templates/create-react-app/frontend/README.md b/cmd/templates/create-react-app/frontend/README.md
new file mode 100644
index 000000000..9d9614c4f
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/README.md
@@ -0,0 +1,68 @@
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+
+The page will reload if you make edits.
+You will also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
+
+If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
+
+You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
+
+### Code Splitting
+
+This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
+
+### Analyzing the Bundle Size
+
+This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
+
+### Making a Progressive Web App
+
+This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
+
+### Advanced Configuration
+
+This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
+
+### Deployment
+
+This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
+
+### `npm run build` fails to minify
+
+This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
diff --git a/cmd/templates/create-react-app/frontend/package.json.template b/cmd/templates/create-react-app/frontend/package.json.template
new file mode 100644
index 000000000..8cd6ded3b
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/package.json.template
@@ -0,0 +1,35 @@
+{
+ "name": "{{.NPMProjectName}}",
+ "author": "{{.Author.Name}}<{{.Author.Email}}>",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "core-js": "^3.6.4",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "wails-react-scripts": "3.0.1-2",
+ "react-modal": "3.11.2",
+ "@wailsapp/runtime": "^1.0.10"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": "react-app"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/cmd/templates/create-react-app/frontend/public/favicon.ico b/cmd/templates/create-react-app/frontend/public/favicon.ico
new file mode 100644
index 000000000..bcd5dfd67
Binary files /dev/null and b/cmd/templates/create-react-app/frontend/public/favicon.ico differ
diff --git a/cmd/templates/create-react-app/frontend/public/index.html b/cmd/templates/create-react-app/frontend/public/index.html
new file mode 100644
index 000000000..4bf3c2570
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+ You need to enable JavaScript to run this app.
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/templates/create-react-app/frontend/public/logo192.png b/cmd/templates/create-react-app/frontend/public/logo192.png
new file mode 100644
index 000000000..c11e5883f
Binary files /dev/null and b/cmd/templates/create-react-app/frontend/public/logo192.png differ
diff --git a/cmd/templates/create-react-app/frontend/public/logo512.png b/cmd/templates/create-react-app/frontend/public/logo512.png
new file mode 100644
index 000000000..564d5c188
Binary files /dev/null and b/cmd/templates/create-react-app/frontend/public/logo512.png differ
diff --git a/cmd/templates/create-react-app/frontend/public/manifest.json b/cmd/templates/create-react-app/frontend/public/manifest.json
new file mode 100644
index 000000000..080d6c77a
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/cmd/templates/create-react-app/frontend/public/robots.txt b/cmd/templates/create-react-app/frontend/public/robots.txt
new file mode 100644
index 000000000..e9e57dc4d
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/cmd/templates/create-react-app/frontend/src/App.css b/cmd/templates/create-react-app/frontend/src/App.css
new file mode 100644
index 000000000..74b5e0534
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/App.css
@@ -0,0 +1,38 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/cmd/templates/create-react-app/frontend/src/App.js b/cmd/templates/create-react-app/frontend/src/App.js
new file mode 100644
index 000000000..3c6bc564c
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/App.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import logo from './logo.png';
+import './App.css';
+import HelloWorld from './components/HelloWorld';
+
+function App() {
+ return (
+
+ );
+}
+
+export default App;
diff --git a/cmd/templates/create-react-app/frontend/src/App.test.js b/cmd/templates/create-react-app/frontend/src/App.test.js
new file mode 100644
index 000000000..a754b201b
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/App.test.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ ReactDOM.render( , div);
+ ReactDOM.unmountComponentAtNode(div);
+});
diff --git a/cmd/templates/create-react-app/frontend/src/components/HelloWorld.js b/cmd/templates/create-react-app/frontend/src/components/HelloWorld.js
new file mode 100644
index 000000000..26be1aea1
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/components/HelloWorld.js
@@ -0,0 +1,35 @@
+import React, { useState } from 'react';
+import Modal from 'react-modal';
+
+function HelloWorld() {
+ const [showModal, setShowModal] = useState(false);
+ const [result, setResult] = useState(null);
+
+ const handleOpenModal = () => {
+ setShowModal(true);
+
+ window.backend.basic().then((result) => setResult(result));
+ };
+
+ const handleCloseModal = () => {
+ setShowModal(false);
+ };
+
+ return (
+
+
handleOpenModal()} type="button">
+ Hello
+
+
+ {result}
+ handleCloseModal()}>Close Modal
+
+
+ );
+}
+
+export default HelloWorld;
diff --git a/cmd/templates/create-react-app/frontend/src/index.css b/cmd/templates/create-react-app/frontend/src/index.css
new file mode 100644
index 000000000..4a1df4db7
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
diff --git a/cmd/templates/create-react-app/frontend/src/index.js b/cmd/templates/create-react-app/frontend/src/index.js
new file mode 100644
index 000000000..4e64f604e
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/index.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import 'core-js/stable';
+import './index.css';
+import App from './App';
+import * as serviceWorker from './serviceWorker';
+
+import * as Wails from '@wailsapp/runtime';
+
+Wails.Init(() => {
+ ReactDOM.render(
+
+
+ ,
+ document.getElementById("app")
+ );
+});
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/cmd/templates/create-react-app/frontend/src/logo.png b/cmd/templates/create-react-app/frontend/src/logo.png
new file mode 100644
index 000000000..31fc8249c
Binary files /dev/null and b/cmd/templates/create-react-app/frontend/src/logo.png differ
diff --git a/cmd/templates/create-react-app/frontend/src/serviceWorker.js b/cmd/templates/create-react-app/frontend/src/serviceWorker.js
new file mode 100644
index 000000000..f8c7e50c2
--- /dev/null
+++ b/cmd/templates/create-react-app/frontend/src/serviceWorker.js
@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit https://bit.ly/CRA-PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ 'New content is available and will be used when all ' +
+ 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (
+ response.status === 404 ||
+ (contentType != null && contentType.indexOf('javascript') === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/cmd/templates/create-react-app/go.mod.template b/cmd/templates/create-react-app/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/create-react-app/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/create-react-app/main.go.template b/cmd/templates/create-react-app/main.go.template
new file mode 100644
index 000000000..034c43466
--- /dev/null
+++ b/cmd/templates/create-react-app/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "World!"
+}
+
+func main() {
+
+ js := mewn.String("./frontend/build/static/js/main.js")
+ css := mewn.String("./frontend/build/static/css/main.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/create-react-app/template.json b/cmd/templates/create-react-app/template.json
new file mode 100755
index 000000000..57ef5d907
--- /dev/null
+++ b/cmd/templates/create-react-app/template.json
@@ -0,0 +1,14 @@
+{
+ "name": "React JS",
+ "version": "1.0.0",
+ "shortdescription": "Create React App v4 template",
+ "description": "Create React App v4 standard tooling",
+ "install": "npm install",
+ "build": "npm run build",
+ "author": "bh90210 ",
+ "created": "2019-06-07 18:23:48.666414555 +0300 EEST m=+223.934866008",
+ "frontenddir": "frontend",
+ "serve": "npm run start",
+ "bridge": "src",
+ "wailsdir": ""
+}
diff --git a/cmd/templates/svelte/frontend/.gitignore b/cmd/templates/svelte/frontend/.gitignore
new file mode 100644
index 000000000..da93220bc
--- /dev/null
+++ b/cmd/templates/svelte/frontend/.gitignore
@@ -0,0 +1,4 @@
+/node_modules/
+/public/build/
+
+.DS_Store
diff --git a/cmd/templates/svelte/frontend/README.md b/cmd/templates/svelte/frontend/README.md
new file mode 100644
index 000000000..360f27156
--- /dev/null
+++ b/cmd/templates/svelte/frontend/README.md
@@ -0,0 +1,90 @@
+*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
+
+---
+
+# svelte app
+
+This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
+
+To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
+
+```bash
+npx degit sveltejs/template svelte-app
+cd svelte-app
+```
+
+*Note that you will need to have [Node.js](https://nodejs.org) installed.*
+
+
+## Get started
+
+Install the dependencies...
+
+```bash
+cd svelte-app
+npm install
+```
+
+...then start [Rollup](https://rollupjs.org):
+
+```bash
+npm run dev
+```
+
+Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
+
+By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
+
+
+## Building and running in production mode
+
+To create an optimised version of the app:
+
+```bash
+npm run build
+```
+
+You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
+
+
+## Single-page app mode
+
+By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
+
+If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
+
+```js
+"start": "sirv public --single"
+```
+
+## Deploying to the web
+
+### With [Vercel](https://vercel.com)
+
+Install `vercel` if you haven't already:
+
+```bash
+npm install -g vercel
+```
+
+Then, from within your project folder:
+
+```bash
+cd public
+vercel deploy --name my-project
+```
+
+### With [surge](https://surge.sh/)
+
+Install `surge` if you haven't already:
+
+```bash
+npm install -g surge
+```
+
+Then, from within your project folder:
+
+```bash
+npm run build
+surge public my-project.surge.sh
+```
diff --git a/cmd/templates/svelte/frontend/package.json.template b/cmd/templates/svelte/frontend/package.json.template
new file mode 100644
index 000000000..167ed910a
--- /dev/null
+++ b/cmd/templates/svelte/frontend/package.json.template
@@ -0,0 +1,31 @@
+{
+ "name": "{{.NPMProjectName}}",
+ "author": "{{.Author.Name}}<{{.Author.Email}}>",
+ "scripts": {
+ "build": "rollup -c",
+ "dev": "rollup -c -w",
+ "start": "sirv public"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-transform-runtime": "^7.11.5",
+ "@babel/preset-env": "^7.11.5",
+ "@rollup/plugin-commonjs": "^14.0.0",
+ "@rollup/plugin-image": "^2.0.5",
+ "@rollup/plugin-node-resolve": "^8.0.0",
+ "core-js": "^3.6.5",
+ "rollup": "^2.3.4",
+ "rollup-plugin-babel": "^4.4.0",
+ "rollup-plugin-livereload": "^2.0.0",
+ "rollup-plugin-polyfill": "^3.0.0",
+ "rollup-plugin-svelte": "^6.0.0",
+ "rollup-plugin-terser": "^7.0.0",
+ "svelte": "^3.0.0"
+ },
+ "dependencies": {
+ "sirv-cli": "^1.0.0",
+ "@wailsapp/runtime": "^1.0.10",
+ "svelte-simple-modal": "^0.6.0"
+ }
+}
diff --git a/cmd/templates/svelte/frontend/public/favicon.png b/cmd/templates/svelte/frontend/public/favicon.png
new file mode 100644
index 000000000..7e6f5eb5a
Binary files /dev/null and b/cmd/templates/svelte/frontend/public/favicon.png differ
diff --git a/cmd/templates/svelte/frontend/public/index.html b/cmd/templates/svelte/frontend/public/index.html
new file mode 100644
index 000000000..a9c8fd65d
--- /dev/null
+++ b/cmd/templates/svelte/frontend/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ Svelte app
+
+
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/svelte/frontend/rollup.config.js b/cmd/templates/svelte/frontend/rollup.config.js
new file mode 100644
index 000000000..c6b831f24
--- /dev/null
+++ b/cmd/templates/svelte/frontend/rollup.config.js
@@ -0,0 +1,110 @@
+import svelte from 'rollup-plugin-svelte';
+import resolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import livereload from 'rollup-plugin-livereload';
+import { terser } from 'rollup-plugin-terser';
+import image from '@rollup/plugin-image';
+import babel from 'rollup-plugin-babel';
+import polyfill from 'rollup-plugin-polyfill';
+
+const production = !process.env.ROLLUP_WATCH;
+
+function serve() {
+ let server;
+
+ function toExit() {
+ if (server) server.kill(0);
+ }
+
+ return {
+ writeBundle() {
+ if (server) return;
+ server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
+ stdio: ['ignore', 'inherit', 'inherit'],
+ shell: true
+ });
+
+ process.on('SIGTERM', toExit);
+ process.on('exit', toExit);
+ }
+ };
+}
+
+export default {
+ input: 'src/main.js',
+ output: {
+ sourcemap: true,
+ format: 'iife',
+ name: 'app',
+ file: 'public/build/bundle.js'
+ },
+ plugins: [
+ image(),
+ svelte({
+ // enable run-time checks when not in production
+ dev: !production,
+ // we'll extract any component CSS out into
+ // a separate file - better for performance
+ css: css => {
+ css.write('bundle.css');
+ }
+ }),
+
+ // If you have external dependencies installed from
+ // npm, you'll most likely need these plugins. In
+ // some cases you'll need additional configuration -
+ // consult the documentation for details:
+ // https://github.com/rollup/plugins/tree/master/packages/commonjs
+ resolve({
+ browser: true,
+ dedupe: ['svelte', 'svelte/transition', 'svelte/internal']
+ }),
+ commonjs(),
+
+ // In dev mode, call `npm run start` once
+ // the bundle has been generated
+ !production && serve(),
+
+ // Watch the `public` directory and refresh the
+ // browser on changes when not in production
+ !production && livereload('public'),
+
+ // Credit: https://blog.az.sg/posts/svelte-and-ie11/
+ babel({
+ extensions: [ '.js', '.jsx', '.es6', '.es', '.mjs', '.svelte', '.html' ],
+ runtimeHelpers: true,
+ exclude: [ 'node_modules/@babel/**', 'node_modules/core-js/**' ],
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: '> 0.25%, not dead, IE 11',
+ modules: false,
+ spec: true,
+ useBuiltIns: 'usage',
+ forceAllTransforms: true,
+ corejs: 3,
+ },
+
+ ]
+ ],
+ plugins: [
+ '@babel/plugin-syntax-dynamic-import',
+ [
+ '@babel/plugin-transform-runtime',
+ {
+ useESModules: true
+ }
+ ]
+ ]
+ }),
+ polyfill(['@webcomponents/webcomponentsjs']),
+
+ // If we're building for production (npm run build
+ // instead of npm run dev), minify
+ production && terser()
+ ],
+ watch: {
+ clearScreen: false
+ }
+};
diff --git a/cmd/templates/svelte/frontend/src/App.svelte b/cmd/templates/svelte/frontend/src/App.svelte
new file mode 100644
index 000000000..dbc1c3ecc
--- /dev/null
+++ b/cmd/templates/svelte/frontend/src/App.svelte
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/templates/svelte/frontend/src/components/HelloWorld.svelte b/cmd/templates/svelte/frontend/src/components/HelloWorld.svelte
new file mode 100644
index 000000000..10948498d
--- /dev/null
+++ b/cmd/templates/svelte/frontend/src/components/HelloWorld.svelte
@@ -0,0 +1,18 @@
+
+
+
+ Hello
+
+
+
\ No newline at end of file
diff --git a/cmd/templates/svelte/frontend/src/components/ModalContent.svelte b/cmd/templates/svelte/frontend/src/components/ModalContent.svelte
new file mode 100644
index 000000000..37ce80fbb
--- /dev/null
+++ b/cmd/templates/svelte/frontend/src/components/ModalContent.svelte
@@ -0,0 +1,7 @@
+
+
+
+ {message}
+
\ No newline at end of file
diff --git a/cmd/templates/svelte/frontend/src/logo.png b/cmd/templates/svelte/frontend/src/logo.png
new file mode 100644
index 000000000..31fc8249c
Binary files /dev/null and b/cmd/templates/svelte/frontend/src/logo.png differ
diff --git a/cmd/templates/svelte/frontend/src/main.js b/cmd/templates/svelte/frontend/src/main.js
new file mode 100644
index 000000000..2646517ec
--- /dev/null
+++ b/cmd/templates/svelte/frontend/src/main.js
@@ -0,0 +1,13 @@
+import App from './App.svelte';
+
+import * as Wails from '@wailsapp/runtime';
+
+let app;
+
+Wails.Init(() => {
+ app = new App({
+ target: document.body,
+ });
+});
+
+export default app;
\ No newline at end of file
diff --git a/cmd/templates/svelte/go.mod.template b/cmd/templates/svelte/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/svelte/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/svelte/main.go.template b/cmd/templates/svelte/main.go.template
new file mode 100644
index 000000000..b5718c494
--- /dev/null
+++ b/cmd/templates/svelte/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "World!"
+}
+
+func main() {
+ js := mewn.String("./frontend/public/build/bundle.js")
+ css := mewn.String("./frontend/public/build/bundle.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/svelte/template.json b/cmd/templates/svelte/template.json
new file mode 100755
index 000000000..0b1c0ba39
--- /dev/null
+++ b/cmd/templates/svelte/template.json
@@ -0,0 +1,14 @@
+{
+ "name": "Svelte",
+ "version": "1.0.0",
+ "shortdescription": "A basic Svelte template",
+ "description": "A basic Svelte template",
+ "install": "npm install",
+ "build": "npm run build",
+ "author": "Tim Kipp ",
+ "created": "2020-09-06 13:06:10.469848 -0700 PDT m=+213.578828559",
+ "frontenddir": "frontend",
+ "serve": "npm run dev",
+ "bridge": "src",
+ "wailsdir": ""
+}
diff --git a/cmd/templates/vanilla/README.md b/cmd/templates/vanilla/README.md
new file mode 100644
index 000000000..4762fa49c
--- /dev/null
+++ b/cmd/templates/vanilla/README.md
@@ -0,0 +1,5 @@
+# README
+
+This is an experimental template for vanilla HTML/JS/CSS.
+
+The webpack rules may need to be adjusted to correctly embed all assets. Babel may also need to be setup correctly.
\ No newline at end of file
diff --git a/cmd/templates/vanilla/counter.go b/cmd/templates/vanilla/counter.go
new file mode 100644
index 000000000..ec93892cf
--- /dev/null
+++ b/cmd/templates/vanilla/counter.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "math/rand"
+
+ "github.com/wailsapp/wails"
+)
+
+// Counter is what we use for counting
+type Counter struct {
+ r *wails.Runtime
+ store *wails.Store
+}
+
+// WailsInit is called when the component is being initialised
+func (c *Counter) WailsInit(runtime *wails.Runtime) error {
+ c.r = runtime
+ c.store = runtime.Store.New("Counter", 0)
+ return nil
+}
+
+// RandomValue sets the counter to a random value
+func (c *Counter) RandomValue() {
+ c.store.Set(rand.Intn(1000))
+}
+
+// Increment will increment the counter
+func (c *Counter) Increment() {
+
+ increment := func(data int) int {
+ return data + 1
+ }
+
+ // Update the store using the increment function
+ c.store.Update(increment)
+}
+
+// Decrement will decrement the counter
+func (c *Counter) Decrement() {
+
+ decrement := func(data int) int {
+ return data - 1
+ }
+ // Update the store using the decrement function
+ c.store.Update(decrement)
+}
diff --git a/cmd/templates/vanilla/frontend/package.json.template b/cmd/templates/vanilla/frontend/package.json.template
new file mode 100644
index 000000000..174e5ac93
--- /dev/null
+++ b/cmd/templates/vanilla/frontend/package.json.template
@@ -0,0 +1,42 @@
+{
+ "name": "vanilla",
+ "author": "Lea",
+ "private": true,
+ "scripts": {
+ "serve": "webpack-dev-server",
+ "build": "npx webpack"
+ },
+ "dependencies": {
+ "core-js": "^3.6.4",
+ "regenerator-runtime": "^0.13.3",
+ "@wailsapp/runtime": "^1.0.10"
+ },
+ "devDependencies": {
+ "babel-eslint": "^10.1.0",
+ "copy-webpack-plugin": "^6.0.2",
+ "eslint": "^6.8.0",
+ "eventsource-polyfill": "^0.9.6",
+ "webpack": "^4.43.0",
+ "webpack-cli": "^3.3.11",
+ "webpack-dev-server": "^3.11.0",
+ "webpack-hot-middleware": "^2.25.0"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "eslint:recommended"
+ ],
+ "rules": {},
+ "parserOptions": {
+ "parser": "babel-eslint"
+ }
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/cmd/templates/vanilla/frontend/src/index.html b/cmd/templates/vanilla/frontend/src/index.html
new file mode 100644
index 000000000..c7eb55539
--- /dev/null
+++ b/cmd/templates/vanilla/frontend/src/index.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/templates/vanilla/frontend/src/main.css b/cmd/templates/vanilla/frontend/src/main.css
new file mode 100644
index 000000000..cbfc4313a
--- /dev/null
+++ b/cmd/templates/vanilla/frontend/src/main.css
@@ -0,0 +1,45 @@
+
+html,
+body {
+ background-color: white;
+ color: black;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+}
+
+input {
+ background-color: rgb(254,254,254);
+ color: black;
+}
+
+.container {
+ display: block;
+ width:100%;
+ text-align: center;
+ margin-top: 1rem;
+ font-size: 2rem;
+}
+
+button {
+ font-size: 1rem;
+ background-color: white;
+ color: black;
+}
+
+.result {
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 2rem;
+}
+
+.logo {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ width: 50%;
+ height: 50%;
+ background-image: url(data:image/png;base64,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);
+ background-size: cover;
+ background-position: 50% center;
+}
diff --git a/cmd/templates/vanilla/frontend/src/main.js b/cmd/templates/vanilla/frontend/src/main.js
new file mode 100644
index 000000000..609e55e43
--- /dev/null
+++ b/cmd/templates/vanilla/frontend/src/main.js
@@ -0,0 +1,47 @@
+import 'core-js/stable';
+const runtime = require('@wailsapp/runtime');
+
+// Main entry point
+function start() {
+
+ var mystore = runtime.Store.New('Counter');
+
+ // Ensure the default app div is 100% wide/high
+ var app = document.getElementById('app');
+ app.style.width = '100%';
+ app.style.height = '100%';
+
+ // Inject html
+ app.innerHTML = `
+
+
+
+ Increment Counter
+
+
+ Decrement Counter
+
+
+ Counter:
+
+
+ Set Counter Value
+ Set to Random Value
+
+ `;
+
+ // Connect counter value button to Go method
+ document.getElementById('setvalue').onclick = function() {
+ let newValue = parseInt(document.getElementById('newCounter').value,10);
+ mystore.set(newValue);
+ };
+
+ mystore.subscribe( function(state) {
+ document.getElementById('counter').innerText = state;
+ });
+
+ mystore.set(0);
+};
+
+// We provide our entrypoint as a callback for runtime.Init
+runtime.Init(start);
\ No newline at end of file
diff --git a/cmd/templates/vanilla/frontend/webpack.config.js b/cmd/templates/vanilla/frontend/webpack.config.js
new file mode 100644
index 000000000..c5490acce
--- /dev/null
+++ b/cmd/templates/vanilla/frontend/webpack.config.js
@@ -0,0 +1,56 @@
+const path = require('path');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+
+let imageSizeLimit = 9007199254740991; // Number.MAX_SAFE_INTEGER
+let sourceDir = path.resolve(__dirname, 'src');
+let buildDir = path.resolve(__dirname, 'build');
+
+module.exports = {
+ entry: {
+ index: path.resolve(sourceDir, 'main.js')
+ },
+ output: {
+ path: buildDir,
+ filename: 'main.js'
+ },
+ optimization: {
+ splitChunks: false
+ },
+ devServer: {
+ disableHostCheck: true,
+ contentBase: path.join(__dirname, 'src'),
+ compress: true,
+ open: true,
+ port: 8090
+ },
+ mode: 'production',
+ module: {
+ rules: [
+ {
+ test: /\.(png|gif|jpg|woff2?|eot|ttf|otf|svg)(\?.*)?$/i,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: imageSizeLimit
+ }
+ }
+ ],
+ }
+ ]
+ },
+ plugins: [
+ new CopyWebpackPlugin({
+ patterns: [
+ {
+ from: path.resolve(sourceDir, 'main.css'),
+ to: path.resolve(buildDir, 'main.css')
+ },
+ {
+ from: path.resolve(sourceDir, 'index.html'),
+ to: path.resolve(buildDir, 'index.html')
+ },
+ ]
+ })
+ ]
+};
diff --git a/cmd/templates/vanilla/go.mod.template b/cmd/templates/vanilla/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/vanilla/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/vanilla/main.go.template b/cmd/templates/vanilla/main.go.template
new file mode 100644
index 000000000..b1a0e04bd
--- /dev/null
+++ b/cmd/templates/vanilla/main.go.template
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func main() {
+
+ js := mewn.String("./frontend/build/main.js")
+ css := mewn.String("./frontend/build/main.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(&Counter{})
+ app.Run()
+}
diff --git a/cmd/templates/vanilla/template.json b/cmd/templates/vanilla/template.json
new file mode 100644
index 000000000..2bad445ef
--- /dev/null
+++ b/cmd/templates/vanilla/template.json
@@ -0,0 +1,12 @@
+{
+ "name": "Vanilla",
+ "shortdescription": "A Vanilla HTML/JS template",
+ "description": "A basic template using plain html/js and bundled using Webpack 4",
+ "author": "Lea Anthony",
+ "created": "2020-06-14",
+ "frontenddir": "frontend",
+ "install": "npm install",
+ "build": "npm run build",
+ "serve": "npm run serve",
+ "bridge": "src"
+}
diff --git a/cmd/templates/vue3-full/frontend/.browserslistrc b/cmd/templates/vue3-full/frontend/.browserslistrc
new file mode 100644
index 000000000..214388fe4
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/.browserslistrc
@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead
diff --git a/cmd/templates/vue3-full/frontend/.eslintrc.js b/cmd/templates/vue3-full/frontend/.eslintrc.js
new file mode 100644
index 000000000..bf594a121
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/.eslintrc.js
@@ -0,0 +1,29 @@
+module.exports = {
+ root: true,
+ env: {
+ node: true
+ },
+ 'extends': [
+ 'plugin:vue/vue3-essential',
+ 'eslint:recommended',
+ '@vue/typescript/recommended'
+ ],
+ parserOptions: {
+ ecmaVersion: 2020
+ },
+ rules: {
+ 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
+ },
+ overrides: [
+ {
+ files: [
+ '**/__tests__/*.{j,t}s?(x)',
+ '**/tests/unit/**/*.spec.{j,t}s?(x)'
+ ],
+ env: {
+ mocha: true
+ }
+ }
+ ]
+}
diff --git a/cmd/templates/vue3-full/frontend/.gitignore b/cmd/templates/vue3-full/frontend/.gitignore
new file mode 100644
index 000000000..185e66319
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/.gitignore
@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
diff --git a/cmd/templates/vue3-full/frontend/README.md b/cmd/templates/vue3-full/frontend/README.md
new file mode 100644
index 000000000..6953f66d0
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/README.md
@@ -0,0 +1,35 @@
+# vue basic
+
+## Project setup
+
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+
+```
+npm run build
+```
+
+### Run your tests
+
+```
+npm run test
+```
+
+### Lints and fixes files
+
+```
+npm run lint
+```
+
+### Customize configuration
+
+See [Configuration Reference](https://cli.vuejs.org/config/).
diff --git a/cmd/templates/vue3-full/frontend/package.json.template b/cmd/templates/vue3-full/frontend/package.json.template
new file mode 100644
index 000000000..dc7eef41e
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/package.json.template
@@ -0,0 +1,37 @@
+{
+ "name": "{{.NPMProjectName}}",
+ "author": "{{.Author.Name}}<{{.Author.Email}}>",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "test:unit": "vue-cli-service test:unit",
+ "lint": "vue-cli-service lint"
+ },
+ "dependencies": {
+ "vue": "^3.0.0-0",
+ "vue-router": "^4.0.0-0",
+ "regenerator-runtime": "^0.13.7",
+ "@wailsapp/runtime": "^1.1.1"
+ },
+ "devDependencies": {
+ "@types/chai": "^4.2.12",
+ "@types/mocha": "^8.0.3",
+ "@typescript-eslint/eslint-plugin": "^4.3.0",
+ "@typescript-eslint/parser": "^4.3.0",
+ "@vue/cli-plugin-eslint": "~4.5.9",
+ "@vue/cli-plugin-router": "~4.5.9",
+ "@vue/cli-plugin-typescript": "~4.5.9",
+ "@vue/cli-plugin-unit-mocha": "~4.5.9",
+ "@vue/cli-service": "~4.5.9",
+ "@vue/compiler-sfc": "^3.0.0",
+ "@vue/eslint-config-typescript": "^7.0.0",
+ "@vue/test-utils": "^2.0.0-0",
+ "chai": "^4.2.0",
+ "eslint": "<7.0.0",
+ "eslint-plugin-vue": "^7.0.0",
+ "node-sass": "^4.14.1",
+ "sass-loader": "^10.0.2",
+ "typescript": "~4.0.3"
+ }
+}
diff --git a/cmd/templates/vue3-full/frontend/src/App.vue b/cmd/templates/vue3-full/frontend/src/App.vue
new file mode 100644
index 000000000..6939bbb9b
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/App.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/cmd/templates/vue3-full/frontend/src/assets/appicon.png b/cmd/templates/vue3-full/frontend/src/assets/appicon.png
new file mode 100644
index 000000000..9f22be34b
Binary files /dev/null and b/cmd/templates/vue3-full/frontend/src/assets/appicon.png differ
diff --git a/cmd/templates/vue3-full/frontend/src/components/HelloWorld.vue b/cmd/templates/vue3-full/frontend/src/components/HelloWorld.vue
new file mode 100644
index 000000000..92d381cd8
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/components/HelloWorld.vue
@@ -0,0 +1,34 @@
+
+
+
{{ msg }}
+
+
+
+
+
+
+
diff --git a/cmd/templates/vue3-full/frontend/src/main.ts b/cmd/templates/vue3-full/frontend/src/main.ts
new file mode 100644
index 000000000..0d2c5a90b
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/main.ts
@@ -0,0 +1,8 @@
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from './router';
+import * as Wails from '@wailsapp/runtime';
+
+Wails.Init(() => {
+ createApp(App).use(router).mount('#app');
+});
diff --git a/cmd/templates/vue3-full/frontend/src/router/index.ts b/cmd/templates/vue3-full/frontend/src/router/index.ts
new file mode 100644
index 000000000..73c740289
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/router/index.ts
@@ -0,0 +1,27 @@
+import { createRouter, createMemoryHistory, RouteRecordRaw } from 'vue-router'
+import Home from '../views/Home.vue'
+import About from '../views/About.vue'
+
+const routes: Array = [
+ {
+ path: '/',
+ name: 'Home',
+ component: Home
+ },
+ {
+ path: '/about',
+ name: 'About',
+ // route level code-splitting
+ // this generates a separate chunk (about.[hash].js) for this route
+ // which is lazy-loaded when the route is visited.
+ // component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
+ component: About
+ }
+]
+
+const router = createRouter({
+ history: createMemoryHistory(),
+ routes
+})
+
+export default router
diff --git a/cmd/templates/vue3-full/frontend/src/shims-vue.d.ts b/cmd/templates/vue3-full/frontend/src/shims-vue.d.ts
new file mode 100644
index 000000000..32a1b5cd4
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/shims-vue.d.ts
@@ -0,0 +1,5 @@
+declare module '*.vue' {
+ import { defineComponent } from 'vue'
+ const component: ReturnType
+ export default component
+}
diff --git a/cmd/templates/vue3-full/frontend/src/views/About.vue b/cmd/templates/vue3-full/frontend/src/views/About.vue
new file mode 100644
index 000000000..3fa28070d
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/views/About.vue
@@ -0,0 +1,5 @@
+
+
+
This is an about page
+
+
diff --git a/cmd/templates/vue3-full/frontend/src/views/Home.vue b/cmd/templates/vue3-full/frontend/src/views/Home.vue
new file mode 100644
index 000000000..cc6aac4cb
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/src/views/Home.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/vue3-full/frontend/tests/unit/example.spec.ts b/cmd/templates/vue3-full/frontend/tests/unit/example.spec.ts
new file mode 100644
index 000000000..bbb728f18
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/tests/unit/example.spec.ts
@@ -0,0 +1,14 @@
+import { expect } from 'chai';
+import { describe, it } from 'mocha';
+import { shallowMount } from '@vue/test-utils';
+import HelloWorld from '@/components/HelloWorld.vue';
+
+describe('HelloWorld.vue', () => {
+ it('renders props.msg when passed', () => {
+ const msg = 'new message';
+ const wrapper = shallowMount(HelloWorld, {
+ props: { msg }
+ });
+ expect(wrapper.text()).to.include(msg);
+ });
+});
diff --git a/cmd/templates/vue3-full/frontend/tsconfig.json b/cmd/templates/vue3-full/frontend/tsconfig.json
new file mode 100644
index 000000000..e4ba95a15
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "esnext",
+ "strict": true,
+ "jsx": "preserve",
+ "importHelpers": true,
+ "moduleResolution": "node",
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "types": [
+ "webpack-env",
+ "mocha",
+ "chai"
+ ],
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable",
+ "scripthost"
+ ]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "src/**/*.vue",
+ "tests/**/*.ts",
+ "tests/**/*.tsx"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/cmd/templates/vue3-full/frontend/vue.config.js b/cmd/templates/vue3-full/frontend/vue.config.js
new file mode 100644
index 000000000..8dcf3e339
--- /dev/null
+++ b/cmd/templates/vue3-full/frontend/vue.config.js
@@ -0,0 +1,42 @@
+let cssConfig = {};
+
+if (process.env.NODE_ENV == 'production') {
+ cssConfig = {
+ extract: {
+ filename: '[name].css',
+ chunkFilename: '[name].css'
+ }
+ };
+}
+
+module.exports = {
+ chainWebpack: config => {
+ let limit = 9999999999999999;
+ config.module
+ .rule('images')
+ .test(/\.(png|gif|jpg)(\?.*)?$/i)
+ .use('url-loader')
+ .loader('url-loader')
+ .tap(options => Object.assign(options, { limit: limit }));
+ config.module
+ .rule('fonts')
+ .test(/\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i)
+ .use('url-loader')
+ .loader('url-loader')
+ .options({
+ limit: limit
+ });
+ },
+ css: cssConfig,
+ configureWebpack: {
+ output: {
+ filename: '[name].js'
+ },
+ optimization: {
+ splitChunks: false
+ }
+ },
+ devServer: {
+ disableHostCheck: true
+ }
+};
diff --git a/cmd/templates/vue3-full/go.mod.template b/cmd/templates/vue3-full/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/vue3-full/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/vue3-full/main.go.template b/cmd/templates/vue3-full/main.go.template
new file mode 100644
index 000000000..e2262bd1d
--- /dev/null
+++ b/cmd/templates/vue3-full/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "Hello World!"
+}
+
+func main() {
+
+ js := mewn.String("./frontend/dist/app.js")
+ css := mewn.String("./frontend/dist/app.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/vue3-full/template.json b/cmd/templates/vue3-full/template.json
new file mode 100755
index 000000000..a4e0cb520
--- /dev/null
+++ b/cmd/templates/vue3-full/template.json
@@ -0,0 +1,15 @@
+{
+ "name": "Vue3 Full",
+ "version": "1.0.0",
+ "shortdescription": "Vue 3, Vuex, Vue-router, and Webpack4",
+ "description": "Vue3.0.0 Vuex, Vue-router, and Webpack 4",
+ "install": "npm install",
+ "build": "npm run build",
+ "author": "Kyle Muchmore ",
+ "created": "2020-09-24 21:18:55.09417 +0000 UTC m=+90.125590001",
+ "frontenddir": "frontend",
+ "serve": "npm run serve",
+ "bridge": "src",
+ "wailsdir": "",
+ "platforms": ["linux", "darwin"]
+}
diff --git a/cmd/templates/vuebasic/.jshint b/cmd/templates/vuebasic/.jshint
new file mode 100644
index 000000000..0557edf11
--- /dev/null
+++ b/cmd/templates/vuebasic/.jshint
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
\ No newline at end of file
diff --git a/cmd/templates/vuebasic/frontend/.gitignore b/cmd/templates/vuebasic/frontend/.gitignore
new file mode 100644
index 000000000..185e66319
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/.gitignore
@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
diff --git a/cmd/templates/vuebasic/frontend/README.md b/cmd/templates/vuebasic/frontend/README.md
new file mode 100644
index 000000000..6953f66d0
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/README.md
@@ -0,0 +1,35 @@
+# vue basic
+
+## Project setup
+
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+
+```
+npm run build
+```
+
+### Run your tests
+
+```
+npm run test
+```
+
+### Lints and fixes files
+
+```
+npm run lint
+```
+
+### Customize configuration
+
+See [Configuration Reference](https://cli.vuejs.org/config/).
diff --git a/cmd/templates/vuebasic/frontend/babel.config.js b/cmd/templates/vuebasic/frontend/babel.config.js
new file mode 100644
index 000000000..a6106c484
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/babel.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ presets: [
+ [ '@vue/app', { useBuiltIns: 'entry' } ]
+ ]
+}
diff --git a/cmd/templates/vuebasic/frontend/package.json.template b/cmd/templates/vuebasic/frontend/package.json.template
new file mode 100644
index 000000000..1e51d9f38
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/package.json.template
@@ -0,0 +1,51 @@
+{
+ "name": "{{.NPMProjectName}}",
+ "author": "{{.Author.Name}}<{{.Author.Email}}>",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "lint": "vue-cli-service lint"
+ },
+ "dependencies": {
+ "core-js": "^3.6.4",
+ "regenerator-runtime": "^0.13.3",
+ "vue": "^2.6.11",
+ "@wailsapp/runtime": "^1.0.10"
+ },
+ "devDependencies": {
+ "@vue/cli-plugin-babel": "^4.2.3",
+ "@vue/cli-plugin-eslint": "^4.2.3",
+ "@vue/cli-service": "^4.2.3",
+ "babel-eslint": "^10.1.0",
+ "eslint": "^6.8.0",
+ "eslint-plugin-vue": "^6.2.1",
+ "eventsource-polyfill": "^0.9.6",
+ "vue-template-compiler": "^2.6.11",
+ "webpack-hot-middleware": "^2.25.0"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/essential",
+ "eslint:recommended"
+ ],
+ "rules": {},
+ "parserOptions": {
+ "parser": "babel-eslint"
+ }
+ },
+ "postcss": {
+ "plugins": {
+ "autoprefixer": {}
+ }
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/cmd/templates/vuebasic/frontend/src/App.vue b/cmd/templates/vuebasic/frontend/src/App.vue
new file mode 100644
index 000000000..4700f3a71
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/src/App.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/vuebasic/frontend/src/assets/css/main.css b/cmd/templates/vuebasic/frontend/src/assets/css/main.css
new file mode 100644
index 000000000..ae5df813d
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/src/assets/css/main.css
@@ -0,0 +1,38 @@
+#app {
+ font-family: "Roboto", Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-align: center;
+ color: #eee;
+ margin-top: 60px;
+}
+
+html {
+ height: 100%;
+ overflow: hidden;
+ background-color: #131313;
+ background-size: 20px 20px;
+}
+
+.logo {
+ width: 16em;
+}
+
+/* roboto-regular - latin */
+@font-face {
+ font-family: "Roboto";
+ font-style: normal;
+ font-weight: 400;
+ src: url("../fonts/roboto/roboto-v18-latin-regular.eot"); /* IE9 Compat Modes */
+ src: local("Roboto"), local("Roboto-Regular"),
+ url("../fonts/roboto/roboto-v18-latin-regular.eot?#iefix")
+ format("embedded-opentype"),
+ /* IE6-IE8 */ url("../fonts/roboto/roboto-v18-latin-regular.woff2")
+ format("woff2"),
+ /* Super Modern Browsers */
+ url("../fonts/roboto/roboto-v18-latin-regular.woff") format("woff"),
+ /* Modern Browsers */ url("../fonts/roboto/roboto-v18-latin-regular.ttf")
+ format("truetype"),
+ /* Safari, Android, iOS */
+ url("../fonts/roboto/roboto-v18-latin-regular.svg#Roboto") format("svg"); /* Legacy iOS */
+}
diff --git a/v2/internal/typescriptify/LICENSE.txt b/cmd/templates/vuebasic/frontend/src/assets/fonts/LICENSE.txt
similarity index 97%
rename from v2/internal/typescriptify/LICENSE.txt
rename to cmd/templates/vuebasic/frontend/src/assets/fonts/LICENSE.txt
index fa6e64ac4..75b52484e 100644
--- a/v2/internal/typescriptify/LICENSE.txt
+++ b/cmd/templates/vuebasic/frontend/src/assets/fonts/LICENSE.txt
@@ -1,202 +1,202 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [2015-] [Tomo Krajina]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.eot b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.eot
new file mode 100644
index 000000000..a0780d6e3
Binary files /dev/null and b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.eot differ
diff --git a/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.svg b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.svg
new file mode 100644
index 000000000..627f5a368
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.svg
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.ttf b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.ttf
new file mode 100644
index 000000000..b91bf3f7e
Binary files /dev/null and b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.ttf differ
diff --git a/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.woff b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.woff
new file mode 100644
index 000000000..92dfacc61
Binary files /dev/null and b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.woff differ
diff --git a/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.woff2 b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.woff2
new file mode 100644
index 000000000..7e854e669
Binary files /dev/null and b/cmd/templates/vuebasic/frontend/src/assets/fonts/roboto/roboto-v18-latin-regular.woff2 differ
diff --git a/cmd/templates/vuebasic/frontend/src/assets/images/logo.png b/cmd/templates/vuebasic/frontend/src/assets/images/logo.png
new file mode 100644
index 000000000..31fc8249c
Binary files /dev/null and b/cmd/templates/vuebasic/frontend/src/assets/images/logo.png differ
diff --git a/cmd/templates/vuebasic/frontend/src/components/HelloWorld.vue b/cmd/templates/vuebasic/frontend/src/components/HelloWorld.vue
new file mode 100644
index 000000000..722175f7b
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/src/components/HelloWorld.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/vuebasic/frontend/src/main.js b/cmd/templates/vuebasic/frontend/src/main.js
new file mode 100644
index 000000000..ce05741b7
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/src/main.js
@@ -0,0 +1,15 @@
+import 'core-js/stable';
+import 'regenerator-runtime/runtime';
+import Vue from 'vue';
+import App from './App.vue';
+
+Vue.config.productionTip = false;
+Vue.config.devtools = true;
+
+import * as Wails from '@wailsapp/runtime';
+
+Wails.Init(() => {
+ new Vue({
+ render: h => h(App)
+ }).$mount('#app');
+});
diff --git a/cmd/templates/vuebasic/frontend/vue.config.js b/cmd/templates/vuebasic/frontend/vue.config.js
new file mode 100644
index 000000000..a2691b1f7
--- /dev/null
+++ b/cmd/templates/vuebasic/frontend/vue.config.js
@@ -0,0 +1,42 @@
+let cssConfig = {};
+
+if (process.env.NODE_ENV == "production") {
+ cssConfig = {
+ extract: {
+ filename: "[name].css",
+ chunkFilename: "[name].css"
+ }
+ };
+}
+
+module.exports = {
+ chainWebpack: config => {
+ let limit = 9999999999999999;
+ config.module
+ .rule("images")
+ .test(/\.(png|gif|jpg)(\?.*)?$/i)
+ .use("url-loader")
+ .loader("url-loader")
+ .tap(options => Object.assign(options, { limit: limit }));
+ config.module
+ .rule("fonts")
+ .test(/\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i)
+ .use("url-loader")
+ .loader("url-loader")
+ .options({
+ limit: limit
+ });
+ },
+ css: cssConfig,
+ configureWebpack: {
+ output: {
+ filename: "[name].js"
+ },
+ optimization: {
+ splitChunks: false
+ }
+ },
+ devServer: {
+ disableHostCheck: true
+ }
+};
diff --git a/cmd/templates/vuebasic/go.mod.template b/cmd/templates/vuebasic/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/vuebasic/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/vuebasic/main.go.template b/cmd/templates/vuebasic/main.go.template
new file mode 100644
index 000000000..e2262bd1d
--- /dev/null
+++ b/cmd/templates/vuebasic/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "Hello World!"
+}
+
+func main() {
+
+ js := mewn.String("./frontend/dist/app.js")
+ css := mewn.String("./frontend/dist/app.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/vuebasic/template.json b/cmd/templates/vuebasic/template.json
new file mode 100644
index 000000000..f2d017823
--- /dev/null
+++ b/cmd/templates/vuebasic/template.json
@@ -0,0 +1,12 @@
+{
+ "name": "Vue2/Webpack Basic",
+ "shortdescription": "A basic Vue2/WebPack4 template",
+ "description": "A basic template using Vue 2 and bundled using Webpack 4",
+ "author": "Lea Anthony",
+ "created": "2018-12-01",
+ "frontenddir": "frontend",
+ "install": "npm install",
+ "build": "npm run build",
+ "serve": "npm run serve",
+ "bridge": "src"
+}
diff --git a/cmd/templates/vuetify-basic/.jshint b/cmd/templates/vuetify-basic/.jshint
new file mode 100644
index 000000000..0557edf11
--- /dev/null
+++ b/cmd/templates/vuetify-basic/.jshint
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
\ No newline at end of file
diff --git a/cmd/templates/vuetify-basic/frontend/.gitignore b/cmd/templates/vuetify-basic/frontend/.gitignore
new file mode 100644
index 000000000..185e66319
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/.gitignore
@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
diff --git a/cmd/templates/vuetify-basic/frontend/babel.config.js b/cmd/templates/vuetify-basic/frontend/babel.config.js
new file mode 100644
index 000000000..a6106c484
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/babel.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ presets: [
+ [ '@vue/app', { useBuiltIns: 'entry' } ]
+ ]
+}
diff --git a/cmd/templates/vuetify-basic/frontend/package.json.template b/cmd/templates/vuetify-basic/frontend/package.json.template
new file mode 100644
index 000000000..430566f67
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/package.json.template
@@ -0,0 +1,53 @@
+{
+ "name": "{{.NPMProjectName}}",
+ "author": "{{.Author.Name}}<{{.Author.Email}}>",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "lint": "vue-cli-service lint"
+ },
+"dependencies": {
+ "core-js": "^3.6.4",
+ "regenerator-runtime": "^0.13.3",
+ "material-design-icons-iconfont": "^5.0.1",
+ "vue": "^2.5.22",
+ "vuetify": "^1.5.14",
+ "@wailsapp/runtime": "^1.0.10"
+ },
+ "devDependencies": {
+ "@vue/cli-plugin-babel": "^4.2.3",
+ "@vue/cli-plugin-eslint": "^4.2.3",
+ "@vue/cli-service": "^4.2.3",
+ "babel-eslint": "^10.1.0",
+ "eslint": "^6.8.0",
+ "eslint-plugin-vue": "^6.2.1",
+ "eventsource-polyfill": "^0.9.6",
+ "vue-template-compiler": "^2.6.11",
+ "webpack-hot-middleware": "^2.25.0"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/essential",
+ "eslint:recommended"
+ ],
+ "rules": {},
+ "parserOptions": {
+ "parser": "babel-eslint"
+ }
+ },
+ "postcss": {
+ "plugins": {
+ "autoprefixer": {}
+ }
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/cmd/templates/vuetify-basic/frontend/src/App.vue b/cmd/templates/vuetify-basic/frontend/src/App.vue
new file mode 100644
index 000000000..6522b31e0
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/src/App.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ dashboard
+
+
+ Dashboard
+
+
+
+
+ settings
+
+
+ Settings
+
+
+
+
+
+
+ Application
+
+
+
+
+
+
+
+
+
+ © You
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/templates/vuetify-basic/frontend/src/assets/images/logo.png b/cmd/templates/vuetify-basic/frontend/src/assets/images/logo.png
new file mode 100644
index 000000000..31fc8249c
Binary files /dev/null and b/cmd/templates/vuetify-basic/frontend/src/assets/images/logo.png differ
diff --git a/cmd/templates/vuetify-basic/frontend/src/components/HelloWorld.vue b/cmd/templates/vuetify-basic/frontend/src/components/HelloWorld.vue
new file mode 100644
index 000000000..28487aa06
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/src/components/HelloWorld.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+ Press Me
+
+
+
+
+
+
+
+
+ Message from Go
+ {{message}}
+
+
+
+ Awesome
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/vuetify-basic/frontend/src/main.js b/cmd/templates/vuetify-basic/frontend/src/main.js
new file mode 100644
index 000000000..bbe42e116
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/src/main.js
@@ -0,0 +1,22 @@
+import 'core-js/stable';
+import 'regenerator-runtime/runtime';
+import Vue from 'vue';
+
+// Setup Vuetify
+import Vuetify from 'vuetify';
+Vue.use(Vuetify);
+import 'vuetify/dist/vuetify.min.css';
+import 'material-design-icons-iconfont';
+
+import App from './App.vue';
+
+Vue.config.productionTip = false;
+Vue.config.devtools = true;
+
+import * as Wails from '@wailsapp/runtime';
+
+Wails.Init(() => {
+ new Vue({
+ render: h => h(App)
+ }).$mount('#app');
+});
diff --git a/cmd/templates/vuetify-basic/frontend/vue.config.js b/cmd/templates/vuetify-basic/frontend/vue.config.js
new file mode 100644
index 000000000..a2691b1f7
--- /dev/null
+++ b/cmd/templates/vuetify-basic/frontend/vue.config.js
@@ -0,0 +1,42 @@
+let cssConfig = {};
+
+if (process.env.NODE_ENV == "production") {
+ cssConfig = {
+ extract: {
+ filename: "[name].css",
+ chunkFilename: "[name].css"
+ }
+ };
+}
+
+module.exports = {
+ chainWebpack: config => {
+ let limit = 9999999999999999;
+ config.module
+ .rule("images")
+ .test(/\.(png|gif|jpg)(\?.*)?$/i)
+ .use("url-loader")
+ .loader("url-loader")
+ .tap(options => Object.assign(options, { limit: limit }));
+ config.module
+ .rule("fonts")
+ .test(/\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i)
+ .use("url-loader")
+ .loader("url-loader")
+ .options({
+ limit: limit
+ });
+ },
+ css: cssConfig,
+ configureWebpack: {
+ output: {
+ filename: "[name].js"
+ },
+ optimization: {
+ splitChunks: false
+ }
+ },
+ devServer: {
+ disableHostCheck: true
+ }
+};
diff --git a/cmd/templates/vuetify-basic/go.mod.template b/cmd/templates/vuetify-basic/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/vuetify-basic/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/vuetify-basic/main.go.template b/cmd/templates/vuetify-basic/main.go.template
new file mode 100644
index 000000000..e2262bd1d
--- /dev/null
+++ b/cmd/templates/vuetify-basic/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "Hello World!"
+}
+
+func main() {
+
+ js := mewn.String("./frontend/dist/app.js")
+ css := mewn.String("./frontend/dist/app.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/vuetify-basic/template.json b/cmd/templates/vuetify-basic/template.json
new file mode 100755
index 000000000..b58b5b041
--- /dev/null
+++ b/cmd/templates/vuetify-basic/template.json
@@ -0,0 +1,14 @@
+{
+ "name": "Vuetify1.5/Webpack Basic",
+ "version": "1.0.0",
+ "shortdescription": "A basic Vuetify1.5/Webpack4 template",
+ "description": "Basic template using Vuetify v1.5 and bundled using Webpack",
+ "install": "npm install",
+ "build": "npm run build",
+ "author": "lea ",
+ "created": "2019-05-25 09:39:40.009307 +1000 AEST m=+59.539991073",
+ "frontenddir": "frontend",
+ "serve": "npm run serve",
+ "bridge": "src",
+ "wailsdir": ""
+}
diff --git a/cmd/templates/vuetify2-basic/.jshint b/cmd/templates/vuetify2-basic/.jshint
new file mode 100644
index 000000000..0557edf11
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/.jshint
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
\ No newline at end of file
diff --git a/cmd/templates/vuetify2-basic/frontend/.gitignore b/cmd/templates/vuetify2-basic/frontend/.gitignore
new file mode 100644
index 000000000..185e66319
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/.gitignore
@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
diff --git a/cmd/templates/vuetify2-basic/frontend/babel.config.js b/cmd/templates/vuetify2-basic/frontend/babel.config.js
new file mode 100644
index 000000000..57e6d0a51
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/babel.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ presets: [
+ [ '@vue/app', { useBuiltIns: 'entry' } ]
+ ]
+};
diff --git a/cmd/templates/vuetify2-basic/frontend/package.json.template b/cmd/templates/vuetify2-basic/frontend/package.json.template
new file mode 100644
index 000000000..0913c632d
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/package.json.template
@@ -0,0 +1,53 @@
+{
+ "name": "{{.NPMProjectName}}",
+ "author": "{{.Author.Name}}<{{.Author.Email}}>",
+ "private": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "lint": "vue-cli-service lint"
+ },
+ "dependencies": {
+ "core-js": "^3.6.4",
+ "regenerator-runtime": "^0.13.3",
+ "vue": "^2.6.11",
+ "vuetify": "^2.3.15",
+ "@wailsapp/runtime": "^1.0.10"
+ },
+ "devDependencies": {
+ "@mdi/font": "^4.9.95",
+ "@vue/cli-plugin-babel": "^4.2.3",
+ "@vue/cli-plugin-eslint": "^4.2.3",
+ "@vue/cli-service": "^4.2.3",
+ "babel-eslint": "^10.1.0",
+ "eslint": "^6.8.0",
+ "eslint-plugin-vue": "^6.2.1",
+ "eventsource-polyfill": "^0.9.6",
+ "vue-template-compiler": "^2.6.11",
+ "webpack-hot-middleware": "^2.25.0"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/essential",
+ "eslint:recommended"
+ ],
+ "rules": {},
+ "parserOptions": {
+ "parser": "babel-eslint"
+ }
+ },
+ "postcss": {
+ "plugins": {
+ "autoprefixer": {}
+ }
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/cmd/templates/vuetify2-basic/frontend/src/App.vue b/cmd/templates/vuetify2-basic/frontend/src/App.vue
new file mode 100644
index 000000000..759c1fba4
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/src/App.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ mdi-view-dashboard
+
+
+ Dashboard
+
+
+
+
+ mdi-settings
+
+
+ Settings
+
+
+
+
+
+
+ Application
+
+
+
+
+
+
+
+
+
+ © You
+
+
+
+
+
+
+
diff --git a/cmd/templates/vuetify2-basic/frontend/src/assets/images/logo.png b/cmd/templates/vuetify2-basic/frontend/src/assets/images/logo.png
new file mode 100644
index 000000000..31fc8249c
Binary files /dev/null and b/cmd/templates/vuetify2-basic/frontend/src/assets/images/logo.png differ
diff --git a/cmd/templates/vuetify2-basic/frontend/src/components/HelloWorld.vue b/cmd/templates/vuetify2-basic/frontend/src/components/HelloWorld.vue
new file mode 100644
index 000000000..84708401c
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/src/components/HelloWorld.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+ Press Me
+
+
+
+
+
+
+
+
+ Message from Go
+ {{message}}
+
+
+
+ Awesome
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cmd/templates/vuetify2-basic/frontend/src/main.js b/cmd/templates/vuetify2-basic/frontend/src/main.js
new file mode 100644
index 000000000..245eb1e03
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/src/main.js
@@ -0,0 +1,29 @@
+import 'core-js/stable';
+import 'regenerator-runtime/runtime';
+import '@mdi/font/css/materialdesignicons.css';
+import Vue from 'vue';
+import Vuetify from 'vuetify';
+import 'vuetify/dist/vuetify.min.css';
+
+Vue.use(Vuetify);
+
+import App from './App.vue';
+
+Vue.config.productionTip = false;
+Vue.config.devtools = true;
+
+import Wails from '@wailsapp/runtime';
+
+Wails.Init(() => {
+ new Vue({
+ vuetify: new Vuetify({
+ icons: {
+ iconfont: 'mdi'
+ },
+ theme: {
+ dark: true
+ }
+ }),
+ render: h => h(App)
+ }).$mount('#app');
+});
diff --git a/cmd/templates/vuetify2-basic/frontend/vue.config.js b/cmd/templates/vuetify2-basic/frontend/vue.config.js
new file mode 100644
index 000000000..8dcf3e339
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/frontend/vue.config.js
@@ -0,0 +1,42 @@
+let cssConfig = {};
+
+if (process.env.NODE_ENV == 'production') {
+ cssConfig = {
+ extract: {
+ filename: '[name].css',
+ chunkFilename: '[name].css'
+ }
+ };
+}
+
+module.exports = {
+ chainWebpack: config => {
+ let limit = 9999999999999999;
+ config.module
+ .rule('images')
+ .test(/\.(png|gif|jpg)(\?.*)?$/i)
+ .use('url-loader')
+ .loader('url-loader')
+ .tap(options => Object.assign(options, { limit: limit }));
+ config.module
+ .rule('fonts')
+ .test(/\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i)
+ .use('url-loader')
+ .loader('url-loader')
+ .options({
+ limit: limit
+ });
+ },
+ css: cssConfig,
+ configureWebpack: {
+ output: {
+ filename: '[name].js'
+ },
+ optimization: {
+ splitChunks: false
+ }
+ },
+ devServer: {
+ disableHostCheck: true
+ }
+};
diff --git a/cmd/templates/vuetify2-basic/go.mod.template b/cmd/templates/vuetify2-basic/go.mod.template
new file mode 100644
index 000000000..780381065
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/go.mod.template
@@ -0,0 +1,5 @@
+module {{.BinaryName}}
+
+require (
+ github.com/wailsapp/wails {{.WailsVersion}}
+)
\ No newline at end of file
diff --git a/cmd/templates/vuetify2-basic/main.go.template b/cmd/templates/vuetify2-basic/main.go.template
new file mode 100644
index 000000000..e2262bd1d
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/main.go.template
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails"
+)
+
+func basic() string {
+ return "Hello World!"
+}
+
+func main() {
+
+ js := mewn.String("./frontend/dist/app.js")
+ css := mewn.String("./frontend/dist/app.css")
+
+ app := wails.CreateApp(&wails.AppConfig{
+ Width: 1024,
+ Height: 768,
+ Title: "{{.Name}}",
+ JS: js,
+ CSS: css,
+ Colour: "#131313",
+ })
+ app.Bind(basic)
+ app.Run()
+}
diff --git a/cmd/templates/vuetify2-basic/template.json b/cmd/templates/vuetify2-basic/template.json
new file mode 100755
index 000000000..a33450fe5
--- /dev/null
+++ b/cmd/templates/vuetify2-basic/template.json
@@ -0,0 +1,14 @@
+{
+ "name": "Vuetify2/Webpack Basic",
+ "version": "1.0.0",
+ "shortdescription": "A basic Vuetify2/Webpack4 template",
+ "description": "Basic template using Vuetify v2 and bundled using Webpack",
+ "install": "npm install",
+ "build": "npm run build",
+ "author": "Michael Hipp ",
+ "created": "2019-09-06",
+ "frontenddir": "frontend",
+ "serve": "npm run serve",
+ "bridge": "src",
+ "wailsdir": ""
+}
diff --git a/cmd/version.go b/cmd/version.go
new file mode 100644
index 000000000..9fc48b03e
--- /dev/null
+++ b/cmd/version.go
@@ -0,0 +1,4 @@
+package cmd
+
+// Version - Wails version
+const Version = "v1.11.0"
diff --git a/cmd/wails/0_setup.go b/cmd/wails/0_setup.go
new file mode 100644
index 000000000..bab0949d4
--- /dev/null
+++ b/cmd/wails/0_setup.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "runtime"
+
+ "github.com/wailsapp/wails/cmd"
+)
+
+func init() {
+
+ commandDescription := `Sets up your local environment to develop Wails apps.`
+
+ setupCommand := app.Command("setup", "Setup the Wails environment").
+ LongDescription(commandDescription)
+
+ app.DefaultCommand(setupCommand)
+
+ setupCommand.Action(func() error {
+
+ logger.PrintBanner()
+
+ var err error
+
+ system := cmd.NewSystemHelper()
+ err = system.Initialise()
+ if err != nil {
+ return err
+ }
+
+ var successMessage = `Ready for take off!
+Create your first project by running 'wails init'.`
+ if runtime.GOOS != "windows" {
+ successMessage = "🚀 " + successMessage
+ }
+
+ // Chrck for programs and libraries dependencies
+ errors, err := cmd.CheckDependencies(logger)
+ if err != nil {
+ return err
+ }
+
+ // Check Mewn
+ err = cmd.CheckMewn(false)
+ if err != nil {
+ return err
+ }
+
+ // Check for errors
+ // CheckDependencies() returns !errors
+ // so to get the right message in this
+ // check we have to do it in reversed
+ if errors {
+ logger.Yellow(successMessage)
+ }
+
+ return err
+ })
+}
diff --git a/cmd/wails/10.1_dev_newtemplate.go b/cmd/wails/10.1_dev_newtemplate.go
new file mode 100644
index 000000000..3c52b45a9
--- /dev/null
+++ b/cmd/wails/10.1_dev_newtemplate.go
@@ -0,0 +1,121 @@
+// +build dev
+
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/wailsapp/wails/cmd"
+ "gopkg.in/AlecAivazis/survey.v1"
+)
+
+var templateHelper = cmd.NewTemplateHelper()
+
+var qs = []*survey.Question{
+ {
+ Name: "Name",
+ Prompt: &survey.Input{Message: "Please enter the name of your template (eg: React/Webpack Basic):"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "ShortDescription",
+ Prompt: &survey.Input{Message: "Please enter a short description for the template (eg: React with Webpack 4):"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "Description",
+ Prompt: &survey.Input{Message: "Please enter a long description:"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "FrontendDir",
+ Prompt: &survey.Input{Message: "Please enter the name of the directory the frontend code resides (eg: frontend):"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "Install",
+ Prompt: &survey.Input{Message: "Please enter the install command (eg: npm install):"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "Build",
+ Prompt: &survey.Input{Message: "Please enter the build command (eg: npm run build):"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "Serve",
+ Prompt: &survey.Input{Message: "Please enter the serve command (eg: npm run serve):"},
+ Validate: survey.Required,
+ },
+ {
+ Name: "Bridge",
+ Prompt: &survey.Input{Message: "Please enter the name of the directory to copy the wails bridge runtime (eg: src):"},
+ Validate: survey.Required,
+ },
+}
+
+func newTemplate(devCommand *cmd.Command) {
+
+ commandDescription := `This command scaffolds everything needed to develop a new template.`
+ newTemplate := devCommand.Command("newtemplate", "Generate a new template").
+ LongDescription(commandDescription)
+
+ newTemplate.Action(func() error {
+ logger.PrintSmallBanner("Generating new project template")
+ fmt.Println()
+
+ var answers cmd.TemplateMetadata
+
+ // perform the questions
+ err := survey.Ask(qs, &answers)
+ if err != nil {
+ fmt.Println(err.Error())
+ return err
+ }
+
+ dirname := templateHelper.SanitizeFilename(answers.Name)
+ prompt := []*survey.Question{{
+ Prompt: &survey.Input{
+ Message: "Please enter a directory name for the template:",
+ Default: dirname,
+ },
+ Validate: func(val interface{}) error {
+ err := survey.Required(val)
+ if err != nil {
+ return err
+ }
+ if templateHelper.IsValidTemplate(val.(string)) {
+ return fmt.Errorf("template directory already exists")
+ }
+ if templateHelper.SanitizeFilename(val.(string)) != val.(string) {
+ return fmt.Errorf("invalid directory name '%s'", val.(string))
+ }
+ return nil
+ },
+ }}
+ err = survey.Ask(prompt, &dirname)
+ if err != nil {
+ return err
+ }
+
+ answers.Version = "1.0.0"
+ answers.Created = time.Now().String()
+
+ // Get Author info from system info
+ system := cmd.NewSystemHelper()
+ author, err := system.GetAuthor()
+ if err == nil {
+ answers.Author = author
+ }
+
+ templateDirectory, err := templateHelper.CreateNewTemplate(dirname, &answers)
+ if err != nil {
+ return err
+ }
+
+ logger.Green("Created new template '%s' in directory '%s'", answers.Name, templateDirectory)
+
+ return nil
+ })
+}
diff --git a/cmd/wails/10_dev.go b/cmd/wails/10_dev.go
new file mode 100644
index 000000000..3d237cb98
--- /dev/null
+++ b/cmd/wails/10_dev.go
@@ -0,0 +1,18 @@
+// +build dev
+
+package main
+
+func init() {
+
+ commandDescription := `This command provides access to developer tooling.`
+ devCommand := app.Command("dev", "A selection of developer tools").
+ LongDescription(commandDescription)
+
+ // Add subcommands
+ newTemplate(devCommand)
+
+ devCommand.Action(func() error {
+ devCommand.PrintHelp()
+ return nil
+ })
+}
diff --git a/cmd/wails/15_migrate.go b/cmd/wails/15_migrate.go
new file mode 100644
index 000000000..f4c935f77
--- /dev/null
+++ b/cmd/wails/15_migrate.go
@@ -0,0 +1,385 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/Masterminds/semver"
+ "github.com/leaanthony/spinner"
+ "github.com/wailsapp/wails/cmd"
+)
+
+// Constants
+var checkSpinner = spinner.NewSpinner()
+var migrateProjectOptions = &cmd.ProjectOptions{}
+var migrateFS = cmd.NewFSHelper()
+var migrateGithub = cmd.NewGitHubHelper()
+var programHelper = cmd.NewProgramHelper()
+var lessThanV1 *semver.Constraints
+
+// The user's go.mod
+var goMod string
+var goModFile string
+
+// The user's main.js
+var mainJSFile string
+var mainJSContents string
+
+// Frontend directory
+var frontEndDir string
+
+func init() {
+
+ var dryrun bool
+ var err error
+
+ lessThanV1, err = semver.NewConstraint("< v1.0.0")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // var forceRebuild = false
+ checkSpinner.SetSpinSpeed(50)
+
+ commandDescription := `EXPERIMENTAL - This command attempts to migrate projects to the latest Wails version.`
+ updateCmd := app.Command("migrate", "Migrate projects to latest Wails release").
+ LongDescription(commandDescription).
+ BoolFlag("dryrun", "Only display what would be done", &dryrun)
+
+ updateCmd.Action(func() error {
+
+ message := "Migrate Project"
+ logger.PrintSmallBanner(message)
+ logger.Red("WARNING: This is an experimental command. Ensure you have backups of your project!")
+ logger.Red("It currently only supports npm based projects.")
+ fmt.Println()
+
+ // Check project directory
+ err := checkProjectDirectory()
+ if err != nil {
+ return err
+ }
+
+ // Find Wails version from go.mod
+ wailsVersion, err := getWailsVersion()
+ if err != nil {
+ return err
+ }
+
+ // Get latest stable version
+ var latestVersion *semver.Version
+ latestVersion, err = getLatestWailsVersion()
+ if err != nil {
+ return err
+ }
+
+ var canMigrate bool
+ canMigrate, err = canMigrateVersion(wailsVersion, latestVersion)
+ if err != nil {
+ return err
+ }
+
+ if !canMigrate {
+ return nil
+ }
+
+ // Check for wailsbridge
+ wailsBridge, err := checkWailsBridge()
+ if err != nil {
+ return err
+ }
+
+ // Is main.js using bridge.Init()
+ canUpdateMainJS, err := checkMainJS()
+ if err != nil {
+ return err
+ }
+
+ // TODO: Check if we are using legacy js runtime
+
+ // Operations
+ logger.Yellow("Operations to perform:")
+
+ logger.Yellowf(" - Update to Wails v%s\n", latestVersion)
+
+ if len(wailsBridge) > 0 {
+ logger.Yellow(" - Delete wailsbridge.js")
+ }
+
+ if canUpdateMainJS {
+ logger.Yellow(" - Patch main.js")
+ }
+
+ logger.Yellow(" - Ensure '@wailsapp/runtime` module is installed")
+
+ if dryrun {
+ logger.White("Exiting: Dry Run")
+ return nil
+ }
+
+ logger.Red("*WARNING* About to modify your project!")
+ logger.Red("Type 'YES' to continue: ")
+ scanner := bufio.NewScanner(os.Stdin)
+ scanner.Scan()
+ input := scanner.Text()
+ if input != "YES" {
+ logger.Red("ABORTED!")
+ return nil
+ }
+
+ logger.Yellow("Let's do this!")
+
+ err = updateWailsVersion(wailsVersion, latestVersion)
+ if err != nil {
+ return err
+ }
+
+ if len(wailsBridge) > 0 {
+ err = deleteWailsBridge(wailsBridge)
+ if err != nil {
+ return err
+ }
+ }
+
+ if canUpdateMainJS {
+ err = patchMainJS()
+ if err != nil {
+ return err
+ }
+ }
+
+ // Install runtime
+ err = installWailsRuntime()
+ if err != nil {
+ return err
+ }
+
+ fmt.Println()
+ logger.Yellow("Migration complete! Check project by running `wails build`.")
+ return nil
+ })
+}
+
+func checkProjectDirectory() error {
+ // Get versions
+ checkSpinner.Start("Check Project Directory")
+
+ // Check we are in project directory
+ err := migrateProjectOptions.LoadConfig(migrateFS.Cwd())
+ if err != nil {
+ checkSpinner.Error()
+ return fmt.Errorf("Unable to find 'project.json'. Please check you are in a Wails project directory")
+ }
+
+ checkSpinner.Success()
+ return nil
+}
+
+func getWailsVersion() (*semver.Version, error) {
+ checkSpinner.Start("Get Wails Version")
+
+ result, err := cmd.GetWailsVersion()
+
+ if err != nil {
+ checkSpinner.Error(err.Error())
+ return nil, err
+ }
+ return result, nil
+
+}
+
+func canMigrateVersion(wailsVersion *semver.Version, latestVersion *semver.Version) (bool, error) {
+ checkSpinner.Start("Checking ability to Migrate")
+
+ // Check if we are at the latest version!!!!
+ if wailsVersion.Equal(latestVersion) || wailsVersion.GreaterThan(latestVersion) {
+ checkSpinner.Errorf("Checking ability to Migrate: No! (v%s >= v%s)", wailsVersion, latestVersion)
+ return false, nil
+ }
+
+ // Check for < v1.0.0
+ if lessThanV1.Check(wailsVersion) {
+ checkSpinner.Successf("Checking ability to Migrate: Yes! (v%s < v1.0.0)", wailsVersion)
+ return true, nil
+ }
+ checkSpinner.Error("Unable to migrate")
+ return false, fmt.Errorf("No migration rules for version %s", wailsVersion)
+}
+
+func checkWailsBridge() (string, error) {
+ checkSpinner.Start("Checking if legacy Wails Bridge present")
+
+ // Check frontend dir is available
+ if migrateProjectOptions.FrontEnd == nil ||
+ len(migrateProjectOptions.FrontEnd.Dir) == 0 ||
+ !migrateFS.DirExists(migrateProjectOptions.FrontEnd.Dir) {
+ checkSpinner.Error("Unable to determine frontend directory")
+ return "", fmt.Errorf("Unable to determine frontend directory")
+ }
+
+ frontEndDir = migrateProjectOptions.FrontEnd.Dir
+
+ wailsBridgePath, err := filepath.Abs(filepath.Join(".", frontEndDir, "src", "wailsbridge.js"))
+ if err != nil {
+ checkSpinner.Error(err.Error())
+ return "", err
+ }
+
+ // If it doesn't exist, return blank string
+ if !migrateFS.FileExists(wailsBridgePath) {
+ checkSpinner.Success("Checking if legacy Wails Bridge present: No")
+ return "", nil
+ }
+
+ checkSpinner.Success("Checking if legacy Wails Bridge present: Yes")
+ return wailsBridgePath, nil
+
+}
+
+// This function determines if the main.js file using wailsbridge can be auto-updated
+func checkMainJS() (bool, error) {
+
+ checkSpinner.Start("Checking if main.js can be migrated")
+ var err error
+
+ // Check main.js is there
+ if migrateProjectOptions.FrontEnd == nil ||
+ len(migrateProjectOptions.FrontEnd.Dir) == 0 ||
+ !migrateFS.DirExists(migrateProjectOptions.FrontEnd.Dir) {
+ checkSpinner.Error("Unable to determine frontend directory")
+ return false, fmt.Errorf("Unable to determine frontend directory")
+ }
+
+ frontEndDir = migrateProjectOptions.FrontEnd.Dir
+
+ mainJSFile, err = filepath.Abs(filepath.Join(".", frontEndDir, "src", "main.js"))
+ if err != nil {
+ checkSpinner.Error("Unable to find main.js")
+ return false, err
+ }
+
+ mainJSContents, err = migrateFS.LoadAsString(mainJSFile)
+ if err != nil {
+ checkSpinner.Error("Unable to load main.js")
+ return false, err
+ }
+
+ // Check we have a line like: import Bridge from "./wailsbridge";
+ if strings.Index(mainJSContents, `import Bridge from "./wailsbridge";`) == -1 {
+ checkSpinner.Success("Checking if main.js can be migrated: No - Cannot find `import Bridge`")
+ return false, nil
+ }
+
+ // Check we have a line like: Bridge.Start(() => {
+ if strings.Index(mainJSContents, `Bridge.Start(`) == -1 {
+ checkSpinner.Success("Checking if main.js can be migrated: No - Cannot find `Bridge.Start`")
+ return false, nil
+ }
+ checkSpinner.Success("Checking if main.js can be migrated: Yes")
+ return true, nil
+}
+
+func getLatestWailsVersion() (*semver.Version, error) {
+ checkSpinner.Start("Checking GitHub for latest Wails version")
+ version, err := migrateGithub.GetLatestStableRelease()
+ if err != nil {
+ checkSpinner.Error("Checking GitHub for latest Wails version: Failed")
+ return nil, err
+ }
+
+ checkSpinner.Successf("Checking GitHub for latest Wails version: v%s", version)
+ return version.Version, nil
+}
+
+func updateWailsVersion(currentVersion, latestVersion *semver.Version) error {
+ // Patch go.mod
+ checkSpinner.Start("Patching go.mod")
+
+ wailsModule := "github.com/wailsapp/wails"
+ old := fmt.Sprintf("%s v%s", wailsModule, currentVersion)
+ new := fmt.Sprintf("%s v%s", wailsModule, latestVersion)
+
+ goMod = strings.Replace(goMod, old, new, -1)
+ err := ioutil.WriteFile(goModFile, []byte(goMod), 0600)
+ if err != nil {
+ checkSpinner.Error()
+ return err
+ }
+
+ checkSpinner.Success()
+ return nil
+}
+
+func deleteWailsBridge(bridgeFilename string) error {
+ // Patch go.mod
+ checkSpinner.Start("Delete legacy wailsbridge.js")
+
+ err := migrateFS.RemoveFile(bridgeFilename)
+ if err != nil {
+ checkSpinner.Error()
+ return err
+ }
+
+ checkSpinner.Success()
+ return nil
+}
+
+func patchMainJS() error {
+ // Patch main.js
+ checkSpinner.Start("Patching main.js")
+
+ // Patch import line
+ oldImportLine := `import Bridge from "./wailsbridge";`
+ newImportLine := `import * as Wails from "@wailsapp/runtime";`
+ mainJSContents = strings.Replace(mainJSContents, oldImportLine, newImportLine, -1)
+
+ // Patch Start line
+ oldStartLine := `Bridge.Start`
+ newStartLine := `Wails.Init`
+ mainJSContents = strings.Replace(mainJSContents, oldStartLine, newStartLine, -1)
+
+ err := ioutil.WriteFile(mainJSFile, []byte(mainJSContents), 0600)
+ if err != nil {
+ checkSpinner.Error()
+ return err
+ }
+
+ checkSpinner.Success()
+ return nil
+}
+
+func installWailsRuntime() error {
+
+ checkSpinner.Start("Installing @wailsapp/runtime module")
+
+ // Change to the frontend directory
+ err := os.Chdir(frontEndDir)
+ if err != nil {
+ checkSpinner.Error()
+ return nil
+ }
+
+ // Determine package manager
+ packageManager, err := migrateProjectOptions.GetNPMBinaryName()
+ if err != nil {
+ checkSpinner.Error()
+ return nil
+ }
+
+ switch packageManager {
+ case cmd.NPM:
+ // npm install --save @wailsapp/runtime
+ programHelper.InstallNPMPackage("@wailsapp/runtime", true)
+ default:
+ checkSpinner.Error()
+ return fmt.Errorf("Unknown package manager")
+ }
+
+ checkSpinner.Success()
+ return nil
+}
diff --git a/cmd/wails/2_init.go b/cmd/wails/2_init.go
new file mode 100644
index 000000000..06289c6cd
--- /dev/null
+++ b/cmd/wails/2_init.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/leaanthony/spinner"
+ "github.com/wailsapp/wails/cmd"
+)
+
+func init() {
+
+ projectHelper := cmd.NewProjectHelper()
+ projectOptions := projectHelper.NewProjectOptions()
+ commandDescription := `Generates a new Wails project using the given flags.
+Any flags that are required and not given will be prompted for.`
+
+ initCommand := app.Command("init", "Initialises a new Wails project").
+ LongDescription(commandDescription).
+ BoolFlag("f", "Use defaults", &projectOptions.UseDefaults).
+ StringFlag("dir", "Directory to create project in", &projectOptions.OutputDirectory).
+ StringFlag("template", "Template name", &projectOptions.Template).
+ StringFlag("name", "Project name", &projectOptions.Name).
+ StringFlag("description", "Project description", &projectOptions.Description).
+ StringFlag("output", "Output binary name", &projectOptions.BinaryName)
+
+ initCommand.Action(func() error {
+
+ logger.PrintSmallBanner("Initialising project")
+ fmt.Println()
+
+ // Check if the system is initialised
+ system := cmd.NewSystemHelper()
+ err := system.CheckInitialised()
+ if err != nil {
+ return err
+ }
+
+ success, err := cmd.CheckDependenciesSilent(logger)
+ if !success {
+ return err
+ }
+
+ // Do we want to just force defaults?
+ if projectOptions.UseDefaults {
+ // Use defaults
+ projectOptions.Defaults()
+ } else {
+ err = projectOptions.PromptForInputs()
+ if err != nil {
+ return err
+ }
+ }
+
+ genSpinner := spinner.NewSpinner()
+ genSpinner.SetSpinSpeed(50)
+ genSpinner.Start("Generating project...")
+
+ // Generate the project
+ err = projectHelper.GenerateProject(projectOptions)
+ if err != nil {
+ genSpinner.Error()
+ return err
+ }
+ genSpinner.Success()
+
+ // Build the project
+ cwd, _ := os.Getwd()
+ projectDir := filepath.Join(cwd, projectOptions.OutputDirectory)
+ program := cmd.NewProgramHelper()
+ buildSpinner := spinner.NewSpinner()
+ buildSpinner.SetSpinSpeed(50)
+ buildSpinner.Start("Building project (this may take a while)...")
+ err = program.RunCommandArray([]string{"wails", "build"}, projectDir)
+ if err != nil {
+ buildSpinner.Error(err.Error())
+ return err
+ }
+ buildSpinner.Success()
+ logger.Yellow("Project '%s' built in directory '%s'!", projectOptions.Name, projectOptions.OutputDirectory)
+
+ return err
+ })
+}
diff --git a/cmd/wails/4_build.go b/cmd/wails/4_build.go
new file mode 100644
index 000000000..d42cdff6f
--- /dev/null
+++ b/cmd/wails/4_build.go
@@ -0,0 +1,212 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+
+ "github.com/leaanthony/spinner"
+ "github.com/wailsapp/wails/cmd"
+)
+
+// getSupportedPlatforms returns a slice of platform/architecture
+// targets that are buildable using the cross-platform 'x' option.
+func getSupportedPlatforms() []string {
+ return []string{
+ "darwin/amd64",
+ "linux/amd64",
+ "linux/arm-7",
+ "windows/amd64",
+ }
+}
+
+func init() {
+
+ var packageApp = false
+ var forceRebuild = false
+ var debugMode = false
+ var usefirebug = false
+ var gopath = ""
+ var typescriptFilename = ""
+ var verbose = false
+ var platform = ""
+ var ldflags = ""
+ var tags = ""
+
+ buildSpinner := spinner.NewSpinner()
+ buildSpinner.SetSpinSpeed(50)
+
+ commandDescription := `This command will check to ensure all pre-requistes are installed prior to building. If not, it will attempt to install them. Building comprises of a number of steps: install frontend dependencies, build frontend, pack frontend, compile main application.`
+ initCmd := app.Command("build", "Builds your Wails project").
+ LongDescription(commandDescription).
+ BoolFlag("p", "Package application on successful build", &packageApp).
+ BoolFlag("f", "Force rebuild of application components", &forceRebuild).
+ BoolFlag("d", "Build in Debug mode", &debugMode).
+ BoolFlag("firebug", "Enable firebug console for debug builds", &usefirebug).
+ BoolFlag("verbose", "Verbose output", &verbose).
+ StringFlag("t", "Generate Typescript definitions to given file (at runtime)", &typescriptFilename).
+ StringFlag("ldflags", "Extra options for -ldflags", &ldflags).
+ StringFlag("gopath", "Specify your GOPATH location. Mounted to /go during cross-compilation.", &gopath).
+ StringFlag("tags", "Build tags to pass to the go compiler (quoted and space separated)", &tags)
+
+ var b strings.Builder
+ for _, plat := range getSupportedPlatforms() {
+ fmt.Fprintf(&b, " - %s\n", plat)
+ }
+ initCmd.StringFlag("x",
+ fmt.Sprintf("Cross-compile application to specified platform via xgo\n%s", b.String()),
+ &platform)
+
+ initCmd.Action(func() error {
+
+ message := "Building Application"
+ if packageApp {
+ message = "Packaging Application"
+ }
+ if forceRebuild {
+ message += " (force rebuild)"
+ }
+ logger.PrintSmallBanner(message)
+ fmt.Println()
+
+ // Project options
+ projectOptions := &cmd.ProjectOptions{}
+ projectOptions.Verbose = verbose
+ projectOptions.UseFirebug = usefirebug
+
+ // Check we are in project directory
+ // Check project.json loads correctly
+ fs := cmd.NewFSHelper()
+ err := projectOptions.LoadConfig(fs.Cwd())
+ if err != nil {
+ return fmt.Errorf("Unable to find 'project.json'. Please check you are in a Wails project directory")
+ }
+
+ // Set firebug flag
+ projectOptions.UseFirebug = usefirebug
+
+ // Check that this platform is supported
+ if !projectOptions.PlatformSupported() {
+ logger.Yellow("WARNING: This project is unsupported on %s - it probably won't work!\n Valid platforms: %s\n", runtime.GOOS, strings.Join(projectOptions.Platforms, ", "))
+ }
+
+ // Set cross-compile
+ projectOptions.Platform = runtime.GOOS
+ if len(platform) > 0 {
+ supported := false
+ for _, plat := range getSupportedPlatforms() {
+ if plat == platform {
+ supported = true
+ }
+ }
+ if !supported {
+ return fmt.Errorf("unsupported platform '%s' specified.\nPlease run `wails build -h` to see the supported platform/architecture options", platform)
+ }
+
+ projectOptions.CrossCompile = true
+ plat := strings.Split(platform, "/")
+ projectOptions.Platform = plat[0]
+ projectOptions.Architecture = plat[1]
+ }
+
+ // Add ldflags
+ projectOptions.LdFlags = ldflags
+ projectOptions.GoPath = gopath
+
+ // Add tags
+ projectOptions.Tags = tags
+
+ // Validate config
+ // Check if we have a frontend
+ err = cmd.ValidateFrontendConfig(projectOptions)
+ if err != nil {
+ return err
+ }
+
+ // Program checker
+ program := cmd.NewProgramHelper()
+
+ if projectOptions.FrontEnd != nil {
+ // npm
+ if !program.IsInstalled("npm") {
+ return fmt.Errorf("it appears npm is not installed. Please install and run again")
+ }
+ }
+
+ // Save project directory
+ projectDir := fs.Cwd()
+
+ // Install deps
+ if projectOptions.FrontEnd != nil {
+ err = cmd.InstallFrontendDeps(projectDir, projectOptions, forceRebuild, "build")
+ if err != nil {
+ return err
+ }
+
+ // Ensure that runtime init.js is the production version
+ err = cmd.InstallProdRuntime(projectDir, projectOptions)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Move to project directory
+ err = os.Chdir(projectDir)
+ if err != nil {
+ return err
+ }
+
+ // Install dependencies
+ err = cmd.InstallGoDependencies(projectOptions.Verbose)
+ if err != nil {
+ return err
+ }
+
+ // Build application
+ buildMode := cmd.BuildModeProd
+ if debugMode {
+ buildMode = cmd.BuildModeDebug
+ }
+
+ // Save if we wish to dump typescript or not
+ if typescriptFilename != "" {
+ projectOptions.SetTypescriptDefsFilename(typescriptFilename)
+ }
+
+ // Update go.mod if it is out of sync with current version
+ outofsync, err := cmd.GoModOutOfSync()
+ if err != nil {
+ return err
+ }
+ gomodVersion, err := cmd.GetWailsVersion()
+ if err != nil {
+ return err
+ }
+ if outofsync {
+ syncMessage := fmt.Sprintf("Updating go.mod (Wails version %s => %s)", gomodVersion, cmd.Version)
+ buildSpinner := spinner.NewSpinner(syncMessage)
+ buildSpinner.Start()
+ err := cmd.UpdateGoModVersion()
+ if err != nil {
+ buildSpinner.Error(err.Error())
+ return err
+ }
+ buildSpinner.Success()
+ }
+
+ err = cmd.BuildApplication(projectOptions.BinaryName, forceRebuild, buildMode, packageApp, projectOptions)
+ if err != nil {
+ return err
+ }
+
+ if projectOptions.Platform == "windows" {
+ logger.Yellow("*** Please note: Windows builds use mshtml which is only compatible with IE11. For more information, please read https://wails.app/guides/windows/ ***")
+ }
+
+ logger.Yellow("Awesome! Project '%s' built!", projectOptions.Name)
+
+ return nil
+
+ })
+}
diff --git a/cmd/wails/6_serve.go b/cmd/wails/6_serve.go
new file mode 100644
index 000000000..1ea93c366
--- /dev/null
+++ b/cmd/wails/6_serve.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "fmt"
+ "runtime"
+
+ "github.com/leaanthony/spinner"
+ "github.com/wailsapp/wails/cmd"
+)
+
+func init() {
+
+ var forceRebuild = false
+ var verbose = false
+ buildSpinner := spinner.NewSpinner()
+ buildSpinner.SetSpinSpeed(50)
+
+ commandDescription := `This command builds then serves your application in bridge mode. Useful for developing your app in a browser.`
+ initCmd := app.Command("serve", "Run your Wails project in bridge mode").
+ LongDescription(commandDescription).
+ BoolFlag("verbose", "Verbose output", &verbose).
+ BoolFlag("f", "Force rebuild of application components", &forceRebuild)
+
+ initCmd.Action(func() error {
+
+ message := "Serving Application"
+ logger.PrintSmallBanner(message)
+ fmt.Println()
+
+ // Check Mewn is installed
+ err := cmd.CheckMewn(verbose)
+ if err != nil {
+ return err
+ }
+
+ // Project options
+ projectOptions := &cmd.ProjectOptions{}
+
+ // Check we are in project directory
+ // Check project.json loads correctly
+ fs := cmd.NewFSHelper()
+ err = projectOptions.LoadConfig(fs.Cwd())
+ if err != nil {
+ return err
+ }
+
+ // Set project options
+ projectOptions.Verbose = verbose
+ projectOptions.Platform = runtime.GOOS
+
+ // Save project directory
+ projectDir := fs.Cwd()
+
+ // Install the bridge library
+ err = cmd.InstallBridge(projectDir, projectOptions)
+ if err != nil {
+ return err
+ }
+
+ // Install dependencies
+ err = cmd.InstallGoDependencies(projectOptions.Verbose)
+ if err != nil {
+ return err
+ }
+
+ buildMode := cmd.BuildModeBridge
+ err = cmd.BuildApplication(projectOptions.BinaryName, forceRebuild, buildMode, false, projectOptions)
+ if err != nil {
+ return err
+ }
+
+ logger.Yellow("Awesome! Project '%s' built!", projectOptions.Name)
+
+ return cmd.ServeProject(projectOptions, logger)
+ })
+}
diff --git a/cmd/wails/8_update.go b/cmd/wails/8_update.go
new file mode 100644
index 000000000..70224aa49
--- /dev/null
+++ b/cmd/wails/8_update.go
@@ -0,0 +1,164 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/leaanthony/spinner"
+ "github.com/wailsapp/wails/cmd"
+)
+
+func init() {
+
+ var prereleaseRequired bool
+ var specificVersion string
+
+ // var forceRebuild = false
+ checkSpinner := spinner.NewSpinner()
+ checkSpinner.SetSpinSpeed(50)
+
+ commandDescription := `This command allows you to update your version of Wails.`
+ updateCmd := app.Command("update", "Update to newer [pre]releases or specific versions").
+ LongDescription(commandDescription).
+ BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired).
+ StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
+
+ updateCmd.Action(func() error {
+
+ message := "Checking for updates..."
+ logger.PrintSmallBanner(message)
+ fmt.Println()
+
+ // Get versions
+ checkSpinner.Start(message)
+
+ github := cmd.NewGitHubHelper()
+ var desiredVersion *cmd.SemanticVersion
+ var err error
+ var valid bool
+
+ if len(specificVersion) > 0 {
+ // Check if this is a valid version
+ valid, err = github.IsValidTag(specificVersion)
+ if err == nil {
+ if !valid {
+ err = fmt.Errorf("version '%s' is invalid", specificVersion)
+ } else {
+ desiredVersion, err = cmd.NewSemanticVersion(specificVersion)
+ }
+ }
+ } else {
+ if prereleaseRequired {
+ desiredVersion, err = github.GetLatestPreRelease()
+ } else {
+ desiredVersion, err = github.GetLatestStableRelease()
+ }
+ }
+ if err != nil {
+ checkSpinner.Error(err.Error())
+ return err
+ }
+ checkSpinner.Success()
+ fmt.Println()
+
+ fmt.Println(" Current Version : " + cmd.Version)
+
+ if len(specificVersion) > 0 {
+ fmt.Printf(" Desired Version : v%s\n", desiredVersion)
+ } else {
+ if prereleaseRequired {
+ fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
+ } else {
+ fmt.Printf(" Latest Release : v%s\n", desiredVersion)
+ }
+ }
+
+ return updateToVersion(desiredVersion, len(specificVersion) > 0)
+ })
+}
+
+func updateToVersion(targetVersion *cmd.SemanticVersion, force bool) error {
+
+ var targetVersionString = "v" + targetVersion.String()
+
+ // Early exit
+ if targetVersionString == cmd.Version {
+ logger.Green("Looks like you're up to date!")
+ return nil
+ }
+
+ var desiredVersion string
+
+ if !force {
+
+ compareVersion := cmd.Version
+
+ currentVersion, err := cmd.NewSemanticVersion(compareVersion)
+ if err != nil {
+ return err
+ }
+
+ var success bool
+
+ // Release -> Pre-Release = Massage current version to prerelease format
+ if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
+ testVersion, err := cmd.NewSemanticVersion(compareVersion + "-0")
+ if err != nil {
+ return err
+ }
+ success, _ = targetVersion.IsGreaterThan(testVersion)
+ }
+ // Pre-Release -> Release = Massage target version to prerelease format
+ if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
+ // We are ok with greater than or equal
+ mainversion := currentVersion.MainVersion()
+ targetVersion, err = cmd.NewSemanticVersion(targetVersion.String())
+ if err != nil {
+ return err
+ }
+ success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
+ }
+
+ // Release -> Release = Standard check
+ if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
+ (targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
+
+ success, _ = targetVersion.IsGreaterThan(currentVersion)
+ }
+
+ // Compare
+ if !success {
+ logger.Red("The requested version is lower than the current version.")
+ logger.Red("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
+ return nil
+ }
+
+ desiredVersion = "v" + targetVersion.String()
+
+ } else {
+ desiredVersion = "v" + targetVersion.String()
+ }
+
+ fmt.Println()
+ updateSpinner := spinner.NewSpinner()
+ updateSpinner.SetSpinSpeed(40)
+ updateSpinner.Start("Installing Wails " + desiredVersion)
+
+ // Run command in non module directory
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ log.Fatal("Cannot find home directory! Please file a bug report!")
+ }
+
+ err = cmd.NewProgramHelper().RunCommandArray([]string{"go", "get", "github.com/wailsapp/wails/cmd/wails@" + desiredVersion}, homeDir)
+ if err != nil {
+ updateSpinner.Error(err.Error())
+ return err
+ }
+ updateSpinner.Success()
+ fmt.Println()
+ logger.Green("Wails updated to " + desiredVersion)
+
+ return nil
+}
diff --git a/cmd/wails/9_issue.go b/cmd/wails/9_issue.go
new file mode 100644
index 000000000..a58b2938d
--- /dev/null
+++ b/cmd/wails/9_issue.go
@@ -0,0 +1,127 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "runtime"
+ "strings"
+
+ "github.com/pkg/browser"
+
+ "github.com/wailsapp/wails/cmd"
+)
+
+func init() {
+
+ commandDescription := `Generates an issue in Github using the given title, description and system report.`
+
+ initCommand := app.Command("issue", "Generates an issue in Github").
+ LongDescription(commandDescription)
+
+ initCommand.Action(func() error {
+
+ logger.PrintSmallBanner("Generate Issue")
+ fmt.Println()
+ message := `Thanks for taking the time to submit an issue!
+
+To help you in this process, we will ask for some information, add Go/Wails details automatically, then prepare the issue for your editing and submission.
+`
+
+ logger.Yellow(message)
+
+ title := cmd.Prompt("Issue Title")
+ description := cmd.Prompt("Issue Description")
+
+ var str strings.Builder
+
+ gomodule, exists := os.LookupEnv("GO111MODULE")
+ if !exists {
+ gomodule = "(Not Set)"
+ }
+
+ // get version numbers for GCC, node & npm
+ program := cmd.NewProgramHelper()
+ // string helpers
+ var gccVersion, nodeVersion, npmVersion string
+
+ // choose between OS (mac,linux,win)
+ switch runtime.GOOS {
+ case "darwin":
+ gcc := program.FindProgram("gcc")
+ if gcc != nil {
+ stdout, _, _, _ := gcc.Run("-dumpversion")
+ gccVersion = strings.TrimSpace(stdout)
+ }
+ case "linux":
+ // for linux we have to collect
+ // the distribution name
+ distroInfo := cmd.GetLinuxDistroInfo()
+ linuxDB := cmd.NewLinuxDB()
+ distro := linuxDB.GetDistro(distroInfo.ID)
+ release := distro.GetRelease(distroInfo.Release)
+ gccVersionCommand := release.GccVersionCommand
+
+ gcc := program.FindProgram("gcc")
+ if gcc != nil {
+ stdout, _, _, _ := gcc.Run(gccVersionCommand)
+ gccVersion = strings.TrimSpace(stdout)
+ }
+ case "windows":
+ gcc := program.FindProgram("gcc")
+ if gcc != nil {
+ stdout, _, _, _ := gcc.Run("-dumpversion")
+ gccVersion = strings.TrimSpace(stdout)
+ }
+ }
+
+ npm := program.FindProgram("npm")
+ if npm != nil {
+ stdout, _, _, _ := npm.Run("--version")
+ npmVersion = stdout
+ npmVersion = npmVersion[:len(npmVersion)-1]
+ npmVersion = strings.TrimSpace(npmVersion)
+ }
+
+ node := program.FindProgram("node")
+ if node != nil {
+ stdout, _, _, _ := node.Run("--version")
+ nodeVersion = stdout
+ nodeVersion = nodeVersion[:len(nodeVersion)-1]
+ }
+
+ str.WriteString("\n| Name | Value |\n| ----- | ----- |\n")
+ str.WriteString(fmt.Sprintf("| Wails Version | %s |\n", cmd.Version))
+ str.WriteString(fmt.Sprintf("| Go Version | %s |\n", runtime.Version()))
+ str.WriteString(fmt.Sprintf("| Platform | %s |\n", runtime.GOOS))
+ str.WriteString(fmt.Sprintf("| Arch | %s |\n", runtime.GOARCH))
+ str.WriteString(fmt.Sprintf("| GO111MODULE | %s |\n", gomodule))
+ str.WriteString(fmt.Sprintf("| GCC | %s |\n", gccVersion))
+ str.WriteString(fmt.Sprintf("| Npm | %s |\n", npmVersion))
+ str.WriteString(fmt.Sprintf("| Node | %s |\n", nodeVersion))
+
+ fmt.Println()
+ fmt.Println("Processing template and preparing for upload.")
+
+ // Grab issue template
+ resp, err := http.Get("https://raw.githubusercontent.com/wailsapp/wails/master/.github/ISSUE_TEMPLATE/bug_report.md")
+ if err != nil {
+ logger.Red("Unable to read in issue template. Are you online?")
+ os.Exit(1)
+ }
+ defer resp.Body.Close()
+ template, _ := ioutil.ReadAll(resp.Body)
+ body := string(template)
+ body = "**Description**\n" + (strings.Split(body, "**Description**")[1])
+ fullURL := "https://github.com/wailsapp/wails/issues/new?"
+ body = strings.Replace(body, "A clear and concise description of what the bug is.", description, -1)
+ body = strings.Replace(body, "Please provide your platform, GO version and variables, etc", str.String(), -1)
+ params := "title=" + title + "&body=" + body
+
+ fmt.Println("Opening browser to file issue.")
+ browser.OpenURL(fullURL + url.PathEscape(params))
+ return nil
+ })
+}
diff --git a/cmd/wails/main.go b/cmd/wails/main.go
new file mode 100644
index 000000000..bbcc94b32
--- /dev/null
+++ b/cmd/wails/main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "os"
+ "os/exec"
+
+ "github.com/wailsapp/wails/cmd"
+)
+
+// Create Logger
+var logger = cmd.NewLogger()
+
+// Create main app
+var app = cmd.NewCli("wails", "A cli tool for building Wails applications.")
+
+// Main!
+func main() {
+ err := app.Run()
+ if err != nil {
+ logger.Error(err.Error())
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ os.Exit(exitErr.ExitCode())
+ }
+ os.Exit(1)
+ }
+}
diff --git a/cmd/windows.go b/cmd/windows.go
new file mode 100644
index 000000000..774730f74
--- /dev/null
+++ b/cmd/windows.go
@@ -0,0 +1,19 @@
+// +build windows
+
+package cmd
+
+import (
+ "os"
+
+ "golang.org/x/sys/windows"
+)
+
+// Credit: https://stackoverflow.com/a/52579002
+
+func init() {
+ stdout := windows.Handle(os.Stdout.Fd())
+ var originalMode uint32
+
+ _ = windows.GetConsoleMode(stdout, &originalMode)
+ _ = windows.SetConsoleMode(stdout, originalMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+}
diff --git a/cmd/windres.bat b/cmd/windres.bat
new file mode 100644
index 000000000..1ce33a4a8
--- /dev/null
+++ b/cmd/windres.bat
@@ -0,0 +1 @@
+windres.exe -o %1 %2
\ No newline at end of file
diff --git a/config.go b/config.go
new file mode 100644
index 000000000..dd0c457c3
--- /dev/null
+++ b/config.go
@@ -0,0 +1,157 @@
+package wails
+
+import (
+ "net/url"
+ "strings"
+
+ "github.com/wailsapp/wails/runtime"
+)
+
+// AppConfig is the configuration structure used when creating a Wails App object
+type AppConfig struct {
+ // The width and height of your application in pixels
+ Width, Height int
+
+ // The title to put in the title bar
+ Title string
+
+ // The HTML your app should use. If you leave it blank, a default will be used:
+ //
+ HTML string
+
+ // The Javascript your app should use. Normally this should be generated by a bundler.
+ JS string
+
+ // The CSS your app should use. Normally this should be generated by a bundler.
+ CSS string
+
+ // The colour of your window. Can take "#fff", "rgb(255,255,255)", "rgba(255,255,255,1)" formats
+ Colour string
+
+ // Indicates whether your app should be resizable
+ Resizable bool
+
+ // Indicated if the devtools should be disabled
+ DisableInspector bool
+}
+
+// GetWidth returns the desired width
+func (a *AppConfig) GetWidth() int {
+ return a.Width
+}
+
+// GetHeight returns the desired height
+func (a *AppConfig) GetHeight() int {
+ return a.Height
+}
+
+// GetTitle returns the desired window title
+func (a *AppConfig) GetTitle() string {
+ return a.Title
+}
+
+// GetHTML returns the default HTML
+func (a *AppConfig) GetHTML() string {
+ if len(a.HTML) > 0 {
+ a.HTML = url.QueryEscape(a.HTML)
+ a.HTML = "data:text/html," + strings.ReplaceAll(a.HTML, "+", "%20")
+ a.HTML = strings.ReplaceAll(a.HTML, "%3D", "=")
+ }
+ return a.HTML
+}
+
+// GetResizable returns true if the window should be resizable
+func (a *AppConfig) GetResizable() bool {
+ return a.Resizable
+}
+
+// GetDisableInspector returns true if the inspector should be disabled
+func (a *AppConfig) GetDisableInspector() bool {
+ return a.DisableInspector
+}
+
+// GetColour returns the colour
+func (a *AppConfig) GetColour() string {
+ return a.Colour
+}
+
+// GetCSS returns the user CSS
+func (a *AppConfig) GetCSS() string {
+ return a.CSS
+}
+
+// GetJS returns the user Javascript
+func (a *AppConfig) GetJS() string {
+ return a.JS
+}
+
+func (a *AppConfig) merge(in *AppConfig) error {
+ if in.CSS != "" {
+ a.CSS = in.CSS
+ }
+ if in.Title != "" {
+ a.Title = runtime.ProcessEncoding(in.Title)
+ }
+
+ if in.Colour != "" {
+ a.Colour = in.Colour
+ }
+
+ if in.HTML != "" {
+ a.HTML = in.HTML
+ }
+
+ if in.JS != "" {
+ a.JS = in.JS
+ }
+
+ if in.HTML != "" {
+ a.HTML = in.HTML
+ }
+
+ if in.Width != 0 {
+ a.Width = in.Width
+ }
+ if in.Height != 0 {
+ a.Height = in.Height
+ }
+ a.Resizable = in.Resizable
+ a.DisableInspector = in.DisableInspector
+
+ return nil
+}
+
+// Creates the default configuration
+func newConfig(userConfig *AppConfig) (*AppConfig, error) {
+ result := &AppConfig{
+ Width: 800,
+ Height: 600,
+ Resizable: true,
+ Title: "My Wails App",
+ Colour: "#FFF", // White by default
+ HTML: defaultHTML,
+ }
+
+ if userConfig != nil {
+ err := result.merge(userConfig)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return result, nil
+}
+
+var defaultHTML = `
+
+
+
+
+
+
+
+
+
+
+
+`
diff --git a/go.mod b/go.mod
new file mode 100644
index 000000000..f7ac15a09
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,31 @@
+module github.com/wailsapp/wails
+
+require (
+ github.com/Masterminds/semver v1.4.2
+ github.com/abadojack/whatlanggo v1.0.1
+ github.com/fatih/color v1.7.0
+ github.com/go-playground/colors v1.2.0
+ github.com/gorilla/websocket v1.4.0
+ github.com/jackmordaunt/icns v1.0.0
+ github.com/kennygrant/sanitize v1.2.4
+ github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
+ github.com/leaanthony/mewn v0.10.7
+ github.com/leaanthony/slicer v1.4.0
+ github.com/leaanthony/spinner v0.5.3
+ github.com/mattn/go-colorable v0.1.1 // indirect
+ github.com/mattn/go-isatty v0.0.7 // indirect
+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
+ github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
+ github.com/pkg/errors v0.8.1 // indirect
+ github.com/sirupsen/logrus v1.4.1
+ github.com/stretchr/testify v1.3.0 // indirect
+ github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
+ golang.org/x/image v0.0.0-20200430140353-33d19683fad8
+ golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
+ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
+ golang.org/x/text v0.3.0
+ gopkg.in/AlecAivazis/survey.v1 v1.8.4
+ gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
+)
+
+go 1.13
diff --git a/go.sum b/go.sum
new file mode 100644
index 000000000..1fd5178da
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,93 @@
+github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
+github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
+github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
+github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/go-playground/colors v1.2.0 h1:0EdjTXKrr2g1L/LQTYtIqabeHpZuGZz1U4osS1T8+5M=
+github.com/go-playground/colors v1.2.0/go.mod h1:miw1R2JIE19cclPxsXqNdzLZsk4DP4iF+m88bRc7kfM=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
+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/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/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
+github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/leaanthony/mewn v0.10.7 h1:jCcNJyIUOpwj+I5SuATvCugDjHkoo+j6ubEOxxrxmPA=
+github.com/leaanthony/mewn v0.10.7/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ=
+github.com/leaanthony/slicer v1.4.0 h1:Q9u4w+UBU4WHjXnEDdz+eRLMKF/rnyosRBiqULnc1J8=
+github.com/leaanthony/slicer v1.4.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
+github.com/leaanthony/spinner v0.5.3 h1:IMTvgdQCec5QA4qRy0wil4XsRP+QcG1OwLWVK/LPZ5Y=
+github.com/leaanthony/spinner v0.5.3/go.mod h1:oHlrvWicr++CVV7ALWYi+qHk/XNA91D9IJ48IqmpVUo=
+github.com/leaanthony/synx v0.1.0 h1:R0lmg2w6VMb8XcotOwAe5DLyzwjLrskNkwU7LLWsyL8=
+github.com/leaanthony/synx v0.1.0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs=
+github.com/leaanthony/wincursor v0.1.0 h1:Dsyp68QcF5cCs65AMBmxoYNEm0n8K7mMchG6a8fYxf8=
+github.com/leaanthony/wincursor v0.1.0/go.mod h1:7TVwwrzSH/2Y9gLOGH+VhA+bZhoWXBRgbGNTMk+yimE=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+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/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
+github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.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/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
+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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba h1:2DHfQOxcpWdGf5q5IzCUFPNvRX9Icf+09RvQK2VnJq0=
+github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
+golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/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-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/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-20190222072716-a9d3bda3a223/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-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
+golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=
+gopkg.in/AlecAivazis/survey.v1 v1.8.4/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22 h1:0efs3hwEZhFKsCoP8l6dDB1AZWMgnEl3yWXWRZTOaEA=
+gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/jetbrains-grayscale.png b/jetbrains-grayscale.png
new file mode 100644
index 000000000..c5bf0ab63
Binary files /dev/null and b/jetbrains-grayscale.png differ
diff --git a/lib/binding/function.go b/lib/binding/function.go
new file mode 100644
index 000000000..b7f654801
--- /dev/null
+++ b/lib/binding/function.go
@@ -0,0 +1,165 @@
+package binding
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "runtime"
+
+ "github.com/wailsapp/wails/lib/logger"
+)
+
+type boundFunction struct {
+ fullName string
+ function reflect.Value
+ functionType reflect.Type
+ inputs []reflect.Type
+ returnTypes []reflect.Type
+ log *logger.CustomLogger
+ hasErrorReturnType bool
+}
+
+// Creates a new bound function based on the given method + type
+func newBoundFunction(object interface{}) (*boundFunction, error) {
+
+ objectValue := reflect.ValueOf(object)
+ objectType := reflect.TypeOf(object)
+
+ name := runtime.FuncForPC(objectValue.Pointer()).Name()
+
+ result := &boundFunction{
+ fullName: name,
+ function: objectValue,
+ functionType: objectType,
+ log: logger.NewCustomLogger(name),
+ }
+
+ err := result.processParameters()
+
+ return result, err
+}
+
+func (b *boundFunction) processParameters() error {
+
+ // Param processing
+ functionType := b.functionType
+
+ // Input parameters
+ inputParamCount := functionType.NumIn()
+ if inputParamCount > 0 {
+ b.inputs = make([]reflect.Type, inputParamCount)
+ // We start at 1 as the first param is the struct
+ for index := 0; index < inputParamCount; index++ {
+ param := functionType.In(index)
+ name := param.Name()
+ kind := param.Kind()
+ b.inputs[index] = param
+ typ := param
+ index := index
+ b.log.DebugFields("Input param", logger.Fields{
+ "index": index,
+ "name": name,
+ "kind": kind,
+ "typ": typ,
+ })
+ }
+ }
+
+ // Process return/output declarations
+ returnParamsCount := functionType.NumOut()
+ // Guard against bad number of return types
+ switch returnParamsCount {
+ case 0:
+ case 1:
+ // Check if it's an error type
+ param := functionType.Out(0)
+ paramName := param.Name()
+ if paramName == "error" {
+ b.hasErrorReturnType = true
+ }
+ // Save return type
+ b.returnTypes = append(b.returnTypes, param)
+ case 2:
+ // Check the second return type is an error
+ secondParam := functionType.Out(1)
+ secondParamName := secondParam.Name()
+ if secondParamName != "error" {
+ return fmt.Errorf("last return type of method '%s' must be an error (got %s)", b.fullName, secondParamName)
+ }
+
+ // Check the second return type is an error
+ firstParam := functionType.Out(0)
+ firstParamName := firstParam.Name()
+ if firstParamName == "error" {
+ return fmt.Errorf("first return type of method '%s' must not be an error", b.fullName)
+ }
+ b.hasErrorReturnType = true
+
+ // Save return types
+ b.returnTypes = append(b.returnTypes, firstParam)
+ b.returnTypes = append(b.returnTypes, secondParam)
+
+ default:
+ return fmt.Errorf("cannot register method '%s' with %d return parameters. Please use up to 2", b.fullName, returnParamsCount)
+ }
+
+ return nil
+}
+
+// call the method with the given data
+func (b *boundFunction) call(data string) ([]reflect.Value, error) {
+
+ // The data will be an array of values so we will decode the
+ // input data into
+ var jsArgs []interface{}
+ d := json.NewDecoder(bytes.NewBufferString(data))
+ // d.UseNumber()
+ err := d.Decode(&jsArgs)
+ if err != nil {
+ return nil, fmt.Errorf("Invalid data passed to method call: %s", err.Error())
+ }
+
+ // Check correct number of inputs
+ if len(jsArgs) != len(b.inputs) {
+ return nil, fmt.Errorf("Invalid number of parameters given to %s. Expected %d but got %d", b.fullName, len(b.inputs), len(jsArgs))
+ }
+
+ // Set up call
+ args := make([]reflect.Value, len(b.inputs))
+ for index := 0; index < len(b.inputs); index++ {
+
+ // Set the input values
+ value, err := b.setInputValue(index, b.inputs[index], jsArgs[index])
+ if err != nil {
+ return nil, err
+ }
+ args[index] = value
+ }
+ b.log.Debugf("Unmarshalled Args: %+v\n", jsArgs)
+ b.log.Debugf("Converted Args: %+v\n", args)
+ results := b.function.Call(args)
+
+ b.log.Debugf("results = %+v", results)
+ return results, nil
+}
+
+// Attempts to set the method input for parameter with the given value
+func (b *boundFunction) setInputValue(index int, typ reflect.Type, val interface{}) (result reflect.Value, err error) {
+
+ // Catch type conversion panics thrown by convert
+ defer func() {
+ if r := recover(); r != nil {
+ // Modify error
+ err = fmt.Errorf("%s for parameter %d of function %s", r.(string)[23:], index+1, b.fullName)
+ }
+ }()
+
+ // Translate javascript null values
+ if val == nil {
+ result = reflect.Zero(typ)
+ } else {
+ result = reflect.ValueOf(val).Convert(typ)
+ }
+ return result, err
+}
diff --git a/lib/binding/internal.go b/lib/binding/internal.go
new file mode 100644
index 000000000..ef6cf4341
--- /dev/null
+++ b/lib/binding/internal.go
@@ -0,0 +1,71 @@
+package binding
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/messages"
+ "github.com/wailsapp/wails/runtime"
+)
+
+type internalMethods struct {
+ log *logger.CustomLogger
+ browser *runtime.Browser
+}
+
+func newInternalMethods() *internalMethods {
+ return &internalMethods{
+ log: logger.NewCustomLogger("InternalCall"),
+ browser: runtime.NewBrowser(),
+ }
+}
+
+func (i *internalMethods) processCall(callData *messages.CallData) (interface{}, error) {
+ if !strings.HasPrefix(callData.BindingName, ".wails.") {
+ return nil, fmt.Errorf("Invalid call signature '%s'", callData.BindingName)
+ }
+
+ // Strip prefix
+ var splitCall = strings.Split(callData.BindingName, ".")[2:]
+ if len(splitCall) != 2 {
+ return nil, fmt.Errorf("Invalid call signature '%s'", callData.BindingName)
+ }
+
+ group := splitCall[0]
+ switch group {
+ case "Browser":
+ return i.processBrowserCommand(splitCall[1], callData.Data)
+ default:
+ return nil, fmt.Errorf("Unknown internal command group '%s'", group)
+ }
+}
+
+func (i *internalMethods) processBrowserCommand(command string, data interface{}) (interface{}, error) {
+ switch command {
+ case "OpenURL":
+ url := data.(string)
+ // Strip string quotes. Credit: https://stackoverflow.com/a/44222648
+ if url[0] == '"' {
+ url = url[1:]
+ }
+ if i := len(url) - 1; url[i] == '"' {
+ url = url[:i]
+ }
+ i.log.Debugf("Calling Browser.OpenURL with '%s'", url)
+ return nil, i.browser.OpenURL(url)
+ case "OpenFile":
+ filename := data.(string)
+ // Strip string quotes. Credit: https://stackoverflow.com/a/44222648
+ if filename[0] == '"' {
+ filename = filename[1:]
+ }
+ if i := len(filename) - 1; filename[i] == '"' {
+ filename = filename[:i]
+ }
+ i.log.Debugf("Calling Browser.OpenFile with '%s'", filename)
+ return nil, i.browser.OpenFile(filename)
+ default:
+ return nil, fmt.Errorf("Unknown Browser command '%s'", command)
+ }
+}
diff --git a/lib/binding/manager.go b/lib/binding/manager.go
new file mode 100644
index 000000000..34e79055c
--- /dev/null
+++ b/lib/binding/manager.go
@@ -0,0 +1,371 @@
+package binding
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "unicode"
+
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+var typescriptDefinitionFilename = ""
+
+// Manager handles method binding
+type Manager struct {
+ methods map[string]*boundMethod
+ functions map[string]*boundFunction
+ internalMethods *internalMethods
+ initMethods []*boundMethod
+ shutdownMethods []*boundMethod
+ log *logger.CustomLogger
+ renderer interfaces.Renderer
+ runtime interfaces.Runtime // The runtime object to pass to bound structs
+ objectsToBind []interface{}
+ bindPackageNames bool // Package name should be considered when binding
+ structList map[string][]string // structList["mystruct"] = []string{"Method1", "Method2"}
+}
+
+// NewManager creates a new Manager struct
+func NewManager() interfaces.BindingManager {
+
+ result := &Manager{
+ methods: make(map[string]*boundMethod),
+ functions: make(map[string]*boundFunction),
+ log: logger.NewCustomLogger("Bind"),
+ internalMethods: newInternalMethods(),
+ structList: make(map[string][]string),
+ }
+ return result
+}
+
+// BindPackageNames sets a flag to indicate package names should be considered when binding
+func (b *Manager) BindPackageNames() {
+ b.bindPackageNames = true
+}
+
+// Start the binding manager
+func (b *Manager) Start(renderer interfaces.Renderer, runtime interfaces.Runtime) error {
+ b.log.Info("Starting")
+ b.renderer = renderer
+ b.runtime = runtime
+ err := b.initialise()
+ if err != nil {
+ b.log.Errorf("Binding error: %s", err.Error())
+ return err
+ }
+ err = b.callWailsInitMethods()
+ return err
+}
+
+func (b *Manager) initialise() error {
+
+ var err error
+ // var binding *boundMethod
+
+ b.log.Info("Binding Go Functions/Methods")
+
+ // Create bindings for objects
+ for _, object := range b.objectsToBind {
+
+ // Safeguard against nils
+ if object == nil {
+ return fmt.Errorf("attempted to bind nil object")
+ }
+
+ // Determine kind of object
+ objectType := reflect.TypeOf(object)
+ objectKind := objectType.Kind()
+
+ switch objectKind {
+ case reflect.Ptr:
+ err = b.bindMethod(object)
+ case reflect.Func:
+ // spew.Dump(result.objectType.String())
+ err = b.bindFunction(object)
+ default:
+ err = fmt.Errorf("cannot bind object of type '%s'", objectKind.String())
+ }
+
+ // Return error if set
+ if err != nil {
+ return err
+ }
+ }
+
+ // If we wish to generate a typescript definition file...
+ if typescriptDefinitionFilename != "" {
+ err := b.generateTypescriptDefinitions()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Generate typescript
+func (b *Manager) generateTypescriptDefinitions() error {
+
+ var output strings.Builder
+
+ for structname, methodList := range b.structList {
+ structname = strings.SplitN(structname, ".", 2)[1]
+ output.WriteString(fmt.Sprintf("Interface %s {\n", structname))
+ for _, method := range methodList {
+ output.WriteString(fmt.Sprintf("\t%s: (...args : any[]) => Promise\n", method))
+ }
+ output.WriteString("}\n")
+ }
+
+ output.WriteString("\n")
+ output.WriteString("Interface Backend {\n")
+
+ for structname := range b.structList {
+ structname = strings.SplitN(structname, ".", 2)[1]
+ output.WriteString(fmt.Sprintf("\t%[1]s: %[1]s\n", structname))
+ }
+ output.WriteString("}\n")
+
+ globals := `
+declare global {
+ interface Window {
+ backend: Backend;
+ }
+}`
+ output.WriteString(globals)
+
+ b.log.Info("Written Typescript file: " + typescriptDefinitionFilename)
+
+ dir := filepath.Dir(typescriptDefinitionFilename)
+ os.MkdirAll(dir, 0755)
+ return ioutil.WriteFile(typescriptDefinitionFilename, []byte(output.String()), 0755)
+}
+
+// bind the given struct method
+func (b *Manager) bindMethod(object interface{}) error {
+
+ objectType := reflect.TypeOf(object)
+ baseName := objectType.String()
+
+ // Strip pointer if there
+ if baseName[0] == '*' {
+ baseName = baseName[1:]
+ }
+
+ b.log.Debugf("Processing struct: %s", baseName)
+
+ // Calc actual name
+ actualName := strings.TrimPrefix(baseName, "main.")
+ if b.structList[actualName] == nil {
+ b.structList[actualName] = []string{}
+ }
+
+ // Iterate over method definitions
+ for i := 0; i < objectType.NumMethod(); i++ {
+
+ // Get method definition
+ methodDef := objectType.Method(i)
+ methodName := methodDef.Name
+ fullMethodName := baseName + "." + methodName
+ method := reflect.ValueOf(object).MethodByName(methodName)
+
+ b.structList[actualName] = append(b.structList[actualName], methodName)
+
+ // Skip unexported methods
+ if !unicode.IsUpper([]rune(methodName)[0]) {
+ continue
+ }
+
+ // Create a new boundMethod
+ newMethod, err := newBoundMethod(methodName, fullMethodName, method, objectType)
+ if err != nil {
+ return err
+ }
+
+ // Check if it's a wails init function
+ if newMethod.isWailsInit {
+ b.log.Debugf("Detected WailsInit function: %s", fullMethodName)
+ b.initMethods = append(b.initMethods, newMethod)
+ } else if newMethod.isWailsShutdown {
+ b.log.Debugf("Detected WailsShutdown function: %s", fullMethodName)
+ b.shutdownMethods = append(b.shutdownMethods, newMethod)
+ } else {
+ // Save boundMethod
+ b.log.Infof("Bound Method: %s()", fullMethodName)
+ b.methods[fullMethodName] = newMethod
+
+ // Inform renderer of new binding
+ b.renderer.NewBinding(fullMethodName)
+ }
+ }
+
+ return nil
+}
+
+// bind the given function object
+func (b *Manager) bindFunction(object interface{}) error {
+
+ newFunction, err := newBoundFunction(object)
+ if err != nil {
+ return err
+ }
+
+ // Save method
+ b.log.Infof("Bound Function: %s()", newFunction.fullName)
+ b.functions[newFunction.fullName] = newFunction
+
+ // Register with Renderer
+ b.renderer.NewBinding(newFunction.fullName)
+
+ return nil
+}
+
+// Bind saves the given object to be bound at start time
+func (b *Manager) Bind(object interface{}) {
+ // Store binding
+ b.objectsToBind = append(b.objectsToBind, object)
+}
+
+func (b *Manager) processInternalCall(callData *messages.CallData) (interface{}, error) {
+ // Strip prefix
+ return b.internalMethods.processCall(callData)
+}
+
+func (b *Manager) processFunctionCall(callData *messages.CallData) (interface{}, error) {
+ // Return values
+ var result []reflect.Value
+ var err error
+
+ function := b.functions[callData.BindingName]
+ if function == nil {
+ return nil, fmt.Errorf("Invalid function name '%s'", callData.BindingName)
+ }
+ result, err = function.call(callData.Data)
+ if err != nil {
+ return nil, err
+ }
+
+ // Do we have an error return type?
+ if function.hasErrorReturnType {
+ // We do - last result is an error type
+ // Check if the last result was nil
+ b.log.Debugf("# of return types: %d", len(function.returnTypes))
+ b.log.Debugf("# of results: %d", len(result))
+ errorResult := result[len(function.returnTypes)-1]
+ if !errorResult.IsNil() {
+ // It wasn't - we have an error
+ return nil, errorResult.Interface().(error)
+ }
+ }
+ // fmt.Printf("result = '%+v'\n", result)
+ if len(result) > 0 {
+ return result[0].Interface(), nil
+ }
+ return nil, nil
+}
+
+func (b *Manager) processMethodCall(callData *messages.CallData) (interface{}, error) {
+ // Return values
+ var result []reflect.Value
+ var err error
+
+ // do we have this method?
+ method := b.methods[callData.BindingName]
+ if method == nil {
+ return nil, fmt.Errorf("Invalid method name '%s'", callData.BindingName)
+ }
+
+ result, err = method.call(callData.Data)
+ if err != nil {
+ return nil, err
+ }
+
+ // Do we have an error return type?
+ if method.hasErrorReturnType {
+ // We do - last result is an error type
+ // Check if the last result was nil
+ b.log.Debugf("# of return types: %d", len(method.returnTypes))
+ b.log.Debugf("# of results: %d", len(result))
+ errorResult := result[len(method.returnTypes)-1]
+ if !errorResult.IsNil() {
+ // It wasn't - we have an error
+ return nil, errorResult.Interface().(error)
+ }
+ }
+ if result != nil {
+ return result[0].Interface(), nil
+ }
+ return nil, nil
+}
+
+// ProcessCall processes the given call request
+func (b *Manager) ProcessCall(callData *messages.CallData) (result interface{}, err error) {
+ b.log.Debugf("Wanting to call %s", callData.BindingName)
+
+ // Determine if this is function call or method call by the number of
+ // dots in the binding name
+ dotCount := 0
+ for _, character := range callData.BindingName {
+ if character == '.' {
+ dotCount++
+ }
+ }
+
+ // We need to catch reflect related panics and return
+ // a decent error message
+ // TODO: DEBUG THIS!
+
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("%s", r.(string))
+ }
+ }()
+
+ switch dotCount {
+ case 1:
+ result, err = b.processFunctionCall(callData)
+ case 2:
+ result, err = b.processMethodCall(callData)
+ case 3:
+ result, err = b.processInternalCall(callData)
+ default:
+ result = nil
+ err = fmt.Errorf("Invalid binding name '%s'", callData.BindingName)
+ }
+ return
+}
+
+// callWailsInitMethods calls all of the WailsInit methods that were
+// registered with the runtime object
+func (b *Manager) callWailsInitMethods() error {
+ // Create reflect value for runtime object
+ runtimeValue := reflect.ValueOf(b.runtime)
+ params := []reflect.Value{runtimeValue}
+
+ // Iterate initMethods
+ for _, initMethod := range b.initMethods {
+ // Call
+ result := initMethod.method.Call(params)
+ // Check errors
+ err := result[0].Interface()
+ if err != nil {
+ return err.(error)
+ }
+ }
+ return nil
+}
+
+// Shutdown the binding manager
+func (b *Manager) Shutdown() {
+ b.log.Debug("Shutdown called")
+ for _, method := range b.shutdownMethods {
+ b.log.Debugf("Calling Shutdown for method: %s", method.fullName)
+ method.call("[]")
+ }
+ b.log.Debug("Shutdown complete")
+}
diff --git a/lib/binding/method.go b/lib/binding/method.go
new file mode 100644
index 000000000..9a0a32603
--- /dev/null
+++ b/lib/binding/method.go
@@ -0,0 +1,236 @@
+package binding
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "reflect"
+
+ "github.com/wailsapp/wails/lib/logger"
+)
+
+type boundMethod struct {
+ Name string
+ fullName string
+ method reflect.Value
+ inputs []reflect.Type
+ returnTypes []reflect.Type
+ log *logger.CustomLogger
+ hasErrorReturnType bool // Indicates if there is an error return type
+ isWailsInit bool
+ isWailsShutdown bool
+}
+
+// Creates a new bound method based on the given method + type
+func newBoundMethod(name string, fullName string, method reflect.Value, objectType reflect.Type) (*boundMethod, error) {
+ result := &boundMethod{
+ Name: name,
+ method: method,
+ fullName: fullName,
+ }
+
+ // Setup logger
+ result.log = logger.NewCustomLogger(result.fullName)
+
+ // Check if Parameters are valid
+ err := result.processParameters()
+
+ // Are we a WailsInit method?
+ if result.Name == "WailsInit" {
+ err = result.processWailsInit()
+ }
+
+ // Are we a WailsShutdown method?
+ if result.Name == "WailsShutdown" {
+ err = result.processWailsShutdown()
+ }
+
+ return result, err
+}
+
+func (b *boundMethod) processParameters() error {
+
+ // Param processing
+ methodType := b.method.Type()
+
+ // Input parameters
+ inputParamCount := methodType.NumIn()
+ if inputParamCount > 0 {
+ b.inputs = make([]reflect.Type, inputParamCount)
+ // We start at 1 as the first param is the struct
+ for index := 0; index < inputParamCount; index++ {
+ param := methodType.In(index)
+ name := param.Name()
+ kind := param.Kind()
+ b.inputs[index] = param
+ typ := param
+ index := index
+ b.log.DebugFields("Input param", logger.Fields{
+ "index": index,
+ "name": name,
+ "kind": kind,
+ "typ": typ,
+ })
+ }
+ }
+
+ // Process return/output declarations
+ returnParamsCount := methodType.NumOut()
+ // Guard against bad number of return types
+ switch returnParamsCount {
+ case 0:
+ case 1:
+ // Check if it's an error type
+ param := methodType.Out(0)
+ paramName := param.Name()
+ if paramName == "error" {
+ b.hasErrorReturnType = true
+ }
+ // Save return type
+ b.returnTypes = append(b.returnTypes, param)
+ case 2:
+ // Check the second return type is an error
+ secondParam := methodType.Out(1)
+ secondParamName := secondParam.Name()
+ if secondParamName != "error" {
+ return fmt.Errorf("last return type of method '%s' must be an error (got %s)", b.Name, secondParamName)
+ }
+
+ // Check the second return type is an error
+ firstParam := methodType.Out(0)
+ firstParamName := firstParam.Name()
+ if firstParamName == "error" {
+ return fmt.Errorf("first return type of method '%s' must not be an error", b.Name)
+ }
+ b.hasErrorReturnType = true
+
+ // Save return types
+ b.returnTypes = append(b.returnTypes, firstParam)
+ b.returnTypes = append(b.returnTypes, secondParam)
+
+ default:
+ return fmt.Errorf("cannot register method '%s' with %d return parameters. Please use up to 2", b.Name, returnParamsCount)
+ }
+
+ return nil
+}
+
+// call the method with the given data
+func (b *boundMethod) call(data string) ([]reflect.Value, error) {
+
+ // The data will be an array of values so we will decode the
+ // input data into
+ var jsArgs []interface{}
+ d := json.NewDecoder(bytes.NewBufferString(data))
+ // d.UseNumber()
+ err := d.Decode(&jsArgs)
+ if err != nil {
+ return nil, fmt.Errorf("Invalid data passed to method call: %s", err.Error())
+ }
+
+ // Check correct number of inputs
+ if len(jsArgs) != len(b.inputs) {
+ return nil, fmt.Errorf("Invalid number of parameters given to %s. Expected %d but got %d", b.fullName, len(b.inputs), len(jsArgs))
+ }
+
+ // Set up call
+ args := make([]reflect.Value, len(b.inputs))
+ for index := 0; index < len(b.inputs); index++ {
+
+ // Set the input values
+ value, err := b.setInputValue(index, b.inputs[index], jsArgs[index])
+ if err != nil {
+ return nil, err
+ }
+ args[index] = value
+ }
+ b.log.Debugf("Unmarshalled Args: %+v\n", jsArgs)
+ b.log.Debugf("Converted Args: %+v\n", args)
+ results := b.method.Call(args)
+
+ b.log.Debugf("results = %+v", results)
+ return results, nil
+}
+
+// Attempts to set the method input for parameter with the given value
+func (b *boundMethod) setInputValue(index int, typ reflect.Type, val interface{}) (result reflect.Value, err error) {
+
+ // Catch type conversion panics thrown by convert
+ defer func() {
+ if r := recover(); r != nil {
+ // Modify error
+ fmt.Printf("Recovery message: %+v\n", r)
+ err = fmt.Errorf("%s for parameter %d of method %s", r.(string)[23:], index+1, b.fullName)
+ }
+ }()
+
+ // Do the conversion
+ // Handle nil values
+ if val == nil {
+ switch typ.Kind() {
+ case reflect.Chan,
+ reflect.Func,
+ reflect.Interface,
+ reflect.Map,
+ reflect.Ptr,
+ reflect.Slice:
+ b.log.Debug("Converting nil to type")
+ result = reflect.ValueOf(val).Convert(typ)
+ default:
+ b.log.Debug("Cannot convert nil to type, returning error")
+ return reflect.Zero(typ), fmt.Errorf("Unable to use null value for parameter %d of method %s", index+1, b.fullName)
+ }
+ } else {
+ result = reflect.ValueOf(val).Convert(typ)
+ }
+
+ return result, err
+}
+
+func (b *boundMethod) processWailsInit() error {
+ // We must have only 1 input, it must be *wails.Runtime
+ if len(b.inputs) != 1 {
+ return fmt.Errorf("Invalid WailsInit() definition. Expected 1 input, but got %d", len(b.inputs))
+ }
+
+ // It must be *wails.Runtime
+ inputName := b.inputs[0].String()
+ b.log.Debugf("WailsInit input type: %s", inputName)
+ if inputName != "*runtime.Runtime" {
+ return fmt.Errorf("Invalid WailsInit() definition. Expected input to be wails.Runtime, but got %s", inputName)
+ }
+
+ // We must have only 1 output, it must be error
+ if len(b.returnTypes) != 1 {
+ return fmt.Errorf("Invalid WailsInit() definition. Expected 1 return type, but got %d", len(b.returnTypes))
+ }
+
+ // It must be *wails.Runtime
+ outputName := b.returnTypes[0].String()
+ b.log.Debugf("WailsInit output type: %s", outputName)
+ if outputName != "error" {
+ return fmt.Errorf("Invalid WailsInit() definition. Expected input to be error, but got %s", outputName)
+ }
+
+ // We are indeed a wails Init method
+ b.isWailsInit = true
+
+ return nil
+}
+
+func (b *boundMethod) processWailsShutdown() error {
+ // We must not have any inputs
+ if len(b.inputs) != 0 {
+ return fmt.Errorf("Invalid WailsShutdown() definition. Expected 0 inputs, but got %d", len(b.inputs))
+ }
+
+ // We must have only 1 output, it must be error
+ if len(b.returnTypes) != 0 {
+ return fmt.Errorf("Invalid WailsShutdown() definition. Expected 0 return types, but got %d", len(b.returnTypes))
+ }
+
+ // We are indeed a wails Shutdown method
+ b.isWailsShutdown = true
+
+ return nil
+}
diff --git a/lib/event/manager.go b/lib/event/manager.go
new file mode 100644
index 000000000..6020fd349
--- /dev/null
+++ b/lib/event/manager.go
@@ -0,0 +1,159 @@
+package event
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+// Manager handles and processes events
+type Manager struct {
+ incomingEvents chan *messages.EventData
+ quitChannel chan struct{}
+ listeners map[string][]*eventListener
+ running bool
+ log *logger.CustomLogger
+ renderer interfaces.Renderer // Messages will be dispatched to the frontend
+ wg sync.WaitGroup
+}
+
+// NewManager creates a new event manager with a 100 event buffer
+func NewManager() interfaces.EventManager {
+ return &Manager{
+ incomingEvents: make(chan *messages.EventData, 100),
+ quitChannel: make(chan struct{}, 1),
+ listeners: make(map[string][]*eventListener),
+ running: false,
+ log: logger.NewCustomLogger("Events"),
+ }
+}
+
+// PushEvent places the given event on to the event queue
+func (e *Manager) PushEvent(eventData *messages.EventData) {
+ e.incomingEvents <- eventData
+}
+
+// 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(...interface{}) // Function to call with emitted event data
+ counter int // Expire after counter callbacks. 0 = infinite
+ expired bool // Indicates if the listener has expired
+}
+
+// Creates a new event listener from the given callback function
+func (e *Manager) addEventListener(eventName string, callback func(...interface{}), counter int) error {
+
+ // Sanity check inputs
+ if callback == nil {
+ return fmt.Errorf("nil callback bassed to addEventListener")
+ }
+
+ // Check event has been registered before
+ if e.listeners[eventName] == nil {
+ e.listeners[eventName] = []*eventListener{}
+ }
+
+ // Create the callback
+ listener := &eventListener{
+ callback: callback,
+ counter: counter,
+ }
+
+ // Register listener
+ e.listeners[eventName] = append(e.listeners[eventName], listener)
+
+ // All good mate
+ return nil
+}
+
+// On adds a listener for the given event
+func (e *Manager) On(eventName string, callback func(...interface{})) {
+ // Add a persistent eventListener (counter = 0)
+ e.addEventListener(eventName, callback, 0)
+}
+
+// Emit broadcasts the given event to the subscribed listeners
+func (e *Manager) Emit(eventName string, optionalData ...interface{}) {
+ e.incomingEvents <- &messages.EventData{Name: eventName, Data: optionalData}
+}
+
+// Start the event manager's queue processing
+func (e *Manager) Start(renderer interfaces.Renderer) {
+
+ e.log.Info("Starting")
+
+ // Store renderer
+ e.renderer = renderer
+
+ // Set up waitgroup so we can wait for goroutine to quit
+ e.running = true
+ e.wg.Add(1)
+
+ // Run main loop in separate goroutine
+ go func() {
+ e.log.Info("Listening")
+ for e.running {
+ // TODO: Listen for application exit
+ select {
+ case event := <-e.incomingEvents:
+ e.log.DebugFields("Got Event", logger.Fields{
+ "data": event.Data,
+ "name": event.Name,
+ })
+
+ // Notify renderer
+ e.renderer.NotifyEvent(event)
+
+ // Notify Go listeners
+ var listenersToRemove []*eventListener
+
+ // Iterate listeners
+ for _, listener := range e.listeners[event.Name] {
+
+ // Call listener, perhaps with data
+ if event.Data == nil {
+ go listener.callback()
+ } else {
+ unpacked := event.Data.([]interface{})
+ go listener.callback(unpacked...)
+ }
+
+ // Update listen counter
+ if listener.counter > 0 {
+ listener.counter = listener.counter - 1
+ if listener.counter == 0 {
+ listener.expired = true
+ }
+ }
+ }
+
+ // Remove expired listeners in place
+ if len(listenersToRemove) > 0 {
+ listeners := e.listeners[event.Name][:0]
+ for _, listener := range listeners {
+ if !listener.expired {
+ listeners = append(listeners, listener)
+ }
+ }
+ }
+ case <-e.quitChannel:
+ e.running = false
+ }
+ }
+ e.wg.Done()
+ }()
+}
+
+// Shutdown is called when exiting the Application
+func (e *Manager) Shutdown() {
+ e.log.Debug("Shutting Down")
+ e.quitChannel <- struct{}{}
+ e.log.Debug("Waiting for main loop to exit")
+ e.wg.Wait()
+}
diff --git a/lib/interfaces/appconfig.go b/lib/interfaces/appconfig.go
new file mode 100644
index 000000000..6e063b4d4
--- /dev/null
+++ b/lib/interfaces/appconfig.go
@@ -0,0 +1,14 @@
+package interfaces
+
+// AppConfig is the application config interface
+type AppConfig interface {
+ GetWidth() int
+ GetHeight() int
+ GetTitle() string
+ GetResizable() bool
+ GetHTML() string
+ GetDisableInspector() bool
+ GetColour() string
+ GetCSS() string
+ GetJS() string
+}
diff --git a/lib/interfaces/bindingmanager.go b/lib/interfaces/bindingmanager.go
new file mode 100644
index 000000000..e145d3b95
--- /dev/null
+++ b/lib/interfaces/bindingmanager.go
@@ -0,0 +1,11 @@
+package interfaces
+
+import "github.com/wailsapp/wails/lib/messages"
+
+// BindingManager is the binding manager interface
+type BindingManager interface {
+ Bind(object interface{})
+ Start(renderer Renderer, runtime Runtime) error
+ ProcessCall(callData *messages.CallData) (result interface{}, err error)
+ Shutdown()
+}
diff --git a/lib/interfaces/eventmanager.go b/lib/interfaces/eventmanager.go
new file mode 100644
index 000000000..c4f4e51fc
--- /dev/null
+++ b/lib/interfaces/eventmanager.go
@@ -0,0 +1,12 @@
+package interfaces
+
+import "github.com/wailsapp/wails/lib/messages"
+
+// EventManager is the event manager interface
+type EventManager interface {
+ PushEvent(*messages.EventData)
+ Emit(eventName string, optionalData ...interface{})
+ On(eventName string, callback func(...interface{}))
+ Start(Renderer)
+ Shutdown()
+}
diff --git a/lib/interfaces/ipcmanager.go b/lib/interfaces/ipcmanager.go
new file mode 100644
index 000000000..bc6a3cc00
--- /dev/null
+++ b/lib/interfaces/ipcmanager.go
@@ -0,0 +1,13 @@
+package interfaces
+
+// CallbackFunc defines the signature of a function required to be provided to the
+// Dispatch function so that the response may be returned
+type CallbackFunc func(string) error
+
+// IPCManager is the event manager interface
+type IPCManager interface {
+ BindRenderer(Renderer)
+ Dispatch(message string, f CallbackFunc)
+ Start(eventManager EventManager, bindingManager BindingManager)
+ Shutdown()
+}
diff --git a/lib/interfaces/renderer.go b/lib/interfaces/renderer.go
new file mode 100644
index 000000000..a0502c871
--- /dev/null
+++ b/lib/interfaces/renderer.go
@@ -0,0 +1,29 @@
+package interfaces
+
+import (
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+// Renderer is an interface describing a Wails target to render the app to
+type Renderer interface {
+ Initialise(AppConfig, IPCManager, EventManager) error
+ Run() error
+
+ // Binding
+ NewBinding(bindingName string) error
+
+ // Events
+ NotifyEvent(eventData *messages.EventData) error
+
+ // Dialog Runtime
+ SelectFile(title string, filter string) string
+ SelectDirectory() string
+ SelectSaveFile(title string, filter string) string
+
+ // Window Runtime
+ SetColour(string) error
+ Fullscreen()
+ UnFullscreen()
+ SetTitle(title string)
+ Close()
+}
diff --git a/lib/interfaces/runtime.go b/lib/interfaces/runtime.go
new file mode 100644
index 000000000..fefd2997e
--- /dev/null
+++ b/lib/interfaces/runtime.go
@@ -0,0 +1,4 @@
+package interfaces
+
+// Runtime interface
+type Runtime interface {}
\ No newline at end of file
diff --git a/lib/ipc/call.go b/lib/ipc/call.go
new file mode 100644
index 000000000..f332e7407
--- /dev/null
+++ b/lib/ipc/call.go
@@ -0,0 +1,35 @@
+package ipc
+
+import (
+ "fmt"
+
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+func init() {
+ messageProcessors["call"] = processCallData
+}
+
+func processCallData(message *ipcMessage) (*ipcMessage, error) {
+
+ var payload messages.CallData
+
+ // Decode binding call data
+ payloadMap := message.Payload.(map[string]interface{})
+
+ // Check for binding name
+ if payloadMap["bindingName"] == nil {
+ return nil, fmt.Errorf("bindingName not given in call")
+ }
+ payload.BindingName = payloadMap["bindingName"].(string)
+
+ // Check for data
+ if payloadMap["data"] != nil {
+ payload.Data = payloadMap["data"].(string)
+ }
+
+ // Reassign payload to decoded data
+ message.Payload = &payload
+
+ return message, nil
+}
diff --git a/lib/ipc/event.go b/lib/ipc/event.go
new file mode 100644
index 000000000..9dc9b1afc
--- /dev/null
+++ b/lib/ipc/event.go
@@ -0,0 +1,37 @@
+package ipc
+
+import (
+ "encoding/json"
+
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+// Register the message handler
+func init() {
+ messageProcessors["event"] = processEventData
+}
+
+// This processes the given event message
+func processEventData(message *ipcMessage) (*ipcMessage, error) {
+
+ // TODO: Is it worth double checking this is actually an event message,
+ // even though that's done by the caller?
+ var payload messages.EventData
+
+ // Decode event data
+ payloadMap := message.Payload.(map[string]interface{})
+ payload.Name = payloadMap["name"].(string)
+
+ // decode the payload data
+ var data []interface{}
+ err := json.Unmarshal([]byte(payloadMap["data"].(string)), &data)
+ if err != nil {
+ return nil, err
+ }
+ payload.Data = data
+
+ // Reassign payload to decoded data
+ message.Payload = &payload
+
+ return message, nil
+}
diff --git a/lib/ipc/log.go b/lib/ipc/log.go
new file mode 100644
index 000000000..4bf75a9a9
--- /dev/null
+++ b/lib/ipc/log.go
@@ -0,0 +1,24 @@
+package ipc
+
+import "github.com/wailsapp/wails/lib/messages"
+
+// Register the message handler
+func init() {
+ messageProcessors["log"] = processLogData
+}
+
+// This processes the given log message
+func processLogData(message *ipcMessage) (*ipcMessage, error) {
+
+ var payload messages.LogData
+
+ // Decode event data
+ payloadMap := message.Payload.(map[string]interface{})
+ payload.Level = payloadMap["level"].(string)
+ payload.Message = payloadMap["message"].(string)
+
+ // Reassign payload to decoded data
+ message.Payload = &payload
+
+ return message, nil
+}
diff --git a/lib/ipc/manager.go b/lib/ipc/manager.go
new file mode 100644
index 000000000..8cff6e3db
--- /dev/null
+++ b/lib/ipc/manager.go
@@ -0,0 +1,182 @@
+package ipc
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+// Manager manages the IPC subsystem
+type Manager struct {
+ renderer interfaces.Renderer // The renderer
+ messageQueue chan *ipcMessage
+ quitChannel chan struct{}
+ // signals chan os.Signal
+ log *logger.CustomLogger
+ eventManager interfaces.EventManager
+ bindingManager interfaces.BindingManager
+ running bool
+ wg sync.WaitGroup
+}
+
+// NewManager creates a new IPC Manager
+func NewManager() interfaces.IPCManager {
+ result := &Manager{
+ messageQueue: make(chan *ipcMessage, 100),
+ quitChannel: make(chan struct{}),
+ // signals: make(chan os.Signal, 1),
+ log: logger.NewCustomLogger("IPC"),
+ }
+ return result
+}
+
+// BindRenderer sets the renderer, returns the dispatch function
+func (i *Manager) BindRenderer(renderer interfaces.Renderer) {
+ i.renderer = renderer
+}
+
+// Start the IPC Manager
+func (i *Manager) Start(eventManager interfaces.EventManager, bindingManager interfaces.BindingManager) {
+
+ // Store manager references
+ i.eventManager = eventManager
+ i.bindingManager = bindingManager
+
+ i.log.Info("Starting")
+ // signal.Notify(manager.signals, os.Interrupt)
+ i.running = true
+
+ // Keep track of this goroutine
+ i.wg.Add(1)
+ go func() {
+ for i.running {
+ select {
+ case incomingMessage := <-i.messageQueue:
+ i.log.DebugFields("Processing message", logger.Fields{
+ "1D": &incomingMessage,
+ })
+ switch incomingMessage.Type {
+ case "call":
+ callData := incomingMessage.Payload.(*messages.CallData)
+ i.log.DebugFields("Processing call", logger.Fields{
+ "1D": &incomingMessage,
+ "bindingName": callData.BindingName,
+ "data": callData.Data,
+ })
+ go func() {
+ result, err := bindingManager.ProcessCall(callData)
+ i.log.DebugFields("processed call", logger.Fields{"result": result, "err": err})
+ if err != nil {
+ incomingMessage.ReturnError(err.Error())
+ } else {
+ incomingMessage.ReturnSuccess(result)
+ }
+ i.log.DebugFields("Finished processing call", logger.Fields{
+ "1D": &incomingMessage,
+ })
+ }()
+ case "event":
+
+ // Extract event data
+ eventData := incomingMessage.Payload.(*messages.EventData)
+
+ // Log
+ i.log.DebugFields("Processing event", logger.Fields{
+ "name": eventData.Name,
+ "data": eventData.Data,
+ })
+
+ // Push the event to the event manager
+ i.eventManager.PushEvent(eventData)
+
+ // Log
+ i.log.DebugFields("Finished processing event", logger.Fields{
+ "name": eventData.Name,
+ })
+ case "log":
+ logdata := incomingMessage.Payload.(*messages.LogData)
+ switch logdata.Level {
+ case "info":
+ logger.GlobalLogger.Info(logdata.Message)
+ case "debug":
+ logger.GlobalLogger.Debug(logdata.Message)
+ case "warning":
+ logger.GlobalLogger.Warn(logdata.Message)
+ case "error":
+ logger.GlobalLogger.Error(logdata.Message)
+ case "fatal":
+ logger.GlobalLogger.Fatal(logdata.Message)
+ default:
+ logger.ErrorFields("Invalid log level sent", logger.Fields{
+ "level": logdata.Level,
+ "message": logdata.Message,
+ })
+ }
+ default:
+ i.log.Debugf("bad message sent to MessageQueue! Unknown type: %s", incomingMessage.Type)
+ }
+
+ // Log
+ i.log.DebugFields("Finished processing message", logger.Fields{
+ "1D": &incomingMessage,
+ })
+ case <-i.quitChannel:
+ i.running = false
+ }
+ }
+ i.log.Debug("Stopping")
+ i.wg.Done()
+ }()
+}
+
+// Dispatch receives JSON encoded messages from the renderer.
+// It processes the message to ensure that it is valid and places
+// the processed message on the message queue
+func (i *Manager) Dispatch(message string, cb interfaces.CallbackFunc) {
+
+ // Create a new IPC Message
+ incomingMessage, err := newIPCMessage(message, i.SendResponse(cb))
+ if err != nil {
+ i.log.ErrorFields("Could not understand incoming message! ", map[string]interface{}{
+ "message": message,
+ "error": err,
+ })
+ return
+ }
+
+ // Put message on queue
+ i.log.DebugFields("Message received", map[string]interface{}{
+ "type": incomingMessage.Type,
+ "payload": incomingMessage.Payload,
+ })
+
+ // Put incoming message on the message queue
+ i.messageQueue <- incomingMessage
+}
+
+// SendResponse sends the given response back to the frontend
+// It sends the data back to the correct renderer by way of the provided callback function
+func (i *Manager) SendResponse(cb interfaces.CallbackFunc) func(i *ipcResponse) error {
+
+ return func(response *ipcResponse) error {
+ // Serialise the Message
+ data, err := response.Serialise()
+ if err != nil {
+ fmt.Printf(err.Error())
+ return err
+ }
+ return cb(data)
+ }
+
+}
+
+// Shutdown is called when exiting the Application
+func (i *Manager) Shutdown() {
+ i.log.Debug("Shutdown called")
+ i.quitChannel <- struct{}{}
+ i.log.Debug("Waiting of main loop shutdown")
+ i.wg.Wait()
+}
diff --git a/lib/ipc/message.go b/lib/ipc/message.go
new file mode 100644
index 000000000..8c09ab988
--- /dev/null
+++ b/lib/ipc/message.go
@@ -0,0 +1,93 @@
+package ipc
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// Message handler
+type messageProcessorFunc func(*ipcMessage) (*ipcMessage, error)
+
+var messageProcessors = make(map[string]messageProcessorFunc)
+
+// ipcMessage is the struct version of the Message sent from the frontend.
+// The payload has the specialised message data
+type ipcMessage struct {
+ Type string `json:"type"`
+ Payload interface{} `json:"payload"`
+ CallbackID string `json:"callbackid,omitempty"`
+ sendResponse func(*ipcResponse) error
+}
+
+func parseMessage(incomingMessage string) (*ipcMessage, error) {
+ // Parse message
+ var message ipcMessage
+ err := json.Unmarshal([]byte(incomingMessage), &message)
+ return &message, err
+}
+
+func newIPCMessage(incomingMessage string, responseFunction func(*ipcResponse) error) (*ipcMessage, error) {
+
+ // Parse the Message
+ message, err := parseMessage(incomingMessage)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check message type is valid
+ messageProcessor := messageProcessors[message.Type]
+ if messageProcessor == nil {
+ return nil, fmt.Errorf("unknown message type: %s", message.Type)
+ }
+
+ // Process message payload
+ message, err = messageProcessor(message)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set the response function
+ message.sendResponse = responseFunction
+
+ return message, nil
+}
+
+// hasCallbackID checks if the message can send an error back to the frontend
+func (m *ipcMessage) hasCallbackID() error {
+ if m.CallbackID == "" {
+ return fmt.Errorf("attempted to return error to message with no Callback ID")
+ }
+ return nil
+}
+
+// ReturnError returns an error back to the frontend
+func (m *ipcMessage) ReturnError(format string, args ...interface{}) error {
+
+ // Ignore ReturnError if no callback ID given
+ err := m.hasCallbackID()
+ if err != nil {
+ return err
+ }
+
+ // Create response
+ response := newErrorResponse(m.CallbackID, fmt.Sprintf(format, args...))
+
+ // Send response
+ return m.sendResponse(response)
+}
+
+// ReturnSuccess returns a success message back with the given data
+func (m *ipcMessage) ReturnSuccess(data interface{}) error {
+
+ // Ignore ReturnSuccess if no callback ID given
+ err := m.hasCallbackID()
+ if err != nil {
+ return err
+ }
+
+ // Create the response
+ response := newSuccessResponse(m.CallbackID, data)
+
+ // Send response
+ return m.sendResponse(response)
+}
diff --git a/lib/ipc/response.go b/lib/ipc/response.go
new file mode 100644
index 000000000..3006b3bbb
--- /dev/null
+++ b/lib/ipc/response.go
@@ -0,0 +1,45 @@
+package ipc
+
+import (
+ "encoding/hex"
+ "encoding/json"
+)
+
+// ipcResponse contains the response data from an RPC call
+type ipcResponse struct {
+ CallbackID string `json:"callbackid"`
+ ErrorMessage string `json:"error,omitempty"`
+ Data interface{} `json:"data,omitempty"`
+}
+
+// newErrorResponse returns the given error message to the frontend with the callbackid
+func newErrorResponse(callbackID string, errorMessage string) *ipcResponse {
+ // Create response object
+ result := &ipcResponse{
+ CallbackID: callbackID,
+ ErrorMessage: errorMessage,
+ }
+ return result
+}
+
+// newSuccessResponse returns the given data to the frontend with the callbackid
+func newSuccessResponse(callbackID string, data interface{}) *ipcResponse {
+
+ // Create response object
+ result := &ipcResponse{
+ CallbackID: callbackID,
+ Data: data,
+ }
+
+ return result
+}
+
+// Serialise formats the response to a string
+func (i *ipcResponse) Serialise() (string, error) {
+ b, err := json.Marshal(i)
+ if err != nil {
+ return "", err
+ }
+ result := hex.EncodeToString(b)
+ return result, err
+}
diff --git a/lib/logger/custom.go b/lib/logger/custom.go
new file mode 100644
index 000000000..fd055d0e8
--- /dev/null
+++ b/lib/logger/custom.go
@@ -0,0 +1,104 @@
+package logger
+
+// CustomLogger is a wrapper object to logrus
+type CustomLogger struct {
+ prefix string
+ errorOnly bool
+}
+
+// NewCustomLogger creates a new custom logger with the given prefix
+func NewCustomLogger(prefix string) *CustomLogger {
+ return &CustomLogger{
+ prefix: "[" + prefix + "] ",
+ }
+}
+
+// Info level message
+func (c *CustomLogger) Info(message string) {
+ GlobalLogger.Info(c.prefix + message)
+}
+
+// Infof - formatted message
+func (c *CustomLogger) Infof(message string, args ...interface{}) {
+ GlobalLogger.Infof(c.prefix+message, args...)
+}
+
+// InfoFields - message with fields
+func (c *CustomLogger) InfoFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Info(c.prefix + message)
+}
+
+// Debug level message
+func (c *CustomLogger) Debug(message string) {
+ GlobalLogger.Debug(c.prefix + message)
+}
+
+// Debugf - formatted message
+func (c *CustomLogger) Debugf(message string, args ...interface{}) {
+ GlobalLogger.Debugf(c.prefix+message, args...)
+}
+
+// DebugFields - message with fields
+func (c *CustomLogger) DebugFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Debug(c.prefix + message)
+}
+
+// Warn level message
+func (c *CustomLogger) Warn(message string) {
+ GlobalLogger.Warn(c.prefix + message)
+}
+
+// Warnf - formatted message
+func (c *CustomLogger) Warnf(message string, args ...interface{}) {
+ GlobalLogger.Warnf(c.prefix+message, args...)
+}
+
+// WarnFields - message with fields
+func (c *CustomLogger) WarnFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Warn(c.prefix + message)
+}
+
+// Error level message
+func (c *CustomLogger) Error(message string) {
+ GlobalLogger.Error(c.prefix + message)
+}
+
+// Errorf - formatted message
+func (c *CustomLogger) Errorf(message string, args ...interface{}) {
+ GlobalLogger.Errorf(c.prefix+message, args...)
+}
+
+// ErrorFields - message with fields
+func (c *CustomLogger) ErrorFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Error(c.prefix + message)
+}
+
+// Fatal level message
+func (c *CustomLogger) Fatal(message string) {
+ GlobalLogger.Fatal(c.prefix + message)
+}
+
+// Fatalf - formatted message
+func (c *CustomLogger) Fatalf(message string, args ...interface{}) {
+ GlobalLogger.Fatalf(c.prefix+message, args...)
+}
+
+// FatalFields - message with fields
+func (c *CustomLogger) FatalFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Fatal(c.prefix + message)
+}
+
+// Panic level message
+func (c *CustomLogger) Panic(message string) {
+ GlobalLogger.Panic(c.prefix + message)
+}
+
+// Panicf - formatted message
+func (c *CustomLogger) Panicf(message string, args ...interface{}) {
+ GlobalLogger.Panicf(c.prefix+message, args...)
+}
+
+// PanicFields - message with fields
+func (c *CustomLogger) PanicFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Panic(c.prefix + message)
+}
diff --git a/lib/logger/log.go b/lib/logger/log.go
new file mode 100644
index 000000000..791328645
--- /dev/null
+++ b/lib/logger/log.go
@@ -0,0 +1,47 @@
+package logger
+
+import (
+ "os"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+// GlobalLogger is the global logger
+var GlobalLogger = logrus.New()
+
+// Fields is used by the customLogger object to output
+// fields along with a message
+type Fields map[string]interface{}
+
+// Default options for the global logger
+func init() {
+ GlobalLogger.SetOutput(os.Stdout)
+ GlobalLogger.SetLevel(logrus.DebugLevel)
+}
+
+// ErrorFields is a helper for logging fields to the global logger
+func ErrorFields(message string, fields Fields) {
+ GlobalLogger.WithFields(map[string]interface{}(fields)).Error(message)
+}
+
+// SetLogLevel sets the log level to the given level
+func SetLogLevel(level string) {
+ switch strings.ToLower(level) {
+ case "info":
+ GlobalLogger.SetLevel(logrus.InfoLevel)
+ case "debug":
+ GlobalLogger.SetLevel(logrus.DebugLevel)
+ case "warn":
+ GlobalLogger.SetLevel(logrus.WarnLevel)
+ case "error":
+ GlobalLogger.SetLevel(logrus.ErrorLevel)
+ case "fatal":
+ GlobalLogger.SetLevel(logrus.FatalLevel)
+ case "panic":
+ GlobalLogger.SetLevel(logrus.PanicLevel)
+ default:
+ GlobalLogger.SetLevel(logrus.DebugLevel)
+ GlobalLogger.Warnf("Log level '%s' not recognised. Setting to Debug.", level)
+ }
+}
diff --git a/lib/messages/calldata.go b/lib/messages/calldata.go
new file mode 100644
index 000000000..d3da1d414
--- /dev/null
+++ b/lib/messages/calldata.go
@@ -0,0 +1,7 @@
+package messages
+
+// CallData represents a call to a Go function/method
+type CallData struct {
+ BindingName string `json:"bindingName"`
+ Data string `json:"data,omitempty"`
+}
diff --git a/lib/messages/eventdata.go b/lib/messages/eventdata.go
new file mode 100644
index 000000000..c15ba08c1
--- /dev/null
+++ b/lib/messages/eventdata.go
@@ -0,0 +1,7 @@
+package messages
+
+// EventData represents an event sent from the frontend
+type EventData struct {
+ Name string `json:"name"`
+ Data interface{} `json:"data"`
+}
diff --git a/lib/messages/logdata.go b/lib/messages/logdata.go
new file mode 100644
index 000000000..068b6a709
--- /dev/null
+++ b/lib/messages/logdata.go
@@ -0,0 +1,7 @@
+package messages
+
+// LogData represents a call to log from the frontend
+type LogData struct {
+ Level string `json:"level"`
+ Message string `json:"string"`
+}
diff --git a/lib/renderer/bridge.go b/lib/renderer/bridge.go
new file mode 100644
index 000000000..44da78495
--- /dev/null
+++ b/lib/renderer/bridge.go
@@ -0,0 +1,10 @@
+package renderer
+
+import (
+ bridge "github.com/wailsapp/wails/lib/renderer/bridge"
+)
+
+// NewBridge returns a new Bridge struct
+func NewBridge() *bridge.Bridge {
+ return &bridge.Bridge{}
+}
diff --git a/lib/renderer/bridge/bridge.go b/lib/renderer/bridge/bridge.go
new file mode 100644
index 000000000..4c6bc4cce
--- /dev/null
+++ b/lib/renderer/bridge/bridge.go
@@ -0,0 +1,218 @@
+package renderer
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "sync"
+
+ "github.com/gorilla/websocket"
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/messages"
+)
+
+type messageType int
+
+const (
+ jsMessage messageType = iota
+ cssMessage
+ htmlMessage
+ notifyMessage
+ bindingMessage
+ callbackMessage
+ wailsRuntimeMessage
+)
+
+func (m messageType) toString() string {
+ return [...]string{"j", "s", "h", "n", "b", "c", "w"}[m]
+}
+
+// Bridge is a backend that opens a local web server
+// and renders the files over a websocket
+type Bridge struct {
+ // Common
+ log *logger.CustomLogger
+ ipcManager interfaces.IPCManager
+ appConfig interfaces.AppConfig
+ eventManager interfaces.EventManager
+ bindingCache []string
+
+ // Bridge specific
+ server *http.Server
+
+ lock sync.Mutex
+ sessions map[string]*session
+}
+
+// Initialise the Bridge Renderer
+func (h *Bridge) Initialise(appConfig interfaces.AppConfig, ipcManager interfaces.IPCManager, eventManager interfaces.EventManager) error {
+ h.sessions = map[string]*session{}
+ h.ipcManager = ipcManager
+ h.appConfig = appConfig
+ h.eventManager = eventManager
+ ipcManager.BindRenderer(h)
+ h.log = logger.NewCustomLogger("Bridge")
+ return nil
+}
+
+func (h *Bridge) wsBridgeHandler(w http.ResponseWriter, r *http.Request) {
+ conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
+ if err != nil {
+ http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
+ }
+ h.log.Infof("Connection from frontend accepted [%s].", conn.RemoteAddr().String())
+ h.startSession(conn)
+}
+
+func (h *Bridge) startSession(conn *websocket.Conn) {
+ s := newSession(conn,
+ h.bindingCache,
+ h.ipcManager,
+ logger.NewCustomLogger("BridgeSession"),
+ h.eventManager)
+
+ conn.SetCloseHandler(func(int, string) error {
+ h.log.Infof("Connection dropped [%s].", s.Identifier())
+ h.eventManager.Emit("wails:bridge:session:closed", s.Identifier())
+ h.lock.Lock()
+ defer h.lock.Unlock()
+ delete(h.sessions, s.Identifier())
+ return nil
+ })
+
+ h.lock.Lock()
+ defer h.lock.Unlock()
+ go s.start(len(h.sessions) == 0)
+ h.sessions[s.Identifier()] = s
+}
+
+// Run the app in Bridge mode!
+func (h *Bridge) Run() error {
+ h.server = &http.Server{Addr: ":34115"}
+ http.HandleFunc("/bridge", h.wsBridgeHandler)
+
+ h.log.Info("Bridge mode started.")
+ h.log.Info("The frontend will connect automatically.")
+
+ err := h.server.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ h.log.Fatal(err.Error())
+ }
+ return err
+}
+
+// NewBinding creates a new binding with the frontend
+func (h *Bridge) NewBinding(methodName string) error {
+ h.bindingCache = append(h.bindingCache, methodName)
+ return nil
+}
+
+// SelectFile is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) SelectFile(title string, filter string) string {
+ h.log.Warn("SelectFile() unsupported in bridge mode")
+ return ""
+}
+
+// SelectDirectory is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) SelectDirectory() string {
+ h.log.Warn("SelectDirectory() unsupported in bridge mode")
+ return ""
+}
+
+// SelectSaveFile is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) SelectSaveFile(title string, filter string) string {
+ h.log.Warn("SelectSaveFile() unsupported in bridge mode")
+ return ""
+}
+
+// NotifyEvent notifies the frontend of an event
+func (h *Bridge) NotifyEvent(event *messages.EventData) error {
+
+ // Look out! Nils about!
+ var err error
+ if event == nil {
+ err = fmt.Errorf("Sent nil event to renderer.webViewRenderer")
+ h.log.Error(err.Error())
+ return err
+ }
+
+ // Default data is a blank array
+ data := []byte("[]")
+
+ // Process event data
+ if event.Data != nil {
+ // Marshall the data
+ data, err = json.Marshal(event.Data)
+ if err != nil {
+ h.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
+ return err
+ }
+ }
+
+ // Double encode data to ensure everything is escaped correctly.
+ data, err = json.Marshal(string(data))
+ if err != nil {
+ h.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
+ return err
+ }
+
+ message := "window.wails._.Notify('" + event.Name + "'," + string(data) + ")"
+ dead := []*session{}
+ for _, session := range h.sessions {
+ err := session.evalJS(message, notifyMessage)
+ if err != nil {
+ h.log.Debugf("Failed to send message to %s - Removing listener : %v", session.Identifier(), err)
+ h.log.Infof("Connection from [%v] unresponsive - dropping", session.Identifier())
+ dead = append(dead, session)
+ }
+ }
+ h.lock.Lock()
+ defer h.lock.Unlock()
+ for _, session := range dead {
+ delete(h.sessions, session.Identifier())
+ }
+
+ return nil
+}
+
+// SetColour is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) SetColour(colour string) error {
+ h.log.WarnFields("SetColour ignored for Bridge more", logger.Fields{"col": colour})
+ return nil
+}
+
+// Fullscreen is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) Fullscreen() {
+ h.log.Warn("Fullscreen() unsupported in bridge mode")
+}
+
+// UnFullscreen is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) UnFullscreen() {
+ h.log.Warn("UnFullscreen() unsupported in bridge mode")
+}
+
+// SetTitle is currently unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) SetTitle(title string) {
+ h.log.WarnFields("SetTitle() unsupported in bridge mode", logger.Fields{"title": title})
+}
+
+// Close is unsupported for Bridge but required
+// for the Renderer interface
+func (h *Bridge) Close() {
+ h.log.Debug("Shutting down")
+ for _, session := range h.sessions {
+ session.Shutdown()
+ }
+ err := h.server.Close()
+ if err != nil {
+ h.log.Errorf(err.Error())
+ }
+}
diff --git a/lib/renderer/bridge/renderer-mewn.go b/lib/renderer/bridge/renderer-mewn.go
new file mode 100644
index 000000000..894203a0c
--- /dev/null
+++ b/lib/renderer/bridge/renderer-mewn.go
@@ -0,0 +1,9 @@
+package renderer
+
+// Autogenerated by Mewn - Do not alter
+
+import "github.com/leaanthony/mewn"
+
+func init() {
+ mewn.AddAsset(".", "../../runtime/assets/wails.js", "1f8b08000000000000ff94587f731b3792fd2a43dc1d0358d088f4e52ae761262e47512ada92a594256ffee0b252e04c93843d046681a6181635df7dabe7f74854b25be5b248a00134fabdee7ee068b533096a6bb811c747e502888fc5ac190c903b71d42b0e73b7100e70e74c409f43f823b70efd8c96d89886e2a38e9ccca2d154d693d1b12866f522438b129565dc366ba595dd6714d286593c9a74630586dbd8480c9318248669dcb92a413a71c4d0d247f1f474b7fc02098629acb4815f9dcdc1e1a1343b82d96dc1a96506d16822d780912b4421317471ffea6c67aad5291bc578c8c1ae82fbc37669b3f1b8fa1ba2bd47a7cdfa41adc7e3d74e7c692b8f8f2adb41c43eda7497012b847c6d31fbfd77f0b559b36c34a9dcc5c1f54b50a663188fb989911b21e4ff8fa141c8ccf48a7f4bb3cc9647b1b8b993198fe95fd89dd42d222c5d5c3b97385008dcecb24cd076183aee5e73dd4996c24aed3264cf235eddc21442be2d1df2655cba201bb1b28e97340ab4098cc030e54e5ad95e17c4b125112c8a70a94d5afa25ad100dbf1cc5c8c42fd9fcecb6ef5b8b6ed7b0f6bd884e4cb60c26bf4032c5240809749c7d06496d58872877162d5d32dc287fb7374db0aa2ca005b4471e33269163e8e38928f87cc071245e7a08286609b219610042d69e5c1b8d4cf2973e3f1442d4601e8b590d5c1556f6132c77eb93abb242b456d766654f1af99ed16fca1982f2945ddab3bb72ceba9356ab9ed5cf0a5576d26adf5cc736d7b1d52a2bd95d0ee6f3a79b93eb96cdeeb5ddcf9ad2ea84e1a139403707e86aa196ec16f627d77c6dd6a878bee8ea65d2e056879ff08f8c24c8972af97afd5384325787ccaa34826236a8be7ac5f7daa4761fee95cefcd2e9740def5f0e857b587a9b7c050c3d502a88a836823f109c5159a8cda3fd0adc08a9c20ccc1a373f4cda3c83783283ef9b89199c9d0935870537a2e07fbbbfbb0dab14d5ab03774214eddd7615cb13ce32bb66f298c1236491915bf05ead2182a2679cd18d769ca515df4c6fcad753ba24597f26ad67f60db3fa93ab7a122a3af5a7f6f5d4aae2901105dd33978f835eb66eb069527b9765a318c7e3fac3d313c77822a4817df0abb35bed8177c83b692b54f52cb5471d9b3376cece722e8afd4667c01fe77a5115ca1f26a22286077cd05bb03be47d06595e660467972acb02b4013b33672c40bd8534b03b0c834ff0cf1d780cae7f8a0276a68528844431a323e223565bfea24c9a41a4a4032a3791950ebccd1e2172c50cdd817022da3179a482a9cdfa566d898ba942153dc3996a911645a230d9502c136bbccd202c634dc428fa44d80ccadd9ab38a9d213b2bab5a6bb7a4ad6aab0d673f3abbf7e0c23667fb101e5eb7adf2d688228f6b9e27ee90a3ed97720ab88909b9cfdae0ffbefde09c3af069db1e060bc335e0276552bbfd3b3527e2e37cb23855fddf4d26df4ddfbd7bfb7fdf7ef7ede4ddbbe99b8f0a37a12bd77251940560db7681c0f0925fa3179d7ac44da08d476512ea7920046e9cdd07e4f0c321879610c6580c08b6400549a6bc0f940f54d06cc844c171a3bd34422231f67c2ae97b7859179841036cf45aa8f23c3b542dd308793e1dc544fb491cc71ccfe3a9280a79354895bb2655aee66611d37f4f4ff345dd53c8e96d79d319cd84f9ce6fb8eb6179df75df673c9b2f429fe9a4f148b9f56e0b06bda886f9942a767c343da642314b38834730c8245699fd65e0ec2d1d478c6ff21af6c1cf4d1818d9b33323e468d2f1bbb21c4d8bcee9af1558648ed2c51455cae651bffed6a8d568fd464381f60161a68d46ad32ed21654db79a2f5aed4018f9ddd2274e2f610092ade267480d9456d0537a208e1883ecfb105e51287c78b5d5489552673ef20793441ead836897a70a215d1e56ce1a0493469495cf6040416a7b65dd954a36bdda64c4d1701494ec9533d56ea75415473173e46c593b4e397867fecc3d226be3ddd0818a33b972be6c60189bbff2550809e37113bd52216db4efa0bd6c139a5f36f25679afd7e6e9a9bf5fd720a7d4201b72f61b65c58fb89d9bc362d62c73a45f51fc5bf20fa513a4dde9dd84f4b62a5abd29eae428f3bccb90a28e711db8f8388c7adcfff2f4d4cd76f6252bafe3e32dec7fac3a42f412d8f9224cac49147213fa3c238e854c941fdbfc1cee4c59028dca98b62ac3c59399fbbe99389fcedc593cad4ea9de8b6576dd722b442f6febd4628d1356b226c354f0a8329d065fd4a3a23cca31d02918d42b0d2e6442cc706e17d4c4e776111f8bb242ceeda2a8941d84b9cdb9a84fd57f7aaafecf4ed58bf8592b2289d57b4337b469cb5fc9810edb59db4a8d446aa1ed93a39310832484d8d0e3617d62b62bfb85c4828b4236bde125da3313a790d8143e7fbabeb4dbdc1a3004bc833c5309f08b7ff8b38bb5644c7443f3c9f93b75be5a1cdf1634f53fff3d2e83e00e4718666e5d6d9b9a1ab36b538592ac825c790f29a99f461b47411b7f086b4d2959187cace565c0444b4d31ab2a71c6b1d26b1582757b70711561da55a7d2c68f0ddd1a0917b32628c137eda94eb26f4acc1dacb5474847a3116b4e1a2a22dd3f95845306ca355acf86038926640a192004e485ac77786fc34ab7f17a404434520a380e21753e51c85b8b7a75885ec8096abb5db9c2b241d71d54c82af1caa1ba70f5338fc6c90f4ddd8932571074ba0f1df4a05bf1216ce458e0c12021674af7c2a02cf80135ed202e7b6d615b41c2b5188fb1a91f4e4e45516a0a2c0af9214defcb947a76c3ea52a94dcaf4a87f89b8ca80be71566521011322fc41eda1b55cdaf440d5134c7ab9d1594aec80f1f8beec51d786e27d797f7fa2eabd7a161e3260620694881f109d5eee1038a3bace24a3f32f12ef99901096b6f71b007cdfff1226de3f947e4630f0edd99964736b53287fcbe92637a0d2a7a7f6eb1ab076ceff787850a5a6e78c8c1869583138a1bab5c6e881427dfdebe50dd1da801b4440b502a49037f1f1c6ae23276bfd1d595975f3e878679ea17457ff8671673eee32d47906d19d245512ddcb5f40395c821a22dbbd8b498acebe100f7a75ab4ca26b83e01e5556fe6441d5cbd133aa1dedbfa3486616a2f4e143f2d5d87d06e91aa2674fea111dd393dbab5666ab6e51b033f4d9049bc6f15e5d30927d43358e36e214a5fb52ca68f97b74dd93a10f952411c5e5e0192f6f44d38aad29537df83b26caf26539505037761dd63de9cd9b376f824bb55b6f30f86c36654549abc213d01c13f2b5a55de124f9fb9ad5e74f376401af5bdc6803c1ad252b7cddead266bbada9eddceb766508c8c676ba71e761a51d2c77ebf1b807f05fb11eb759c9fa67c959ffe4c0d0eda0d6e2e6afab8909bd4b62b641cc7d747151f9adf2fc821a15fa8b2ffea2f6f13cd308e117ff5f1e95437aa1421ad36132d55e2d33f86d438fd6d2f64382fa11e295ca3cbd61432a1c312381a71345d7bce884059383ac1fa4f22900afcdca5279a6b20669507b474f4421ef1bed9d5995d293a45888d9bf020000ffff0483ffc1ec170000")
+}
diff --git a/lib/renderer/bridge/session.go b/lib/renderer/bridge/session.go
new file mode 100644
index 000000000..7ad689cf7
--- /dev/null
+++ b/lib/renderer/bridge/session.go
@@ -0,0 +1,135 @@
+package renderer
+
+import (
+ "time"
+
+ "github.com/gorilla/websocket"
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/logger"
+)
+
+// TODO Move this back into bridge.go
+
+// session represents a single websocket session
+type session struct {
+ bindingCache []string
+ conn *websocket.Conn
+ eventManager interfaces.EventManager
+ log *logger.CustomLogger
+ ipc interfaces.IPCManager
+
+ // Mutex for writing to the socket
+ shutdown chan bool
+ writeChan chan []byte
+
+ done bool
+}
+
+func newSession(conn *websocket.Conn, bindingCache []string, ipc interfaces.IPCManager, logger *logger.CustomLogger, eventMgr interfaces.EventManager) *session {
+ return &session{
+ conn: conn,
+ bindingCache: bindingCache,
+ ipc: ipc,
+ log: logger,
+ eventManager: eventMgr,
+ shutdown: make(chan bool),
+ writeChan: make(chan []byte, 100),
+ }
+}
+
+// Identifier returns a string identifier for the remote connection.
+// Taking the form of the client's :.
+func (s *session) Identifier() string {
+ if s.conn != nil {
+ return s.conn.RemoteAddr().String()
+ }
+ return ""
+}
+
+func (s *session) sendMessage(msg string) error {
+ if !s.done {
+ s.writeChan <- []byte(msg)
+ }
+ return nil
+}
+
+func (s *session) start(firstSession bool) {
+ s.log.Infof("Connected to frontend.")
+ go s.writePump()
+
+ wailsRuntime := mewn.String("../../runtime/assets/wails.js")
+ s.evalJS(wailsRuntime, wailsRuntimeMessage)
+
+ // Inject bindings
+ for _, binding := range s.bindingCache {
+ s.evalJS(binding, bindingMessage)
+ }
+ s.eventManager.Emit("wails:bridge:session:started", s.Identifier())
+
+ // Emit that everything is loaded and ready
+ if firstSession {
+ s.eventManager.Emit("wails:ready")
+ }
+
+ for {
+ messageType, buffer, err := s.conn.ReadMessage()
+ if messageType == -1 {
+ return
+ }
+ if err != nil {
+ s.log.Errorf("Error reading message: %v", err)
+ continue
+ }
+
+ s.log.Debugf("Got message: %#v\n", string(buffer))
+
+ s.ipc.Dispatch(string(buffer), s.Callback)
+
+ if s.done {
+ break
+ }
+ }
+}
+
+// Callback sends a callback to the frontend
+func (s *session) Callback(data string) error {
+ return s.evalJS(data, callbackMessage)
+}
+
+func (s *session) evalJS(js string, mtype messageType) error {
+ // Prepend message type to message
+ return s.sendMessage(mtype.toString() + js)
+}
+
+// Shutdown
+func (s *session) Shutdown() {
+ s.done = true
+ s.shutdown <- true
+ s.log.Debugf("session %v exit", s.Identifier())
+}
+
+// writePump pulls messages from the writeChan and sends them to the client
+// since it uses a channel to read the messages the socket is protected without locks
+func (s *session) writePump() {
+ s.log.Debugf("Session %v - writePump start", s.Identifier())
+ for {
+ select {
+ case msg, ok := <-s.writeChan:
+ s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
+ if !ok {
+ s.log.Debug("writeChan was closed!")
+ s.conn.WriteMessage(websocket.CloseMessage, []byte{})
+ return
+ }
+
+ if err := s.conn.WriteMessage(websocket.TextMessage, msg); err != nil {
+ s.log.Debug(err.Error())
+ return
+ }
+ case <-s.shutdown:
+ break
+ }
+ }
+ s.log.Debug("writePump exiting...")
+}
diff --git a/lib/renderer/renderer-mewn.go b/lib/renderer/renderer-mewn.go
new file mode 100644
index 000000000..e66dc5003
--- /dev/null
+++ b/lib/renderer/renderer-mewn.go
@@ -0,0 +1,10 @@
+package renderer
+
+// Autogenerated by Mewn - Do not alter
+
+import "github.com/leaanthony/mewn"
+
+func init() {
+ mewn.AddAsset(".", "../../runtime/assets/wails.js", "1f8b08000000000000ff94587f731b3792fd2a43dc1d0358d088f4e52ae761262e47512ada92a594256ffee0b252e04c93843d046681a6181635df7dabe7f74854b25be5b248a00134fabdee7ee068b533096a6bb811c747e502888fc5ac190c903b71d42b0e73b7100e70e74c409f43f823b70efd8c96d89886e2a38e9ccca2d154d693d1b12866f522438b129565dc366ba595dd6714d286593c9a74630586dbd8480c9318248669dcb92a413a71c4d0d247f1f474b7fc02098629acb4815f9dcdc1e1a1343b82d96dc1a96506d16822d780912b4421317471ffea6c67aad5291bc578c8c1ae82fbc37669b3f1b8fa1ba2bd47a7cdfa41adc7e3d74e7c692b8f8f2adb41c43eda7497012b847c6d31fbfd77f0b559b36c34a9dcc5c1f54b50a663188fb989911b21e4ff8fa141c8ccf48a7f4bb3cc9647b1b8b993198fe95fd89dd42d222c5d5c3b97385008dcecb24cd076183aee5e73dd4996c24aed3264cf235eddc21442be2d1df2655cba201bb1b28e97340ab4098cc030e54e5ad95e17c4b125112c8a70a94d5afa25ad100dbf1cc5c8c42fd9fcecb6ef5b8b6ed7b0f6bd884e4cb60c26bf4032c5240809749c7d06496d58872877162d5d32dc287fb7374db0aa2ca005b4471e33269163e8e38928f87cc071245e7a08286609b219610042d69e5c1b8d4cf2973e3f1442d4601e8b590d5c1556f6132c77eb93abb242b456d766654f1af99ed16fca1982f2945ddab3bb72ceba9356ab9ed5cf0a5576d26adf5cc736d7b1d52a2bd95d0ee6f3a79b93eb96cdeeb5ddcf9ad2ea84e1a139403707e86aa196ec16f627d77c6dd6a878bee8ea65d2e056879ff08f8c24c8972af97afd5384325787ccaa34826236a8be7ac5f7daa4761fee95cefcd2e9740def5f0e857b587a9b7c050c3d502a88a836823f109c5159a8cda3fd0adc08a9c20ccc1a373f4cda3c83783283ef9b89199c9d0935870537a2e07fbbbfbb0dab14d5ab03774214eddd7615cb13ce32bb66f298c1236491915bf05ead2182a2679cd18d769ca515df4c6fcad753ba24597f26ad67f60db3fa93ab7a122a3af5a7f6f5d4aae2901105dd33978f835eb66eb069527b9765a318c7e3fac3d313c77822a4817df0abb35bed8177c83b692b54f52cb5471d9b3376cece722e8afd4667c01fe77a5115ca1f26a22286077cd05bb03be47d06595e660467972acb02b4013b33672c40bd8534b03b0c834ff0cf1d780cae7f8a0276a68528844431a323e223565bfea24c9a41a4a4032a3791950ebccd1e2172c50cdd817022da3179a482a9cdfa566d898ba942153dc3996a911645a230d9502c136bbccd202c634dc428fa44d80ccadd9ab38a9d213b2bab5a6bb7a4ad6aab0d673f3abbf7e0c23667fb101e5eb7adf2d688228f6b9e27ee90a3ed97720ab88909b9cfdae0ffbefde09c3af069db1e060bc335e0276552bbfd3b3527e2e37cb23855fddf4d26df4ddfbd7bfb7fdf7ef7ede4ddbbe99b8f0a37a12bd77251940560db7681c0f0925fa3179d7ac44da08d476512ea7920046e9cdd07e4f0c321879610c6580c08b6400549a6bc0f940f54d06cc844c171a3bd34422231f67c2ae97b7859179841036cf45aa8f23c3b542dd308793e1dc544fb491cc71ccfe3a9280a79354895bb2655aee66611d37f4f4ff345dd53c8e96d79d319cd84f9ce6fb8eb6179df75df673c9b2f429fe9a4f148b9f56e0b06bda886f9942a767c343da642314b38834730c8245699fd65e0ec2d1d478c6ff21af6c1cf4d1818d9b33323e468d2f1bbb21c4d8bcee9af1558648ed2c51455cae651bffed6a8d568fd464381f60161a68d46ad32ed21654db79a2f5aed4018f9ddd2274e2f610092ade267480d9456d0537a208e1883ecfb105e51287c78b5d5489552673ef20793441ead836897a70a215d1e56ce1a0493469495cf6040416a7b65dd954a36bdda64c4d1701494ec9533d56ea75415473173e46c593b4e397867fecc3d226be3ddd0818a33b972be6c60189bbff2550809e37113bd52216db4efa0bd6c139a5f36f25679afd7e6e9a9bf5fd720a7d4201b72f61b65c58fb89d9bc362d62c73a45f51fc5bf20fa513a4dde9dd84f4b62a5abd29eae428f3bccb90a28e711db8f8388c7adcfff2f4d4cd76f6252bafe3e32dec7fac3a42f412d8f9224cac49147213fa3c238e854c941fdbfc1cee4c59028dca98b62ac3c59399fbbe99389fcedc593cad4ea9de8b6576dd722b442f6febd4628d1356b226c354f0a8329d065fd4a3a23cca31d02918d42b0d2e6442cc706e17d4c4e776111f8bb242ceeda2a8941d84b9cdb9a84fd57f7aaafecf4ed58bf8592b2289d57b4337b469cb5fc9810edb59db4a8d446aa1ed93a39310832484d8d0e3617d62b62bfb85c4828b4236bde125da3313a790d8143e7fbabeb4dbdc1a3004bc833c5309f08b7ff8b38bb5644c7443f3c9f93b75be5a1cdf1634f53fff3d2e83e00e4718666e5d6d9b9a1ab36b538592ac825c790f29a99f461b47411b7f086b4d2959187cace565c0444b4d31ab2a71c6b1d26b1582757b70711561da55a7d2c68f0ddd1a0917b32628c137eda94eb26f4acc1dacb5474847a3116b4e1a2a22dd3f95845306ca355acf86038926640a192004e485ac77786fc34ab7f17a404434520a380e21753e51c85b8b7a75885ec8096abb5db9c2b241d71d54c82af1caa1ba70f5338fc6c90f4ddd8932571074ba0f1df4a05bf1216ce458e0c12021674af7c2a02cf80135ed202e7b6d615b41c2b5188fb1a91f4e4e45516a0a2c0af9214defcb947a76c3ea52a94dcaf4a87f89b8ca80be71566521011322fc41eda1b55cdaf440d5134c7ab9d1594aec80f1f8beec51d786e27d797f7fa2eabd7a161e3260620694881f109d5eee1038a3bace24a3f32f12ef99901096b6f71b007cdfff1226de3f947e4630f0edd99964736b53287fcbe92637a0d2a7a7f6eb1ab076ceff787850a5a6e78c8c1869583138a1bab5c6e881427dfdebe50dd1da801b4440b502a49037f1f1c6ae23276bfd1d595975f3e878679ea17457ff8671673eee32d47906d19d245512ddcb5f40395c821a22dbbd8b498acebe100f7a75ab4ca26b83e01e5556fe6441d5cbd133aa1dedbfa3486616a2f4e143f2d5d87d06e91aa2674fea111dd393dbab5666ab6e51b033f4d9049bc6f15e5d30927d43358e36e214a5fb52ca68f97b74dd93a10f952411c5e5e0192f6f44d38aad29537df83b26caf26539505037761dd63de9cd9b376f824bb55b6f30f86c36654549abc213d01c13f2b5a55de124f9fb9ad5e74f376401af5bdc6803c1ad252b7cddead266bbada9eddceb766508c8c676ba71e761a51d2c77ebf1b807f05fb11eb759c9fa67c959ffe4c0d0eda0d6e2e6afab8909bd4b62b641cc7d747151f9adf2fc821a15fa8b2ffea2f6f13cd308e117ff5f1e95437aa1421ad36132d55e2d33f86d438fd6d2f64382fa11e295ca3cbd61432a1c312381a71345d7bce884059383ac1fa4f22900afcdca5279a6b20669507b474f4421ef1bed9d5995d293a45888d9bf020000ffff0483ffc1ec170000")
+ mewn.AddAsset(".", "../../runtime/assets/wails.css", "1f8b08000000000000ff7cf8c712ef3e932486eefb29fe7735379a3d4defbe890989de7bcf1d7ff4de5bbdbce29c967a292e104015322b0bb56001e0bffff36ffffce3cd63f94fbeefff54db3cfed31cc7b2ff0b04eb763ffeb36e8fe6fcfd673e8f60f62b876cef3390cee9b2c848a8406114c62b84cc7002fb95485962349457d81f4a239bde7f8e269bfafdfff76fff0efedbbf81fffedfc4d53c1dfb7fd6f35c0fe55fe67d29f3762c27d09d7ff331ff1f7b3994f9d1ced37f56d9d80eeffffe2ffbbf5008fa0f0c82fe0387a0ff2021e8bf09effbfecfbfa4ebd96e5b39fc253de679d8c1bbfcfdf1fccfba9cca2d3be6ed9f7f07ffedfffc6baab2bcfce7fffab77ffef97f567f22fdeb9ffff15fb1fec7fffa7f1dfbf10ee5bffe99e66dcc86ffb6de655b37c7bffe4121e88f6ddff27f9ddbf0ff2fb223fb57b62c439b677f1200ff6bf35c55ff2b6fb26d2f8fff7d1ed5ffa4fed72fdb4b02fb8f02a225b76658e6cf277937c31a7fa74775338cc3fc7f7cace40eaef967b2fe5dde7fc63cf5e200150ac10d12866164e18f71f8c36325b1d484890bb376f067ebf067283686611c7c9a63fb79e4000e398661d4f70fc8ff03d25ed4b3560ca27dfd531886d9a83faebf7889abcdf56d12046e72866138f86f02f61f25446df16dcd76c5e4d40cc318d81f97f007cf72823f6b3792de2902310c03f57f8cfb9f5063e81f1c30a588f8fde1e38f3f50e60f88e1fee6c74e3f7918ff64f287cf177786515e2adc0426939a21891c8611f7bffa8c3ffaf8c444b09bfceb73182642ff1eda5f5295a10ef607cfbfd8c51886fdfd11c14cf31fea758a86c8479e2b41048611b13fc560df3ffa8afa77f388f58b44ec8fbe98aeff9b4f611485c98a3389e08161d80af85b893ff9d992e5bf1e10b3578e3a0c23c97f0f81fa833ac134d2232097d5e10f5f42fe15fda794ec5cd5b65cc0681aa923c3b0fddffa32c9df03ce453a86fe46fd5b5ae6e391f6fb5b7a5ea058e1fc6b346d4c31710c69501b09662e615906ab6cb0b4601ed0da905b964ce3086cc832eecc340c3357bd50a73a40dd95afed2a6630d6d3b70a14a0de2d79b62a7d554310b613a21797bd3285917a154e8a102bedecd5107e1fe7805a7e1949176e1002d8a196045d68ba297a377847696d09582c75ea743f7f52e20dd60b29db838d9308896dc78548e213c24aaf74b123f107c62670026b519d953dff939bdc65685a90b9ea0f629e55bfaf8c554f567f8b766291a6bb8e06311e8c7199d420a710ddd170289c125bb4ef16ed3264e6f21c169a7c4ded40c1ac43ab66aa8e8670fd241d73d23350cb4062aaeba1b6706ebd885dd07bd8381b20b42d24787d3f555598ac01d1d8b62d10ad4e90b6ad924a296af2612d13cf7d5103a4b2e2b26c0e1076ecfeedaa63040a1425e674f048008c4d2b7cd836d572a65b73e00703d40159d4e1f128be35711511a2bbed0f9a26c2a6d2305980c32bfa7ad0039536733bbf2dce366648f31ea112670807689dfda4a08eed6006e8b6fe390fd6e3fa8579883986c5668ef0c2b5b227094807ba78dfe884d8992e1fadd07604a99d6dce1a66b18acc5440d4cceb7aa53b5f203a24ea6ffa8523d194c65ab8a359d6835cb82e181a45bcc568f9a0b0b43d34ba5c2512b0a7c2d38ec4d536cad28948d78a5533dec75c238b50da577bfa58ec1b0b8f9d8b9108d79efb097349095b75a0c62c40df8bcd86190704ed9299935065d53adea186cd511060d5e281b27b0ad78c20a078d4a9fed3bb4b8a9393dc52fb70bda8bb69673898836e41a2449dc3597ea139d49d1a2ec9558009a7c606701fdf4fb53e271912069bd9ca64a5ec2aeefc15e1151b304e1ec3840b1336c424ed9622b8e934b48e40e64789dd2d1f9b2c150363663d73ac8c413a3c3c330a5c8be8b5b32dc0da19a75e55c6a6d76ae50786e4e6434d6e95c64371ba1925bebdcffbc1caaeafb1ee8b95659d1046f8b9de8ce4f806a5d3ea3bd22e7734484d371ad5df1192c23fd99a488f3a21c146ba263a87d846003bcb72b0e69ffece49a3ede4e5e7e171176dea2c058baebe48eab10b0d6e8d013c3b8d547cee44940e9fcd9de3e94ff72e7048a32d92fb4bfbaa45af4ed202297292e7b6587a4edb651f338b5a00f9e05d874f72650938652cfa694fd01d51b6171a19b68bec9cf089220855dcd6da0be62ac7c6919de299a1b52477fee6077a3566bcd031f2c754bf5b4e1d3e5a183daa25a740b8a7de21ce058c5c4d3de4dc187e606ca9673a8a6d63a6309b95e2506d6363b6cad65bc4427fcf136bff3946925946ec197364193bf5859a0c89151b05484f4327881a65bc17b48bd472e731eec93e166e26ba8b0239d94dc9175a18367230c0fb3d1a15db919b80acb805e12592cd0c43cc8d67f60023498cc112354fa047f1aa5dc223ac87a86d34cfb9490295706413ab4bc6e588d6f4457b12d60761b19ed91138014c07539ccdd3d4392bf73b303a836f2b7f4ee6edf16a7f14343457218c8548ec25e485af689675e947ee0534807a4d0fe0f8c1c9943500d301a454ec65712c083282ed62b8e533e8ba8e08c407f1632deb381883bb28a620b9093bf92b73a9944b56016d5898cf7c2148d617a91b401d1338931131286fba6330657024f24cf96b614e07b8ea2180ea800919c4a2ea42da71c2921ef865a5c297d51a3d1abc4c164c36d54d16a45b928b8dbc0732a268ac5c1901564c0bd088fcf95402496a6ac7191c6e95ac7757932399d8a623b89331726890dc51a2346ac99d533ad2f161b28388283860d6bddd879635c5c1670ce6724800ef7de5e497db344d662e0d5bf43ea1f2b0fbd8e1022ff6416f3ee52f609e189a13c13652e074ec654cea688914bf059e676c2489489247ec233e331c697263624af0014550ad4f4ae9632bbef4004f638418a87f1048bbfc9dca35039089c28e2f74ea7ea7e5d2534f233cadc3e0f9cb358b3cf162ca6ab3dabf0aeacb12cd9cab97bb5a422892472f19bb1b02d5edad963b4e2634bd421241bf9429a4f05b86319446c28e8cd8ab9435deb191c7f9e5146ecec81739665316660e693f7d31d9d17e56d1cb826b44fd5f808723b1fd58a8dbc77b36c15d26271cacc12a26cd83c6a0993a4f26a5c7c2e941aea329e241e2739d268a29e914f86da77a93278e65b957195945d75b421480499ec09f1efd6c32b994ccf6a2139195a44687c5cdbeeea3affdaae510e16da44f50be3c71e01d105a57978c13a4f9ba1fa3a7b1ed81afbd1f83f64eef5af8dd259963c39f4e5346fccaaaf77ace68800aa1aaf03e2f2c087183d2ea6fdbb2f52de65320e2c2becf4f4b2fafe4f20eff7a0938061334cd71df4f484088762fa3b60a5d90a10ab315acf36632a0aab8fb123c24fe0a8a8921c7b727993fbc6f53b5f97b4fc8c31a8d014ffceb5283116c6e447cddaf6cddd47fb905e7163b58c140ddf1a8493cd8cde9a71961453f065dff9d602bf86afc6ce971f7e9b2c10a3f4c867d6e000d814995c058d61b941ce4d483241ab68b8cb5c6f68f750a85fa1e204125a08a52be9c3ecf681fab781ef155ec13275f77f48404e594e4e34d23ed4e89fd5649ff3823ed1ac9783e9e499badf60afc65d56e0bfb075eb5e485530a177a34217aa2c67ae89312dcad73c2ed8cbc7ea5c3515278aee4de32b645a41a252fb271348e9b14a24637b3f0fae1de94524bd4c6f4c6011fbc46d87be9951f49b072f75689bd3403d0b78703f659e3f26a9c3af03406b0259dae82d368fa55511b164bf03bdf9687b3dd8727dfa6822092cfdc9f3d599e959321a573b68c66407e4cd32feba02ab4ae69a0d13c23367a56c9f54410697ad3ac527fb63bc31a74c62f0ca72fda0d127ae30bbdbc31c55aa91d61378b215be47ed0cc25ae2f5684ed67ac72dcfdcadd6a28b44fe20a2dd402afd17c4dc7e3862aaa82a92e9ecab3bdb44dab226726ec1f56ed96b349b5067fc63acdfdc29dcf2922cf499a24e98f4c1c219d56c8a047099736c643fbe28087cb7d1c8824cbf418ee1599240153b9a16fd32d30603b4b1c6482f94e0bbeef6620090d9b18f2a690bd804463d7029826d4dcfd6072a27577c679ef1b2fe477d3ea28545fd9f1457ff86a16836f5b24623d5299ac7c12f05ffcd98deae9c421db55cd3ea848cb393a455aa43301a2e9aa2e251491a29b759765e6e23886153105a75753c0e632dbd06ef7f98e3ccff34d3c4ba9599a8b64b9c9787ab21b49c6870fce5a4a8c601e19c1afb5c0cf217f6db6835ac3530e48f998974b39bccbbb419deb9a86b57f2a36ad4fbe041f7d5bc660be51cc869a7c18775982b06312f14112b1432249c5136dc66d30334f48a9fe69bde10bb7a13037cb3f3566cfd5adb1098f386b7d75277891a7c2446054d409580ba96c84c6edcc46679b6cde3ed1c69eccc028bc703379c4dcd99f7e3f41d83a913226617bc9ad13a764f4de48745c67eea9d69db2be35e7718adef0e5db389c079472235d9fdada53c8c544dd095cd6eb45cb0906d60fc4ea0ee6261e25735ef475a2a91d0401de77b42544d8e4666b21cc815ad1987af6183e8c2835a428253440e5b63b9000a909bc415007bf0bc0c0860476597db0bc9af8faae30b97cc8bdd2f9e74e665977a89d6c00804e36582abc426558c96c19a97c54c5c0c4a25933a64e24714890a2c62d7dc4cab8836926109ac4155d231654cf1b1629ecf1d20945350ac2d00f613e808e9f0b17364af1ce6b76026c7cf97de81ab2ffa2ef2059289fd2a7884d34b7fd97be6498a2408567668b73209dbf5ba39d09b54b4ebb159e3693e89938a677ff70ef49aef887ef4f36c32b5d9e08c90218911c08999e283974248ed3c0336a016dbde4109cdafee0d1429a103ffee6c46395cce0487dcba3390b4ffcf0a85ede2f7d23df1c3e8c1babcef847d30bf433d1fc91f94da32cbf9981fc92b4e544a3702623742170ecce431d3cacde0880ca1a29f326e5da0d44031618833135425bffbe853804fb63988c6173465298d4614746ca99cc615746da991fc37e8c2630e5cd7e8c9c3325c3e28c3cff59538c8c3135c3f58c1630b9c3568c183085c30aac2bfcbd5fc6a144fac3c10a2664f2b7c53e665d5bc51cecf8e83cd3d9b0ce24c009dca6fc29e24eb398a8e6b6a8c0ca10c7ba0c9a6dcf58cf99ab8c77d2885043391dd6089db9b59a0259dff9e514911ad0a5f5d98212d1650740def7865eb03b848001ae1c3f83a1097f2330c43814438ec667a48d0506404c6797cb1df86365ac80febdbd2d051059872bb480a40b32ab048077457d7b72f0be4f9b0ec1957dc35ad92f0f4e9e7c26f3dc771b98f5108b94dbd0bc643995d326520fdc720847367dd72317fabdbcf1c8f8ebbb4c801c07ab6e00ccc921850a8187f3fab209c8a8b62fd83afe9dc46a2186fe917f18b28bc5bed973a218eb970832629851e9dc255f53a3686aa86acc3befca48b25e02c74abecccc385e7c2ab8ba0c10d4deb33daad75d5db305d2b3c8f1ad31884fdaa0ad1ee62fa28afb6a52d910d1eb1974ebea94e698132152c8a41d192e70397788f1a6d9eccbbb255cd4c90fe282da8a9f0e050a4ef74f4470a8810c61be13234e5e0084fede175bc9709bac1652d11fb1acaba92c5f7594df46ca0c8e951c65b20925fd3ef002bc59266cee3d4da9b658c1244c7403e72d5e5beda4c48f7855da02f770feaf2739876141906278b4cbbe18922bd60fe98bcfb5e2f4d0f2d95f2ffb9dc7e808acf6eec2ee39432804c9aa882ee3f61c41b3bde6abf5cad53362d5aca37fe31db1ba5833aea373f34a27f1c502873f09546da1df847e2341002689822001921faa7e27361a40999700b9e0f06be1e4f2c93c9d9fb3e05b9c196fac7242dcb08bba09f0da2c11a9692589247b10165292a1da37f6e38b1e9e7245b06f50b5b751f4c114c6490b8e6ded368795c08aaf43f300b57ef570803f6e223c7d91d9f1ab379b53776c1b98f067860d4751a1b00751a037fe7a7ac733d16138f79f98ad2a3dce709b735b18a7011724fe04ec0b0e7bbac29fc1632bc82fbfb2c1c97cd4c3797fc13ed5ee62f10a4ff4b7a07d845879a984562232dd4ac31932e1404461c0f984495fbd672277ae5c13b6ba77b126b48da70bcf3009ce1e38aa320fb3cafa422e5b87abe80aa1151e16dbecef5815257005f0727c9e0e04a86e60223da47ac49b94f1abf5f69a6d8b5ac82c28c5e735d2eb68a6a0f11abd2d36befdd27a5f0ece11e6c29a0355d74c7434e91606c31a1bc1c3ba891732d289c24b7ccb3e3b55c27d93290a5fdcf2867357ddd673ab364e0cd2be35685cb74e4bf4f087b56d973279486a8bbae2dfcccdda80c079cd3471d1ccd4e04572a406be136b6d468ef5539d661f93a34fdd2722e8f483b89ce7793aed5499eb6855004040f443e1e3217b5d376004007848d51fefba920d951688614078b955f03eb8e55bebfeec4f381c4e475069e278cfc56a44e08670ea05cb899bc09a94af99a4c47ca135b09ec6f101085f26178bbd602b94aacbd4ed273f0ddfe2b0f68e834c5ba4a24f9b000554fa6ddd3cbae1a9cdc1ad8ea18ea95e098d410eaf12bd6a2b50c3b4fdd7da10b1a57340ebc8a1035352a05df503715a8e0b1620279be2c9acf709944e07c91a8849f67edbaf15e466610ee98250d73a9ef6cbbccd30418aef6599b761a7d99b6c6b650d7090bc14b84880069df034678a3047f05e71732011fb1dbbce6906e1125f1136e79383c77e8c9845b4b5a4d04bf453e06095624f4e17c40b73d8d9628119f8eb518edce00ee69126e9c6294cd37a8298b17a5cabab35718dcfa42b636dc7c06c42ccf37c695aa03ef4978e3b061b24321342cc31ef61f4c11fcbd6ecdd84a83247e2e6e92d008eaa07692fb471bffe6b8babd277a04a508eecfb10c33e25412bc24b3a0d4e0e0019c21825bb9326e96072dbf9fa8e07f9a8cd5ca9f118c11388fcb83d0250816b74bd4963420f0b7ea86b3b48f8b4d09f01ce4027dd14656855fb2d53273f2d0dfaf18fa2e8f40214ac750f108b8f658fbdc4033c2ae67e713b5ef6160550305f22b1314a280a743245fd235275545fa1b0b55004b4439cdda3d74d2798b4e1afb3c56c288b5b1d247ac7e790b4ebd813c910696284d248db5e2cd261fa3c51e7566939d583feb4733f715fadabec717049442974ff74793adf05c74e3e395d748dd6219d6c0c7e3d295d4dd606b8bc94d1afeb37fbeed3f7d422c41481fdaa6e02fb0fca918f98727bf203df2f0d4946e922b223aa2f4837bfd41b03a9dd4101e9d3d7a539e44edf1ee090db23ccf7cafe9ed487225bc737c883c21d4bf3223ff7f614607912b51134b60346604a978b022c1919dba590bab3e2881cc2374f06ea4972576c6c921731630b26acb8af8c39cdab0f689118cb4946e5881c2168426f0f7bb1633df10c9cc38d6ef73d49a44ecead4ad7295e57ee7527e9f7229913087bd22d5635200b437b2bb458fe08b1bfc78b37cd61ba40c1f7d1a852d2191720898d2a5d427a0d032351e7b83195d58354cb79cbebc3cd469b227652cd67cdf59e4e45b0111783c04ef7384c302e0ef4870bdf89f4f2714d5b57133afa010d5101d7ef325e73fba104757e3f29bc3e6deef6388ef5e354647e59a425c1815fe24e5fa60330165423aa1f54376414bef4a00c8232b15ee4f514e5a10f360faa6c3f51ba8da04877caa2802ea51d2565031ed772b4a685c0cc6fbc74556e8505d07e9a9231f760a7682aabbfc65d3cf6641b106a87cc127283290529d514247a2464c2311ecdf2842c732c8eda86be2443c12dae90452e1fb63d74b7df3af7728950b8f276183aa11e76516ef7e9922f2e0c38a6b2fbb3747ddbfc534aa90f4cf8af025eeb9d8ecfd961aaa6d60a01daebca6c0be2d10b2102e4546ed005c0ab06bb2f8f891f5adc4e40a5eeda543b8954b07e050ccd51fde5813f6ed81c6746c0e46b45bea84175948b15ba1abf40ea6f7b591c37f0ab5b0cd2b3f8d79e10c82eb6bb42dfb62b64246650f43bf989eb60f6a25b2c77df3975194a15b84055669e270f21d92ec11f23b23ff361364e3d06cca06b3cb03974a041930b2734e778c73cfe0985d0846d0836fc656cbf763e5899307735db840017786e0199c691d319ba3da757f40e70207561a9144a10c2432c73914f7b4557ea39ac2b96f906d061e5dd545142719015fd2301887efb65160eab42ceff5a783f8e26467367ca0ff755ec3d48827984a345e276b59e35bfd73991e7302f7003a91ed9c6d7b524b0d7319d0e4f916e24d1ab3147ee954e3fb959b0fc63e8e40d541f4b3706407df0a75f418389dd38c73ea14f5fa64e26a7a9c898e2bcfb232132d35f4f2d9f641fefc7329ca4ad67e853ee32ed1533e0e2cc1c0bd272b0687ce96e494799687b7ad4e119a4011407ece200a203b3482a2ec6db72a9089ddd0d1482c060444857302526894233d22effe5d304f241e79e7c85d615e2bfc5c9202567bb18f2ac6f8530a1fbc9c1a19821f7834453565d81262360d93a6c3cd218384ee4ca5438c3161d9f43116487a4ba258ca3eecbd41486bbd299bf47d79749e5b4ea2ebf91e3c3899eee3bb17101a1c22c634f54119a055d05383e4d8c730a4de7d45c9f93ee3a961e0465b4950b6b98a12295cfdca91e1aebc0d5bdc03c8e9fba06f4470ac2c2eb3d5d8432d27ab33b02bb0140ec6990da25144213703f43773f51772b0e1c6ce696f1dbe2b0ec0ca057f0e04b48847a3c8be108c0ed3b8526cdcab502c0d156b8939a8606d272ecb8ae0f5db369ac5d9c6716da296445c6708479ae5dea154dc61b5621d1631e678bb8893b7bb1d1a9eca7b454a92791cb81598805b5e01c51d244ddbddbe7d3a8c1ade5e8c9b1372c4ab4c4c0e1dbf00d904e807f8297439346722984b5b1df9c77e4ceeb083b758dd74c2a327ecda95ab08d9e6ed855a0a1de569f0d75a8b56a7092ea39a364c29dba11386106178e80ee73e9eed84ffdf1fdb9d5e1f90e17c6c6df089dfb65ff034f385824de52f5f0437d10c0e569821c8ca5240274c85b0495add439dd1501a114af36e318d0595626141fc65bcfc2fe8c215224e7259ad45f67087273b76390a02716b9736ec8541981b2f94108f453f59ffd7de422ecac7b0322e1667093c37b1ea82588758eb54c0d4639b82c944ed558347eadb50657c38f6466958b9a847df8ea454f58f76ffcf1fcc40d2efbf7016f6293da9919d1515c10086d6bbb8eb2b653d41579597e44a04d4a774d4ffd19eb13bd5190b5c00a0164158d687e02965635d52f0394726644b80b27b9cd6f9053b2f10bf045798ba004b15c51561c7e74322b37c2fe5247c8b8775823a1687772e29540ea7992e04961e2d688e08b35d5330faffa09dd1a7dadf48ac74b4ca4f5b1250e4fb09a8cab7977a4086eadcb4f5ca7171abb214c87ae0e0e9717f6c15436cbfa13390b2e8d711472a3af710d2e66adf4992c31c8093b2736ad46d2c790d66f90aef6d6d73c3d78f674a5260537e97b5249992a34107eed19de9c8cf4240980f866a25da6d0dd99b7dfd78480f6ef279649e3ff02c2b9e1085195c23f5047053574e7c4abc8a28e58f2768d83343a0cd65b8b247867aff10d25e2e4a2bb1adb4c4cbee5b7792c7fe7f86e47f42bea3c9e2afee28e93cc455010a170dcc88752c7abfb4090c127f036e7ce47283c3ceaa1f3a93788cd22c7449284af23f66654ac754b3dfb7aa03efc2c4f7c67ce876e36588e022884ca1df13008cfeed1e1a3789e572abdfb1626646837ae30ab0fec73188b838e28fc066e2a2171b5a0285df8f474b39b3dc6bc966f01d2b4e64e637164e0270a5aa97f88d43c118a5e091afa4c9f472198de3baa20dd4ff4e82bb2e5be47ce51b67f0fe7c6bd7f69bd25a464005ef8b99c7085d31950859470752310520002a2028296567a69200f72015bfc6cc6d67eaf5b0713b7ca0895240e23f3759a087c236b63758fd5a1ca254710658b81e139e3d4a57e3c54d356ca6109268ce9d1766f782321e5868f567397c44b0019bc615fbc3b0c8fb3309fe836c34b306b2fdbf36487553d578a5090492ffa513a2326735303af5da9dd5a98e92e0f5fbe2250062b9a177ebbe5e1fb38fb981e849c64f0c2b70e69e17a2f419040a5e4c502bf48a014d28c17760ce6f5b8be987d891890d09b0e7f46b77d638e6e130aab16299e340ca3f645f6d018916faee0fa1321ebb25234edc8616a6d511394e354dd18062a62b1a62e595e919d04d1afa96fea87e7680e30ddfd60bb06362cc5543710b7dfebb666f3635368cafb2f14c222532e4de0d0b175dd8774f8d94744cc5afb5fddfa0f637e86580aaa91f2afdbdf59924fda61c9b182e913014a51131b846a8ba25158a79ff86e8b1d922883c0a9159b959a8a36285f511ad327003965a39b14bcfb1a8f0d9e58b656e57a53a274c2b2df43facdd174e97108eddb46e0bd32d1340020559951f07e20097f25f3a29ff342612b95cd307ae706e7af6667cc380f38a2d8ad6625cd1afd644f24e64db518dff42555db236c9a7e927e265f93a331445f1c85caf4179abf87a96c3356117250ac94ad67316e73993745d118cac12bd25fc649582e8db5b48aec77d5f7ef5625d34db6be79072f6687d93582f17c385e60566b68d3048c75b77b0eb0eb8890c4dca11e8bd9a3193be17dbc5d97f064da3e42c75f47722cd285a04e029ec8bfe58323f574a071cfd0b7d9af2007ecaa2d35e467709b1a04b4cd0733120d12ce4105c4830fe69c4740e9a8d3455469a9e52577636c4fbb49abe43d16e3cf16cf1d3c894ff02dfec9c1b68362adf9d0e2f3d8dd5b4d2fe2fb288b63b0f10921a64448a34cb8d79797cff0c00a1b2fbb88999cfd6d17c756a9910d2da737e61457efb573a31ad5af3335754c41b0886874ca4a6804c730b6c9cf69d6cd479f45da2cedbda9206276f134d690c2e29b6d8b68f565cecfd5124e8222c0d12e3af829cca6e891ea9d1222485f248d7e9284c26d32d12551843830c7dd47efd665fbf279c6234a2ef0435217dd4c597a02eb4994250863663870d59835902943fd768ada2991a5cb517b9206b5b1ba504fd925e7b56d2dce53e81d2533d6caa53bdada9ae497babe39c96ded9dc899e149fc5de1484fd074d23adeabcb6969672f97da0a1d0a590499e99c6a1cea55cff03ba87b09113d1ec4c5210fd24cafa12473bdb5809ad918abe2a086dc599c3043ba44dda455df6caad8200cffbed435819bb73844df08f90df1e88ac9373ecf01e6ca5f7797c8ef250b064c5da997480a355cfb4d5430772b6537726f8944db6f7a02f9e2137043d4c2fffc2438e0231d4009ae6a7cb7785cd08c3f11b7a7577f23dc5d5521506efc731063e0399b0fd9cdb0524fa1f6219b96681ac921ff008371c2c59e3a7d9444faeec8c078d2e90fd7b8b89ae04f428b262ca501f26c5fbcbe4faf14268a776fe22dbfd88b9f5be11bd5ab55bf4028ff80765cf2e2b16e88b486b47ab6920e09a864c8ad26a89b28d557a3b55ccb3686cc884fda31fce51adc2bbcb58a32bf686602e5c79e60a2dab9bd31f84e54df995f25d7100066dbd450ac13d46967b4dffc3efb8a1bc3cf84634ffdad3b3a3039f64c3e2cf98eac7a76ea9848e78bce0384f5a2fb2e9e239ad3639656f7866bc08a33a4212e3a59e64c2166bb5bbfa9f0af03b269f61cc9e3cccae8f78075dbfab986e881ea5a3490b08addf16a126c634340ed9ae5b96c7af8f0a3d210a0bc6cf04c8b0e482f6b64ce5efc392b9e1ee0be84f1e107034e15976ea1b617e31b88616523870782e7a97757c6a8a93f2e1631e1ac181949f919ed9973523accba8dd26d9bcbc59cece7097a20ec5f88398e6a43fb8d013f23d16a9699b958faea8895eef7e22d5670aab95617074d38515b837723ac7ef20c54641dba3a88f8a68eb3e00fed851449a85a2b263de324f98e17066d1aac7df3c117f4f3eb025b2d858835bc107b8bca98f2adb2c4fae0971c80bfa6ab8c74d3fdc5d979e756655f6438684c86911be5bebb7beb137e6cf38a0b497443e836256fe31b6eabe2ca8d7a2b144647b9b6b5336b5a577f2dd647bdda96f161d96f9dcc7698cad0979e908e836ed57ae70f1a5ffa3062641c5424cb68b0d0bd810c7c042afda377f35cc502e19d7521494f51494aabf64789715a4ab18548a0bc751d64dee0921eaf495f840783ecb444da9f0b6ed066e9bbcf260f11ff05a0215fdd81f5aa3069a3d5c933619285b82b1375b5545adef897026aa333d0e57cb4938fa6dcd5d5282edf0fc4a64260019ded1242a7919f7d320f8673feeb31349e5accf11446b6b3f99cc05ca11b8ad0f5c0663abbd188850c3f745f958c3b77fbebf22760dc3f4e3e3ba20c19a6119f4494422cdc61b65cbe95a56913de694d9645c398eb2265c7d08ea231e5d9fb5200bba7aed2552c095419fc4a8bed99229032eda98fd373a28d2f4c2f99528bcd3e1f300687cefce8c3664e3ffac1e48f5a3b64ac7e93f0ede7c67444db772261b5f80815e7820ae662d97661230431e8fcab3952dad40247447da01fc42983b822cd5520174c40070ac9e23f137aeb5934f446bb037de9bf05c89c45f0268abbec383740814438c8d5a8325660ba3ae8a248cc2906a82b6503bfa255ca3fcb4d7096c72610166c239fb22bb595f6f05c418c5f120c24f4b620b5af79a345eff8a416923472818e51532249fb6b64525b61d7ab2142ebc133b071401ca862f44232542f160ac4cd799524d906bbfd6baf2a2816584bd82e779d2a579d60607e311b1b781918acabbcf52b9eaca6df299dca2628a3b6dbd05ceb7a5f0e70b286cc40c280a5f71124d87cacdc5aa26ad79e3c16769d4a13e2dd625bc71caea54d3d748d5c5d1365542cac1f1b0d554dc38570582df4a726ce14d5e92220d1a39943cbb9bcc0225a2bee8930dd731bb1e30928de50df2afedb5eb556f58df12959d600b27ad6f8cbe87ed169db3ba00c640d7f40e345adb7a8fa0a1cd8048f1670288fe1930c516028af409b2642b0547b8ed872588e75e79c5a79b9291bb3339d77c171539d8913d88189847170de69499cea625e22f733ab17f543db14a84ee84791f26ded50587f84664eb75ffedab288f0a74e06f1215714ce009861fccb34082970743af27f59177a74fa22c760e2b5267785f6369fe539a692d7bda918c347be0275269923e6eaeb1707edf88707e4c78fc43f9ca8ba0d04dcd88edba061c9629dd09b5ac8f8d04bb407a5be737f21da4aa22a098591aa11325d19f9be6003baa91f19a2bf7139d1d79f0ad55f7eeb458dfcf2fac522ac0f0a6970f66259ca81615713d50a0392ce300d5aad1f2a6d1457e18c091da6dc0474e29f17a5f115efb757426eaabf1d5d5520ff11a20cd67b9257eba9b8dd30d4817df399954831d95f229ad45fa876a671581d7f29afc332fb009b0f81848ffe75b4e4d7d23e9dbba61fa923da3a3668c69d13349ad03f480c1533c0dc7d5078aee3de7376f3277172a0e81a9d9e3e5f6daf5e24417afe1563e70e3f13acceeb90519c00e81a0492f8d76bfaeff815255ab5083d6d1589ce3e760c56b4d1ec5bac9aaabd768fc7135dab260facc83b7005f1be06013bf679c1eaa03d51c82acb8f036ab306db62e501216f8d5079b3a4626ee1575b329b64de96b421bee983e7ae53dcad3dfd28a88c792a7955075e5091001908408dd8932e11e9b0a9f9235298768f201ff0cadf34080b5d16b8a9d9ea2270ecde79813b4a000887a2ac9b9c270c40c208cfaf3396f0f42cd8ee60f1cc4fa89f1188f4dcead180e82d848b1dc73fc1d0017e810676cc3e04514733f3b36ec4f7dd944c5bb240bc1ea22c65db9e3c7d29d4d6905e7e6967c6924453091ac47ff965dbbf2881d709aa721f2726f25a7a14c7a0a96830b54762d91d67b0da06eb2439e127f67135f1d6265f23fd6ce463e0a8bac11990f325fbcee3b63534b1fc1e9fbc69cce96a8421233cc6d705fdd5f7fccb2425491d98820609c21aff2e86fa55e6dad7acf1ad872567678483d16692dee3549dcfdd96c2ed3eb5f958c2294333dd5977396bf0f07f5fabbe1d5c34ed36f61666ad98b64987dd49aa687ad20fd885b1de6ac11412d3f30cecd413312e18fa3d280c70820c34b07c5f65728e0098cc870ebbda140481e322590358f721a76819a2400f4fd84c2129f6f3b32edef9b9fe78616857f1916a3ab642633759a39d3a053a492bea476334be0149410742cc787ab27bdf9364b5281566af89fe916d557f1757ac6acb17e2afa97b8d1a981d850c2a34e0546003dbaf909a06fd760ce911daea3c85f3bc41eeddaa74c37b0f803aa5a91f95e36807b5813d1fc8de5eb2389df2a9a379294f542cfa1d3b669133efcad7289583a839c5833ed0b40077736740ed0699f49219171ddb572d84dc9cc6cc7c1a331c4e6f86f068bc19ea306eb69713c85197c6bf34f331695cde24486fad425d74648910254f09a452326483a2f3a84b952ae585a73c04c76890d6a38447549ad85074da28990815a990098d3815aa0acb9fd379b7d78f852431d928148552e898accd3bb080956f6cca066f92b469d6269e5d9aaa20ba82308c90bd0f9a97eb82a30b953c143c803f398fcd19e2b449f236063f71109bec9d9c23f48c85684f57997f1df7e61baaefcc2609e923d65fea8738c3e2bf31864292adb20d0b2f3ef6e705bccfe0aef8d9277d422676b18bbf3c4b6cafbf673ccc967575ddf860b9b34b579851a948acef332f35e65dcae0876d52d6ed561d51f99b8067759c07592d1bac559db3d7361c3aabf1eb175132b1d675bbfc0e1014ca87ec1114287c43a128df5c48e05df799119465f5523ed89e4cb6344bac2a8b04164ef287ef4aeb24db90911d01b84b124dc08fa85e8748d5976e7d4aaff39bd694bb30044a7492597464aa7221097b072c367c461edf1b7a18b08ee1abbbc5ae13c206d5f8ee2e3e78f21fb51c07d27f1a40b2a769703b12f3c13e43eadc5431729d515a91f3a1e7893ee7d8c52c916d03c418c5a888b26277c66e003754227156195d6f341d8d743d07514e5c4e1109c5d08711763ac2246cc1a77a80882b6cc4935c5134d18486f693329362dd1958f2f54ee35663290cccecb38a248ad68fc88f632ad7d9a2c396187a3edc69c97dcd11272236de739cd3577e6bab7255378928e319dd16b6afacb4feaa1489119dd63ba406efa4e102bf4769733aaebaebe1f2be42f0e08aeb5e0e6a4b69800fc3f967de8615dfed3825e8fa9a38bafa3ac43b8c82b1159dce489e0f998c7c4c435e5d1fb1c6aa07de88b1dcc905d2371cc2df01571475f86571fb153357015dc68f0413fb299bf9135be242ac569f3d26f09e931ef6776f7e0f6a1287b4550fb1fb0e25ed32290e704e45d0ec830f858fe6fe90fd5ef3af3d0f3a066e8bd9e3ed6722916053c19c6e632df6254fd9e7c541896bd9469767e9b43f5ff28ec2a07491eb2c99f8345edaa6b79620d8d9b32dfe9c4591e81781141650ea0e0e535481ae59ca7e185d4164f1dd50119c6c9d1c67a847f65159d66aff80ed3a54ba39a6b5a743ffc16d18ddefcd0c7ebb98a428c7d03ea008f2247bb632f9f527b00d5fb9110b5b8b3a2f50e212919e4fb22f92c2687072779a5ceeedde106a0c64730e00a759a3bb3dfb7d94f3d49228b50dcc2f94624cd7cf6841155ed6719a400d0ec3e21ac92eb5862ca14c80a077f23e0a6aaf3c9aa6f38b908cb7a21f907e3ec2ff1d35d4eade683eb79cabef685e41d371b49e485311279f833033ea4bda67114d83f36e5a6bbeedd83c2c25b4a865e51b608048ec16cb390e98a16e7d63ef680fc6085596ae8b1f505a93e59976fcd534682097ec2c64955397c89f60134437b454aa06d2ee83ea70b3a4180882bcb65855dfdd7cef60f26d13d6ce6efea07c1f99f0fcfc4bd97653de3e1a103f84c82dfd74c4cb5c4705050c7afb9b5915a0ce931997dc4b429fc8df1475bde7db8bc60793b2fc46d41b305feb6c881f3ae998e75d1abb7f4452802d392eab1f61d94e14ee46552369e3b4ff16d440860b82df522b461876401bb955f3fb81381817d2042ae1703d525ce4c6961926c446dac67655f256504a4b12a7c683aaa491bf3601de0a02523f38901742c670c5dd7850526cf3cc5113e3b444b6a051e3975633fa28f709333f2e8152266db4cbad51a60e3ceaa87f441bead01039ce46f1c30fa614904de0b1e4181b39628932e2079cf871d9b02bf24f034c089c55f256e92b633a973457990ffcc86094805780970900464497db7241bb0def71b1b22e75e27db6a0ac170f2e023d4b4dd6623fc2b9839e261dadd396bc5b7be2fcb31b6debcf12f717790a012935eac26fc5be7912bcb096a61fa67841a974af74bc03618cc31ba2adec6a860d83e8c2659e18d32548b75e03beccc1b2ac50a128aa33908b53ba75cd5f50d0ca3bdc04ba07a4151ac825c1a42f46a5c8333f363ddef9e4272a5a6f2533e6b4fd5a8599d558d1e8af9ad6abfeba98961f2c43bbf0442e22cac125ce380626f5eda70c52d35e293eaf6e6feecb4188ed52b7214358b672d5763401e7766b65cdd60b8c96f088b58ffa2291a1013ed0d0028273f0594832d14addb0716b0e2405c88fb961e57e4acc72d36f744a5b11f98291231e994f48cbc6c6500a8e3aedab0c104ba684df0a880f19958be09512d60ed407e36a13634a2a2bf5e8d044ed629924b26f6a487d8d48c40f5ce92040f9dd5b5ab238454b60b5f5e8ad5ce9059951bdcb755ec55a1bffce3ab70ad9b72c4b40835a4b2cab4174ab01474efeb74980c65278059ecc381302e9ace5c51509aae669a74a5c8c25d94850b2c2285d976e4c73d136d5239cca95c01ba96cef5061bf9f7db3253e75f4db56a625bbbe63e2bba487f4454c6da4888cb0d17c9464a38ab11ad6474b5563808e22344c0cd2a1d4b3c466c8fc449814cdf13f3839ea18f9d5db6a1168e933637d9c1ce411fd521bb295580f2607481376ea31d85e19d607336d8559e77ab4167d80a4dce89065c26a9bd9af97484c37231d03f4d0d6336088c3db567ad396e81fa2daf027556397ef957cc7467d7ef54512ece219d90baea178521703a233be42a55b4bd822285de85672a89f00080651fb059c16100a5748ee31c315936ee3da55dc89bbd50d385f05268d904464ca5415e66a425632e7fc4d55017243790f1cadb9731e2ba656037786eafd6208e692f48b9d3ef487d428e1998ca86e207b8793232f0b51f58466c322d8fdc30bca11d5c98e55bba4b70b8ea86344b70c3311542707c9a22d2466cfb3fa5967e199385eba374f57d674217c0ee4c79b55158ea6323d3b7d56de4b669ebef2b16e3f884c7087bb96c0f271c80065948b32eaa98902b7ce02c69eae0211269b98a136c2ebe5b9253231f6bf36e68179948814a069ab8f3f14436694d701a5406cf0773702721c9cecb9c859c13ce97dfb51d7d1ab543bb31df747e25f25574f4f3a4bd53898e78b22beea54b844dec50794b41e879f79d01b0704287495fa3bf07c3f7e3375a4c2a95366fd7fd374d73adb724b00860f8802b712797077e87077e7e877de2ffb6f57b2eab9a7985cd1a82b60a549fefd7aea985220f8453b1a838045d8f072499665c73db59330fe69186885b5cf005465b06827409f4e113ca86c8c1067a55510b4bcde32d70f9b1ea4524ca17e83f787fb86677d47011a31296de12144fd74cca5ee7edaaa5095941b4f9ee79abef91df509ab982ce3d9a90939ec75005387358b4c45a607a0da44c6b8125dfe310f1d7d53f9fb856439bffad12a2a56a3a567ceca6ae98e7cb5b8d003ead8523413bb5146638b979752b0d4f8d243d2dca9f3970d3b5efccd9f78322bffbbd06a789fb248f5c03394074368eb8640fa83944ee901ac9a59ecdbd0a950ae3b27d612010ae32f8b6b83883ab003b613b16e042b5d92ba056aa0ed909c5b509b66558dd95bfd5a04d6e28f35fb58d078c948b7ae6d62ffcc51a4abfbfdf73bced874c5cf6d4502126b014f22795af520429f272d41fd251ce6a21625feddf60166f42e36752c819a2e4d417e61bfdbb26fc83a634416bf19fdd8b53bc2818b1573e4c6df0f2566c00dd28011da64b617299c479541b8c6fee532d45fd58f85939d1e0428d45fef93161cefe6360aa0af7b40144b644fc76b5a0090f5ba5eaa9f3cfa85a92cca75f4b3f57461649d8ddf1d4a7a27930da6eaf090cee584e0b71adc700d528622484ff32fb0d6acc94b5305b3ac50b59ca5a402d384405795a0e94971ac696df9e9a2e59e5d7af7b09f84fd693a435210e0f89c92302d93e5f1e7fbbe7213658225dae95299eebc50843246d5d5352eb86dc130d78b330fc2242da0495a3a3c1008a2449f9cf79e3c8f52a36257f6cc8671ed9aa2705a3ff54e3ed40657a459c61922199b90d1a821cf0c7a4c9db8e1752a85c7951a6cb8579a02a79a0ae71514dde764c5ca64fc05eb0551bfbe7bef16e59b1f066f1095c8a88d57901d239ae07d3e815cf4cc6cb9c1cce0f608958662ed71bcfc775cd3216bfb50ac052ce107d05da948df65f57927b45293073d402be14a13723b28c3d3ac70a7bf584f91b9ed4bbbbfb7165c4f36beed825762fe79f560f31f0bad585f3e22de4c2e61dfe7c84bedb873e8024d3415d5636dfc82b82cc810dcf8c3d80d23860b2f12312e62091ea75d657f1ac881ea1bcc20ec8550ea2a24a1ee74629a80e682193ced79036b44dd3b1038af6ec246f2b3292f83c98d9fc4c991c01c8b158f0ced628e0c228caaf6ace2f4bc34785b18abebf02a9256367d2eec7270c98b32b0c0f66b3b856ba382136f1b120351d1bf56373a8d4b8b3643c902f1a2ec042f002cb50b89f672fe2cb147fa0cc700a95cc823caa0cb034ee2b2ac8d0c2c60803c4b426f1f517a3aedd28d7278ecf3f1c880fc90f8ec49437f4cd7de02a5778740191f0e1eec446fecf19c5d2986bfb9b979be906cc517607e741a61cb0141af07c146e9908ab31a5fc198635bdf8ffc6d68f398a13de9b7b159e00d7525e34a31f4f5d3f2e4f025cc9b8e28e545e8f71400d8992448929b50209b45bb26164125597c242d9957748ae0368e3900538409ef38555a2cfa8bb82f51236c33191111bf7ecf3b255b776354cfc908ce5b67ce13b55dc150dbb3dff35c4bd30f4967c7f7fc87b54d04f2f4544f3c8550ddd7683a7691086d9885ba2035fc2268b574efa43056770781ce310100cb4f0200f71f7be0bf4842dbf32583c2062dd142b99954444e4ccc991c88667383c3f6cb26a23981bb77e89cadc00f921dc21692c17a97090146d90886c6adcd64420a72ed7ba1188778642f96808b91817ad58a7d146bdfaac548e6ca09303e343e379c441af08efd1fe6ec0bff7afde43ed296ba52a015be94e3affc8eafbea37aa6f35ce11c9466acf3b3b8b6dd8dbe5de3b17de75bee987c3ac77c0b9bcff996025739be8904958527dc7c14534eeaefd7dbd6a5c684c0013654c6621d3a8aeb2216e2b47b99164e2d1b350061a5055abdfe445bf445bfb8798393823d2f04cb0895eaa82042e0999ef2237144c2dbfa21a790c707c0bd908d049ffef722204236a6f5532959a05003d94d0836189a84585d176939ddf171d93dcf88b9c74f0cae848974dfa3d954e2f2ec80c314d68cae915f052f378d3995940ce10bf1f4a3dc3f2dd9a3f803945e8dc835d67666971d8d6d92145ce0173e15018e976473d5661247c6583f2b69b928980bf585a6af192e74f46e7037e109c758f823bfd2038fad4b362c8b60c433daaf2f2a61acf661ee2f6bedde2a30013a8291b3ea8a14403fe3a6c56ec31432ea47af5ec15f742dc2295494228cbf2ddbb613262fd76e25fd73fb83a13e5badeff96e7b580ae20df5898d033e86ecc378aead6569362fe5a211a25c3f964cec72d491009e1ad29639708f93e3aa30ddd21f8ab3bad1971c0c415e597fbc312264df7df17d0dc37428bb874f12a73c0bcd36cbcbf315a1da0337b4860fbd6f167a14de0d5a712037054befa39a165a75d87d4c59763c109d9e9ef701c475eec5f2543995879c93d005a6e1316c8e4ee4d0205d759df121487c861e98d004295f688100609a9f01e890e5a303008057838a1240094ed68b67346f1e056b9e347b3a89de95069c47527cc9ce897cdda674c51dc53b66b8f20b4120a3588ae2a91e173d0df24ec22f700fd7692d0ede170a3e8dc3aafb4cd505968fa34bd3a53535a6238f5ae0c409b7ee4228875a3d654acaa285956a10eaacd7cd35939581c6908cd8a198efbd9e002f61fcdc184cc7f4d90cf4a2a98d0a80a49e5a0f4663af867d11422ad0148fb1bed3dc7fe779ce0c729a2e12b3e0889cabdad95513fec6b5bbd58b96e09f761981ea3dc1d1f0b8f30e8079d22f3f2598368cc0c036bc4ad7026bf3e451d9eec4b085fff25a2c0941b9f75ba92715c5633cf6237f335b1d5f60a717c49dccc017008fa41f620b96a97cfbb9c7b5b2b7f9b7bd5d4c78ca2a8f25d7666f2413185315c0b8b94508088f3eaa82eea2cf47e09b26b3cc0b6473708103fef247b2997d6d8b4f9fe8c153e4e73720a0639c50a24be8dec5ba0cd25894bcbb6955daacc350895e9f9a83d0f6d0147598194fb934127debf5bce6c5e32b549aa367614896db2388a5d3d083bb7e4d9bbc0a7ed71c60c711774c539db6d0740e564ee183bb491e106188dbd09a92568570e8190db04a282ea8527fb6228d1045b6d01896fd32523a920511f30dafb4001ac5bbf29e38f05db25f146e3d8670cb5a5ed394eed8a3efb193cb08e3cb5775ece29de4537b8e58849f4340656cb7c82a99b43a24b65bc5e11e356aefb94630fc4201a9b68d628a0306b1e89a79f888c56bb2139ae84aaa5475da35c7b6081da86738a139837b2c597ff81e5b6f72d112d1450109ac3043983b56c456864a3a5750316205915d02daf7b84632278a6157632ffd515a89b465b515d27df5503ddd865a34ef562500ecf769945b421a370e2b4dbd8cb1a9a3e935b48191613bb15c1bbc773afd887dc81da827ada3ac6450765530420ee20ad8b4a977d0382da8217a3bec714a948fab92e310bdaee644faec294c6832bf064d976fafe08f4ceded23dd742f0e14ba8b5da4f2d0925032294ab058c0dde8129377667a3563ac84a392ca1510b1d463ce7a8f518238504234ace82b9518b0a468aae323e4a571201d102a334ba9de88bb8a5fa17a2a5a21ae27cb3319872da56376501661eeb5856d645b95c90c7480fc37e5d4cdf3cafab01b6fd08d6f6b6ac45d158883e6e38c7b4c1a272dcf4d649943440e830d9a4e62dcf9d87ca0de0396cc61c1dd3fceef012ec70b9e44be4ce740b30106bc0c606784acf6c833d313fc29ba6114105e5912aebd85bb9b241604fd1ba3d528de1e721af2931fe2ba1453677b71d8e485217dc0e11fccf56ca6770a2fe618b36625a68f91acce32300caeae87c7a45db5e2a96b51c62adfa02c41ba7cae180075b8e56fac3501c2b589c5a834b32da15564ab9ac0aeab18a61c0a731883041a29a2208d3050ca8f1350ca25a69ba100175d80f82477b14753dc7580d036d5c32391d3403b5fd39aaf1c14bd97fdcc883e2e36411e9683f9984f446e8b39f79341a8fd81c63a958db96b6211aba6c12bbf3a061fdb67a674af025fb51f313f9a89cd05d9da2e752ea50751c8b993f69679a0dbb4a58f47821751c4b4852bfce3a316d6d218a0b02d8094f7119b2f871b748e1c48e527f880660479a7e4704e611796a8978133d13f054ad221e051aa37a9eacd126dc32cf2d0d09437eed3a4a4e6c4b7deda960000bd272ad6c9a41aec5ec1a4d08180f4d1b1cf9292c1c1024140dd0b2d14368978a6d713239b6edd6c345042bf6123575995f5212cb2618920c78b1c6b5030974acb8a2ab856c8d46b541cbce46c3be5c5c2de00a8e771dd76d5d4e0dd265633a29dc2194d3a713e2dc335826f5035697a9904e52f665163624e8a7b133fb6e1a5b9b9b35b0e53dfb81b0e7f83b709a5cf7cbc128c36b6bd4a31dcd86671a2fc6885ef099ae8df4e80ca3bbd252b9d08818a9596ca2237a0c7d9094384af7dc7e6059b33c325901703581d55714d4c1db41eff91ad4579956dc653ee5552d9ff7438fb851e605447d4a5d88b94f446cf54626747d4b43125c18961fcf23547524245fcadd52b94ea91b48225758215eb6cd99cbf1bf33236208adcbb62d06d28964753abb62a12888082fc1718d8be7f3335845192f6b32f548a4a9bede37450a3de521d7b8194feee9f6b88e8fe262e93f40b93bff2d10a6eae5f6fa02dc90c7f8e35de2c2c6f0b99114b62a6d66ee82c320af77b0c6035dea9a6614f668655df1ac340d29fdfa26e9c0b5164d5e6098d3b04241195cf0cd9c117df04160cc768e0bf398f6a234469b4190446d1e0da384f6ccd686976c6e6c3f45875315666b1f090354aaedc917d33d004795aaa087538ae8b2a1f353f2a9b402ce7bdfbdf33b8bbb22f4ab0751db41e4bbc3826c218d9c2185eb0c3520479a8d86d13b00c57799e9b586d5589a820daf3bf2c79f118ad4e754bf610dd046334d22102e85325c061033e7f092b8befad31909fddfada014e562707e26f7407c03d88fab0b54435cd747bc2daffda9fe98239fda054e397db73cc837859c7cdf70f38e70e4a7285d0f14b1c6ec26f955962ce9a4a63fc3dd325089a211fc39cca3b4494f9e6a59b2040b3f30d6d073f2ba8b2bcf432ca8f68291cbdc03052436903f951e63663f9c8af86fc032bc42ba66d20bc7281779ea60e83183d19a0b9c2e99720bc7e8970167e08ad770a45bf5f85707a0e60b7f6286b86a0bf627765f14780d12fbfa20876f512e43e31972712fd45df80f22391d20fb084c06f731daa16a76f94e0f9968f731b45ecead64c399ca26e02dd058910f4f0796118ebb00e0fef85e2739e26f8da160a4f827f12405f2a7b55a3b14edd90797efc492ab87926ae24feed1d9e862fb61e1f6d4c9fe19a1e17e9ee362e2c601b9dec5d1a36dc4d947329082c07849170db44641a82d1610d9dbf905795ee00705c3a8f4d6abddbb8b66fe22dcaa1eaeec27791ebce1dd58eb2828631bd5d47e6d6f9c92631dab22a4bbf31265dda8b08e419ed7210fb57642e80f60382768622d8ee531f3b123f88cd89166c45eef0a84a51f2302db22cb238df431384ac30a34cd9cdd918d4ad08cedf638f066c373d618d548662a2bfe6610f765c842373753d2980f47bd46723c2faf0ee5e144fb6125379cbbc90b144867c8d0ed806ac8dd9fd5abe22dd1f1ca61e4e89763a428d096b67a2388bb6f6f40fe7a0697ca3763774f3e8dc626d67cb6d925f4039bdd737fa325d65e7f4e68c45496c4f439ecc9ac98efc91af04e88680bed0fb246f5a6cba163dbf614f49fd344f1f8e818ed253b5d5311c52a879043552b47f8d4f282988e91280eb829ddf185f97947136189075c1a6a892b9ac0953f862dd221eb0a409172df5e20891fc93953c19c4ddfe5956f84eb01b20bdc23706b752d0f731764510a7ba2c98cc0a9e90dd0137225afb3dae3b44fa19d2fd20428861c63f079710ff4d0733e37f959137d724756e405695a7cc10a4968b9831918dd94040a904f1538102c077b91918dc68a5175644cfb21ac74666cf0e6051ad7121d7135745d3e8de26e4eeda9599a0a8be5b27a6cc13f3cfee8ede5950444a0ed57c5f99ac01e8843d52f354aab41df6e66a32f895536779318d93cfcc1e649741acf1a865434dd2f0be945fc8a525aa71f5058267fa186f4c2131b4c19fcc6af13ed0c9f06aa06d1ad2e155ccfc81514436dc298017b35d5667b0f1d425d9af9d254e745af363c40a6e0c9b4c12cccf8a60b98c4831e27e61bb2388265113ce86b8fc17a75a6d22a54763f090022081824e98d3b9d81979e78a12ab9ce3ab79c2caac02e6ac2b0aa4d55311bdaeb313d4b266a9e0a487082afa2108e2ce9b88d285c9aa90505b5afcd7e296c5e2c014b9f59d93e1304f1279de0f7869aa98d363370cd083cd0184e679354439f7112f8f3e032203104941f12cc1d7b1817bc558c0ce571e6067ef570e176221631ab46040e44237555eb891e01c966e2516726a753a3932262c21bdd3223c3296c892d31078fa5f644b214f33fdd446666f58c7f7e5887d65d3846e600ea8e714f995d7a83bca04c6fe7d74617db8d42da4a29c190af2f89849d3fcc12bb70e083ec893abde1624d96bbf0b3a50df88ac4f8227cdfb9c378cc99b8bc2e5cac595997715cfca6a47f3aa895e13ca7dad6a27c9f09d5e930ee83316911a2efdbaf26c801b8266d83cfa4aeca82e44880258725ffa965a9f227a232ecbe0e045c4af991076ff17982bfe9ca40df34b0fced0455223ddad9ca51a587e38655bf29ea7ae61bc76561217fe88dc33f9b0c89506f96f0de2ec920d396d93994feceda4128da3387b7bf336e083fd1b28377033978c5d072350623ae3cef7ec70858a85827f81ba9e186619cc7d3d95987996f0d56f9a814285398192b06dbdfd3469b1bb5e82e2bbd7d1533562e397f049f7ad205a03b79028d069d471dd8c7dc8b7a2f30396ce9dedb8e824be8927af996bc000c7f41af491543a7b81cf3babf28d19a16bc5aa0119f7d1d3da91249ba78aad42ab91a0302f9364fb64e8388d9e6ffce427fd824dac1767e45166bf2b7c67fc2027cf82648f4a3beae758477d51608a39c3396d1ea067c617e530ccd78c78f43eb3dfa85292f558b644ea5b70d5c5c797f16e41b4abe7b75e5d965d484224f3004f33a3d5ff42a53ef8b983e84a016708260120d9bafc72950cead90c0cb477ef66fc35bb102a6d259807c0b7664f1292458aa212f6b5b30cf7f0097c35c917e31887b5b145b99988b4890232a88c660dd6d797532db9d6680f70f51894bdb6eebe7917b1c2c2320e203ed01fe494ac149bd84d76f2534ab19eba36fa8a21a4c055e2b8828a8e0eb72603f1408d8224c34af82cc9a8c8859c1397a8ad8afe9d817509e7c00fce80cc2b8bae85aa883931b49071389debd773454531c31ebe538a10ec7804cfc4fae4d3bfd8ec7893bd23e736692aaa5d11d31de8e5612612a5402ff5a856317ceee0ae2abfb0adf3c807f11104b876febd4de1f610972f75d03dfdf4c9aa7c2d5855ebd1a9c831ae6b646099bcaff8a8be8ce96d6e37a67e79179c4dab78f1e48b52428ad4e0491b34e875174aa04e683927d9d0be4dc7ed78c4242acae6fa31d46ed1d686f2ce2c53badf3f7419f2464c20502a0718279b8ea22c5ca2a1bd9b6d6f6fbb5b9c78b1ef37e147ca24c5da3020b09d96fe08c256e920c90c6df31a8093ae1f060e536e89c6660d4eab4208dbb0d8b1af9d8a9c110d3c2469915ce3800dd8a1b3385a42d853639a1b2132e5812f287cc1d58a30a4a301bd25c1a2aea82d2a7da8ade12156dc83e2e058ebc6b29cef951736bf07b495014c95a19a614442e54d262772a6081824a939c04b01eb4cb9562d90969df547d332eb62fdf4436e68bf71f6c2ed11a9dc8b1dc38df90b11f19ea205a527e78690cff732a361f5cb4425f56bbfd4de7be79eaa8498805737e8bae3c10d81cff19d6045e937324280473101c8881a54d93018454063a5cd01bb51142213e9b12a8c444465d4e6935c6041d29c980a7fea6a139a27a48f7bc9499991225c5b9d15ef492cccc7ac9006fa66d17190644a0f57d015c66f53fb9a9683dedcd6c16bcfb39f17a00f176379c5e99c11dc0b96710fc9427cae249122b278ce3861df40cedc0aa65b675b206b7dd794303b234dd70e26e8688092df9bacc996142988049686903a7e1d044d0908189cf495aa517b7a6054348ee7d99689f7e27e0a17e35272a05abfd8a4f538a737f6caa86564afd0a99049a3051a6fefd421d8c9bf7fa007f9a416a8aa9c4a13203bf60a603f07c3f077fe163363afb1f77c09c1327a0b1cf7f68a7e95c9c4b525ef5865028d4f38855da5625248908bf3f694b48a120436c8340b534615df21a844b433b48c9ac5644bcd1cf11251edaeca4357c5f272c554cc1972d518befbf38296b5feb50fa4ae1e96b183d3fe607853536fac47de3ad2f3c8a57b8bb474596cd82cf400f8f9fe1ec09b12a77924026f604c9eb8969bc66293bbf6288e1286f9a356b2b4651a11da00fc20750bcf1e1fa7028af8a6b59890e047bddb1550eaf467dcfb23515de47c01bc2f3e3fa00fc3b9b2001ea76d0538a42640e738e40a595620511ee47a316b6a3ccb0d4057f5660151603345515a779749d1531284553735fcd49bda57d97ec8f5b8a65165c3eb5399520c83eb93df9c177b92191f68b2bddae390ce9961a29aa8cd508a7f5d79fda5580fff6014a8af17b552cbeb744e6e538f9b72ad0196d33c3500a12a3a67dd7a72a8641f6528aa8e15ff8c807a28ac0a406bb24864aacc4837af68b67e2129910fd621a81bf728cd1c12804d57e67d29e67f2dcebe50765e0b6e5367a225fa664fc8533311f51945f8aeea77e26b8aef1d4d04928fbfa7e5383c03913b2cb830009acfd218d88d1f371ef9ce45836c5da8a89d14d80e708b08e9ade18662393fa131739a418cb13f6cdc9241e1d0ba2361289ebc852cd45ae467b3fe521047b7f2ed6d623e93e8697d27cc1257f784758f53ea54da9b4a93500bda743da8b47bcb88961994ec03bb73e8a988e0862fbc58285fe3da67a2c62004020e8ff474d0a4b2ca35efb2447a18888940307dc4b689b6b16e9c197252087d8e057a6e9608d159f15d9acd68b0955023d1a0c171a15a75fb215ad6fb540f88ca86982e6dc4fe112de7a52b53faad8032599720cfc35598ec7803d2a39be23ba25f65f240b7ecf22292361c85d486567d8faf98f63bf4f3e9eda1505a0f704b09488804c0d4451ad4ab4ab35b1ad29e143f204bc0f906bb04042d98de9294dc7cebe47d451b84a7b7cf8d4f4edbfd35b3a12ad38d9f09fb2b1ff59b80f7bfd04cd73bb71b5d00c8d91d13fb45c6313615dd6808fb932b581895c4a05266f71b6dd8b9cb689edf2f7e088dd3c724b3188ef8dc5ff48ef60dc5b8e350d84c0058179564aa4331e97ebef9892a90288826c8fdcb00346574a9402bc42290e83db3e80103e08604668b86d1971120489b3cbe23245000ad696e018f56151a4ab2549448ead1041ec074005569228372aa157568568a0b80bb0eb81a8100c99ac1303810294afd80ac1c5064ad8a71b8a8e35be1160478ef4418741d69e13be1f089e80f4cd6f97ba95403119d751d019812a1ae84cca8085aeb26f0e9697a1008414bc79a261ca98fc01e735826421fe74af6c1e8a3397eeed3c1d7b6b0461e5a3359c748b03921ae82a4431ca2ee4b0f8472e6f989a5f7a225c44d47bdc5d5f93c202d921f7980338fc86f738239a27083120c2e88e9ad20ac037f36ea16cdd59af9198d76f66eaeb293332deffedd5b8dcaa1bc09bfe4e7d263a0d8e2d06c44b42f7e976c5dbb09fd949943035011320fa711df2b39a4e76116aad75790572ffd47b48f4c6e893054b048f0085dc3f24e9036b8259f91bf0dbcb9161aaee6a7dac82c0bd216c586a1230484237bb183d2fa2be835f0ae665b2ab6487dfe60391a4e2320a93695ef675c81dbc5f31ac5dc0436350defdd9a6248491f56c64633f108c441e7bae7162358054a29f53c8d3aad0c2dcee2f6a809d1df7a9d241b352726eb891c46758cf5fb7e6764ab2e2ced277818a3e1ebeaabbb01e2498e846c2c9a25aba71d680d3d4acc7efe2455efdd5d4ae9c30aa01b1e9101ef4f3bd166a3570868c16896081327146dd78c063acb96035901835cfb36e01bb32b3eb384755b988d7946c8fb28fe79689741f824e15520a0ef060edd3de8664906109acd1899304764a09b5e6d13b05d9e99766bc560f71e426ebb5b33db253a6156b6f15666515243c629f4455f74980b195e4da3c11d185379fc1f64d2464eb4a625d5c74059a7c753455be2f68e2218933d71a0c303002a36ba5c294c50a97659c1f2651b2ca51fe0a4fb9ee925110615bf935082d2d04d14d7efb8a8d23ed04ffd4a032e0ea41cf134a5a9d7cf3dbe839f589aeaebb0b40ecd56da8a5e3407f5b0f3367c360f0d65512b94461ee4828e785289fb1dc6102ba0535517cae97441ca64684a1e484e0da27fd175ac4f8f68cf4d15c648a2ed2a7aec0073ed985ab287bcf010d2f5949db18a90566f0ead74325e202a87c17e418c4ccbdb08b5df8edf011c0498ca00c6391ce870a89c7841bc2c3fbb1b57471e127819b306a3993e31b49513170f1f1d1fe2814fcd478c0e44646939b24194e95289fb96d268a3dcd9dc83ccf242bd338a40093d1f1fdb9cd82602e3a2d5fb3c8a1befab9faa91bc1f37ad713efe983414acfa37c8c79023deb01396005ab52e64790a8eb2f12ac1aa7d39cc613e688ba60406605baf5511e530a4a7a60d370f83bfea4b8a3f1cf495278d9a77aac7e44e073f5ae80f2c67b8658fa59285f41c91cc98af6495924387ad1fe959023f666dc3cf0960303d27673ee3c9aa1b1bba7c2000a333f8f33286313d9574b9cbec767eb0f6d11d0702c347ebb6faf6feee06db67b12ed0721b0431afa44d5e77f3420cac40e73a10cc2757613bf0951d7abd6cf4be315492c65e8d303fe4786532a8990fd72281d197138e59eda4a3f3d4445823e0e91f1af81d08a99bc502f217659d916b40051216f9540a3d7e5215bc2250e37776be06c894d59e1e6a025c3de0b7558593cd7cad003c9dc0f6e3016dcf495c921210cb158d52588888b99204ce0307f71f68db78a60c2a1fa7072d8b868e1226f86d1dfc8625959e989a10b19442d74bf63ca740d07a6437b6f59276dd75d20ca9ef41505452fee7090144bd7dda99ec59446a8ae85fb75e7ab4eccb36332f93377ad2ed03818bbab0b22a5dcfe24aa38b53e24677a27dd00d1835eb6c69a42a9aede349990a4a1fbd13a687b4360375a90610fa9830f788d76d89ee0a98c2b84632d92390b89e0476386131934966291d46f3965de57bd1300e9d0df5034437af462d7a506f8f98cfb5ce32de2cf4b39ede5d5eac395a02b5122040b50d9e6f7a539c8020e2a2887e39bdcd5cb25fa860e170a638816b99d3f622a03637cad35c90d33fd05ee6bfaf6952cff0e28d3d5f2770214a4a5ffb9ca5b62feb3181f1c13f15a46cd7a6a5d7624ef47e0ec39fea64426634b60868460b077662fd9ab39e6d20f02b089b56b18eaca086f42afb142699ddfe5a4ba91fcb47f406b10b3daf05dcec6d501be1cb7a9e7e8877a3c19977ab7111d362680321ad87ba8f198e8de37e3d8b7d7fceda43c6da8cd6bb1b036be4aa729a248e626a90e6fb18bb3e3e72cf2e0c1d106b37d2e712c9623e7a3d03167eb19041bfa2b460b0235378dc58651641fb8779460dc3119f270d88771c5a63dbc36e6c778afba67029ff9d5de6f3c69dc08edfe409a93afb03268209a4129c90a8f8332141a7888ca95ce6fe0249dc1fd4b984914df68020c7430da52080723a4d11e733fa576c73417c4bb90143f4255a6fb1f8814b8f5390a9d9a5cdc1797d4af79bc30d6440ca58b33e727866c6b4be561a361b888d4f844edcf9432861cb98242ac7301941c491fb5494326aa395c64d822a317f8481ffa8f5df41c8817d64ae92bd176f22c6ae986b7a153270f62fd7ee2c0b00281e913eddca47d7608cdb76ef133a4c2c3c2b54b9e9d51d2228bdf84922eea07f51589a2aba116f69cc50e699c8ceda118757f3e67ef7c3fca2651e12ec32a16edbe160e5ddb67870bd2075634fba2d2012beb357882bbd38e08396905131b7160fdffb86beda39322b2139f9602147cc665c01c3cf57c982ce88317459606fb723463305538df827db48308c208053535d7682560a239ca198b3a792ea8a6a2c36a40fc4fb276bd8caae2a63fab88f25a2fe0de96c227b449f4ee323df702bcf4a952dd7c0c7f739a269983a04e6becde41c18bd28dae27ef8b76be8ae7d1b72d1f9c808ce89f27bbcec68c3cb56774dd834c3262ac968fefde6d0a1b1818f95ee581af8fa7cacb0dda3cc2ccd56ac041221acecb7f60b0192920568eb81c674d85592a09ab977370cae4ec4c09ac3fcebb7af4856535dcc07f86c06786014d30441251cc3d61835f2a960d2626f87567458720f87bbc861794b751243943885c76c874da90ee76b1373740c4eb9b319516b178b75b32589dfeef97ad9bd23f58c633b1c89ecf8165ce253d5b421b27ab9ca37a30f9b046506ebac01a4fe8166606047a937ea2891db3da27229ae1a689b2c2feff420990d05eb152a4b29030f28f580764bb2cb99d6bad32988c8ab7ed79ea048403a9afe0c414e28738cd1a2893be878a0ac82dc51052e9b7c605abf10cb2b80c3ba70339b22023d438d50be06a0693dd9bcc4686b00d41142c9266a37262a5a68f010a336b32ed884c4dec71e0bbacae3737ab365624b5e1025af7deb22a61f93ddb949e9d76ce8911c8e202d3e4a9c8a193dbe1aa16068329bbfd4d572f467836298a19548a110cfcf60d2f4c59e987e7c0c61b9af44da499237500cfd5dac73a017cbfd4a1b1a172b9eadf5ad8f3b625df68401fd301265621bc3295fe83e9e43de351a52dd8def6cbda7787774fd95ee436442d896ae4d9ee25fd8c814a06137f287460ff8bdcf246820ddde0aa741ad122970ef9623893634e27f5e43a7fa888a7d5302b2742f44c5fc72ef9ba1fe18d02af752c0de48afcc00f53000d0d042e3620514bb937988f28fb497348c3dc2fdda41e1d37ae7a7c1b6bd839bd0cce76f62bf45668af74766708c69b251ea191dc38b612995b5f055ec57d3c308418e5f854d7c40cea5f56747a945e87be3a9df757f76a2cabb44d2d232ed914ead7856dac7b07b473e6f4d79ded597336946632b4493a6d8d727ce956b953b13279b483440538b7e4e3e6998e07304fcfb92bbe717fd01446092341456d52ffa92d8dc85649e5beb5c98ad06614fb5871b7e5b745d89c8196a01f56660947458d14b5673e05b80a2df3161055cb7ce076018d84d4b05829679849f12495f279282914e0883b12841b5d425ae474808f6824d3f61e41890f0612dfe04ed26159382b52bfc50879d9e93ac0f93ca4476b971094e5b2565a67a4e68b86dc614b14584bdc9c280fd2a82bb5376f1759d8ab99d4ea1626178c0ea6d11f3c5e518903d46cb1ed00c775afb4a6e19841a39d3d33c26d26d06c658f33fa91d83599b26b67b02e0c79a7d8f88316df24baff79e547a35b7b6bcf0b6799b8da97d125326f6438fb65be8bd4edd640df3bdb32369bf6e5ece77a772a4e80e3005d8015ee0eb2ec22cba0490a2e1b19a696c00388efc9e3d99d367acd16fc31180478137f43a9e7c7abcbfe291c42524aa3c1487a6832ea84aa9b161b16369fbf52219e4111d584a58ec70d3f1f030ee395eea8f68a07c39c004eeb3acc42f0b24ea85fd99a116b364c10ae4af18992b50128985edb62dbc1058738f43a5e79916fc7b10d1edf49093cc890c7a286ed0d0a0a93b0efc6d4ec81d2013788a2e4cad69849cb7dbdbfd48cc5b1d2e7c2f79f16cf7320af99128179b78acfb3c3e6c1e396992a13a4ca653c5c548986ac6a95e52ea7eed18037c9c9f27eef52bd379e9552979442a4072dbcc31b2fb22a97c049c66721fe4c6ac108dbac9d907d47ff4b6df615bbbe2cb8d42303eb090ec9a143d325977ac387a127874b543ec2d11207b283fb67990a6939175457c56226b15a2e0635a116247393f80579e58ff89e9891849b50add939951813c0950fe4a12c9af872d92d14840d3a564e4db374a7a9d8d59f0c08301395e0df233dd4122e548320e07c1ad332e948ecebb6d6b50a523bf1358242a0fc00af32df2306f34613bf564a1086cdc09da2c6177b633bf201f225cff6742a9cf1af91d72454f655e5cebec8f3f72373923638fc9510c652352e0ef730ea86c5af63bc241fdbc08af021388fc080004ea8cb1ac2d97d9431f6b2610a0339c21e7cdcfe39785e85779d41b7334a6487a21afc7f7b9269b6652b237056192b06ebb82c3a3ca1bfaf784f0a483873c98e597464cf4402c5d5b5ad692a7c65953ebacd00e5e472135c5477c831b43a6234958eff77d44d6bc3aba2d8bad5308f825b40d7c2ed6e332ff4059f0bd353c1075307516706b40c8915d2d544a26bb9d94f0c28126ec9d52f7ccf95fcc8b46e54f42a6ef2149faf2082aa237c5bb41a1d5c61cc24cbf753fc00f441472d34f1b7cb461e4029c75f5089a6a5577ddafe539f03a7adeb8556e74cfb694b18f9ca9f99c9220f8109894b1545b81cbf2956fa6c8778c24e08260aef84c0ad6e0bae6b548eff55eff08f26cac8dd7fee0ebe645c4f36c4cac691bd5f18f0b3d751965632726bb49b8da4b2152f8b451f312e0d9535c342748043efb68b375a70c913fb03e7757539aeb01b033a240b509570fadc15b38630712cfb5535a8afdbae07b934f7a75a2772d5a34f981889919a2d82b68ca68ab38cacda291a924053c376f32a6e5f62c1c29ddcde0b1083a4cec6098ba1799681f0b0465e2c5e31792798cfeb840509ee329e363e7db2bed77737724ae98628a29e278b9635f41e0f32e440b1ef9d8cea93e37ba87941aa93238bd7e7abb3467d4db61c804ed05ea3a0a0adff6a126f0297a61ef765fc6ad5cb9b41d838ee81c330b407328dc05f401744de948dcde0b47295681aa3cac5ed6b0ecd5df77ce015d33a734629c0091636af64b7e0f4333e7df2a7470fc242e2137c13443cd61573c797a31a16771c34cbf9b40ad0cf7923bc0b00317c2099e15132120feb221b01618f8a3c9da7b9f6c2793b1ca2df1f397e2ea38b4382b527acf25b3df1d2471ee6b26802f1929cc3b4b5f8510e0cf4f272c2f592c77a492783c9be48c7fb917d9a01a1a47ff23aeed273c6e1849cee4d427290b73c3683d2261d8c765ec9861949658d77d9a8f09bc64e6d9a17b48e217ce2f1535b0c6616b8723111710bbf21d4814f4cf936947a8ef0ec58b7aa7e03e5418b48031114ac8474cd780629c0bfee1e0934148b2cef6f66cd33cf967b389245c44f42912d0b1043a77e0b97cf4be5c5e10d618eea02d1870b1a961852d4c8775e80f8b0d00f10d6895b574fcd97b1f5114d80451f9092dbb61a56585de5105008c038a0baf7f31e828946fad00a0fa6f046fc4e40aa20e594ad77995f643b66152dfb6e284d9e7023352a7e24260b34a5017003ce0ee6196ffbf47d5c7225bc7232bffc21e1475faa2f0809d669d653912f4b55a9a41d002b84ad676bae44ce7ad0dfbd51986f133484f6b164346a34e44be562d821c6691bd2a0dec2c7a434c3fa93979c6589f19475b04a62ecd12c57005c791f4c8baaa0a441b6f96eb66f7bf847542e676c25a5bb0defd0cb936b8cd88c57bcc9d47f1c167865595931bc781b8d7f8b92829abea2f65b340ae6d10385aac55d940e484d5490ec42b4b5aafb3e78ec1ef024ba0046812073f98b934c601a1df30b8f403c145cb11784318af7045a1242e3d1308f0af4788df03138c8079cfb4e2756fade2a4c176f9d83950230829b2e4577a7aa008eade00f942e772eff7f6aeadba4bccdae9a8ec59ac10776650b88a7e29e31d9f28d4f25eae50937931bdf22b1638a7495fd92252cb3557d23aeee96469f25976ae9498b174bf532a9da0c8362dc094967e222ebd5f7a95e26a34bd1f1582962c989e6a149e2af85d68fd323db0da45e7e2456770fbb455df9775e79214569f14e56bf5791cccc56155372cec96276a9880fdf745a997a47dc362afb9ef7ea232175e0163ed5cedcb63ee2ac13e434e7c79b5e741d9c45cd2e4007543d9aa47905486731ed35e004f0374d2843543960a7136ad6ae54858fb6f3f6ced317dfe5b6aa230862650aeed14d1536eb3219542ea788dbc7dd93613b3d1c1c6d2712505462f47ada1189f5081680ab1943001a72f430b6ed8a63e582bb7c390bd3de3ddd4013d958a20c162821843bc065f182e87ea09a2d70cfc41e1e629d8802a717775980591862628cf8eb6020eca3f03feaa8062cae4a188d0e3a4c0707a6a335347b9091b26be08bd409ce472be796be12df37f7f17167958143e387ee5aa5f592de6f2249c0215fa0f5387ae021f4f0a0d71c23a50031b314b8d308fe14413854146f0690e60c1f72564e5388470589378b79e70cd1da0d9aef4a495340296ee77db2e8b9cee8107fc7130292605c7d2a41cf2d2cfe76b9e842784b699ff3c394896784d8e878e8775bf7fc95142e2176f233da01b2894941818ba3037dea6f48df58637e6dc59a329116b8a4dbafb10d7793889f5f3f06b819c596e719288bc24bbf514d2e745d9284fe91adcb5eec92c5ec134cba4b123eaba5238300f141fea173d5f694c580c76d7cb33b2c8743da7dd76c02afaafbded8833304e623d48e6ec61e1ac5e991a25fc11987db54eab18f240fd3adfd31cad90c145e97ec06e8f6f65ca34d65ef549d9949b3bf2a608b842f5fdd96b77ffb8c9201f7e7bf8dd5a93b6f73312b9c225a547fb4e7cd94c69f36978e54b36a8f6823e60b2eeac897c5578ec4110749f7f6b5cd5fd650b3862a6de3a53ed7cc91e06885bb5cee71076b340421631ed8635a59d00568b3917bb3ead3ef730841de2df2149cd603ec2eef0cbcc230f19c01073a0303572c8a28c66b1ec0562013d9cfe5990f034b2c8d8b1c6b3be2e3c4d2226d0bb75fca397fe03ba7298f08936e901f5f6a1d2c48fd04261f8deb8a69d7ba9beee2625890bd7d77e88b79b1978ac1e5d8808e4d792ccb657671f21326887122f171fdf513919b18c44e5f3737f12bbe749b7ed39f975409e29b73a29291e795a68ef32983c5a6d719e8b37da94ef5ef056918ae00f237f55e7eefff95f0c63ee8c5333c2ff917eee9fd35e3321c3302cc308c5bfd7ffd1f51ebb1ff34a96d87e9ee2ee7a06f2403975c3809573d64f69a79c7396e17737fedf1dcfce5a50e429d6a962908aa21694bbb40844d5af555694d1025398168a9d182396e8a97f9025c48fe1eaad64295c2b1106f7333e4db1bd5a962fa436621e60545b11f4e631313628cfc83ed7b3a9f9b57a571a056910c4845ba1c053e370db614ecd21c35de0cddb5fa8949bf6f982d9d82f432afd6e8951b11fdae6f646693ec2da23ecc70c41238bcd40a0b868b1078487a67306d9397213214a1ad11a7feb1b496c14fb8e346b89e8275d07da0382995ea24d083e4a7b8072614f2e577afbad1bd6364d73e53386795cdbb68c636d9c64890fc5341572420322ba16140761d208bf60d32ad62a34f158c501d0230e2dad681e2515694cae96b006beb06060fae759ca1944a41143d953b71381c8c02f08356b469158f6ef3b1916daf6cb869e72c93bfbf39efc7074ac1c2e75d1edb7f14ad3bdb63f23db217001f7dd09bead9f13e4f5f79f37aee7c3e1ac07b7e0a822fd8f0e8375cd670c073a4fcd69558645ed5762cd1b231361050be82b5c88558613f9712cfd240587b0b2ff9e425f6ca028e2223e437ae2e3230f31b099905cd97b72e51b72a1d302f787ce585ef7e508e4f0549a477c903d8d03ef5cec6887866c5dae7bd13225bf6ca4d75f5416c948b7ef6a5ecb9baa674f5ce6943b87ee45005c0d1158e747a5b2dfdf542bcde580f862ffa182f380f2a5c64461e928ea9ce08d6e56e16ca56fcbdb35fd180f7985fece4526656477855825e3f894999b927514c9b19db19d3976f43029667e13b899cf0b55680fe090d5aaf3f6d170605b20502b9e9288ade22afa2f681fe10719c5a8a90a8e0942dfb7a3eb7a9e67153cafabcab2e7f9de77d3ad685aaaf072390809b939438843cb9dd3c42fb880838baab19b25beb9b1558b857e5ee6ef6f9d59d76c670973d63d3f011387a436e2afee24a39fddce92f6f8e96cd68a5fce190d2c5c7cc9200cbc537529fe1a7232e20f0f843d70cd4d5325e09b14fc4a3e07b372f5e9e2f14792f4c4d89ba89afade372b9ae3b31d5b79ad5697e4a3cfd7880c803d481fee2a43cf0145cf7346da969374e8c4fd3edc7da1b3a430e52bed898d0ef26e8f0a731a907aba6ca5744b790adfbf39210d7f8acf24b3ba2feea05bf9a7c18dca11477c23eb992f4d086f9a4547df60d139f67ee349e1092ab4c723b494dab78ddab7f7d2b7cbd2b758d2b743325939379013277bf8db66c78ea56e9fe366d6b7164eaa0ef52639a7254ebb9225b0e4e384608a59f5a8af2dae14eae518e8838cbb840cadbf3da50d85f7167849454853616d20d5384c953da8c80cbf896d2511225b41b8c42c763cd7351928ccd8df5b3f4c4f726ee8e8f273271f137a2c4021bca6e0daa2ba852b58858c61580ce923eb0a49ce6079264fa8c2a91be7ba14d6139a8608b545e8cceda654af88d7e16b5f48434eb2e03e3b3f87df9d5fb7a83df7f066087e80158edb2066a726c55988a4df3f35e7dd813d2a11daf3b185a50df531a3be203ad89b9ff4c3b50b319c24ad1a518d94281acc6e67521c2c9a199436ef03e99c826d41d761abf98de41318cff85aa8ee9ec66e6119fc74342496343025194c774ec1552066df824c23715bf1fcc3fad00a5b0197e4cad4f7efd1e98d1d9fbede1a75b6bc0b87741811e6598450402c092df11ca833e089cedcc96fd17ffd3574764b9be2fb5dc67766d9c7b626cd22009169d4df63cd06b47a4735c3fc8f7f55f336a6c77fff8f7baeaafff81ffff5dffe9ffff67f55f374fccf2acdcb7ffddfffed5ffffa5fa5b11ddefffcd77fb873361ff37ffcd7ff27d88f7728fff35fd31fcbf0bfd1bb6cebe6f8cf7f6110f487ed5bfe9fff3ab7e1bf17e991fe67ba2c439ba7473b4fe0bf6bcf55f55f79936e7b79fc9fe751fd4feabfb2742f09ecff28205a72ebbfd0c7308c94ee0c6bfc933d71e7dfe1f2fff762257770cdbfccfa4ff1fe4bf3c48b72e75708ee2ffe8ba3c21fa8fff1f00acf32f2eec2acfdfbab5afd81cdc8304cb81da876ff44f907071cc3305af4a7e4fec995b34fd950ea695fff1486618e7f8c047f2289abc7f58362046ef2bf384ed57fa0fde709515b7c5bb35d31397fa0f917db191efb4b01e1150c4c4aee0481188641fe9c64f73fbe31f00f0e981244fcfef878e81f53caff5a23300ccb4e993cfcf96b070cc3f87dce300ac99492c8924833c4a1c33022f2a7ca187faa7c6c99f83ea45233c40ec344ff5e58fcf9c3a80ca0b2253e67918b310c5bfc3361a63f91b56ec56303c873c588c030123bff597efff88a8691248dc94211fb331277ceffe65318456152fa8c43786018b6fdf38fc9fee426a21b84784eec95a30ec348ce5f7bd87fbaea0493500f815c56873fbea4fa33c2fd734e2a40e03974356812aa23c3b0e3f28febf13f33a4a0dc08fac7ea3f43cb7c3cd27eff74152f50ac70fe039a36a609e6fffa9c666b8c315bdd899d5852eb3864fb95d1dbb465fb646d9a92497a1b29303689d9d7371846c400d6fee366316441e2126503d88da40bb5fc1173fabf9beb283dd22a9cb8309c53b3aa7be790e23733f3c82ca2892b02cb3aa4fb1dac27dde44b42cd3abba8edc8b0373ce4b92876b494db900d1c64a473cdb7105a523faa8a3f2e6b530e5558a1c8d94af864dd0a96923819d96775d2d746a2a8e5e4562dd754cbe5ee557a27eeed0f784a93b7800c2c01917dcd18de210f5957dbeef8eec7182e20378f7bc9360d721289d00c668bb247e79d1f5ab512184e8df1408173cd995a8f756e8cc4578d9067c28d01596f805869f86e6d4c4a0cbe68cadab75c2c82adec5a22197ab42b7da0295a8e72f9309ffec144f3c22102f69d839d2fbc15425bd1b115a7836c5ce6c9f2dc4d425c5f3dd8d324c7c4f43b8130c3f1eb385444cf706d42828fa7fbc0bba3e3e09cbdb8a99694f6fc9e86ab43d36db6604fe262adcb5b6eb17b4bccf405ae37b9bad160273bfeea73e2e9ba98b7bb4aac4710495613899bd0d71ca74d7423cc1010033a8f323dc06d50ef065fcf111655087ad11bc6cc016e9cdd16a71ef0e18cb457483cbf4b5d6ed3a2a2bc3b801a365c0ede5aff546b5d543eed0725506da9afbbd4d34d9ce3334b70acfeb5fa26840ae52d63ba954904c2084ec84ff78bf207950e7c7eaee1407220f870eb4934f549d5968abcae5dde7e779a7098f1765a43804543faab9a349934e3aa6abe18a5027083ef4ce27462e47aff609f78f8758f7bc34b5dc3470caee0db31cc5eb6742e5f189bf9808d2c8d4e3458c62b76630aa7e3fc09367b5fd310e41e500c48a005a42cd065a813157cbb079ef462e708c9559ea3a0045a62807f97706c256e47e9f0ef660f65c369d489f4bedd2b8f2626e5b7a4fb3bdab79fbc9d1e4e0a3f9cacb0bd3ad27b2ac982954601fd4e93cfa12848df7eb9d7a8547eeb49b4c5ce3ac6cd72de290af5df18b7f435cbcef970ca992c180beb544103fcaafd941bd62c71d57253024e65dc96f928157cc3c22a6e16346edceaf6c84f09024f4c0faf0e8e42b6ddcd69b9189aef483e553cb562ec9315c337e02bfabdf7dbc716fe4bcad684bcc885cb2722871f4c810844b7bc68c74d0d06c32e39127078fa95bab12192e713b5b4481adaa536019687bbd082b858cfbb28cef3bef15a3d8c62b4bf2b2da9e19ae41e87c441f98edf6e8adc1907a6d8cefb28a2dcfb613af02254728a3077e77f98e291a128d5272cf64a48b410144a2d04495e83c47aa26650be4f7c7c37829e2c159cef2f87c4213d8a68e291a7c8d73b7e0e346954272a9256557e3279e2f9cabdf90198832787d799a483ffc9a1b6a725c65064cf652313a86af46aa09a5d9a421e4329b46f6c17888e94163df268e59184c8d4a7280e496248a522a2d6586a23f293ac06ee75e5ae7b1ddfbf171ab3a653425be76b4a2354dc3c408e42faf2da93ef8d8412fa6cc1a7a20b97cd49cb8196b5b6f5d797d1b94dbf37fd9dfa5eedf97c9f0fe0be3c4792a4aed9fa5942ad24d0a9c7998bd243c207da43a7956a1841e4a713f933d107f4d14f1a82a7ed440348b6d040e7c07cea40b411e530345880e9f8e623718f5f3a104334a25ac54b9962331fa638ccc7c93add1350e8d0ea19ca5b952d12c55e9c6d790903d8f53ce30f1fa915e980fc7c22e0eca54071ad7885787a9bf3758b4f5a651c487db319de7d00af97ed7eb62129d05a4a6483e417c504f073376c6675073b9a60dee600daa0f9f162c9429400913ee80fc0362272dcb582b06a221a28a4ee62e9175081be797cb98322fb330c809f1040defb6c8f9875f1426d49fff6e4d203c81b24f56811ee4f6803951f47404d3e3ee4c65fbe125da8a02a032e6022eafd5ee1d7db3c784464faa0b4617897273937ddbebaca0abcf4664bccd6547bda77fae04d6f7c23f62b5f74913dc046d211b8675be56ae5433b6e090aa7194b105fdd19ded9efa27e87fd765303ebf388f3798ab71fd9d2caf975bf71b92d008685b443f03aa9dd899db419b9864dff6c4ab5eeed4624263eb8058189f71b81146626602a1ce46a80be573e2aba30be6b7831bbeb0a70285a183bd0ccfaa36a40c5d4fdce04f56762343bbf8442eec2e71a15b4cf08c26cd98847f9709aec8f7f5066813b3a977e75eade1958f9008965fe229b54a7f3134c951bf84cbb9af0a78157a58c4b034c0127aa9a3cf8eb9403b84fa60581f3254af06a35dbfa445a9b8a8fca3bebfc70eb7c860b67746844285f84e127e8a47df58da98d3293f261a520aeea92e1aa345622346c9dd896d89715aa949fe06744025d664b3f85267247461411a8ecc5bb0e502cf994c418f8d46344163951bce92275856e442161c50fd02de6f39ccfc618373d164cb5b2b9c22d761c771305de9e5a89ca9bafd6b23178ba65df88f53139040fa00a4033a26f9bb000abd8f0937c4016e7103f180fe0cbf225eb107275c2d601378d20577f4e04624cd01051bbfa5243dc132f6cb76326b2361be6980bebb4aa93a49b648c9694bc0ddb24840ed5f5d9d3363367b7b79c6e997e9ec3a8518a45c83451c4be618f11008def823c12236c02d0a9cc561cd0a66a0b740fdcdfc145c773f3df041daad3b97c661660f8ac42f985efd324a938932e246be6404425adda90d7c7909dfa13e17a4a46664e5e98ec8b183815f84938c4709004b6d9ed07d165e47852430f7636015c42300e143ca5b5065ca35136170f64ce0efb6259c4af0ac992412c6d6bd5c9eff9fc20f73f5843d88a43a6651d90316dca09198356f4a082a68d1163c4b43eb7f9f7405f209415b1b40a9fb90f30fdbe55dd88c0dfc1aa8fbe15539e756419d17d6cf0a7ed48d8466bb18998c759bb4c74b300c01430463f9fb620530d9e9b076a078cdb41f8822330023fe8821cfe05be15e0be1af8699b76ebb6fc1ea45b2d05faae042a8f3b26165f3eb5e0a7083b9d6a2af1e39320347adc08c82f71d14355485829de3ec6cf3642f08950abecf157cf427f3de03eb201902e09e12272142dbd69c5b9f4c050dd557e54185effb39dd4c5925bc716db86867e778a2c3e39343582b09e99323790b05d5e0d8f94b0aaa4d07794a4d74e3837a70c5677322ac64fc159b94d3feeb67e29ae755c6c39a5c80843553d544d9479ccb61e0fca089b08cddc331a2fc0b5cdc1b52d608d64d44273df6f1f26969fdf7b2c9a9a262c4602cbca0b3d46421b5c1f06f8923512cf0775c1f11c1bc4e6dc2bbd91ab5a50c708db41024b7018cfd6bbc4be9ccdc4ed21294abc37752a69f1b329727a38a226dfdc164b1ec6f069d497823ad89850b85425282713c6b7718d0ceecb0268ca42b372e7a2dc5b6bf8ea5d1bf66a155c748d0c7905bc10ddbcc8107cfb09acc78cb7c57c2ee5b24d3f87f163540c83877bec3593765bc2f1b6bca428782132cc2fe013e8dffd1dbbc9f231b7b13177b7ea70d2b27520944e675689d074f1c8d68c2428a5c133b40f304e2694afe8085481d260012532dbc6d566dd4675f354376a63ac0412db4d35e0c69b58a30cd78b6c1b33febd1b90c3fcefdf25b1943231dbff8db653327a6fc43aae33f754eb4e59df9af338456ff8f26d1cce034ab991ac4f6ded09e462a2eefc5cd6eb45cbf90dacff13abfb3737d12899f3a2af134ded2008f0bea32d01c2c6375b0b410ed48ac6d4b3c7f04148a901452981012ab7dd8104484de00d823af85d00063624b0cbea83e5d5c4d77785c9e543ee95ce3f773ccbba43ed64030074bcc152e1152ac34a66cb48e5a32a062616cd9a32752c89438c14356ee92356461d4c333fa1895dd1352241f5bc6191821e2f9d4054c35f10f801ccffa02373e1c24629de79cd4e808d2fbf0f5d43f62cfc0e9285f229798ac84473db7fe94b86290a547866b63807d2f9bb35da9950bbf8b45be16953899e89637af70ff79ef88a327c7fd2195ee9f2444816c088f840c8e444c9a123719c069e51fbd1d64b0ebf53db1f3c5c4813e2c76c8e3d56490d8ed4b73c9cd3e0c40f8feae5fdd237f2cde1c3b8b1ea8c329a5ea0cc44f347e6378db2fc6606f24bd296130d83990cd185c0b13b0f74f0b07ae30754d648993729d7ee4f34608131185323b4f59fed0087603386991c8e67148a196eae61b41fb3dffc8f311866adb993d17be66678893177a663388fd17766acb9809173a6aab99bd10ca6a9b989111c5170fe799f14bfecb04248705007b2194c16108ec191c6fbc684974a4210b9dc0fba50ed2d2c1a38a947f345d27347a81c4514f862bda4946b16a1cdd856883cf9e1ec56f00ce454e6ce212dd2fcbea5ff3c6a75d7a95a528af6f1d773ee03923e3fa3fdbe85f79b4717ebdabaf885eabb48332983de2922c17a97465afa59c8cce03e7c690e95f8fec9869b3781874c717346822cf8594f0b99a60dc1fcac129e20e0cbf178987d493942bc3748d447d4c41d732f6a3b622d062e830e1c9f3fa5fb791f89ec5e8af109d5a49a02eba708248c17f9538ae8cb5d57ef8d73dcfd341e751d50599b6df2010a020bfeec2ebd03e4dd81a6aedfcdac9d2abee104e759cce2dff8076d79ffb6b7295449bb528e222446ce75d3801a13b0083ceeb1ba740983660196743899d4171533f389bd83143542f0913d2807887d7e472528fd7602305a09696a37ee8e568b19d5155429a84649da015a67b2fe775db6a37a0c5e49768543ca6191b1cbbfd111fd2c238895722233d4d6f5763bc2332774672aae755c67a89d4c0026ad6bcff9a9c1f41b12bb3bb96c00c4b5ba9a295dc3a094c104079dd1f202daa35c9595bc237b532688049eb0fef76621c3c82078337669ae74e514b568462165de0b8296dffefd6824bc3525e199ac57e345e90be98905d771849fe3f56be4e897b3796eba9af1288efbcba8c3cad677153b3244b93fefb167a2b12b1f13f7dd04bf0df946085288eb044910ac0c6ada55e3cb7f00b9e0f06be12fd95b3211c36aee5b9c196d8d76cee3f5d3ac849261c5840ee9fcc592ec415840e9a97010a6e4207807bf1d0b2002aeb0b9881b9c58e978e899c95c400ded7f303d57b5c562606f2e283efcde40344801c91afdd45a17a396df5d288e5999b914c8bbe82c68c84b72200ce7ad51e94879c93b78dbf61361c1be971399851a172e5ac831d8fb147dffa544c13de7a6faaf953f22eb9137a4de3f309cbf059603f7a8f2f04e101dcf0efcb22c882630db456913fd5c36af4c99cb5f75c4a3e5e302aff12c21de27b1df7ff7ddcc43bd8ab282a9478be9bab0abc78ca9f41d4ddaca0e4660905a9aa083772ccab741bca4373acc210c5ae18b289eeed578a8e7ca40b2a4408f96262d94e6afa9f9507f5f9b3fc37abcd90d063a9dc322fec759ace40771161939d68f2334d0d19d4f8935fbc09e8f2adfe565350dd1cc53bfbeaf702db7b2114ba97e4d6529d6b3180b0ae14d69c7a841d9383b6e60de8998cda13cfba954c661d3d20946a073fd44bc4ad37754657ac8448e7dddd5b4c44a12826f2c5558e9ee470211516ad9c15b2be33c31b9a227bdccf7bd28d00a187f5dd4b6c9d7cc0004b4d253f0177bddd646404f8277f57d96fb5eb57b363d6957c05bfdd13774522f58922c09d66875cd28a5811e91015c82c4c3b45845296016d8c366762a1705720064da2a77b7f80fca7e8955f5f812a2800a7ac663ced88a692763dfa29bdd1a709bd00ad4d6cd5670c56d6dee527d9aba937446917018f04bd0f4895cfab700e70e7af6b15d559241c82f314464047b32fade14d25f2f390c193b603e33602cf58faa16694cb239b98a46876833b1938748c84ea95e7ee26fe1d7b38558e6105d695b42d448026112c4e669e8c0382dd478ccc3429c3b47579cdb5d3acc9fdbe496020cc5588d2c070bbcdcb90d788699e47ca02d3e8a739166a10417266d74f06b0a1fd99929c5450e09a033dfdb63157ca3ebe3662d0c8baeccda1f3d7d170cf2e428c562c257b033fc5e4533482251b5d2712545b710ab6c1446e0debb268e6d96a83895a2d8d6404736b0b2712b8e311fd3bdac2cffda48824aca28b4b36bea541a4391cfa3ca6143a705700ba8a1ce8f0baad2bcae11a0ec5e363ff0b9ceaa9b41cc27df0748cfa5b8dfdc9eb2aef946f43a071a082b22c55ef77cd4e95ad4c9ab76d24a220d8fc4f334c852c27ef5213e67ab8cace8bb79160f11fb3bc47219327609df6120f5d8511d85bc80b886e42bd7d6123713df173098ff8860ffd183bffdba8a5b46dfdc45c8521fc67243c3db2345ccc9611fcfa9e8c5db4f072b707f811ae97af7dbc48f0bf4dd0c8a1e1376c788b9b8a0acdced3836fe05b0f92b3c2b7862263541c957ae06b62ff8a8f6f91222a13bf637074b2f6491628f680ea4df88ce86e6b9611ea05df9e1d8f16bdc16c90e21a52a6230418f2455257bc2fd9ab68926a231faccf54d8a24386834a5496d6b6a003c3a31934d70a30583d41704cfca8bf390e4443e9d248e53a24f98a633ae816f93ac738f26f35ba5dc629101ee6a98e3a3c861b6fad5ca04b7ad6349f5b6b0630a3668cbc6a39ebb162bd5f1b798d5f7e15bcf90e197f37032010ffca2655777127dd5923b4e154459c3931a3f095ee192c6cd386230340d4b82c86a05e7d74117bcfc7226baa385d2f24448c3ee528d0f911f31737b92710ad7aeb59d29805e28b4453bef07988d816f945dd35c656d13a9a6af6e0330abc22ae53c9152c52a08d90a18ceb0ecc1ee5e84092c29b7aaabd105acb6a3a24b08e98b51c7817673e87d8cbaeb724ba919e0870c99b7c0dcf723615d47a7b571910f20008a1e9703985f3321d37232f96ddee98d44e15937c7d05e7e9529774be1a2449181f5d309391a9f687b0ba73f0684f55ba028b97d37d5de492ada131f14171dcbde24d01e0f4ae3c700726a1c4d5899f0b1bdda0d7556327c39e43ebfd24b6507515cd6398664ec9111acd9d4f6c9cac85ae8c290c406e9907ff60e1ef7adbbec3cfc541cac3790c8334e8fa2ef1936315daa428c4e0824f3ebaaf30ca914bafc0e37e329e2f0a797ca8313363f00ec9999807a555da337d9bd7b7fc4152c6eb0a82ae45237969ad0cbf6750b754b6ab0174e072b5fe3703b4e0a7c4422f742d0b44ac962f1074afa76b06f9c01744c073b5f4524dc314c8355be1bc4f09057a9e3c053175ce151f87770a1d4bc1c807741e2613b76aa4034baefdb1fe243e264b01499bc64d85c04183580592493f0fc39f6b5888b76553ec2942c27ad6433c46b88b5a4f9c2b062a6f5bb3de65b7b9a30bb30afe56eec54b3cf7baf56dfba19686ac6730dcdf9dcd26ad28a601668b857fdb54f7fd5213bac0bd2ccdc5cd76054f379c302defaae33b14327b16308983859b28051320fa57b347b9e936ebbe03c264984a6fca03b307ed274ba3d1bb411b3723f7673c6860e6c96c594137f9207a779bda39efcb4a206b97168a6a815bb4ccc2229bd1820f65e0dcfb94d40f885fb8f1f1d43d18c0f4e87c75e29b774327b04e1f47a518f92c233c949ce4c04cdcd61d57b9387096115e3f8423425f359e7ad54c5180996c47e701c436cde9a9c1d2965ea240b2c1a4fe3334495661d86c1d8dfbd2c9c76d41b98fa862c3ff557092644a1074fe3ad397f02c45aa1c05a88217384a18a5bd5b448c2397bdd08e680b0ccc7db8d36accce9f784a97a473cbb6ffd04fe9063b8ac9253f4deb55cf8bf203f201ade2699df0100b0b8bcb04cf9457290e480cc12c3df5e8ebbfcc1202d7d6d591c3f3b4d41d0a3c78ef4d870b16499aa5f5a7da20ab4e4d780c29ad46da623a2c9bae5689a1b5dd31f3c0c7253bd988931667d29823a901db8b63dc5f61817bb197f13da5f2dd594d75a557df7dc5088428c48024952eb22bc60015cebf2521e4fd92fbb5b11cfe86725518398392d2e79acfc0c399d5b824be942c977fd3d793acb43dd6dc1bf241a35867e961e642c5e2c1ac73cb921538c2131d82bf181daedb84ee23dfe4aa7d8f1486deda5515badb73766c0b88571f3145529f3c2533c1bedd6064c5913f3e9f3523bcadeb2abae7b5d6e24d14441f004679b74c1b44b8f897ad013a51b14d475b87d1a20eb86cfe96a9578c131997457195eb0559ded5e43a60ae6f0fbb14e24f3f610cb68c4631f2275e15c3fcee0c77de6f4f0900ba3c3da9c042bcae1463fe6ea7ad7d85be9ed8ae6e42516fecd6167bfdc7ccedae6714dcafc6acb8b86ec55b04959901c329b900d24837d95c1671bcd41e705aaf1a62a7f5bb55e5d6f6fc109e4966c04613eeae9685ee0761eb07d4dbcfff15a6ec214e1b06fbc87bcff81d344a5136995866eca60b50750514aae7b04c4975f00503d185bad183b563107ea1e370093e9e698b2c5cb177ce6a58bac644490c19b79c0164b6f6b64ec18ebdd369a326aed62b03d678cc97a268b932aa6c32896f3130611937ead080a3a67340d7df376ddb0ed060811c93fef0c515807a0688f1c32e763880b17cb879b5b002a02e22925bc2b557ae3cb7bb2416e7287da10772f17ca2cff6ed348a941bb1187fc7e2c35198bf788a473eb0711f3872dc5040d7728f082bfb8c7b6e26dadb21f60e44711a29cd3cbb7e57819f6cf228ff9b94b1a26821c94e668b28beef27afa7a585231594ceb7a6439feb05cc31b5618439591fe3938ace6da53bf4b44bbc414ad140fa0e9dc9ea430312e626b5b13a48f4bcb8a571eaf0abcf43e4341f52aec0ba6f1421d52384d631b7c217395e4e2d23e0cde28c358cdd061bf63755fb9259842661f5fbcef33147ed2a821115ba6091fb30e87d4b3bcb2bc1a3b40bd248c04a50e7b0a133c94a10ef73914dc7036cc06e1bacfa63a56d7e2cacaf18eef8c22f5042a5d1b49beefd04ce7e2db6114f34d63c5131e0c52c089b35b269f7b3ef9c3775369c18db64e1e6ae82488971e224a81767f91f45dc94702aa40010add899767ba202003433c0ca9a0e808076f31a414bfecfc1ba986d524d59964b93d61ab69f66517de24e21a50982240bc06538f50595c0fdcb3e823896fa5e4df456110dbf1cfb48e8c440b0c3dd0780779e6998f78b65eeebe44e7a75319e58e73cd6ebada46b5a7bab158d785ed9998db6bb600f714fd404627b88209abbb92001c3675e0fdc71ef1d107bf969f88df891880bb13d0769e9563c8bf2117e3ba94dfc66f5c347ea8dc46fcbdcd50188500aca8bedd4069d28609b2b252438c81f21c2b642f7a9d6c6e7aa05e9026424bce521bb9d0a6210f390340878a4355477164927d1a4c85e14794bbfabbe44fa5c1cc14f3b5b7a4c9ef3b50c01b1d704f980b97a860f1d961b13b310707266fc5724551d6ebc20b0b66fd9442b1d6c761d6be01f70c3fe57b990626f375210d98a0abb5102dea18aff622523cd1e783949742818a6d38be9b6686f1922664f50bc4924c6f5cbbc0503f7679faaae994c8a1bfd353c7ef1b70160df3d00185483f3adc14687bba30460fb8840da28bc246208c3a42b281c1aaa48b08201b8d7f3e872b6db1a584a9fc46a51d52e487df126186f4c0b3692a3c6c1b9a8dbb522947dfa775f46ba36587ee90a51fca4478799104e9e36313235068b7472e1c4021b947feca2ea196f8bd0a2e37042878e1c155f8ba9835b4c5cf9f0adfc64e9f9be019f070f8cde40e88f672c6840eb0dea979534e24db3c36d547354862817e06a1a421ec8530746ddb96acf5c7c9b4fdefeab337e2561973c007bbaefd35c6b05af608557232ecb988134a8196ce4b4dc4d4d9632995fb24beb48133bdf2906fb54a31b7ec3c4abdf2d6f3934b7976241b7d22bb01c8efe635dad152ff0aba88c29f245002d9c05422bf53be6b402929dddd77c9246524497516c85d390712118f6ea7dc02717765ca71b6192a35671bad9b7a059e2f12fe3bdd28a6bf561fce3298d75212acd51a6f6bd1b6b121e6a1604a34225f2bbf0d6fbd15bd8a50503d43c85c6e76d3eca2463fc8abae0cd6c5bc444038218810501688ba9bfa00b22519751f84589d9e5e9333f8d675219740493dbab7ad6f59dc03d3b457207755e448da3ae7397dc74c2bad3adf3144b5dfe9dad3139d1075dfe262962d101d1ff909893f4902cce24f93aeb4b830f1106852d83ab0f6dd5bc07f0027a6ab725d8c08b355a8dcc2e0776105e4fa7c07ef9ce25bb1bcfecf64886185ed255ae9f1fc51eb5845f79c26190c90e7de36d40703cef5b2332319d7bdffde9d5c1002c23bd5f387715d1be17469a8ceeefea5c0ec098a3bd3b41f1f11cd9b30e1cd0d25dc2be6e48aae2e6bde3408a43b364d23d43a6e31aee7112c632905304be250a0f92a28d4bd4ba8399ffac0d4549ff21ee5dad539cf54ff93503ba65bf67a2a0d3c24754230a87f497eccc8dc2a4a6226f4f2a3215e4591d38b5cf8fb1c95d668507ddafb39b720d5af75a485cc4e8745f3eaaf3a4871fb8109e42ea4039bd57d74e345140fec7bc64cd2e4370991669cc6f4d92831a2a94bd3998d7c450ac028fa7d8b2984a21c2a28891114dd267c9250e48c901f00a6d4851ef561694c5fa8f069d9993b021cf3011e3ab16f0cdec619e63d374986a8a579215aa917095aaaa9617ce78e2bcc8e1cccdb8f935c1e6aeffeaeabf9e49984f831b542ce9cf36555f66e55757d1c1057c4861b55a596c14ffe5473a482893aceb9bac1ea89a65f009a4f593c0eddc4309f3a271cd0b82247b47dcdab4d62eb84aaf4d8443b8c73de333d84867e9ad3435de8ee77b216e3e28b970b86c3cf2b6ccab33a0ded4f37d7c537167f4ba0fc18256a7525323fd57d6276c7810bb18b1b200b9244ba2a26083f9b7e25fc6d09f643f582805cfdf0a029093e9aaa82b1a667919e08b8b2945f0cd3189a1eea8cd886ef8434c0c93b3a9227ebfa4560527cbd1cc2793cf4aede13cb787b9b2ef63ded88317ead5c9f86c0a837942a5313753465cce1cc5f3fbc76c3c137a16f8d1cb6446520e6a79b4f68dee6c87ede5896b7924f78a87168d0aa977d109def6345e3aae2108228a7fd0aa6947229c88f4e3f22e5590f747c613390d80618d9684b07f11200b20631cbe98ddade2875d0cc3970a8cc778aa21d79f4987e75ad8a3cd36c4f734ef92d631c08a0a5f468833c15bc85b0353ef28cd483393408a257cdc50337a5c8b344691f6432358186a10a20fd3a6a4cdd1d7608caaad882cc591f94977bde41084c10ac4cbce454089df0e7130a8ac65f1f8e650947d20ff5e4988c839278740e130df7ef34cbfa59ea6bf32fe7938731a24f01af6404347cacb231df2bc6fa08f62a833f3be143e0b8f532051262bf72e42a8c1e8acab4d17cb23c6292768b34249f7bfc98099d7f953400b68e5a7b07d11389cf9c2d2bbf92f36c89921e414df39dc3f65c921a720145d620cc0f07cc664eafb6fc15f0ec57edfeba4c021873daca676f94b992cf286ccac1fe484219fa69c169638233b20c38fce3dff5923f9dfbcedcde3116c702cbce867404ab953cc153e676254c4d130951c426ac99535da486d24befc10271e14955fa2fd5167c5ca88e7da8d6673f0180fa7c23be6fb661075a5a88a0ac85fb686abf92f2b6fe0c5e596abf17ef9ff668bcebf257bec99032b0deec52429f68bdf0f96a5b77f7a16c5669930594e938f3834d6d022e2ec33023961740ce1e73c938ce713ac3102cbe63a33aadfa49553fbcea104d068b7da5f8a86a26206bc613ceb1eff72ba49296458316450b3fdcb88708934d0805ec1442f3f8e34cbd2a64a06bd69325a8504a72ada82791894749c8807ba42aa4f54cf58136465ec5b001105e4f5330e230385792084b52662d5d0fffc34fd631d4d1305bb3553d1ca5dc0a2f412a21ef875bbae5b6a9c3ded047b0e97e2b13959a0ce4120acd933ee152b0cab915f0178befce718990323db6a1197de93724da950940e64d063ff3aafc0909ef4634c329c96adf0cc6857ed59657188149a7744bfc1123350e7b248ad0456f851b96bbd751825cd9773e27d248c65fd1cb82d17c71723b982ea85ec31492d0457d8130ba394d044dfe3be891acc7ab9381a45dc1311bf522b8d3df9005871899fe10d41aa514eb32e3524fbbacd83f6ea3c4e639e98ad1c02e131b95bdbde8543bd18528a57d2ef6a91dcb97df0810195cde1b255c4a68718aed3763a8e77571876683f94efad45e2f318ec4fbf2c8a717ac47bf9e367891ecb1a0952decfa497a39aedbaab02a60d12fd6c1a38b0ae7e42df476bd9d9d56b84795eb0a9321d865d300c96c4416ad5da81b51a2a907826099f1c8411c08d4f5186da779a28432f413b02304008c3a3fe0582243308321d55cc68445decc880083b87bf265ba9c43c1d29e741ce7323896320617723df2c57a451d2a0db75314e16187da4328b707fbce3f0e1a80b2d2978e1f45ed3f3753df09ac2e8bdf00eda22a1a2b6dc2281c6d8f4127691187a9e489954958a389fd13ae4e73d05b241c95da19a6a208f7d163c209e73a45997c57bea128368fd4790d839637c57e5b1ca63d660a9ba4cc96f580772ff57927350183e19feaaaef7245e62253fee0dd958a86944e922f6460934e165b79dd14b5e8307ad3a70f023057a9a467af36a61abe4de97d40d9fd883a2690cd9d95b9999485adfb58cf226a01e24a03f11bb4a98918f294c35fdcfa6697e9097bbca3c72bab5f84162ab596264d3ff3125087ac584f7fd87ed6dfadf43ff0e13f6a99fa662ae1aa5890dfa6f60810c34823aa40f9dd176335003767151252cd45234fe6e1de3d65270a3330f64e4616b911efc5836cb3f826e435c7af3e65925557a5cafb0bda13b9c7713013cbaf95c51e50ff6e4c9702ce9f01f55266b00acd1d9a3fb0d658d2a3e53c4b5ca5f15a12418f28e69f5ad603a70294304d5706dcad600616cb8b75044990bb44870f8da796b91a9756ccc5aa6e70b174f1a5fe48a31ca30c9bf67a200918c7afaec820a03ca28d18d973e2179078d80e261fbb8c791661cd755fae78218586ff510f7ff34c628da786f98ca7709bbb6ab1c075c73a9b69c478e6e82758fe1c35d9ee165cb4b4bed550e65f507d96dd27de827550f61705372ca28107a495fe1b1996d8fb378f5735f7ed714c3fa34e23b5e4aa825ceb3c356a4eaf9f5c6aa75adc5008b53279a261fde04df9fee835c235904b28581cc1982a6e092b6da99e4af8076165d4f30edd649da9623e24c95d292d8089059fc5584182359ff16c39e2542abbe90b58b5d16e657e80edebca7f5ce11809d8a19cc33cbb082547d18c1f79b598c4c8462d3616b31a9bcd9cdfba8e30939101ba1760d8035da93e7e5efc488ca37ed7469c08a854e229165e226b72bcc4dbb7d2ea1b88db4a93783257d5aa82d95c916915ae5c4965c479122838f936deeac924ac49c37ac15ab4f5075ca02d8ea704895b2076a6c4b716052718d47e356d48327ee647dbe50e0d1ea5cfbc7e98c86367b9a417d37276cf52c98d9f43a04951246fc470afac0cf140370749a590155e456d5b9e32baac7e05e0679183e5806ea7a7b88c00017d620e7831245d9528272c5d30c1dfbd8226befeaa5c8b66e6d59ef9c6415fee2ea7737018593fec80d790d1a09b3911fe9d3fbc0f1faaa5b5619dbb4ef0e901747c4eef9d17a626e66e990f8cb4bd07afc30fd3f1c262f023a9aac2a9a9c12f85bf6499e2d1459048bae109c820766b75d406bb83b7936ed8fb941432dc0ef66f76cc76c7bb8b62718d39302c424d18137c948bec9e4e7e30f42e6b40904a1bfbad7da517157cfa66db319d49ac61b849c3f0933c4f99e65c097081617b86439b53408db86a0a2ab59fc88adecd42741ea359bf288676bc7231e31d9041d7b7fdb9beea68c66caa703f1f04f52bd31d3310fef60c5c6ab4aa11d936c4145c587c1e1d839edaf72b45a8b82028dc0a7bd3d62b734c060e87dcb295ce528434ee1864ec8ed77ee26fa142ae106d998f705722279bcdd098339058672448b6649b52fcac5bc46cb5de7b9ee9747e0c5ac370e5838acd836193efd61f54c7c7f62b867ed4ab9186f664e251bcbb1d1b4d2bd516037f6206744922489522b42713fb026f0f7c64b043eb05083e3da9e8d0e64d2e142c1a335e610b937966f3c55102d65626aedd13777da6aa75014fc11509246293a5d8ba3bf2461dbf4ae2e84fec607e79b45edc73cc6e3a9c5643b35fe362ce3223d539f67ac20b9bf4468acf34295cd6f1f7e737965c91b4b969c035831682660cc85c23f69c749eabe2c59e4adaae2b50f9481fce4371b7eb1966786b160b5f4b714ecbaf1f073746444d3f0a18571e7fd2e5e7867670f74c83859be99bbf7e9bd442551c771ee96a183a9aca740e5fb4fe9cb77a1bbb37ed9528434f4dfc8836db2fb4b259447f3488d4eb2fccd075a44b88038b5c928fb0504e0426819a480cebf552384351d012f0a14b507c00c9e42d7c3025e934d1bb57b07e7a36d5f003e7ec942b266ced6d921b737fa34eb73baa6b88937affcc2b8fc38bd905a3adf2da638c4918255744a16b241838a0cebd653174f3affe1cf38cf6b0ef4cccaf3ab506d1c5e1068d8b28a83485c04d4a563a362f10b7ebf804cf852333262c40e294a305b3eae1aac0921669b0e0370f94ca0661ce36bc6964c7912a77b8020d06cb608ae2305eb7e3b9ec47783788a10ed47e729cc677a33f43f72a9ba329837bc042428c637f3b6c28f601892862d2e2d7058e9db82db461418feeb60f505888377b8761112e49d320dc46695a00c5011800a5508039da475845bc5dff7a942f6ea2539d7aab2bf6bc28f11430dede00b38567070613a165fc6e4666023367e534702df28e789e5716cf37b6df7206ef853ac92defca9a1e72896669e5f43762ba761b1e845b2ad4aff77ac133f4da13a5ddb903659234f92d9e54122c6168874e4753d21b4c3deeb64de7180d1b20fbb217284f4d50d453407407fbc0611f9744357647de76fa504a0a52626ee1432857fea4b06c820bbb03d946828a3aa1cd59bff63747c0b6b35ce9461a2a760cd75dbd0e1a43e49df846368bd28c17eac658112bb5e94a2f17c7ba37d933819affbf1cddb5b9a5000044e18208700b71bdb867b8bb53fd7efbba3833c9af336b4251a5d965a5c8ed61c46f97c56165b182dd384fd1997c5e659df2764a1337c5b208b4557a891bfd4217045c605da8b5f0dcba4c4279c9255bcf31cc1d5f9b4e812218d8d23d84dbea8a818fafbe69dbec49fdcaa5936d64f750b56e78282ae47d097f1b173c38ac289c21c85c2d26dbe602bc745ac2fd1911defd423f1a267e0726afa0178cd6d920306894a08b12be0f437fba0fedb74fb34c3da4fc3d05eec55260ea078f985dd5599e4c9c8832460e2b00f6d952f8fb31da7cab8deb14f96cbc7efe1b205ffdf97af2a3864680cdc0e729256dea35297f83dc991d97e0aab8b1c6b43a37c8d5460c8fdaf062538eefa766688c3fcb8fa0b5f95ce26ab3a2497fad61227a2d12272087963095c5348e50174c5add71513f67c257acf441d02dd070c34f023b4e1039c30bc3ba7a093f6fa1a5ea02bf7747f16e489ff857299c504d138009d97b13d6357f903a5fb88730f95bc092f7f94cf2a6a42e0dc9a4c0463253dd6feddc676dba40ffed3ffe3796da5071404832c6e8bb33f9cec5b900500fccaa6b4928ef5e12e7a91471c24425ace009934f4d006b2798a648fddcd33629e6bcf1cba1ac92fe51f721b990e5db8b262ed4b8fa9f7998bbdfcb435a20fbbd04690e7944118d94ea8adb8fb2c5f528248b240ff8228e0d8699a21d68f38282ee1d93127ea9d1a0442cc301f3c4a5469dcd15fe895b779da9c3820824329a4ad4731395f0cb10af641ebf918b4a4dfc72a323afc2a94799e86105312ad316247524e25fee47130e178845d221402c5912c170e01671c855874aff44181b486f00c6ab145e43cfb6da3f7bb3096ee558197d4bfa4c48278ef0551b2b8c010415f0b34975a8dd3d99564766c7bc84ed7c879fcc66705015101ac178f86b66a519c722862e45a74d788c9f29a3ccebf2e1f9475a100d8824cd7401c6f6a0b46a91866c46c44d844e679c9c512ab7910e3022f581c8bbd3e5dae42ee2b0545cdce8ad26e44fefe31ef3e1b2e1594eb3a5c2a32ccda7f9586fe2db57dfc50f74c0f857b5c953ff3cfacc1cd6187e747c34f365cb97caa18522dc349587c89400d470df2f5c40045825044c9e4721624da1b63041ae1b8b1c0f2645604dd6834762ad0c58d0248f28656c82cc30182e948518716513cb2414d1b3a84a7e0488d75e378894861494d9e6f3920ffc4531fb2e183b5bd4163bf0b289a4321ddc58b07ee25501e745ab376ce0a0f2e168255aab7d83dd2f9d9926992f7f3c0dbd6125c8d34b85817c38e076b44f0a1a2ee254264a2d13d215334706a8719d32a4348cb5bb6675b1248d332dec661e1bb778e1bf59aaf21ed889fee5950d6be5fd40bb5eaafe08fbedccecd1f64bf0825eda414d229137dd2da607362ed4a16e5e74a8612f8153f7dbed54a51da4f7f3bef30b4a1090bf0b9ce4c8f26012cc163c24c088ed0f596d51b71bdb09370088262ab902cc2a42e539a3687057f5365f1824b41a27028b43cb6a08dbd6a4eacafacf3ea1c4cfcf1e150c0968b6072640689598ca8e37e79cd6cf82888aee4fde391442d32b9fa4c1737be80e1930f218f1ca065002f1418400c1786260aa5920d286e64a96917564f3a98f4970bb8e5a2ae9949a4a44c5840928c2bbea8e688cdb5870d27af15b7454844c96bda16863115a1b3a0b27a9e62450c48549c766952113e81dc92c2f2a3c5eaa85ffcef2e7119aac5497ed1d334fde93ea265dcaa6de9ca5a80c90f894c3ed6f4a471a5703a8ed1fc9aff451b93fc34dba0fa14b5334635d5b66bf2a7facddf2e91a8a1854e5164492c03e6e1d00c6601015355995c6859bd8848ee748af09793fdd70e3b9ec55ee4b80db79c105582657750e6250e57005b8dc4beed56ef5550d5ea735a8cd8fa6f0da016bdaf632056123d1319345518a785dae50f8a9e5dc38b67da4d1af275a816ea345390486c2cf6464a9021bd939ffadc7368f1e0a9d2d99197855022cb380d667c23402651e791b58cd9ea8df5fad4d5100ba55da1eeeec95f06ecc7e36a64d5933710d0899ec3407f7b2078c671ac56b374b1733b993d948e3fbc547f429abd79a5fabc1aa78a854d3ef24e19748215b05987c1bf67e5e547c3285b6163d0048f054c65ad502435909be4966df6c1fb3c87edb3c303c64a6e453cc3719d79e0c7d385bcb658f82fe3c64ee92632fda214f419b21fd0d144db3bf499642befcc626c27a0fa9d4ea4475360e8b58ae85856840936715d211f224007a51d2516f29539cb7a1f4687db265d19c98008e12a81e723a3074208eb085c607e60f9f8ba809ae8f7c50f35d92bfe4b1cf36d19378249483929bda4a4c83cb9b5b6cefdc8a2cbb0f4312a409ac5f38100e373240fae943b98d5136de3e2e09cc557c81294f6b2e783c12f7fe516cad1578a644541ede438b2c8df6f6c34cfa464382208c5529dbe904a50d5ea5f2680a9676534912c015fde2c5ee74829edee47e2963a9813d9bbd21de5ce02af5bdca3018151c946fa946c9ead48c9e115ecd46982ffd20e96388c44bfb81f5182f8e857e7bdbdecdd361e6414062c4b552bbe5a0d19aafd38836cfcb84674ad36dddfd696e2de8d55369de592cd67d2141a904070d8929305ba6d6b2469a0949fd12fb6bc7e0b0ec4b0756c68379da6958d4624d9c335a9ff45199ee973bcd8594b3ffd0ac81c86931f3a012224e8984df316ff6b5fb81217e4273095cd8d997d8f48aa365c2a17cb9bb6f1835dc5223783efa39697bc45a35088eb2296baf15ae55ec1e960fcae50c909157e30f27fc782d519221897ab1b6666841f6dc838dc73f94a268a0b41ea2cc1519f96ea342e5ce0b007a45c13215ac6d6d8a02acbde884646c146905f36e291a0f5c8735e093b189e1819796dd139e6ee89aca5b04e9d8a014d8bad5a214ca5f5c71f8ad729e6f7fe226a01f153bac76689e7597fc476d2b8ba56a280f6ddece77598fea7327380b586f619b4724a279f72dfa1a993a5aee7ac98caff1d3b7ea77927999b17197289675b438067af823eb10019c5716b57a2262b712b9147d41950c92c3cd9c55f3d646b2a80336d31c53565376bafec16c4122bdca7828aee054a24f08da33247a812050215697dfce619820e75064cbb24a238db7f7b89743408b0dc823566dbed7d269a583f7600e569cf6c48b1599a76479ca748d91b57147419fdbd6968f5782082a7d0df9e082313dfe91aa37ce2135fc146b4a3537e56fab067fda2f067f0d1fe4c578cfb341b081abd772d90266968e44e4738e90ca4d17ae61c10e3f7581064a0a345371514946ed8b89d63e0f10b24a9bb262a511acaeeb5a2c43fc80daeba0479cb69faaae4913da98e848a091a66267fa8ee4e9ea63786cc2c6e9d8b44fd4b4aeb8d2cf3aa3c9653a72294d9e7205bd3ec426e2ddd4c9f95eba57b99221d1ff541165bdec13428575454c7f677f9df108730916701c1f6b62f5ebf8b58557603674767b09536d34e826798d3ab614c66b88d2ab9e7d998506898bae0707b3c7e8ebaa901e8de9c96c5d718e5aae7382a9c4931d89ca6f435b090fdf362cdee4772732638ef6709e3552bde18d24f9d5d8c4ca1aab419831288438d7dc663016a70813b39661dd951a64f8215dce276d5019a75cc7a4bdbb4021d6d576441deaa8aadd33667014f46388f7a4a3f3c7842633b44d38d85f9c5824dc25a579451f38b2c2f769ae4c2feb6282946a5628398430772923987e90138160c3a0517fe89177e419ecd0b919ea6069c9e64d3826c443650ef873aa5898304c4996759a6e105bb9c96cfc79395ccb6317764c110a99987dc9ed6fa9a70f266210dd814939e2ea71aa34e307a93febedfc717c294f07a4d3cad163c913d36a92bcec3aa47d9404ec3ec30b4c796b56fe2dd35af676dae5c51dc1d5355d8f00c050a6115250f002a42de4e375fd669358f9d75d4aab04682742400c8e52001647c40f1b1da0fc40bde901ca75f95c53c48b0d0a54e433a3998566f9558f9d4850246592dd0a94c7d839f857bd7047027006586e691fd39774b2874fc49db84b4324a6e939385497bf0e3419780738dd329e82b47eb9ab99326a697c8dcf19436c5e2378bb2759038d3139d577f3360855a8cb71c46f017f1e1cb6a5b2df44272e8497601787ccb3582c5a1833f63e409dbd7dddc6f62b6a152f87b3e7e89807bfefd09c457564e444fb1f993280cd92b14423cdfbcb1653597ece8539805ee01d80f3dea116ea862c96177a689c2fbc1ac05a92694ef00cf60a210c4076274b76d77aababe70f23b301015c44618d5cafc1e74a06ca602b5eeb3e7e4e32311f155143f172e83077640f349f9df3231479bf89b9f97556aa5527941813d4550ce64bc94343570829c3038ff49378c173872443b945705375cceb87308948fdae95be2191c8530a09220f3b66baf4fd626e20a2e875ed95c83a0ff783097c3ce68e4bfd75e49514e520865490a3b4bdbd37ef10da60fcc21dc19ddf37d411085552f2b89ef8f6663b790e333cae6fe683516406422d0f469cda01601a5e0e0fbfed21a084fa0163be9999143594c52515e94812a27650f9f9643041ed3ae965aa5e90ebb6890f811395020c60d527c40b24206728e4824a96358ffa06c8656e94c06a7c349080672b842fb8e62851b9584e95a6a957a53cc2474e3a54ac4e2e7942c09122919539152cd3d066e50725952c7482f305fac56cfd864b6adbf847ba5df8131274d01dfb77681ea54b0f29162ddf25218ae89f58d6c5c6d030c1ac021b989ad0547c18bfb2f52b00b736ef4b3736f853428a914d49e64713182b5bcce58132c453830915c348a80462cdcb84b53be23d6b17e9c4e15548d682a8a181f235f79c0a666bb2b06733073b6c020d1bb7030cc3e526df15aa940257dbd849d86315c054ed4dd50b8d999721fd50986a569ba2f8f3a71f49517df57e16a90b1db314e643a6b4cc0eeb26c62b3f2dc34d1d293e388c75441daa55996deabe0a3108a839629945460dd44c0b0eb6c53475ee5a245f33ea9942253de8cb806201b2b23cb9e4629c93562375475283bed0d52d10b96b713f47adccd82185cd9c1921f032655f9c2c760b17c1f5c7e35a164574599a1b5b21e8765b850ed2610975384db48d4def38e467f2e09ae0b644f9daa8706c92d951135ba372040192896c34280f525ec0492783b4efd53eb3d0cfb92db46fdad93b323c99f2d85eb1b7e738b206225387edda8b90c6c6e6b0de4ab006e3de9a5ab15f4c9c3d5b01d2e4d97596533b33b8e2900e10504c69276a7d01253bdff3960a72be29939630fdbedffe2b7ff54be6d40fd9522728ae6acc00520f138f9e7fc177df799e751075110906d0649f3470d2a843f4239b10413fb87e1d7a477175e5b0e61cbbec1dbc22e117a6d21a53ced3518a7dc600fa04ed7639177979a31f9475f7a1f3389dc246bdab77fae98f2e3147881d09a7f0e103b7d47abeb86acc1f395002899ace4b9a5f649c7dc15b112810955244892fb011598806938acac37b68040c8146f640e29ebcd339eb0dbdc502d24f979fce4984f5a8e0c5f39572361a808fcf39b86ada5dc515eb4a9b5063b3cb8e18ff258a107838922703a422b47d8384943d0198db7d13c8f83d4af7f4e0a40bd649252f6dabd1aeea072b7d5584410a15f7c6a2ed20b175db6dd2edf9e9d68fbc6c7c4fd465e72879997490337dafd2c80b62e2a5a0cb1af1b79cfd6ad7b9f013d6ecdd952f878db9443237298df48221d2b66c457b2f4b39ea7214aca169002e9fb5973c2c3ab89f14ba7a04e963759ada9845b9ec094f291d0b739a1a3a4500448ac7a2d9bc278655ac234193360734eccaa97403952184000e5a621ab042e1f351874dfb8124ccad2046d3c591bbf8865fcd72624a649ff580ccd159b50e66e663b8dea996837dc63309539d822c7feefbf711ad1c255dfab34f2540a8541a961c6a3d740fd3d0eb91e76efa8d249f0ea814d259dfb49a0678a109da84439d8ecabd443d614a8f3073b600c05565a0cee56b6ece3aa50b6b817be9dc5b062340140c7ebe229f12ca63a1f09e20a68387170bcf8cd4236535b61b3c67f66449d178eea025fa71e0a5ae3b8ca5273c628304e8d3464979c581805fe14b6ad3753abfa6563ab741c355b639fa1d0697bd4ceb4e8bb661a2dce10582ea6c5bbff124244cded093bbd57f51e42381419aa9e64a034b0bc78aab9e83e4c0d15ef7c1f80f947e767aefcfe6c0f8e56be1a3974eb11a64b1a19430c913c046288a46f2abed553532feeb3c809f176700d973857ad300b2dbe2ada7245f7a9800cd682cd3ca7aae38bb73a8b6b6b8f666225addec2203e530646aabf3aba4ed5373856ecb7ecc0b44a73cbdc8a80213de7862c2873b7388b0796e1dd746ab0c73decff2bc6320ea157995cbe240a05b85a2978b1eb9ac0f14585a1ebe02e469541a5919a3e506bff400e014adf30a2d51fe44c84638aec9dbf0ee9c59290d747279009962a8a07aed8f8861d61a55eb92c3f46ee6e46b93f3ca3fb200db8670e3a1a6eb89f8fbac58f2a7e13fced422b631662a70cab54e3fb14f17399e26ef07341696d09bd39e311fbcdef66a709da249b00fb939d7bb34160ff6008fc00fd4a484f3481d4b965757b5a1e5898c4c71462866386bd4e50f5d1d50a658209a36fa18a561b0574ec558e7a0a62fb7d1559da6b786fa57238c9ec5754d58202ccdd3b1f03d9b48cad2d1db3ad9de7b472be67e2a6a020ad355d015e8578d69820b44e6c46c65b41e9778f629d4d0639e6e76fac6068a440765711623be4ec366224b8675245baca92af3e0ba9fa4bfbd516c128f4810aa51f31aefcaf154b32e828836296f02549062c292a872e7daa7df6ebd25bd9dfde077bbb1c722378288283aa3902a938f11728ca3080be9dca2c14c8af4554af3961718ecac4701705d37c5951ca4473a16e493bc2969f857e4e5f010611ac7545222c13a1b23ea6e624a533d4f1eb01e17d3ad0559ddc0f8c581377dbe1e78ce904aecdd3873e1a2ae5870e779c97db5be4f06091af9a4992c9d43faa2423493060bbdb30902cc0ece26082cf9943db9bc923aaf72419ba32ac3a8db2dc1cbb66325ac966c361ee6cd2ae4545398f2b75c85787a8563db4d5ece7fea86f466ef58d77c955e205d8a1da6aa518487d73f1526abf2f6a2b27bd3da47b1b78e03fe7b5a472cc77cbbe53ca226b442013ea003ba9e404633d2bd262e72834a2fd3dafb23282b11050e539f5c57b8ae82b9cd7568ac56cc66969219be4ae05614e629edfeb442317d0d99e0ae19bd281d4ca32b1fc4f2ba4bdfde7ef6820f00a83c5803e3bc3a42a51969a01181e6a29bf898f01b6d2e0842d9cd05c9f7778c4866db7a322ad3ce801cbde6bf085349e707ca736d2661cc992f0338953ef9640e8b9a896acb474d5b14d66e977205efbe61a4fd7c5ee7d7a8ada83a31c3b56bd84d48bfd5f02caab66edb81b1c1ae6238e6827d0b5dd5af0eb9fbb537d5736443fcf26072313f3d65de27f657594e965350e849613ba0052e14e6d2a935746dcc3053040259b992637f31d791902c8b91719cee7667166938f96f0e83c5913269f54ee1979bba7e7f9de7943d932c839291b6be62911108d32fddc54a3502752081d36bb883b060bfa16d1bd4705a6367d32fcfe7fb7d947c2cc61488b1b0ca9ab3c31bf781fc36b659b1a0b7d737a13b09a1a736c42dcc53e842cb4ca435d40def30685ab2e54be77bc0392aef4421494c11c74a35b6b3a0afa8c481bad05e4585e841e9bc25974db151b79bbdae7b1789254a57a185cb25dd10c2e83578cd2bcc3bdd7e623905f6888accf85a27af0dd7ecc849dd0f0b227d585fd4e77428840914e2a44ddc9869f819b7b6c36946ec8c79b44fc7618ef1b3c17ed947e6261bdd151aa8cd186170a901c0d2c8bdc241214abd0c2ef482767384414b9ee8071a03cf569061208abdc4563c2bca0aa04b2c0fbb04bbb39d1f0f9af261b2da902d284ae0999f08d60050fa0dc9ede8022f230e8c1f6b6a5ebaad1935c0a8a23abe3141edec012eb2377c150a0d31722d91374aee092b188454c3f9d1f0909638b00c5559a0b6383fa523b72fc984b4d4ba1224b256c9844da91668ca15df8cd3f4740dea5512e9505ff622c0eb2ef700b0336d4bdf8edae510178163b56718b0875d4fb141c22f33841d0eb523648ee6d30c2ad30132d90df25fb3a4fb85e26f55f264bc6b5b98665a5e35a4aa76baf86b1003ff3859996905c047c16432d5916bf487c3d622b746880e00032ebe3c2a3932242a65cc17a713e77809f56af2aef6bd3f10484e0ef8c1cd8944ef2df1d35d4ad4beb7d0ea9551d1c0ee30a0e953503f4895744b8473914cd12ed93a22242f28931dd71402297b25e85b81fd93ce9c217ef31dba52901ea71a70871c3985da0484ce12ae5153873439de29f9a9ed8796f20b2fc967060ed354d70cd33e8c5ed572989ea087b61bcc3a9208f7eb0b5e620c9fb0ac36ac4bbc11679fbc981e27c29b16d590f9acfec3e1db82269935f93dde437d53ca98bd4dc77f581269650f41eeaf91f97ad29e0e1526e5ad99086d6f88e490070a0de0d8e3fdbdf0d5e5713d4a275554283889a325e7e35fb75a8ecabc6c4c6234efa38139f1598d83b166ca2e41cd923907a461ec5798688171127265218f1fd1db06cff391444564c952b53be09544c60d2533b4456a6dd69264744a2a8d3f63b1bfba48ceb75bd4d2ada3a83d81ae960a3e6c97d14c37d6dbe967cfb036cdec0914743c83e2e2d33a9dea42c55adf982c9745291432695c8c3194da49b60a606dceab4dc67eeacd7a0810a2c44a2cc00bbb62448700f5ba2083c315887283b11b52c2ac4820e50d01a1c6895269a71c89a658adca1e26490f835808b8ca9db7e80cd3705796b4d3281c59230442d8af746a4038cc777d7cc1077c32810b63efe937317399c33a3d6f42b85fec569f508dc977b935ba46fa86588111a2b21e4d0585e9d3c3e1cc2dbad15f1cdfa83eb790a2caae370df8b3a023a7c8cc47052b6203899b84af614233f19d9bc814c7c951fd833153077abb0ca885a86ba3248921ca867e6894ef13f80c4bbad512e4e4738ab5d417a4c2752fbc4a0b4e2b67702d3be4397674d68095059fb297db0aa9c84a1834faaab23e7c7de2006dee8dcdf6084557aa7f714a14cb7b1a0378f15bde0c4363e4088764b44016043ec04bf7437ef4d27ededc8e3b90af7ee8f515db177e835fe34afb9036a2de2d07f6958cbbcf0be2bc591cb08e7555f4eda352421ea15fc8d320de1977c45242b4b394cf4c588934a79a181c36ca298c9d0beb9aa1d2486c3c240c73db39307df596a944a2ad1e8878c5e9bdd3ad171b6d96b0551df8c8a82b9908f14cb1b2b6d72b4ffb9fdcf79418063d833e7cb40b54b10e25852cea27d630449348228b5b6072c6c740813079b2a9b815949deb354fb68a01b7713753aef2b6a231a875003a94ebbd44735531e78fc6b052a56981d546cb3c563ae9d4d7c01ef1e858c645370b33d08a9ed206b1bb48c7f33201a0bbd24b710f1d8f1d27b2007a4d3b33d645b88462571b35b64d0f5b368e0d3dc3e6eed74a4c84eb463d97496741b921bccaded99d2807ee4101491762155dd4b9c488df6f462c2e8c7a4ba13ec4318ac19b3585e3c834dc37be3618b83d085df2db9a4f097087bb0b97a41b071ccf442ad0d802d0828dfd96b123599888cb656cb2193da46004005caebcdf1fdace3905aec0eefb2d73ee7bf044ec789b27eee670eaa24c7030aaedc6720dda91637dbb0f004b9335b168ea3bb365ac5433f8dcaf8054d399b0923fcbe7991dcd9ae1442147371e3d0937ddf2d9d3fc36de8a99ccab24f3a9493e37f3034e49af3e330ded4ea1bb4c0d7f5cab17cb2542a6cf2e66d8ba1d0c950d8a2dc1ca1a6352621ebb0241022be8dcafb604f167d1c3283afaba78c29251a7a7722210fce9c0343cb68c3ede6bd6c1573f05581f3b8c891fcdb326e8d78caaba6acc202b71d88ddb898a5eebb7f50af3e59bbd5d1cec12c09d12452cb7262d830d23ef14789bffb3b6a09be819661c849d902888994547cfb79e312dbd1b3ffef6f1ea7a0f606a3545bd056c1414d9c277ba302d1d96f3077494ef6029e69da789aac811a7636fafbdb242928d8d7ead40bfe3a7d961ed3357beae432ef6e60004c6c94cfc472b2d294568d2ed73322b43ad4e2eb501cae8242b300377de07df51419120ab608ec2e6c99e788105d6a389f581b2e78fe89118e68d948bd1fe2053729efadfa4278e04a184073b08311d8d40d3af117846120010ee2e38bb1c2c60e20fe1e784ed4d38a8b23046da04c6f15cf88d98812f2f94446139cddaf8a5bb6daeefc45fb3f2c1b130347bf6799abe0fd26838244706892092ea5c1b96845559c9a961a7ea61e687ad99f7bf0b9e9bec48c64d1724ed3e8be9a216b56921aa807c7b10b153088e172c4d95962d1ea373aae15d9826802005f2abf1ea5f55306fccbbddf541e56e57b45959ba219b9fe2ffa2ee986130ea21198cdaef27dd56dc02e0d2665d6e04d6a516cfb931b6f5fdca0286d7e9d401d8bb44e80a7e143139249df32852a0ada62e59a1dc153b4e797a9418166af4055a20162d36aa677b6c009cb06581e84ff839b0c6a6f5dec68d1bed05c341a6e3a4c5309003c68cd94a9649b7874f7e26c30319e87b91cdc88d8cc4d1ab27608cdb8db1801d7470e8b1f668360284975a193fd09829efd229dc6f8631f566197227a56628a73a26873777de1b62da93f6dd5eff2115732ae848000f7ccaa4258fc8b3dc1e7829b503dad60a9f883f9c0b86da18276405b7c7233576d72f9058782cd180391926878c24fa3e1ede88f68eaafe9bfd0427b9032305f3b14c906f7077fc5c4941583000b2bf3ee0a49296d26402da07d2073995ec6930a7c9039794ed4899b7bf9901362e93d315b71bc90703a102dff931619404edaae5cbe64b8e712d68d59dd2e735cab304828d8ca23797839e484ff56874b672010860381931c3ab9d91697798f511972fe893c85f3207b35cb81fbb024a481b43c59069d25fcda0cc13422f6d29a98275b62ebabf8209551d9d293cad216ef561efde3752ac4bc1315e3add96ceb2efcb69fb50f7db5fe489ea233006badde9fcc213301fc853834eaf1f52693725183c550ce656af1be40b13f85a3ca15fe0706ac846453e11a69340033ae6871e00bd1a3d0e12847ecf5af46d471d674945a73241860471c8fa43dd9e8f5e5345ebae09771005769596d013ae4b4ba58b117f9c46b8a00528e2c006e8e3b582236e518efa232bccf477180dae1ce507cc8aaf1ad7092d0c7781caa56c62ef65f6e2a9e44422cf18fb025e2abbc3de072020bbbd929fad0ff19e8d5b6239edf394a435f2c6af6661c026010a1a79c80a7c69f4c1a305b54c6e9875e7385fa714155ad7aaa34a1743fec0c60a81605bab0e98511c07576844f0ae7d28de29abbb38c3c2a30203d1af07724d46a67b94aba4d56b57052904120cdd862388de70bae8a56a99ca7b3ed4c70299356e5eb83b0891f5ee197cf04c6db351835a6d61c27305c489a5474fd60ac39df45f480767977c98d305e220fb00ed11309ef786c7220fd897e518176f9c5465318e5629ac7f7da9b7bd1c072ea35e172e6a56b03aed51b46b4159dccfe1d35639e225cd1334c5f07509a1788f91c90dd9fe7cf2fa0af6c9d67d55d2cdfe122ead3911d791254b6cebf3c108fa1208e7c2f81ede7e49856d8f9b42315856b4562d59d2c11221da76e16c3b0c8e180716b00b61a7cd048914a4ba5d0e287f416afc58c3a59bd8d9a617ef672b77862168c28f764498f14a49d79dc7d2e2bc4017e853aca631f396cd8b63620673d10b8e578c0466c04329fae78a07d0acc47a0171f026263078fe0a5f647ed73d04e017bd042b4daa10ba50ae7e7d18c33b146bfde1c48bba4f38c16ab267d8d2f15b7eb1bd1582212ed8d0306a482b154776e0834243b570e267f65d3705b99712fe98932bac2516efc788842894b650db38a2a2d16bf321221ad58a99f81643e8db2db7cff32be471d18bb80be76826f897fae6960e94faaa960dbeaf63b320a0aae03ae5602919f67d026ac7efca1251027aebd1371bd708aacfc5b230432956075c5c1f84c8b3134bd9f37732119f7643ec7a7d13d6e552fe0c852f43cad71e2f5976a8c47b975bcf42e513ba05249a37d8021fdc1d68d9fbe3f08470216aaf93a79b9063f2f5180ad6a3c370999653289e57da8bfa78f18171d3979a7dbb605b6b54251aaba180c92dacd7581f57094c33bf46cc851d4fc5172a27c250677314763f92885f4316d429b3ecbaea7f778483fe4871588d330cf0165155c9ea8256c52fd41d7201514f64392bc132aac250cacb241c4ecf51fc9c9cfa3d64e552d4475797198a6777cad13637b4e9d32dd8199599e6ab63847d4d6e13b093f9b91430d946a4fe9de77a916998a76a5b5f112ea019aaefcd709651440eb74114d7595d4aeb5cf0a54d6302533b9a36d6510e72952b2370bb1aad7fd158588528a741eebc74f64400ed0044cddf0537e9251e2293e572701fd77d55696f0bedda2a840e31eea290fb32015df16954b0f6e4b6c436ae3b19b5e3c31f2615e5fbfcdae78bcfb4127a195bb88c0d552a136022182f9c8908fb0ef7df78d3edcaf4e537fa70f7de0aaa24a211ff6a547d3930c5f101e2fa33247ba7ae8d92c3b7dc5ab10371a714b53e9897818f0aa8f2812c4db59e4eadbb6c1a80bc7c5f1d585844cd88b00b938d1d6ce3e6abf1233c45c4f1fa1468777a5f5638a31e8a47cd9d210cb740b5ea92868d41c17aa8675fe6ac3dc3cce82d84e8d38e67bdb4155f07ffe571315246c0c9fcf0cb5aa61016a20970d985647d26f881203818f2c751a9a915a89493e1055af9cec84f51490b8b114a9b54e86b384a15c16acbdbcbfca6825811cddeac08718ebb682594b3a788df7cd6a9d8aaaa2d178bdb230aa69b82d61d87f8cb643b5ee15010249f6f8778b5c40492378539cd9a732898f43f3cd70d6199f1d5247b7649db94cd9c0842cdf95dc7e77cc39fc55df836d66cd3f49ddb5d7593650751d48da5c4b518f48919b101746256a9998b3a9b8d86f4bf439951d451e79ee233349b1be8b748a732274c256e4274514f3ee64a9742e84b8a2390fbaebf9850249f93a44488fc1d04e162c5e9405ab253376a6e9878c2e07623c9075878e378f23c0c780f254f1029947f6d8f4cd5e95b566519d3809198666f755a12aa190bae3689b563a042930abb89626cc4af4d61c3691d2ad9316f7bdadbb1a69c745820571298600a18e93fe07af2a1ec3df363259f8ce1d33789c820c7318fb63054a5ad02f5edc094d44a163428ad8a2605c2f257dfc4d4750095f5164e1db23e6c7f0ec65a82382dbaccd50d9b21cfc596fc887d3bc850a646725b384074356b218812de30e4a19d97aa008342f6318b373539268197df0c550194ce56e33aeacdcd4753fc122b2152959e6c2c31f9e11994143c7f4eeccf503792dc21bc65bab0c40cc588efff083a031e92ff30e0ccad56c1abe04367803a4d537cc1acbcb49207aa9fe5ad78b1ef3cf4800d731f5775d54f3e36f88d7dcff6e2e52ff2c65ac132f385a9dca80532aa0e7a1632acdf2b50facc79422a9224869cacf4684f3075271ec6a14dbc5f71b62ce344319ab97f6e389e3f8664b72b505e5a85f22c35fd79b1c6b84bc7f5d60303269ed12beae494c60ef0dfe7be79dc4cdcb13fef9d56f9ca764d6967f796d821e89c6f59138386b8c456bd9d2d6a223dafc65604cac9ffbff2ee0292c465522f1d7b497f21414c2015e0c1b9bcfb3db94c2f5d87182bf163dfae4ba6faeac31e7149040f7ea317666bf1f3c97018b69df876b300adbcd8e54af8203d751e852b603d18ef60250f6f83551280578f62ab72b1bad09dceb3b0d80ba801a9a1811f058cf3b357b662a7fee7db860a43d38013c1dd045b6517613cc143cf94d411f476c32724d315a00e2da1ce48a1f90b7ecc56c66c3c7c4831f956d7b72d1242e24d51acacc15df9849eff4ebf3a9cea63b5d14c05f244ad589a3979667fae0b0284d9a57847287965bd39030d586f5431d9a4ec43cde987154ae29927e0f7900416ba4164fabf475df6fc9bf20fd1d1fd5d5a247c404c97ccd00d6a7616b48fbb365790cd19b678b7d50ea682e43a6ec21fcf6d8303bf94373950a84d0f777aa1209075290e174456f4f1f29ae32033625f5c6cb00d3549ea4f52fd85a88b19fa9817e237fcd1687c4592ddae3fdf0957096691a1aa75e6da09d9c3097bd08d285e4638cdf16df28dd19fa3038e7d11f4e9e6535c1be27ff2dd1711f53dfc67c28a0dd6f6d94ab770d08882a2e505da7d2e5a1f9afcd05ba5b12f8ed697d7be6b7ac8df76fb87f0c5d9318c497a0ab0c5f7ebc41dbb339fb978fa3f0ce0d447e881f71e16331aa016e44da08da294b020048caa0bbe3257c154826619092ae36cd31ec95aa03698577275e0b263871ad9097fc25bb45f7c2a6eb1c77151c55cc6b92cb76e95cf8b35a3a90bb5f4053e7536d265a8a21197cc62055e75fe154e0c446e5a3d59a1f5524e386a45c8912a5ac242c43b1f12344504fd6d789d0b10049ea9cb0d03057d4cae8990b315a2b14906bcab83e7f092f1b996044c290758b56e5749621694d40ea71b4af0064fe054fa864b35fabfebbd4057bf010c30529cc01ef2f74c930c09d40eaa65d66cdf37d1af7c9a849fcf3c139a72180c83b4e77a977b1a90978de27226eac750effd223eae5ace2996339741a0d86e241ba4b9daa9b3fe301a064f3ef9257c9cc1b2bdd021916e10f6532f332a64a8ee71b4ae69fce92e7f214f174374331ab5b0ebe819a97f5ccc006d7ec789bf8a78b54823bc2e87adeba504c56eef0d28c1a06bb95351aae18b66c7bc2c45240a817e949b47669f2cbb27bf22388c060142796c98d213584fb9f7b9889e97239b956a1f4978618efd7581a58eb0c7d4901f03b4d911c99138d3defe6c46e65210b29741352b6eb3650629933211c3dbc3b65f326b205087612184a5dcad9b7ccf5305f4c8fb767e9b3065a278b3fe81ed63d688548f01e1066ec0c5ee076f52e3186b82c142bf50e73b5ee7a064ba9acd14ea91cb7eab1edb3bae5668ea9a0beb767fa234e30ace22aa7f22b03932febe40b6d5e7d02d46f08c934b19dcb6698e8022269b0fc9a2ece43f188a3069c697937b4745d7b9cf6f2e82e5728718c31dd5d34b63f82ec4e7915921fa7829c9fe06fd034bcc63cd5cc5c23195364bc1c420b9d760c20f1df875b5e71aa123e9e341a7bd44ec70f7ea820a40cb7d7dafb614b2af7fbd97a2933c9a84994cbd103fe60b69a8900e6df1f0daefc983dbded29c559df033bb3bb637467b338500031bdb54929949659fc326e5e7d882a807f2ea27477ef07018b3593e81cf23e4cf60073c10509d59bede311c8a16834f730f1f6b86115cb317b3d9f32d5ca7a562521c57cb2bf63352df5d09d2167b404f3c42609defcc9a367f27c01b94c79e68fa7603b3f9c8bd32701dc3acdcc246ef994c2f7495ef343f53a177cc0bbb34bc38596f03d2f62c1b6789f3c75594535fe027ad912d2bc54e639d8da41e7134ce2e5d89f66798d0a55ae63131c42f4f39b67bd38e216b1ac231b0f5c2c28cdb55c013a39419025523585938ee82d5c8b6237cd331c5ba55b977d7432e17c663aa214b553f2f085e750098b2143822d2dd03753ee121d3d2ecd7a140c3028decac2e09bcca03946c2aee711dfa56ab02a3b30421c2228949b352920c337127ab439e58f4570a91813d204d342bc5bec8ea191752c51433a5055a5ce807aed73074cd0d15ceb751c0f5ca20daa03054ca0012981114750de99e37caa5dfb4a98f3aea6be217a17c5b846eb1c6650a2b77df3215373ae8e6f278bd134becbbc5eb1ea7c4f20fb184803dbc03063c0d054de57a466261ad7148cc03f7c55733336ada1b631fabb68f6d85484e5e25aa2df6bb4b08f51e02af652679016b8e31e8e81407385edb6861f3b1cfa455944c16b3bd0f10eae6ec82f8f2ade4f13da5a98ee4e197baacc8bacde708d7ef430971e996eddbf8616dcb61d11d445853187f1700a695c7c3b1e4fed294cb2d80473fd4d41f3be2eee836413ad9a1366d869ad2a74fd73107b4b75606e2c81d5190a06f5a9faa1094df0751d5df2f9bd2d0147347c02f5338ab399462c2d95fa16119af2b360614b20be3c408ed065be0afd2aa5e635aaaf242efd2d24e9ee0ccc83736e0882c15ad5cdbbbf3a768ead9120029e2e4f2375a0bd44c7c7452d2f76efed5ea53ee5d20f0592677e7cae1ffae0a0481a4cacd1829f31abd031354494db95f183bdec91b7786d8621b1b71d75af23cbeb5f7daead881f7c77f65c68b83cbdc678ecb4f72e5b0d1e839059a7e18bb126d8703fcee1b516c2ef622f3e6c0695d0a35a56faf596a208474d976ee49116f160877a3360cc3d8aa6c3720b1e2da528622baa16036c55c57cf854e1b1fc6e00986bde0e04564e5d7f7302b7bbc70507d3231d0f1e136d352b51f2ec8facda5fac9d39522dfe2d4509849275a4a270fafd3cd879f0692b6f3db4132eaadd9662d0cc7c222f6e265ce63278225a61283c1f15ca4d9b24e859e526fc16f3c7f17b65dd065cd68c90751ea56557b7530dfe710c27f8daf2c0b6d4dcb9c987d32b9b869e3fc33ee1791256ed6885bf01ed00f14d0740251a3a372e37bc58e6259c2c41cb0a1cc85aa6f437638e8ebc3a2c6352ea3658314a2d5f87a211e9e071c542d2ee3023da5837440bed7645aa9e0caceed1efc92ea6765e33310bdd83a9b14f355220b52a5e2e7f171c5947c37460c3c5f655aa9461029346b18ab8692911bb3b6515b327cc0d67311a00da3c5717f993ac010914d605f5d348f4cd887589b13a6b517a772a4d482045f2689147814643689e570b2a86486464a318dc74c6eb0dac5e92dfec033ebc8c81ac13f2dff7175da058bf2b534fa5de1ef3a7715f6f933a1ace3315aafb841d45337e80453b1ce81c49828e29770d4f52bb98c84eeaa22a080a62e4475f4766e0d24e4508eb8c4ef7272cda65037a1d2444fc5fb6bf51b32d51e6fdf1a29eff1795e8d75db6bed59422803b7dc27af15bf31518ddba97f6c929a66d211b693235ffcebf8cb63cfc067cb19e3fa488274772b678eafbc31de658823ba00210af331da778edcd83c4f3fdb3c83b4cdeb97205dfbaa160687295fb840b2096f7ffc8ace0fcbfb771292e6a4cd03d5fb0c1bde868b4df08faff7d6812558b2c4fcf98ae7cd2e8a8bd2ea0d066069adbba4575a6b5d04ff9db8f7cd0ebd6da3912a4e9f8c043232a28db38e1b2c930b4270b59cc81f43fcf238bad43a2661fec37505d3d21b7a1d5fc60e7c452620d78d4ecd78cb02ad4be6c6e614c7dcf46db48fd078476278e7b16cbf4773df6808ad5d043b1d1a2c083dc6416bfcf0139b1bdb5ea74e27f6df34e02f45613c5aa4a23b1354e3622454b4513264f3e9361c43f12d06e06d82bfbc3cb2e786db2ce62c0db35bb50c5d75e21aa81c79da349716707118c310978a0a6d8eea9ada1e51e041a5b570910f204217166af883d3eed176a9c8aa42bca535282e05096188c0b11a47a2bc04be933c8bb4354c3abb41bfa39e1a98adaeb384d884869837e8d70ff53427865fa9f4b5eee550c775353e473c270c8372df59c44a12c9164da01a98d7b434579ff7ee4718a0e99334b3f3cfe8a198b4f8dee70b26a9148536c79d65027c2c0faf9fef0b3f15e54bd9165d7986da30fb470aabeb7d114e9cbabb527d902a41598ba522168c36caecc20d9d9a760b856147fb8423bc6f59bb235edf761a8e76fde61cbfe1d062328df940e472581615e757a9eb86bd25d85bc2b4de3db8b881c6e5816de4a6edfde476667e8f0fbd7c35d7956d2980fa38c7cc6a87c2d24040f1845ddd165c33be2e90b54a428c66d16adc465d47ca1e3718c2b8009db3c408f6c88ce72d5c7869fad7d9200b8ab714d5ffbffe9dc4b6b1cce9a876271f027e832f95f52d08fa053ab992b920585102e2d8094e29b8ac3130afdc0cc3bc0f7390ffa5afd6c7163383db19d6b1486315458364f9369fc1b801cf683737664e2997e672f410ad04a2e03f1b8d92e470099cb6dc4a6d709fe6edd2fdcc3c3722228ffce8db27466725b466a0f74af137dd118d5385adf192588f332502d214c5f9981616b7d0e5a4ad99fbc0f38bb985469d4017e76d191aa8ecb2cc2970406dfb81d902bb732065cbc47f4aed3ce72504e2254d7d0e0662560362ff5def6f481bde4ceb3ba3854978dfa80f55389ee94709b4cbddaea2b57230ea79dcf318cc3581be15a91ec897d70f0fd08d3b98e9db873f965f3b8c8fd43ea4281f7938f1e972c97ba894a6b4e503e3315adf5e7b0aab83cc69436fab329fb347633379037cc5d7a9bbbe899007a5f60e6698a10485b49b61af486ca81c04cc9de3ee2576d4aa4164698c0769e3b727423a8396b3667b43b4dfc7a637d8605c8f690c34fb9de7a78d8485d21e72b3b36bc9fd24b3c1538347341e11b9cb4172794d56ac48d4caeccfc09212032a7056909afe2f948a2d3315b04fdd4a9989f4f9ac04ae41c887cb58409a37d9697a7cdc0fddd09bf29180aa31151e7301770d0dd1ec0aadc38a6fb5b901a4285d1e3419c12b59313ba19dca34666bd09aebe5f42ce0ec757183a26cf42c705feeb2291f4343a2f95e7e22c9f3a051e528ccbbe3617ccafaab59f7d3b6604ca884865bfc7c5a9988a15d953a625f99e4b2b943450447f7284d5164856b5ad568baece23beee87671e9b73b3ad070a6e91ae51e26770dae126fcc8c74267428d4723129a1bd977acaded7c25e911c458583457afb68930a429cf243b8b283a4935f192b884cdaaabbb79ae5651d4c98b30dab02d90abac7f2bc8d426c51df7fbc4d3de28d4adcb920f9194bf16aeef4a5e6a349dc06f789d40220870b167b5d7575eda8024f6590d27a9ed2e2710e5a02e4c2ffeac5c726c7deef2bb9f143ff78d500f0861fb5330e3eb98a2db1a14263899349d8e76b10d69d4b6f7e63bbf8381a0d29808725a5688fbea6b5a4e4412d1d7bda894d730d6fb4534a1f0415e4bca4dfec2cbe7bb55913fb5d0c85c151cb97d681fd50f6a19ce8e94a9d7c6850b4a904ec282ea8d5a71e5ece9c15bcde0ca1bba75228951fd488af40cd4ed946bbb23d0ca0d2ca64d4a9ef46ae698ae2dc80026c78f9a3a6eae52aa8905035d88b9c1b6adac39b2a9ff7cceb229e4e78fbc26b7dd14f456fa6de56cb9cfd5353a33e7a3174b344acd4261fcc1e2d5df455c15f4554f5fc71761c546d4b68c2f3abb4d09f0baf2b7c2b71cd41b7fc84682ba1149d3460067f82fb65d5c3194dcd2db888695a8bef47997a325b638ba409e9f507bf42c5dad23e9b39fc210138670d39664c2e4018d976d1c2062d67af134235ebb40ec62a5b1b465537a19462bfb23347c3c4e07c0ca177fb98cfe0976e1b0de01674394e26d9ee77dbfb365d6ef773fd80553de20896d9dd1c6f089191e797f1556214fd8064c84d935c8523fcb81b93fba092897dfea4b98f735393f1e6fef2ac939026d98f498be96b362bcc896d92009c60d8a616f644b0f1bb8bc4238fd0013f13e4fe209611a5abcf668661048661f88d117c86fdab99c8304ccdb00ca33062cd308cc3b0c95fcd3a164326d87b2192735887d6d3ba51eaa38bd8a63b3be59c15961550a15332ccc36acdc8e6479dcbb0f175e7b0bbf40459067469a1ca2d3bfd40de281856efd429ff9689b4647b446b357ed8a2852c4565d41212a443a11ab795bd21d7dcb70321b9b1c4ecd96cc680a6c62dd50ce0b168da95e26a5c53499b5dfb1053d5de367ff405596c86d33b37c25cd73a5299dbe02e841f177020ac821dc28546444471d9bf5c167c36aeb509c9b047f2e618aa384c5662182b7b39d94e4ace817e241dfd6a4ace4d917913b74db0930656ec5df2a9210f7eeb0e200b93300c9089fd396e16ca8c35ef0994d782049b7882eb36fec134da12d0183811d1d8159ac9a5333b1345d1347b8c4234efa12125ee8a59e5a79fe38681236c5ee6cbf569eaf228629408a989f653134964f75e9e568e0ece1d2de6714af3dddb91c9c7f60ef2a9722a4dbabde498101f38e60d694f641389138ae678bbcee97ae101919760ea400d5447ae5f6013194494d37d8ee11e533fd8ee4ccdc03ff7bcf6666b64901d70e28738fa17c644c21798b936f8af65952dc5443aaa040a1f4292b83c3768c55eeb1c963455da52ce38c9ca6e834aea3bc19a5394d9c2ffbc0f20bed87d54701e50bed498282c1d459d13bcd1cd2a9cadfcd9f2764d3ee321afd0dfb9c8a48cecae10ab641c9f327353b28e2239b633b633c78e1e26c58c3f819bf9bc5085f6000e59ad3a6f1f0d07b60502b5e22989d82aaea2fb41fb083e6414a3a62a3826087ddf8eaeeb799e55f0bcae2acb9ee77bdf4db7a269a9c2cbe52024e4e60c210e2d774e137e700107175563374b7c7363ab160bfdbccc1fc54b465db39d25cc59f7f802260e496dc45fdd49463fbb9d25edf1d3d9ac15bf9c331a58b8fc248330f04ed5a5f86bc8c9883f3c10f6c035374d95806f52f02bf91ccccad5a78b479f24e989b1375135f5bd6f5634c7673bb6f25aad2ee9873e5f233200f6207db8ab0c3d07143dcf19695b4ed2a113e77fb8fb42674961ca57da131b1de4dd1e15e63420f574d94ae996f214bfdfcd0969e82b3f2699d57d7107ddca3f0d6e548e38e21b59cf7c694278d32c3afa068bceb1f71b4f0a4f50a13d1ea1a5d4be6dd4bebd97be5d96bec592be1d92c9cab9819c38d9c3df363b762c75fb1c37b3beb5705275a837c9392d71da952c81251f270453ccaa477fdae24aa15e8e813ec8b84bc8d0eaef296d28bcb7c04b2a429a0a6b03a9c661aaec414566f84d6c2b8910d90ac22566b1e3b9aec94061c6fedefa617a92734347979f3bf998d063010ae13505d716d52d5cc12a647fefb70e357767e30c969130f1e9203a4bcd5e9107482b1860db335d85045f9c6fabb8e13c70f34aa0b79e6cc9c90e7cc4420090043199662a48c1f63beb8a626723e9991a699d3409e1f138b56602765611d57b334fcf606d563b2c628203386668898fa91bde925733b19f51e626c95867fce09db98df8dee4fc0b62467d4d265387a4b5b5c60f7a14209372e867880b2926ef4dd3c63df50da10d40196b560186ed17486094b884c23041e51594c130cc7ffee7fffc47356f637afc8f7fbfe7aafaf7fff91ffff6fffedbff5dcdd3f1bfaa342ffff1fffcdb3ffef15fbdb11dde7ffee3dfdd399b8ff9dfffe37f4fecc73b94fffcc7f40765f8efd1bb6cebe6f8e73f7008fa33b66ff93fff716ec3ff28d223fd67ba2c439ba7473b4fe0bf56cf55f51f79936e7b79fce77954ff8bfa8f2cdd4b02fbbf0a8896dcbf6188611829a518d6f8db3ccefb4f48fa3f7c58c91d5cf34f63fddbfd93c53079e24579201482ebc70cc3c87f621e33fcc1b1e2486a82d88559dbffb374f8f3552a0cc3b88a44083f40957d38e0188651f13f46bf3f3cb417f5b41513e89ffefd59baff0165823f781257832bc4c508dce40cc370e8df0dd87f9810b5c5b735db1593f327a09a7fe90a7f9aac22f477188dc99d2010c330f01f5076ff833706bf8303a60411bf3f78fcf5773f7f9dc1ff89d1ac3965f230320c63ff21f99b048651d07c38289c459a210e1d861161eaafd11f533eb6d4e5e852a919628761a2ecbf02fe9f0d3234c656c19c452ec6306cf1870933ed7f9cb49e0b2bffc6e78a118161a4bf87c17e7ff815f59af49f928522f6875fdcdcff8da7308ac2a4f01987f0c0306cfbc7094cf1c7d436ac94f08d88bd72d46118c9f983c4527fac4e3009f510c86575f88397d47fa6b83fa0ac0e60be8bd36812aa23c3b0e3f197fa5fd74b055d44d0df5ffd7bb4ccc723edf7970a2f50ac70fe1d346d4c13130c69501bf1672e665906ab6cb0b4601ed0da805b9654e3086c4853ee4c350c3357bd50a7da47dd95afed2a6230d6d3b70a14a0de2d79b62a7faa86206c27842f2e7b650223f52a9c142156dad9ab01fc3ece01b5fc32922edc2004b0432d09bad07453f46ef08ed2da12b058ead4e9bffc4988d75f2fa46c0f368a4324b21d1722894f082abdd2c58ec41f189bc009ac457556f6fcfe97ec399a145b2e46819857204e63943e4da52dcf432d3c99a2b0e222380f2b989be8b7cc711892d2ff1c63f86d0e970e876987638c57ace041c4ca1e7b9e7a620b41fa77060c3b884c8d14c8aa8a21b42b8dd06a66ec6daef6543b3893169e5304e7fbc00a4f5f045439987c5b05a6250ec3b8306890b3d11133a0df0050ae648636d902fe600147450047b20467b99041b6fcb8ca734747cf8539a5cadc2e99dd2eebee9eb68144d128220eeefaa2df640e3f3b42b712f0e600a24b0f37ce4eabb96bf0fa78fb657dba60632a9e07ec004f29653bfa9c049d8d3f66c487f886774d79cbb5d167bf71d9a53f13affaa837a8a91a03bddb3c0f222ba46f5cd9154e1be68272ad70db64fb990a5481e4cf107fdf707d746013817544c85a4585f03bca21a7a30d452d322001f2e1d17eba9c96417b969a79edb6419ebbcd8c3920d69ef91897d53ab938130b37a60118f4adf6d87c1b7e229608764649dc2eb27d44c26f4356980339bab5b4141748dd96ecc9d6a2491a1b9c341114db9115590bea0e2b885b1b7aef80f48ceebc4e02f91de88f5c1002ad903e3013ac5c3ca26eaab8d97e483a477cd550b3d403af3e8ed817736a4401af2f1e353ec4acb733ad5fa7613d996368903a776ebe671b2fc6874c87304ec92417cb9f09c664b752d530520f483d74d3eaf0f0cc2848cd95375b520200314f99c4288df1c1cde8de0db7b2e30d3b094c29f3ab99aa38b8c025d96dfce9506a528f9a8bc1826d7b6725716044e1ec2eb7c4fd3caa8d0f1966c9c15c3b98c587edfd3159d617b14aa0811406e5deae98355b609ead74bcd1fd85387c67a11a769451b00eb258fe3c1f4acdb29c5f9ff552db2693a2a9526394f0074f119095bae7f825316a804cfcc11fff39617242de44aeb40dda995776033dd6c876db762d2212010c79b58cfe691b26663fe84f7dfaab28b53ed3d1a85b428c822d345ccba0648185659006fae470eb3259be9de116aeeecb465b6233d667fa8d5536c6871585f0192a753873662b22fe529ce26a2c9cf7fefd42374ab631e9e97e89a216e2301ac10ae1b4cd5574c9639673646ecab65c75f6f0f964661fa416c02e74490168afc0dd14baaddcdf36d3edc770b0ee7043cc1acb2eff96138221045910395cc8ed7f9f5ffbc3c95b246efef3f8e09ce96ff2b99588dad3bddb47724d28f8b53884e44dad179bb8432d6c3a8fd5afeab4504e1acd97d85470514145122eb2b46fa16c0d641b8699c94a59a1748fea1c65ffda0455104e0f6e2ae59913d267469a4845ba440c2d6207be422e7aaaaa01af90275be9731a93c1e875e252363df7d93dc2c4cf16d4a35711e4b99051d291f942ea956c89eb94b74be666104567c8d1905f0f59127521a898c5387d4787e62729d8ed3909c66b863a7c8181894e74048551ed0ae1946356e9cedb255720f6b5d44962c2a33981880db1205abf0d325b10dd4be0a4756b7e186906bea7b2bc64555624de5f2e49f378790d880dd32a47ec9017b41445b20b1ef2d62b14f494d5249c6d064764697610fcb189c8d7894546721d095a1a291beb449b47b7c06b0b99c0dd676fc89f541b223ede372722753f3688f3542063060ed78b1e5dcc46622a46ca4aa2e9d927c83042f37a2ce90b782ef4c0ca2b95aaa31f25fc176503e11910099ffd2720c61413addd6028d5b54786a1bc1e6d3aca42e50740a05ca99d08036cfb2194256591c48fea4f026b1e78393d557113443ecbc35dc5a4ebab43eaa9a4d875d74d50243219eeaee802d3fad8a422ffc66a6281864ccdcd0e220c89119803e55136482a28d1987b2da8736b99d4abe8b2d2b377db379aaaab730f19d1aa100f75eef5ce1cc8140c0784ae80e393b80d12dfb7212fce76b69074b31033a1a0c07da2f8ca62ff2cc0109908008e1e0460e7297f9748f046146aed1fd4b5c94e7fb86c8064486652f700d1a71b05c80a106f7a94fce6ebf9520c4a7251c68353f5511368ddcc444a24e88c14b600e6d500184d0f8fa42c25bc10186a8ebcf2f14ea10f5f5d622c9a2f25a55b6d66389d893742f4c33c5538e34f2c086e5195a3e3025092d26cf668c26f15d412c6ce53c1d190aa07a8d466d15595d9e6f267795e97b82f5b4066a3b4db5bb7ecfeb0c6e4647d3f867efea6392eec9f6fbdaf0f8ec0d7db7d7f217ebad87d44edea06a206ce2896df26db6178aa52ec8bfaee6badabd847c34d9e3a03f1f5808bf79b7d455cc22599207f0fd518fde5f5c7aa6710d4b6532f796f10967836a4082d1d6c3d6c0d9a36ce8b781e9879afb592bdd3e1de676f47cbe2d6dcf5bde1e7d83181f9bd92e0d3d872dc4ebfddfd322e6ee4faedfd6b33dc8d79da4f002e5c88af8ff89408b2b85f8f3c5f773aa7db1c305fb6f303f30afef25a8cb18351d4c868dde59ed7c068dd8564dd1e6f0ca1fe29ed8fdd936c0e85e3ddb860d512ad3e71b6d7fdfc95152be976f1e976d178103626708e527eaf248b5463c7a5204bfbd26d01f4c0b3f3356b8294f9065552b088055cdb030d774a11e1d3597d561fb43f364fcaf302f678053b7cb60aacd35b6a5741b3958cefbed64e4ef6f41e3386725be14927c38c11daacd7121f1494bfb31735b10407338bceb8dbb5eca2bac67d4763ee38481e25d50b417ea88dbe1df05067b124d41b0c5edda026b0e97a409df9760633e288d7d5631b78c3fa0a0a6969ae709fd31a3308fe33011d84389e9eab5abd7381abeca1e02a87725d9db3dc4fb38f620bb552df2f6253bf5cd0c1224f73c48274ae023a12e072ec149d545d8c087b66243e7dd3748286e5e6f26beb7b224bb6b6850a5cd50091f1eeafc54fc442a06c3afcbcf48df91117252e93356a106d4a92e7ddd1ee3e7d7e371548032356315fe7e37600d3dd34e86a473af2cd9fbadfafc1047cf45db5c7c82b4bcefe498ddf59ac68aa5779a065346b72acd33d69d69e419aaa6f68a11f364a9b8d27f421e39f14c73196bbbf78d9fd52ac99642a77a5c3d5459ea6ae81b3950e2302957bfbc041064f8e691c6d2b6cf114256347a7ce0c7a293942258a1e4d252203179cf14df949e8494016828d974dd434add9ab0a17165756119b75ba3c5e81667820cede594e04d0b9e79f561143b5d001ef3becdf327e32883d68e8ccbbf39c355fc1a848acefe29c01d3bd27996d2c314cec34d86df0b08db1ecee924ac0fc1eb44269d4ed73496d809d641e90b15f2b5e93baf163c92a795c6cf790cf19cdf125b566d1cd81f93fa9cebffef663eb64666bc94924b66131e6749efaea779e2b9983257866e14516711ea3625e9897a9e47ec64d62886772e5da3f58571035c7c759cf174d270059af15cb3846dafe5c2bf4fd4010334d661da258ec188971838f60ea99e31a853183ec1ea2ef6efc8fea18c00637f0ad401ea42ed002c90e902fe2ce27b97980dce259b02e48f97ca8dd96d9b6ce0ffefd4e9924e1f6f21358857a468811854948a1afbf3a40a465e6d83a0e832e41f81e3bb3062fed08621d4f6c844078726750b5b04fc2df80b3be3f246e1098916ffe3cf8900308ac6e935710e3e77fe6c741fb963e4766a1bbecbec524c2395adcb4ad2354157d1f551b5c8da9bcb3db9cf26add3c9a2d06283f499d33a45b914afbec41175bbab8c64f5cbb70830932db65b25b5a343b46b23a43fb9a269494e2e50791fa1978d8fac9433aeda8632567d3284233dffa7e1e4fda2c2139c2faeb942f755e97fa85d0c737005e5c2832b93b5a7c426eeddb8d5b5287bf54bc8e649184dd9195d33675ece35f004438cf0fa788ec46f9bd6445c7ff7c87b919689bd7e56f39849fc59de10dc6519883e108c65098e7e63dc69a99d1e169c68d1994e13bc68218eae669c68959e1e66346f3198ce11e46bf998ee12d465598c7e16146a7988de14e46704421ead22890c8df70b0820999fc6db18f59d75631fb3b3e3acf7436ac3309700cb7097f8ab8d32c26aab92d2ab032c4b12e83a6db33d673ea2ae31d3722d4504e873542676eada640d6777e3945240674697dbaa04478d93e90f7bda117ec0e21a08f2b47663034f1db080c310ec590c3f1196963810110d3d9e57207fe5819cba7b3b7b7251f22eb608516907441669500f0aea86f8f0ffef7a34d87e0cabe61ad34cbfd93279fc93cf7dd06663dc042e536342f5e4ee5b489c403b71cc2914ddff5d085b297371e197f7f2ee323c7c1aa1b00737240a182efe1bcbe6c0232aaed0bb6ceef8e23b51083df917f18b28bc5bed973ac18eb170b32629861e9dc255f53a3686aa86acc3befca48bc5e02c74a3f999971bcf854707519c0afbd677b54afbbba66f3a567f9933c32c88fb4415b3dcc2ca48afb6a12d910d1eb1974ebea94e698632154c8b81d19ce77397788f0a6d9eccbbb255cd4c90fe2fcda8a9e0e050a4eff9d88e0500319c07c27869cbc0008fdbd2fb692c136592da4a219b1acaba92c5f7594df46ca0c8e951c65b23125651f7801de2c1336f79ea6545bac601226ba81f316adad7652e247bc2a6d817b30ffab84e730dcdf12d205820c4363c806d32cc2f87091dda83a25d158e8668726ba30320c7b203ed4f635ad6d1cb779ac36b44b540e0a51d7715f569ad26adf7887dcbbb633a7d8dc47b2c035ecab283d027b3a72857fe4822619b3bdd709922008e2f74e9e6a674f937c9d4459e4dd5910f973836fc4a82aca8cdb6c72a7cc4fa3d0ef162bba9d92d816950b728be16eced97cd53c9fe41870545a4275535a1d0ef7caf8a4814e2b812a68b9f8f71b4e13501fd71eb17a3665df82f5c87dbf0920e4914c9dc9996242ef3b29628653bb377b67ec3955f429e43c80b43d2e998869734bdff0b6888dc0ed7c312b64bf30538f064dadbee12f412fbfaf9daed04c04891bbaa60edaa1c8f7bcb33608385f044da36e40fdb042b351b18663810d920a049a6198848ba756ec5701162e758dc1d60b917f4a30389f6bb906b60f7b2771aa74c4499fb7f7017c2d103e820fe2186fb359d1aa5cf018e0e5381ded679a2768003a0fa90a28f1064676e013963439138b02f0021b152263dd5eca97df62ea25e289ac46dcfda31414873ead85c2ce60b94efef8988c8c1cefc7e11dccdfed4f89b542ac8f852f6c6638530ee9e670ee29584354773bca93d47b8b85d4d23e1b3d026ebfcf24cfc7e41b7ccfeba5c78e3c41fc0eacd5c37ed9ae2a7dc7c5a8aedfc7058f00ee870f37bc2ac52f8600b12b9c2a29e79d3d0924e82b6adf68b83299368cd3804445b8473ce4399164f80de5ab4ea7d4913f9c096a42a72590cdcb10b155c330c571bba471ccfeaa73c93246828a9681ede556288ae9206ed5366681a24f80de892578024cc847dbdd84595f880323386265c1155f7e29567272fef89b122b8309590581df3966f588fbe2c5c944eb342e08723221807afb43a23f9782cd9b01ecb22b15834c44d9f6f9a323b008b3ba9ba58a479f1db3ce2a40698b3c606d6db09e27a68e2a0b968e918318dd5707490627b95eb684710aa14b6ca60ba3978746b5e7d8b8369f445ff01178cfe2d6dfdc43820ef563105d43cfce5b53dc3c33eb51c0a2af7dec92e1b8689e5fbebae998efa0daf6823ee979db9c0a89441b74c740b715230c13683c6b3027787e70b164ea4a0303bc8cd94d0c45c7eb99857dab0e41106f2a07805ffc33fb7663604b28528dae53470967fd85d2d5ab2f02e2b496da74adddf419c473ac060231f6b54ec6ab71e9f41bc51ec0b70739fff3c409bf6c69c8a282c5232e10bbfaf9e86247bfa70169e7e3b264f1a99cfc78120706fdbd131382e717c0e94d6b9f8cbe6c202301d332d09bda915f03df6bb3c458618551847f574901306d0277b0c400334e17eaee66764be777fe3822d200693d0dc470fb2d0e9afd577b8b76ae101235e2aa7d8b36302b36481fe46f7a206c7d4aa360fe662c7a3641951f979a6ca5c70260ba47d5810ab38036fed76c10f35aeac75c3162787bb48b794b96ac59accdc12fbe278652ed8b69d0b7b82ab73eb9dcebc23c9eea1eeadb1a54acb5edba68bfd5d957ade6dd526839910fc069415f7d502f0cd037d842cb33045108c32ca270212c354646beeeac7dcc2031dcdf4a573bb28a004dfd3a875c10f22b318f2e586c63000dcdcec83c5020b4ec6ddbeebebf083a3b6e5cab01ba9a5a3e426dd45ea00b8eb20301e71cb42dd93b888e5cb05be17477fe661e747ea72a323635dcb5718ddc365a283b9fc38c79cabdab2343e5492f704d0f336bdd7a6da77addb09fce80bde7b80c315c2ff92085695fce90fa3bb23dd47146c6a5388a94d6761ca35dd745cfbc5f18822a9369dc2fb37f81e78e31ab06096035cc3643b3e5913b3e7fd001abcb237cacc352dacc8c5fa7ac5e6b5b33c2c6baf85c4f38085cf0483f08a8f502630e6d55e5c8961f7543ab810569fcc93c55d56cfdc2e3dc27a9a4da282210501ffd3d1fe67c309c3d384424f94817706250804275e98a66d6b8c9c3dc566529c90521ef123d441d05e00e81e3c64f6de8caa4030f4b7f673876402974434d1a2bfb24667853686cf34db4b433aa510cb43cbfd8f9213e837baa5df1a1929ff92f567449b6716a816dbb8d271f40c73341161144f8d6ca51b3ecf6da53d34f0cd3034358c04f125dab5521fa49b87d8b6eafe67b0866da5a18f62fb974e14ac3ad1a50850791d0163a82d61fb83916dbfee19e9d2aa13e6c450f3a1d0f70744859744349b49d326c6bdde64b29cf538eab9be3e1243c24f633565bd4f0f1067eefe61a7d5f50c1a8ae57192d27029a1a8c22efa77c61c547e915e261e9811ee732d0de6604a94785e8dbab6d5772f526d62017b063553de5dbc298b2d324beb0fbce09ec8dfaf4c6cdf49a8eeb322cc81a732537a7222e86cb50ed2cc88674fb9db0f202eb6bcc9d5eaf9f6a5730068b626d4f1fa581d6cafc4818cf97d20a99a55672e46b8753186e45eb441b2a38ac64f86b97f372477aef8f26acb0f0cb736d097e812486d23238ef4f12121798d850504ef9eeeb7ae62cb841a2c5c05b4b7b02686b71297ee2ad0afdef4ee1588f8619f7b0a98d85c391971fb35e8cbc24bc3fbe1fde7b8f17809f4283ab816a93050877e134ce3890768053754cd695432a619e3193ecc6d0b4a9bfd51d1ad797354d4887d3891c3e7206c54dacdfa86b9b47d55323bc2460ff66d82d69dafe41f353b560b5dde28409e093ed6fad0b9e278de175377bd38496ca00756d5fdbd340920dd325535c56c430209ac14801aa37f6eb36f1cef4317337f7a3ebbd460edef067c20ac1a35b746f8348d75f431301030004c0e842093ed9208d5fd75a306c90dcf381104d37c65ac7fc71741e2b9264f3383363557fea6f85e85f8ad8a07fa5d36158e0d9850cf73e8c44b1f0d3dc58a1ab03a429af9e0c6659288217ef6264f82be2cdd0f07bbc24f4536c2ed0b315623022818467b337f276536b43fc8548a7aa2e146701498e509202ca3c13f717f86dcd7917dfc6cab860a7fd08975b4674174c9cd7aa119e87b31000d8cfb69874a60360f9a02cce330c95de8c7be3c16b38b666a9589b301a4faa75eb6d411dbc4a2431cd5dbb7d2dd4c89df12fbf7c8cd6d94c8cbed3b5b00d4650ddc294aa54ee74a33004c190588232f789f7ee9efd56248d38d71d81f86965340974c1b8f987e7e2ea57b1428fe7a9cbe0b59352aa4ec25629d761655d26236b8ad972725cdbdaab61abe543dce71f81170cd1bd64a6abb0e20b0dd1dd7a754577940ee018089622414724e0572b82db1df412991dc9d697ab97868196556deda78257b094e0395b51d2517762ad2ff2e43c2898f555dd4cf51ca7f2e49b7b75eeca72bae07a770009a6c22d2560bc999b0b7a68b0e3fe04535dad10866ac852c6dfc89cc258efef5cc5bce9e2e9590679c0aae729a7f2103c31fe681cf050fe1276bad6c770aceabd22294a31ff726e73b86b0969b9eb5138b967ad9507c8f3b4d9c9fa3bebee3695b8a0b7d76c59703087b7e0401020db068300c8bc9f0a543336bb23e0d2d2a827dc0a1500960b17030ca8cc139025b7ba143e28ecab9209a8c86913087f60509de95148d1c4d72705b81975d4b24789f65672a67efa80d3fa68698623de63374bd8da10ffde265e31ea93137917e01b10da90fc49331b27db2a240b436a96cfe8de593f41a4baaa52b42ccbee1f2474acb7f3c23a1a13198e0542128f8682821f96542bdf7305cf60bae9b85e02e0f400799399949c068c695861f7255e14a9ed899b2d9e4b183098278126db503bd58b29fae87f4ea35812b440b37c96f94b91533645f70aaf03544cbafa4d90fcb00286272505fc2691b050a7367d227a883d391c2737180660f17cc62f8ea757c2833ee41ef6d60986fa7c12d10a7f52ead1758b15d84ed70ac9b5ac67cb58c5dec707dda0e526d61992cf59edd4a60d58a174f54f2753203d4712c5c9019c60c7f6a3f0b781c01cf91b791fa2074e363a18d51a642060291f5db8d1c9121f5315da4d5a7947ed7cf56623967458cc5f7aa21126421af4bc133ce0ba83fdbeb219ab80eb2535e968fb9d0e8e71219e8aa46ff9b080f8d43b3b3ba085c3872631a84646062ade8e58a77ddea810dfbe86a78459dc85d9c7259d812a36f8a43198df1b0867f665030f305ec5580391aeee4021f9a54c22b1ce858f898536ffe27e054eb501fa02514dc1fd52e0cff832fda199c47194621338f81014634155b974f6c6b79d5f28f9267b77c7c08b093fabbd738ad16392dfa5446d5b21db47d275547364e62de3789bb920ddf9088ef144f4de929cf3e6c3701eeb140885b33037e836178bb3eed7f80951503322309cdad8d3b9381a133db29e8536f5ad7455b6e4ba852089940d7875268d13f7cc69ec1c9da5f72de35c1b896ace6e2146b7261cc307cd79082e795b731f2102bee9b9b6049559a015dd2149e70640484db8d1cd032c09308715b9ddcb5546ec589d633499d37aa9cd3190caea93c9f9e18065d0069afa4347a1294d4511887fcfa68a304cd079ed5713ef4edfea3e1c92771976e325bc410c8b528e926f6752176bc9dc2979b3e278e52322cae8c5a4b5338b03b32c3b8b7aa8302319632c4e17589cc95a0ca1cb991ccb549f22226b178a78b3d8fd41d9eaac56007057baeb2144bee737f09c1538aad7e8d330cd83eba146665959d5fa94b5d1c46ce0b30300b947c6c7096e119e180499ef87a5d14faeb308098061045b762166536a965a762f973d95bffcf7e0c526bb9753d118c59f37578d9078d92f6331f4d4e1f48ca6129988709aaf7af5f325503fea76cd1ca73aa2b25eab6c945efc829b1f7e1bb11665f5b5160dbed576ac87573dba591c177e2e29a3c63ce00053508894eda9fd3c624a791bd32464b734a9219265e93f035b39dfeb82c95fc8cac98abd1c7746e5a09cca4d1f21948cc6dadb696e03b8d59dc3b13bf05f9441301e9b72a7faaae6ee3d43a69ee912d4118e50ba7411b1f2ac44b5ef5dd6cc67ee9f514d866d0dfa9661b671975435ec73a1ea622d06f64adc53cf0b01c07331b2506aba7c2674b5391c1cdf87ca8ceda5f75f69df6f352d6dfbf100940020002c5351419b2cc3084e5a2017e53c49049f126296f85ecc91cf92765f6644f9176c26371bf2d0bbc7731fec03fdd0bb457288a89edbb166ca540bd0971b77ff9c6695fbca25d6ca868e3359fba065e68805339805b74c648daea8ec7a99e8ba7bda8a8396c572e1f923355c14b40b17006000035cbdea16a84c7486e20f4d692528e6348d94883cd9f82b8d38981d3049d3a8eb828f7346c74dd127f22de06d41db4192fed99fd5ee1a53243e55d90ba0b4a99ccc959bf87495717c561031f499e690f462b22df56834fcb27417ec40d8ea253a622e67758d7946aee100110df951de77913c83ca4a94999aaa1036401dff2ca78aba413f313c25c941fb201fd5477bc5468338f4007648551e4431609e4c00c44323b9a96af75fe509deaeb9bfb64d3e961485d5937c67da61060fbc2e9254a2953ecf3be6467695a763b3f64184c983b4ef518699ad1f1b5db1581195917acbdea5448aaf5231c803a9ed477ad413f5d941eccb69235f8c36180f14400ab48c086b0346807c4fb53a2f24137ea9ad5ddc3ee95fa111edb55eb5bdf6492756f5a5964825ec34a29fdbfdfd021fed1e524781df6c134f5b4924e9d95d4a970e8fb512f94389365a23a014379a26219e05c917ef92f6d2e0e3a3dc6c43c157d4c772e3c73aebba6725b7632c6231850f14b4f8fb42f16a47e4fc293d81bedb9d796bda8140477a5387b7104c520a577e65ed63b56e74ac8f95e3ee28cfc57000b21692c5eb0ee9fe3136d7cbacf43854658a51a5cb044c7f02f6ebafbe4441636fa706d77438802979a24462f999b37a22be4b9dfcef77b876ec0bb605c37c1252f32f2d39cec400d16542d383c86d3f42fe793d7c7c8ead8edc641530ab58839b7f7bead305206dd105cf7b8e3b50ad74bfb8ce45e556ba646e29a5d6e6ae8df5e12442682c22ca03cf75c911dcfb81c859f5d48baf24f19bd06bfbfa4da449aa814400ccb9bea1b741c3ae12b87ece240d955465c669eb2b95b5958a9927c8c848e26068dffb94e8813566a07e56a4315348cf8d140883e2e341b0ea37de83f9f2f5328b06e2aa2e73f1962c483a973992a1318d0f9acadb7d4c38dc35ac48f5af6427aaa35797b7e4897c558f0fc17ed5a172a78ae323568a3f51349bc40007c66b0257ad8a6c041ce094c6463806b605a5014c4daa1401092aa5039a2ed7e4c186c57f478c5d2916396a061caaf584d093c4b86a161746e17fd95a3662e555bf2829d8807dcb4953baa7e09f64919c560168675ecad275e9048d8e0e249e410648c7d29c08f7ec88c3d0ef5a098caaacc12749c35db4ba7a10c75e76f09853046055d8265dc583cd8f410cbcd9d6aa341c6726731c594204628f67d9f533e1530cfb727c4b3e24613de7ebdd372e7198ba5fa50ad29b58d15f84b714787892fda2c5f04938be98073b8d8df8a4d465c32c3ea7a0ce7cb9c2b795488194f3013ad3348f8aee6161b23489b151dd892804a57da1d7637c8496cc791304ad4f9f4b289d0e6b9741d164fcc8e0673f1126ab7d2b1744a274e2658dfe66639cd3e1587a4ac53dbccf6adbf580d90384a6add12a822313bf9db9f07d719d9d8716c954658c489def4de7eefc0de01104b69d624af6b6173f54150eefb69f01ba9d1e407ed3207e23bd4d4ecff0022a51e53f1844e6691b7ddc7af48b2f06604abcef6c264fab36b8477e3c178fd28f4315b15e58c76fce70c55cb05788e671729fd5df9e27ca517e591dce1489e32e2204ec4ece142e2ff1bd2ce57dbd7efb3399e62d81dfa36037cf0e76c4c50c5f4633476231d5947971d05eb3202680d8e0d9517c446dced11411bb8fa1d52ac0248fd678a0c6f3ec5e5e88ef95fbbe3aaab047e437542abf9be173a4a52f1ffd969bba608cce816fbba896df91f49adacb87ef4306af46c23f5153beebd4fab9cb715572e5b8a71fa7f29397e0534c81d7796d7a65e08245e9d596d1ce4bc55ecf0394edda74e2a227ca5f655bd55765148fb8c2c31cdc826c03c55bf83e7dee9b75d96859594d635607c6127736701917c6ae9325e53cc5792be1a38bb75454c34539bde22f5c99149734dfd22cd3784aa2459549308ecd4d6a739b831469d4d1b8ad1ad2e1e5308d547b0d5f6849449be372be1395bc8be8f696438d404541fc8c80a572bed17213919c31144009306e04d41704f227cc7db55d93b56a0cc899ed0b5166647c9a9f226de9f6a490c27373cae8e66daa4acbe1eabe1eca9215f1fc4b78929342c0c02621921b6c2a6bcc0e3c68b2c7dfdb970e192739a2d327624885e9906f18ae896700af6a972b6b0001cad591e07884e958b784181214c88162d89feb834c472b9e390a03d92fb6086b92ef653f5c3f9501ba5bb485fb1d5d5b7710c153bb80a6446c322291dd2f4b1c9ce4e6a10611800967427142673ba23fc962914f1944daa9cd72f936edfa0c40ba2ecc6ea3edf86eca5d7dbc855de779eb44f72a7bb62afb0e53749281a52b7c60e8753aada7c07c9523b50bbc4152e65190e6bebd577e244704a425ea702f015841cf9ad610a1ab79c397def4bb07c538fc7a4e22bb7109ed0c1087023e997cfb5e1ee7fb55966f4dc1477a2d3e8e6994155aaadd1667fb82015b0caee1b61343368178d8ca8f0996c826c9c5d2e8402613184d9e4805f770d68cc956bb3c8b65bf569a57be43c3a7df4861478031509df84366f0d28624d1c8fa71824094cbd065cab403f4f803ace59590914703ee55b0b57ad679720679b27b8caa1091eaf57ceb2ee5a0a6e7aba5b716f75fc1f73ba2c8d96298429d16a253c83729bd2d3d0fb597205c25c89e67793918093b22a8446a03eb44ac556f0b49565e912603d9c281b69e2d47a50bf6f3500fe590950d9b300597cf61d0500342df6fadab4f407bec84e74bb1aec20cbd689a965d747a065cb2ebded1b8443ce94c97fc7c09b8270e6d89f51dabfc58bfbfa670919343d889a2f4612bce149daf9e98e6a4a983d4c442ef302e4a784d9f00d202ff93840a0a58eea13ea455d8add2fbbb001302b77404b3326c8fe4525bbd9257d2073338faff38384b64cb0100081e28224c32796166726166cee9b7feeaa9716dda3478c93a75209dd1f197e4e168198362461d55746556df2ce7d83d3b81116b560c1ffa31e5874269c28d7be7d32734ce7b2222726aafd0ee188cd263a8943342cbb6bab1c69adb35e38287112f4a27d76089bb6a0816a91020a3380d58bf24b384cddd2dc1d6cd08b3f0731b546d7d66b874976c9eb61fcbdbabb26fc6c7b403b9d8890ca0a6ba24501f3325e0f47cd489f54e360005207d90a1c49d00131c830a4c690db0b763098d09252d34cd5d7080a8dd078599895562ba2d7329f194911dcbc876708f59abf238a7bc753c20beecac0cf32212699fcf78c96d81e4f2169518dbcb40bd56f199aebf0957aa46f0eb5d886f73caeb02cf39e19f9c096c2797d2137b8ea843e3e1d8632f43e990563e74fa234a2e766be0765d4e9f497b1b3b4e9ca61c288e47de75bf3d8a9f44cac59b9825e74e29b32ea05887b193f8724281ec09dab086d0eec71cfbc6719739c76563d2eae52eb2149f282faffc96b0041471ba78009c12d8c3176be5caed3629a3eea2c91bcd8cbf5eb36bf587762a63662cda714bc29deda27acf95801b627ce68a9969281c6317f6af42038f5722618fe88578622a02f5afd30968d782b92a80c1ce02aedc51d0659afb5f57438f5661a8a153cad351ecfd1193e3cd60f83462e4e9e9f26c6a837bbf07d9d0dfe8f4fb8f14e3560c6d1954ca7359cffabdab6fafd6a52d41acd06e7093096c3a57945bb6d3284764de19dd1f17586d635f4e6d556307cc706500775d9989cbba7a3b30a5e4096fc3bcbe7c67afaea6a1c81daa3ce2770582751c980ab0f6378ad01dfe7adfbe4a57d543827b3349b9831cf19dc7e582782525b73f78da847e4bafc8edb544bdb65755e0d050e595e8a093ff6bd83b760a6fb698ce7ae6312606b484fe3e101030832206f03ed10c7632b5b88f5f70ef0ccb313246b1b3be8ce65d958648e6c2291ef6b42b556176bf9f34404aad6362fc25334af29cfb04a108550798eedc4d9a3908d5f382eef3e8196b6bee4c8205fba2bcbec54be935f81ef681ad05b5901c028b5fa5e87df4e57d40908a230b028b6338ad4e3dd87e4d7dbfa9f2bf684d4008e6a97b387580ab7089f9c965304eab718e7941b2a133bf492f5cebcf7ac88fee71c3ee56513f59fa823aae8e35794750ee3e281c31ba17611e725622a5350e8fff86c2184db296ed49444404de469c247d4b659c693861059e43eb84343e2f9dc73cf295f77772fcfc64222c7e268045556b8dcf25f853937e42a0bae9d6fb4c132efff52d58738f1f18b8ad064ac6c178ee519759c45451591a7c7e1d159120eca69619211fb9ed217cc11894a052b1809716a45083c1a0413bc303e755a591db05e101bc026be2f8072e50604e55862408307c97bfef339e92cd6488b9822717ba42d86173fe592a337672fd3503d6a7d2745f6e3e25d7683e6ed4939b5675ee77186e8140e1b09d9f2433bf9630a599c5e6c6be8f6d26446d8f7ce59313dbce2465850acbbec3d189222f5e6910c883ad24aa8cf9a0f21279e7423a331818afadf2707580adfaa60d0e8d2dc18d537c7050ef811026c57ca818721306043a2af416157c55c1cbb50da106796a9ccbcf2f2a33e9e53c1fba9999d8db2f3967828e6ff680096764b56a4a7559ddeb03f8d4902478b219dcc0dcf84289e19b9a7c922e6112839ec83e87a2bc0e5b1e6e27691704385a8c00d71828ca44b29287a8e025cf9123c995c8c7be35f89c42ee1f647c4a1271d1fe3417434142c3958c704babf4d41934fbe9c5110c0c311c0cb77e3c09aefc005fc3617c78ffd2f7c9f4c4e6062d7d7c05e3d8141eea634650ce7c3f46d992279fe2220d7990cbfa13842d6f05499823bf318340f8871455590a5749e43b765f168ffe28b0451672da8285935fbfac22682aafab17dabd126a393752139cf2e387d086169fd0d08defe365de67105dc4807dfded82e3634ad3164725329d23093d1774181fe4f7af2eb66151f7b1893c2deb2f41e62543d5b84d2a636b28d8fe8cae6fd02a104e09552363113789f82041c9214630d020c229a81b8db860917dfa9580cc44d705988925dab605a44b59d1a9df7516e2175b02d11dfd0a726237814693ecb4963921134c8928e418b3b11fcb289bf3a95a150d735533bdebbcde38e743d23da7b73fe44ab5fa2ff5dccfc16b25f56d8fef450bea1cf9661cfe73f9fd2d2a008aad879c1063cbcf0ab83ccf1c85a01235aa16cbeb0c13ea9208766796d90cee422d567668865c453047b82fdbfc1d779d88351f33bce6711ea29d3c1537bfb7061246e8470c2b298503ed5282633557200159e2e5be842eb03b578b99a73f8d97f1f3224efa000f5c8625c4fb1a37d5b20d521017b3888421fd909519278ea31fcc9a96a3077d8b046280f6050326d1d7fa6008274b00b89421b24e42b21aa932426a8ee0c5820d9da2a91274add6d7d12d7881c3a077e814adcca57639a14e543f7a00bd6d2802596962bffa5976569fb151831e93ddfa2eb1db0ce6c250b35f26f36e4d249439fcef1c071060b599f81374bdc49903f67bc3304f2527511f2fae334411a5ed8f04a7b3e35118a7d8b3a5c458c450498de0d940b091f5f17b8ef3d8263ebb1e527edda29a4ee2d413671bd18499c137b7e3672f9616276598a7940115dc90a890a4202cb153b04070815600f449300be2aa4c60b5b64c7288a55e89ee883ba3a7466bd54c660ffcddaf84df48252975c6ecd26a2a894ff3c6d607cd4dc57afaf633284acd466a1d7a8ac7a715f1c8286a6314325cd3a126b5eccc8882129902309755c646309ec7a14067c1dc655fff95e0e6f4a0a0731d3da5a302f0452674743bc051a2b0fcda37ae28630ecef67901337ead85c817f54a156474196e5df0d1e724f4a3310f7b925225811232774b0660b8eb5a7e67eaa80b1a53109b80e2ae480470eaa838f29923c56f65341de0dc303221f54f37e257c41cb766b5cc2bf9bc1d1cd4f844a3ea779e65c6dea1a35fd1a858f753b0720807e127ae5b6fc8f4be2e6ad18fda238b83a2acca041d8c8629e988d8a67160f798416d6b69d97c9aa3087f546a9e45fc1506859681a7e1e4a8a0a1782527d11a51b4d12f005641c40f1175b553a70167f69ab675752f0a366501de62e45c6ff530367a17f4770d3b87fe14076cf30564e9f9b340c9ab2ca40724f1dccff35ccd2bd4606c2667cccf09d010a3f69d960082828261b00811bae2885a35c0abe6e0e349bd07a15d35c994c5cad9edc2339098566ad5973b4dfeb075bba25c03d65009e80b9c1dd9d8b41b50b28e9847a288801df558909c66e005b1ba40d303e8e103f62d98803a09454bea3041dc0153fa116fafc89e78dd79c4bb4cddd776b4fff545b0ec1af1fdda6a7224c1208615f4fbf54794be7dec7c7718c074eddf2f6e88685d2717b9b3c5e87bcea57b5d8408c9da3ab3284140dbaa8c4facacd2c389f42eee205753372498fe74b41e17189e60d5fc78a4c26fe82811ebbad139d0afdbca2dfd771055dea3526a4d232cf3e5ce2e12df8136a97516416b3b48070ad1555bded3d9e96cde26c8eb5475092932dddd1b85756d005092d77b67d7d66d285e1de817924e5114e6b90fd1ebb8c77c4e9148b486533994026612bdc8d5f07c0d6e22084d49167be76240b0f279639c3c5c47980a32657ecedaf781b1c970d2a70d87bc5225ba0075a9ef6f801c3488af88e272e2faf2dd452a1b67a89f68de9ea5e672ecc69de8325f9d607e1ab7aeef5b6c521b5bc9b8e0da84722a5303447cbc6bc0a1a8f4295549ddd4b9ede107c9b8378de2cd7f7f197216ad5a85475061496cc4a9e3997edcf591916ef45366a2324cf36a7110a79221aa082233276d0e424321b7b8516e60afa43c3c43af1f252ca2cda680afcd95caa342a5ca72428b2800d43f8a7366531d211d1214f5c8aaac4402eb84e348065726f969c304fae457bb5d712ce67c998f7c6ce72685ae9801d748e156ba1cdf9a943ff15a32098fc3728dccb8c7c466adfa763fe4f6bc0c2a7e77aac687ae785f1a258d14c14d8525a6a2ae9a9f57b66b59aaceb65491c0fca6692394fb9b031810cc3de9644587a81d1135bbf67c9415f65e1c1f363ef6b61d42cf75e330c3c5831fd240add30c6e1e767e695d0bc46c479a13750c1526b7ee69c6ebcd2e25be88e1e1834b7bfc3d8189ea3a9da3f9e946625a12c3172a2afb50b75db108c827bd01ca09b91fa41bfa0a8227c00e3099813c716d5ebdf06baccb8299e3b7a8908e6126bf712f7294811e01cd5ffc50865bee8f2e944fb649beb20a719ecca1961c7b102d3aad665161324d9be23d0f0511922dac696040f3ea0af8ae04a2b74d108e7e1f48dff8140108648b8feb5b84e51798216368af01d641b84e12c25de93a7d397f8f44ce59a9f80565c1d94303375bbdd515007a3b3d36207bf96c102a5474ac6df231c8e8021d441937d93d593432ef69f05467c11e697be74350f87b43431a04d749edd1fef4504f4ec517e29ab1eb56452df6b7107cb63f5247f9af3adf6f59aa265862f1fc84aec09c310792fccb029b3539a914f59ca457cd6d9a26c59582d2ea70ed38436b631ca07ef637fd1e4b6a5c5199d10f65c1dee28e3753a53640b3d32bbb12848a56f8a1bf615335fe9635b5aa5fb996006d097d68343f64476c7fa59b56ae0459a0e5802023d6ffdc014f7e50ad8e812f9021e104cd01bfc44bd61a3800ab3576ed52b41149e5604b7c57616d9c06251555440e844388f7f3ba0e25d80fa555cbefcfd91373a6c5e0a348b14d78ad78ff9de5b84ae5a16234ec589bae8b889873a4d9cbe75de56c6cd3f2fb7d0ffdc3e9ec985396deb0eba5926ed1a4169578b566c0004e6039ba9f24c415b9c2e60bfb913c7327f9d4ea1420e767ffed41516dc544bb4e80d2077c391849810d7a6646441096347128e600800a0b182ea9253cf640d42e901ca7523e28e4f51bb5d11803088a4799a34a5f83e037f628d6aa34db1ffa6cc36e21541b6586999637b4b429dfce7e643d91efb354fbe84ec34369e1e72d13aff1dd752b13afd87130412570780652b8fc943aad2a04e7eb2283c84f1f2e01727b12887b3e4582074dda4c31c3736086befcadf04ccc612911c8239e36d5d4d452cdfce02e486df79350bc0f084f4c5337bc7030c5780cc4106a32d73b9cfdf503afb05d5e2dbe0c3ca8a20b218c13752800a02fbb23721954ef5c5fcd1b8a7526912f650a34c47ade24e5fc20b7a6fc508092c73ec2698e30c7b2968d1a1e7f2525e17778c0b35fed7771cab23c3ed22ba44aa427fa7a186bd8c87e10203d9ad6e0457206c1fc51e95e475e5ed7b1cdfcb2e24462c374df534e3a686732b65beeb63ab426b9e9b30a480ae99c89e68a739f3c20a94c3dc2ce2bf3368c14a5b247111f670b8a8a26a4014b6b22a092fc5dd7d82dd0175c098ad2956dc971345a6c03373dc2d0ef2f7436f6240d8f91b63ab4c4c48004cff6623d47cdde7ebb197290fad57ccfaaa85d79a4230123af835fa608c9df678e834cf118bbea7b35d53c4c95b86b5a4d15af44153ff08262ac614b697e63476826bcc4e84620764fe807803f08d2dd11f52d566a58abd0bab5d826a1a30f785a01d2d688ee49a0ef8ba0d030ed161546e4409460793c2a123889af6f5515f8e5a7d8a3de87e2cdf6117d6f4a9a519ad5827745dc499c6ff111d9ef5b17a283ee9b42448786baf522720df3c6b64fcaae223d3b7d0ec79213dfac7a9f7a905c2db14bba91dbf5db6b60e5da8168220b12b54af4adb934d16f95c662034a57dbba985114d1f642e5b8eac9767ac67e4032c219019b44fe796efac8fc96ce163cb5c15b082ea397508a5f50538d8dbd4917f448565ea71402604a970728865f659d961a86704e013acd850895e63b5a7a60654ed6e426e8317dc34082c82e4924dad133847e07415129267124fc82586aac787c86397f8d568590dc7081a883d1e7c79d667a7fa60cf4c2b0a0bf379c69a1a32211c54d3b87b9e2433e4e11ab0a27eca206ae664fadc5f6e802e78ca4cc0b9618f18e6bf36bd50df1d72fadc7d6666e4db504770871ddc424ed206a60db74c31b113ab1e2701f3040fcfb32d11d9133317c9d9f85ae7eae7089faebd928945ee34f7cbbb41a182f4db6241c111e1d95d7f94c41ce900aaa2c21ad0c6be312fec5e55d8736e6fdc423a0e8d6edfb23337d91d43d74e4dd4b42e7a5508377f573afa1cdaf409e441ab39988edaeb16889b039de8a81e7b52113111f6dc63e512ed40b0357099f191e5ecdfdba843c44dec1e946c3252426fe68fbdb9eb73c1f2e4afce9bf982906670991dbbc13cad08daad5ab1f81e6a12a15a8aba5d0a4fcc8fcdcccd7980e2b92b758f0cd9043c933950f04c6136aee6b7fa6eda0cb6bbb40ee62293c3346f6f8e7941eeb105638e095041dd8a97649595d1268800cb24553f390785b58d1ec915f09f2d2553c53af65f8200d1b4d008eb9c18955818400807bc59bc84ea05a373621cf6d1861a7406a889f321762048dd331595381273ad23f370de65147ec93c439d89b961744071f8d22a93715240af40e531a9d92933569495ddaf41f401a051c611b20f4267f2740dc6ff5fa099abcad7513263f879253ae4ba55b14f58a51182eb4a7c6a1ed7d1474dcdcbc1aefe7e4e9798d8efbf7323398fc8041e0ae4c846942ec13a9bcdc0a260fc4b92206223e6e256540d4c80818eced349db868a78389e27a3018c8ff082ea22ad90c0e9ed58037e3ce10bb58a8958def6d813e02ca57c3a68f9abb960f57c04d3b2822b0e2857287bc649756517e937cf13db08d978197f379d7f4252a2c91f66934d99b8df1712c053a308e7f3526c4f132c0a0455284911f5cf2647ec7617053830fdb25fd6028d7f497c5c04c0652945adc884ae11f9baa685fec1cd2e1864f64b8931aa5cc58b27e6860e3543e0ee3b413a1c86347281f0f904213e441d460161d36584dbf812c4e048d4021294a3d8c5642dfcf0ba497a2a035902fa5d70cae4b15ce80a0c383a4f15e78cc643dac7501af52f97a926ecc7cc131c70ccf0ad8e6192dcd0bed8cc918bea04648ea24a48e546380490167ff0137f49e42947b2ecba6480db6b287e8789abcb6579ffe4f59f79cc684c66ce1b9cdf198cc10f6c844cdea81c0319e0ed6b751ce07213d4cda487e29047259d079dd3a4601430b7c0bcd0a44689fb879e9e6024ce8e95a6db5cdc82a02552fbfa00e2d5270bd983ac6ebdd58922e3a844e4e841ee3194a0d31c77dd0ca3aa84dc18de973a580a9d7d2662434d7aa3c24512847eeb71b24275004750891181e7292b3a8b03946f8eb2ef642857bd2cadc6182cb95f6848d3d82a2a8a22a6a8d045337281798b8827e8812962e0f57ef0b2c70bb9e6350642dc8c1396049bea1fc37117773474543276ba7fd3ccdb50f219a0b9ccda9d9c63aec02ce0f7af683cf89fc88f239b2aefe9065d47e07923d3096f805c9acdb196b2a389c7924aac24646d44d813a7d86688ee99ad9eaff846d381416ef2732345f5d1045c2feb286d33766bca7491504d928ea3ab12141ef9a7ad5d65f7cd0297b61edc67fafe17e3a2050ea96f29f4ed4cfec813a02f4b13213f8f0c679516733168663327f2e28cc95754e99df633836e1a03febf074e5a09d1f5688fca80a1233abb9b473021bbc01cc4dd8c96b88b4f32828b6a00c8e89cecdc365f3149ca2b0c5ce9a28beebd49050f1ca733b763ff2518379fbde4c61056b934b802344a5436bd20a0f11c6d09237122991301eff02d5905bbae738f27b330eb9de65d05448e6398f23c6c9d6e11d88cb465c57f0ce9185944ad9dd6360cdb5c1dc26fdcf1e7c9285ea62f4341025d7633aaf3318021ac322f70189223b509c9080fe3d42a0a236950d7ea98e618dadd08907a02f841e47b31e5166523008d3e4e26d090a5cc07805c60a1f84e2d95af70a7ed50fd2fad56080634103cac7d55bd34a7495ef848e80f54a5dee0a8c9f6017637174eb251eac7a2872f262d530f113922c8bacc81935fe3e79c999a7f0a26b569afbf4c635ee6eee524d19010e7a8063f4d5eafbc71d8280f308b125caa29ad6a702ca624500b202d780a046a3ad0ee5bbfc20297cfa498b11cdacfd6946cf8111b0f4f8c733a7e0d89e89ad145989a8eca2cd9b25bc3d0b7f837cdeeefd231d79c8148e642288d98051686bf7c6efa53491c0e837e0359e7be25ec283014c88448a6ea63011e336d906657c68955656638920e6688a3251f3990cf5371a82bd46ab77b3716b0ddad56b7c69867a1d60f438b1c65088ce3363bfe325b6ad69917955b634a6e88ec62984cc82e25dd12dc27fa1b0f8d13121eaa4798dc0e279d8b5f33579789f6528b7d06fa03180b73d3f838eb67baac12d1e19597760009aaacb9728bb5662b867400b1a8bec8d1538f9a411c3304a8ddb276ed2549875115e7863d13487b77e74411e82dcb759908e1fb43b63e9a5dac7a47a5e2bc2e1c61c926285dbc554e42e8b4525855e9d0477a244bc914b0dfe44cb681d3fcb16ec2a7582b226455c8a25205edd730e8db8fbf970f634dd46c85768891d4366713898e284e2e926546cb2e08c102a11a0d0640101913301b031ede22a5804030ba37f0d4a5f8e8dae1fbe6ad52be06a714f2cc69db53834866634bf0e2dc379f14042730f8803bb105c779133c9327e597095b61b33670db84aa9a460bfc4193476c23c124b4ac013f66c1a9e954a4d7dbf0c8bc93a3ccc33ffb9ce6d98cc3407a2aab19fbffec214771ffb130385b33ea1384305a03ebafcbc84820e2118b36587131121336b7668ab7a384b04e63812f870027a0b5b10205c0fa22c565a0c58c959c69841c0976a61f4ac84e7ab8ccd03d98030a871bfa4468a6b0380f60292b59a6494a759c400aa3477b78b4cd8b194455aa30e05a50d6c5b8ff94e6f96d3316ef7d98d46ed7c6397f2019a84abc92b44db50c2133816a3c6188b75a935465a826790b832e0233090e2429500f072d7ec5f51e913ff1d49c61ce4211c2db45efc2f00f8d1bf62ead7cad6b4e208bd6dc02567a58314b11045c98fba393f3af31dfb362ea7182f67c8cbd647ce82f81b6fd688f57ce08bed7b19ce7442f3bbbe39779d4f737443b65ce1616ef34c1e048c0878fb83bc1fbe8d919f052ba9bf38db7185339448109f8c0481d88474e90568d04ec4bc017119365a16ef30ba913d8cdde3e9dad461d77f4e3c121018b0c4de2969a6ed3aac89b2a7539bf22995d845077b559562f9e41367e99ea0448d7d02d20bc68a0568761c30acd7e81ff6625bc3961fcd7e2e8233da21f5c416e1f463768a68c1712c3468951dd957e49fc57e21110bcdb6abe28f574755e2e45a7ed4e45213f389de3453e56e2f5ec350650cfc316830d30da9059b398c8671c51e24a5c690ea447b7749df8723dad3bf5ae423a26846d348fa23885f2571f5634ddc6628f97417e0c3d6637a35acd384e667cffca99339da60aa2da8497c7fb2689cdbdfc50fef2e43eb2e1c8e65095ba817b0236fa5a60e629d6185bcfd5e15c543b82929d5b16eb3e7f1c043c54cf068635d1b1384a1a0c9f89ed62a550605979e024531d247d0c01361e2395a8365171a82c9fc807aed81052fdb9c96d609518d520cd77531f15d0aa1ae54214b1b4b0e0adf288cd71d92078e80157f3f238c2af50cecea8ce3c3f77ec28405030effa4362d53dd6be1235a57170ba4b482955638c7842e6928acf7864ecb560b5e4675d35ebda307bbab96b339beceb585be61278da9041b5d223d85c90732b147539c2d8fd513bd256dd9aa0bdb2f4660559e15911bce2fc794fde02f36db982e4960f9890ad7709448f766187d5c6467a5bef10dab04c878fb0520365fe4570d54bb5f27ea1052057e2b42836ebe91dbd9790914550e591a44747440aa1a3691582387239fea6a4354558250cc72d0ad086d00e6a00bc9ad6dd0315233459690204dbfcd02aa5c719fb52f8a82b021adddc85ade15f8ada06199d6e167c16ad19530d225f092956b8a37618e9080faf121601b26da8fd0610c8703589b52ea30e07e04c2e380ca8f61660dce5a12f7f95db5f953fde022268bb5dc08f03571f9e54ad1963b229f68958f9525a8ae91da64eb93008a463478c5613bcd6e23e53f9b81b3b85ee31b37e73bb67cad35dcd453ab8a33636f3df8fa1b5a19ff7e918c17614e300d190a258ba5744ebd7e1e3a4adbe81949815c2cb752e215b2b6c066930ca08e23d4a0bb32d975fdc6b31cb1b24a8f780e340901f8d6dbba351f5175ed3af6c3e84743f55bd3b128f633bbd74ca3c79ef692e8081b74176ec10bf232464b24a8b99c21d499423c3108f6a07e2d885478eae353560aefdcad137a58d2deac1956d4f9ca66217664079c79ec9e89ada1ead2b8ed118d01efc473d87e0ec9eb3745abd38821dbed42cf6e67132b1029641fa434094c761bc015e1d0e80f027dd82f734afbd3c6531bdd521bb9bdf2c6388c10806571b918de4fccf9b8d488a8eccebdc0c31b43e81e7d429a5d58edeb3575140bf0bca37bf20582914a5536c8f470f38ee7b329c634cf2596d44a31515777be74b3357e84e298060ccb76e47b0066f898024a2cefe44bd425f761775164af22b2bba96882375b2aaabfc9df49dadc042aac408ceb3cd5582f7c17094bf380c425d6907f88987071e3c421e411a6e50e48c54415a99f6ef67ba08179e7b218f53eb89428de9582cf7de2fd3dcd61b41b06278a017d4d043a49ae0ac2eb23e33c24f343323ae639590a220c58ffa0216d0690c5e359e20f68a1c8d2bad6016cfd15b5fa28e9ab2561ab1e29806549f3f8a7fb0ad8bb600d6f52a192e9073f59d6c19bb022515db6540a412cbf0958d43dc5ba1e3e787479e03b7697b703e719c3987347156ad1581ece498dd9803bb9bf7704cf53b860e8d8c936a8c98962e090d270933ea40542104c29900d9d8bd017009c494ac125a34f9ae5188da0158e09a1abca2e2d067d1dd7570a729197015ffd42b4cf596c4a9432fec1c4a903dd450a36c5dc22f6b111bbd617a18101d757d56abe482acbfa272b5baab0703764cfb8b9720d626ef54fea9e03b1ba8ec231bdf35253ab3b1d197bb55ff1c2a7b67723f0587c25057a726d01cb99f39c588d6e02054caa794c241682200fc62782819dfb6ee4fcb4613069e4db64dea9e1332986184a792982cdf1b0e7b03bb9cfd8c331ccfc4620a09ff7f1ba7b7a7bbabc1cb6df15cb44940c30ece787287b416e6626a37d1f8a0daff40ad9c78ff515962f98b34f155741dc2be5d0174e476f63cce8a1f1a29a24ce0a4ca2784e3792d2140d643efabd9cebeb973a4b8f9d65c10411a9d8add56441132ae92c25f789250a07b2d48847872cd490643a4ece52203faea5afb826ae59a570a7e7543af34d98cb776a1d2f5ac31bacfc344a7e83ddc8308e0343df0dab7a27a80108f988955269a7ca3034ef90faf198f653b4f956e66696c09ce390d34fb47b117b3e899db5c45223cdf1f9ce0bcd157bc32d5aed0296bac8ae14dc207a7a89f440885e50fb1e3200d7024805642692b6d184e7aa6edcb6c8db24df4e6e05e26916c34c7a703853d2c6f83e26cfd9db4c46e447f97c0a74271b3667c50aafae7fe4b318e1591428d71c9270db2a987bd35aefdcbb2178258ec436665651771110c76913e2de4bea406b93003b524c9ba3ca2dd8b0a5d4bce5099b0f34e90d6b182b4dd9c8a3b41c214aa2ff1821498fee641c474470ce961975127febe1d72b16ae572ba207954e8f2031d296e0347ea34659d397b96fbf2856dfd16444c44a8b3bd598bc7f922d3af9204ef59181c053443ef74305996c9323ceda464b05ea41cc2c0ee08f2942e4d9bfcef7c7e25109e43345cc96a281dd1c8e22d1538322093b41bcd51132a7be38698990a82a10e4d158ef2dac200ddaa622b6c498e097c4b959bea1705575be6bc5f9ab6264cd7e12d89904f16813f9fc6e22d166906bb19e5ee487d00b4c90f821e4859f1e765b8c4b0eb41fa488e8dc91f2cc368c63c7d77937081bdb21cde0ea2591ca640cb7c683f24db42f2f2d97d64ef713528a7a89282511ff8e40747069100dabcecf1022fd2bc705caf8f983e42efc3c38f25576d3afecaeb21a11a4352230625e1049cddbb20a175360d24f759beb3ca5defc136f9c35c8277b3d32a5be87eae48580927534abbb6f7fd9a5e87935092540c8ca3531f4427b0babec6baa3ae6d80ab132a07726e0ddeffb20aff377d79e15d9e2574fc47edd62ffa6fea69b9800b3695e98916c0e4749276dc4ada95333b9ffec26fb7e05c42a47ba34edaa01906e9f47663051c35ebce418802094ac951bb8967a6ebdf0788ff9babca001835f0a9b7a5f72f90ce81774173c46b9a86548bb34cdd230f964ae8d4249928bd00e52b7c03600566b759255cd76686be7eb4413e9fb1b24c224002eb9c68a7831589ad0add9e7b4a60d323222ee25c7bbc08c6f4da1d7af94eeb8a5c74fd27b25d38df58dbf30d0f5925d23fa530861397e83cf298c2a708cc6b1dc3ba4960869541e942782fa5db79736a78912dc3fe5598960dc03b84c3b2b2632dd0d94c764a02648e2515f2824515ee78a9e421481cccfabeb1f04926ed5a1703f0602f573f88b0a65de47258c433a1332f04fb51ad00cc3ae944afc5abd9df23ce3ca35350bd3e529b71bf06219ca1c470a0b640eab1b1674ec28e43b3b61310322cae4d07483bf7ae020f5a7fbc6d582bf9a2c08ac8ca06cc8eac3169d3b4563aa264697dc0e56cb451f71143dcc66459586b940b6fc579e891d4bdffe5bd5616f87966e449d25fd245c34df8ce1fdec938d7f44e1e74dab9a30ed2db7d33dcaa4dc9ef06dfbbc2c9f32bbdaab3e7e638efab95ab0ae8f0e499a36a08f6f4f3af0affe793c2a1a79388c4f57fc883d128be9d156034d0eb595930fdd6e969e1f3846c860412135d972b535c63dd4bb727fd0719df2841cbff29c3b41cf101e49b382b49f96a42653d3b494ffd8e98b333c850040475830b433354b4f93ed664cd1dd283713487bbbbee61d0856327e3e886fba638f8b162ba09d7298f91505987caf8ca3a281e67a560e0ca6eae9a83c837fb0cb6d128942148f564356291f588146af4aa35788433701d8036c0d840698252e37720fc6311b11bf13066bae7bb01050b8831a2966edfa636d58290c3baf85d2733fc0663846b730e7a6b43596c6866be044d5033aec0df9fc7d395919b698bfda821c77a695d9895b1060ce6f4977d7aab90255265308bee6615624f27be83e4aba50feb27da64bc91a247faf50b7adc728023d7fff051127c1a620e705008cc21420d7a5e1529eedce48e766696a38367f3412fb5d89faf9156606c45f95c439fbbdd093d44be572a18333e3695a22b890f605de4713d13117e45d3a9edd77ef1d763f703b1ad62ce1a363ecbb057064cc3b737db6eca489d322ececc4e79f0c1ceaf9b12cd19ef7310889a869c3cf44825dc544eac354168426e729dcb4df01e85ce5d4e0e0c778681dc8a268de8faeb42319b99bc678128e38c21ad219eb63c81f028ba16a6115f43307e0215174ea494aa587fd1a2e00641b8c36e4cbc72cbb459b1f9fcde2d938798b13c32f5b78db219c5f4f87b54e87dbf1584007a98c411c7ad64f1f744988254ba933d5bf8cad77a5751637a5f5db165fc1033ab0e947e7c5ac77df87256293326f60942746db8dc3d54d3d3b152b8c1197e041b30ef5c7c927821b2216b5f7b3f3f6eb7a2cf5ea578c69eabcce5b40eb93af6c7c06fb7993fa65e839ac3df705116b2e6f22a52f5bfd840e7a3aa755d28c1cd29658dc528c1308ac6fce7d31c8f7a13b73b9d4dc7f2f8916bdd094f0edb5fe70e1b72998a43be98a6a22eff0fdce0da8e919cde4a6820922a22f7d582af3b8d942c4af2f40ce4d16972d17ba0eba4fec01896c12667f58cfd6ad859992341b5360785a5125c5a0f45242fa5755aab555f9cf371ebfaa477188893ad2f67ab4040a6430cebdd996dfee85557a922788c3da2b2a12b91ffeeb135a03aca768844ffe628170c75e874b80c903c5871de2171cd759c131c4752929ee815d3a3b1f552702aa0edf61513db86403fdf22a4b0989ccd6bee5e7879ad24bf3eb60f4d2f1e2ad161b273890f244372de5295ef60bed3e4385fe20bd6412c8ba0b64222426e7b4e3798ddc85fd65b931e0c98ce715b8facde19abc6f00cef5b54727058bfb10fe50e8589795409726044a33b7f52e2e90a014849d5f63b4ac674e2689980f79babff5e0aa305e11a6257f5fa13ff52438127646cbe3e2525bdccfbad81922bb9bb67862164b6216f4071330301aa37be7e0219b4065b81ed053b9f2b2d884db89d90ab7d9fc12f0dc87abdba4e1ab06f860dcd4227e64bcd80a208444a05bc3d1c2821acae7438b60d1ada0a4bc51a206af9f29be19304e2b35ef4f41522846eddd995252380b88c77ff5795fc085fb00a8622e545525a73dc8065c5144de6042b9a0fba01b3c933374822d86e713dc55185daf40c9297a52841b3280b1395355bde3ba65aead7f280b015b74d3d378a696688f5a1bfc192bd657f041bc47b6d425519de05343f646900fce68ee4cde1856e334380b04e9e00ab24c999f68aea75d837a05ec57565f2b47cd6908e265222e8068cfdc587546852f62a71df2b131df6703e0e0c1e6e0fa64bc69045df0144dfeb7b4d3cf0d7b8cea0636c8b4e12121695c80eb52b41e79bd89fa0839d4bdbec70faf839c04b55583ea77ad5f62aba8b320b44ff25b70c5a674ddff90d2f53df1771f1c53440a29346bd14325bf84414bb583a57dc16434443ea771d6238618364453a3b98ab344bf3be1715234adfb8f000631d50b5ce22c1e0f983167091105fee22cdd6dffe0625d4973a38e5b1cad86a0eb2cd99ee9efebe6801d81ef727623ff64d5c39fdbb669d26bdb049a98ae1ef43d882b17d07de260d49c6a06b36462233c588a9e00d1f7504975f0f7660a24e700cdc673528ffb4a5412417508cffd66d8b7a949388b6a4d0e24272e0a255e3f877c64797463877d579480e66e06202e418612cd94c7abc4516c032a9add2ed5a03163d45010db1020d4dec6617ab626d7a3a792842cad334c9cd3a6c73c5611b1eca1a21f2bce5554ffd269320145c8bcc5e703ab91571cd93249fd4c44bfa089c2989468f6b84bb4f56c83f60bc5b5dc06511dd4b1298f34be78c0fb41d3d36a2ca048ca24bc394635c4fd8ea9f55e618ecabde2e6139c609fec95b6727a2ba756be8ce42bf1abe369db0dd6b42132c20f9f88f7c5ccba45b2260c2136000f0295b28a873752ebc493d32cfa1b9844c06bdd7e06bc6f392d02aff4870e150998f29c48e209a6ba49827ed324d298e71f125a0674e3ee761e8b5aa207cf4bcf1c847a12b8814a6ba06d923a2b6a2cbfc26f324d302b2cb2c53adbe826b23c5b2c4904df59585d59d5f727270e6146369fd36ec46cacb3482b0ce1e72f0da2f4f1c998c9c78c2ad22790bb41256a2cb2bfa4ee8d3ded70d4e7b30bb0e4028be67c4cef0bc9a9aaa8c8f35dd2407bdf160ac1570b9ee96514ab45095ea1e6d10e12492c49e18a2ab1e0db3f4c55ad2f865f13590cd0b51ac3f391c1ea5e2877f7c3250368019c68077d75b82534765f260abce682eb443957c4167107704ec70ae1fa0ebb0f469ee2192ea4e7c5d0574147ff623fa3d1462d31d34c3b949fa11ab174b90de5dbb6f48dcca01e2be777cd00f48fb13453e8f1347f1899e4b1c3bb3e9ba8ff465c514635e5527bd9432a4af5c5b0298882c3f45321e74288f0228c2f33ebdc7c69f9e04fe8e3156640b244591f099a734e27ca5f315c75c3d67ecc11e6e1d3a32b891c6eb1442ef587c65c0bdc1ff738132894ca1dfe344ffe5527bd2cdfa7f5ce75cb01d2ea38f98b83aa7bd15ca8b51eed7af2e7515e9c3db87871a5298b0e9c20da1ce47e7fe3b4c464358d1779493a965ac3df2796c93778a399c7d0ae6b9c178a9c84fd28d35844a19829b1da28534728cc83b1413a50f5c8b988d8b91d66945de69ad2c9d3ac623108d60a92195a65a0aa71364ae6819a60899bc5bbef6f0ab10e2f261e221f7496015c5ff543beebbe937e60eab6efd5bb138ffad997125203ebaab03eca868db30a499c758cb4fa05bdef92457f3ceaef3702505f7c4df9aa84ba7286c8924f8ffd805b604123427cdf5df0c9e6aef110490e1a627c349a4f8053b8b176fcaea569d6ad37e9ce8d126ebe4c2d47e80f5f51fb36ed6c7c5361d824be31d3c8b4ceb8dc5817cc7678dfa99c585b1fb2f3766e8a5ed04a7c2e3d9638ef22eb8283380d8d690f9e3e2dd9a2d7faa7b925ee94e4acc35f630aa3582ec2f5f3a6b98fbca670e63e8be5f1d15a438d7145ac2359d1a13cd59660fd5c0b74cb820cf20903fb1f8c9da44ddc3e4799c18ff870aef9906a19f8f564e42f8cd768e18d9255adb83a093810351d7f3fb12706d7e933ccc7885b4378f4513544eef1ab5ab60d7c049204ae6bd8e95c64e1a002017bcf33b626a838e6c419b504ed9435d8c24e5c4dbe8d83960807a47e8e58fa272378721836732c933f09c836486d53f89bf3ba457308ddbb77031060abf9690fbeb86f5ead7a08e7652b000aba41567da895b2a1fddbd13e6d00ce4a87f88cf7138a2f138678dccd150ffc950560f50770123daedc70f1c126b4b63fba08292a902959cbd00d96503bef3f12ed2617e9b10bdb007b58b43700862aa62fb4d721e480ac462c44bf9ed4ccc766bbf5034f93e0f191fd704b3fdcd1016a083d60a5e4de6fcbad9d89e26e05ab5d76ec1ca1e0a9b15b109bf998afd60fe0499aea23f8a4a13156776057c28bafc05f7269969fd4164c4e5010b113fd860a1b04d357a7b3f35ed7835ef9b2c3b2d8744727fdf68cd984f374b307455e0f69698b73390d8c3b2cb78a5d5f2c7fd715bcc5d3f040ab89a76fa83656f770575266f2c585d0520d27ad084bf9f95e182d338634fc4a13e9c5b5e6dcec9cfaf038a5defaf0516640f3b8a4327ef53dbcd7e7477be1cc21de3d7e05914092ddf234c45f4b2aead79871a1fffb60efdd07c8707ac203fcf7dd2635f3c4e1071316befc923287fcf4437697fbf4ad2e8b33d84b6d485623872635d25edf8fb0d74dd07101b9d355bd45e3286212ca4c531f83f2ccdeb344efeb2481045282a8942d4251586daef6406f93bbf8cf4a3c2c5649628fb6144c0bc251c0f64051cb3e99c91d68e3b9c387da76a5f1451af7247868c286b23f540f9cd5c8bdcb692ae9fd6ce61f5fefad433b906409fafd15e5f50cb8036a558d01965a6b4d8f975a6bb9d87f5fbc57ddbdde5ce3227932f3c4c900898848272ebbf51fadd4e27d8477d01e09db15841b9fbe949d4f44e8eece28065cfcc1aefe8c9fe8514434fff2c7f28b4a30b47bb1218800086bb17dd836142fb5141aa016ab1cb2687a0aa3cbeb8f78e319596ea063bef4fba5f6f7c58c66b2989e26a810f37d22a06c4a5877741acc987e3e8ad8b7c86047a71c276edb1ee3554c4e07adfd288b26edcb5ad3db23ec03deaca92a61d3b12701d970a7f7fc029b545bd001e18f2280a782debcdcedb9fa1e1360160ff4fce7678b4bf8cb000bab3b709fdd86ee542027f4dd20ba0fc4675b18a66aab0665ecaae4c716fe4cb7978844cae0f1d8f966f0c52126ab5c79f4ebe680192c31d8c6664917aa284e781cb7102a9f5633836945769213d44c09e5078da299f9b8ea4d0baef6c5630d4ad5a1766b0ff6387a61d7edfa593a174e52561c369a4e133c0adf99f236a386454d59a4ab71093ba498e46b5f8c4243f22a1d0e3048a1b33cca18386330964360537be21a3f1881cb83dc4d47958b8102a954c51393a4dc5cd75a5d14fceac5f0253b7e9ef93bc11023dac9030fbf2b71b23a50f49a362c855f3cf190605de1b826ca37f475ff0dd63ddc6d267f250fc54621280ce0adab1df6575b8d0dddaa9ca933d424e341a84ea4ea07632ecb222a9af85d74033f4c63848e6db5bbc8a2e663a4b98d0a935d178f7b886c47f115c8edc9f7ce4d02e6fa112713df76e7b7d9d859303aad6269d1759adc1bd0371fb9546725137b9bdd7936dcf57d519863ec02eaf4e5b3622887c053aedb0c00fb9cbb44066ce7a7022ac9d1b2dbbe431e1e3290cc98e0bf311f6ce5a1814ebeb1e0cc14d39717f173d1f647af586d4ddf25d7528f2e804953c59783017df785e25fcc0e8ff5e7183dd9fc2c03b517dd70ad4bf8f8ecb200f02c78c582519c9af1c5389efe496a48cfc617d01005b6799463be2846f6dbc83fbd3694f67a60edb7f99eeb37ed9de94987135452117e3ae3258c1a68db297d79320dcc93486e3d777cf012a6773bd77d65188179f318df85cb565903c93b1256cc41d1c83f82ac9b085c9840a0903fc95e8d4a512ddc213668d7aae2353738ae4d52f782d7f530cf7e4baf995eb2a88c4a334a10714d183dd2ce06a1ef19f4b61ccc7d0f1c38455cd97d9e9abd266fb43082a54ce371bb6dfd99dc35f11fe5b5051fea00361e629784d6ab1a139fd3b0d81db49c05666328c5a065b3ebe9c927b1ad99ab8e371d7e0f4bbc1318de6a32c7f1e04ad667b877018d95554d99403312b0fe22886285bbe0f44d4af4cdc6cd703dedb9adc30f4471d4352f2ffe926f8aca358ed4a2a9b1016ffbcc8b95afc6c23a8d061aab055645a54d185516f5953d349653606842df6a545da45734959e2a48e772a6d0a4423f0980b9dfbcd10334f5e72b37f0227d956fbc04247e5b3a5cf17cd364305e7efca914941664aeb9f6445155d6e13b08521bb7c7e6b5c8ea871653c567e8829407a02f0019f09708a779dc15fac3e584e171fea0da595d350a396d122b09a7956ae277ac46243df76f02c86cdd63bd28367c1d3db5eaa48f4aa50b744f5a91e9eacbd06e46ddc85179df5d2f898f9d8cdd09fc6115c2a15660062d751bb17754baf374c57b223e89c665bc95b3adb976e75228a41f59617f7ac1e2ab9b64c607d6994a57f930e6de4a6af091f49f6e81991f0eb5c0e3c89e816dfe4f66815a907a196dbb4406ba7d4d47cf6a93bbd93b5aae815c7c59e81ca06e620946b81dff75a2794547a57c5bc1ecd310e81bd370b4d5dad4a4cf148860edad45ec61aeb4f9c12d09317753ea3b7ca8adbbe9341cf31eff23f017923335cbae4fc5293884ec886a844945567404f3e2ca618cd427ddbff4d5057e65514aa97b62fbeb6c248170ea03b00451963c4b5a4ad50b1fd0665a74472e2d7f3d7a63462ab4bef5b999f91834141b98eab42faf26db5ad0ae46e1c3f9c9bcee541fdc6a12d1485935266af1d83beb66f01d49360c995b5acd3f7da12cbe659023c88fe5ca0866527c91ec382715ee658456c5e5ebcef337914247d0fd95b4ad0d457e12dba618d2975b76c851b39eb5141d0db82db619a8feec2032fa73485fbc0e907bda5d7bef6425eaf244d5b98431ad80b04a2df0dbc98d3bcf844241f3a69637ee32e5855106eec8a80ee68ea2365c7849efaa056b17c74d3d2bbc5c216b7791c091cf0847d1ef52cae2141027aa9bbe9e6a9e21e471ec9509f75f5a0f536678bf655595426d363c901e4856007e1766a54771cf629a4aee6c45ed124acf36df0b1670101eb0c851c7c29260dbae06e907b9a37f4f00f24e03e2fa448ead62db2654f77e79989f0ccc71c6f46131a79145314bf73ba5a90c4c6016f12d22a40eab5a070346ce64dd83bfb76e12c5a42340bb0e3efc2a5c9865aa489236bc4b9a784810530fd816f77dad637f49cacf867d4ad4bb29ec9d47bd89c33db857095744bedc2015302dba60a0386a75482684050731bfd32818b9d8b6064535edcc0366cb08d1c6a7df70ccc84d0ff61309fe9333f45930ceb01a6462bf541074af8a3c42fa082962371413431e132a42696b3ba03ecc36eb2da91c34f34861ad41027601decb35be96dde9b307185f1e183d0a4b0c313c1448ace10e93431803f41888f5529590dfe92f32b4f648286b91232f3a5655cc10a2d788f5dc1ebbf23b549dc1a935dbfbf60a6d19f5536e9fa0190a8d93962048315c9ac9c7519e0557481f5d92ce5b55de5db5da21424e30b9059a119c91ea60e8a44aa82767763bc6beea7df4bfa74e5d4bb466887392010ebe72ec747a783b7f6a11aedba197d1d621673a575582c66f46d1a0508ad12b9a5beb7ff55f4e10b64f24b64e4636092d2c9476faa2f0909d6683653912849896872a3a0661b3e391b2624ef7ad4de735188671124ac7ffab3f9cc4d79a4d90c32cb1557e817045c7fb9c7f8c103a37b7334b1ec855dc65bfd42c3c367445258e49ddb63badb7d5461bc6a016b2fe9dc574569a105b069b89cdd2ca66291b9da6f7bc53b161fc8ca43c659c3a647b21ab7568b6b90aa29a404d5cc6035ac90360e57468972fe9fe5e4454ae131cc2530a48ede832e712bc1d0fef18ba09acd9234d2595e23e2978c26475422c5542f7564148bf4c8aed5615794f622d80f9d05101ed3c5dde3d340727f380316d9cb60edb39ec9c057414d75186db01d54cae222370d119f1512574fddeba8079c9b53b880b7f680bdfc9ab4097ac98c134ded72a7f3f89b7c706259f2a3642ba45c7b9ae8d94e8f49d815f850f39b01b4e01cce5e2c52374d9dd24528c5453857951c53937051d6618148d5b052fcfb670b9631e11943a4ff7313769409016e802994c50f0ca49cd1a687c93fc79a13cabad01dd643a8bcacf2cb2916e7aacecc30d3d8b7d5eead84d59df3520f7b608629be8b68371de50173de056557c915a43022f1105d459caeebe788051e4b2aba3c2591f90d549f43440bcee65a0736b94f9818bb304e9e3be48d71e9783a1dbf5798806e2f28c056a3ecb71cf0d13c48815a696c422bfb5f341c9cc8ed08b1297b4a4f400d801f72da31c446a340723f5812685dee2a6e8fe999ef22030bfa129083c4a3a8b2368a828bb38acc95fc282165173d9e57d1ca56b490fc211ee1e058a564ca5185fdc649ef3d5818caf63690f3106195763e554b76687ac275f3bdda5a806fb7d49efa9d04048c08b29d6b86581c8a2936c1082ce5226cc7b0bc890a8af3b640228d3b5ef3b35d2aa1bd94d580791e2a88ed6cf3260628653998f4b05b6c5390c064cf53487fb9909cc808f87a251b4c8da1775f701c75f9f1de81c20dff27b58fbf7ead2b729d29754b780263916fde7f75b438acccb4a3274326c18fd438e8c95bc781b1b265df711a726e3d04639fbb667e0aeb7b889d67e9add89f05382d0cd8770eeddca7c90235f03e4ca728f9320e52ed910279f3d1e2ac7b945b7e0feeb6ed23655fcb3251a33e3ae2bc503d341f9a1b8f8d64b79c204b0b9de1fa39e4c1f70e9edc3ac0209e3edc89c85713f568554c3e9542eecd55f2344028539575bb64aa83494c007aeeef285022e6c279cbb23bdbaa59ad864d70d15515ee66aa46ae3aad43756ffdd8deab1830a079319546bb739aec0a62a8a5b6aaff74df65a3fd5c85bfcf551e7c3edaba78111bcc326ec35e4f152d18602f86d17edd72f7ec810f3e56a51d0e39e3c5a548785261e4e6b7705bce9bdb09a2d57c8d9c1077458a63dde611a3b447ebd9a39e16247d20a6f48f3655e31cb05f740cb395050823a5516c68b5fc831e480ee04a46003f0b262846c4319fb7d835218a4d4d35606d1bfa66fd2e9924ec58ee677fcf24d428b6d1e50ed7d46d1f2fb36cedbb5f53923fd4826d250f774185f24fa25fa65fabb7ad607b4957745ca0e36f2739e77eeddc952d5d4dc9e1f5f5fe6bfaf038c507df649d57273190ae3977ee8645985e9c89d09a4d5a7e3f93b20e5596958b80d556e1c5fc938acb5aa79f5afa67602c330fcc60806c33aff42989a61194661c4fa6fcf3b36adff7668c49009f1be00f303b795d06770821532d519d243d27c9388cd42a1153a07691ec65135c7411fcd01191c7564a0319808643c9437eff6b8492750e25a42ef5c83bf6ff8a2e16c71cbcbcb1c44afb2a2c13ef5821f052b0885a840ef5b94554e93adef1ac0e9cdfbc72522168e34acc53c4f7a7586e5d5dd0ccb778e8509d0b73f0dd703c8b236cd327f56f0dd1d0fa0f4d6a5519648fa13eacfd4463e5551d5cb69fd6eff433f171dcab57c277e00364ca63f40cd4ba0a0be4ab001b0068e95a0fcaa48993d2183705d980df623a1bd1cf5c0007ac6da3f857f001bcdaa68c34de648b5c4494439a8739ec67e47df9fb2d942021918b9634f7e6b7462eeaa1904681f18081a42f1f9c8af74841f4e51be1b3445e485ba6cfe24b842d27e81a914c7f60cd9be9e74b7ddd230b5991fba23acedf367945fcd135eeaed824e0426a78c4966dafb8ab44dd4e9f25ddca6c50d1b87853cd2fbb204f37d65d5d4118ae8eed35f295dffd1ea2eea3678b6ea10bdfb110e613715733c8366a0610ea89c903ff6f8ac0e2d0ad4537864e2494eca61e5a4baf5344a7112e9a8ae5d81e2a00f0302cb4ee3b44a9ded26f1bed4330cc1185a21fd756b0627e70ea8fcfe11853f0e4318c8e575128d7b7552ce1ca021c398e5c97ff30fd43191c7cf5d5ea64a6ae81608bbaa822341bedc71983b70832a4fa07058fda7783e7b07eccc14d2e608cdae9833d78a98c8d429437399562b52aad769cd7a15b0a3b2ee82a54f4e0ea18ced65a361409120c1344ad3474a23d4871be982eb3b0f92cc7d37d58ee7795a2a8aaaaae09061ec7d3b3ecffb7a56c5d4f332d7464104ee741e5cf89bbc89fdd0c58d4002520b20b3eac492f9765e6acf6bc7cebe8de4e518c9c0bce6b445c7afa370649ecee28ce4f51ac9c0b2a7930c4ac03b45e592afc90623f9e66f34fa3ded2cf9cc9fce1e02ef0905649ffb1f69e5dd484f265fe3f724996dea5f179233c01ef4d0f97ad2567c634dd7f8cb0c0095da956c09014f7672e66677cf234dfc216d73ed14ce8e7699abbe3dc8ba87935fdd9918908b257a711a0c369c0c7e92796ef78e75226cda8ce22ea9d318ec588fef20053bfe195b24f4fa0a777cb6e2ef28fea27d26e1ca0a48f8eb742b23bf61ca3d0d153cfd1e3d4d183d9d1b3d6d1c3dbd193d2d1c73fcd6e233570a7dd2d413f99a4572bf1e3e3d859cf681ffd4e1569681d3b683248637ab7e750d037e61ac83902607910667500df25c798bf76cd05d9bda140d73eccc53b0c6fbd0a473fb84e0714dfc7a6ed76f393730246b4cca887519e3af7e3f9d3921584979de43cc97f82e01f50512eb164d84341506198f8e6677ff9b5fdb39b2f0bbca488c5fff66f0704aeae41ccd53b6f6f5784f830fc7111605c1dce8ce98db9bcee06abbaade23961ba6f8ebd206df73c0444c787ecd13b523cbde5600ff696550d488ca501a3c3e5cbcd049bd129fd277e7fb02cbd2051f5edf7692dc816549942802accc3af47ddc33807150c0ec3cc8df2fe308d0c71d61d3f0bf7cd43594a8f7a4a645c3b8e56e47124d1d2f9317de21adf151a320ee53cb8c69bf71ec4d2793931e674bee0abd8361c4189aaa886198fffd3fff51cddb981dffe3dfefb9aafefd7ffec7bffdbffff67f57f374fcaf2acbcb7ffc3ffff68f7ffce7d3d80eef3ffff1efeefc9b8ff9dfffe3bf26f6e31dca7ffe63fac332fc377a976ddd1cfffc0709417fb07dcbfff98f731bfe47911dd93fb36519da3c3bda7902ffb57aaeaaffc89b6cdbcbe37f9f47f5bfa8fff8657b4960ff5701d192fb3774310c239537c31a7f87e7ebfc0963ff871f2bb9836bfe19ac7f1fef3fff79eac5792814821b247f6aaa3f7192d1fff0f00acf32f2eec2ac1dfc595afd015b8a6198a89da18fcd693980438e61186dffb3c9fd33af9c7dc646524ffbfaa7300c73fde50bff6ae59a67fd8e04819bfc0f3dfb27de4af61f25446df16dcd76c5e4fc01cdbffa843f43561140212690f44e11886118f48f1276ff636a0cfd8303a61411bfbf7cc4dfc3ff7506ff27aeb3e6f49387916118fb8f3eff481846c1144c9ddc3b6a862472184624fe8864ccbf9b124bc08f25939a21711826deee7f25090cc3a80cddb3253dff62176318b644ff80d31f7bd6fa080a088ecf952002c348e21f90fdfeec2a6a47e635ef1789d81f7dc9cbfc379fc2280a933d6712c103c3b0dd5fe5d91f95e6ae9dadf093d92b471d8691fc3f204bfdd9758269a447402eabc31fbef4fc2b5afe339f896b6c37079a46eac830ecf4d77f4cf2d7eb055dc450fd5f2910c37c3cd27e7f8fc60b142bfc65614c1bd3c4f43f4b61b6c618b3d59dc44924b54e22b65f19bdcd5ab64fd7a62999b4b7910263d3847d7d8361440c606de15fa9d3822425ca868f8bca15509538ab78af658f4c29c0d0ab5de12249cd2d68597b668c8c4889dc2d86e86f9c602ce4cfef7c2307b5d953607fcba0ae1dd4326806fee9a34bc91ec7c9267389b8c3d54ac30f1876e3c21fbf76323d00f450399b9eaac23783393c91df40d2d754a00051daaea6789ab60f0bada0328aea3034267442eff1c8fb360ae07d15bdf86d6628400525e15ba6a618aedeea642b9e3447f83e98a9c6b4c9f44805be02b99ddfb781d34725c5bf851ee4a112a8945d7abf551e054f1de8bdefc046f0725650f781c31a507cf6a543f806f7faa5bfa4371e10af2555e71dbd01bc637db0f834d647f95399c6869b5fe117b3570638a0feaea1375d2501f96f161c8bcd2971c9a5aade4a9ec66cdbf9c60635790eb2ad462e3a0bb248072bf8059286973697339f7ee91c625d8a102812df68a77f2c0e2b5e76641903f78397f88bdacb9c714894d2c7e3b8deef56bca66981fa55a67e141c862250be88eb22de5a65b1d78657a294a729f75bdc1d444106b0e44d4bbd243573b8368d0e0f9d35c32c8d95458762151af4e21bc64def2f918e80f97e33e32381919608935f378a610c932ff373af33abad33bbac33fb5b67162f670ceb287e861cce84e5c418a705e92e0f75b8cbcf6533be65d0db5a5e04cfcb30129cd66da04cbd4faf1ec963b354782d065385b1cc0821e0ad8054ddcd64a7cef90565bc517de12b1990d91f572ab45cc77ddf85e9b90fb1677a9e38b2bc18b7c12409d330426ff4ab40a56f57a5b02558a26fd2dc6931082af537888ff406f97286726956a0370cd8681b07cd9eb091168f7722a7314f33b96e655776e77a102676e229d2dbf4bb2aacb68aca80ac517a4955638b371321c4d2b44166f7e04f844da42ffd5a89c6ac689a0e497ee32feacae64c8fcc24247c84283940cee7e78c539a05d0a27ad1a06a1eb4a93b364583b1f2e9e613482f9d2f2170da2a686b3ff80c193a2c183b8b85e0238e8d4139f7a3a7fa69ba505e490a34f2d37475341f0af1819fc6006961a3c13558a05a68293f1cf2386c9210fd16e908daef47d9fa41cd1fe52cf8306dd07ce1494135fb92c659e4fd86c0d3c2e08da4253dc9c05bd0c0238c3cc1310422aa4f632d01a5595c1a8543e2721c0ba7462e050d59225da75cbfba936314eedfded6478b3e29aee7dffdc66b1295468b43b43e25e35934291ea9d3289ca54b92b230535c0729b6059b05a93df0eedb499a20f254f467f4bd48d9b8e4cf3666eac76c8d71941d0a953a7aedf4d7d549986703aad9dc4c6bddbbdad94c651dbcdad64c133c7971a7fb3da58974776e9d2d47cab6f56844a4c67e9a471d9cd9d06f95cea38361233a370f47a63858a8af3b850058d4b75b1c58ab542354939e7a5da2fc2c0a11a78a21011edd8d293bba8fd35d92253e35c1376e604b2b3223170ad83cde07636cd64a3de2302cfaeedbb3f36843188bcf7b257179eacbf77b34dbbb35b2d5e31c2ede0e5aff1c64eddeccb6de1393f508233f994da8be6c906ef94442244cd98867772682276acacef5c54cc8ec51d2633112c0a33a558f0ce52e280f1787e52ba9f261401bd50cf79e3bcb9a0d3716e821e3c2a6c6fc644a9d7fd563733f493e35348773aafb5e3b4d31c444eda724047d4ae34217c845b90a3d4435db9b29ad4356db7a2c02d6ff7f3b71b188dfe58bfb89228a19bfb939cbfd66ecbe7920762678a022a2f32a466933463b32e1b3876717be6fcbe8e393e8d7133ca29d3b8d81c903a7b499d3edeb572554b1e6f3d24813be7fa8bf2ea173f0d785ec85b40c7c15c50767727311725ec66458a8f5b64cad42feb0d901df94a7071c0d29e907c8343475a85ffe2e7c2cc6661ddb98987292cc1bfba09951d24a55fb09cb79eda67db9e0c44f456c17a23a2e0f5bfe44ea5628a249338cbca7b61c2125793f2b734bd5e43ee133ad3c0f8c81dcacb74c02206d16e097283a3c5f539fed54919b3b424be3950f31b184ccf89d0348858739e7eb7d1522b9800a906615be79b42b1389bd02b50d0faac34e0022fff015fd94d8e27f68d948d5c84793608d2441f3fe50593a99d84578243fff332fe2a28891bce45f8076490924bc5f13d687063441540e8d6e27f2b9bbc10b943e6e9aa494f330fd10b7233f8b3f9351fabc9817afd1a8ee46b372cb5ef0aacaafb3a86292b96b501263e920f8c645cbc90162005d4f8c083a80f42bbf32d06892fbe7683e66ded6c97a08baddf60000c9354bf05f9978d7a14c205559b41fe3360a8efb2b0dc0d680c481e3bf18be3139c1f704ab6801063d02a24dcd0de0bb700885070b9d7d88a2e4a769e8becbcdb7981fdbe1d21b9fd8ca72665237c7e22340286d7e329d6e4de03a02d67f92abc4d5f4faaa94080b9baa1a12268a48bba16ebfd04bc1c021e6bd237be3ea01310510d03226c8572d32e88782aec50fd66ba55037ee5d21238ae5efb664b8c462248ad736aaf5d4ac8d88f66f5dc336e9c1044297f41cbe4b311fe877ff26d26f260b894f49359658552a6cfcbbaededffd1295c3b907dcff7655ca5ba0f06fb87c5283943990cb9bb0cd7443f7f018db8387648caea571d43ce37935a1cea634817cd8df7be8b9f15a39750d9f941362a11deb273d9386c4940d3b748df9d7ef9755c637595f87a7d5cbac302138d71692829382487c9ad872224d28a5495b021e5325a064741d1dfef99ea33bc0419ac8c64fd8cf601eda49dea58f4e97b28d6f5b7abf61af272c9438ea40187779bcd4f96064fd891a1a45c147447d06640830fa8ada122b2054fd2b0ea2f9102327e458bfdfd375efd9dcb566f0dc7e98a7dd3a6a0ab1aca7d5664bd79c33265ec85ef4e7ad9be1794a73bcd0b33940caee3da4e0eebbf6d3fc55d228a21b94028363d9e1d306a9b6c158d7cc1d3cb2b5efa8255a8ecb967a6efff0c167c617f9919c3e4c782cb5221c952dc525937027d4feb00684bacf643ea7d9a62eea3a66e3ba8c7d3666ebb88cb3156ea168066fc087fc3a1a8998152e04c0182ab350d477b9d2f84a5853d9f26090dc4491e1fda28c24e62b8595f8013c8474af00f43692458930ce58fa743764312860102e94662ffb8e33f88ca0bd8e8b9f7a45200302d62364a5b97489e4d689c5b691a1df9d228b4f0e4d8d20ace74f991b48d82eaf86474a585552e83b4ad26b279a9b5306ab3b1d15235070566eb38fbbad20c3b58e4b2ca7141961a8aa87aa89324fd8d6e341196153a1997b46e305b8b639b8b605ac918c5a68eefbeda3d4f2f37b4f4453d384c548615979a1c7486983eba3105f7e8dc4f3615d703cc7868939f74a6fe4aa16d609c27690c0121cc6b3f52eb12f6733497b488a92ec4d9d495af26c8a9c1d8ea8c937b7259287317c16f7a5a00e3626142e5509cac944c96d5c2383fbb2009ab2d0acdcb928f7d61abe7ad786bd5a05175f23435e212fc4372f3204df7e02eb31e36d319f4bb96cd3cf51f21815c3e0d19e78cda4dd9670bc2d2f290a5e880c13847c0afdcbdf3f372936fe3636e6ee561d4e5bb60e85d2e9cc2a159a2e19d99a9104a5347886f601c6f909e52b3a0255a0345840a9c8b649b559b751dd3cd58dda9828a1c476530db8c926d628c38db5ceb03dc37ce8bfecb1753ab3b5e4a412dbb018733a4f7df53bcf95ccc1123cb3f0228b387fb4bf302f53e9fd8c9bc410cfe4ca7570b0ae206a4e80b35e209a4e08b25e2b964982b4fdb956e8fb8120669acc3ac489d83112e3861fc1d433c7350a6386bf7b88bfbb093eaa63001bdcc0b7027990ba400b243b40be883b9fe4e601728b67c1ba20e5f3a1765b66db3a3ff8f73b659284db2b486115ea19214114262585befeea1091969963eb240abb14e17becfc357869c710eb78622384c2933b83aa457d1af903ce06c190ba6168c681e97bf021871058dd26af20861f7ce6c741fb963dc7cf4277d97d8b498473b4b8695b47a82afe3eaa36b81a537967b739e5d5ba79345b0c507c499d7f48b72295f6d9832eb674718d9fb876d10613e46f97c96e69d1df3192d519d9d734a1a4942c3e44ea67e861eb270fd9b4a38e959e4da308cd7cebfb793c59b344e408ebaf53bed4795dea17411fdf007871a1c8e4ee68f109b9b56f376e491dfe52c93a92451a75c7af9cb6a9639fe00220c2797c9c227e37caef252b3ac11738cccd40dbbc2e7faf7bf859dc19fe63bc9d791cde644c87411dbe61ac9d216f1e63dc9d591da161028ce56f2166dc9b551c61677c86356f51601c87d96f5e65548a69188e61c49c8118de63044714e22e8b4389f48783154cc8e46f8b7dccbab68a39d8f1d179a6b3619d498013b84df953c49d663151cd6d518195218e751934db9eb19e335719efa411a186723aac113a736b3505b2bef3cb292235a04bebb30525a2cb0e80bcef0dbd60770801035c397e064313fe4660887128861c8dcf481b0b0c8098ce2e973bf0c7ca5801fd7b7b5b0a20b20e576801491764560900ef8afaf6e4e07d9f361d822bfb86b5b25f1e9c3cf94ce6b9ef3630eb211629b7a179c9722aa74da41eb8e5108e6cfaae472ef47b79e391f1d7779900390e56dd009893430a15020fe7f5651390516d5fb075fc3b89d5420cfd23ff3064178b7db3e74431d62f1164c430a3d2b94bbea646d1d450d59877de959164bd048e957c999971bcf85470751920a8bd677b54afbbba660ba46791e35b63109fb4415b3dcc5f4415f7d5a4b221a2d733e8d6d529cd312742a490493b325ce072ee10e34db3d997774bb8a8931fc405b5153f1d0a149cee9f88e0500319c27c27469cbc0008fdbd2fb692e136592da4a23f6259575359beea28bf8d94191c2b39ca64134afa7de00578b34cd8dc7b9a526db1824998e806ce5bbcb6da49891ff1aab405eee1fcaf2b4ae7cf8b5533cc05820c4363c816d12cfa04619530a0856d107dfec60537acbaef0dc1f09a8118bc12a03da65882c171342ae0568d99c5d599c3e9b98cbd5d574acd02f181c347feb50d6226b308174efc520bfc91e8e97b4e8b5d208856e53e5248325e72659d6559563b39941a863ea0f02da294dfea88734b780f29daa59d0c3e58b3f617195a1e79e7c6b83013b66379f1ecc454c7568edd39d0e5437d72fbd5a0d35b867d774265aacc144641e141b598f9cbd7fd57b5c2e539cb84c14f1ebff889ff9a3774f90d9bb2527b4bed8495e6791cd2e6a2839fa8c025fc28f5b4283fa2345e0b82d35f22d5e8558c94c7df027d1f8ae739566445f04a4e20814171dff9618e582f51075c7145bf187821520ffef00b7e5e08efdc2b5eade14fe4a6eda97872004bcac412c87e131fdb3f8555f4c64ce1d449c8f730c09cd99c9d5ac9dc2fde044ac9d2afb89a9cd2ea3158887b67808316ecd0df57fc38612aef9cdab5c5987eada1a4b1b9feed319ae9e6a2830905b02e7d5bc018dee798c7a271f355c971893cc31286a48c15118f0530c49d0ac64e5ad4b094df5e1f7c90861f5cc484838c6993898ad3e84b882993fa94607f325b4fc763a3014a3f944bca3efac21b37f4c1ab6030062457b7201ef8af2a990f5ecaecee05bd0c5403dcb7fc0dda7aa29be364ab5108b7d3d56f85c75416320edf584362fea6731800304df4c13c4b02f9d1b45b5201af5047fc4a88533190498d2083d8db2f1a238069096d84d912022ef13c0c80636c9077d04fa7c9bcaa003e6f80aeba375a9f2f4c25d366435f5f3f7b0c0603fce8402938ab16876890e19293707bdf4d117fc4c51c0516d4b76f3e5ab86bd7302e5f5d5b98028105b9e9c382c786c5d899545b298caa00970bac80069c416a4c5fabc8aedba6f95db6012c6fabd7fe4daf9b0254c323834f215941839983b4c827cb609d0f8bd3a1862785a36666ee1537db5904d4bc0a84ae2009f78e69a0ea24d47a7d3460dd3ae25bb7891c798ddaed9d46f7acbce620061dcdce0df89bdbc228616775a62d501c28574b068277b03c76bdb73c1e7b14849bb2a67bdaae476f9394cdd09192fe14dea386c6785377b3c2725e792608c8a70cab9f9c7017765f786d8a6a01ff706e93962a0e8d902f3dc7d11b3a0f24402b795b88247b640c3174c35ea9fb6bf40b049e3d5b863f8fccedaca1fdf8c8a953fa440a34dbeaa0c02db73d5c8e68da29f0a42c915ec9ad897729a4a06de8f94848bef619f4a92b268717ba585d85b0c2acc0914ba88a766140af6a1212ec76e9bc7c9088383ea33c66b2f826e3d19a5834bf84173587238ec347289a9cff4dab7004629a695fac2192470f968e84b11c8aede02ca86e49ed081d4f19ba55beee8b3803190780d92e17872dcd02bac44e812cb2a22a762348479c7f06e3ae73efff6e753d6b9e4f9c5e1b09bd603683c8a454ee4c775f82c01b5d3f640dd1acb01a97577d76dca72fb845eb70981694aa462bff18d1bcb280cadd513b46ee0fd04adbbf225a224599c613f935e8a1d811bb427bfcb850b97ce22a8eafe7878a498cc6640c630909c2e58397303981b586f1560dce2860e6fdaf83a0cdfad1359c80207f7e151ad9d5a720cbc6122f78a03a0d9a38b75789782e85b2c56c1a3f7a3cc093fc08c22c8bacb84e2b74fefa980b8b96712dc471a519c9851271df41ea3a6d80df71f4b600937dda8b6ee390657ea1b4a654994bf1562e88c69b4395288186c58a78fbe58e4fd11cfc42552b92211b1fdd92db68b3f5e6889a3e0367ea68a078d4e18a76ac077f518ed0accc300c120a17398c3d1b275242786dcda9a7b0623d3bfda543e4ddf292e1713205037aa08ac1ba6814dbab2caefd0b043211e94ee792ba62eaaf544cccafdf138338dcc9d9cb83d94779ec515b46f9aba5aa0a2cf61238720baa5e84f4ab235b5d8a26891d7d86afecc36fbd16a732647639f4aeea18914f521a163eacb5db75d360d623102dd3587d904458321f39d4b4dc5c8befc8f4df2d84c0eb862753a2192c9e363acee96b9c55d75adac0ea013bc5df2ec31efcbd0cacb8c6c57e5fd9eb53e20e690516d6219764be23c7d6dc10b859c9b79258c3e3ea19977435cb7184ee6808ae031ccbcf25c2aa789c1aed017209c1faa5bb6f07f42b5890711c0271e3212b27794e4099a772c6be2f8f323ab9103289f386dafcb6152b4951a3aeea8036a8763cd0e4cff3cff6ab5ae84bab2cb5e03a251b543de8146174e2be439425637de0dcd5d99eaff35ca1c93407e7ed9f97f4d7fe580bf5035b158f8a43b12f72aac7bd347923b64133f2bce2025606b02014cf1ce74405e6541ba3fd24d49bc676a7ef62c43038d1c75598f3229983363604d851a71dffc95c52c6aece73c18384ed8924775f042bfc4ead2a6170f2aba21cbdd32fd9e263c634a299a68a6c70997a8c19c45ef1394630e96228e9f78120bdeedc3e4f460dbed60e0eedad134321231527491bf6c33332e4ed42c6415c2aeac7810f23c40852dc5dd3267fc4c747b0891a524b9cc97c2f43188481f2d4afe66668f850be4b21818c6e0a20e8e2767de052b083dcd506c75e94b8751ad8556330bd134ea8d4a8da88868b4c0705950cf0c29ef3c3768cda58643d87cd677c1fad7586bb5f2a53e9a20ea05798ba58a1d8f803e6bb44b30a5d4521cfaa06bd31941a48166056ac9acb5dee755e1ea131b0ec11e281153ca95f80cf20baea10b30fdecea453cd88533662d78fb284727c45514845839b03bf51249febaf90d4707fa719083b1a84a6c6d9329ed2cd1ca1ffd0d2e6400aafafb70c34d8c160c32470dceb501cfea6e6d3c6ea5ba43cabecc92f936bfc8e6ddc195b6548af9bfc881fcf2ad3e9954466d90baab6ed40f865cd1eb40959a63b1daee1b2d1990087eefa2c15ef6d91521973a906d644dbb6ede670615b1825570271668da15688491083704e466cc0016745a111b43fbe4172a905ec675a2a263c29fbd48a8f22478cfb2454c26b19f354e5f7747dcfee9fc6ca5eed748be7e9c2e7e32bc4b7ac1ac3e3657b38664969c8691cd4f8072ccdc2944958022d4eb843c0dae9bfe20d772e877a9969e23ec36a306bcad3f233924c292aaf5abe5880ea1c71ded6d943987c157a2a65791280a0377b7ae0bb2cd0ec776d716b815f7cde24e98ac49373a0093764a630d53ba2f5bcc70a78b7f70018b3c2d15ca614412573298d19a071ff0e359948fdd7f2222716edbeffbc2b69a82a60765eeb02a56465329f121824925fb1aa09ab1cc358f3980de99c9a8b821300f77b3131fda15e3e57a8eeb921bd44ada0256a4063c683b105db4df984857197be208817e4c1019539f94dbb57bf74edc1b8ea0f2384018468b7632457fb7041053fc54f6f40904a735dc5dec25e6510ba90cfbed05f2754bf43cc4ed0bc501567ed0ce18ee56c109063ce5f2f588e9d5a88754758edfdac6ccb7e5c63afa278594e16d91ae6722a87351961b27920be9d98a40227b6ef202c47467598cb566c59530c73ddc3c7fbaf24f21fb057e10f648dd69d3afcf5a66e41758520c3d85b1372baa91acd31f9b41d298a25cc10295a833a68f3e21c5d7e8733dc5db21f0f432adcd01d9be5583784f04f4d12cbe2885da8500504ed59b175aed3f70b134e6fb0f393a068f5cfe5d0bb7578126daefb841ab6aaa3d443b57dc080c23ec1f5f959648a4389f6f38e8a106f7a53c1497cf0ca3ccc98c6157299ef1cb0e11501d66fee9e18073dfe286664ca7f36bcc3204233cfd333c0c32faa472a62ccf9e7afaee69c62082e109048bbbcaffb40566348c33b62a8c5eddaed0fa10c7aa132d99da16b8171f33a9d733a50dd956ffb30d25b6fc3788edb5522c1d23cb6748ebd120771c4d99d1490cdfb1cfb42ce37562ee4ed11f9d1f96667f915881f5dd179340af48caa32b890c210ebea01bc856a5ce3cfea101f482acda7d4f575ae6b00b4c48771f99c68e229a9eb1de20a21b167839c189beb726f9ca9d771437fd9606d00eea7c182975bac19f4b2887394dabf441bd94044d764154fdf01c0f0e4cfe755ff1c089718873c2ce9c4259dbd3f2dd0ad1915b35833303d5284f789bbad6dddc1d79b2f67ae85b6652ef50600af596e8ec282113357ab3d26d0c2d7e66cbbddaf9dd296f3828417ce2fd6a5a297a81334782774ad719fe43fde3369bd4ca205b7ad26ccfb7321ded242a6b3a22910d84bcaabcc2803c1d01a57b0af386e4ded49c76f288fe412e0d1eea9a9ca5cf0fd6a6737fb15133a595575e971195f991b9d1770ec71fc10bf43f69b831bf401e4d204f899d267ae74fdae2294a0139bfe980fe21c854a63f355619951cfc911b24f6e3cb46cbd12e6e196a861a6e6f05eb0d73a15d92d300936e7805eb080dc114729623340e4175332dc0e578727c097d36fd2fed5dc889a314be873383467870e6403b8b748c0ba8d1c29fa3841f486a1928c8409217be27807e4266916554ff07d3d241d7515a3d24dbc33dbb633048cfd13f86d081a2edba6689ee627c9a9d0e519d260a66fc7fb667755170e3aa36225b419fb39246f7951b4b1dc129ac0969b8223364e2131e4bd1b2a0b4c6716060471193ff7ce1168df783f30ba1b65a754d6fd2fe61aff78b911f08e48edee3d4be7a265b6cac765295cbf14db19d8d0a7e6f6f842c4866ce221740e6553f018a149977a34ee3b914628583d45654b22ce4e7c4c921d63ed4447989c4abc35f835762ef72e226c43368a3e281d800bca99176b384dd3c90f997206bc134f8050ce70d7eaf5995d8e960c93b33bd4b66851421e934a7eb77e399f2b99d01d78bfd6934faa534c0d87c0633e4b9a530b2155ec59685d045a6c4ce81ad3e5a8236c63e2407f7bb0ee533559cc10c86d42ed567ecde6537e09f736f451242cf3b4ce2ca2296c9d27e5aa2552dcdf72f8b566b973f31a408402648c7fe097083fafc2a085895bb6b5bdb9b4d71cdb053891269fa95bbfdba6d90e1443321435264397079cc45fd580177a4100744ebc7e057a97de5755a0d70432964894bb3471277fc96d0497c0c51d38541ce3d7ce20e89cc787bc3992c9cf9b18b65f0e305e3f57bc5a720d639d29106341c4e6c48631039e9e879feef7bcb764c398101b615608da11bea535446d856d9e4887ae52c22a1a80de32f8d1b9b07426c9a1e996e5e9f798da3f12cf3e5a0ad1de52f2d9f81b6568939c0c4314d2fc44ecf17bcb0ba10ee1572dc2d4a0df10556d80842d68368b42285d2171fc4a31dcec2eccde59c0445f185c38cd3d353782bace50ef4a1c85890798b38f62d641b5d059d62e66113b270e7cd305d36f7c775e7ba49c85b837f4c58a3cc7ae36414194883ee14ac802006c7bcd2eb62749f45348127de828faa568a623e34cf8f073f45b181f7e6e672578eee8a1a4b4ba3cbc41b7e449194fd1231124034cb55f1e7d985f466e9a1f8a45e915585bd9b6fdb1f4d292552ef9d371392af8abbaeb353809a5b9f7aa5059791910e983a1c7bd2b3bbaed704a249b8fef584ab454bf136b96e3943bc4a82c976a32fbfdf2e7817adf9190ecd6c4e5cbabdaf839cd182cf52eab75b0db1603d8a9060722dc2ad1ebd10712844c9d78c20e181a37e654e3f8c42ea74b5aa19b546d7a5d6b5e2e054b9641d5ffc7a159235b0a0000ec4014b895380f77eb7077e7f43b7fef9099a4081ef18ccf9a98040930b864b8dfeb87822ccdd9abca7c12331e739c50b7582937fe7b13c6bb3a0ecc7ecc38937542d15b13d9c92abc83bd0019cb3ec359eb7ae0bf411fddfb1e380cb8eba5df33cf3538eff56b253befda0bb2994fc402c74b5c6125d459cb3785f7ed2e3895c294d1872ed60bbc03c32ed3a3122a92aa9a28201e2080b5b29222803cd5fd814b1a7845303d530cc6c26e9eb644325e708644795aa3a7a6fa7d5f3b8261f3ea7061fa97b1e4caca4d6e78a2ec2800bef16db3bf632a8f5c11af325bdf410ad90613ac61abac3cb00b3cb5436392b229cb7eeea8fbe6d0ebd03d479c687b6884b14b9d710a0714cf4ce62efa59936bed10993634e8c2284a05173a91dbe7656e78c426f315d442a202f95ca8764e402367048ca15175f1c3bde855445e080b40d019afa749908a423263b1f0bc5cd934c1b3a1c231776f7fb759ffa0eb2734b16cda6ea85a35aca633e08a68c21aa0f6ccda21c302acc19820ba40d594a4435d5976f0654c822db904fe42bafc862604841e4384f6f06fe86a0c076bd411a45fe249d6fc7e8379308c0487905773f1f8c84fb87f4c08b14bad7906dca96427b72dca55925ca8b7cb30a34ce377d690d5a6545645d4e05dbad588d217632147388dd627e270c6e7049c1760963f63912ad6a2738fc6c0d7efa3ed2a1d3ff4665bd1a95e1fe01b00c1228ff68127e99fc374bbef761bf2adb28c55b1876d445b7263cfdd0fb9e655a1117c72186cbe1bec4f55418149329dd99531e5b1ddb1b5e4718d1eda4f4f6b9648d185cf4c15326e64715d7725888b51a8ea1a2ced5db1845cf0977601f303a4f008b57a0fb1aa25b60078baf6fd26734be0e825a24181d4ae332ee46ba7712bd2f8eb8153e7ba4e1aa50f8bc089caa8f00632a47a160b0e268844e33dd3939a2e1291d76f67d58ef829bfd047d8a3c3fe6c810e22b3de26d9e82fb0b0c5ed0303f1857fbabec6e05753948e3b5584dea475de7b59ce1d4101926ff8e6248fd4751be8ed405b49b9a42badbedd642184a5decd824fc6538a9ac4c04f5cee8d488b9850e1be6c77ae677fa91424472e943a81f7cae005f94ce8324fb0522509cbead46056d93b346d01fe9c20ecb96f0612bb98e4da857f1f6a6dc0de50bad944ecfea0f306eb449828d06f9ebf33afb0d69cdf4c76825d0f634a5136d7a8d5fa48f5411b95489042580107b05868a44551068542922b2b38a061b2360194b0d24041ae171093be9595dc0e2073e97573f7c5771fb9448884bede6ddb50aadf3b4c257ebff05142cdbeed7ab5d29e96cefcd6c74b8e0da84e89a2acfd0aa7069c594d98b10521bf0d8e4453f8dfed53846678b3c5b7e293effcd29ad3ca48dd9739f7fae31f3eb7c5d44e4a3b243328cfca2843161f67f46f8a1b7cee7b948290a212bb2be2e93254a6fe9a00b27251371d875f9dd22f12ccd4aaabcdaceb970af9edefb7b7aa192636b2aabcbd303f47aa987ce4990bead4b324b9db33f8c6a92cba74a008e0fa801149c436d24db34e0eac0b6024cd827382e1fec8e04aec30ec0d7d0a45ace230855f60e209d104c74e331248a393af2fb5ae1f3ef44c3e52e4431ee36d91470a5e0a1e90c708664e95e53b025958029ef42d2f00e879c866c9e5f4d66010250ebad0a0315c164a06347e56d5d5aa2eb5211e3095b4acd37401737836578af219772a8d4729973ce7a1cb6c9d5e703f02c4e48e4042335ea37ab908ab85ef4ef153e094179552c8000627c1a19daa540b5294b21f6ad9e0f673abc5f5d50f2c4af343813a03602af8f003147eefdd1d91ee023d0dece228e79390a4493242dc17f5919b323df7d5bf5cea62766c3b83cb8a486734ce52b46538f475c78493f0f57788439215c2437550eb17493631e171c7075b79581fe4d2abb31e9d4858034a6690a27d24d2803e67588848ceb96d40e8640ea7df7bbe90c821a10663519f028915b88db202bd43da314c365dd8eaade2ea1e8e4dde09b1b579b2e89548e8f326d9d81eb7706cd16112939ee1c7f9a0bb6d8053184a09cb240054ae0be7e765b6fea2d0044da0dc04ac455ea93cc052e2fbe45fa2a25c4533029938957dcd4e7653beb67a37a340c2bc587b8527983cfbc007539167594d3236e031188afa4482ae8ac094c8b35f7771c4d2d54c189ca51beec572ca2a1924fe43e62d51069de26a617df36379f84875efedcc1a15d3b8a5dbb8520e0dca871b3712221ac1bd3de4999e1d0668d36a2bf5ad171f687ca932a2b180132c4d0040e9627be06247bd064e8e4b293e1459ca49a60313ed257362e7af345c612cd1b0caa216f1060dddf0e759156500d322ecb299a08a0a90f84077b72b4e11d3574ee4f7e4a891275cc32f62ab72deb2b426166b82e2e8c6102b6e879ff3af4b9a5cfd9c1ab29680db4e685a5687f2a3bfc0de495410411e7831ee04099e58842c52dea4f8da2d5c5551b60da7658e8bebc47275de87f367a0666a015952b0d6275e73cd33db8839729deba85204b0bd43f5ea46fea628ab38f3e142202740e9a33bf8d1a39d23ab083174d273f2d2c08849f17a8b59358a0a64a0c1589d2e2a0f67cefa0a52b551735c485ce6e4ca35d8955eff5de88fe195b8273dfe277395589d2dd43a399377e04774e5399b9a49344752577877031a47fc16a987570d869b71e05386f89df8bcc07274ac0c0517dce39f8d07cc48fbd67509c9d37742006df3b46c6c4db8a2a1e3657692af5854b5b62de4793e4ad9317f17c7aac172d27a06e9c79de71a838dd66e268ab22a987128e78ff32f70d5e84bdf7e9a53410649537e6e6856f95dd888ec24a0f20d5f6638a0f9da2ceaa1a826a1f2cc0f39f21ebaafaa27b5fada790d97d05be0c2fe05caaf0b2343fdf2cdf38df1cbaff072184f17a3a02c03510e4be0785ceb0f98903aed28808d00cda2d85b23a6e3ebc1f08176667a873ccb9af96ba9739bb449e96b56a6541bcc7e8e0af5a006d3186b65407c81680e5b4b5532607e7d600e42d20abc0b748537320f02a7c3eb92a46beaa7af2d8935d134eef51b8594ae2f0807690785c83d72c0a67bcf96a2696bba60e7c0ce0b56c8d685208b992936a28d8c5a628217e09fd86c996d5519a90a78f1b191229705e5133f49d925fed773a6b3627ddb66c63858a34fd3f45e64132d92f90da1dc791a15f345064bf16d57e8eed52ec85cbaf6a13f945598a43bbdcc1c0e267582ababf24d6134ba03f0cf9be83cc3dc66c7471ec24ee5fdd69f10ef4ae3061cc5dfbcb7a3d99baac6e4266ab525158d629d91510ea5fccea574cd5493249653fafa47a63eaaa26f34a0df4b7720f003090e10a3815f11f9eee338c7a21413b65abfe9f4850f491375b7fd09ea6fb7a6bc6fc1e542fa4b3ea5cdb39a6bef0691f9092b7cf652f653aebec134e0ded21e222e30ecda83339895c6ea0cfada5863887012a3fc12209c9bd36c075058f76996b1427c5bb1265feef636722e15fa17b310034a8cf7acc7cf8e565c9eeff8874a074d170b1ef33c90bc1edb076f2491cabdd31e49475e7e30b62bd6865e5b03b6883c15a21af12ac63eec2c63bc3a6bfff6affdcd91af513b429f71e2a57769103ccd01c89740c9518298e4d731dd4cd5357713e052aa4f9f2ff80b2dce694987a65aa02728fe195037010d4e3070d279b7dceafd9dd02e50fbce458b47a43af16b41859029e0b21ef04bc3a56c2bea3b70924eed1dfe2ab4e767804ae1e8435b35965110dd59e856b0f4622c6eee2434ade7b39a2a1c9146849250f010d4edf1cd9ed3c070d9e9ee3750284f9c1923d5fe9c8994c9a3054c79c8269354972c43f662e42d72b8e271bb880e2fd60999281ae9a6030283a8d7702d0346bcda0144893ca3c4b48b72ce3d7262349ca9c0d0611eaaeaeacbaa244d36231b475dcceae7974794051d021f2b4116a64f3d33d913b852ec1fefcea6c4829747f26cbc367325010d3b4aa2b80c63e49353b13559c8c60f7c02f28217a4d451cd2419e400ce1ffcbb61b4296be730171f7d60f871811cf3190a97fae4cd7daa2de7d140c5479cc98db3cd5ac3050206a93fa411c686cf6931c0c09b3bc48039a91d63be5e208adabe1904a36b0e6251dd0ce6d710b724f7919f337d3ea37e55f41d5721fe381d7dfad1eb4f289680aba288c39713dca0c8f4d9ee52bf2affe228243d576228010800568580a25d8aa6533a43014c4199b4b636ea5bd7f73b94609c9bb4ad9a2733db8ee85750bdd980f51186db94379a1747fd9f42f783b9343f621107bab0046fdb2165e64eb785f758e8e883cf3cbfe2e82e4bf4f018719051f13b9fa57e954e50c3215e87d6683e802cdbf2126faeb9d96648a37bb2844d0b031600a2a17e3913b3d22319aa44f287f4e86f4b59491a32fd048855768ca987d457e31d98ec7e6344fed8ba3447c6e88cf899fadc566c95c06682396ae2ae255cb89164b7c85486186578f33e6b23653469b9ffa4721c04ae911a71430a5db4bc468dc7ba018630096d296e332fe84e0dbd260b04d3327e6c442651f6a1e7070b12e7cd010acbc01c38e918234ae425f5f83adfee4bd28f298964fe9d51fe89102e9da7cf463ac4ff006db2d98df5d70ed7b725b1d1bde6084d8951eed2fb561d6c63dfbd1d04ee98d8c508350b699b664a6b61d9ee8fe6f1381cbdb906c89274937bb366720c3d6ba78c608ee78b5734ca3cf2712a47b125c75b7322c8878e8130928927d92f4025a4eca8ac3ee25424ab44f3c0c0e40d2a6deb785a438861982e1a84c8fdb73078b8052dba9c3f1702c032bc92d2b9f19ceec6fd6651f790c094653ff368769819e3e2ed33c321f77ac9eb62e89a087f7664b7d64376f66c0cf42971f4ac9f2b51c6e3996a30f7428472748f65b2dfa2906e93a5f6f940ce8fa8abc7eba9c4b0e88249230c675447456995ca1e2adcb09dec39a3ce68a1e5a262ed7df7782927b1d5a77e36c52db221e1d34ae37b6266e31d5b4be616da0a7fec4ec1435de29b9a2477fcd1f7316aae3c363345fe70a1fbc0bb2dba8f943a028cd31ce9f95bf9d09251bcd09e61870931138512d7615fe117b85bab27cb4ef7b1bf4305bc0cc2e80a16efeb362dea0cfd18bbce62ed7852ef1d776ece9c4784f5d7281229a491a3836b746c923c8a7922daddd21d6d5aa9ae185522d711171334de5881baa5f6e78bfb9971fd7706679f29d86f25f5ac6e2397c39859a5997bb4b96209705e38e608021fca2d4a62ae88d480d70c481eb3983f92a14f3b012cdf838b278d022bf2e13ae73088ee00a9375eaf7892af336b4382d82bd18cf56ef727cc99f81d30396d1acd91f45c5713d5685b25ccd7ecd153eae50203f770ba151f37bb797f1d32057bfb6761d8303555174f8e6875aedafed658499e4f1200fc3c774adcadd95ac44642e97889c06ec088e8527b7a7baac6385b963ff65bee8223fcc929980216cf67245c58586b79cf0b9f1d51046f694da27ff9e237620bc6449273ee027ea5f73caef5210aebf5f6939f270c0017a8accdafbeeb14eb34a73afe5452f27c81e5dd007d82afdd5d9c04f2412e8b3b0cd0dc3161fc7b700125aeeb6ee3bec30f473b17726533063b2357674521509ceb9d8a6cdfe55f80c93474831021b24d979b63d83d40c3fcc66e3c11c9e823afd9f7a16a89cb8d57000756824c287b67568824a3306c204583f7bba04215a8cc907226732a8b138a305a2f198bf45017d83f04f5ae208b29aab352cd18a29db9389cc9302552753573ed8861c6f2b08589e869cb11cbc60df8a29c462782e342d88244d69ff1e70add94d33ff64b351941c28251ec97dc446ce38daafc600ca60aa433bf74d7490a298e565abc2506a7ad10005827f00fe92ead0830f0b2f04477112b32e3cca4145ddf99fd3eea6981b89d323656dd3aa576aa5448baa82a794c37e75a086534d97c804d1194f590458f51c2d92afbf5097239467ffba680008f1140bdc3d4bfa281083b57a3884c91e55a47c9be706125d841c58b7c4d6398a3d8fe00d0f4c1f1b488cf2a051f12964a42d284d6ee356f303904bf993d3f675cd080c040b0f5693ca6bbd271533ad7e0d176bc939d848fb168972f2166c4928913ff521a4f443a4113f0c418e320484abfdbac16a28fa89ec4b1b0c41a8dc2e33182632971f23cbf9d2b627e5a692c149a565cb57d04ba8ffc88a38d63c919d71c1835f1273d7028ae90c76f5fa75f61d7eea0c8d9fa28b9ff287380957d7a634a2b85c24ad572dc44135f9c8fdae22f69c3d64313005ac59e6a113c8ede58cc5e079f23979c0f9277cca556cc415242710656d5d95c34ea177067a5c760e0f8dea04743c474d3d5c9b8636f08f8e5ac177a3f5a6837edddcf62f334455a7e6861ee545b3173e55a0696b8f72658ba56ad6ef30480577f6a7b6908243e901231011d3107636efb098897d5af47919a1e58c51c76ce7a81b92ec075fba419556c1540e4e8f5294fdfbf5739d00ac5e73b7314b54d5b3e1f75bc3dff8021e33d87d3dbae5b7a21918e2f82333361c3c096bbc1768bd340b8dfaa940e8f3accc1b6215df9bb812800189a29b89b1413e604181ee0b40723555c29dc7825e54d70e833f0c4d89887b74d9cfde23efecc9de5f201ec7fbc24e02e95c7d92268d9bc40be49a711c5d24e615f206fdd24e1f0f94b094642ee36ed6ca5d704ba9a09518658fbe31f9d467cbf0713a5695ce107a9e9df9ac866c84cbc0a0af126f90cb3602713248ae63d470400e73c0a87624e28ba6da2ce3c8358ad0dc8af4851610fb0c9ba5c733f2b176bb558c09f935b32e1ba4eb8cb81b3f10074f80d0c3683b35458b5fdb0dca4d6900ad093a5d50d5c2b9cd707851416aa491943b48fdc29ecbd9393c437a51b629181ff60da82663bade1bb39660c3a10ee410a491343f7869ac954731b99b1c111e3c52f5e9d5ba3a92b02f5e0fd35a38711955744e6ef6b60e8c5031478ca1145ae12c076f5e2a1ac9bdd151c64d1c68d5b3846a124fe5901d86ebefeb3c23420dbfd9aa548ac8886c481598641993086f0c44af02e6cd1525f64fb6d422054c5ca719211d8b8ec5fc66ae83d0b4f76cf41fbe6ab737b38757f4b9804a40cfa7cbab24bf61970677ad0c88437bf426b205da43315413786a16cb41b28da18aff93bbf6c406172c93d65c261daba9013495d36ae60493f3c64ecdf3e4f7a6a9bfc82ddc2e23892362b69ddad1b0708a7e948cc1126a9f9ad28047d31d053198b724453e4b1757cda3a0401895f1726e62b311558f2e6ac3bcf6bb52513f5335999d2ee4a801ff1d642136b579b675286298501d42ec1a672c104175fd96b6c233027bae6a35c3d87a47213ccb28acec4a24f0160dfe58606711caad26dbf6aa0b27eee2506b363ddb8dd1195d8dae166d547598f9c009731b49bf03c0b2b13babba64b4dc31ba42a96f3de0bda172379c8a5f5210801d530e788ea8b74feda40f057d042d00fc5ce03a1cd1aa2e8e30d992207f27e92abd193739846ae112c97d2c549e44726f52d81d1581ccb48fd6ab2d534f62b5143f782cd36a7cc15db637394887194226ef1a0b89b780bbff9283475da6f444f9211be24e699c9e742287c437497d38095e4dbaf69b3d2e0144ae5a02bec1cb258e96adea9fd46a82ea4c89f74e2f528c61614e62bfb4bfa5f6d692c3a936aa37ae803781e1a9be57c947c6fdb36f41b76a7753c56850ddd481daad0d86c334498f941866c326a8baf675ddd7a7d403cdc667970a4e688c15c15938697391a62d01a856c62c21a1cb1aa407e955529c3680ebe480b529d247dc92227fb27e45829586855111454e015c876fd02a28053250f5a5ad384463620cae4d3652046a2fb065cef2be2225fdad657681a6ae228ffa3bbe5b7c43ff187fedcc74e0b5b442b37d6482f6951796b096f875f67863b9093d7405cd037567e1271aa542783527d276ef903dc1a1c0d5b634e796f946384619e70b2d43343f52c6c18b7d2a6905ca59d119121041bc53eb1c5e5953854ed384ef0428a9928618d8a75e29b246ed4ce55edb70b94f7e5b14837fad01ea60aa022b49ab0240f0709381fb200f4c1225f490a25a0238ee18ab360a107581946cd28bfda729c6bfd59028ab0892573dc2314f3b90bc2a70ddf50dfddd2e0c45bef5f046f0ee8a74b6360688aac8fe54e88f98a8a42fe312ed8f944158bab8ed8c39d5d20fb91f3e04a165a9194a18d9b17243247a6d5a11aa26e869a3a364e4f1b2f73efc94d247e2505a30c0328b3c4d30c69c4abf1f00e60a0951a2380f12520235e704d1e49c3100491ddf6e19815f022f942a9296adc9352a7d43ded7d84a8fc812b0a566719212681c7d7159a99779ab2b680d8836edb007f020ac26d57461a4a7f4f3ea3430b9335281d321d87d43bb0917844ee7e53582216936211ce8345eeee0a16c4808c61f124a47935ba16db68cc063f2455ec7acb1516c930e7c254380a8d0e0d5a969c1c887b22394f01f15ea99d90cf6037ff03307723269cd27f326e4715028f2418f18b7822ad7cac6a6acf15d3b9fd466b13fe5655e405c0e75d6dc556414e7d854457f8963b66b88c41d6f8f3a0de401c176c789f8a04184ca625f68746502190ce474d6cfc7e28d45d3a090623dd1d57236c99815da0ad965afc649d1ed1a0be676c64d19ce18ee1c804723fc0c3ed2da13960dd3be6ac0433bb550b7e205d7b0c9cbfa5efe17dc922c5733b58fd4564820ee574a8b4cfa1309cb6e5ce2d8e0368e3726643454b65e87c8b9d5dd28a855a0ee0435d00e4d48a8f6addb9335daec16d0276dc29c64257276d06f15b53fdf3dc6f808b1fa88b793dd1b9ebdf9e5bb544fae4d709931975be42e41c3f60074001031124dd708ea0734749014b9ea2f24465085e79baf617499309c0621495c4d250e025cf99e22dafeed1beae692963c3ba128526696a861eeaae50b2a36b605748f1bbbfec483eefa10065f6489efb8ec01b1702e7dd6b0cf56ab18498fa6184db4ccc0b3918ca0c9528dfcedaac42f329425b7a031c1ff16f6448d51fbce55ec31f9317ddb870980a5d9b3beba1fda601176d4b51225be4099676f4025634379c56690faddb06de7ccf0347e2f36553a11159db3f7c644976c48423b69f222ef82867c2b452a68f79c21ffe6542af9f0d3c6f9bab755bd344b017c6b016b1bd183bf1b904808038e510576f7bb0ac41dab8ff46639c5abcbd9f879082e9699468e26373dbba1d39f35ceb8863a59e871c842693ad2e4c9af029662fc0cbc772aa732a7ef71a83a71a0ef3523ec678bcd58fc8e1c5d0a912d2f7e062896b14ab790b374b7cb2be45573922382629de04510115e861fc49cd4ab92da508bd9a7ae99585316261518ca023544806482171923c1200d9601e2027cbbdc1b33a37a00df5e96bdd4f99b1ac02410fd98508f801666f49328f9c966c8d247db1db865b4fa24dc8626538ba170a7ae0b26bc55ef939e79b91a84ce21b3177b00bc747f4d13bd02124ba565be51995fab52cbc4c759b42b21b6bbe11986f1d86c12b89119bf84a1de00e8f3d5a3b2419abda1b5277d83310cadc65baa8ba9160bfc5deb1a313bc35c0dad7fa83ecacbef7058ee85cedfd5773b9299afddcd95f1e304b6f76c2ef4e20d6d548fb0e08326dabc57f72dc4ce4095fe14da8c00527cbdd2201de084679738827862deda58ad19b2ee890b9630e2227058c638a8cb32291a39837084b86a35901e2521fa7ec3357aa241c33b11473e5c6ba58f266ac472cc479562f5ba612c7504b0008231c485f1126ef3e8ad2788494302daa5dabc3d01572619eb136dedf90768204753b3fa9cce627d057295c5abd4a2171463c38aef7a4b29790cdb4b780b463a53aa7b925485600250bcab48ff714b5fc406330e96ad08e3add847adabcde47be4b7750876ee920f3010ec47a15671c15c2285f73230cd8206eb136693f6ca0e861dc5a45ab12dd806e1bf1ba28edc427259d99e4b75ed05483ced427e5388c48c5d125023064ee9f4307d26e8aa776fade8207510190028df6ce2341a16632419f708ded43e86ccda0da2d266b15b46628329a5d6d5020cb70d0df40f2d372df18b3546e7180867914265ec65567a44f827e01022d68f5eb41d05e32045aa85e468ba382e6fe46b327d8eb5e539c28d4e3ca7bdf262ff2511bc47bda4e3104474b39a6fca24e3bc55c113089b7e1338ff4958cf5aa2ee137c757868f180f86533494960388220d1bd123a543454b927eadc837e87d921cf3ee9e38ce539ff6ba81a7607254fc2b7bfb1991ece48220124428f1b166b06407324d5d5d67f0075ce1914ed472333630555ca7fc048b804809c36f88e9ab4cff85da118a443f2231e114cb8dbb9e89fd82a6c97e0863df5dfd2e0a884017ed6cd9dd78805398bd27a73151425be8b055495555cda430c8a354750e382545d26a4de72e616d752dce657673390b21cd8b20775dba6bdb596f7b5dde2632c3b103be02515b30350fa5eb8e2a71b30052e2643f4a9ced0878611b17fd1df208b23a49ada8557c702f3cea8bb1469542512ae94b6ababc2ec94b46ee425041f99db8d4f401b441013818b7a97fa824a2225cafaeaaeb3b332f80c4e336cf8c02b0d1a2d94627ef29e647984de1fcdeda761d7eccacba2b6768584d45d0cebdff46671e3d63d15f01419b683b92e2e9a3c454f1f1ed4704c2ccc815b767376437f074fb5e3b83737d3130546d5aed033a7498a8a02e3bd01325e107a673af5111eb78bb95a68bd628bef7014f8c0fbf9c56d34fcbe022d0157197c4c31e969d4ee94805dcbddbbcccbaa2f8406dfd7b5c8bf238e3fa6008cb309a6d96f1ea7b32e5819fe3af50714734b97e4920730f826b8f2473d599eca15f8e9f2aad3e1b8ba0ab977a472897b3b7222adb901092ef9ed3aa59456b9eee8a8185eed806b834308f3be42dda5fdcfd02e46aeb5a7a416867ecdfda0072097457b8299f367d28199ef493650e9089005fba9abec888e0cc909fa22ae2938ec0a331af128a9c7ce9ca74b7d24da8be07189370fbd9f6b7098d77bd27509d24946ea532ad5ddf56a564f3897c39efe2979dc6f74617e7ea845aeeac9ec3fe1e51916ac8c18428dd5a67d819a553a68c446a58a35b35ff57195d9e506de5051f3f87b4916fe3d8428fa03e27688e480e610bc6273d48a7188bc945535dadf69e009326a7c72349ce7847f04d724ee639c66a1757ace574c094fcc8bb958767bb91cc44b2f2ac38e2ca2d356f4603be748b62c9252db84d0c9e757c21e472cd2dda3eefe8d9d283330c6cc99202d80047c99683646033f9cedea632466f652559a5511abdd1d0162aa4baca6345c14d6533a2d817e4382b9f2faea1e3393aa81202385aac85f3b3214678d1253c0213a9aab47871797343f65ed663e7b754d4183d81660041c4d5a84223c117b35c91236263cab23bef90c9c4964c4b3baeca5f69b504c5af586a3e9f01e600d01240f730c1121ee8a8a7d0be38e175421398aab5eb17fd3ed3c75395874c1ae2341347c8310c4f3102066c1e57dbc953f412ea2220387e43b2930b5666dd1bf8e48ed94444d22c75491ff693c6934e774d0a89fb1a503d0ce0116c09a3623db7eac8bc46f2cba28330db29057eb96b934fc79fc8da624a28c1324c58b30cfbdebb44e88d0862c9d7ead3d45214da246821997c4203f573a25647949005f0c5e5e7fd486b756af3dc0e39fbb1cb478fa6449f702db34e5da4416d8ef575e5c4c08fe3158de4afe7150eee641dc901f4e1f01191a92bf614c5609e2cc14cd4e98746884c9fcdde20f386762f9aeaa36c632134a04992797b28e8a56b7dd6566a6d2b5e7dc9307f436518a6d71586e46552ff16fa89d3716dcf7c9145f57dcec994f5e72c47301510b44991a5e2ad056cc3858d5dd79b5d3fbee4d740e788b913c2a9d26a24078ec431ec1fe537927260605d00a886f1fb2393f4b60cb35b3407300ef62557d845d4d136171b3bc18e288c6862bda7f51827bc3849afbec7d78b3c09ff34e40e85eb1017f57331b1de520fa382814c6421cb60950b38b0b25d201d42015eb93dd02bd387c535e9a7bd820317becca4b054916653bc6b614fff055f53f9112445b08793f23b745a9ea277810af740a5e2e99e58c966924a19ad9c75d2da1cd0a01ca68e2ae4964ff5ec610ce35b9e190ef091a639cf5c36173ba79dcf73c37cd06f083fb78a8118d5659db4de7bac7f38d5d5b16a30fc68a4bd6a1597171de72d7770ecd15a2bd730658a36e399adbc216754a4cbcf9e22232ff6f23bea37bb15fdadaaaba27bc2559d5e314c80286029234e2d208f2bce206b65a95c37ab8b1b437de6e6cf566fa8f7be22bad3baa8e64dc10bfce20909e397a4ccc955709b071fe1e7ca49cedbd5874b8dac57420542f534f23efb5bc1e6414e5434a6f1d8886c0c878526abd12ca6aae370ed1bcf2bf2efbe39765797c72c7268a32fbfa9001c1ccc11439e36ca53f5a9be9793a556bbe509a8e658371af1a1255d73ec162b54c8e2f8fc8f414b0f1536149d4ed334730a1b01a16a0ca7e8317c1b75d31e289b0ea0ad2d1fc28a75d70df0503b1825e94e6970c0d27fe9bc940c587e06ec64a5cc54dc0ba51f9d9025e9c22f9789d36ae0e591f3ba7b18615ffd1661dc53a1adb2fb5b28d4167c4ff749af30df7424f656a57fda862eed244e65e2276c0ab9d0251a63e580256176531cd06d84be0d92f7f1f215bd93c2b284dce91e152255926b3886d6e9863f7deb1d2320638c503c980b49687f60371af0789e0dd3c353a49b02e8e4a004f9dc385d6b54755b6e4c7ec28db53e4810012b3be56f8d624be0dcb87088f5fb611c286a6ba5acb5daecce66e4cc750792a05f0c05c7e1c76fa4d28023fbc65d58a7e508c8c173fbec071a218c83824f90fe15d9c3410bde6cd6a33d3eb84d3a9a3c163656fd5aa815f3e854a7a8521e33ec22d8692ceaa684041544db61bb36be34a1bf0a8d1b0a4c6b1efa2730401a8faa410337e12a068ca149b1a6bf9ec58b3d4f64ef3048dc130009e88217ab4c644bda4d5767f9e109842cc2a1800828397e38d97d16ccc2e49e6d512bb6c4128eb4b4a7eaa19dfc6d3ec635dc1a2e3aa47241715e8eef468c9c7f959b98f0088cca5c2be1ab1a610ef30340bbba19dfc6eaf0a20c907868b43728f4c2cab238e974a08b47e3fac118573787590c5b5314abedcf4dafa9ac846a77692d252a644ecab688a344e8d014867fbdfa2d4ddaf1e761699122a8d743ed12fc85603962f3b7f52bfbdc18a688b454f4b8a3674a77addf183bd8ccf189fc33c67b1d639f41c9a42cdb7d8db1fcb462d126c59814a834f47baf041f0b8137c5c27f2180f944c40757db4e563995d288583ef9806f8223e3d4819ba18446d029f2264eb21aaeeaa0925e8ed2ac96c8bbe545f2f87b4eae072bf005ebd4c6e4f507f9a63957301f4afcdc28b339b7a28d818866804c5fd51ec033a0b0d80a86aeb99e993f8c6882e0b7d08adbf8ceca724fb73666ffb1abeec5cccb3591563ca92878af8eaedbc21e5222f03f7350122658a1ea814ea1ffc97931a45be492bba87b614e86e1d8b15191b18f79b72935ebcbd20d92d6c6a24fb3186841a3fdc8864721378b824c4e6f1d57ae8ca4d9162898e890b03fc57d8f6422ba4055eb9e9903c5660c91dfcfe8a75da22b1dfdc1836b5ae55b87221fc890f4b42269a1e369bf205f29e8ecfb16c2ebdc1fa4b84e0b60dc1bd5541a827327589bedc90d6d7bb9085a3970b8e7f943a3f7258e432d8364d38b6072a2b242807a170449ebe3a47d01579c51edda2b0b68e813d2f81ee47407918f2cb353d2533ba209befa66a9d60cde5daadf1add80ade6e2f9c5c85135f3e14bf7317be02324093be735e2907767a7a6529ab19c4b8da35b62da54fc9aa3c5fc5605fb34dfae73c37d26a0ed602208ee78eb213ad898fcd1ea1a09c23814c4d9c28f11400ff78076bac25c7b86ce5dfeac24551c9cc16ff5283266a5d143843518e8e5cf39263ae53e289bad0e32629b4047bae0cea5d041661432e53dd0d265cbba1b5c4424f5c47c56cf86071d4409afc2950e12ab41fa2960e22d2b8acf734ccc11e3ac3e0369b5ea6a311a14afc3f0655eec073cd68f048b8a823a70344b54f3bc0b118e3d5520d2dc77e9fe7890c38bb7e41dac4619df0cf8a4c0fe618d67fd0e9e3c8f8c338a329612234cbc2350a11b2f94000eb3508c8db48cc29a5be4ece05a3429dc06bc7636d1cfc32bd1cbd0cfd12bb75adb53063c22139936845df177fe8d789a05c458b8e7f126f193904b216cb475a9f70b23d6a85d1ec93dd9017e000780e7dbca39ef9a3203f29d8b6c6ac64db8ce55c88aea4333b0d767484bacd17132d4c69b8c78659523fd70e65a152ddeb196dba2f9be8a6e1c91a110965c32f893f8c3a1f96e21f679f9e45675dbcfda41fc31bafd5abced8d87c02887abe32c0e19b04f9dcea418f2c43f53c1666cae454503e0aaf9517e844b059f9b0841a8ea2ff0278b30253f1b7ffcf67675902f414e575ba9f908f5db38e47482f5eccf8d7902506baf76622b3860fdf08eb6f40cc253a555da7783062323667bb69d40024a1b1c155695c543b0c75beb0016d46bab7b6081f12d357795f13a575616cd88dd5fb0e53e4a7343a11fa466e1e25bd81d9099b089f9e6dc1353f8300bf4d97a89e02b1aa8b8b3fa6a1f57eba02a8e223d8317d73671c4fb3db92ab3933f4129912975b7c79c37cfafacf6e5796a5fe12cad45758183a11246f1373e38823fd1e2b3d8198049976ae141c68eb68ceb4f89fcb6fbbad672b8dc500a75eb5dbcef08dc085bcdb6d6a4fe06343cf91d918655ca79043b30af7a74b89650015089f189fbcfca389f7645872450d91d36878ce453735830420f19b093d4937e3dbd6e309b3664c08fa72c7f1182a8ae34056a40b37ac40cc02986f31a9e46168b0c86e0e2fef35fde4ab271f6fb24c1799827d2a6b7aceed3323a39fa29099408d57bd5abcf7bd6ed689145d548f5407dfc0f81558a715679807be4d09ddc170d1de4442e2d3b8f0be697733bec334c949f1a20725137d701630609020822f768607cad686a36607a80bef458769f7a41a997353e404178bc125f21bc4289cabc5a06bd447f0a8853a785be03e0ae6c695ca33fc1524916f059901368e2877ad8bce605378bfa1d17878493e27019c14094da280904525f10d404eec9600a8721879939983cb1834ddf5292f8a5218c296cfdb0cd67c8d16f78664841a304c54031af0f9b4b9342d22f314b20f2671317a0d3f31b31fa1472ba2beebda39fbd2efaacb1674ec5e2076f08b96c30d3c7f6597fd64b17c192d3529e84f90a50184bf0f31a216e8467c585d4e0f99cbb063be053de6efde5b80eb899432611d665bb60ef8a206eb8a10427d4367da941f7f219c50eb3ee0ca13aa1caa21a25bd2fd3a280562de9e4e2919529d0555c1e7268b7d40b4f0387c843cdbef1c0570a66839cc7a7b29c2ffc8026f4803012785818c594bc8f52404e788c3308c209ce1f0238b9d6cc0d91dc701a098cb28b22f7aa4a054759dd0af1dd96a00f98994d5d028604f39ae29bc3f6512ce016839df165dc3acbb59adf5792167d74b158e0f3029b3518f04b02e2a14015ce8b1b02937b7bf5572c66f0f4bc8b4fe127c1b03086363e37a2a9c3e3ab2f4d2a627ed5ac9be7e9c71b8c75fb88062746c06a3077f84217f196cc7463d3658aaf5e8a9ce5b1ea1424de4c735e514f6c4f34b2f38f951aee7923f10a6bc220080f0f43ee3499b6ece8b1586d80d483b8626172aedd2d3ce76e90f320202bbf8cd189ae489a228a0b8ff2e4344199dd99425b89a23242a79f0011d59e2f2ab1bce7af2df2b0b20a35f6f20dc9becb135957705c22d4bb26006d3b81dec887242eac19b0070381bb1b2c7ab9c85699f1a4922817dc439302845f810181f47c35ccd143ece1df18962c9b52cb2cadc567f123cd40c1a294005a92da7b5f03c12a09dc2de5c414587ecca48ce8ae0612226919be7a8db50c04aa3f7f16b8fd8e7cacbb91e2567af0de405e9114022ccb04da806a6dfa1b8176433ed7f981beca1dd74dd48e471db3578d0d6ed14c873b6779bc2e081d72e5f91ece56d7fa94a114ad60efc643c4d8f409a5e920023f65443c83e9792f557d656882f05c1cf55916a8c1f8a6698cdcf4c6d913839f47f105334dd0f35b7ec273847dc20a890f6615754d86963b1b359fd48d1c42b64ca4a4239cb86c3a7d4c825443d0e4d60219c412404ffd3a754dd9fa64de4feebcc28e82a99530036ea3cd7bff0ce36cbc8ab54a9dd775812327a99a464580ed46a22b48719c19a581a29454d32326e98f322bd12503d5e8bf9b0eab7e203d060cfd549986843d5332033227503f36e8c5bd001e604665f5c565aa01b23b44191e9d6015f450c38ac5e4157c87e663d2058bb32490a447e87e3db4c9567bd75d6f9eb8fcc3ffece2406f09aaaa4255aa320e6b6fbdf486ff6ec331bddc10f110a75c13ce120d719b0040ee1eaa6aa02e06e1f4794ff30e1308bc1fb5d67059789a109d1e78efdf643e22833cb685ed4a3f8d52cc73549f1697bab3d0e9484e40bf895a48b2dd2b5a14574a2d1b1cd6a93b2735b4be44b2d88475ee152a76f03f27a6c6725b42a5e2d1c486c6634783a38cfcb339e378cbaf6649b6014583b4a0d4d867c52e6b1dd4700d5474c377d2f87e8013ccff31be64bbd6b1f34d145a0ce68738789b724fbb82ef5a47954f02e407fd36ba7d9ffd7464a6c733b535ffc2482b2ba06f177f1a1ca3f8920b7f38648e2dfb0079764a042082b90e82b3ec1e782808c4177a3efaed531f74b70e3fc271c420bd8e23d936575b07df1d912b68ec39c4cddfcba3c15e931871b86799a6c6b64a2b7e52e839ec910e2a2a740c3eb4954d90a3aa338a11eeea6f7e37350c073e799c2f0115454391b9613749d347c06c3636ebe00d3c916a23be3e9426a01fb638774f5b44cb200896ed6de7e6942a944338e0a505d7071cb2477bd67ebc585598666c5710185a1bb91568759e291ebf3695892e99b801b9b45f9bbdc5f6cc775c7dd342ef9ca7c8d94268e4514525bdcbccb381e883da96b6edf84641aa4c0e17c1ff301052c568c47ed12b74edabcc14716fe1413fe6441aee33c2eb581b5b9c2beafe3cb18b19617a993c53f211347969bb9855b4e76f1c62fe19601dffa4764a853acabfeb17a85e84267a354dce1b0f4525281a447fd153a6702f8d469a52a14316b38e6fe1760f3b584f73a97e22d71c9fc3ced36cba5c9990ec3fd6dea4875e67c912dbd7a778bbea162e334fd5ab649e67b8c00eb8ccf3ccc5f277b77efff7fe0b5bae9625774a888c1319274e5e14dc6415dacaf5bed43d6e68e3e3e756fd36f9d68bae79193a8bd1feeb11cbcd1479b1a944d6e2d57c93589e253ea047bd0be5acd383aa554fcb7d240851e1d84152715ef3a6073fba335eda6abeeb0df988f5e52797a337114f29e6c3cfd89934d6bb30f6379436dad6853851f77b9a38c6a7fb8445617f0f170e7e43be401fbdf68ba219ec33e1824f1ba3916326c1d487cbfb8afc17f5fc8720548c74bc361d3cd75e2cb1770694ad5b6f361c9ffa058d69c9bf85e8f3d087472a09e08f43f5d6eea94945c9d8de3060dd4ddc32fd5b164cd3bc5fae1e255b23dafbcb2ade26cad5b17be3e7fce221878a1b91ea599834b0b387b002cb973b157fbe09afa45c43545858713ffc928cd4401f4302a23f74d6d40cab6389865012e5e9d9158a3372eade48799e6d9a69f5b383566bad44a38c4a42152a5328ac6896aa4dd78a646eca2cbc870a822eb640aa878aced3994bbaaa88fe6ad97a42f413ec8aa7dbb88e09245ede93b3116692cb10944eeaea62cb3be6fd2dac4b2d0562cf6fadd5d0aebb65444eb70593c3132c0058d8e2adb9fae7aefda2e7ce8b4138bc4defe819c526afcca99b73c9afec9f89e4ea77c2798c5dcf02a4d04cbafa1345dbafe61f0ce1588f53a865025f1c76b0844b9fdea8525d6c092ca4db68b70ba870577ef08977208dd1bb1d5808ec4fdd232ba9576bb10877de05bb2a4df91e381a61fa6b0a7a7097c76c7e2eed13e85a17b11274fb3a89606888e35da7b4cca6fc263ba14a9c570c1dd5df569c2c4d289268573f0173c78c23a3653aaf247f636777bd07ed2206d6a2705601f647a736c44fd0a373f9e9663f762d8e83848869f6af49a069ea20f36178b86e220d5aabca1d0ad6e5eb5043fc96ec9a1b034107efd8c4c2d4f81036e547c4ee1fe32161c236bc915bc7021340c5aa5c4d275f55d64c1b9c6bd39faf25b82f0707971c59cdee236ddb321e0421f7d53f5d6875d9661fd6c4836269868f45106e98a149f0b1064e1e494d4814ae473bc56caf0c6d8d900ab3608831f9b55f7e3f6c41b0d21d52bf6539095affd5e0842f4b42cc46bfbbd6f0abcd62a8641f429f11cfc1e7e0bbcc10e546ef1911d9a7103bf730facf56f425ec31528a5a333df7f6f4dba1e4973d07a7b5b8b55aab064e9be0776c13694410d8ce94d12e10d400911ad65fec7e19c5f93bd678f41beff7b3a6e1cfdccc949be4d749ce335cf3d0d605064c16169a565958b95de869f12129e266cf2515370984d033666fd7cb4a5d4b97db330fdb34abb309b29dd583e7b1dbd06d629b11f02e20b3d947eb8b87fb7b668be2f76497196a16146660f8b42624b949e11f2ef5a4d2f7b780da7309a964fe2af3bedd1a6c14cabe34c21fa3cfe265a85b96a22c629a54ed58cf8284f3b4c7331e666132b3653e5787b8c73b2fda05aae9363d143286791ea4df28d2733f44d87e82f02abc59fe92488564d896b7d548a0aca934794ad406ff1c9be0295cd1fe0228a7f38e5a45310db3ca0833ce90874a8d627dfb9a4b2fa781765012b9d2beb676eadc49ea6d1ceb38c98e1cae3f046c96cfe616e9183df549b67bc50d5786b01bddf548d18a25fdcaf6a2a89fbd4852e5b378901bcdfdb9d526a2f718e75899a21ff387c3e4c4ad2bf709e6632ae94ca3199ac96885c5e79fd8d4e00b6fdfc680417b16ca327249c1a672e6bdb18c25471ab21f310e9a2493a2966ab2eae0789b109237c87ee4fd8a0c1e66bfcad6a50a47a68ebbef22a3dfdf14520522d0a7136be284129d57cf06eb5ef28eec5c37db85427bb51a7c9597897b79b242b313a078f775dd6add1dffb10cd5134b52b7314b5e62879d87fcfb3899c68ceab96290ddd1d2b0918886b046304721fe797667f8a8b9f7beb161c01ba24ebf1e2e1e6f807e059c6f185c7d83e68a73bbdabfe2484a8f69fccae2c44832b0e34811875bd0f6b784bf6485291c6013e8f26c6452e43b7faba3123e0b792cf482d8262620ce207c45aa9ec883aadd415cdd91597a5b0d1bdb2047af0d73890ca1c25b03f571fbcd221fbd01638d8d589dbd41822911e8e370d9e0416ba6b86e3e0612d7eca718c71ff2cb56611aa3e1b6fb616f082c03c4342e8695fe3a340d956d5978ddcd1168f7a48f3d9f7b1a35ecaf9296f7c31de3c4200be456b92f7cd70f5cf2467ed52398b4787e31b19844d2b0669eb5fc30d10550ba817fc76795f21fc34f7a3d20745e0cdd5c8d296ab45eddb7e74593d2fc57f69937083cb14f665a1ef87d09a58a2447cdf86c2f3e4ff60e26e22a9304f59b9a92dde5f8a8f0cbf52c784738305e21d293b81a6b14e31702c7f4e933e376d95a7b639796c614cb0e1554ce02af40191dd1288336117472e6e1d2623bf7697f49ba822adff62b33212abf97f95cc9a229d16f62165a3d2b8b5ecd208723ed86b7d06987215be082397c48f33df859e9d8b9e015d188142daf720ce06d199763bdc606eaa138e3d8ada23b3bb39903710308927e05d6154699bcbe6d0e1d8a52eaefac9e51f999e610a8b26356c939371e40daca9d6cf25522625506e8f353f9d6824588a0f27bc8489df848670893b89430f7c77fb62c04249c1596ca84d32af1b8288d16fecaebd74042f77a06e17994826112233a6c7ea9e22476beb8874f2d95906b3e6c65ad248d12b07dfb34721d0d5b2e705512f7a85399dec7a69970c60aac5445356f199642671423eea4df6f3de696ad296ccb43edfb97e4e3fa3d54571ad108ad6559c93687092e67aa0c7cbdb0ccaf2e2d18689a2498145692132e5ca75f29478cfb13462924e7609e16e1197e233d7d10a7dac39dfd2a96762370c84d2d9df0230ebe2d3b6f632422581add9a30540708caa84ffc342390c7384cff5825bbfc995fc5df1583a0b2eb1a6621d7c179c84ecba764ee991126a636a365ecd78404aed744b3ad0b8abf61bd3257cce83f5aa453fceb2f9c1286ab90a056900db04b3049bb5dec05d11f7c73ba0efbc0a8ff12d8eb5148eeac01fcfcae2ddf0e69bd055694a85f62d17e5a39a371464edf7f02affe65dd96bb58745dd89e6ed8de2b037a07f96531cf43e27818eb2f4be12556317887d24933054b0b0dacaca06290dbf735128a7ed719aa366af46d4146095ea3cc948fedd16a7cf240cb13ee4b5365b31e7479a23d03ff1cfe87a0af295ba016a640a4bc5c5152aeb95b4e69395c5ca4ef077164f9ab2e943fa3d28f3d1bff1b4be80f0fbbe0fb49d4767b8f5d6ca47d80fb23e12b430743dbe69adeea33232b3b7799bffca53e6747d156f4ab8cadfa85a874ca9f96648c9d72654cdc3bbb7cac9d6eee762bda990b401f5ec9dee78e854c0b91cb41316132207122dd2b5f3625cbc79cc2121baefe1dfb3aea46b01bbf61784f1abdee3d72a676e2ac65ce990b1e4efe657d9c97900662799241d6cc7bce3d8c1086b79c69f6610136bb258e60e8fa365996432cee8896a3abba077f96c52f971fbd92d490eb1e7250d14d858016142fb5ad9e7296da81023b0793310ad67c7f9e3f87021aae12a6953138692f0e9342c85dcf231b30505622b75dcfc31abe8ae33dde60364794cd4cbd58c21b36417c82fd4ed438ad2af8e85802858f07cac30c625d8a9eae59a1a514851d767171d81bd5d2d9df94129a9451cfcf4b0cd889d113dbe3d6c526d17e488729b6a5ba1630326be8dad9bbae9b94e17047fa2ca7d37c0ae675cf675b80f09191033966fbe5834ffbec51eb2fd9f0266da07186c37a96cd44ac2f0f2261e8c231774bdbce0ed7677ea47662f9ed627ef66de389bd0f2d0281f8e951dbc5b217b0fa7b959938456abfb5c980af254cb489eedd6c08817b6174ceb2322e94f01d38bbe16aa0af423cb0d29a612d6e2eed68122ef4c013c21110985e1cc8dde22fc4d879d8c1476b780139a4cc59011c32ab96fbee0b1c6d5251b18a42f8c9ae5c735332a2c677dbd74c04d93160d462d0b4105098a07f64060f0e7f66fb27c0deca49d01555e46dc4898ef68be9806c796b9d6a597695fb59c8243af4fea89a1d222ca275a1d0db78acd518bf1fb23b197e662b7a57d0699ecfe0f86e5d6495bfb0d2569c411c1cc3beb415cf66c1d3cd0ae924164b768e24b8e98b7b98a718db622914ee8db2287a28bd6e322331f20ed265492c9ff2c544ebca235e68dc556e577a0fb4988ecfc8ee724fb86839c4ca2e69495632d1490c34ecfe4a5707f5f3a9c628f820a6da27862d517c5e6377e356f605f8f51ad387983b985df9806573cd89cd253bf4ee66e554bf33a2dc4bc604425b2ab4fad3961ad160f9bcf4e7f40773881239aaf61db13b57b8eb7955974f814994d7f3a528272f33b4bf47f2dd87fc9825f47b342cf09bb41bf1a126cac4c0023f49177de85b3dd53f1095dbd472b3219e29ec924b03c5a470c892483db32c4b62e3f73e3fd1469fc3de4fbd9d21a76433e1464583559da4d4b5ea577b53c5e216eb63376717ee677ebb290f76834de0f90809f3556e8511dd09371f0c76ce8f30c53cfe89b46fa36ed7dcb5ec8f0f641a9bdc385d91e2627ed5b5506150f283f228b1bfb0c70d6b66848536179e14d4d6bb777c57165235257fd1194405e577e6d512412111faa098776f7f3ca0734a9f6271ce6a9d41dd65b4d1db7286392b852723f1b4d790f270981ad4e121f14171565bc6d9e93f5326cc841d732add7268eda19fc757563e1465af5c90020917652febeb3cd2856313093f859d62eef3d946bd47106ebc20765cd067abbd349748b4a1b4bd32cce27613a2272c8bb9f2806bfd6a24e895b1244f098854b7275c4b0a0f8adabcd8b693a8bd87e43e25aaa2963d2486b9181dac98e32a0cf333fa59f105e2d81bd054bd574cdd7024a4acfb2d3a9a862ea5ace21e7f1af45054fc850f56b1b1d83ab3a91b9ebc3b0cee105fb3f0d25a84dc0fd3d95a50fc84a029394579bc4e30bd049678295c41321be892112a2ec1356fd45c74f3d56987a9ecf2c99bb06d03e531964ec15b6b36f2c36437f16febed37f04227e5e43fd01d905d731bd42969f23214799029f4f8d3885e158c8b024737d685a805f77dd3ef13c4c64f256749ef3813c4d2388479f2c97a023a35908c9353af62db5b4ae4feda3352e7c55083ab9d89b624a44ce439ffa7c7f1c9eb68f026afc8400d731b7701f1b4e362d567c887940a75423459fc599fd8f7c862f938f3263e9f9af5fbe0572021f56cf2c31065a22450588a8d74240fcd43c38c280d09d25ddfb6b8a5f2aa1bf2a844213aeffa853f3685cffd4a0df3081daa14592f71bf67e96bad78ced9c6f7136d5f733d7e3cdf5060ce22a928b85f996fca474f443577fb7539dd3539ddd5e8755fe26db21d1b462cfae8b9384de3a891e8e4826721e9e35a2162e25403eb86a0e8d2dd05c833604031b1443d7ea674fa0a8b111341481237a81ff7a3f7a26c243e82a74dae98f06dc201eb7a7ff5dbfe57bfee44d66ac869e02614998b41636eea6daee10958343b41cce1098e9d5ec0c42b85b83e118545cb38e072f7cf7de7bffb01127c079d64c6d5310ec3e804a734af98809342136c40b75f0eb3086cc48218a9c3d00be4eb9d709c1b679a573ef3960c48438492e6b7dfea148eb54da5d6a79a13c068eda3afaec1fd16a7d8b8a4619a78bc28718bcb4ac68b4a9268b809cb0984ba47aefbd9042c766b3642e8a3d3d59a66209b2e4a86fa9e5f067759736ea2f713fff86150a1d7526fbf6af1c24d1ca819d36f7da01f29d9962dfbb12e4151dccbe0ea27603d23cd9a9f15d64fc7169f72b8863e119959421e26b4675fb8771aa6f0307cba216b1ffeba87a4bcfdd876672873692a686f93e2fb8a16bf051dea05ae3224eb0ec1d95196d6fd245bfcbea3727af3c43c8aa87c55b4213480ea15858d075516fd6f6a3e54bf3cee0829097da9e832246fc00247ed5949d3a6636f4897695c9294a77cd7db86edf8c1ef061eea46477549e3f0eb7a4d5c425652fa7eb3ccb5a99f5d9524ae21bfb7195fa43936f2795c79b1e25b6bfc799643317076f3451845eb29914d0ca8b25235f1b7cc55741ee401e2bf5f20524e090b946d7987a38a9f2b74d7ab372a3398919bc850b023d8f99943f67b7ff229791e4c954bd852384b1e38e6c1e7831a7bcbd575f01c8e686e6bddaf6b2d44581fcc9d0aaf66faf8d7dc416d9cd1c2673e42a8f3f3fb62f0a7026dc199c340d899c7895549fe4203592b2e89e82be0df7eaffbf3f6e1b2438abd157cc8d0fd00e1eb87ba5a88737c1922777cea01119da715b640f14e469f2129a37bd36af3954f0c62cd0c4b7d3eeedb8c923249d15aa8f266dad45051d227b0f5a9042689aafa27ec15b8df1fbee458568290d0f113b4ef43fd7a783cef08fcc3d15e4ed38a05cdd8adefa91725892ea5c645fc4cf9eba91a82ec8fa2a7362d54f46f63bded75c4e1a7a601a328f123dd67c301828333b7b0a57fd69be0d6855934edc7acc2a70db43399d2fa1df5e2513005ba65831777cb7f725d4c5d6803fa1e51bf1b0379721f2dbe9be76dd37c410442addb467ac7b6403ce6a47c0a6fb8d358fa2653de18a7593e0ece93843b35541905f755da1111c7f5c4a474174286410897b03119dc3a905fab3c681990d56ec98359c3ea6ee48dc4c8399d51afc669c5710eca56d935e3311b31ec7ddb4f74159a3ba83691d86e37c6e51f3dd31ccfc24589a9abfee25d4cff3c02aa497d5ffd824d4b48ad5acc5fefaefee80b985bb6e0d058a483436cf4cfac34b46b378f3e2e2ea75f4aff2025a71f1f9791243544db89092fcf73f49870f741d4437b09a03141ba6390a19020fd7dd5b3f9b3ee47a850fdb2f29f058f442714a041cd3e8f7fdac1b20c630325b0e16287a823d8e65799722cca57f2106c72212acf97b61c524ac65d8872ae258755d754eb57e6b539351abd3583f971e4d84f306d634d926c97b1fa245560393bc6df88a1aa3bd4dea59ca4493c39bf5d5afbe053b1fe9d4be96e8ee889715b7a1c2dbc9be7cffef3467e71dd2da4aed16d6726849ae3c8773acdf419207378d0afedf6e427a807db279fd29677d27b870becdaf66e5051d143783acf1bc5ce51e0f8809e810d6a30e64bbafdab9f1e00f60edc1a48ff6cb007780044006af00100700048e35fa8d2654d6418c1ae0149400e48e4bd2610f7588907bede087f772ddff41391fbd65fd1324bce5fa4753990aa56aa39ac08647b78f64e1c267c915b5e90b4d3e8f42659864a4dbcd0132eac5a5136b49b2b4c51dfd562885a25dc897e6b64cbb82595089cdfcc34f7af94346272d094007ec4e8cb03c2ce0ce33dcb8641245a173b1607e8e3b03884e7d9c7a7946252f820f70aab2bebb033e521cbae5f98a4452f5d2568170334a8893a71bef2db8913229a06af39602eb8994abe454fef464d3d0df88ea23798353d41931f1b68f1498923fbc20ff689a689ee922ee1ee5b31f4777244a7fce551a5d12d33becdabfb3862213609630b0661aa7b254e1c5d542e883cb565c9f10a356dcbd9a45e65f79e791f8508f213cb5d61592c38abb6cba266531da27f9bd8f1245b671ef427be83832ffb988ab816a2777f54ec569903d63ec6f0482c16a8b045584885019bb8d4e2fb1a49644c1fe6e8afb0aaec60add02ad050a75d25ef6d3980423bb2bd93d563e74fb1c59815dd5c8bcac816062f8f848460bd85620d668b7559377066346873a3d9b2691fc514285f42e2055fad49303613331f749b783cba6b3504a5b51c88f0dd435c741f58b9b484fada068ebb277ce39bfd75b7327094ed9a42e0633fb1bf0b096440f1568453735ec8c0dc949cabcaaee38eedcc73a34fc809082778b39e1f52e13d442276abcddbcba29163c350adfa6a2ab5aaa71a61d43e628898df51d7549210c5be6f47cff37ddffe0a82a1a9cb5e147bdf4db7aaeb992a28e520a6f4e60e1f1259ee82a5c2e8820e3eaec66e9685e626563d11fb79995f4690cdbae63a5b9cf3ee0945421ad2da4cdeba93cd7ef63a5bde93a773383bf9f1ee68129f25904dca243bcd9093b7a1273379c948dc23cfda744d86de4925aff47509bbd09e2e19439a6627e06c9266197bdfac7841ce4e6217b5565d72803f6f23018878b0feb36b809d23869de79c766c37edf0890f5fd2fb2167c910ea5b3a13171ff4dd1e15e13630f374f9ca18b6fa7c83e0e6c5ec13aa0148676d5fbcc1b08b57471b8da78ee4c6d6b3589a0fbae9361bbf83cd16c4ef1d4f864c71b13d1eb165b4be6db4bebd97be5d96be25d2be1dd2c92ef8819e78c5277f6d7eec44e6f50569e57d6b93b4e632bfb4e0f5d46d57ba8496629c3042b5aa1e0ff4c5933f463946c6a0901ea5206bb867aca90afe822e9984e81aaa0fb4967c32758f2a3a276f6a5b698cca57182d099b1bcf754d078630f7df6d1c962fbb377274c5b9d38f853c36a4527ef3e5db6f758b57b48af95fe7e30eb79d1be6124e509880cca28acdb17555570e3401a67e7674de3f86afa1988c610e902f75dcfcfc26ab457a311c3f9beb8450fbcb5363baffac713c2303372438b71bcc1f315cc531934871ecc70622fa53efc0bc3efa2634e4d84b1c8c0271539b0630e136df41c21189307099699e82297b9fda91b59080bc23920227b1bef7a624e93713b08a0d01d5fc9ad51c1663cb2d375426f29ac57205b4b4a520ff7df79ac2060454f9a76901dcdf49bd4b020614610d006f3f56231177affeacaaba65b2c24ceecfbb3e62351cad01f8efffa8e66dcc8efff6eff75c55fffedfffc7bffd9ffff66fcd310efff83ffeed1fffa8e6e9f88f2a1bdbe1f79ffff8776fcee763fef73f2bfef73b6b87fd3f86b99eff5a9867455f6ff3397dffa31db3bafccf7f9cdbf0dfbed991fde75f36bc4cf5ffc8b3bda488ffad8d38dbbb115daeff6af36af9612386f55fff3fffeaf96afe51a8deecf9fc99ec2867466208fe278303aef85fe1ff95efff19d72a0df3f7f5ff664b1c701bc52304509b0a57277fb05ae1905af1088d0775a570f71fff9feb0f6ef0f5fc27d6136ee60ff6677d253ff7df9c7f38fec4fcc1fff8ff607ffcffe4f588526ef6bff99a7f69d078d0577fe5e490bff3fc3dffc3f53747293ffbdff6dfebfef665d253fc9df30ffec7b6f8fbfe338f252efc5b5f293f772836a62f727fedf34f4c2c7961f397b6bf72fcb5df546a8a3f9a7299ab7399ebff601f890bff8ef9b38f3f7c7fec3ff90bd9eb13c94bfed6fe5f3d9bff99efffeb08c546fd73ef94df9f1b5ff65fa5ff0bc03d3f184c00ee4497b9fd92846237dbb0120141a975ed6b6fc808fc44434eacd0657737a1113a483e9d74e32d90e12e52fcc606d4ad501cfb294efc20be1e18ddc5f68f3af76833cea86a8e6cb04bed9bce161a6ba9d88e0791cd5e6dc87071f8df2d6d39eacba08e7590b0bde89f6a72146685b5400459f973d90ae732daf65925e7d57bc1cc349bd2c172aa63e1d0372198f14bfddead07408d453ff4b848e932da94ff59462a5fc3591fafaefafff737fcbf8dc4c96bf75ff39f03a162e445ff2b471320c4df15c94cecc149ff4be9a3a484cf72b1e5bbe6ed1dd67c24ece7104d54d19241ae77a298db2948cefd02ab2047405407199b2d47b50eec1b332213b313c66a91af48a61ca574d584c4351c3833915479b8dfbe5c5c49cdfdf032a6ec960f8ee7916f8e91d717676bd7ad0f193a1aabb06576270e5e1707045bfa0a70b9e05530eb6e4edd7099d0ee71cc1dc4cf5d5f75379bda02bf44a42a2e146f4333d6e20f117f3347aa56ad5da7f87bd840f53fcf898e751d7b089be90d67d49c28cc309dfee4cbe652a56d161dca6837b8e1b11b18a4145ada117079843ffc1bb74d55c9eec1679c94e15cae6263e8c3640bcb37d8be4ae9ae4f648797646bcd84d41f75fa3531557e575e946635c0c45c52454147c25b6e82c7bb6d4095c95ac28a29cd4f7323d076e4779cff18f7d3fcd618d6b97b29dba56a0fb97e5ed60fc1937c2bad1473bf28ec77f8110fd1b9c9214e2af1f99cab07e9afd480a13ed160675b3f4a47999f50b3f30690d940753ff3159bdcb3092631fb045cfd62604039ac56e14d006d612dcd55ef30e03e50482f84cea4a2b482e16b8a1987c78fb36850bb3ca0e363ba9608e1b91e139c01e4dbd6cc8a394fe83ee17a209329a609bc99f3e673a9e85cbfc82abc6f10904cbe5f20a7852c3e31047d1cee062d4082173939698550615c2626b8faac176a591cd90beaa21a16680ca92abec4256929a73153ea0270b5058aaf6cee9dda62a433b38e36bb1ac1b9d4e94ab753edcc9b16ea801ecc974d0292e0e7c704dccdf12885e42814563f693d8a0b43fc66063dbc4ec2d623319f26cecf32f80dcfc189942a610a401749c8a18a26929f69706123075a662a17b4b1df96886b73958809ce5daebb99cc356a207273356d0eeb0c97e44ccc522d073ff54076f22a3b7fbf429fba7a4119ffc1b2902f8050f322b9fa387ddbdf2a3d1826ff2e4149c2b71135fc3eeac1478d31cfcff88c3f8102025157b66f4454bf17fa16d8b4a284bf52382b212b8625de9a7ff2cb7246b22f13d1006a3036eff4d437457ba4c3e4f9ebf997853903e76b9483552891dc832f5bf95147c56107f65c493750e3ec6a85b31d5e8c34241cfe46f99284f019df70f099dbc74cf392f0b73aa26acfcd7f0c83092ccfb09a7e6bdd443ea4057fd6f6a8b0ea5068b27a2458b01c12592afdfa453f115a700dc61cdc81bb82006aec1fcee47ed850caad983ecc1723e3ecabc3e7f5e2519736feac38fe356c1976e2bcdc83c026aaf88e00e52c6ee3f2a65ce6032c7e4fe477352c2f59ddef854ff406cbf0a16ea2b83e5ce91a185dd264b1e508444097e5a75a632ea4d3bc05e2608cf30dc65f65ff861e222a52816b48158d585b0ad1b43a7626636a08622aeb4ced856ee4ac30dcbf707c7e7974f3ef05ff8d04541ac74a7dabe3ba6fae829f7cbeb701142c3f33922bb7a3a388c981cb2ba9c32742f746e6766166e3c7051d354765f1c9db088d77845dc5a8fb0810675d272d7f9cfc341357aeb1b64f86e1787c78a4a5f53a0ef9c73fdb8957df988f3a6b0c117c9adeacb8aaabd964543e8b5ee964519869f2869b58810e6c839836ff662d6211f295fc1cada22d36e24d35e22c12d6d6927b4df2d9144124380f50221c818e3b0954b0613217e99cf64022dc01b97cf08b7ca43caffd8616be15379d8aea3437c74770d9813bf951b8019957919177094e09cde0e922616dcf138ecc972291e07906b37f2e8d2900060d07f8fd9488026332101291bcfc8b3a49802541296d0be1712013b7490c843a230566a9360d1875ce45d4bc192f4b532b72a73f3f364983701f40493aa972b52ae99fcdc107df831ad82943219ef69a85607dd91511b624be65d4a6b6842be4eec477e91eb60aa972a048f1408f2b064cfa7e82ef34a4160468f87c150a494d11dc9a600f766e4d1ce417662a14b731605fa7652e8f6d799caee1e13d42df6e9e4589f9baa071a1d0956d457d73185975fa2691a21239dc7720b836fd5f99d317440292340789ad8a8eddd1b726e611115037c701370151a3d009b98915dadbe10b6bb40cc12d30e715f6890012f2801758d1598c507b78f557aa297fd169ec1b62979578cfb83768b628493a35a0f2aed2628886c78715b55c8265a6e4fde007fb1fd2fd0a1670c24afd1ea9a979842006f442742a50a3c913908cb77383fa22dba7425280b40a5b129b9270046d30ada97636e800dfcd842b735458f346b26df3c7a99dc77bb183cb2c794de4aedb96cb89210c16eb4063b2d58d9c6219d50dde19b381b567cbe0593395b528f4eb670004d491b7ed71c1075e022a7bf2e326ad925037881fc6cd956b51957111c2f8b54848789562662dd7acad9e39a1e1eea50ff8d6aa0b358e544271c83d899f7ed2074931e0b5c2bb59ca45f4b691d9243562dccc5059e14013ec796a28e4c4583e8fa92b4e4c7cf91d943f59440014a937b4f979867ec9b61ecee5a61b77d13621b971d7d110e9b640ccb11353201d7bc11937ef366e09c232bf0c2d30bd88801bc059a9771cb151e6f294efb0c29be499690bf0ec8d1911952dfe6b49f5234dbc07f79503196bc71c584b516e193013f4e1081eaa20449485216595f3cc48f4cdbec40c4b596263c1bd8711826735253b27155c6b7c81133c1ba90d30f44b6f1202666d006e7fe0536ecfcd9648f697a6771bf31d6e4da7fdfe9066728eb239b5170edce2e27cdbee76adce5ae69b024185fdeb11e9020518e78910a7886bf6c605de9d65359f879027f61de034eb3a0cdc9c8ee5c3380fa03887bf99615b9568d7b67f1d2dfbf2f9dd89185fb6718827ca9a771e6d03c5fd96e7a0c1a963c008f6c0f0999bfc3603d6366c1922c98a1e9823c6868a88f7db25f89d8f764ddeb997e18402cdb2fb349f2280f8a2ec8caa8eb5f46d812694119b3fe290b674c3dc93b29b119c10df71fdc22ebcecab307c3d914f50d9bf98ee41379f86d32a89bd42a9cdb2cb9d69333e091814fcf608adf8b2b901f42ddb2930a0291b7ef7aca1024682a99f5892a1091cede152f3591c98141230c2828c7d85bca393edfd5a193f0cbac47d9cc6fe79707dc010445412c47f805d83648fbd5111620744bc74bdfa5e7e0a0adc19648fc462e8a630da02619c13fc3638d4e167ce8870b7f01fc02ae3d033e8d56088031fa95c6359822518d07aefd130810cec1b2e45788f9f1a74847177f8b11dbf5d70cd81092f58f6d269855af887a2c7271d33f8c06b9b2899cf21dd23dbb0945ba9139cc374445770b549f8d6a356868a495eaf6c28cec7201a19ef212780a2ac39767033112749523870a22b722fa8a9d6b4deb782c131a10303d9efa145a48abd59a48d6cc0bedafbdea1f8ede9f72a8f2ac3bebd7afd5805983db0799462e3f3827f702d5cbfb7a3c8d4758a4870ee9cdfc4e8d918d585f1c947c5b6422698bca5840ebe03590f2810447e36898dd73688afdeb0a43f0b5a85580b78d2c3de0d892071ae6892471578307d2da76924f20a21fc8a9ccf99f5bac61a343146be68390cd93e17bc0df0a81f9e7c61a1c8a90b09d898ce64b896c6fe917670f8ce977150f85e8ae50243b432d22c585693f612cc218943148ae7107e1b1fb39cd0ba3003b91682738e199dee1cd8b23f32170b15f8f0915d70559d2b104b751081cb2d4c599fb56244785830c0877ca3df1e7090c0ba729ada12d01d57033e80364cb198b5884c9d7b7e3c4b848700e6584edf144f1556de04a1f1618a98ae7e2953414e023a20396ece532fffd6523e71372393c657842353f8a8956cbe365c080575e0015d7e06349b9e93b6c99030c5833d0ce49b70a1f8e03517772adb422817d4bf5a272255b8471ae97f1cd60cce1973ad0e69f8d6f63003bba6d36ac7f7d3fd88cb4fae94d3a24bb1203b3021c0e7a433031125f706e216e4dd97737d804addfc4fbd7e142b9bf777f6be8496985eb85106ac7d6a15e0dfc0634a413e156585f9ba34d8b81636cda1948bb3cdda943d7e773c477658008f18818c53054c6d520df5ca13b6150ede87b51ea814a5c2edc81e1040614b28ee2252adf1dc618912c75d20ad5b5989763e6c840bc6996395ee9912011ae286bff39b8753aa89aa710e129776dbcbdd6b4ee15bdd8065980f61c7f0a18cd321360a2716d4102390e6110828ee05dc2c125f582e5388f9b2409a812de3aa60c027c53abb2b8c8047250c2fee7a635d5681ed1a2b4274a49cfdd74053a506bee45998dbe83b040a35996dc4060a8499a53c5bb76ea35471d6204fcd7a6f1e63a9d604d06e89737db2aff47c91ae923f822cf44bc6640ec6e6c685e8e0579f8357988fbf4035b041a25ac9560116dccf412e4787a71de0ae5ad04bf5721f0eabd178569b1d97d3b9e64ad3f0d8498cd1086b2a1dbe20fe00aa78562fc171d062984b4208891ef8ee9895987b83cc758a313448e49b86df9a1bc08a03abe96ff3692359db39941f4dbc4563a963e78b7a890e1906ae6905d215dc4c812a4e4ecfaff041f8dd665743728e07687b73734b0ebb68e8d4386b5dda0ba4acad0ac9e4555c498b24aef9f813b5ec8f53d72781b385d5143c2b37f5d88195283edf759da76420921861ac85fd7c362b4aaaafa8022fe1d479a1596ae104f74d5720adf2d56c993370316ee35540d9af272d39c45f72c70e0ae1a7a33ab69e54b84a0b2a112d4c8f3904f3db063ec67f5bf9699d9d5d1ba1e6100103a5a655f3f22d13b015e66bcaeecfd84c85eec4bbeac862a9fc0e92143a4bb40f7b2299dae83e8f0287926adec35ffb2baf592d01ab720a4ee4aedbc08fbd7139a436d2a057eab27d4525e365a8e6452673f9d9932acea8349a4447febafc0ed262d3408eca80cf84db42cfe728f717bf56ed3abf2b568350d90755ac2fc06db721ab1b2703d1965cf3d24ca276bc691724c028135fff2483099a88f23f04a059d241db05aefd66bc1a0f9a5a90fea2414501a33a16c335bf7bdac5f1362fbe594af6e33fcc20ea4070cb9a5bf82cfaddbd37021be13646da7977f8fc0a7e0a290123eec3a012adeca28efa546dc3180e32571d8447c82f6c315720936fcfb775a44c772568bb403ff83271cba2241e04d763a08088e25e5e268d4aadee74058965627c9116bea47ee359a1e4a8fd5a030f7bd743d1d9eed07c89442107f5d50c58172cdc1a042742148441551610adb1b62059e547a6a98d536d2440d45a3b3bfb615486b00b3230a55151dfb467438abfc21f83e039645fd1464cecaaa029d46edf2f5ad6180028f04379d199af6df27229d704f4dd7313543b9680d124570cbd533561b8a3d7866d787ec072458e7cc244251eb5d830c95e464661085fb07c678998ab1a3684496a28f9b9b9ca113559f38b9877768d2127d4ec0877ff2ef7eba498a114dc08c41298ba03b55632daa0766c99c3ac5333a3d255d03d6eda65ba53f8ac3c85e71b28fae2b1ae419b279b7c15b872087682b3aee2fb717b0e0614d86e59a1851f9213c2a17df2bb84917cee977a46b87adf95394d0acd444f3e020a2c359a81432cb429c80fd2af79c26dc15982bd8e5dafe5fd1854b9e4ecafe55b00e10a70140f8cd4376f881ff050c2ad0e2a14cd8354ab825fb9b14caa4b78587d5388e4b92df590a93bac4d6c78a134b32ab86d1faea081b31626207f8f69bb85b1fe3e52fdfc2065ad71a633c35d700d1704147fcf3e911e1bb251fd8f1ac5289bc0169508f9a754b3698a04e284a30b4da0f5535e544eb8481a1e04a72e187e11f55713210ee5d4f6a825c69d0e907535592bb5728f1dd55b084c0dadfad63cff13de38e9558fa6f21f83ab540dcbd3f340ed15e6d0c94a9557ed35afd7f01cf51ad86e8041e5c3723637de011356274ffa5a52ccce6e40d3115eaafcc8f2ce837dbdc64b25aa96ff7d003705dc6dced78d3e1194421fe746c92f54b29a1201dad5ba09582e8c7dd486526ab1ac4958a5d1e507d41bd09462469a8a82501ed3f962ba72aba86f4675d5d56497114b38b456c346518f09cf75cfa55c4a802e4d69bbc3bfad52db556be08e85e994053a2054fb47a27ad4cf009770755f5961e05e81cb3b3798f8304a8e8a7921a99c71c341514dbebd1cd8d8ed118a0c141748cb03a47a6491f69d7628f51270adeddc9041f73bd15ee4b907e6cf849bb55a81207ff4fab7ef70048e729626fa42d18d8213d3fdcba251d8d54249836eb6ebb8ba3dd8d7473a0231c9cb150197dda1d523f7de26f7a44d0278cdaa273a5aaeea7e20d66937e665b2ca88d717eeb96656288edbb7da76795b48650de4e081f989ac69c02d207e683dd7acdc5f7660d58529cf502d4d47789f473db276b05e86935a813421347b492a6cf75c52cc9c92a87b27d7624ba7a458ab551da0746398b0090aad89b264fd4a4011d54235802ab6c76c018139b2a9a6d2f76271d648275a61f64af94ba3d13c5c9170cf6e5aa37a6c57e4060997c2bcf37ed13fcfd8b865d2cf2aadfa44ef6d82276d08b0d747dd53a23f4cd017d6e27d7b0308fffe399f5f233094be6d912315901cd0540572d687b8fe295c80d47c0960d1b98c7c7cffd2d67c6ba5e63125ad6066d7abf822135840a38b25389ac3a0af074b43c8c52bba1072f51afee1ff001e0c40e99b604a130ed4c9e1ed40e6ac21366fd270a0041e146871a278dfa007dad0b7563ef7fd69f8b6e612345a18c0f3cf24a4cf9fbde1b74c524f6099bbdaba3ab8c730725ac428bfd20b9915be913a73c1ad74e939e320b2b331ef459e822b3fd680fdb808624e6e9bfa70b77b8130b396904ee681536407b52f49bf6408b9e0a77d9bc61d7048873ece96431ab4a14fad53e582f18238135c1cffc610a6533006e03c12aee3e175db13fc035c556ed871e652a60d1bb2e4c87d17b2d68061e4f0a02f6c5c16594211607cf9161665ba5c40407c81948685038d78fbc1e2186e3b6b73851c86b2ffaa01bf936a038843abc0d46dc2b4c2e814cccece1361011cca16e0c2018f130d231b50b0b7401029a19ceb33939f68b36bb2eaa7be68a9aee43b9e5c2636b9b2afd6893eb815513b7cc0df962b86ef160f3132c16f5ac07370ab0a708c80cf112701c69aeda06b649d3912fdbe94c9af804d04ac21347c09b82896abf96b55099588c22c6660dcdf64aa352b5f15968596c2ee609e61836fc59f8d5195dea1704a5fd630c813ad2b9a9238c2a56714d2012a43d09e70c9133dbd10efbfe3524dbc808fc807dc8e90b9354f1c9a1c37b8423550088fd9bdc2a820dc8102e4a81d5b0101cea159f6fd71f3150f95554d0c0c716affbb2f6b2f107605ad28ede511f7a22ae3d5d6b3a5350f910d7a8587a02158ca82bdbd56c884bf17db425529c052d4d51276bf72c45fbff08e382671a0f70d720c72fbc62da73dce9f0a00260ba4e6ada5ac252906561b6a7dcc4c80e5c0be2c36c14125e2e7d53a64ca60301efc70342089a7adb387daf7d9656bfe74be16b3d872da893a734f0b54cc9c1fff39432ba7314e1bef81b233595124c106cadb8699dadaccdefe1e21e03652fb1cd5d3c14ba55563450a6f6276ad8803971674b959814ec088d8d5a34321df9d53295e12f923517c14f0048f435f4bea4dd268f0d9964ba8239f2a4f7c4aa9f874931e6339d10b0cc0e6e96d7aa9e277f1f0f54515662f46a0ed9579a754ec213cad6a6f77015d67d970517c3504f00cc7b3b24d4696432a50b4ca97fd8c64b0db94415bd657af168a281b82f84cfe27c4705683d98e859018c24060d6432897d0c0cfc221d4960b6c3935b70afe7dfc550cf854881320d719b680ad53d72698bab4e06d91de96f8292ea7dc3918c0697f0253f4cb0397efc7e25bfba23a78897f38fc8e0a296735f7ad87cad6d5907bbb5acae54c89650efd4c06a77f39c3e56c157721506af19c776ec929fc37a46ed9cfc13b7f915ff524d6fe6d8f29a898eebcb2ea99986a98d81d4c39d0cffdbceae9e6bd5fa09361379cfa4d7ddc94aa8d196491a70c4febac4bc6afcd0af8ff8be4b2489e560982f88158e0b66cdc9d4176c8e03a38a77ff1ffde05b23ba37e919575d4499af6229b0810070746d738de2c541798f3e8443ba8f83aaf75b827a338c7f702553c196ad9b26ac813ddae31b5f353382f1347c059609e15c9a98a6de6262a5d0ce05df81a9b2b569bb38140efe752986769a5e1e7a811b38ed8f99c1dcc620f0c55eca39c179fd6b2ff3e464aaa8f10ed716d113c41a543c6dc68cc92b51b69a2e57e451539dedfd083a45aa2d902ddb009865ff4fff3c93020308f8ded49ff1eee95fc4118bbefc3c4f636e4963290a38bd7bbf94828dcaa6d46384f476d0c353cc8db8e631d33d9866b9b2913f05a536ec568812815725dc632e06e7c995993e586da49f893f072d038f755b6ae43d6b7b9c26e5566244cda52f3088e0b528af782bccb7b84f3187181416803f943fae937e93903f65014d0ae9c7672cdd5b853839f83a001d5992ec0b9cf1a3d1eafb22e809a9786d2e6e072462ce624a06e5cc87eddbe604d0e4b27259ec076764350176b0661db72de48f0e7105ac05c04b6a9d96095cfae7a627f886fc0cd42bbecae4356cbe2c8a9c3debfea3ba89ed43b8462539c2074ce72941c6aeadf1337e880f91a4874e908dc18b6fba1000f5fbc01df16968233316bc9c42cd704ebc492d7a0c9662da3dc53df84ac0a541bc29d6c8252713b40708b584b890003f80b55146ab87e3349bece7de6c547c211a18d821b8eb9e332d96e810954e507099e9b402dc5d7b52260dd7c763b0cf6699a6052134fdf42c6a0fcd9741dd32afd9a6ffe6da180a4f1edb7efaf976673c284d52aa239fcb638ee48b9dcac5febf244df057accf9a1ef82f6e687ad0712501738c30b4925ab9fa5702bb046301db49386ed836a85e5d8688c11bef2b98b552a187ef7b145ece10fe46795e523733bc1adeaffc86eecbc5db8083d97951e23844994138b22f85a7020f1220a791472206d3c371e6eaa611f103f01d0115960214a090610036f76d9dceb37483a59de6f0b815860afaa79b1b4024b56df0bf32cf321f4a8c26a7ee4bd24a845f733efef2d0150d46fafbd4966d67acf41c19b023ee1766a75dd81517da0cfbcd86d2c9390ae740427830520bc2f5a24ac566123783155bc9f6a81c09ab8d666e1da4ee2f951b5b0f94ac839c0e7d3d28ce0df95f666436894447fd4c299bb0cf4aeb61fa7c82c6fad64f24c4b903a74bcdc119f41607a1889e9412c0424da80e8d469b8700acc0bc220da380801b6dc82786c519d5eafe4034d76185bfee4a9069f08f87d14c6d1bfc2206b052160ea6b9bc3784569370421be2fcf56c08cab80614d25dd95320d6fd3f001602366bf3668b0065725549c7ee98c7ff3fd073a6fba2e97fc2b0cc3112e052737c1d77a678894237d9257c7a1b4300430fabafbd51c42010903a1fd9072e5f656d004de051fa01a13d3f77cb15df2c17fea8a0b5a07f404f381f2462bc90489452fe1303258159037e5d754bcf1497fcf928dfdd94efa116dc38f57dd5a4638a523758b5126b5a8bdb8e0453910f884b118b101ef6388c73786a972b1befa78699e1883720d0cacb3fc51945f7fb0be491a6cb07107f0235f1fb10deb88f08042a8690142505c90e2daeecfae031334c0b59ce7d650d751ab6e86e3676ceaeb238aadfee530626ba626baa039c5cb67b616e6a738a498709dc04df27524b1fe011377d1e95a00b9f9f082fa12fd0c4e100115a87d6d22126c147218d3a4562b315234317219a297f0580a98e21550aec028aa19eccfdd319f61221222629d03ebe5cfe382824ec0024289d0871e1cc20e2483e110d3ef15cb702d18e8aebb08b451619569c5abed490cd1bac655ce1a3eff74800eb9d4bc87b5ac805353249b0fe19e3a6a307fbb2d1a8dcecab59ccc830454574cd6ad1e8045d8aa1ed7891f83a73621a995e4d727797bb25d07bf9c30190d20e3bad1b509a4fec65c59fc106224f1a7cdde2ba494e1d5fcca25c068bb44f41e69aa957af00a25e1a166e899a01eeb62e576d0a5baa8ab04fd3327820fb331f7e1c2bf401e6607a8c94863e510ce65407cbc8f81ad96e4cad7c4f50f95f916f7291ae0a00f38f997134528ac0d17819c1c7cfd1898b31070d615a9a2dbbf45d6131c6a39dad25bd822cf0cbf7b887399762e2ebf074ded6c592c9d20217b2cfbf21809b7750d86e32bc1a815b5e132e0b9ed73ef3d476a05306b59fade420bf77c851295392f727a1532c1efadadd914dfa0e5ecffe2a59057c552822e76a68a7a52649b5ac5ec69f07c5003805ce46c0e1523630e2142bcec834d7db79ebe6459d7dd6ba1cf87d7cc8fa5ab8eb0a59f4bc97b33ae89696c9a204683900c9b751c9444be87005c5228139c32ebc1ba5c201075f7bcb700d85fe383b8fc64c67fcf9de576a62dcb99c3a1085fe99272f18c20d9fb7aaf7314993f22b5eeeaa6ddb326e9033b8c12ee5b1862af5d32a316dd3bee9fde87b1101649ae6c00f859575ec4ade735807d37974754a4aa8936cb9879a380f12c6bc1f3957bcac94e8b61cd7fa491ff6070a4f32acf80a0ab7dc08b9a0a5690413a6f4205c9664e017777990bb1b19c22d801849a82642ecb1d801c6caffee13770c8ce4c976782da306bafee87e044eafb800e88ae47122bf57e1b1384ee78050527c927e77b580b6d182b30043731b623ed79ad630ad5970ab83b39657ec31e444b8f281969823cbdaf4fae7f6a1ef50be0dbdaf8a15c95a99b4ee031ffad1b157c3ddbadbac7449c77c8ebcf71a96c863c78e8bc7bfb9bd593d075355d02050739385ef5235d921baca9c9acebdb81053743b2150e1e7d45bc56c0083630ce3a02bc241f167e30ca9e0f22a2287b029dd6778e13d1b7c50ab0eed4ed7d90e3e80ca80f18009cc9b913e9e308f46e37a1003cd7a681b49564a160bb3293a1cfae8908b75cc04a791af00e996e54a5d262cbe33827bd5cf0c8fc4ed3f2c559872b24d518910fc1a5352f4b68e6e1841dbe5f76fef63902482454c3f4fefd00588b9ecff82ce49eaf4b5d3fadcdc3bd955aa1eef7284d87630e6e610252c5c98de7164500d77289b7c3cc41c08c820791426d591cbb8c4f856ee8823744d9a6080739bf7055ebfd26f45b9f5d935131c5515d69e99b9792dc09648fe23ec9cf9a2cd9475b23ee0ae440d4d16489f81b8ddcb5466a6dabb5424a5c85f810857b41f69713f9fdadd410ec7a30e716b8a909fec2fc84f0de3e6ee9e131cf1012a64e36b3e51a0ececc93a877987cfb32cd4bfbae54c080d10c54bb18d41525ca3c5bf009478d4824013d0158c8a8cd4bca680ae18cfcdd9e28d41fb87d55287bfcefe05e861103c3bf9666f35bd8b50d3e0bf65a9fb78b7f09d90b5d20f47b2d1e1d3e2840d5abd820dc846fc0fbd2fc168e521b188cecf65fcea9b5c0ca602fb89a521a98da9d23fe8a8d91fd294ef7b55a5d64db20a238e26c2be4e2c2765684b3125e20dec06dd8c746a8dbe974a283336fdd7310d23a6fc6d7cca3a67735b5a6d42ec1f144098b56528f8463b8e96af3d671c5ee5303a6e2dbdbd307fb836c996e84c0611423ab187422098c23a75c940d6db934b52b9359e2850f9bd962683ccff0ed39d0d48e3473a82b1490f3c9800f858e2e2b45efca7d849e2d5496faef80c3e38f0d39f9cbb1b8837d8628d28c7d0479e27058513e519ac5a0f30d7e931f1ac27a1b54fc4a760f1068918f0523084dd92cafac3e5c91d0fd4f2f1d358bc9cf803063ff0c37c1d36f21362ec8bd81af7853b804865775067c1fb99c9db8ec1a05b8a3f05d2c442ecc049a3cf177c9918b5232dfc640266cac479db947fc1fbe94966e1420b32d297d43ab4e98ad69f8ae64a90dac330b67759af00327c80e45426a51a8492d9dccbcec9989636299df60761d1f6a1ac9f4814af88490625a50887d23390b6bea46c8d9026b8aa2317f2bc920648d83ea70300a77bc249fa7159b244227dfcb4610e110708901afdd925e7fa1492a0f2fd6740291024eef058de9d947fb01ea45b9c92177fb40882f950aad85b729dd54393f015bf5e7ae5eec88a9972b125dd51afa3ce3c70268e0097e4ef734e30d40e0e7830d2ea54d041be0fa411b2b631e6f7f79654a8e5dd4b8e45ce257b14f572003c36dc0974248ad453e3a0256362ae625d0700fbd0555b23882b435b17ea00d9a0515cd808f818fffebf211882287d96d3bf47a45a484e471bfa6e6fb9ec21ee18c735686343ee40f8193649ed1e8df37fca8526fa56e6235b411105df239a98d7b47ce37bf46a53585e15a6eff0c02bf1a22750436502241e8262847cc2f2fbad108d4797341540c606f0b04e4fdb0eddf2b5c6b4bd519e9521cd89161cc5ffe38d0422aca64df7c74b6821abe929beb162859aed09a600cc489a11370c402ab6e9c809f920d0ca24740d03805db63ecb23371bc1e5f475074f982b2114381428589d2d51793e619809f2add79436a89a0861121cc5f774303a513d4a00ef720aab155d1f8820b44a5a2a19fbe5a5b02810732e0502b2fde5643e3fd2040efe54298579311c9df06d5291cd043a11a9710b7185f8c8ee208d3f56d5b14a0ceb5b9355e41a6d5080c7fdc2b17056c9d1b312d5c615a4dcf6c5c127c7c0fe700a7aabb00f9ebf318e070cf7557f749e19990b37127969a9c6e275ce3bbe558213dbb69740d1134e03e504e7c3fcff7cc7154ecb929116ac6b834aadd8c4a332eb0486f93b82d581f663c0f3e86cab3a4db6a14cc92f18b0b3c4602482fae79b826bda8e666c540ccc7819827495dc86957c26e53cfd76d3bdcdb989901d98e105784404778f7805023e1b604ba66cffa042b182f1d9b01f4eb9d88187850dc3cc1ad849e2751cd510eb0b8ac92795bd9bbc9298a9843023da3ba568a0a4d350ab17ecd1f3c99a35a5bd578a459b730ca080c157fa357190ddc648be04b0dba7c09b4ebc6c5f6b97e89b36183200c52cdd1d5a90a86929b905184aa4970953cbefd25f0b4fae68da87cec39846e7c656d5fe422d5179044e4a4a62cb73106b0274cc04dd8ad5aef15830fd9e7b962fd226069a54b42e6a4b86360d6399cf6777dc2ed62140447b4893e04f79031f201be415c6d983d6709b06f8aa692485751edb6abbd1e4090808d97f4428c66611d09394505220cc12f712bb1947687fc68856643e9a0730910202c055ff0f92013a3852f2289db8f823c91b3811b8c334e1ba7ba44548ed5e2c93c579f55c2729ce1d842c636440c029e20008b7b82f791c09833dc1714d6c80643cd272796169cdafa8adf75835c76af605ee93e50c58cd25af887f146ac5e27156e18111c497c3da965d49ea82f811aa4cb08359292231032f34bf10cf8e67c66423a5431b75729f73cb0146f66df55e3925ce81e3539d18911589918881a688a4b8cb929d60241477d81a03eeb03f61181526442c10d0b9362f217a4a4342d2c84f095aec2be120e7298d82238808061b85872fd367d1c5ae9b54eac22270fc643abae53ff8e47f9f9b173b603c9fdb62cf0de8a8f80ede2f1d1e18d8b01a83380b80624391dc3b6396ef5851e2e033f69e6d42829a86198f54b36cbced2c9d265d0fa19410f38733519ceeb6d662831055440bb00d3e4788c72b3a003f5eb2e474a2f951aa3ced0df0e2ca93ebc855fd3b879ab53a05f923f211030febe2e2a4f58eb2b1ef6771c32a17dc711484650db307813c9e5232eeb5cd1fd11af365ccaa816c71ad3b1bae6f8c7681d884adb2902a0d86544060ad667c1edc2d836268c0fcb1561f2ae72415a952a475f10602774af14fd9a2f355f145ce300b7fa05642efbb5fd75c9f45b01e392b71432399f77cb0d01b5045b1c0f6e9edc273bca6186cdb681aec1a9fa86fffb2e40d2864b3b99af7f692ee82cd11f9bc1cbb3abe65cb1039aef922ab01b9df410d7447226a59db4c59d6f52cb6a37da736a501a30ee77509a14b6ab578f7ace56bd533838f5319d676bea1650fcc1c1c5742e1155e4210d99b944e9927d81be1f3387e43ff3f2f1f84f2a2c3fb1dec1cedd9c5e2dc90c00333fca4334b251616714627912b2876c5d5fd758f46a88fb1768a2ef1ac49c5d7c729a7d0a9cbe2700c9f4f9e27fbdcd7efa9c6159313560714e446f737da40297de6e57da22c40f36e62c04ce2332cd0dec1b1e9e8b17fd1ae4bc09957b2702d6f7eae3aa17bea307620a09592427e87a4d85170e80e4aa39d5cbcfc53aabfc40f1251171fdf1e0e53bc02812e98968d65ad6e10b105870134a3ccd089191b9b372081780ddba30575041788ae77479a64b640ffed06b15d4730a6f09f6a9c17cb862ccf9bfed2a3b961e44983c26a3a047fba6b9e7eb50d40b2f12e0efe3926d224bc7a8e6ef4f68fe1ca3f89ab5a596787c5b1e852689372e0962ba16c23b6a61428f0bd80fbcf2227dc8b57a991c306ae17dfef6207562fce5af0464dfac1665582934e9fe35aaf174d67e9fce35ea7f85629f23da83d97b6b3212d98745e6a6d800653b217d59048e1032a1c0d8fb7ba6dc774d82ae150fabb5251389d0d36e5de478feabfa2a4f7ac36420f3dab96e378803ad0fb345b33f946dd8f81df6a2c4f0bdbd89bbaf08fd657a3c432bdc4bc7149fc6ea9b7cb3c207edcb73af89cd686065b8142139c4573f95b666ded54ddd82adb7cb974fbfd5ee81acdf7171bad26d9291fc1d08e330474d86600702182fd1bc10ed7c3fdc13afae60f03b1c0d67853f5fd1acc1fa01f1b7fca2af00f8ecd7402bc02959b85634b9bde34d57eb74d02640582707e06834847b24682c9d7efa60c1217d366c3ff888448eab178e9efec05c7c71a06764ad6018f2120a503fb89ce59520a7250d94fb549db63acfe48d6a1988aec1f4875c329f59806434f50c918b817584313e469a0d3f354d40010f318e41324b21c89fdccf502c836d66c9405e67901fb09c1af11b6e115d0fca6f33bdc44b0e7881532fec8f55e072c2c3999cf5692ed0892e48d402d4a7632f68cd8f4678ce49c8e6233483cd95c5067223722043d682b3a857c1a3c309bbc1fd7bbf8066404c05fd4682104fbffb4fb58732ff92124a62f48a73627c2a2167bde8ac4533e772326f008986de981559689195765d4b4b0d1230c1edcf3771ac7cb60bca0b5b751b4c67777881d5b88bcf2baf41be490636d496ebc6c1719e440fefe82bbbb4195e894b770a405ca59347ae07356039bb53afce0aacca02f12698a72c822917b9c6ccb011810278b05e5e053874e6d59aa9796d405ec76abf48ea3f6f2d6f2f1e457887af8936e9678920b43121031099c7a909ae7836ad9eb99a1379c304bfe87be4d4af3a2316eb112ec95dab51edfcab95d79ac192bd18cef3068e110b05d77109efbe233fbce86633c013cbc3820628fd44f894f4df131b80bc7dbd4bcae68beffa1acc6a01021c45408675339b3649ceef51a03cfb5e03f7f051ec762964825bf124c9a468aba9125696138168a9cdcfecd8b77a7186a89d6581ee1fb703920162aeb55d8880f6f333add04dbaa2c65dabf6933afcca3fb7e0f207e6afa54a80a84d6886f0beb39cf1ac55c8c66a70fdcbbc1072d20ba8ab1ba2b07eb113ef7c451f432848d14fd29ec95c6c7c5afc7c6bcec3c71f2c3c6a04ecbab98499d9c602ad55346b31cc160a70c2631e48d5f1d9a32a9c98dfe5e399f0e46c59bb2af8dd1fe0fe56495e68efde2e58c26a511ad30a67c362c5fbef674cd5a13586bb635c323411700196933bede2f6f3f79d0271bd364b3f45ae01ad4c75c69291f0305111a139290dff4618e525cee521b1e6b256b7af7e0ddf82bda55a4b909f1ee06cca0f29fd62b948fbfe955ffcaa1768ed8230ee878608c105f3222e33776d3358f2b95fb5117357efcc8345c19c7e67f69003b7d762873079a011a2eb3c9fce8bbf58f3de5c9799ba904b957d04bbeb27a4d12b84c188a672be3321fc9a9a83afdb7ed1bafa58832284899aba22bb015969c2cddc11d4465e7f0b18311e0bfa18e186f8a43580c48be3c7deb548382fed4a54aef084292932c27bf81892e7abb428704fa86ebbb5ff828f4892e186ba52962a6d93986385ef13339adf1b248139597b809a093758fe2a4850c84dc1ae1368497e9911aef6efbbab7ed634786be21242299b4ea10b56114c7dadddf959f6417e649a867d35b4cd8a672400a04e72853fa4fdd3e033cb60f44d4533ae51f5bf9cb9090889aedd8da1585120d1f8e607f973e701e880077c08a645f9ce44228c0bed606ebd5d40210d0601d87ab4b32645c3c7e32d4b21c8498c4c61b02440709f0fb0126ef8da4204af4aebbb3eb63440352add18be30935b83ec88010222a9dfaf86ad94b7c8777ebc78942a51a02067fa0c41beb53dc66155dd4cfc88655ce61f9dc64077e59f0f6a80f4430119f909a5fed562c0d78dcb9f441236c55d5d5cf4d33454972f26794132fdb25ae9312fd0d71cf12b45afd1d850816bf8ce235b33997471fa305cc75bf6741708a603e5441d214b12feab04dc69baf31d4821c38d006fc33a683b3c4b3242438a6c19acd145eaa99e0bc3295f63fc02e82c15ed90635aa3a830d0b34fd8c1d5cc009155af6c196c4d74b3da2763ec757e636d2334f56d4291ba6e7f28c69c19f42561cd9953e37909940c8022fc8d3f7cd6e49716b9032859866b379929acf9f9c83f5a8b4fff0da5e905a062a1f70bc4d27d543c55ab7b3e5e010f15991ff8bfad8b06f3c8fdcef3bcf264f4a85f80031f8460ac65a60e9a2a103170bdf7a64b6eea3ad751f37549e35599d7b2181c4321542ec2515d04d3286045562d8f9fc54b784090cd6eb28eaed85c9335a4928709a4c2b5bd664d34f5312219fc2238b5735bfd6db9ca86c7651107ed0de2aec37d2da3a602b4751bb3868dfb6ef8004b87e1fdfa51253321edb2e992f3a5752acbf236c058dd36ab6adbfd619e58fb307a1aba952b489a5b611bce65abb7775497fc8d040ff3a7d0f5d08718cb0de87a4106520ae3932e27be863be5e26a5beb9d4bcc8a060b93397d9e1fc7b78e2b09cb9a495f9510856f72e540db23544f3a676576ae5521b421f856ab4d94635bd104ce2748381233d44a26b8bfa6c08955eaf4f3b213cfc598a345872cd25714a266220ec3dfa3eab9b4be305e3f8908799ac3dfe9f827ad5cdbe9ef54adc1af214b2f112f090936f1a385c24fb4c3f63b8f17e72426e2cab312c2ea990fc8378e3995439789127a891154dce820e8d2d562aa76070f7841f04f8ce931064a099ee127b3ab5345dee3243e4c5a0902e3d1206f157bbd68ad6bbda4e9e7478440aedb2293cfbdf9b90927f365b64387abac8628a1d0ec98dfaee91af3aa53c04e30f8dd77c1c980dd702ee56146403aa6310adb8edba498f6e8e547e2392044a62d34836b0ec04e0149a7f84df850195550f3e8c7aae538292850eed163088510800022dd57ebfca51716086ec37aa2e9d2af568166c26b82739669e16a97bfb54877d354d1c2aeb984f069ee983461c4702c15e51ef1505942f8d92eb86ebe0f80c989f79484281787caf571b5bb5c65b76f3e5b96d625c1723457b081a53a511e4d0c5b3a47854c2c07275bb559a8567c08255243f7430d983cd673291c27c7494fe701b07142cd1581fa49d040990b01a145ee6beb25b9b68f3a41ad3f628a72036686910ffc393f2f4543bdb484507cfa27dc7411641d1e21ce8003ddce4a36d41449a6a83fc193479902e2ca4d4027eadbb7ccd08204f11b5d205e805905ac5b3a2c989999cb5b68125dad6b4e6aa7c66fee304385c134dbc06c6115884fc246dab4dd637f47b85828ed2237a9b9e49cbe942f952ca3863cfea2176e4230ed78eb85aeef869e138666ce01a55f4bce9eb36986b735261edf1d7d459d54f47d9d9d3354b6656a22be7e682afc32bb74e443dbd88153dd4b02b3ed80749ddfdaae65a0cf4229f2e650ac8a51fa95ec2515c70543e8125441fc20e5950e8a326806ef2335931c012d6ba62bda8afd312b149c3eccf27e75859ffb73c92895c03c0dad96f8784273b69c5e4bb51a720583cb410c0c6fb00303c022c908cc7259299d1e25ee21b8015748216e84fd0edf7b688a489827fc07198e7e62e78d42a583298fdb6d76a46a84caf88519407ed903ed5ade4c705f088e53cf20d7b4f5352eacaf5fe301ec296bde8a513771d1ca9d8ac91b4251828a389b56e854b0223f98ad4994517cdcdd16c6ead6e0221f5fc8ad1827753ad38eb894682f8167b61e2cc082b389e3fcb69ae9ee7bf5a2ba71b9688b2165ca10786dc13025296504f1308757f65f9efbac6031a07fba3efd08b88d205e6103b856f065384d145d0be533b512c0061774a75bea48378b3da810033e347bb926e2c9800dbe041f186c546b3565402f42dd9051b513b8c6f6ab623b0475fc9a59ccb9a9ca1d3121f83530c6f5a22db7a67bf114c86eae1668f6a19267352d370742fbb3656a8ebf8f5061bf34663a66ac56f44de17e62d44ee8931edfbccc3d20df603d2514508ba093b43f2f51e4ba01f029a82e1f7afd08a3022a54cbc26c061c24717aabd815e223341d348db0a433e2ec0c5f769c98908b5d0b6582a3122cb63313e16e13c902cf140f35df8db538732d40a8e356373af9bbcd04a2a56a53617b1c048fbfc7f3f7c0f8499a58cd815c0870b4b1a3ed2354575042a954db84cdf058037fc13c303cca3db5a6a9d7d63274590ee19f96adb1b78ade1e3005e4c55378851721ce8f80b112393327c181fbfd3d0eab86b8e9b7cea2ec20d8c8fb564d6e033b4eca5f919de33a033cec73a60660d3d557e2517e38cdfb45251813d67fdc808ff10b9fb89c7bd953d65d71e5254481560f8aa6292ca39bd87e7a3f9f34197ea513076d1a0f3695752fce3cdcc67aa643985c02fbc23325772dcbfb59e47067e831f7865e8703b3056cfd59470af91d5e71113ec1fb8269cdea50c19f0d92b589c4f4a3516666bb12905bf70439ea141c1c8861e4415a9e9fcf2e735c8400012c0c6705e605e174905f9c71f74308ff9c0418122e06ae764b9c581e855e16c757a43b652ee46443ddc07914224859db845fcdde0f228f44ee5daa58050d0443c244d9f2330b21788003cf5bc997b4c3f01641d79a6a5e1567e6ad5de2e787ee5a19dd6eaf145714520846312bfbd34fed125022ef76258a1f76c7780a88f977f888f00d808bcf3b63e5de4783e8094e72abe009be102cf10ac457ff0c87558a78f23929079a031f4e90e98051c67b6d410aef8bc344ff19162a85668df9be4133004d84399377df4d3010db6c637e5ab8a67638ceab8559bcbe635fd27b647dbb3792a033f78925ba586ce7740fa5ad4ba6ccba490c0385082520ec6c76fd84fb68c0bec2e9cebdfc78cd10751559e755bd9dce5f90eb6c28c75182eacee7460bbcc4b4e160c8644d8320717a8953c962bf61225e39db6bf63a4102204e150405a225b444fe02bebb78994fe46ccd7e5905aff4a97fd76bc1d0c8a5a7e70dd0ea74fbc9685eba16d9a111850631fa0ae222a6b207dbda6b5957c2da17739ac06d701d570bcc3465325687374497707e5cb5107448a2ac1f92a5ddec229d8b0d964f4ff93b9f9419a9bcb5119f496eb8d6dcb76e0a026d86819a115ed37fb456a8aecc623448869eda3c7e64753edf8943aef472a2039dd92bd9c645a05ba317dbd38643c1849fb7e6e61da0c0545506d8096a005b639a888184a1eb5f28a5ac19a238206862c47f8c32fd592ef8663e4219ba1d39a2d6b109b93b30c0beca1415dc08889a9338f13b9eacd8ce7ceb3628ec93fe8f45b0160ec902cc0a633e46f4dcc582add886d78020faa97211b7e1537d6497ecc04405f0714be4466db9820cfceba3cc5dbf010b17cd4ceb90896a33fa13f12a120da923602ac119092deb93abaa27fca638f6c38ffdc65bdb8013870b0a4d5b4e700333012967964e5af3da01dcfa1a77425591d55bbf2b1b310c895767c4226056d88c3b39a94c4e7b7c7580e3dcb82af33aca22d89fc4338ea9c426ba96bb7ab1642023bcfb11b96ec17fb5a89a04d5884325480f3804e117e3b0094184aa68d36e3c2c3fa7ed76d40d02bbd9c545955e2e4a9f12814d2d48e43f4a2d05359917c09cc529f364deb4beea499879caad762275e7533c7490c09e8674700eefc36f5a90d436345ccd2d78c31e922fba92abdf0a22c81d9ac1242781530987d11c4d860a3f1c1eab02606d2de89cf69d6160f38590a9ad215a720db468a818ce28d72627a32e8c0cc984bba8832b8a57a141330572917fdb0df09a4b1ff227141cf94bd294d7f8e82aca8f8a252028d504cca0d8645956c348f6fcbc1ddaaaf6043afac2f4a070351cdd635fbd79ca604c9a6f0fa28d7404f4bc799569771360c5004a492ae52f212720cb7741cc03af10c8660a80fc540eebb0b3943ee687a2999f209c8fe2be507aa238fcf98191ee51af221ec0cdb228046520aa299e2343e8f9c7008b652e95b1db26e28dc8e86ea97fda25753a32ab7239b9e4804fb3bc63d6da854907ce6d38fb6a86faeb2657dd970f9843adc6f2d032daeed9161e786054b7673b8ed914e3f5d6bc4292bbdf4ac18ccf6eef0f628a1b2acdd7323172246b12a3c7107a04f6bb1d180fe3f8f665c99b1621981972065c764f2405bfc1949852664bc323915f1a8a91d0f89931d7883520a85e768b1cbfd7bead7f180696cbb4d1d7f32f7296b346ada5256796eec5daa996a961565bb7dad37172f8fed449c281c57cd0407f715ef83d438c6688cb41d5b55e9c8ea6dbfd032e2e0818c3beaa69a4273e378409c3a53b144f52a4ae54a5dcd0aacbb46397af9caf0d9331234c326fde59c9c3fc8226c6d716808297ab2b9b6eb3af70858d68ae77c543548e8f56a473cd9ce3f73ac4ab0baa8d4043f0e9a41c8d12f9cbb39276dce4023045313babf254d0bb562901e0c021e387020ba229e5efb0379911be08912a4353f69fc254d68f987755fd113199976a8db4c8777f99eafd09d575a8221156b66ed288eebeae4cbf0151e8daccbdaafab67dce16d027b817cd82536673fb2a7c196b068e57c9b74ef176004b592755bb29cc25cc09052a3fe541ab2218fcc40621cef16e46eeb3302f8c3a4ec59e82a3a7d89e4fe40dd0f6a1391c8bbfcbc331dd5a8c1515c0df11d83d5ded56b324bb503654359956a19fbb84ad5458699d305c574b6ede7467127f2906a1baa9efc35cacaab0a457bc6082808d37dd4cc09b8911b48f426917760e1f41a211b7cbad1354420547e5ae833a757f6982ebf5024c529ca175d15ebbe7b8d40fdd67797c05e7e738d3046b6782c8c77ec0b86ffd32db63248eedd7c8bef61c69f25f04ad86d7f0565fc51426402beb741435af609164ed294efcf1c7ea12b901bf89b34d9e915a42afbafa451bb76b047de9f834b9f203011123d5a8362af638caa89c30d5fd4adfc54d2e005d6472edd08e8893d7b3655bf95a51401a2c1164bae95ddb7ec680ba388bff0836a832b1e24491b08da92fba86a854504b200d3d0fa66e4dde006c1e67e0f24f55dbd49ba7bc600301d3b923e53b81a4b8802a2bd780814bc706a51fbc4b965bea5eb97b7e3670b0bc3dd94ab0db0003a1c0de4f37d995606edf659353be173095e1241848a4f2378b328c54805425d630b30197ea89ae4a44b9df5c6131c173c83b3820d38551c724e4ba8b0010354e016ebdca1b800e91635d9a9c0eee91cc3cd450e6207f9a731d1050b4d004aad5e3b3bdbb00eb8094d385b2a7bf282f4342be7c38b39e2739a14957be6defb0dc6382d6be399bd629905c7bedeeab7462afb34d3da9f4f66f71f2a24b38955414f8ac2b28cbde8e74cdee51157e9c818d71439a764180d6dcbb5f84029f5e0ea3bf85ebeeb6b67fe439e3b86bf080171e999ef2c7e50997c1781e4a894a758c3e68ba902aafcabb0dac0e102a8a573060b2369a8106c02cef8af2a1607e8669c58a7ee0712f731453225c7bcd9f6199d2d51feced393058588579913f70d54af7b58c81105cfc15206abe586bd56f93c80f576e45634c141810d0bbd70c4496dc5605f143d9eb14fc98a3f227229265083da14a825012ab2e56131e84420246f76c60d702a8661efde435b8d1ab076457251aab648b364b0c87acbe513b38f99da8dcc6af5baf67a030c04f560246b27b2821711eab8f0f98eeab14cacda641589054557bbb6ef9de89e2ab1ba99676a52e408b4df0f66784fe3cb009ef92a7fbc617ed6f4ec055e92c4372ad0386d3414651c7542dbc362129d499558f3983f7ccc0119759561cbc3402ed61cc7697cd216125f8c3669c3dbdddc6d426c16304e1850e10789888119faaa54c5b84a4868e5ab31108e64c57c3cbeaa5e7f1cf8f75950400990d5d5f15544237bdf0eb16edf6b1a52d9c6d1c28dd3d2df4772d117de73f74be7f60eb9dc123ab137884f77210a930657e7539c01670ae99aeb082b0afb09cabee94e0431e12f73a10e542021780786f18814c58896923cc5360a608cc5a8899d1b915dd9cc4834692b1c2b98d3c55875ed88f39705f58235c2f470d1400914f9cbc18022f4624897962d315af3d1c7cac81328e0675a2145a657f3f3b1f01ae6e825156d9cb0022c54d2fb18d282788004cf0d8b03841df6788080f306743835e18c55c0d21ed5be661c490002f1e6a60739d0071c3ed6a12d442c9fceefde0df404b4481bf848eea826dc8c0dc150093bf29a3fa33cf8fbc0c9661ee24ae7fa9fb3ee11ba86a0d7b88cc1ef0871905f1f905ec39c73ae0becc1d87f599c3514b019e2552f1e285f315c7709adddebaddeb56c17265ad2157bcb2827969b5ba4aae04a8ca3a9130a6d82a371874afbcb64d7e44f665bb35a600a79a8f072e8b2015557eb41950f2c9ce478fec84f08d47bdedfc97f043dc0f23defa7ac2d0850810b915f8b7cf70341f2bda6ede9200c3063789e0ac593ea926427d8361d8729d856b3703a211dbdc09f458022b41340243ce22fab2c27601134cf94bf08eb3f808e7e973fde82a094043d71ae92ab53929f7d6700de898ae8cb6b950677a5658cb78d0f96b81c913617fefbedd5b280ef3378bb3bca71d39b9101f362e31c143f4ae159c8761ba7532a6b300e37593bb7d2dccfa6c1108e75ab5c0aadd23088d52b32ee0c178548f8686dba22fb9ef18efc4f38a8c6d78da960f63e4c10879a9d72013f440f0ef3cd5b2cdcfd65b2e031c16fa3658cdad51b217c62556ab1cfdf9aa9ca049aba5c92ee00e3557ceaccf85deeb65831fb46e109f28ad4d232f1972c27136521527f61d748052c342da826d1201fb0e5ffa77c2a68b1dcbd4bd3618eaca9989c536afa6ce42ae55e2b7357e9b54f321f886466dcf8d3701420012533d1dbdcd570ac6c0fb6cbb99bd409731c73d2374aee0a0767e9f91318b17f8484c312777501c4c8aefe23eaaf50e6bb1d91749f4b75fd9bb2e00f112fae9ac0502ee0d572ceaa6ce63876d002fa825c5cf0a51739d88656c027dfefce8c1cc054e08b3222f0d4bf0650f4b342366e5fa0046fc1fe1dbd418bb958b1c9ccbdb0942008d49422713676e16311e554002ce236d4d1a370168c7f19d337f37f9ef1990381c0809d668757e65b9f083ca8fb31c27d19d0ce754cbec0367f3d2ebe2057902873b438c9867620ff45be962546112e0d46f4f4b869000e2d2da2164d85ada38ed46bfb596f7c6f72083c86b9d8a70a019c599ae9a45e754d1820e85160c80934475e9823a2a333c0c81565eb26ad401d3d7bd0034560590789cd424e7e7917b7864493da7d19e5f4b45845295044db1644022bbc99bd3fd7ec08627095f77ccbfaea6edafc4a49b921b84693f1f72e1bfc011758950b0e2645c12c880237262ab368f404f083ddbb57575396eb1cffdf3d2cbe908362753a69e66cd6a622f6603c83b3382bf645f11a35290375010b2c871d218ee97e78a2101ae5a000ae9fac00e286fedb98e1358ec891ecc24d79fa8fef41a79fcd1ff3837b3500013e14e41585579873a0430ef6341ed50d042f76e7219161034bf16bc49e9c6252ebfd7c5014e0e3d20e5b3461a0ec4d6d356afcd82d38576a6534f78261c3f6504f44a58e3e5c42b96c30af9add47b17c837d92338f67e4089788a173566f82532044431d29067af855499b98bdfc0efa955a068fc3c8834d3174e76aefeaaf30cce8c9cf33bb5f714060d37e1b18d3007268999fa31f4a46ad5ab5516e782892365547478886f8dc27a10a1cf7a7103ce37fcb0893ef30ccc8932d0a85a2724fb00a2fb35b423883c9a5af63ac0644c8cacaeb1ed02a42eccfb8d49c42cce068c5875122e5f1bc8831518eec6bd40c20a882f4fe3422f69bade014af53b060527bb25e976b51cef9ebabeed8779e8c946fb05cfe2a7e58dca74f0f160ebcb4581e6c19db9c159eac1c0712bcc8daad5a21fd69cb5a5961949695de93b4afe671326ce3980b584808f49acb6e5a2e62e1f3c7fc52638dfb1fbfed0202c6439d727cc36b343804516f401e39dd35bf1ea4ce4b0ac56ad006d2b066cbe17e7d076450cffcd5a8caefe0e52426d041c7fbd1830807255730e6a60467da5dd31fa465625df45dea4bbbac3e94d870e9c3f682b5a40af85d959531858182c6f5bfd32cc9bd73bb06abad652c2fa04d723b24b46002511b51e42f52a35248790b82f182e1f4042709e0f164c2f0d978a19d694496a59743278b84fc9175e79cbd017d8e9818b3c424b125fd8981806aeed064636979b0a70f81750b514235442c77ff592bbf8251ab76f3e0548dcdd95e5501d5687d9980ecd89a69f2973c26808a22106822a404d437ae973f79b50a60a1aa024b6316d311bc0b348c193287f52c1daf6fa5dc81423388998dbe51462903a8a26db331f713cb701776d7435da75679bc5b5e97f25eb226f0d4c772f61fc8654516ee61c2a61d5ba61d9b9a394a71136e16bfd73517bc239b318936bb4839c103bcc3b313ffcf7775e8edf991b6db17e597b3ad9057929ed4a2e6b1b00fb3d9f000e4512ee62ba56a60b548f45bc54803704d974795d5e1c3d8b4ab61b111020ce7bb30caa990038ac8bb694b6294ad9c061de4ec73e1e228cbc0c0e54e4e67f83fc0216364eb6562a8880455fa5c119984cf98ddf21b02cc0591b605f2ada414109d6f3c330b2e2a515b8cf8fe635365c74f702d83154d6c58de0f3325fa7564287f21e8e262e91d98035c740b8c88b7f08b24970205d8ab6f524c4ad4c18ad77f51d1f63eb63a57fc2dd52373a5320215dd1a7823de074842b390c74480e53d7070c09b3438652e1931b6b338bc07c46a0c8e00ace43220c3daa4d06f44c48effb19e30b09f3eea5bc629a54fa48ecc97f8c9cb7d6b43c90ad2f884078130a4fe34de3327c37ded370f567bddf7f269b60146a094a7a766d5655801a84a036bde10bf05cf0520a81ff7653bd1848634ef5c172df08349798bf77a8ce58637dfde8c1b4e3029738361f0c310e2ae358adb8a02743b4b09598fb940654f8139a17dfc9d4db796cef32bb1a23253d70f99413b9af3263beb69325a96e512c2bb7d1d8eeec7938b939a54f0415e1cf1b5ffd9ed7e798f26b8ef3c50f6106c541fb3c5ced880ed1deedf5ebebb48357c77e3e8b9c409ebf1a1ebbbc771c2907df0284aac4e59de91b3cbd5011b8410984c4816ce51db92bbea1db4cb59f22f3c820fe155c25edf2d3c73bf1f60d37284e76fdfbf605dc0bd312b62b217f67c1dfbd26124e7032473c39ae90848f2788009aaf7390747badb3462c60fc05a6454b40135e23bd81c1d16242d2e577d65a262b4cf52da9b4bca3a5ef6644e37b8beb5d7a38d36e4d80a1a92cf5fd7e19058c794474556e650dd58459230f918d5eb8bdc6ea62055e1d82bca5e5df8ba5f631113bbc6e4613f1ad46c62f110fb78e98f704e102592ac3b7a64a92fd199cc8d1a5444df458e8a1ba7f58a2683cb120486fd4ccf669cf292e271e2c889044d4abe26552c2e83b0edc10cae3cc9dd7f15efda6bd94d221652941781c65af0a85c2d615d79744da38faf91564bc0d56bc6c1150b24355d4fbd71537012a256335ac735a796bfa4ed41f046ebd6073ffd6953443a0d9079528bc5097725d00eb09260a9e73f8f3863f1e6d88717354590f773836ce47c8311039735a2baa2a205dc1762fc285f5ef6ea837a730a9f405e5c46b35a32bbae3f60d0a0bb74e205e143e374911cce25ecfce596630e9685074e8459d5fa435b4eee92beef5932eae9e7fefa770c9429f6a6e531aa311371ee39b3bbc7e7a56e028ac8e3d07797622224af27a42fa546d8e3a738032a4067c9993cde74bb27e4b9efca5d4136d3f1dfec189fe723bc27e6d828983a72e4eda616296d7c8aab3dec4e7c22b3fd6aab7480c58f074022c5317209a05d5cd0c2fa35e48ea96be9d450a119950c400b00f8d87ac6a72a775e0bd5842ad8f21763fef66ac35fa16763a2db310146e41f2b0667b55f34911e0cf1a8d5920e82435309884269b286f32936e8a90984d0051946c1edb7c1b442b7ee5e0dd7cb5d9f3d4710c1f71d4b96364e23c430f4608063c1f65e4fbe9cb1b01485d104785923e12bdd89b7e102c76df3907a29fa5f79840bead46553b8dbaab975b73d1ef802f9bd712f54a8fd3445a19837e35c655a882d96765db547f9bd275bd8b4336822662ec16405f3fa3c1e0d2e7f0d3fccac8c55a19f0eb488531e600e1982d22145fe4ebfd26a1c1dabff67b5b3514a1600da0dcb2c671ed823fe829f72a787d0219f0e65483970e94f204c2264cd4b6f3c89040abb15728ccf4fb4ecb37205d01568f7949f42ec878669909ef96ae7cba1530d55f4b1b04b6dcb6684e0b83709db6d61f242922ff2be865770c3587fe1201dcab3be413c27f84e3d5c1b01a52dc2d1d8039190a178acd0deef5737358a9929359b205f0aeb06b55811e85dde84fc061cc0a01169cd42fa8f2ba42f32e774f426928ef477c44e1907505e5e40bcdb900520229fb6238f956ec0e0de46839ee77ef04e691c23118484afcbdd638710bceb83e1dd11e325a0b27b6aa80760fe18033464a39f96b046292ea3ea5237edec79df17a1b9c13f7d54d72753f0d91767a0ba844fc4a465320ad4403c182e5738635084437a2272490dccfcca95e843c1ec3e697f39ccfe0d3d50c9873198dcb2a3e1755083b9a5c3077d14fb004c8c22c6affd2259bfb46e44c910e624411dce37b5c7dfe6392f0b9515e2fc27b8a314e7dde9ba0c4b58dd9f05dab6a74227e0a6619572af83bc4d75c5d688348759dd062ffc9153f6e2b58e99fca8b6c6d2a590dd9ae1894a701b5b3f9f54c1237263934a5bf2e896f77e35eb190835a7e196fbe57901a30f54ad4fc7b0f1ad73eef4510b8e82a5d756c5e99d84ba95053e81c9bc8d75c0297bbc48779d699007bc06f1b1c105ed0705729e08d5a72f06be91db02ff9121b984ce301bb9be3b0168960a085859ef86b5bf31af66e5e394bafb3ac93afc4551b6b50adaf5acf63c88d805a62ce84fda33669baf11ff17a6d7c3929611f3b3fb4848ef6fdc2e9d762f073b896bd7c2aae0760fe7a0defdd94b44c94a677da2cdc6bcc10088b48b0b147cd388357f5ed34642b3a4d26e1eba1d5ac791a61865fe36a29f2b2f718ca0da13646edcbe0764311b01acf095e83329da17e131187c55d08c85dbed929bebc41803a046a6e03d8f4740b1e02448eff86a56a3536eafd5ceabbb8efef610d323751bf0e7e0e282c8b78a10b030a869955ca86622c36f19f80e4fc0725d6e894b8c64e69547914ebe5736aebc779b204581a7835f2632c07df96004c1acb3bf095e95f5be0965773ba86065df864105eb97f82576e23f407091a1da36c539551f3e25557db77774b2620b99a64e39f254b69da03ea93da89675cf29b72edcfe643f02979d9e838a62bf88667c5549c2fd9a8a98063901434e0cbf1d2570c2f2d61a58f267ee58c97776540ebe80c50f512795e94a0581cb65780bd5e6ee2bfb31e382f0ac52126aa057d06c56613dd5d2d3be0094b946055501f58f15982c6bcf24dd4eaae1b9b20e3fb628f0aa80494d2f2220bbf18bb693e181c26405b2300582b8da2614cfe33a58d44f415ec1b6b859378369a774437d15aa954d21abe0a28d33bb7b40110d74163e8892eb94263bb59271aad4e81b58177a91cd3fce51a64605700861360896f3a1234cc5bb95c38d40ddc6fcc8e788dafe1101743d66208f4386608e816090101e2faf87c8b75b253a78e1eb19ec346f43170da31033b185e427a55b9237cd99d0c900890fa171049c95a3c2d4fb363718de2ea1440acd877ee0206628d50eaa60326620097a30467202c060013ac8d363753745dcdd738f90fa6829d3721d7f850274553af4fe7f7e648f03a03800b6f80fcb00ba2d796d9e0335805dfdc0d749fb8495b4ce5136843b9e157560136feb5eacb89ced1899b6a83ce6b27b4af088929afd446fdeabd3db81b3ce122d1ef463c16b9e10a40ceee5c213b27889762c37c383cffe60977711757fb6d54b5f32e6fc3d7f7cfc86f831bc59a50165e76c0ea898db3fa69b0bc851c42ffa8995126026eb859beb80c78883c2e07531b53c31d9ec846c79c7b8f086e1fc2210ae8f26a4c8d90d166711fe9d6398b77059637af17b41d2da85a93e84e86cd38fd4e1944421ae07e2ee15178d9d065046b144e74be8c68b9ac0bf05cb86080bec5d4e24d37de46578887e16316403c11f703b87c6733cd2521a6b818eccc4b412144e449c994d205f2252479e7d2252dc40475d835769201dd0282ab32a99a16570deca4115f909f2125646ec1102514e1008d065eaf1f2ea018e2a5f69f5d5432e042e444463d607df0d355a8b36f54efbaee7e9d427af1beb6c367adfcd585a4721402b41dfd43f4574aa8215237717acd253222e8b47d7344903f820a65f1cdcba1698aa92b32d61bb268c046506cf8c4994b3ed4185b1d5994acc190eed46d712b81b81fd15570d91212e70bd015cfc526d0e1712deefba957f53ae0f5b30b288a5ceb8a5ac1e5df911969ef5723f38f0da19f399f48f38e8fd82c9104f1da5cf0374ca198cc8e2580d55521ef8acaf0bef8586f8e56f934ac873835fd2022ddc6b463bde0eb537cb9a6112e19a150e5aa047aa55bcee5573ee4c719f71bd28202094d153f27ff465c67c85b822dc0a2ba8032b385f708ed2e1ae15232cdf8204cff8bb098bbe22d729cd7a556cb14134ee04897604a440da52acce8ecfad40f9efc6cb35e0856fc006e153db16b6f934e315f5323b81d0b9b5f76435dd1c715156845345bd13d783b46076fa369e502f2558c857d8e0e355db7e3ddb23db5345d09dc9fc649a5fe3df80a81fe604da168c3e90d13579d657e73571039afe63a602b9ae1ef61fc39963ef5cb61b7ba72b09ce5390fa1a2cbb5a892771d54cf79eb09cf25fb4ca1cabfc50bf2a7d0419972b1d377202a16501168d6fafc580109a5266b4c9a29a74c02a0adf54930016abf81d52651fbcec3937dab3d4c2e79dbc44b43c802c21d3a620e8d24ba3c675955647088b4df64270fbe9e4f92099f38bef37bb8175701b10e047744daa5657257d5045c0e9a11e31547bc06d4600da72494d6fac5d7c779cf32c0474884c17075361de0d73bbe235fc85108c5bea52d503997c3c12dac535f95e75d00bd120a6ff9cdbb38a487c8154ba9fa229d2d392565b28cba20d08a09c3468e4281b6a38872fbb230279df90966bc0a0f794466b306d3bb130332663ff51a289b26a032d0dc89ef1b3b4c0c81b9b04bc475045af7971b416c8a9daaf65ad84ceb3778275eb7435e6880a8d2f3d3221f9ae5b74f1b522fce16f1a343295769041516a490a5e2073e222b226267a9ad3cc7562b0505ca429347c9f05d05d2533dfa626dc3cf57245e5f64109c4fc2cc6c6d856609c454e034422505108e21bcd4cb410403bae47b8113ed92ae74652eb79950fc9c8d1c6f3151c70ebc0453e3a45b331c274c15276b64a82d9157682322c6b2af3aec75fe326bdeed0cf986aff789e00a147b84e60d4da9ba4163ba1b7bb6d3c32f570648c925c0ba609252ab4b6de91068abdcbab0e962c1754806797b04689ada13feeaa537c4a0b5c67c204915e42f4cd5a05e3bb5e88bf70c9f8a8d093da859169a019b209c5d538b423e5f3b451d39fbd9c38533a9dce4a96d66ded7dd322f31345721634a24855b8825812086131710c018cc5ea2c86ea439c5ad7e7700a679b3afcf5e9fd1bad7202e2042958254c6e0fd48f0cb0788ed9c2abd8299f6d8bd7746a36058daac6c578b44025a309b70f4872e0d781df7f24544f6d50e71c9cf3dc0ee96c9428d2ec38fffb4ef6ad968685d01624af54220cc4c70f4ca3ec8ce052849de1c715d772085d8e22b8a12941f46250eedaa0c063f6c40a8c803488a255fb59f7a99b1664429340654dfb073c21529f71be14bc42f573aa747f6574a05df88e44ebc61a4cb3599f234660bdfd6c95343ec3340f234306be47033df4a18fb433abcfcbde0ddd890b0cd15d87806a65d9d05ec17203288d08e1a0d87a93f8a09a528933e9bfd2ebed8c4a8df62d20bd128b0cb00f4d5da3520240ce9aef708ae2edd721d763085b65be16652d7c9edad73b700b25ea02d4ab0e18d4637d3a2d22619a12e92717ea8e3f276f8d4ebc03035cdd1a1ef30147be773eda13ee45df912756889b8ab8f567992a55d1af10278a0eb2b623b7b9f345e527f37a4582e24affcab2df8774ede45be50790d1e15b49d21bb07608d17eb315f16ca5069c4ec13b326201c03a4809bea24405f791982c318c9ecf26a26c2c54bac9d6d71546da25533f16cbe076e42c7c7f45bfa136c2363256f87268e8677f986ffbccbe86407ee40bad228ea65dcc1f795a7ef514c89042a50a25f9fe3f7db55f19827dbbfba3b79dbd5f47021c53d89c31581e764f0cda76d70c293c07d30106bbf19c00d3a366b5a109cf7fd62a575fd5dcc4fb73030e2052135f401668cc9bfebbbc61dcaaec111cc1ffe27e8a4c857be15fc72ce00cf65b758b5954caa8a795556111b9ef84d21711901ed722c649bf26ff9ccbf74f5c20a75741200d639c1e8bc79f802ca97e076324186f710b8bfcd569b936bb968a7e3f8b319216f839df4b01c94aeb580f3b7b53b65dc6396c67b6e8175917857b97856fc56ecfc3137f078a7e42a62b6d5769855b6d1ee9c534689fbd7709cd818d9135538c2f2fb5218f275eb7351c1d532d46fcae12b18d9f2fdd5bc939c08e9d64f7a6412d50598cf2da61d7ef14d98bec2edbf1b9535f63741d028d7c73bf3429eee78f120d0edefebb95bedb703cda4cce397d487e756950acf0ad92ff869b04b6f127c6583e4298c9889d2f815da2403f0801b2b979f30e22adb8b60dff13711f96367cd5ac712dcd01f28e08eca80f42ac1bd7819a2d0fc4ea02b832c638d3da2471b642d25518c04c0524f8bef229a4d980f4d21451e962509f627f98d12e1965c86f027a09772679002c58b2fe20466cd9feba13de642b9540fa8bb9137688bf591ae0400fba5a54797dd33253a34b9eae80d6a071fc1e688cc64d88500f02c51eebc253544aec566833d433733d9f19a233eac63468cb1d7ab25bc9e1c20cb985660b42c46ebbc4c0cd256728720cc40815cd5ba50048de43a00528d3b07ce8e1a1aec55d6db84b7d6f40fe18a0d701dc06ea8d25a6152e0729cd9a0422439fb27818b2ed7ec59bf93435d351963d890b92ce6debd9a1f017605ec2d37b82510caabc7e6938fd59e91f98d6f0d9a46579eb303867a757ea98f63249e80bead9e410aae04c6c09435b278876a41e0d9470e215baa2147b3a34fb1fa9a227e69f515d33a7d8b34885cd5eb385ae43bc2d9c55bb29228aad245de72a3be82fdaaab1882ad4e9bfaf971646a52ea8454777d42e2c8aefd7648aa05ee2758d21791bf0055eeab6348bc2e005ca6aa2779d43b915fc3933c000218a30f7b8267383911b3a921aa1cbd43ae2c2ba0941727dde45f80c763bf6ed8d352900065e9622cb295d48271bf65578fd8932268551774bf7b4ded08c751cc97c0ca07a5192bb541df7211781eaec0a8e5b8b36d62f63b820259a2fb483af58b31d73b5c414c165b5f1155bbd763bb0c790562a7d2f3d76bf79739c3c61d6ea287647b5fe1334256f1d5f905812449cf7e622e72f615a99d6504d67045ca12e6bf60bbf88b83feb507cd037c120c5f0ff4f5726bc8190735ad0075a4a9e71735d184cf0ff3fa341164fc0a5e3971fa143b6eb7f56307653e125cafbb07795ee0f1c9fd7da067cf6f27c3fdb0046abeb1dd4770dd6cc70ee78f5187386841040ee13c339aff3b039d0bb6b3f481e28a5bec8a2c624468e71c992623f16dff88a5fe7e54ea1f27075057ac5f94cff8f405f1cf0d137ee4c0c4b1fa6c770c9e443f14d633869415e1007138316e564b7f4db70b6af988e4f58a62e4ccb5756d77a50d9082546b4f6487fa8781f8c49e78380c7a7fb760cbf2c8e525e7fbb3d3f1669fb2402b51a1507a9457ae0469fd8a39c09823b02695eeab845375a912831f358f50bf378e294779cd695acfdecfcb57121e90618d87c5c399e088bdeeec1f7520b9e08c752f031b7c0571a3a31b9427e4f111c0d69c1ddf28de7ffb823bda0d3e60a817b93e413a82f9a70a200d089f6e712827d125b283bcbedaf276e580b5e6a3b49fab5a3845aa118d404d02d83c83b748836b0eb15a522652f02b5c4e7b545cb907b592f4e2a9f7ad3b6a18b7c62457cf3f6ea991d8aa18d7090d9aa961daec106aa3ce0df956794eeb3ec47a50b8c2096a5df131979733a738770ae822bc366e024bbf5617843317ede3f33b7b7a9f1597eeeea3148978a1121361f4ed71e814a15f550ce6961d0230a2f1ab9b7cceabdcf202145b42079ec8e7009cfd6bd655a04d8eef451ea0617d24887992661dae5c72be46d03ff1413251d6bfb3ab7075670a4fca4d0eb6c5c70f758a122689952c3a472b9e734bf231658260e54e271f1d996e46fa4633661233872089a1264976f87d99a1783b6c416f72057fe07643046bc9be3641283a284046a7ca5fdd8985b0808f4b36d60bfc405372ef453beb54a5ba76dbe388de44177298552922181a03b9e386c05a50dc354b641e2f0f4b04cccfe468efb7fa996fec7038b3f46faf6881579d5bcfd1314597fdc4101691c7fc37bd86a114d4e3f9a8a788fdd3583ef811844f3d2cbe218432f72e2099f6eca8d37d25ace94e7587cd02938a6d19e58c85aa471bef45a74021f18b68fbbf7709722d621c1630d7971e6a2eb691e5fbf56637c5bc4b500fcfa506312073e1c715a2528cc56914a05faa403bb51f92541639fbf23ee41d59d1a654318d54b39d5c673f40de741f5ba2059e0fc269d1d65bef02e69c6933a5e1f85b3d1e4828231c3e344418b06fe870e34a7610d0d1b97ff110f00f98a8fa0945b9dd7dbe117faf41cc7f3f7939bb800e821cb0a387d578b5cfe0f0a6d5a2a8237f6b21eda8c7fa514ff85181f739491d9c895e73b18564b3bbf50bc9f2a78716a99d65aaf8843583566a299c83f78e7f4486cda378343de9c4cb473d485e052f8c9363a67d81a7c0ac6e0f55cc25454e06ec7a3e5c8d1fc34b98878ba0bccbd43955ad785e3dbe9f73bbfe63b1c66812b049e2e085f8db86858e7bf50d519744c7b6c0cbbcbdcfef806bf6f4cbd38b9951c85cd569094ed98a4ec5ffe5adc17d3a40219c7dab2f2e6960422f8ef21df192b091f95e938ef8ccb7ae958f848cd7fa951c2e820e47420749c35405be088bdc41f296488d91671bdeed96af874e71795c394e930361bd2af1c8fb27110b2802af11410545d0d3c8f497abe38a497c595e7b77664ed5461f0bd9f22f59127eaa30876a05d60f9a1c064fbc1040e33eccb411c491ce7ecbcf6bd2159f6cab7ebd85b4716d4c1135e12cf6b83645651d7c6edd35b07ff1562a76b8b5cb43c153c4dcf734e9b0fa7c35597e03e95068e74afdfee30b3fea917de283add8b943cacbcee72ed73b2dfec664f246d8f6f2c285b47efcc907c37b404db59ca05aa55fb5dadf1c295a1c7324b83fa1ef9959fa90b61464508170e22b7dece44a16c573c6fee4110d5765bc7a5029e801ce2cf89cf9d024c1776bf729da8309e55ab837cf0b46d844eb12d5eba2fefddac2cb6cf4cac0204b66df26662dea648cf265e2f9fcda2a4d93c3916377aad6c1c05a877c52dc73f7ef2162c321be09034f6c1dcb231fc36811bf0b132f63d8ad2329f69457bf351590e3f7e70f0b67def5fefe29de0d0c020bd919ffe9bf73bc27cbe0d8ea683fcce702dc2bd6eb8b1281a4f0884725788d824a6170a2ea8919f4b1339ef1ae728dc08a3e9486ea35b5aa8a6c236d131c8af40872c47dc44ea7c2fb5d8d41610ee4574a7ba902ca20c5ed36a71365ec6fc41897b07e28efc4905a0d97f45cbf2452e702bd9fd18130fdefb958d491dbc1e77f1fb329740ff9f5cf565b4e9053d363f92745ccbb24dfdccef4ecd99fd43505192c5ed8c8bebca56f6ada309e00cbf1e878c7192f10dfef56afe0131134ff3442c34d98f8fee1afd741706d85389f7c4aec199bf3fc45e16c57b7d05296b7d754af2f11441c62d70d4526394629f9bec184e169ff9613dd08cf55e58e94ee3b3e388c1e283fe60da73313dd1e3efae77c4277feabc7f873991c49c798520dc2affb92a772253fbe8341fdf59e293bacc33e1fc3bdae8d8ce2069bc2a3e76000779ce34123d1ef57470bb6af763e308b23b9b031bf2ff5757bf5c88c04b9c90c1691d019e2f65e0007a97b112b6160df0c0443a017f701c2f2442a8fbf549a36fe9ed7861d7d1cfddcf7dc5f1a8e458a02aa8dd1091ae4a1008516e9482a39830b351107aea4c8adb200a1bb85c5dbce28a0074c419ea39b03e13300d283d084aefdd14bf1779b2478a74099bd46ac63171ed015be45aad85e1d1e5b1f71ecd1ee5a3232bf0b42d48a6beef76a28d5f28bca7538165007385508a1bbfba96c9ce0e58b1f12da226b23e7085a09422998f4f77b23d066204c52a96fc12589d226d872851a9c9c4c9e9e84a3da2bbdd8a3a6e58a78ccf287835f03a178a17833bd0de3f820e2428e01885774a91f4454a34e80c13e73921c7ca83c58055229ce27a60ec4207a8465becc113a3709a1ae5dc12ecdcace204c7d735c78ce0bcb7ca9c32637ff9d68fc5aebfe4a9d59d0713670460ea031238a2105c20f20796db5448f4a722d89643c0b5e2803cc1139c72007fc7c89cd824328f96bf04e7eec59f75f6cfde86346db450d2a60f448db03cfc94949e4054e21bcc9fe6da14b2c2db21f4952b4c85fcff3b72e0627898b009ae1d15cf768def56addf6a183aeee09e441986aa859a65e29f0f9a14d0deff2dbbb6cfbe69a6bd6232cf8607b4ba81c83ac1cbaa42830103a788b27bcfdae4c0702ac7204faf8eb012959c693d9e9143c165222a333f185f3aad049b80bdc2e4eccfd8eec2f8f01c039050c394a2095d13893f6a645df7dcc6a7c3954b47432dbea79cc50c858df1e32d62e3f4d5d26eac6f57b39353d3639c5a801fa7036787860548d806eef495cc48e00cccda6a4ce16b35d82da415b0ce5a0b8a40aeeade3ad52e159ec9d5546069fbb0ed05454fb4d6f83d551726551206d55eab5dd024081d426ed010a221fd9d1138448d70e8cfd0e009caa1cb3f47a220cc09e1ac1c227697a7a43aca862ea334f411bfc8e1766e024aa1e04c3b03f9242a0856b2de7c7db49dc235d820a774e69ecdfd2d2bf0f809007c1160c549ac93d39ce43c8fd64bd23c1eb20733bc4db1d40b5088cdd6eedadf3131c7c06f6f8b6090f4fdc0266970a7506316b54cca4c17ca320cd6b9081af3ce635ab1f69f0a51abe712e01254ee4535f3cf78de3252bf689ace9fd6f3b18c329468352506a23b1105213696a57d9bf1db1a466b8be33b09db1758d7d2b67334052509bd665be579ae76044bf5e4e7873d8de811cc19820bba3beb120722b3ce70a0d579335709dc9fffcdc92ea77af46d8bd62bf9bcbb2d0fcd4435040f644c15fc89ee729fa983fc825351cce39804b47e79d6b663015ce7dfec4b7b85a7f0b654e20f4f45856470707bed73e6a6072926d4d050f079550e3e25d8785c8c7f8bec638d8141262cacea4e5f36072ba892effc373ee8109cc211438a156a463ff7269cb42417e7207b5f6014179cdc60a6843054afc3a9dfbd188c4c86f02d8f7fa2098a8649dda2dc4e9a87c12a22202cfc925c38c87739d58ae607c180c24fdd64767e45ad0a465f8252134dcd17b49425d690634e3acfeaa004175c5504206dad080e2c5f396504a123bfb66caf21564a12cec8c577d835796b2f4dfbe701a9a10421d729a4bdeff2e9f777378a72bd393fe3344e27a1a97d4c10ade942ad4d0d75e1784c3798ac48a6376834989f719d03d5bac42cefc307c73d2cb7334a9115e14944d446fbe05abe51f2f9479af666bfa66e96ca87e33be7985084c24c498e406f19edfe9fcd79d7e8047ee180a9385ab2ba68e87d7f9256a97dfe4cf051bc1277de63e30ec205c4171907d6d416621be53e52e0a333609b5173451c036ac74ac50f5018e0350874af13c094c0706b15d09de8130d8c25d3e0b1b364edb137bae3ee4f719ac46dac4868770f8c198578ecf73180d3f38ae787cdbe9425df16259f681020ce783e349fe6d3576e2b9361223e43db7e48c95ca252636b221b9d4040d0fa5eea91b5658e92b6a5876e75eebef55aac3261762360124d42fd8884596908b87567c274c060b75deb1a15960d0c948a42a74287d55311199ed180b172a1a0c855aa2e43541516879fdc3bbb0bb7ecd637fb57c5dd877636025ef8bc38722f97d493e504824f882aee33a4507b13a6978f4e4b61aa2a203f56a8c1a69284f9a820df4a746bdc23663f39985c3dce18e1b7f6fd95d1b7e5ea31bc2460a12287cecdb589eecaee0d9eb2752cb1ae54024bc54f65c049fb834fe3d48f47a298e9892905c6aa0bef867ce50683586123502f9a7e5e45b854a297881481869bc035742f8432854bfdf78036ad0d76e9b85d88c93730c0d746c5e4deda03192c1d045ef01f017d9cdf696b1c651022a221a73053c2bb2c3796372c12b97e5ee62a53097046f027eebee95a836b5c1c8c1e81a47f84af8103a55c0a3d5c95430dd8e8725599e7ef1ed37f8bb69d8b494592ef49b8afbb31581e5ed7ca3faa430f272d812b91d4319752894ad7fdeeaf64defbab0e3dd18daa8ce5aab0d08087f1a885b34c1611ad80e7af24d124c04e0bde5d259ee925ce6120c7095908dd34ea2859bc4100a761042734c56289edc7e522a3389a2f6331aa337a05883110ab0e73f31f4e98d67055b2d8e0fd49adaf89f3897045f8adfc2f5a9f2223ce1f45e1b4dd225150ad032be70e0d12930e1e72061721926a85b47b8fe787700d2bf77a3b034b84c07fa7a2b4c289c6bcb8110be582f6e02f7866ab44e072beec22e34100affe6315f7a39eb4aa9801c5c63136dde859a3820707f1271e24ba5118da62ca07638141bd5485304a4d042eef450a82fa4a49c37affe4c0f4afc0d5e36fb420c2e3e56d6c05371bd64e91111d794599e6eceef29375b79e32a5c040b716b5f0e9901893d17d363f9da18904001356b0a6e9d261f42bc50caef734411589d49323f70b91c3f8bcc8ae8011a4009466e1392cb760822721c39c6c2dff7a161a0b97ac7633efda4f154f6040a0fb78912f553793171e2b4733f7403b4cfc8969b33336fdb4770101a108a94c0470df25d6f5c2198742fc68e976b58ad775bfeb2a8ce0d065c6254c330913d0e17a7b007f84d4347f3545343d4fd2ecb623c81f64a058915967b1d89dc0a1867edd077ed0ab9a06562e4859c2e5ca2514fb239d8493619ba71eef7e2d0246cd447323c84a669aec33cc4edd3061ec7ed040107c3431f0c8cc07e59526b0267c1559c79f6e36d48dafef4a4d6444e91cbb4d8aa46bca55d837cca41b9bb35f7957050edbe225abb5696d4373ffe696255490d1dc17182c685ff8678d7382a85ffa7f10ecbe3a9ed084dbe8d45bedee6b4f910cf07b999ac1a359a5dfcccb41a1cde495b4258b370f52b01bae6788a07e7accd5b99dc8a0b657f794a8231640cadf72c20e65af9f0bc20088a24499aa625aeeb36cde77f9ba3c4ef35806364abbb387d5b9a258a896e2a38bd39bffc0d30f46f9b5eb1420c2cf756e748248296bcf4681e229cf5c9b86acd53351ec3d54f37454fd96718052f62368db591003294d1b8c47bb91150a8e40e77590aa823a53f84758a7fb899e6797e454345790d2f8e717099039158836eca11e59e3a1284e03302ff7530f10785ef5e1f6d6ea78377e3bbd16ea9ae7c6cc5503c4b34bfeb44519d209a48d155a5104a51de5da05594672a58d2d8d7a8459bb4fbf5e5e5b1e647e371146d7e0cb4f06242ea94ca63cf9870dd762f4848eda03522e90fea4059a666f0dd3b8f76956acda6a80439c93fa37564f0254428ba3516cdd3ecdbd6fd2fed6c8d9db03dbcf5c3bde0ae9a2acce0222bb279f7f6951e3c154f0a5e8e77058df199455152abb7e10a2939bb2a2218cbd1638ab3dd83fe2e9977ab2d94e842c72a5f08d47f4c44363f249e0caeb9b3274a7ffb98494123bc0264c58ee3377fc5998668f365cd05703f0c45c1ecbf1fc266bac186eaf85aa6b8985e4b0b25c8943f081724a533e3a0121ebfb25058b120cda70b922e44dc91c414bc3731853a82ea0abbb94f326e6718b8aecf67f6cbb9c65b06ea9167efcbe8582cc479bfc360cbb8290cdeba3acc26b3e9739065bf43f363d8bc5df3b64bfd5511f333dc978e423fa6519426d0bab46dfd63ef1979619f1fcb597b2934c36cc25b4dab85de89cddb204a2ee8f8b2d91fe2668cbdbcfdf1b5d248eac1d7e79bc35ceb906766faebc19abcab2aa0ef4f78b87119757bea6c04ed4bdcb73dee3bfeb1310f43c1478d33115d41a4d619e3d3d4ba8f68a77619c0e4fa85dd040fff7b46eb52d120d60312765874f5479f0d90ebd6de0fa9ded1ba307d120075cd483cde4e0bde94acfc8f2bff890a691a31c8c32f116efc3e669ba2589c6bcdc2fe099cb25e4fe125328e7fd66197884a8a449fd42f6727ac174fddb0523a8299e435a33a90a3f94223577ec697fad528ec5e93dc78e242f410aa3c849c2f350e19b8d1b676d7eea69a39cc929d5be0cae8f496043fa5bbe639b34f8dfbdc95632acce18af3cfb78c561ca2b00b53f7458539743e0d513c4483f3543b8c0f2d9cd07555489190454cf5de8707d886f6562fe7360ab81248ae4ec6b0f39374f26fbdd173e163272e506099c2e55b20379bdc2d4295f57b319a5713be95e9a992e795960d917111e5ec298bcc16f2399b098a973cf12c7799ecf77ee4e0525e04d8f8a3af150c17dc7644de7743f99f64c40c524c5a105b9d1b3e22e477865639a83479a8f2daa532afc2b68da152ae378ec9683dd9ef06167223b002fb697d849b3f531537d6f3623ea7cc5ce3f499a5dacbb0772f635e1f2cf879f1dfefbddab6eb44f08048b6f40623249083534531bd797b3ffc6710c813765819eb53364f70a0d815693e570479327f8bf4b6f48b22dac58ebfd8e60382fe9d0143429a03646dd8c66176f674e096353494f45ebbebbc44f22919cad023db5314b61391cc333eb0137e4526e3737238c2a788853eb84481488313f2b981369f2ffc4e984e3cf7cb0e4abbed5e9a2c1dd7c17203fbb3dc7d80df7cbcb9b2e690515111366e04e70307f1337215f7ae3f3977bb30a105d3e8b0f50d303a6a133de15b7c40cdd7c517120927ad4d5ef3a42f37da89450048f9da3d3d65453bb368c8555c23f983d741de8f56f0bb11b6e2a526861afe9e71c8a3ea956d4fd5d6963fd6004d8971af107afc452eeca1cc798d34e37ce21a5fca57f44b088bc5956d8940280ab845fef9600c47a8e4e07f2516eafe7933a657848606499d75faa38c5714ca978a79db79a0af4b2221ab3dc438fb206533785c80385be51000ae7c04c5aa5389ff62f0956fcad57a2199277c26d27d56fd14691bbcce15f126804f5d53bd73bd2e0afc589f4e20c947c965d367a0246893cb1af06a9fbcae32745bb274558d689ccf13e8ad2189f8ce2337ea25e941d7294f07af40e019fe9a1157e93b336285241ce18230f6bf1fd6853e8786cb0928749afed782d2e4eb70614bf98f7dad425db94e073164c1cd21ee9e8d2a3873b2536ea2e5a60e23c214698ff64da834822df247638843ccd0777c4917057768c7f4d70da8956c65eb7d22db64590d3e3c514b7fb14b467ed009b58c6547c469124d2ccddc55bfdca55096183b506ebf62c163bec2981d271027e57b904e22e85048ffd5e06d903209f30993dccfdba10ed7db663d2d53105646f61daff0fe41a7bfd27076e55b49999129a0ea7e6e8d7b438ce7e678e86ba691348d70084d126ea840dee71358d7acb00906314e008cf58bc818a4e87a33c7dcabfb41036d1ce8279762aba8e577251452fef824493e08ae48c5ec47835691d8f66468e64d72f6cf806a90c205d60d14d84eed86df25be0967555f908b751c68afab6654883428e8720973e2423a74b4db78bb13483f6af8c5cc583f78430bcaeceef07735d6a75d41474ef8586b04846380ec9d5a758441d37ea1ba59f58b23c314b948f7f75966bf4db44520417a294bed69a2fcfe59e4c78581cb8f9fcfc6b4897a51dbc6f72eaf91a4c25aee8220636d8c5889ea233fce949149c763c80b2479547b7e8dc1724f67b80241356c6741bedf7da9812f84be4d0c07a28475f141e8307dc3afccaa854dbe1aa3f23f0c61bc311dd53d42caa7ba5694845f27e9faae129c8b70342401f182c34ef905a2833931753d8c3ee55324af43d33bf9272f14c89fda89247d6ce9f4186146de240206699ed59a5ff32a6a3611084b3a3483e52587d4e7316fdb4f07076e66fd8b9e780946613d5fb97bc308808932940cc5ec79d12cfe6cb29546b76654772cf00958958dc028f368dea276cddefc97e3b82516972553d43ac34a78aefc87f4509a20ff12525713a96fc1c28dbfea250bda5b7a97a29f7ef5119a1f7930f9a811c9281af9f8cb6b327640c14361727f8789f2e0eb327e870ff3a312a50652a945cf7a6dfad0c8dcf6223a87946835f45dbe14f84dfbfef9c68b9add1594f086aeed3ed7b19f8d82997d0b4e4ded0ce56d2fd083901f2cb0ab7599e26bfdc832880a5cb568eac7b1ef702f03cdb93e6585a58dc5cd4601f5a6f57aa51132281979f73b705af8f5e2d2482e841e8f1e8545a0eb257ffdfd6785c07b3e76267acd97379071c50d132e1c64caac51a644bb7f32f58427f8cc885268c5c05d47a1ae4a94920aea57f09ef84ed286e28aded8b68bdc7d35505f69eef2571a2f1a4ef908cffd403f988c42a254d1b8be4b51b8e305edd2278976a7df018df0acfe39116fd6210ca048250da715789ba29b83e62d1d3bb1231b7d408999c405ee100c837cc0be75c309c5aa1b9362a8ddf357b9ec260fb3f2cf07f4fff7c125f689db882296d83dafc166f3eb49db346192dd77b475bbaba4fc83048dec0f888037ee56a14dff3bd851423f4e53983a25f950921c5c38b4a48c3655e8e4446a0fc15ba3e98bc1725e0bb80b7e2c6d28b42589b566033ac3f2a6365593ee0de77fae90080d456f08eca42dc5572478846d1d0a6f20cdf4ede530502e9e16c9c645894159f81159df66d2d6f7b3180f3092e20d3ba81d2a756f182fdb0e2b655ae73abe404ad1fd14ca8d0745f39307281ea8607e3d1c46dd6eef9716b46d43a577d42159a4be7856f1df501b854a59507e1abee846457ad444da6da3af614bd3467303580e0a10e80681c1750d05b4ceb0def541099e150de26e816fd89d8aa84b3f51d15ea7b5b42424bdb49156675699377432b188864ce233a1a67f8d7f90fc448890a013af65f676b785dd5f2f61c7a9a678c3015e66bd5610ff660e7ad8190efd46952eb9a65e65571816abdef3276bf40fe78aa4f975b3c6e389417b35f0982a1e1f97604d2bfd1b5bf0e54281a2061d2ba482b7543782078f373f29ed3857275ecf2657d0f03218727cb0086e6f34fa205482fed2e046a9e644a529444cebb9b4c987922ff0595351786248a623d0feeb8d5fca6b77a1ac69cf2be571e146b0af210955448a97f385ed1062303ada66eba070581d388757abb31093f8d5b333e843438a48f11b3334436b9a911661b80bc2f7f7ba3f48c1c09f2b6846014b1e6fb518960e49b6c3641dbfa512bbf731ce339ff8c0e9301f4e825633c8fbbb26707c6516753a9464307a6dd861236bf01b603c5910bc297cb4a4914ca451c5de2a640b7e097357583447b366e2be5ad8fdfbae1c30847c21e405976f601f5dbcc8c5d2882797fca4cf3c466f776a383ee198722a18bb11a7a10b04c00b0577265e0153d61d9b5627f914969a96917f9485aaf48977a335047c91c226303b8f6f4cf42d6d0f4687c11b7a226ee357a0edb281da4b925141d5fa57d13dbfb07b25427b43df4d8b1c77887c68a4094ab67aca3409d7c69bb28294eb189134716294f7558a82e1c3f57712bf7b3b1b4f4ba1aa69c65e7744911562c39f76f0880576948f409e0389e34c45ec5871702b82e138f72a3ad843cdd070b1b517645ae06b823ebaabb92bf1f6101dd09c04c1de5ed1c0a4aafebb31cd4a7ef3826dae10eabf55bf03175a5061c95fc5fc3f56cee3057e1fdbf2fbfe2b7a3b98c1a1ecb23d4d2f24e76c9753b9768e728ee55430fffbf0fdf534bc65f37817b411e820740fe2b338dc4c929bf9a8c521d066f011cfc2edc25394601abcd10a642c5b7fed191e4ba942dd0d652267c1b622d9489072338fa0a83b4fb434516b6e35157ad3b3877ff5a843061242a05b02e57c67ab8ccb63362bb02da1022efd89f6188ff13aa6365bdb58da26c69518d7739b4600a91da8938756e12f0faa7267a3d97395c447704683d623a9fdf3b93df1bd9dd0db04720a8c9a213be08cbd92bea7789266e9a3f4860dc4d57c6dd0bcc16583220493a8859ef8860e04a7f5d11aa5fb4df1d7e514d9973b618369e37a4b31962a3e5ae1482de07d1e96611db3d59bc88c912a9a269e8067dc86b75904a08110640605162039704621f15492c092ddd37d078386cd910694808485ea9c76ad285e72cab7c0cff2052e2068ef87f46898eb8b5b79411be288c42b072b121c21a829db034c10a6cc0358b85ad1345b510ffcb57b5980cfcc002ac9110b450bbf407d9e3a4b98864843d59938909d6a4dbb3a78634f2002991302407aa01b1931d08f76eefcaed73dd0f0c353a78ecf8fadeec2b9bddcd6ae6106e159b372e1e999fe39d75ed7c40357cc8dba48473cda167a20427a077a0d36e42b81ba5e7ab8f0a0cb5c27dfdb24f82600c525a63f3b9f141aaf992c6b0d709caa984f24f77a8094a8f3d587a9539d42c1f07ff557f205d0027dd10b6397a4209f6fe9edd02ea6f643472c2ab7bb182cda3da828ed972ba56f0c119b2d5896845f4f7b70d7cdf93b0d084d0c98d4b0c49192b59c003d10b50e291d6810f403201a97ccdd50a8a7476b90619fdac07ba5483b4e6600239ea2a22059411ea0e1e0565f20d9d6e4cfc64b152dbb7907b973022f605264a22c917fb7eeeca83aa13d76cc31c9c8d712e8098cd062d560179e05f5e13dbedf5bb4214870255935c90e20a6d4c2c009a54af2e5e39bb93f377efeaa8eccf3c2429141309c6820c5f3ac4c7e721471fb6933eebb38d2ca41b1df24ac1da8350fa05213fcccaacaeed3d34cc54878f6fabba0d349d06d00f1c96e043a84815ea65f37abdad442048e034312b139e801d074ccbe7ad810029e776f457bc44b0ce83f4c1076110086f5c8a04dde38bbe9e14830fa00ec2cb3914689118b87e9e1da4921d0ef028fffc5b3be1226a91637fe8b35141b9f50c5938fba213476d3e3cd5163c9137939cb5e86d566428640cc2759a911cdc5a013bb0e4fb208d17d6cb07624fdf6147c3f6102c9ae19b2a0533a88bd53f99c5c935a6389f75e3c1092e26b0031ebf983ac1cbe236277f5ab7b4898557e5e5ca4e9d543a045c57bc9196b9d10bf3be8e8677aa7afc693ddc2669f5400065082c38368b6f9ba77258bcf4f0ed72d4f10070197e5ebaaf1d225ba828383a35204c652aa0b2a6912c57bebeeb400a307475a66f20584b0e3bdd76b1eaefc7838d89ada735727b0be5ca05334b86519adc0290a1dbf2d45e0acb3e266649ef9bb278bf7c860d8efdd944cc2146e5009c6924ca430a6bfd1e7469d76d441954c42202620283a8b4606aa2e11bc71728d10de6096e8fac164d080124c8178521bc87f64dfd11301b5717683d2df44da2fcdb3e22e649e71c94ae32656e0eaadde83dec70da5b04821d6965f27f7ced3faf30640d6acd3b2bef3f964f1dd98c477492e8362b5bbea0aa3d081217f228130a73f9cb85deec0e73e015cc209a22e8749160d4a97a853ddd78182dae4524f2868cf766a13593a4867df9e5c34bb01e72061bb612ed9bf53e77a20e58ef2a9445903ef9f82472680f2abe33c26d7be299352ca2381a62ff8f34e37b47fa36caf79042c50d6afc44d765a18416ee397146548b9d71087f83583437884cb48db0e40b084b171fd76632a8e96d9c4f1486ab4954110f56b3f6f9be80bc6963c25511342e88ad166e92a8a553e62354a2f183e18947b133715973ac1b288a64575d7012b446614e0d4d46b52240497d047cf23aad4f7175ac728191f3266ed5abd8348172798495e01f3cc019da7e7ba03d2e1ced2f479da5ec5e59eee19bb3a352f0cafde40b02cf3f9e13094a2ac691c5a4f3b41c3c2d136e35f551e476e263eb3b292dbf49cf40650d0991394c7dbd5531092546c288770fef19aefe9defa4a2ecf098d5c01c226fe6ab08bb9be863de1e2db3b245d8a005e9f7e3907d1928301113eadb80937c7f5450707b9b397899b8b312135846d4cada7920f979deb913d934731e4bd80dfddc3f815547e630c3f5eacc0a28fc479b968a5ed5b2f279e2ddd11c731b4e129f080d91ae022b71bb9eb905a09e4e7df4ea520f40adc00bf6752e6c0d581288a8e12b23ccb93e20431351d301beb817cc292d6b678fd6ded4f9bde8b9bebe8744d3fe7dbb376c024f4fd0bdc2982579cf80ccf9f8a7989c2864d1cade53eb2be653da833bf1394908bad50a1eac553afc4d5819709d1a3df4592bc8e953bbeea7645585cb55e50eb2eb0e98c4a8094a1dfd2a61f0ec924fd21c81ff7b3ae3c7f2191968bfdf551c392f44f71af9c0fae65edf643a13a3ab48eb57df31edd932c8bdbc10ea75302a94b4cee1d8aaeece5f022b2455cea1085a7308fd9c7a357450523987442e0c4502b156a940d18815148bb85e7c1f12b460f2902cc2595f94c794680f53c7a7bf48998d4cbbee45a20420a24fd08c422863538d878068a84e06399d49772f8a003c397d480beda1fc9149e1cdf669a278c46866342e2ee6f04c7537fa2eea87c0f89a067f573cfc95729dfdd6f1c314d1b8854cc8abfe6228f89a4e4e24d1c71a574220c767ef62ffaf4dc41504e458b3dc1a11a0f4845f4200a21a2f8a901b0529700d7123b7e17412eeea1e834a7e7f84e469d3dc39b8696389b489d692eab7350c2cc279ee8618774f5eac9aa4d8d657b2bb5e9a6a0001b324fe322c101bc8b97a487384fc883b96b3f0fe6743d158ffab72722e8a2d031499ece20f6ccf111fb3ae556b4b1fd074231fe39be217303fc791ce73e5971747594f28b5332ee2d60399f69effb4a9d782223087b1e8694911be31824ceeabf9f2023baefe95e2145f1b0a2133b392bcfd4d87eb0f2c57f81b51df3579cbe92777ceeebc43f2e7ec2c43e61f9b1faeb6ef014022d1f6d16c73235b8be3e0d4f4b2ff3848834e7fc462b167c033efefd54bcf5eb945c61e932cd053411e7abe32ecdee27bef7e55550881536a52c62efa7a42b15230d69f5f0fc71433387ad22888e58b741dacde8690ac6c38b703249dc093005c67ec9cf45f112160446f1921aee5bbd9f020f5d384abb0c9d7b1b3f75ae8bc80792c4dd6b3cc63d5e2ab3e5fbcc646996bef5afe5f6db808c0bd8d2ef933c05c27e68404407cf8554274a8642b0cfbde581ce9a4d4460f9c4bd01bc84845ab2f83d4a84f0948c646eaf345080afa45f319033d995b2b9b1c50efc044f427a9ce0a492bed353f5b8575caf834a184c33b2b2833d3d03085383a79b64a60237ed0981a33d2606599cdab4250eb25b7f14fa4d8d142a4c18225354ab1488a1a780f31744a77314c0654c01015dc58c95a9db31ed71545d39a054a8da9fefaa76faf47308cbd506afff389430143c3796ebf5959f5ad2d6d3cb0c3d239480fbeaf6b38b9620ad992a4b128b773695d864569e17666a7ffb70dc2460f176931a83e4786e2181d5bd652404eee57a907135e45ff372053c5be2b1f8caa12b7bc2bbb5a728aef5b95a12c5d3da85afd3a6dc0253cb0e4b353e9e69b804dfaf03117904b690c13456384f0827ec8dcb79ae346b0faed299c22948847556652f34c372a70b758645f829ec88f0a65e1d44844592224c1ed240e72ba8aa2e3139d8c6109c4548d36452de9cd27473f1f5c0fa34f2f271a45cc51d0e55fe861ff00025310fbf51bb659db512c7172b6605c6555224afaa2e2e2c7bdfd78f6ff310e830ffecc72953feb91f71c54d55389f22de2d7c74b5411c61923a1dc0d01ad3b58009859d56ce9733042a670c9c7b7abb4a1c9fcfd4e5d3028d330a7c9c97d184afa7307e28ef701f0b5daa4c5230b541eb2abee50d2f49ea31b192300321a9cd10b0f59c7f95066fd53cb819c29ae77bbff6d1be10a586cbb3e2c16aefc3b4df0ecbd378951d17276afdd60445beb87a457d3d102e64fe19ed5160eb94debcb1dd77228cdfaf5e1b1ce9e0b971607ebbb7adf16ce244dec6c6f0fc5675226acf83f3b892eed6402fe57664563d758c7099e417fa091916bf05ee89b7917e137429d316f1aa6d7a79b3e99d7d8f176e6be6fc0df1e9973957edca7d85d7b5ca1af1218dcb127f8c65a06632557365b815aa6c87963a6e4f60737bfcdc8497f9bd3110bcca75c5383ddd2162ab8ae776834e24927cfdb897597b537c895175dde1f2e555866666de830bcb7ee3d5fff6d62c265ffcc539f3541ecf9da4c8f649b0f70b84392645b628197354d53ab21aaceb2dce5dd073b5ecf143a239edf18497d6295d2797c295c2c274c11a24d2b8c07c6881131a2e3b7de9fca19f6dfa31a0d84f8fc8eab200efc2ba07b2c1e01148f3649d96dcfb501f0e10b1756360984a0aa0b4c25ac983bbcbf96c2cc5407db59231f1cb6f07edac3cfd650df090add94f4eb186a3e7b2c27a18a4f5e693877df9c1c531776913c23a9a882f9a24f8e429c031109f86459706ffdf2bc3b37f875fc0ed62a414bdfea3f48b5cfdf7cefda75507042dfd7f7d6ee4bf50fe1f958f528f73e0bfe4252cdadc1c85e0bf5600fce93dde76571d7773fed981fa2b64a4b5d31142fffce7fffac7dffefef72ccd3bb44efb58fcefadf995ffe7eff97494eb3ffef67fff5f000000ffff8a71c36e1edc0200")
+}
diff --git a/lib/renderer/webview.go b/lib/renderer/webview.go
new file mode 100644
index 000000000..c51e76bc1
--- /dev/null
+++ b/lib/renderer/webview.go
@@ -0,0 +1,374 @@
+package renderer
+
+import (
+ "encoding/json"
+ "fmt"
+ "math/rand"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/go-playground/colors"
+ "github.com/leaanthony/mewn"
+ "github.com/wailsapp/wails/lib/interfaces"
+ "github.com/wailsapp/wails/lib/logger"
+ "github.com/wailsapp/wails/lib/messages"
+ wv "github.com/wailsapp/wails/lib/renderer/webview"
+)
+
+// WebView defines the main webview application window
+// Default values in []
+
+// UseFirebug indicates whether to inject the firebug console
+var UseFirebug = ""
+
+type WebView struct {
+ window wv.WebView // The webview object
+ ipc interfaces.IPCManager
+ log *logger.CustomLogger
+ config interfaces.AppConfig
+ eventManager interfaces.EventManager
+ bindingCache []string
+}
+
+// NewWebView returns a new WebView struct
+func NewWebView() *WebView {
+ return &WebView{}
+}
+
+// Initialise sets up the WebView
+func (w *WebView) Initialise(config interfaces.AppConfig, ipc interfaces.IPCManager, eventManager interfaces.EventManager) error {
+
+ // Store reference to eventManager
+ w.eventManager = eventManager
+
+ // Set up logger
+ w.log = logger.NewCustomLogger("WebView")
+
+ // Set up the dispatcher function
+ w.ipc = ipc
+ ipc.BindRenderer(w)
+
+ // Save the config
+ w.config = config
+
+ // Create the WebView instance
+ w.window = wv.NewWebview(wv.Settings{
+ Width: config.GetWidth(),
+ Height: config.GetHeight(),
+ Title: config.GetTitle(),
+ Resizable: config.GetResizable(),
+ URL: config.GetHTML(),
+ Debug: !config.GetDisableInspector(),
+ ExternalInvokeCallback: func(_ wv.WebView, message string) {
+ w.ipc.Dispatch(message, w.callback)
+ },
+ })
+
+ // SignalManager.OnExit(w.Exit)
+
+ // Set colour
+ err := w.SetColour(config.GetColour())
+ if err != nil {
+ return err
+ }
+
+ w.log.Info("Initialised")
+ return nil
+}
+
+// SetColour sets the window colour
+func (w *WebView) SetColour(colour string) error {
+ color, err := colors.Parse(colour)
+ if err != nil {
+ return err
+ }
+ rgba := color.ToRGBA()
+ alpha := uint8(255 * rgba.A)
+ w.window.Dispatch(func() {
+ w.window.SetColor(rgba.R, rgba.G, rgba.B, alpha)
+ })
+
+ return nil
+}
+
+// evalJS evaluates the given js in the WebView
+// I should rename this to evilJS lol
+func (w *WebView) evalJS(js string) error {
+ outputJS := fmt.Sprintf("%.45s", js)
+ if len(js) > 45 {
+ outputJS += "..."
+ }
+ w.log.DebugFields("Eval", logger.Fields{"js": outputJS})
+ //
+ w.window.Dispatch(func() {
+ w.window.Eval(js)
+ })
+ return nil
+}
+
+// Escape the Javascripts!
+func escapeJS(js string) (string, error) {
+ result := strings.Replace(js, "\\", "\\\\", -1)
+ result = strings.Replace(result, "'", "\\'", -1)
+ result = strings.Replace(result, "\n", "\\n", -1)
+ return result, nil
+}
+
+// evalJSSync evaluates the given js in the WebView synchronously
+// Do not call this from the main thread or you'll nuke your app because
+// you won't get the callback.
+func (w *WebView) evalJSSync(js string) error {
+
+ minified, err := escapeJS(js)
+
+ if err != nil {
+ return err
+ }
+
+ outputJS := fmt.Sprintf("%.45s", js)
+ if len(js) > 45 {
+ outputJS += "..."
+ }
+ w.log.DebugFields("EvalSync", logger.Fields{"js": outputJS})
+
+ ID := fmt.Sprintf("syncjs:%d:%d", time.Now().Unix(), rand.Intn(9999))
+ var wg sync.WaitGroup
+ wg.Add(1)
+
+ go func() {
+ exit := false
+ // We are done when we receive the Callback ID
+ w.log.Debug("SyncJS: sending with ID = " + ID)
+ w.eventManager.On(ID, func(...interface{}) {
+ w.log.Debug("SyncJS: Got callback ID = " + ID)
+ wg.Done()
+ exit = true
+ })
+ command := fmt.Sprintf("wails._.AddScript('%s', '%s')", minified, ID)
+ w.window.Dispatch(func() {
+ w.window.Eval(command)
+ })
+ for exit == false {
+ time.Sleep(time.Millisecond * 1)
+ }
+ }()
+
+ wg.Wait()
+
+ return nil
+}
+
+// injectCSS adds the given CSS to the WebView
+func (w *WebView) injectCSS(css string) {
+ w.window.Dispatch(func() {
+ w.window.InjectCSS(css)
+ })
+}
+
+// Exit closes the window
+func (w *WebView) Exit() {
+ w.window.Exit()
+}
+
+// Run the window main loop
+func (w *WebView) Run() error {
+
+ w.log.Info("Running...")
+
+ // Inject firebug in debug mode on Windows
+ if UseFirebug != "" {
+ w.log.Debug("Injecting Firebug")
+ w.evalJS(`window.usefirebug=true;`)
+ }
+
+ // Runtime assets
+ wailsRuntime := mewn.String("../../runtime/assets/wails.js")
+ w.evalJS(wailsRuntime)
+
+ // Ping the wait channel when the wails runtime is loaded
+ w.eventManager.On("wails:loaded", func(...interface{}) {
+
+ // Run this in a different go routine to free up the main process
+ go func() {
+
+ // Inject Bindings
+ for _, binding := range w.bindingCache {
+ w.evalJSSync(binding)
+ }
+
+ // Inject user CSS
+ if w.config.GetCSS() != "" {
+ outputCSS := fmt.Sprintf("%.45s", w.config.GetCSS())
+ if len(outputCSS) > 45 {
+ outputCSS += "..."
+ }
+ w.log.DebugFields("Inject User CSS", logger.Fields{"css": outputCSS})
+ w.injectCSS(w.config.GetCSS())
+ } else {
+ // Use default wails css
+ w.log.Debug("Injecting Default Wails CSS")
+ defaultCSS := mewn.String("../../runtime/assets/wails.css")
+
+ w.injectCSS(defaultCSS)
+ }
+
+ // Inject user JS
+ if w.config.GetJS() != "" {
+ outputJS := fmt.Sprintf("%.45s", w.config.GetJS())
+ if len(outputJS) > 45 {
+ outputJS += "..."
+ }
+ w.log.DebugFields("Inject User JS", logger.Fields{"js": outputJS})
+ w.evalJSSync(w.config.GetJS())
+ }
+
+ // Emit that everything is loaded and ready
+ w.eventManager.Emit("wails:ready")
+ }()
+ })
+
+ // Kick off main window loop
+ w.window.Run()
+
+ return nil
+}
+
+// NewBinding registers a new binding with the frontend
+func (w *WebView) NewBinding(methodName string) error {
+ objectCode := fmt.Sprintf("window.wails._.NewBinding('%s');", methodName)
+ w.bindingCache = append(w.bindingCache, objectCode)
+ return nil
+}
+
+// SelectFile opens a dialog that allows the user to select a file
+func (w *WebView) SelectFile(title string, filter string) string {
+ var result string
+
+ // We need to run this on the main thread, however Dispatch is
+ // non-blocking so we launch this in a goroutine and wait for
+ // dispatch to finish before returning the result
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ w.window.Dispatch(func() {
+ result = w.window.Dialog(wv.DialogTypeOpen, 0, title, "", filter)
+ wg.Done()
+ })
+ }()
+ wg.Wait()
+ return result
+}
+
+// SelectDirectory opens a dialog that allows the user to select a directory
+func (w *WebView) SelectDirectory() string {
+ var result string
+ // We need to run this on the main thread, however Dispatch is
+ // non-blocking so we launch this in a goroutine and wait for
+ // dispatch to finish before returning the result
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ w.window.Dispatch(func() {
+ result = w.window.Dialog(wv.DialogTypeOpen, wv.DialogFlagDirectory, "Select Directory", "", "")
+ wg.Done()
+ })
+ }()
+ wg.Wait()
+ return result
+}
+
+// SelectSaveFile opens a dialog that allows the user to select a file to save
+func (w *WebView) SelectSaveFile(title string, filter string) string {
+ var result string
+ // We need to run this on the main thread, however Dispatch is
+ // non-blocking so we launch this in a goroutine and wait for
+ // dispatch to finish before returning the result
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ w.window.Dispatch(func() {
+ result = w.window.Dialog(wv.DialogTypeSave, 0, title, "", filter)
+ wg.Done()
+ })
+ }()
+ wg.Wait()
+ return result
+}
+
+// callback sends a callback to the frontend
+func (w *WebView) callback(data string) error {
+ callbackCMD := fmt.Sprintf("window.wails._.Callback('%s');", data)
+ return w.evalJS(callbackCMD)
+}
+
+// NotifyEvent notifies the frontend about a backend runtime event
+func (w *WebView) NotifyEvent(event *messages.EventData) error {
+
+ // Look out! Nils about!
+ var err error
+ if event == nil {
+ err = fmt.Errorf("Sent nil event to renderer.WebView")
+ w.log.Error(err.Error())
+ return err
+ }
+
+ // Default data is a blank array
+ data := []byte("[]")
+
+ // Process event data
+ if event.Data != nil {
+ // Marshall the data
+ data, err = json.Marshal(event.Data)
+ if err != nil {
+ w.log.Errorf("Cannot unmarshall JSON data in event: %s ", err.Error())
+ return err
+ }
+ }
+
+ // Double encode data to ensure everything is escaped correctly.
+ data, err = json.Marshal(string(data))
+ if err != nil {
+ w.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
+ return err
+ }
+
+ message := "window.wails._.Notify('" + event.Name + "'," + string(data) + ")"
+ return w.evalJS(message)
+}
+
+// Fullscreen makes the main window go fullscreen
+func (w *WebView) Fullscreen() {
+ if w.config.GetResizable() == false {
+ w.log.Warn("Cannot call Fullscreen() - App.Resizable = false")
+ return
+ }
+ w.window.Dispatch(func() {
+ w.window.SetFullscreen(true)
+ })
+}
+
+// UnFullscreen returns the window to the position prior to a fullscreen call
+func (w *WebView) UnFullscreen() {
+ if w.config.GetResizable() == false {
+ w.log.Warn("Cannot call UnFullscreen() - App.Resizable = false")
+ return
+ }
+ w.window.Dispatch(func() {
+ w.window.SetFullscreen(false)
+ })
+}
+
+// SetTitle sets the window title
+func (w *WebView) SetTitle(title string) {
+ w.window.Dispatch(func() {
+ w.window.SetTitle(title)
+ })
+}
+
+// Close closes the window
+func (w *WebView) Close() {
+ w.window.Dispatch(func() {
+ w.window.Terminate()
+ })
+}
diff --git a/lib/renderer/webview/LICENSE b/lib/renderer/webview/LICENSE
new file mode 100644
index 000000000..b18604bf4
--- /dev/null
+++ b/lib/renderer/webview/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+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/lib/renderer/webview/webview.go b/lib/renderer/webview/webview.go
new file mode 100755
index 000000000..f8ac2b706
--- /dev/null
+++ b/lib/renderer/webview/webview.go
@@ -0,0 +1,373 @@
+// Package webview implements Go bindings to https://github.com/zserge/webview C library.
+// It is a modified version of webview.go from that repository
+// Bindings closely repeat the C APIs and include both, a simplified
+// single-function API to just open a full-screen webview window, and a more
+// advanced and featureful set of APIs, including Go-to-JavaScript bindings.
+//
+// The library uses gtk-webkit, Cocoa/Webkit and MSHTML (IE8..11) as a browser
+// engine and supports Linux, MacOS and Windows 7..10 respectively.
+//
+package webview
+
+/*
+#cgo linux openbsd freebsd CFLAGS: -DWEBVIEW_GTK=1 -Wno-deprecated-declarations
+#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0
+
+#cgo windows CFLAGS: -DWEBVIEW_WINAPI=1 -std=c99
+#cgo windows LDFLAGS: -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32
+
+#cgo darwin CFLAGS: -DWEBVIEW_COCOA=1 -x objective-c
+#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit
+
+#include
+#include
+#define WEBVIEW_STATIC
+#define WEBVIEW_IMPLEMENTATION
+#include "webview.h"
+
+extern void _webviewExternalInvokeCallback(void *, void *);
+
+static inline void CgoWebViewFree(void *w) {
+ free((void *)((struct webview *)w)->title);
+ free((void *)((struct webview *)w)->url);
+ free(w);
+}
+
+static inline void *CgoWebViewCreate(int width, int height, char *title, char *url, int resizable, int debug) {
+ struct webview *w = (struct webview *) calloc(1, sizeof(*w));
+ w->width = width;
+ w->height = height;
+ w->title = title;
+ w->url = url;
+ w->resizable = resizable;
+ w->debug = debug;
+ w->external_invoke_cb = (webview_external_invoke_cb_t) _webviewExternalInvokeCallback;
+ if (webview_init(w) != 0) {
+ CgoWebViewFree(w);
+ return NULL;
+ }
+ return (void *)w;
+}
+
+static inline int CgoWebViewLoop(void *w, int blocking) {
+ return webview_loop((struct webview *)w, blocking);
+}
+
+static inline void CgoWebViewTerminate(void *w) {
+ webview_terminate((struct webview *)w);
+}
+
+static inline void CgoWebViewExit(void *w) {
+ webview_exit((struct webview *)w);
+}
+
+static inline void CgoWebViewSetTitle(void *w, char *title) {
+ webview_set_title((struct webview *)w, title);
+}
+
+static inline void CgoWebViewSetFullscreen(void *w, int fullscreen) {
+ webview_set_fullscreen((struct webview *)w, fullscreen);
+}
+
+static inline void CgoWebViewSetColor(void *w, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+ webview_set_color((struct webview *)w, r, g, b, a);
+}
+
+static inline void CgoDialog(void *w, int dlgtype, int flags,
+char *title, char *arg, char *res, size_t ressz, char *filter) {
+ webview_dialog(w, dlgtype, flags,
+ (const char*)title, (const char*) arg, res, ressz, filter);
+}
+
+static inline int CgoWebViewEval(void *w, char *js) {
+ return webview_eval((struct webview *)w, js);
+}
+
+static inline void CgoWebViewInjectCSS(void *w, char *css) {
+ webview_inject_css((struct webview *)w, css);
+}
+
+extern void _webviewDispatchGoCallback(void *);
+static inline void _webview_dispatch_cb(struct webview *w, void *arg) {
+ _webviewDispatchGoCallback(arg);
+}
+static inline void CgoWebViewDispatch(void *w, uintptr_t arg) {
+ webview_dispatch((struct webview *)w, _webview_dispatch_cb, (void *)arg);
+}
+*/
+import "C"
+import (
+ "errors"
+ "runtime"
+ "sync"
+ "unsafe"
+)
+
+func init() {
+ // Ensure that main.main is called from the main thread
+ runtime.LockOSThread()
+}
+
+// Open is a simplified API to open a single native window with a full-size webview in
+// it. It can be helpful if you want to communicate with the core app using XHR
+// or WebSockets (as opposed to using JavaScript bindings).
+//
+// Window appearance can be customized using title, width, height and resizable parameters.
+// URL must be provided and can user either a http or https protocol, or be a
+// local file:// URL. On some platforms "data:" URLs are also supported
+// (Linux/MacOS).
+func Open(title, url string, w, h int, resizable bool) error {
+ titleStr := C.CString(title)
+ defer C.free(unsafe.Pointer(titleStr))
+ urlStr := C.CString(url)
+ defer C.free(unsafe.Pointer(urlStr))
+ resize := C.int(0)
+ if resizable {
+ resize = C.int(1)
+ }
+
+ r := C.webview(titleStr, urlStr, C.int(w), C.int(h), resize)
+ if r != 0 {
+ return errors.New("failed to create webview")
+ }
+ return nil
+}
+
+// ExternalInvokeCallbackFunc is a function type that is called every time
+// "window.external.invoke()" is called from JavaScript. Data is the only
+// obligatory string parameter passed into the "invoke(data)" function from
+// JavaScript. To pass more complex data serialized JSON or base64 encoded
+// string can be used.
+type ExternalInvokeCallbackFunc func(w WebView, data string)
+
+// Settings is a set of parameters to customize the initial WebView appearance
+// and behavior. It is passed into the webview.New() constructor.
+type Settings struct {
+ // WebView main window title
+ Title string
+ // URL to open in a webview
+ URL string
+ // Window width in pixels
+ Width int
+ // Window height in pixels
+ Height int
+ // Allows/disallows window resizing
+ Resizable bool
+ // Enable debugging tools (Linux/BSD/MacOS, on Windows use Firebug)
+ Debug bool
+ // A callback that is executed when JavaScript calls "window.external.invoke()"
+ ExternalInvokeCallback ExternalInvokeCallbackFunc
+}
+
+// WebView is an interface that wraps the basic methods for controlling the UI
+// loop, handling multithreading and providing JavaScript bindings.
+type WebView interface {
+ // Run() starts the main UI loop until the user closes the webview window or
+ // Terminate() is called.
+ Run()
+ // Loop() runs a single iteration of the main UI.
+ Loop(blocking bool) bool
+ // SetTitle() changes window title. This method must be called from the main
+ // thread only. See Dispatch() for more details.
+ SetTitle(title string)
+ // SetFullscreen() controls window full-screen mode. This method must be
+ // called from the main thread only. See Dispatch() for more details.
+ SetFullscreen(fullscreen bool)
+ // SetColor() changes window background color. This method must be called from
+ // the main thread only. See Dispatch() for more details.
+ SetColor(r, g, b, a uint8)
+ // Eval() evaluates an arbitrary JS code inside the webview. This method must
+ // be called from the main thread only. See Dispatch() for more details.
+ Eval(js string) error
+ // InjectJS() injects an arbitrary block of CSS code using the JS API. This
+ // method must be called from the main thread only. See Dispatch() for more
+ // details.
+ InjectCSS(css string)
+ // Dialog() opens a system dialog of the given type and title. String
+ // argument can be provided for certain dialogs, such as alert boxes. For
+ // alert boxes argument is a message inside the dialog box.
+ Dialog(dlgType DialogType, flags int, title string, arg string, filter string) string
+ // Terminate() breaks the main UI loop. This method must be called from the main thread
+ // only. See Dispatch() for more details.
+ Terminate()
+ // Dispatch() schedules some arbitrary function to be executed on the main UI
+ // thread. This may be helpful if you want to run some JavaScript from
+ // background threads/goroutines, or to terminate the app.
+ Dispatch(func())
+ // Exit() closes the window and cleans up the resources. Use Terminate() to
+ // forcefully break out of the main UI loop.
+ Exit()
+}
+
+// DialogType is an enumeration of all supported system dialog types
+type DialogType int
+
+const (
+ // DialogTypeOpen is a system file open dialog
+ DialogTypeOpen DialogType = iota
+ // DialogTypeSave is a system file save dialog
+ DialogTypeSave
+ // DialogTypeAlert is a system alert dialog (message box)
+ DialogTypeAlert
+)
+
+const (
+ // DialogFlagFile is a normal file picker dialog
+ DialogFlagFile = C.WEBVIEW_DIALOG_FLAG_FILE
+ // DialogFlagDirectory is an open directory dialog
+ DialogFlagDirectory = C.WEBVIEW_DIALOG_FLAG_DIRECTORY
+ // DialogFlagInfo is an info alert dialog
+ DialogFlagInfo = C.WEBVIEW_DIALOG_FLAG_INFO
+ // DialogFlagWarning is a warning alert dialog
+ DialogFlagWarning = C.WEBVIEW_DIALOG_FLAG_WARNING
+ // DialogFlagError is an error dialog
+ DialogFlagError = C.WEBVIEW_DIALOG_FLAG_ERROR
+)
+
+var (
+ m sync.Mutex
+ index uintptr
+ fns = map[uintptr]func(){}
+ cbs = map[WebView]ExternalInvokeCallbackFunc{}
+)
+
+type webview struct {
+ w unsafe.Pointer
+}
+
+var _ WebView = &webview{}
+
+func boolToInt(b bool) int {
+ if b {
+ return 1
+ }
+ return 0
+}
+
+// NewWebview creates and opens a new webview window using the given settings. The
+// returned object implements the WebView interface. This function returns nil
+// if a window can not be created.
+func NewWebview(settings Settings) WebView {
+ if settings.Width == 0 {
+ settings.Width = 640
+ }
+ if settings.Height == 0 {
+ settings.Height = 480
+ }
+ if settings.Title == "" {
+ settings.Title = "WebView"
+ }
+ w := &webview{}
+ w.w = C.CgoWebViewCreate(C.int(settings.Width), C.int(settings.Height),
+ C.CString(settings.Title), C.CString(settings.URL),
+ C.int(boolToInt(settings.Resizable)), C.int(boolToInt(settings.Debug)))
+ m.Lock()
+ if settings.ExternalInvokeCallback != nil {
+ cbs[w] = settings.ExternalInvokeCallback
+ } else {
+ cbs[w] = func(w WebView, data string) {}
+ }
+ m.Unlock()
+ return w
+}
+
+func (w *webview) Loop(blocking bool) bool {
+ block := C.int(0)
+ if blocking {
+ block = 1
+ }
+ return C.CgoWebViewLoop(w.w, block) == 0
+}
+
+func (w *webview) Run() {
+ for w.Loop(true) {
+ }
+}
+
+func (w *webview) Exit() {
+ C.CgoWebViewExit(w.w)
+}
+
+func (w *webview) Dispatch(f func()) {
+ m.Lock()
+ for ; fns[index] != nil; index++ {
+ }
+ fns[index] = f
+ m.Unlock()
+ C.CgoWebViewDispatch(w.w, C.uintptr_t(index))
+}
+
+func (w *webview) SetTitle(title string) {
+ p := C.CString(title)
+ defer C.free(unsafe.Pointer(p))
+ C.CgoWebViewSetTitle(w.w, p)
+}
+
+func (w *webview) SetColor(r, g, b, a uint8) {
+ C.CgoWebViewSetColor(w.w, C.uint8_t(r), C.uint8_t(g), C.uint8_t(b), C.uint8_t(a))
+}
+
+func (w *webview) SetFullscreen(fullscreen bool) {
+ C.CgoWebViewSetFullscreen(w.w, C.int(boolToInt(fullscreen)))
+}
+
+func (w *webview) Dialog(dlgType DialogType, flags int, title string, arg string, filter string) string {
+ const maxPath = 4096
+ titlePtr := C.CString(title)
+ defer C.free(unsafe.Pointer(titlePtr))
+ argPtr := C.CString(arg)
+ defer C.free(unsafe.Pointer(argPtr))
+ resultPtr := (*C.char)(C.calloc((C.size_t)(unsafe.Sizeof((*C.char)(nil))), (C.size_t)(maxPath)))
+ defer C.free(unsafe.Pointer(resultPtr))
+ filterPtr := C.CString(filter)
+ defer C.free(unsafe.Pointer(filterPtr))
+ C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr,
+ argPtr, resultPtr, C.size_t(maxPath), filterPtr)
+ return C.GoString(resultPtr)
+}
+
+func (w *webview) Eval(js string) error {
+ p := C.CString(js)
+ defer C.free(unsafe.Pointer(p))
+ switch C.CgoWebViewEval(w.w, p) {
+ case -1:
+ return errors.New("evaluation failed")
+ }
+ return nil
+}
+
+func (w *webview) InjectCSS(css string) {
+ p := C.CString(css)
+ defer C.free(unsafe.Pointer(p))
+ C.CgoWebViewInjectCSS(w.w, p)
+}
+
+func (w *webview) Terminate() {
+ C.CgoWebViewTerminate(w.w)
+}
+
+//export _webviewDispatchGoCallback
+func _webviewDispatchGoCallback(index unsafe.Pointer) {
+ var f func()
+ m.Lock()
+ f = fns[uintptr(index)]
+ delete(fns, uintptr(index))
+ m.Unlock()
+ f()
+}
+
+//export _webviewExternalInvokeCallback
+func _webviewExternalInvokeCallback(w unsafe.Pointer, data unsafe.Pointer) {
+ m.Lock()
+ var (
+ cb ExternalInvokeCallbackFunc
+ wv WebView
+ )
+ for wv, cb = range cbs {
+ if wv.(*webview).w == w {
+ break
+ }
+ }
+ m.Unlock()
+ cb(wv, C.GoString((*C.char)(data)))
+}
diff --git a/lib/renderer/webview/webview.h b/lib/renderer/webview/webview.h
new file mode 100644
index 000000000..c08cb4794
--- /dev/null
+++ b/lib/renderer/webview/webview.h
@@ -0,0 +1,2365 @@
+/*
+ * MIT License
+ *
+ * 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.
+ */
+#ifndef WEBVIEW_H
+#define WEBVIEW_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifdef WEBVIEW_STATIC
+#define WEBVIEW_API static
+#else
+#define WEBVIEW_API extern
+#endif
+
+#include
+#include
+#include
+
+#if defined(WEBVIEW_GTK)
+#include
+#include
+#include
+
+ struct webview_priv
+ {
+ GtkWidget *window;
+ GtkWidget *scroller;
+ GtkWidget *webview;
+ GtkWidget *inspector_window;
+ GAsyncQueue *queue;
+ int ready;
+ int js_busy;
+ int should_exit;
+ };
+#elif defined(WEBVIEW_WINAPI)
+#define CINTERFACE
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+struct webview_priv
+{
+ HWND hwnd;
+ IOleObject **browser;
+ BOOL is_fullscreen;
+ DWORD saved_style;
+ DWORD saved_ex_style;
+ RECT saved_rect;
+};
+#elif defined(WEBVIEW_COCOA)
+#import
+#import
+#import
+
+struct webview_priv
+{
+ NSAutoreleasePool *pool;
+ NSWindow *window;
+ WebView *webview;
+ id delegate;
+ int should_exit;
+};
+#else
+#error "Define one of: WEBVIEW_GTK, WEBVIEW_COCOA or WEBVIEW_WINAPI"
+#endif
+
+ struct webview;
+
+ typedef void (*webview_external_invoke_cb_t)(struct webview *w,
+ const char *arg);
+
+ struct webview
+ {
+ const char *url;
+ const char *title;
+ int width;
+ int height;
+ int resizable;
+ int transparentTitlebar;
+ int debug;
+ webview_external_invoke_cb_t external_invoke_cb;
+ struct webview_priv priv;
+ void *userdata;
+ };
+
+ enum webview_dialog_type
+ {
+ WEBVIEW_DIALOG_TYPE_OPEN = 0,
+ WEBVIEW_DIALOG_TYPE_SAVE = 1,
+ WEBVIEW_DIALOG_TYPE_ALERT = 2
+ };
+
+#define WEBVIEW_DIALOG_FLAG_FILE (0 << 0)
+#define WEBVIEW_DIALOG_FLAG_DIRECTORY (1 << 0)
+
+#define WEBVIEW_DIALOG_FLAG_INFO (1 << 1)
+#define WEBVIEW_DIALOG_FLAG_WARNING (2 << 1)
+#define WEBVIEW_DIALOG_FLAG_ERROR (3 << 1)
+#define WEBVIEW_DIALOG_FLAG_ALERT_MASK (3 << 1)
+
+ typedef void (*webview_dispatch_fn)(struct webview *w, void *arg);
+
+ struct webview_dispatch_arg
+ {
+ webview_dispatch_fn fn;
+ struct webview *w;
+ void *arg;
+ };
+
+#define DEFAULT_URL \
+ "data:text/" \
+ "html,%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang=%22en%22%3E%0A%3Chead%3E%" \
+ "3Cmeta%20charset=%22utf-8%22%3E%3Cmeta%20http-equiv=%22IE=edge%22%" \
+ "20content=%22IE=edge%22%3E%3C%2Fhead%3E%0A%3Cbody%3E%3Cdiv%20id=%22app%22%" \
+ "3E%3C%2Fdiv%3E%3Cscript%20type=%22text%2Fjavascript%22%3E%3C%2Fscript%3E%" \
+ "3C%2Fbody%3E%0A%3C%2Fhtml%3E"
+
+#define CSS_INJECT_FUNCTION \
+ "(function(e){var " \
+ "t=document.createElement('style'),d=document.head||document." \
+ "getElementsByTagName('head')[0];t.setAttribute('type','text/" \
+ "css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document." \
+ "createTextNode(e)),d.appendChild(t)})"
+
+ static const char *webview_check_url(const char *url)
+ {
+ if (url == NULL || strlen(url) == 0)
+ {
+ return DEFAULT_URL;
+ }
+ return url;
+ }
+
+ WEBVIEW_API int webview(const char *title, const char *url, int width,
+ int height, int resizable);
+
+ WEBVIEW_API int webview_init(struct webview *w);
+ WEBVIEW_API int webview_loop(struct webview *w, int blocking);
+ WEBVIEW_API int webview_eval(struct webview *w, const char *js);
+ WEBVIEW_API int webview_inject_css(struct webview *w, const char *css);
+ WEBVIEW_API void webview_set_title(struct webview *w, const char *title);
+ WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen);
+ WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g,
+ uint8_t b, uint8_t a);
+ WEBVIEW_API void webview_dialog(struct webview *w,
+ enum webview_dialog_type dlgtype, int flags,
+ const char *title, const char *arg,
+ char *result, size_t resultsz, char *filter);
+ WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn,
+ void *arg);
+ WEBVIEW_API void webview_terminate(struct webview *w);
+ WEBVIEW_API void webview_exit(struct webview *w);
+ WEBVIEW_API void webview_debug(const char *format, ...);
+ WEBVIEW_API void webview_print_log(const char *s);
+
+#ifdef WEBVIEW_IMPLEMENTATION
+#undef WEBVIEW_IMPLEMENTATION
+
+ WEBVIEW_API int webview(const char *title, const char *url, int width,
+ int height, int resizable)
+ {
+ struct webview webview;
+ memset(&webview, 0, sizeof(webview));
+ webview.title = title;
+ webview.url = url;
+ webview.width = width;
+ webview.height = height;
+ webview.resizable = resizable;
+ int r = webview_init(&webview);
+ if (r != 0)
+ {
+ return r;
+ }
+ while (webview_loop(&webview, 1) == 0)
+ {
+ }
+ webview_exit(&webview);
+ return 0;
+ }
+
+ WEBVIEW_API void webview_debug(const char *format, ...)
+ {
+ char buf[4096];
+ va_list ap;
+ va_start(ap, format);
+ vsnprintf(buf, sizeof(buf), format, ap);
+ webview_print_log(buf);
+ va_end(ap);
+ }
+
+ static int webview_js_encode(const char *s, char *esc, size_t n)
+ {
+ int r = 1; /* At least one byte for trailing zero */
+ for (; *s; s++)
+ {
+ const unsigned char c = *s;
+ if (c >= 0x20 && c < 0x80 && strchr("<>\\'\"", c) == NULL)
+ {
+ if (n > 0)
+ {
+ *esc++ = c;
+ n--;
+ }
+ r++;
+ }
+ else
+ {
+ if (n > 0)
+ {
+ snprintf(esc, n, "\\x%02x", (int)c);
+ esc += 4;
+ n -= 4;
+ }
+ r += 4;
+ }
+ }
+ return r;
+ }
+
+ WEBVIEW_API int webview_inject_css(struct webview *w, const char *css)
+ {
+ int n = webview_js_encode(css, NULL, 0);
+ char *esc = (char *)calloc(1, sizeof(CSS_INJECT_FUNCTION) + n + 4);
+ if (esc == NULL)
+ {
+ return -1;
+ }
+ char *js = (char *)calloc(1, n);
+ webview_js_encode(css, js, n);
+ snprintf(esc, sizeof(CSS_INJECT_FUNCTION) + n + 4, "%s(\"%s\")",
+ CSS_INJECT_FUNCTION, js);
+ int r = webview_eval(w, esc);
+ free(js);
+ free(esc);
+ return r;
+ }
+
+#if defined(WEBVIEW_GTK)
+ static void external_message_received_cb(WebKitUserContentManager *m,
+ WebKitJavascriptResult *r,
+ gpointer arg)
+ {
+ (void)m;
+ struct webview *w = (struct webview *)arg;
+ if (w->external_invoke_cb == NULL)
+ {
+ return;
+ }
+ JSGlobalContextRef context = webkit_javascript_result_get_global_context(r);
+ JSValueRef value = webkit_javascript_result_get_value(r);
+ JSStringRef js = JSValueToStringCopy(context, value, NULL);
+ size_t n = JSStringGetMaximumUTF8CStringSize(js);
+ char *s = g_new(char, n);
+ JSStringGetUTF8CString(js, s, n);
+ w->external_invoke_cb(w, s);
+ JSStringRelease(js);
+ g_free(s);
+ }
+
+ static void webview_load_changed_cb(WebKitWebView *webview,
+ WebKitLoadEvent event, gpointer arg)
+ {
+ (void)webview;
+ struct webview *w = (struct webview *)arg;
+ if (event == WEBKIT_LOAD_FINISHED)
+ {
+ w->priv.ready = 1;
+ }
+ }
+
+ static void webview_destroy_cb(GtkWidget *widget, gpointer arg)
+ {
+ (void)widget;
+ struct webview *w = (struct webview *)arg;
+ webview_terminate(w);
+ }
+
+ static gboolean webview_context_menu_cb(WebKitWebView *webview,
+ GtkWidget *default_menu,
+ WebKitHitTestResult *hit_test_result,
+ gboolean triggered_with_keyboard,
+ gpointer userdata)
+ {
+ (void)webview;
+ (void)default_menu;
+ (void)hit_test_result;
+ (void)triggered_with_keyboard;
+ (void)userdata;
+ return TRUE;
+ }
+
+ WEBVIEW_API int webview_init(struct webview *w)
+ {
+ if (gtk_init_check(0, NULL) == FALSE)
+ {
+ return -1;
+ }
+
+ w->priv.ready = 0;
+ w->priv.should_exit = 0;
+ w->priv.queue = g_async_queue_new();
+ w->priv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(w->priv.window), w->title);
+
+ if (w->resizable)
+ {
+ gtk_window_set_default_size(GTK_WINDOW(w->priv.window), w->width,
+ w->height);
+ }
+ else
+ {
+ gtk_widget_set_size_request(w->priv.window, w->width, w->height);
+ }
+ gtk_window_set_resizable(GTK_WINDOW(w->priv.window), !!w->resizable);
+ gtk_window_set_position(GTK_WINDOW(w->priv.window), GTK_WIN_POS_CENTER);
+
+ w->priv.scroller = gtk_scrolled_window_new(NULL, NULL);
+ gtk_container_add(GTK_CONTAINER(w->priv.window), w->priv.scroller);
+
+ WebKitUserContentManager *m = webkit_user_content_manager_new();
+ webkit_user_content_manager_register_script_message_handler(m, "external");
+ g_signal_connect(m, "script-message-received::external",
+ G_CALLBACK(external_message_received_cb), w);
+
+ w->priv.webview = webkit_web_view_new_with_user_content_manager(m);
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(w->priv.webview),
+ webview_check_url(w->url));
+ g_signal_connect(G_OBJECT(w->priv.webview), "load-changed",
+ G_CALLBACK(webview_load_changed_cb), w);
+ gtk_container_add(GTK_CONTAINER(w->priv.scroller), w->priv.webview);
+
+ if (w->debug)
+ {
+ WebKitSettings *settings =
+ webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview));
+ webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
+ webkit_settings_set_enable_developer_extras(settings, true);
+ webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
+ }
+ else
+ {
+ g_signal_connect(G_OBJECT(w->priv.webview), "context-menu",
+ G_CALLBACK(webview_context_menu_cb), w);
+ }
+
+ gtk_widget_show_all(w->priv.window);
+
+ webkit_web_view_run_javascript(
+ WEBKIT_WEB_VIEW(w->priv.webview),
+ "window.external={invoke:function(x){"
+ "window.webkit.messageHandlers.external.postMessage(x);}}",
+ NULL, NULL, NULL);
+
+ g_signal_connect(G_OBJECT(w->priv.window), "destroy",
+ G_CALLBACK(webview_destroy_cb), w);
+ return 0;
+ }
+
+ WEBVIEW_API int webview_loop(struct webview *w, int blocking)
+ {
+ gtk_main_iteration_do(blocking);
+ return w->priv.should_exit;
+ }
+
+ WEBVIEW_API void webview_set_title(struct webview *w, const char *title)
+ {
+ gtk_window_set_title(GTK_WINDOW(w->priv.window), title);
+ }
+
+ WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen)
+ {
+ if (fullscreen)
+ {
+ gtk_window_fullscreen(GTK_WINDOW(w->priv.window));
+ }
+ else
+ {
+ gtk_window_unfullscreen(GTK_WINDOW(w->priv.window));
+ }
+ }
+
+ WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g,
+ uint8_t b, uint8_t a)
+ {
+ GdkRGBA color = {r / 255.0, g / 255.0, b / 255.0, a / 255.0};
+ webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(w->priv.webview),
+ &color);
+ }
+
+ WEBVIEW_API void webview_dialog(struct webview *w,
+ enum webview_dialog_type dlgtype, int flags,
+ const char *title, const char *arg,
+ char *result, size_t resultsz, char *filter)
+ {
+ GtkWidget *dlg;
+ if (result != NULL)
+ {
+ result[0] = '\0';
+ }
+ if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
+ dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
+ {
+ dlg = gtk_file_chooser_dialog_new(
+ title, GTK_WINDOW(w->priv.window),
+ (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN
+ ? (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY
+ ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
+ : GTK_FILE_CHOOSER_ACTION_OPEN)
+ : GTK_FILE_CHOOSER_ACTION_SAVE),
+ "_Cancel", GTK_RESPONSE_CANCEL,
+ (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? "_Open" : "_Save"),
+ GTK_RESPONSE_ACCEPT, NULL);
+ if (filter[0] != '\0') {
+ GtkFileFilter *file_filter = gtk_file_filter_new();
+ gchar **filters = g_strsplit(filter, ",", -1);
+ gint i;
+ for(i = 0; filters && filters[i]; i++) {
+ gtk_file_filter_add_pattern(file_filter, filters[i]);
+ }
+ gtk_file_filter_set_name(file_filter, filter);
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg), file_filter);
+ g_strfreev(filters);
+ }
+ gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
+ gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE);
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE);
+ gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dlg), TRUE);
+ gint response = gtk_dialog_run(GTK_DIALOG(dlg));
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
+ g_strlcpy(result, filename, resultsz);
+ g_free(filename);
+ }
+ gtk_widget_destroy(dlg);
+ }
+ else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT)
+ {
+ GtkMessageType type = GTK_MESSAGE_OTHER;
+ switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK)
+ {
+ case WEBVIEW_DIALOG_FLAG_INFO:
+ type = GTK_MESSAGE_INFO;
+ break;
+ case WEBVIEW_DIALOG_FLAG_WARNING:
+ type = GTK_MESSAGE_WARNING;
+ break;
+ case WEBVIEW_DIALOG_FLAG_ERROR:
+ type = GTK_MESSAGE_ERROR;
+ break;
+ }
+ dlg = gtk_message_dialog_new(GTK_WINDOW(w->priv.window), GTK_DIALOG_MODAL,
+ type, GTK_BUTTONS_OK, "%s", title);
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg), "%s",
+ arg);
+ gtk_dialog_run(GTK_DIALOG(dlg));
+ gtk_widget_destroy(dlg);
+ }
+ }
+
+ static void webview_eval_finished(GObject *object, GAsyncResult *result,
+ gpointer userdata)
+ {
+ (void)object;
+ (void)result;
+ struct webview *w = (struct webview *)userdata;
+ w->priv.js_busy = 0;
+ }
+
+ WEBVIEW_API int webview_eval(struct webview *w, const char *js)
+ {
+ while (w->priv.ready == 0)
+ {
+ g_main_context_iteration(NULL, TRUE);
+ }
+ w->priv.js_busy = 1;
+ webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(w->priv.webview), js, NULL,
+ webview_eval_finished, w);
+ while (w->priv.js_busy)
+ {
+ g_main_context_iteration(NULL, TRUE);
+ }
+ return 0;
+ }
+
+ static gboolean webview_dispatch_wrapper(gpointer userdata)
+ {
+ struct webview *w = (struct webview *)userdata;
+ for (;;)
+ {
+ struct webview_dispatch_arg *arg =
+ (struct webview_dispatch_arg *)g_async_queue_try_pop(w->priv.queue);
+ if (arg == NULL)
+ {
+ break;
+ }
+ (arg->fn)(w, arg->arg);
+ g_free(arg);
+ }
+ return FALSE;
+ }
+
+ WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn,
+ void *arg)
+ {
+ struct webview_dispatch_arg *context =
+ (struct webview_dispatch_arg *)g_new(struct webview_dispatch_arg *, 1);
+ context->w = w;
+ context->arg = arg;
+ context->fn = fn;
+ g_async_queue_lock(w->priv.queue);
+ g_async_queue_push_unlocked(w->priv.queue, context);
+ if (g_async_queue_length_unlocked(w->priv.queue) == 1)
+ {
+ gdk_threads_add_idle(webview_dispatch_wrapper, w);
+ }
+ g_async_queue_unlock(w->priv.queue);
+ }
+
+ WEBVIEW_API void webview_terminate(struct webview *w)
+ {
+ w->priv.should_exit = 1;
+ }
+
+ WEBVIEW_API void webview_exit(struct webview *w) { (void)w; }
+ WEBVIEW_API void webview_print_log(const char *s)
+ {
+ fprintf(stderr, "%s\n", s);
+ }
+
+#endif /* WEBVIEW_GTK */
+
+#if defined(WEBVIEW_WINAPI)
+
+#pragma comment(lib, "user32.lib")
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "oleaut32.lib")
+
+#define WM_WEBVIEW_DISPATCH (WM_APP + 1)
+
+ typedef struct
+ {
+ IOleInPlaceFrame frame;
+ HWND window;
+ } _IOleInPlaceFrameEx;
+
+ typedef struct
+ {
+ IOleInPlaceSite inplace;
+ _IOleInPlaceFrameEx frame;
+ } _IOleInPlaceSiteEx;
+
+ typedef struct
+ {
+ IDocHostUIHandler ui;
+ } _IDocHostUIHandlerEx;
+
+ typedef struct
+ {
+ IOleClientSite client;
+ _IOleInPlaceSiteEx inplace;
+ _IDocHostUIHandlerEx ui;
+ IDispatch external;
+ } _IOleClientSiteEx;
+
+#ifdef __cplusplus
+#define iid_ref(x) &(x)
+#define iid_unref(x) *(x)
+#else
+#define iid_ref(x) (x)
+#define iid_unref(x) (x)
+#endif
+
+ static inline WCHAR *webview_to_utf16(const char *s)
+ {
+ DWORD size = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0);
+ WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size);
+ if (ws == NULL)
+ {
+ return NULL;
+ }
+ MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, size);
+ return ws;
+ }
+
+ static inline char *webview_from_utf16(WCHAR *ws)
+ {
+ int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
+ char *s = (char *)GlobalAlloc(GMEM_FIXED, n);
+ if (s == NULL)
+ {
+ return NULL;
+ }
+ WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, n, NULL, NULL);
+ return s;
+ }
+
+ static int iid_eq(REFIID a, const IID *b)
+ {
+ return memcmp((const void *)iid_ref(a), (const void *)b, sizeof(GUID)) == 0;
+ }
+
+ static HRESULT STDMETHODCALLTYPE JS_QueryInterface(IDispatch FAR *This,
+ REFIID riid,
+ LPVOID FAR *ppvObj)
+ {
+ if (iid_eq(riid, &IID_IDispatch))
+ {
+ *ppvObj = This;
+ return S_OK;
+ }
+ *ppvObj = 0;
+ return E_NOINTERFACE;
+ }
+ static ULONG STDMETHODCALLTYPE JS_AddRef(IDispatch FAR *This) { return 1; }
+ static ULONG STDMETHODCALLTYPE JS_Release(IDispatch FAR *This) { return 1; }
+ static HRESULT STDMETHODCALLTYPE JS_GetTypeInfoCount(IDispatch FAR *This,
+ UINT *pctinfo)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE JS_GetTypeInfo(IDispatch FAR *This,
+ UINT iTInfo, LCID lcid,
+ ITypeInfo **ppTInfo)
+ {
+ return S_OK;
+ }
+#define WEBVIEW_JS_INVOKE_ID 0x1000
+ static HRESULT STDMETHODCALLTYPE JS_GetIDsOfNames(IDispatch FAR *This,
+ REFIID riid,
+ LPOLESTR *rgszNames,
+ UINT cNames, LCID lcid,
+ DISPID *rgDispId)
+ {
+ if (cNames != 1)
+ {
+ return S_FALSE;
+ }
+ if (wcscmp(rgszNames[0], L"invoke") == 0)
+ {
+ rgDispId[0] = WEBVIEW_JS_INVOKE_ID;
+ return S_OK;
+ }
+ return S_FALSE;
+ }
+
+ static HRESULT STDMETHODCALLTYPE
+ JS_Invoke(IDispatch FAR *This, DISPID dispIdMember, REFIID riid, LCID lcid,
+ WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult,
+ EXCEPINFO *pExcepInfo, UINT *puArgErr)
+ {
+ size_t offset = (size_t) & ((_IOleClientSiteEx *)NULL)->external;
+ _IOleClientSiteEx *ex = (_IOleClientSiteEx *)((char *)(This)-offset);
+ struct webview *w = (struct webview *)GetWindowLongPtr(
+ ex->inplace.frame.window, GWLP_USERDATA);
+ if (pDispParams->cArgs == 1 && pDispParams->rgvarg[0].vt == VT_BSTR)
+ {
+ BSTR bstr = pDispParams->rgvarg[0].bstrVal;
+ char *s = webview_from_utf16(bstr);
+ if (s != NULL)
+ {
+ if (dispIdMember == WEBVIEW_JS_INVOKE_ID)
+ {
+ if (w->external_invoke_cb != NULL)
+ {
+ w->external_invoke_cb(w, s);
+ }
+ }
+ else
+ {
+ return S_FALSE;
+ }
+ GlobalFree(s);
+ }
+ }
+ return S_OK;
+ }
+
+ static IDispatchVtbl ExternalDispatchTable = {
+ JS_QueryInterface, JS_AddRef, JS_Release, JS_GetTypeInfoCount,
+ JS_GetTypeInfo, JS_GetIDsOfNames, JS_Invoke};
+
+ static ULONG STDMETHODCALLTYPE Site_AddRef(IOleClientSite FAR *This)
+ {
+ return 1;
+ }
+ static ULONG STDMETHODCALLTYPE Site_Release(IOleClientSite FAR *This)
+ {
+ return 1;
+ }
+ static HRESULT STDMETHODCALLTYPE Site_SaveObject(IOleClientSite FAR *This)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Site_GetMoniker(IOleClientSite FAR *This,
+ DWORD dwAssign,
+ DWORD dwWhichMoniker,
+ IMoniker **ppmk)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ Site_GetContainer(IOleClientSite FAR *This, LPOLECONTAINER FAR *ppContainer)
+ {
+ *ppContainer = 0;
+ return E_NOINTERFACE;
+ }
+ static HRESULT STDMETHODCALLTYPE Site_ShowObject(IOleClientSite FAR *This)
+ {
+ return NOERROR;
+ }
+ static HRESULT STDMETHODCALLTYPE Site_OnShowWindow(IOleClientSite FAR *This,
+ BOOL fShow)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ Site_RequestNewObjectLayout(IOleClientSite FAR *This)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Site_QueryInterface(IOleClientSite FAR *This,
+ REFIID riid,
+ void **ppvObject)
+ {
+ if (iid_eq(riid, &IID_IUnknown) || iid_eq(riid, &IID_IOleClientSite))
+ {
+ *ppvObject = &((_IOleClientSiteEx *)This)->client;
+ }
+ else if (iid_eq(riid, &IID_IOleInPlaceSite))
+ {
+ *ppvObject = &((_IOleClientSiteEx *)This)->inplace;
+ }
+ else if (iid_eq(riid, &IID_IDocHostUIHandler))
+ {
+ *ppvObject = &((_IOleClientSiteEx *)This)->ui;
+ }
+ else
+ {
+ *ppvObject = 0;
+ return (E_NOINTERFACE);
+ }
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE InPlace_QueryInterface(
+ IOleInPlaceSite FAR *This, REFIID riid, LPVOID FAR *ppvObj)
+ {
+ return (Site_QueryInterface(
+ (IOleClientSite *)((char *)This - sizeof(IOleClientSite)), riid, ppvObj));
+ }
+ static ULONG STDMETHODCALLTYPE InPlace_AddRef(IOleInPlaceSite FAR *This)
+ {
+ return 1;
+ }
+ static ULONG STDMETHODCALLTYPE InPlace_Release(IOleInPlaceSite FAR *This)
+ {
+ return 1;
+ }
+ static HRESULT STDMETHODCALLTYPE InPlace_GetWindow(IOleInPlaceSite FAR *This,
+ HWND FAR *lphwnd)
+ {
+ *lphwnd = ((_IOleInPlaceSiteEx FAR *)This)->frame.window;
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_ContextSensitiveHelp(IOleInPlaceSite FAR *This, BOOL fEnterMode)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_CanInPlaceActivate(IOleInPlaceSite FAR *This)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_OnInPlaceActivate(IOleInPlaceSite FAR *This)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_OnUIActivate(IOleInPlaceSite FAR *This)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE InPlace_GetWindowContext(
+ IOleInPlaceSite FAR *This, LPOLEINPLACEFRAME FAR *lplpFrame,
+ LPOLEINPLACEUIWINDOW FAR *lplpDoc, LPRECT lprcPosRect, LPRECT lprcClipRect,
+ LPOLEINPLACEFRAMEINFO lpFrameInfo)
+ {
+ *lplpFrame = (LPOLEINPLACEFRAME) & ((_IOleInPlaceSiteEx *)This)->frame;
+ *lplpDoc = 0;
+ lpFrameInfo->fMDIApp = FALSE;
+ lpFrameInfo->hwndFrame = ((_IOleInPlaceFrameEx *)*lplpFrame)->window;
+ lpFrameInfo->haccel = 0;
+ lpFrameInfo->cAccelEntries = 0;
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE InPlace_Scroll(IOleInPlaceSite FAR *This,
+ SIZE scrollExtent)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_OnUIDeactivate(IOleInPlaceSite FAR *This, BOOL fUndoable)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_OnInPlaceDeactivate(IOleInPlaceSite FAR *This)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_DiscardUndoState(IOleInPlaceSite FAR *This)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_DeactivateAndUndo(IOleInPlaceSite FAR *This)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ InPlace_OnPosRectChange(IOleInPlaceSite FAR *This, LPCRECT lprcPosRect)
+ {
+ IOleObject *browserObject;
+ IOleInPlaceObject *inplace;
+ browserObject = *((IOleObject **)((char *)This - sizeof(IOleObject *) -
+ sizeof(IOleClientSite)));
+ if (!browserObject->lpVtbl->QueryInterface(browserObject,
+ iid_unref(&IID_IOleInPlaceObject),
+ (void **)&inplace))
+ {
+ inplace->lpVtbl->SetObjectRects(inplace, lprcPosRect, lprcPosRect);
+ inplace->lpVtbl->Release(inplace);
+ }
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_QueryInterface(
+ IOleInPlaceFrame FAR *This, REFIID riid, LPVOID FAR *ppvObj)
+ {
+ return E_NOTIMPL;
+ }
+ static ULONG STDMETHODCALLTYPE Frame_AddRef(IOleInPlaceFrame FAR *This)
+ {
+ return 1;
+ }
+ static ULONG STDMETHODCALLTYPE Frame_Release(IOleInPlaceFrame FAR *This)
+ {
+ return 1;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_GetWindow(IOleInPlaceFrame FAR *This,
+ HWND FAR *lphwnd)
+ {
+ *lphwnd = ((_IOleInPlaceFrameEx *)This)->window;
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ Frame_ContextSensitiveHelp(IOleInPlaceFrame FAR *This, BOOL fEnterMode)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_GetBorder(IOleInPlaceFrame FAR *This,
+ LPRECT lprectBorder)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_RequestBorderSpace(
+ IOleInPlaceFrame FAR *This, LPCBORDERWIDTHS pborderwidths)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_SetBorderSpace(
+ IOleInPlaceFrame FAR *This, LPCBORDERWIDTHS pborderwidths)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_SetActiveObject(
+ IOleInPlaceFrame FAR *This, IOleInPlaceActiveObject *pActiveObject,
+ LPCOLESTR pszObjName)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ Frame_InsertMenus(IOleInPlaceFrame FAR *This, HMENU hmenuShared,
+ LPOLEMENUGROUPWIDTHS lpMenuWidths)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_SetMenu(IOleInPlaceFrame FAR *This,
+ HMENU hmenuShared,
+ HOLEMENU holemenu,
+ HWND hwndActiveObject)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_RemoveMenus(IOleInPlaceFrame FAR *This,
+ HMENU hmenuShared)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE Frame_SetStatusText(IOleInPlaceFrame FAR *This,
+ LPCOLESTR pszStatusText)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ Frame_EnableModeless(IOleInPlaceFrame FAR *This, BOOL fEnable)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ Frame_TranslateAccelerator(IOleInPlaceFrame FAR *This, LPMSG lpmsg, WORD wID)
+ {
+ return E_NOTIMPL;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_QueryInterface(IDocHostUIHandler FAR *This,
+ REFIID riid,
+ LPVOID FAR *ppvObj)
+ {
+ return (Site_QueryInterface((IOleClientSite *)((char *)This -
+ sizeof(IOleClientSite) -
+ sizeof(_IOleInPlaceSiteEx)),
+ riid, ppvObj));
+ }
+ static ULONG STDMETHODCALLTYPE UI_AddRef(IDocHostUIHandler FAR *This)
+ {
+ return 1;
+ }
+ static ULONG STDMETHODCALLTYPE UI_Release(IDocHostUIHandler FAR *This)
+ {
+ return 1;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_ShowContextMenu(
+ IDocHostUIHandler FAR *This, DWORD dwID, POINT __RPC_FAR *ppt,
+ IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ UI_GetHostInfo(IDocHostUIHandler FAR *This, DOCHOSTUIINFO __RPC_FAR *pInfo)
+ {
+ pInfo->cbSize = sizeof(DOCHOSTUIINFO);
+ pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER;
+ pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_ShowUI(
+ IDocHostUIHandler FAR *This, DWORD dwID,
+ IOleInPlaceActiveObject __RPC_FAR *pActiveObject,
+ IOleCommandTarget __RPC_FAR *pCommandTarget,
+ IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_HideUI(IDocHostUIHandler FAR *This)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_UpdateUI(IDocHostUIHandler FAR *This)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_EnableModeless(IDocHostUIHandler FAR *This,
+ BOOL fEnable)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ UI_OnDocWindowActivate(IDocHostUIHandler FAR *This, BOOL fActivate)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ UI_OnFrameWindowActivate(IDocHostUIHandler FAR *This, BOOL fActivate)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ UI_ResizeBorder(IDocHostUIHandler FAR *This, LPCRECT prcBorder,
+ IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow)
+ {
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ UI_TranslateAccelerator(IDocHostUIHandler FAR *This, LPMSG lpMsg,
+ const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID)
+ {
+ return S_FALSE;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_GetOptionKeyPath(
+ IDocHostUIHandler FAR *This, LPOLESTR __RPC_FAR *pchKey, DWORD dw)
+ {
+ return S_FALSE;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_GetDropTarget(
+ IDocHostUIHandler FAR *This, IDropTarget __RPC_FAR *pDropTarget,
+ IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget)
+ {
+ return S_FALSE;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_GetExternal(
+ IDocHostUIHandler FAR *This, IDispatch __RPC_FAR *__RPC_FAR *ppDispatch)
+ {
+ *ppDispatch = (IDispatch *)(This + 1);
+ return S_OK;
+ }
+ static HRESULT STDMETHODCALLTYPE UI_TranslateUrl(
+ IDocHostUIHandler FAR *This, DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn,
+ OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut)
+ {
+ *ppchURLOut = 0;
+ return S_FALSE;
+ }
+ static HRESULT STDMETHODCALLTYPE
+ UI_FilterDataObject(IDocHostUIHandler FAR *This, IDataObject __RPC_FAR *pDO,
+ IDataObject __RPC_FAR *__RPC_FAR *ppDORet)
+ {
+ *ppDORet = 0;
+ return S_FALSE;
+ }
+
+ static const TCHAR *classname = TEXT("WebView");
+ static const SAFEARRAYBOUND ArrayBound = {1, 0};
+
+ static IOleClientSiteVtbl MyIOleClientSiteTable = {
+ Site_QueryInterface, Site_AddRef, Site_Release,
+ Site_SaveObject, Site_GetMoniker, Site_GetContainer,
+ Site_ShowObject, Site_OnShowWindow, Site_RequestNewObjectLayout};
+ static IOleInPlaceSiteVtbl MyIOleInPlaceSiteTable = {
+ InPlace_QueryInterface,
+ InPlace_AddRef,
+ InPlace_Release,
+ InPlace_GetWindow,
+ InPlace_ContextSensitiveHelp,
+ InPlace_CanInPlaceActivate,
+ InPlace_OnInPlaceActivate,
+ InPlace_OnUIActivate,
+ InPlace_GetWindowContext,
+ InPlace_Scroll,
+ InPlace_OnUIDeactivate,
+ InPlace_OnInPlaceDeactivate,
+ InPlace_DiscardUndoState,
+ InPlace_DeactivateAndUndo,
+ InPlace_OnPosRectChange};
+
+ static IOleInPlaceFrameVtbl MyIOleInPlaceFrameTable = {
+ Frame_QueryInterface,
+ Frame_AddRef,
+ Frame_Release,
+ Frame_GetWindow,
+ Frame_ContextSensitiveHelp,
+ Frame_GetBorder,
+ Frame_RequestBorderSpace,
+ Frame_SetBorderSpace,
+ Frame_SetActiveObject,
+ Frame_InsertMenus,
+ Frame_SetMenu,
+ Frame_RemoveMenus,
+ Frame_SetStatusText,
+ Frame_EnableModeless,
+ Frame_TranslateAccelerator};
+
+ static IDocHostUIHandlerVtbl MyIDocHostUIHandlerTable = {
+ UI_QueryInterface,
+ UI_AddRef,
+ UI_Release,
+ UI_ShowContextMenu,
+ UI_GetHostInfo,
+ UI_ShowUI,
+ UI_HideUI,
+ UI_UpdateUI,
+ UI_EnableModeless,
+ UI_OnDocWindowActivate,
+ UI_OnFrameWindowActivate,
+ UI_ResizeBorder,
+ UI_TranslateAccelerator,
+ UI_GetOptionKeyPath,
+ UI_GetDropTarget,
+ UI_GetExternal,
+ UI_TranslateUrl,
+ UI_FilterDataObject};
+
+ static void UnEmbedBrowserObject(struct webview *w)
+ {
+ if (w->priv.browser != NULL)
+ {
+ (*w->priv.browser)->lpVtbl->Close(*w->priv.browser, OLECLOSE_NOSAVE);
+ (*w->priv.browser)->lpVtbl->Release(*w->priv.browser);
+ GlobalFree(w->priv.browser);
+ w->priv.browser = NULL;
+ }
+ }
+
+ static int EmbedBrowserObject(struct webview *w)
+ {
+ RECT rect;
+ IWebBrowser2 *webBrowser2 = NULL;
+ LPCLASSFACTORY pClassFactory = NULL;
+ _IOleClientSiteEx *_iOleClientSiteEx = NULL;
+ IOleObject **browser = (IOleObject **)GlobalAlloc(
+ GMEM_FIXED, sizeof(IOleObject *) + sizeof(_IOleClientSiteEx));
+ if (browser == NULL)
+ {
+ goto error;
+ }
+ w->priv.browser = browser;
+
+ _iOleClientSiteEx = (_IOleClientSiteEx *)(browser + 1);
+ _iOleClientSiteEx->client.lpVtbl = &MyIOleClientSiteTable;
+ _iOleClientSiteEx->inplace.inplace.lpVtbl = &MyIOleInPlaceSiteTable;
+ _iOleClientSiteEx->inplace.frame.frame.lpVtbl = &MyIOleInPlaceFrameTable;
+ _iOleClientSiteEx->inplace.frame.window = w->priv.hwnd;
+ _iOleClientSiteEx->ui.ui.lpVtbl = &MyIDocHostUIHandlerTable;
+ _iOleClientSiteEx->external.lpVtbl = &ExternalDispatchTable;
+
+ if (CoGetClassObject(iid_unref(&CLSID_WebBrowser),
+ CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, NULL,
+ iid_unref(&IID_IClassFactory),
+ (void **)&pClassFactory) != S_OK)
+ {
+ goto error;
+ }
+
+ if (pClassFactory == NULL)
+ {
+ goto error;
+ }
+
+ if (pClassFactory->lpVtbl->CreateInstance(pClassFactory, 0,
+ iid_unref(&IID_IOleObject),
+ (void **)browser) != S_OK)
+ {
+ goto error;
+ }
+ pClassFactory->lpVtbl->Release(pClassFactory);
+ if ((*browser)->lpVtbl->SetClientSite(
+ *browser, (IOleClientSite *)_iOleClientSiteEx) != S_OK)
+ {
+ goto error;
+ }
+ (*browser)->lpVtbl->SetHostNames(*browser, L"My Host Name", 0);
+
+ if (OleSetContainedObject((struct IUnknown *)(*browser), TRUE) != S_OK)
+ {
+ goto error;
+ }
+ GetClientRect(w->priv.hwnd, &rect);
+ if ((*browser)->lpVtbl->DoVerb((*browser), OLEIVERB_SHOW, NULL,
+ (IOleClientSite *)_iOleClientSiteEx, -1,
+ w->priv.hwnd, &rect) != S_OK)
+ {
+ goto error;
+ }
+ if ((*browser)->lpVtbl->QueryInterface((*browser),
+ iid_unref(&IID_IWebBrowser2),
+ (void **)&webBrowser2) != S_OK)
+ {
+ goto error;
+ }
+
+ webBrowser2->lpVtbl->put_Left(webBrowser2, 0);
+ webBrowser2->lpVtbl->put_Top(webBrowser2, 0);
+ webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right);
+ webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom);
+ webBrowser2->lpVtbl->Release(webBrowser2);
+
+ return 0;
+ error:
+ UnEmbedBrowserObject(w);
+ if (pClassFactory != NULL)
+ {
+ pClassFactory->lpVtbl->Release(pClassFactory);
+ }
+ if (browser != NULL)
+ {
+ GlobalFree(browser);
+ }
+ return -1;
+ }
+
+#define WEBVIEW_DATA_URL_PREFIX "data:text/html,"
+ static int DisplayHTMLPage(struct webview *w)
+ {
+ IWebBrowser2 *webBrowser2;
+ VARIANT myURL;
+ LPDISPATCH lpDispatch;
+ IHTMLDocument2 *htmlDoc2;
+ BSTR bstr;
+ IOleObject *browserObject;
+ SAFEARRAY *sfArray;
+ VARIANT *pVar;
+ browserObject = *w->priv.browser;
+ int isDataURL = 0;
+ const char *webview_url = webview_check_url(w->url);
+ if (!browserObject->lpVtbl->QueryInterface(
+ browserObject, iid_unref(&IID_IWebBrowser2), (void **)&webBrowser2))
+ {
+ LPCSTR webPageName;
+ isDataURL = (strncmp(webview_url, WEBVIEW_DATA_URL_PREFIX,
+ strlen(WEBVIEW_DATA_URL_PREFIX)) == 0);
+ if (isDataURL)
+ {
+ webPageName = "about:blank";
+ }
+ else
+ {
+ webPageName = (LPCSTR)webview_url;
+ }
+ VariantInit(&myURL);
+ myURL.vt = VT_BSTR;
+ // #ifndef UNICODE
+ {
+ wchar_t *buffer = webview_to_utf16(webPageName);
+ if (buffer == NULL)
+ {
+ goto badalloc;
+ }
+ myURL.bstrVal = SysAllocString(buffer);
+ GlobalFree(buffer);
+ }
+ // #else
+ // myURL.bstrVal = SysAllocString(webPageName);
+ // #endif
+ if (!myURL.bstrVal)
+ {
+ badalloc:
+ webBrowser2->lpVtbl->Release(webBrowser2);
+ return (-6);
+ }
+ webBrowser2->lpVtbl->Navigate2(webBrowser2, &myURL, 0, 0, 0, 0);
+ VariantClear(&myURL);
+ if (!isDataURL)
+ {
+ return 0;
+ }
+
+ char *url = (char *)calloc(1, strlen(webview_url) + 1);
+ char *q = url;
+ for (const char *p = webview_url + strlen(WEBVIEW_DATA_URL_PREFIX); *q = *p;
+ p++, q++)
+ {
+ if (*q == '%' && *(p + 1) && *(p + 2))
+ {
+ sscanf(p + 1, "%02x", q);
+ p = p + 2;
+ }
+ }
+
+ if (webBrowser2->lpVtbl->get_Document(webBrowser2, &lpDispatch) == S_OK)
+ {
+ if (lpDispatch->lpVtbl->QueryInterface(lpDispatch,
+ iid_unref(&IID_IHTMLDocument2),
+ (void **)&htmlDoc2) == S_OK)
+ {
+ if ((sfArray = SafeArrayCreate(VT_VARIANT, 1,
+ (SAFEARRAYBOUND *)&ArrayBound)))
+ {
+ if (!SafeArrayAccessData(sfArray, (void **)&pVar))
+ {
+ pVar->vt = VT_BSTR;
+ // #ifndef UNICODE
+ {
+ wchar_t *buffer = webview_to_utf16(url);
+ if (buffer == NULL)
+ {
+ goto release;
+ }
+ bstr = SysAllocString(buffer);
+ GlobalFree(buffer);
+ }
+ // #else
+ // bstr = SysAllocString(url);
+ // #endif
+ if ((pVar->bstrVal = bstr))
+ {
+ htmlDoc2->lpVtbl->write(htmlDoc2, sfArray);
+ htmlDoc2->lpVtbl->close(htmlDoc2);
+ }
+ }
+ SafeArrayDestroy(sfArray);
+ }
+ release:
+ free(url);
+ htmlDoc2->lpVtbl->Release(htmlDoc2);
+ }
+ lpDispatch->lpVtbl->Release(lpDispatch);
+ }
+ webBrowser2->lpVtbl->Release(webBrowser2);
+ return (0);
+ }
+ return (-5);
+ }
+
+ static LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam)
+ {
+ struct webview *w = (struct webview *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ switch (uMsg)
+ {
+ case WM_CREATE:
+ w = (struct webview *)((CREATESTRUCT *)lParam)->lpCreateParams;
+ w->priv.hwnd = hwnd;
+ return EmbedBrowserObject(w);
+ case WM_DESTROY:
+ UnEmbedBrowserObject(w);
+ PostQuitMessage(0);
+ return TRUE;
+ case WM_SIZE:
+ {
+ IWebBrowser2 *webBrowser2;
+ IOleObject *browser = *w->priv.browser;
+ if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2),
+ (void **)&webBrowser2) == S_OK)
+ {
+ RECT rect;
+ GetClientRect(hwnd, &rect);
+ webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right);
+ webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom);
+ }
+ return TRUE;
+ }
+ case WM_WEBVIEW_DISPATCH:
+ {
+ webview_dispatch_fn f = (webview_dispatch_fn)wParam;
+ void *arg = (void *)lParam;
+ (*f)(w, arg);
+ return TRUE;
+ }
+ }
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+
+#define WEBVIEW_KEY_FEATURE_BROWSER_EMULATION \
+ "Software\\Microsoft\\Internet " \
+ "Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION"
+
+ static int webview_fix_ie_compat_mode()
+ {
+ HKEY hKey;
+ DWORD ie_version = 11000;
+ TCHAR appname[MAX_PATH + 1];
+ TCHAR *p;
+ if (GetModuleFileName(NULL, appname, MAX_PATH + 1) == 0)
+ {
+ return -1;
+ }
+ for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--)
+ {
+ }
+ p++;
+ if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION,
+ &hKey) != ERROR_SUCCESS)
+ {
+ return -1;
+ }
+ if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version,
+ sizeof(ie_version)) != ERROR_SUCCESS)
+ {
+ RegCloseKey(hKey);
+ return -1;
+ }
+ RegCloseKey(hKey);
+ return 0;
+ }
+
+ WEBVIEW_API int webview_init(struct webview *w)
+ {
+ WNDCLASSEX wc;
+ HINSTANCE hInstance;
+ DWORD style;
+ RECT clientRect;
+ RECT rect;
+
+ if (webview_fix_ie_compat_mode() < 0)
+ {
+ return -1;
+ }
+
+ hInstance = GetModuleHandle(NULL);
+ if (hInstance == NULL)
+ {
+ return -1;
+ }
+ if (OleInitialize(NULL) != S_OK)
+ {
+ return -1;
+ }
+
+ ZeroMemory(&wc, sizeof(WNDCLASSEX));
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.hInstance = hInstance;
+ wc.lpfnWndProc = wndproc;
+ wc.lpszClassName = classname;
+ wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(100));
+ wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(100));
+ RegisterClassEx(&wc);
+
+ style = WS_OVERLAPPEDWINDOW;
+ if (!w->resizable)
+ {
+ style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
+ }
+
+ // Scale
+ // Credit: https://github.com/webview/webview/issues/54#issuecomment-379528243
+ HDC hDC = GetDC(NULL);
+ w->width = GetDeviceCaps(hDC, 88)*w->width/96.0;
+ w->height = GetDeviceCaps(hDC, 90)*w->height/96.0;
+ ReleaseDC(NULL, hDC);
+
+ rect.left = 0;
+ rect.top = 0;
+ rect.right = w->width;
+ rect.bottom = w->height;
+ AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
+
+ GetClientRect(GetDesktopWindow(), &clientRect);
+ int left = (clientRect.right / 2) - ((rect.right - rect.left) / 2);
+ int top = (clientRect.bottom / 2) - ((rect.bottom - rect.top) / 2);
+ rect.right = rect.right - rect.left + left;
+ rect.left = left;
+ rect.bottom = rect.bottom - rect.top + top;
+ rect.top = top;
+
+#ifdef UNICODE
+ wchar_t *u16title = webview_to_utf16(w->title);
+ if (u16title == NULL)
+ {
+ return -1;
+ }
+
+ w->priv.hwnd =
+ CreateWindowEx(0, classname, u16title, style, rect.left, rect.top,
+ rect.right - rect.left, rect.bottom - rect.top,
+ HWND_DESKTOP, NULL, hInstance, (void *)w);
+#else
+ w->priv.hwnd =
+ CreateWindowEx(0, classname, w->title, style, rect.left, rect.top,
+ rect.right - rect.left, rect.bottom - rect.top,
+ HWND_DESKTOP, NULL, hInstance, (void *)w);
+#endif
+
+ if (w->priv.hwnd == 0)
+ {
+ OleUninitialize();
+ return -1;
+ }
+
+ SetWindowLongPtr(w->priv.hwnd, GWLP_USERDATA, (LONG_PTR)w);
+
+ DisplayHTMLPage(w);
+
+#ifdef UNICODE
+ SetWindowText(w->priv.hwnd, u16title);
+ GlobalFree(u16title);
+#else
+ SetWindowText(w->priv.hwnd, w->title);
+#endif
+
+ ShowWindow(w->priv.hwnd, SW_SHOWDEFAULT);
+ UpdateWindow(w->priv.hwnd);
+ SetFocus(w->priv.hwnd);
+
+ return 0;
+ }
+
+ WEBVIEW_API int webview_loop(struct webview *w, int blocking)
+ {
+ MSG msg;
+ if (blocking)
+ {
+ GetMessage(&msg, 0, 0, 0);
+ }
+ else
+ {
+ PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
+ }
+ switch (msg.message)
+ {
+ case WM_QUIT:
+ return -1;
+ case WM_COMMAND:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ {
+ // Disable refresh when pressing F5 on windows
+ if (msg.wParam == VK_F5)
+ {
+ break;
+ }
+ HRESULT r = S_OK;
+ IWebBrowser2 *webBrowser2;
+ IOleObject *browser = *w->priv.browser;
+ if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2),
+ (void **)&webBrowser2) == S_OK)
+ {
+ IOleInPlaceActiveObject *pIOIPAO;
+ if (browser->lpVtbl->QueryInterface(
+ browser, iid_unref(&IID_IOleInPlaceActiveObject),
+ (void **)&pIOIPAO) == S_OK)
+ {
+ r = pIOIPAO->lpVtbl->TranslateAccelerator(pIOIPAO, &msg);
+ pIOIPAO->lpVtbl->Release(pIOIPAO);
+ }
+ webBrowser2->lpVtbl->Release(webBrowser2);
+ }
+ if (r != S_FALSE)
+ {
+ break;
+ }
+ }
+ default:
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ return 0;
+ }
+
+ WEBVIEW_API int webview_eval(struct webview *w, const char *js)
+ {
+ IWebBrowser2 *webBrowser2;
+ IHTMLDocument2 *htmlDoc2;
+ IDispatch *docDispatch;
+ IDispatch *scriptDispatch;
+ if ((*w->priv.browser)
+ ->lpVtbl->QueryInterface((*w->priv.browser),
+ iid_unref(&IID_IWebBrowser2),
+ (void **)&webBrowser2) != S_OK)
+ {
+ return -1;
+ }
+
+ if (webBrowser2->lpVtbl->get_Document(webBrowser2, &docDispatch) != S_OK)
+ {
+ return -1;
+ }
+ if (docDispatch->lpVtbl->QueryInterface(docDispatch,
+ iid_unref(&IID_IHTMLDocument2),
+ (void **)&htmlDoc2) != S_OK)
+ {
+ return -1;
+ }
+ if (htmlDoc2->lpVtbl->get_Script(htmlDoc2, &scriptDispatch) != S_OK)
+ {
+ return -1;
+ }
+ DISPID dispid;
+ BSTR evalStr = SysAllocString(L"eval");
+ if (scriptDispatch->lpVtbl->GetIDsOfNames(
+ scriptDispatch, iid_unref(&IID_NULL), &evalStr, 1,
+ LOCALE_SYSTEM_DEFAULT, &dispid) != S_OK)
+ {
+ SysFreeString(evalStr);
+ return -1;
+ }
+ SysFreeString(evalStr);
+
+ DISPPARAMS params;
+ VARIANT arg;
+ VARIANT result;
+ EXCEPINFO excepInfo;
+ UINT nArgErr = (UINT)-1;
+ params.cArgs = 1;
+ params.cNamedArgs = 0;
+ params.rgvarg = &arg;
+ arg.vt = VT_BSTR;
+ static const char *prologue = "(function(){";
+ static const char *epilogue = ";})();";
+ int n = strlen(prologue) + strlen(epilogue) + strlen(js) + 1;
+ char *eval = (char *)malloc(n);
+ snprintf(eval, n, "%s%s%s", prologue, js, epilogue);
+ wchar_t *buf = webview_to_utf16(eval);
+ if (buf == NULL)
+ {
+ return -1;
+ }
+ arg.bstrVal = SysAllocString(buf);
+ if (scriptDispatch->lpVtbl->Invoke(
+ scriptDispatch, dispid, iid_unref(&IID_NULL), 0, DISPATCH_METHOD,
+ ¶ms, &result, &excepInfo, &nArgErr) != S_OK)
+ {
+ return -1;
+ }
+ SysFreeString(arg.bstrVal);
+ free(eval);
+ scriptDispatch->lpVtbl->Release(scriptDispatch);
+ htmlDoc2->lpVtbl->Release(htmlDoc2);
+ docDispatch->lpVtbl->Release(docDispatch);
+ return 0;
+ }
+
+ WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn,
+ void *arg)
+ {
+ PostMessageW(w->priv.hwnd, WM_WEBVIEW_DISPATCH, (WPARAM)fn, (LPARAM)arg);
+ }
+
+ WEBVIEW_API void webview_set_title(struct webview *w, const char *title)
+ {
+#ifdef UNICODE
+ wchar_t *u16title = webview_to_utf16(title);
+ if (u16title == NULL)
+ {
+ return;
+ }
+ SetWindowText(w->priv.hwnd, u16title);
+ GlobalFree(u16title);
+#else
+ SetWindowText(w->priv.hwnd, title);
+#endif
+ }
+
+ WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen)
+ {
+ if (w->priv.is_fullscreen == !!fullscreen)
+ {
+ return;
+ }
+ if (w->priv.is_fullscreen == 0)
+ {
+ w->priv.saved_style = GetWindowLong(w->priv.hwnd, GWL_STYLE);
+ w->priv.saved_ex_style = GetWindowLong(w->priv.hwnd, GWL_EXSTYLE);
+ GetWindowRect(w->priv.hwnd, &w->priv.saved_rect);
+ }
+ w->priv.is_fullscreen = !!fullscreen;
+ if (fullscreen)
+ {
+ MONITORINFO monitor_info;
+ SetWindowLong(w->priv.hwnd, GWL_STYLE,
+ w->priv.saved_style & ~(WS_CAPTION | WS_THICKFRAME));
+ SetWindowLong(w->priv.hwnd, GWL_EXSTYLE,
+ w->priv.saved_ex_style &
+ ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
+ WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
+ monitor_info.cbSize = sizeof(monitor_info);
+ GetMonitorInfo(MonitorFromWindow(w->priv.hwnd, MONITOR_DEFAULTTONEAREST),
+ &monitor_info);
+ RECT r;
+ r.left = monitor_info.rcMonitor.left;
+ r.top = monitor_info.rcMonitor.top;
+ r.right = monitor_info.rcMonitor.right;
+ r.bottom = monitor_info.rcMonitor.bottom;
+ SetWindowPos(w->priv.hwnd, NULL, r.left, r.top, r.right - r.left,
+ r.bottom - r.top,
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ }
+ else
+ {
+ SetWindowLong(w->priv.hwnd, GWL_STYLE, w->priv.saved_style);
+ SetWindowLong(w->priv.hwnd, GWL_EXSTYLE, w->priv.saved_ex_style);
+ SetWindowPos(w->priv.hwnd, NULL, w->priv.saved_rect.left,
+ w->priv.saved_rect.top,
+ w->priv.saved_rect.right - w->priv.saved_rect.left,
+ w->priv.saved_rect.bottom - w->priv.saved_rect.top,
+ SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+ }
+ }
+
+ WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g,
+ uint8_t b, uint8_t a)
+ {
+ HBRUSH brush = CreateSolidBrush(RGB(r, g, b));
+ SetClassLongPtr(w->priv.hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
+ }
+
+/* These are missing parts from MinGW */
+#ifndef __IFileDialog_INTERFACE_DEFINED__
+#define __IFileDialog_INTERFACE_DEFINED__
+ enum _FILEOPENDIALOGOPTIONS
+ {
+ FOS_OVERWRITEPROMPT = 0x2,
+ FOS_STRICTFILETYPES = 0x4,
+ FOS_NOCHANGEDIR = 0x8,
+ FOS_PICKFOLDERS = 0x20,
+ FOS_FORCEFILESYSTEM = 0x40,
+ FOS_ALLNONSTORAGEITEMS = 0x80,
+ FOS_NOVALIDATE = 0x100,
+ FOS_ALLOWMULTISELECT = 0x200,
+ FOS_PATHMUSTEXIST = 0x800,
+ FOS_FILEMUSTEXIST = 0x1000,
+ FOS_CREATEPROMPT = 0x2000,
+ FOS_SHAREAWARE = 0x4000,
+ FOS_NOREADONLYRETURN = 0x8000,
+ FOS_NOTESTFILECREATE = 0x10000,
+ FOS_HIDEMRUPLACES = 0x20000,
+ FOS_HIDEPINNEDPLACES = 0x40000,
+ FOS_NODEREFERENCELINKS = 0x100000,
+ FOS_DONTADDTORECENT = 0x2000000,
+ FOS_FORCESHOWHIDDEN = 0x10000000,
+ FOS_DEFAULTNOMINIMODE = 0x20000000,
+ FOS_FORCEPREVIEWPANEON = 0x40000000
+ };
+ typedef DWORD FILEOPENDIALOGOPTIONS;
+ typedef enum FDAP
+ {
+ FDAP_BOTTOM = 0,
+ FDAP_TOP = 1
+ } FDAP;
+ DEFINE_GUID(IID_IFileDialog, 0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07,
+ 0x5d, 0x13, 0x5f, 0xc8);
+ typedef struct IFileDialogVtbl
+ {
+ BEGIN_INTERFACE
+ HRESULT(STDMETHODCALLTYPE *QueryInterface)
+ (IFileDialog *This, REFIID riid, void **ppvObject);
+ ULONG(STDMETHODCALLTYPE *AddRef)
+ (IFileDialog *This);
+ ULONG(STDMETHODCALLTYPE *Release)
+ (IFileDialog *This);
+ HRESULT(STDMETHODCALLTYPE *Show)
+ (IFileDialog *This, HWND hwndOwner);
+ HRESULT(STDMETHODCALLTYPE *SetFileTypes)
+ (IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec);
+ HRESULT(STDMETHODCALLTYPE *SetFileTypeIndex)
+ (IFileDialog *This, UINT iFileType);
+ HRESULT(STDMETHODCALLTYPE *GetFileTypeIndex)
+ (IFileDialog *This, UINT *piFileType);
+ HRESULT(STDMETHODCALLTYPE *Advise)
+ (IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie);
+ HRESULT(STDMETHODCALLTYPE *Unadvise)
+ (IFileDialog *This, DWORD dwCookie);
+ HRESULT(STDMETHODCALLTYPE *SetOptions)
+ (IFileDialog *This, FILEOPENDIALOGOPTIONS fos);
+ HRESULT(STDMETHODCALLTYPE *GetOptions)
+ (IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos);
+ HRESULT(STDMETHODCALLTYPE *SetDefaultFolder)
+ (IFileDialog *This, IShellItem *psi);
+ HRESULT(STDMETHODCALLTYPE *SetFolder)
+ (IFileDialog *This, IShellItem *psi);
+ HRESULT(STDMETHODCALLTYPE *GetFolder)
+ (IFileDialog *This, IShellItem **ppsi);
+ HRESULT(STDMETHODCALLTYPE *GetCurrentSelection)
+ (IFileDialog *This, IShellItem **ppsi);
+ HRESULT(STDMETHODCALLTYPE *SetFileName)
+ (IFileDialog *This, LPCWSTR pszName);
+ HRESULT(STDMETHODCALLTYPE *GetFileName)
+ (IFileDialog *This, LPWSTR *pszName);
+ HRESULT(STDMETHODCALLTYPE *SetTitle)
+ (IFileDialog *This, LPCWSTR pszTitle);
+ HRESULT(STDMETHODCALLTYPE *SetOkButtonLabel)
+ (IFileDialog *This, LPCWSTR pszText);
+ HRESULT(STDMETHODCALLTYPE *SetFileNameLabel)
+ (IFileDialog *This, LPCWSTR pszLabel);
+ HRESULT(STDMETHODCALLTYPE *GetResult)
+ (IFileDialog *This, IShellItem **ppsi);
+ HRESULT(STDMETHODCALLTYPE *AddPlace)
+ (IFileDialog *This, IShellItem *psi, FDAP fdap);
+ HRESULT(STDMETHODCALLTYPE *SetDefaultExtension)
+ (IFileDialog *This, LPCWSTR pszDefaultExtension);
+ HRESULT(STDMETHODCALLTYPE *Close)
+ (IFileDialog *This, HRESULT hr);
+ HRESULT(STDMETHODCALLTYPE *SetClientGuid)
+ (IFileDialog *This, REFGUID guid);
+ HRESULT(STDMETHODCALLTYPE *ClearClientData)
+ (IFileDialog *This);
+ HRESULT(STDMETHODCALLTYPE *SetFilter)
+ (IFileDialog *This, IShellItemFilter *pFilter);
+ END_INTERFACE
+ } IFileDialogVtbl;
+ interface IFileDialog
+ {
+ CONST_VTBL IFileDialogVtbl *lpVtbl;
+ };
+ DEFINE_GUID(IID_IFileOpenDialog, 0xd57c7288, 0xd4ad, 0x4768, 0xbe, 0x02, 0x9d,
+ 0x96, 0x95, 0x32, 0xd9, 0x60);
+ DEFINE_GUID(IID_IFileSaveDialog, 0x84bccd23, 0x5fde, 0x4cdb, 0xae, 0xa4, 0xaf,
+ 0x64, 0xb8, 0x3d, 0x78, 0xab);
+#endif
+
+ WEBVIEW_API void webview_dialog(struct webview *w,
+ enum webview_dialog_type dlgtype, int flags,
+ const char *title, const char *arg,
+ char *result, size_t resultsz, char *filter)
+ {
+ if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
+ dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
+ {
+ IFileDialog *dlg = NULL;
+ IShellItem *res = NULL;
+ WCHAR *ws = NULL;
+ char *s = NULL;
+ FILEOPENDIALOGOPTIONS opts, add_opts;
+ if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN)
+ {
+ if (CoCreateInstance(
+ iid_unref(&CLSID_FileOpenDialog), NULL, CLSCTX_INPROC_SERVER,
+ iid_unref(&IID_IFileOpenDialog), (void **)&dlg) != S_OK)
+ {
+ goto error_dlg;
+ }
+ if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY)
+ {
+ add_opts |= FOS_PICKFOLDERS;
+ }
+ add_opts |= FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE |
+ FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE |
+ FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
+ FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
+ }
+ else
+ {
+ if (CoCreateInstance(
+ iid_unref(&CLSID_FileSaveDialog), NULL, CLSCTX_INPROC_SERVER,
+ iid_unref(&IID_IFileSaveDialog), (void **)&dlg) != S_OK)
+ {
+ goto error_dlg;
+ }
+ add_opts |= FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR |
+ FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE |
+ FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
+ FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
+ }
+ if (filter[0] != '\0')
+ {
+ int count;
+ int i=0;
+ char* token;
+ char* filter_dup = strdup(filter);
+ for (count=1; filter[count]; filter[count]==',' ? count++ : *filter++);
+ COMDLG_FILTERSPEC rgSpec[count];
+ char* filters[count];
+ token = strtok(filter_dup, ",");
+ while(token != NULL)
+ {
+ filters[i] = token;
+ token = strtok(NULL, ",");
+ i++;
+ }
+ for (int i=0; i < count; i++) {
+ wchar_t *wFilter = (wchar_t *)malloc(4096);
+ MultiByteToWideChar(CP_ACP, 0, filters[i], -1, wFilter, 4096);
+ rgSpec[i].pszName = wFilter;
+ rgSpec[i].pszSpec = wFilter;
+ }
+ if (dlg->lpVtbl->SetFileTypes(dlg, count, rgSpec) != S_OK) {
+ goto error_dlg;
+ }
+ }
+ if (dlg->lpVtbl->GetOptions(dlg, &opts) != S_OK)
+ {
+ goto error_dlg;
+ }
+ opts &= ~FOS_NOREADONLYRETURN;
+ opts |= add_opts;
+ if (dlg->lpVtbl->SetOptions(dlg, opts) != S_OK)
+ {
+ goto error_dlg;
+ }
+ if (dlg->lpVtbl->Show(dlg, w->priv.hwnd) != S_OK)
+ {
+ goto error_dlg;
+ }
+ if (dlg->lpVtbl->GetResult(dlg, &res) != S_OK)
+ {
+ goto error_dlg;
+ }
+ if (res->lpVtbl->GetDisplayName(res, SIGDN_FILESYSPATH, &ws) != S_OK)
+ {
+ goto error_result;
+ }
+ s = webview_from_utf16(ws);
+ strncpy(result, s, resultsz);
+ result[resultsz - 1] = '\0';
+ CoTaskMemFree(ws);
+ error_result:
+ res->lpVtbl->Release(res);
+ error_dlg:
+ dlg->lpVtbl->Release(dlg);
+ return;
+ }
+ else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT)
+ {
+#if 0
+ /* MinGW often doesn't contain TaskDialog, we'll use MessageBox for now */
+ WCHAR *wtitle = webview_to_utf16(title);
+ WCHAR *warg = webview_to_utf16(arg);
+ TaskDialog(w->priv.hwnd, NULL, NULL, wtitle, warg, 0, NULL, NULL);
+ GlobalFree(warg);
+ GlobalFree(wtitle);
+#else
+ UINT type = MB_OK;
+ switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK)
+ {
+ case WEBVIEW_DIALOG_FLAG_INFO:
+ type |= MB_ICONINFORMATION;
+ break;
+ case WEBVIEW_DIALOG_FLAG_WARNING:
+ type |= MB_ICONWARNING;
+ break;
+ case WEBVIEW_DIALOG_FLAG_ERROR:
+ type |= MB_ICONERROR;
+ break;
+ }
+ MessageBox(w->priv.hwnd, arg, title, type);
+#endif
+ }
+ }
+
+ WEBVIEW_API void webview_terminate(struct webview *w) { PostQuitMessage(0); }
+ WEBVIEW_API void webview_exit(struct webview *w) { OleUninitialize(); }
+ WEBVIEW_API void webview_print_log(const char *s) { OutputDebugString(s); }
+
+#endif /* WEBVIEW_WINAPI */
+
+#if defined(WEBVIEW_COCOA)
+#if (!defined MAC_OS_X_VERSION_10_12) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+#define NSAlertStyleWarning NSWarningAlertStyle
+#define NSAlertStyleCritical NSCriticalAlertStyle
+#define NSWindowStyleMaskResizable NSResizableWindowMask
+#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
+#define NSWindowStyleMaskTitled NSTitledWindowMask
+#define NSWindowStyleMaskClosable NSClosableWindowMask
+#define NSWindowStyleMaskFullScreen NSFullScreenWindowMask
+#define NSEventMaskAny NSAnyEventMask
+#define NSEventModifierFlagCommand NSCommandKeyMask
+#define NSEventModifierFlagOption NSAlternateKeyMask
+#define NSAlertStyleInformational NSInformationalAlertStyle
+#endif /* MAC_OS_X_VERSION_10_12 */
+#if (!defined MAC_OS_X_VERSION_10_13) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
+#define NSModalResponseOK NSFileHandlingPanelOKButton
+#endif /* MAC_OS_X_VERSION_10_12, MAC_OS_X_VERSION_10_13 */
+ static void webview_window_will_close(id self, SEL cmd, id notification)
+ {
+ struct webview *w =
+ (struct webview *)objc_getAssociatedObject(self, "webview");
+ webview_terminate(w);
+ }
+
+ static BOOL webview_is_selector_excluded_from_web_script(id self, SEL cmd,
+ SEL selector)
+ {
+ return selector != @selector(invoke:);
+ }
+
+ static NSString *webview_webscript_name_for_selector(id self, SEL cmd,
+ SEL selector)
+ {
+ return selector == @selector(invoke:) ? @"invoke" : nil;
+ }
+
+ static void webview_did_clear_window_object(id self, SEL cmd, id webview,
+ id script, id frame)
+ {
+ [script setValue:self forKey:@"external"];
+ }
+
+ static void webview_run_input_open_panel(id self, SEL cmd, id webview,
+ id listener, BOOL allowMultiple)
+ {
+ char filename[256] = "";
+ struct webview *w =
+ (struct webview *)objc_getAssociatedObject(self, "webview");
+
+ webview_dialog(w, WEBVIEW_DIALOG_TYPE_OPEN, WEBVIEW_DIALOG_FLAG_FILE, "", "",
+ filename, 255, "");
+ filename[255] = '\0';
+ if (strlen(filename) > 0)
+ {
+ [listener chooseFilename:[NSString stringWithUTF8String:filename]];
+ }
+ else
+ {
+ [listener cancel];
+ }
+ }
+
+ static void webview_external_invoke(id self, SEL cmd, id arg)
+ {
+ struct webview *w =
+ (struct webview *)objc_getAssociatedObject(self, "webview");
+ if (w == NULL || w->external_invoke_cb == NULL)
+ {
+ return;
+ }
+ if ([arg isKindOfClass:[NSString class]] == NO)
+ {
+ return;
+ }
+ w->external_invoke_cb(w, [(NSString *)(arg) UTF8String]);
+ }
+
+ WEBVIEW_API int webview_init(struct webview *w)
+ {
+ w->priv.pool = [[NSAutoreleasePool alloc] init];
+ [NSApplication sharedApplication];
+
+ Class webViewDelegateClass =
+ objc_allocateClassPair([NSObject class], "WebViewDelegate", 0);
+ class_addMethod(webViewDelegateClass, sel_registerName("windowWillClose:"),
+ (IMP)webview_window_will_close, "v@:@");
+ class_addMethod(object_getClass(webViewDelegateClass),
+ sel_registerName("isSelectorExcludedFromWebScript:"),
+ (IMP)webview_is_selector_excluded_from_web_script, "c@::");
+ class_addMethod(object_getClass(webViewDelegateClass),
+ sel_registerName("webScriptNameForSelector:"),
+ (IMP)webview_webscript_name_for_selector, "c@::");
+ class_addMethod(webViewDelegateClass,
+ sel_registerName("webView:didClearWindowObject:forFrame:"),
+ (IMP)webview_did_clear_window_object, "v@:@@@");
+ class_addMethod(
+ webViewDelegateClass,
+ sel_registerName("webView:runOpenPanelForFileButtonWithResultListener:"
+ "allowMultipleFiles:"),
+ (IMP)webview_run_input_open_panel, "v@:@@c");
+ class_addMethod(webViewDelegateClass, sel_registerName("invoke:"),
+ (IMP)webview_external_invoke, "v@:@");
+ objc_registerClassPair(webViewDelegateClass);
+
+ w->priv.delegate = [[webViewDelegateClass alloc] init];
+ objc_setAssociatedObject(w->priv.delegate, "webview", (id)(w),
+ OBJC_ASSOCIATION_ASSIGN);
+
+ NSRect r = NSMakeRect(0, 0, w->width, w->height);
+ NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
+ NSWindowStyleMaskMiniaturizable;
+ if (w->resizable)
+ {
+ style = style | NSWindowStyleMaskResizable;
+ // style = style | NSTexturedBackgroundWindowMask;
+ // style = style | NSUnifiedTitleAndToolbarWindowMask;
+ }
+
+ // Transparent title bar
+ // if (w->transparentTitlebar) {
+ // style = style | NSFullSizeContentViewWindowMask | NSUnifiedTitleAndToolbarWindowMask | NSTexturedBackgroundWindowMask;
+ // }
+
+ w->priv.window = [[NSWindow alloc] initWithContentRect:r
+ styleMask:style
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ [w->priv.window autorelease];
+
+ // Title
+ NSString *nsTitle = [NSString stringWithUTF8String:w->title];
+ [w->priv.window setTitle:nsTitle];
+
+ [w->priv.window setDelegate:w->priv.delegate];
+ [w->priv.window center];
+
+ // NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wat"];
+ // toolbar.showsBaselineSeparator = NO;
+ // [w->priv.window setToolbar:toolbar];
+
+ // if (w->transparentTitlebar) {
+
+ // // Configure window look with hidden toolbar
+ // [w->priv.window setTitlebarAppearsTransparent:YES];
+ // [w->priv.window setTitleVisibility:NSWindowTitleHidden];
+ // // w->priv.window.isMovableByWindowBackground = true;
+ // }
+
+ [[NSUserDefaults standardUserDefaults] setBool:!!w->debug
+ forKey:@"WebKitDeveloperExtras"];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+ w->priv.webview =
+ [[WebView alloc] initWithFrame:r
+ frameName:@"WebView"
+ groupName:nil];
+ NSURL *nsURL = [NSURL
+ URLWithString:[NSString stringWithUTF8String:webview_check_url(w->url)]];
+ [[w->priv.webview mainFrame] loadRequest:[NSURLRequest requestWithURL:nsURL]];
+
+ [w->priv.webview setAutoresizesSubviews:YES];
+ [w->priv.webview
+ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ w->priv.webview.frameLoadDelegate = w->priv.delegate;
+ w->priv.webview.UIDelegate = w->priv.delegate;
+ [[w->priv.window contentView] addSubview:w->priv.webview];
+ [w->priv.window orderFrontRegardless];
+
+ // Disable scrolling - make this configurable
+ // [[[w->priv.webview mainFrame] frameView] setAllowsScrolling:NO];
+
+ // ----> Enables WebGL but won't pass the app store guidelines
+ //
+ // WebPreferences *p = [w->priv.webview preferences];
+ // if ([p respondsToSelector:@selector(setWebGLEnabled:)]) {
+ // [p setWebGLEnabled:YES];
+ // }
+
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ [NSApp finishLaunching];
+ [NSApp activateIgnoringOtherApps:YES];
+
+ NSMenu *menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+
+ NSMenuItem *appMenuItem =
+ [[[NSMenuItem alloc] initWithTitle:@"wails app" action:NULL keyEquivalent:@""]
+ autorelease];
+ NSMenu *appMenu = [[[NSMenu alloc] initWithTitle:@"wails app"] autorelease];
+ [appMenuItem setSubmenu:appMenu];
+ [menubar addItem:appMenuItem];
+
+ NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Hide"
+ action:@selector(hide:)
+ keyEquivalent:@"h"] autorelease];
+ [appMenu addItem:item];
+ item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others"
+ action:@selector(hideOtherApplications:)
+ keyEquivalent:@"h"] autorelease];
+ [item setKeyEquivalentModifierMask:(NSEventModifierFlagOption |
+ NSEventModifierFlagCommand)];
+ [appMenu addItem:item];
+ item = [[[NSMenuItem alloc] initWithTitle:@"Show All"
+ action:@selector(unhideAllApplications:)
+ keyEquivalent:@""] autorelease];
+ [appMenu addItem:item];
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ NSMenuItem *editMenuItem =
+ [[[NSMenuItem alloc] initWithTitle:@"Edit" action:NULL keyEquivalent:@""]
+ autorelease];
+ NSMenu *editMenu = [[[NSMenu alloc] initWithTitle:@"Edit"] autorelease];
+ [editMenuItem setSubmenu:editMenu];
+ [menubar addItem:editMenuItem];
+
+ item = [[[NSMenuItem alloc] initWithTitle:@"Cut"
+ action:@selector(cut:)
+ keyEquivalent:@"x"] autorelease];
+ [editMenu addItem:item];
+
+ item = [[[NSMenuItem alloc] initWithTitle:@"Copy"
+ action:@selector(copy:)
+ keyEquivalent:@"c"] autorelease];
+ [editMenu addItem:item];
+
+ item = [[[NSMenuItem alloc] initWithTitle:@"Paste"
+ action:@selector(paste:)
+ keyEquivalent:@"v"] autorelease];
+ [editMenu addItem:item];
+
+ item = [[[NSMenuItem alloc] initWithTitle:@"Select All"
+ action:@selector(selectAll:)
+ keyEquivalent:@"a"] autorelease];
+ [editMenu addItem:item];
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ item = [[[NSMenuItem alloc] initWithTitle:@"Quit"
+ action:@selector(terminate:)
+ keyEquivalent:@"q"] autorelease];
+ [appMenu addItem:item];
+
+ [NSApp setMainMenu:menubar];
+
+ w->priv.should_exit = 0;
+ return 0;
+ }
+
+ WEBVIEW_API int webview_loop(struct webview *w, int blocking)
+ {
+ NSDate *until = (blocking ? [NSDate distantFuture] : [NSDate distantPast]);
+ NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
+ untilDate:until
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+ if (event)
+ {
+ [NSApp sendEvent:event];
+ }
+ return w->priv.should_exit;
+ }
+
+ WEBVIEW_API int webview_eval(struct webview *w, const char *js)
+ {
+ NSString *nsJS = [NSString stringWithUTF8String:js];
+ [[w->priv.webview windowScriptObject] evaluateWebScript:nsJS];
+ return 0;
+ }
+
+ WEBVIEW_API void webview_set_title(struct webview *w, const char *title)
+ {
+ NSString *nsTitle = [NSString stringWithUTF8String:title];
+ [w->priv.window setTitle:nsTitle];
+ }
+
+ WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen)
+ {
+ int b = ((([w->priv.window styleMask] & NSWindowStyleMaskFullScreen) ==
+ NSWindowStyleMaskFullScreen)
+ ? 1
+ : 0);
+ if (b != fullscreen)
+ {
+ [w->priv.window toggleFullScreen:nil];
+ }
+ }
+
+ WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g,
+ uint8_t b, uint8_t a)
+ {
+ [w->priv.window setBackgroundColor:[NSColor colorWithRed:(CGFloat)r / 255.0
+ green:(CGFloat)g / 255.0
+ blue:(CGFloat)b / 255.0
+ alpha:(CGFloat)a / 255.0]];
+ if (0.5 >= ((r / 255.0 * 299.0) + (g / 255.0 * 587.0) + (b / 255.0 * 114.0)) /
+ 1000.0)
+ {
+ [w->priv.window
+ setAppearance:[NSAppearance
+ appearanceNamed:NSAppearanceNameVibrantDark]];
+ }
+ else
+ {
+ [w->priv.window
+ setAppearance:[NSAppearance
+ appearanceNamed:NSAppearanceNameVibrantLight]];
+ }
+ [w->priv.window setOpaque:NO];
+ [w->priv.window setTitlebarAppearsTransparent:YES];
+ [w->priv.webview setDrawsBackground:NO];
+ }
+
+ WEBVIEW_API void webview_dialog(struct webview *w,
+ enum webview_dialog_type dlgtype, int flags,
+ const char *title, const char *arg,
+ char *result, size_t resultsz, char *filter)
+ {
+ if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
+ dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
+ {
+ NSSavePanel *panel;
+ NSString *filter_str = [NSString stringWithUTF8String:filter];
+ filter_str = [filter_str stringByReplacingOccurrencesOfString:@"*."
+ withString:@""];
+ NSArray *fileTypes = [filter_str componentsSeparatedByString:@","];
+ if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN)
+ {
+ NSOpenPanel *openPanel = [NSOpenPanel openPanel];
+ if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY)
+ {
+ [openPanel setCanChooseFiles:NO];
+ [openPanel setCanChooseDirectories:YES];
+ }
+ else
+ {
+ [openPanel setCanChooseFiles:YES];
+ [openPanel setCanChooseDirectories:NO];
+ if(filter[0] != NULL)
+ {
+ [openPanel setAllowedFileTypes:fileTypes];
+ }
+ }
+ [openPanel setResolvesAliases:NO];
+ [openPanel setAllowsMultipleSelection:NO];
+ panel = openPanel;
+ }
+ else
+ {
+ panel = [NSSavePanel savePanel];
+ }
+ [panel setCanCreateDirectories:YES];
+ [panel setShowsHiddenFiles:YES];
+ [panel setExtensionHidden:NO];
+ [panel setCanSelectHiddenExtension:NO];
+ if(filter[0] != NULL)
+ {
+ [panel setAllowedFileTypes:fileTypes];
+ }
+ [panel setTreatsFilePackagesAsDirectories:YES];
+ [panel beginSheetModalForWindow:w->priv.window
+ completionHandler:^(NSInteger result) {
+ [NSApp stopModalWithCode:result];
+ }];
+ if ([NSApp runModalForWindow:panel] == NSModalResponseOK)
+ {
+ const char *filename = [[[panel URL] path] UTF8String];
+ strlcpy(result, filename, resultsz);
+ }
+ }
+ else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT)
+ {
+ NSAlert *a = [NSAlert new];
+ switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK)
+ {
+ case WEBVIEW_DIALOG_FLAG_INFO:
+ [a setAlertStyle:NSAlertStyleInformational];
+ break;
+ case WEBVIEW_DIALOG_FLAG_WARNING:
+ NSLog(@"warning");
+ [a setAlertStyle:NSAlertStyleWarning];
+ break;
+ case WEBVIEW_DIALOG_FLAG_ERROR:
+ NSLog(@"error");
+ [a setAlertStyle:NSAlertStyleCritical];
+ break;
+ }
+ [a setShowsHelp:NO];
+ [a setShowsSuppressionButton:NO];
+ [a setMessageText:[NSString stringWithUTF8String:title]];
+ [a setInformativeText:[NSString stringWithUTF8String:arg]];
+ [a addButtonWithTitle:@"OK"];
+ [a runModal];
+ [a release];
+ }
+ }
+
+ static void webview_dispatch_cb(void *arg)
+ {
+ struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)arg;
+ (context->fn)(context->w, context->arg);
+ free(context);
+ }
+
+ WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn,
+ void *arg)
+ {
+ struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)malloc(
+ sizeof(struct webview_dispatch_arg));
+ context->w = w;
+ context->arg = arg;
+ context->fn = fn;
+ dispatch_async_f(dispatch_get_main_queue(), context, webview_dispatch_cb);
+ }
+
+ WEBVIEW_API void webview_terminate(struct webview *w)
+ {
+ w->priv.should_exit = 1;
+ }
+ WEBVIEW_API void webview_exit(struct webview *w) { [NSApp terminate:NSApp]; }
+ WEBVIEW_API void webview_print_log(const char *s) { NSLog(@"%s", s); }
+
+#endif /* WEBVIEW_COCOA */
+
+#endif /* WEBVIEW_IMPLEMENTATION */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WEBVIEW_H */
diff --git a/licenses/github.com/dchest/cssmin/LICENSE b/licenses/github.com/dchest/cssmin/LICENSE
new file mode 100644
index 000000000..c9b4ec5dd
--- /dev/null
+++ b/licenses/github.com/dchest/cssmin/LICENSE
@@ -0,0 +1,29 @@
+Go Port:
+Copyright (c) 2013 Dmitry Chestnykh
+
+Original:
+Copyright (c) 2008 Ryan Grove
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of this project nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/github.com/dchest/htmlmin/LICENSE b/licenses/github.com/dchest/htmlmin/LICENSE
new file mode 100644
index 000000000..9ddea1fd3
--- /dev/null
+++ b/licenses/github.com/dchest/htmlmin/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2013 Dmitry Chestnykh. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/github.com/fatih/color/LICENSE.md b/licenses/github.com/fatih/color/LICENSE.md
new file mode 100644
index 000000000..25fdaf639
--- /dev/null
+++ b/licenses/github.com/fatih/color/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Fatih Arslan
+
+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/licenses/github.com/go-playground/colors/LICENSE b/licenses/github.com/go-playground/colors/LICENSE
new file mode 100644
index 000000000..6a2ae9aa4
--- /dev/null
+++ b/licenses/github.com/go-playground/colors/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Dean Karn
+
+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/licenses/github.com/gorilla/websocket/LICENSE b/licenses/github.com/gorilla/websocket/LICENSE
new file mode 100644
index 000000000..9171c9722
--- /dev/null
+++ b/licenses/github.com/gorilla/websocket/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/github.com/jackmordaunt/icns/LICENSE b/licenses/github.com/jackmordaunt/icns/LICENSE
new file mode 100644
index 000000000..787939ec2
--- /dev/null
+++ b/licenses/github.com/jackmordaunt/icns/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Jack Mordaunt
+
+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/licenses/github.com/konsorten/go-windows-terminal-sequences/LICENSE b/licenses/github.com/konsorten/go-windows-terminal-sequences/LICENSE
new file mode 100644
index 000000000..14127cd83
--- /dev/null
+++ b/licenses/github.com/konsorten/go-windows-terminal-sequences/LICENSE
@@ -0,0 +1,9 @@
+(The MIT License)
+
+Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
+
+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/licenses/github.com/leaanthony/mewn/LICENSE b/licenses/github.com/leaanthony/mewn/LICENSE
new file mode 100644
index 000000000..1cf398a90
--- /dev/null
+++ b/licenses/github.com/leaanthony/mewn/LICENSE
@@ -0,0 +1,7 @@
+© 2019-Present Lea Anthony
+
+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.
\ No newline at end of file
diff --git a/licenses/github.com/leaanthony/slicer/LICENSE b/licenses/github.com/leaanthony/slicer/LICENSE
new file mode 100644
index 000000000..558a6a271
--- /dev/null
+++ b/licenses/github.com/leaanthony/slicer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Lea Anthony
+
+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/licenses/github.com/leaanthony/spinner/LICENSE b/licenses/github.com/leaanthony/spinner/LICENSE
new file mode 100644
index 000000000..7e01c45c9
--- /dev/null
+++ b/licenses/github.com/leaanthony/spinner/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Lea Anthony
+
+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/licenses/github.com/leaanthony/synx/LICENSE b/licenses/github.com/leaanthony/synx/LICENSE
new file mode 100644
index 000000000..75ba4230d
--- /dev/null
+++ b/licenses/github.com/leaanthony/synx/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018-Present Lea Anthony
+
+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.
\ No newline at end of file
diff --git a/licenses/github.com/leaanthony/wincursor/LICENSE b/licenses/github.com/leaanthony/wincursor/LICENSE
new file mode 100644
index 000000000..75ba4230d
--- /dev/null
+++ b/licenses/github.com/leaanthony/wincursor/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018-Present Lea Anthony
+
+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.
\ No newline at end of file
diff --git a/licenses/github.com/mattn/go-colorable/LICENSE b/licenses/github.com/mattn/go-colorable/LICENSE
new file mode 100644
index 000000000..91b5cef30
--- /dev/null
+++ b/licenses/github.com/mattn/go-colorable/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 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.
diff --git a/licenses/github.com/mattn/go-isatty/LICENSE b/licenses/github.com/mattn/go-isatty/LICENSE
new file mode 100644
index 000000000..65dc692b6
--- /dev/null
+++ b/licenses/github.com/mattn/go-isatty/LICENSE
@@ -0,0 +1,9 @@
+Copyright (c) Yasuhiro MATSUMOTO
+
+MIT License (Expat)
+
+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/licenses/github.com/mitchellh/go-homedir/LICENSE b/licenses/github.com/mitchellh/go-homedir/LICENSE
new file mode 100644
index 000000000..f9c841a51
--- /dev/null
+++ b/licenses/github.com/mitchellh/go-homedir/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Mitchell Hashimoto
+
+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/licenses/github.com/nfnt/resize/LICENSE b/licenses/github.com/nfnt/resize/LICENSE
new file mode 100644
index 000000000..7836cad5f
--- /dev/null
+++ b/licenses/github.com/nfnt/resize/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2012, Jan Schlicht
+
+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/licenses/github.com/pkg/errors/LICENSE b/licenses/github.com/pkg/errors/LICENSE
new file mode 100644
index 000000000..835ba3e75
--- /dev/null
+++ b/licenses/github.com/pkg/errors/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2015, Dave Cheney
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/github.com/sirupsen/logrus/LICENSE b/licenses/github.com/sirupsen/logrus/LICENSE
new file mode 100644
index 000000000..f090cb42f
--- /dev/null
+++ b/licenses/github.com/sirupsen/logrus/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+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/licenses/github.com/wailsapp/webview/LICENSE b/licenses/github.com/wailsapp/webview/LICENSE
new file mode 100644
index 000000000..b18604bf4
--- /dev/null
+++ b/licenses/github.com/wailsapp/webview/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+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/licenses/golang.org/x/crypto/LICENSE b/licenses/golang.org/x/crypto/LICENSE
new file mode 100644
index 000000000..6a66aea5e
--- /dev/null
+++ b/licenses/golang.org/x/crypto/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/golang.org/x/net/LICENSE b/licenses/golang.org/x/net/LICENSE
new file mode 100644
index 000000000..6a66aea5e
--- /dev/null
+++ b/licenses/golang.org/x/net/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/licenses/golang.org/x/sys/LICENSE b/licenses/golang.org/x/sys/LICENSE
new file mode 100644
index 000000000..6a66aea5e
--- /dev/null
+++ b/licenses/golang.org/x/sys/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/logo_cropped.png b/logo_cropped.png
new file mode 100644
index 000000000..55113ebc8
Binary files /dev/null and b/logo_cropped.png differ
diff --git a/pace.jpeg b/pace.jpeg
new file mode 100644
index 000000000..897885637
Binary files /dev/null and b/pace.jpeg differ
diff --git a/runtime/assets/bridge.js b/runtime/assets/bridge.js
new file mode 100644
index 000000000..5c2f73ee3
--- /dev/null
+++ b/runtime/assets/bridge.js
@@ -0,0 +1,210 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+function init() {
+ // Bridge object
+ window.wailsbridge = {
+ reconnectOverlay: null,
+ reconnectTimer: 300,
+ wsURL: 'ws://' + window.location.hostname + ':34115/bridge',
+ connectionState: null,
+ config: {},
+ websocket: null,
+ callback: null,
+ overlayHTML:
+ 'Wails Bridge
Waiting for backend
',
+ overlayCSS:
+ '.wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}',
+ log: function (message) {
+ // eslint-disable-next-line
+ console.log(
+ '%c wails bridge %c ' + message + ' ',
+ 'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem',
+ 'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem'
+ );
+ }
+ };
+}
+
+// Adapted from webview - thanks zserge!
+function injectCSS(css) {
+ var elem = document.createElement('style');
+ elem.setAttribute('type', 'text/css');
+ if (elem.styleSheet) {
+ elem.styleSheet.cssText = css;
+ } else {
+ elem.appendChild(document.createTextNode(css));
+ }
+ var head = document.head || document.getElementsByTagName('head')[0];
+ head.appendChild(elem);
+}
+
+// Creates a node in the Dom
+function createNode(parent, elementType, id, className, content) {
+ var d = document.createElement(elementType);
+ if (id) {
+ d.id = id;
+ }
+ if (className) {
+ d.className = className;
+ }
+ if (content) {
+ d.innerHTML = content;
+ }
+ parent.appendChild(d);
+ return d;
+}
+
+// Sets up the overlay
+function setupOverlay() {
+ var body = document.body;
+ var wailsBridgeNode = createNode(body, 'div', 'wails-bridge');
+ wailsBridgeNode.innerHTML = window.wailsbridge.overlayHTML;
+
+ // Inject the overlay CSS
+ injectCSS(window.wailsbridge.overlayCSS);
+}
+
+// Start the Wails Bridge
+function startBridge() {
+ // Setup the overlay
+ setupOverlay();
+
+ window.wailsbridge.websocket = null;
+ window.wailsbridge.connectTimer = null;
+ window.wailsbridge.reconnectOverlay = document.querySelector(
+ '.wails-reconnect-overlay'
+ );
+ window.wailsbridge.connectionState = 'disconnected';
+
+ // Shows the overlay
+ function showReconnectOverlay() {
+ window.wailsbridge.reconnectOverlay.style.display = 'block';
+ }
+
+ // Hides the overlay
+ function hideReconnectOverlay() {
+ window.wailsbridge.reconnectOverlay.style.display = 'none';
+ }
+
+ // Adds a script to the Dom.
+ // Removes it if second parameter is true.
+ function addScript(script, remove) {
+ var s = document.createElement('script');
+ s.setAttribute('type', 'text/javascript');
+ s.textContent = script;
+ document.head.appendChild(s);
+
+ // Remove internal messages from the DOM
+ if (remove) {
+ s.parentNode.removeChild(s);
+ }
+ }
+
+ // Handles incoming websocket connections
+ function handleConnect() {
+ window.wailsbridge.log('Connected to backend');
+ hideReconnectOverlay();
+ clearInterval(window.wailsbridge.connectTimer);
+ window.wailsbridge.websocket.onclose = handleDisconnect;
+ window.wailsbridge.websocket.onmessage = handleMessage;
+ window.wailsbridge.connectionState = 'connected';
+ }
+
+ // Handles websocket disconnects
+ function handleDisconnect() {
+ window.wailsbridge.log('Disconnected from backend');
+ window.wailsbridge.websocket = null;
+ window.wailsbridge.connectionState = 'disconnected';
+ showReconnectOverlay();
+ connect();
+ }
+
+ // Try to connect to the backend every 300ms (default value).
+ // Change this value in the main wailsbridge object.
+ function connect() {
+ window.wailsbridge.connectTimer = setInterval(function () {
+ if (window.wailsbridge.websocket == null) {
+ window.wailsbridge.websocket = new WebSocket(window.wailsbridge.wsURL);
+ window.wailsbridge.websocket.onopen = handleConnect;
+ window.wailsbridge.websocket.onerror = function (e) {
+ e.stopImmediatePropagation();
+ e.stopPropagation();
+ e.preventDefault();
+ window.wailsbridge.websocket = null;
+ return false;
+ };
+ }
+ }, window.wailsbridge.reconnectTimer);
+ }
+
+ function handleMessage(message) {
+ // As a bridge we ignore js and css injections
+ switch (message.data[0]) {
+ // Wails library - inject!
+ case 'w':
+ addScript(message.data.slice(1));
+
+ // Now wails runtime is loaded, wails for the ready event
+ // and callback to the main app
+ window.wails.Events.On('wails:loaded', function () {
+ window.wailsbridge.log('Wails Ready');
+ if (window.wailsbridge.callback) {
+ window.wailsbridge.log('Notifying application');
+ window.wailsbridge.callback(window.wails);
+ }
+ });
+ window.wailsbridge.log('Loaded Wails Runtime');
+ break;
+ // Notifications
+ case 'n':
+ addScript(message.data.slice(1), true);
+ break;
+ // Binding
+ case 'b':
+ var binding = message.data.slice(1);
+ //log("Binding: " + binding)
+ window.wails._.NewBinding(binding);
+ break;
+ // Call back
+ case 'c':
+ var callbackData = message.data.slice(1);
+ window.wails._.Callback(callbackData);
+ break;
+ default:
+ window.wails.Log.Error('Unknown message type received: ' + message.data[0]);
+ }
+ }
+
+ // Start by showing the overlay...
+ showReconnectOverlay();
+
+ // ...and attempt to connect
+ connect();
+}
+
+function start(callback) {
+
+ // Set up the bridge
+ init();
+
+ // Save the callback
+ window.wailsbridge.callback = callback;
+
+ // Start Bridge
+ startBridge();
+}
+
+function Init(callback) {
+ start(callback);
+}
+
+module.exports = Init;
diff --git a/runtime/assets/console.js b/runtime/assets/console.js
new file mode 100644
index 000000000..e64867d25
--- /dev/null
+++ b/runtime/assets/console.js
@@ -0,0 +1,175 @@
+(function () {
+
+ window.wailsconsole = {};
+
+ var debugconsole = document.createElement("div");
+ var header = document.createElement("div");
+ var consoleOut = document.createElement("div");
+
+
+ document.addEventListener('keyup', logKey);
+
+ debugconsole.id = "wailsdebug";
+ debugconsole.style.fontSize = "18px";
+ debugconsole.style.width = "100%";
+ debugconsole.style.height = "35%";
+ debugconsole.style.maxHeight = "35%";
+ debugconsole.style.position = "fixed";
+ debugconsole.style.left = "0px";
+ debugconsole.style.backgroundColor = "rgba(255,255,255,0.8)";
+ debugconsole.style.borderTop = '1px solid black';
+ debugconsole.style.color = "black";
+ debugconsole.style.display = "none";
+
+ header.style.width = "100%";
+ header.style.height = "30px";
+ header.style.display = "block";
+ // header.style.paddingTop = "3px";
+ header.style.verticalAlign = "middle";
+ header.style.paddingLeft = "10px";
+ header.style.background = "rgba(255,255,255,0.8)";
+ header.innerHTML = " Wails Console > Clear ";
+
+ consoleOut.style.position = "absolute";
+ consoleOut.style.width = "100%";
+ consoleOut.style.height = "auto";
+ consoleOut.style.top = "30px";
+ // consoleOut.style.paddingLeft = "10px";
+ consoleOut.style.bottom = "0px";
+ consoleOut.style.backgroundColor = "rgba(200,200,200,1)";
+ consoleOut.style.overflowY = "scroll";
+ consoleOut.style.msOverflowStyle = "-ms-autohiding-scrollbar";
+
+ debugconsole.appendChild(header);
+ debugconsole.appendChild(consoleOut);
+ document.body.appendChild(debugconsole);
+ console.log(debugconsole.style.display)
+
+ function logKey(e) {
+ var conin = document.getElementById('conin');
+ if (e.which == 27 && e.shiftKey) {
+ toggleConsole(conin);
+ }
+ if (e.which == 13 && consoleVisible()) {
+ var command = conin.value.trim();
+ if (command.length > 0) {
+ console.log("> " + command)
+ try {
+ evaluateInput(command);
+ } catch (e) {
+ console.error(e.message);
+ }
+ conin.value = "";
+ }
+ }
+ };
+
+
+ function consoleVisible() {
+ return debugconsole.style.display == "block";
+ }
+
+ function toggleConsole(conin) {
+ var display = "none"
+ if (debugconsole.style.display == "none") display = "block";
+ debugconsole.style.display = display;
+ if (display == "block") {
+ conin.focus();
+ }
+ }
+
+ function evaluateExpression(expression) {
+
+ var pathSegments = [].concat(expression.split('.'));
+ if (pathSegments[0] == 'window') {
+ pathSegments.shift()
+ }
+ var currentObject = window;
+ for (var i = 0; i < pathSegments.length; i++) {
+ var pathSegment = pathSegments[i];
+ if (currentObject[pathSegment] == undefined) {
+ return false;
+ }
+ currentObject = currentObject[pathSegment];
+ }
+ console.log(JSON.stringify(currentObject));
+
+ return true;
+ }
+
+ function evaluateInput(command) {
+ try {
+ if (evaluateExpression(command)) {
+ return
+ } else {
+ eval(command);
+ }
+ } catch (e) {
+ console.error(e.message)
+ }
+ }
+
+
+ // Set us up as a listener
+ function hookIntoIPC() {
+ if (window.wails && window.wails._ && window.wails._.AddIPCListener) {
+ window.wails._.AddIPCListener(processIPCMessage);
+ } else {
+ setTimeout(hookIntoIPC, 100);
+ }
+ }
+ hookIntoIPC();
+
+ function processIPCMessage(message) {
+ console.log(message);
+ var parsedMessage;
+ try {
+ parsedMessage = JSON.parse(message);
+ } catch (e) {
+ console.error("Error in parsing IPC message: " + e.message);
+ return false;
+ }
+ var logmessage = "[IPC] "
+ switch (parsedMessage.type) {
+ case 'call':
+ logmessage += " Call: " + parsedMessage.payload.bindingName;
+ var params = "";
+ var parsedParams = JSON.parse(parsedMessage.payload.data);
+ if (parsedParams.length > 0) {
+ params = parsedParams;
+ }
+ logmessage += "(" + params + ")";
+ break;
+ case 'log':
+ logmessage += "Log (" + parsedMessage.payload.level + "): " + parsedMessage.payload.message;
+ break;
+ default:
+ logmessage = message;
+ }
+ console.log(logmessage);
+ }
+
+ window.wailsconsole.clearConsole = function () {
+ consoleOut.innerHTML = "";
+ }
+
+ console.log = function (message) {
+ consoleOut.innerHTML = consoleOut.innerHTML + "" + message + ' ';
+ consoleOut.scrollTop = consoleOut.scrollHeight;
+
+ };
+ console.error = function (message) {
+ consoleOut.innerHTML = consoleOut.innerHTML + " Error: " + message + ' ';
+ consoleOut.scrollTop = consoleOut.scrollHeight;
+ };
+ // var h = document.getElementsByTagName("html")[0];
+ // console.log("html margin: " + h.style.marginLeft);
+ // console.log("html padding: " + h.style.paddingLeft);
+
+ // setInterval(function() { console.log("test");}, 1000);
+ // setInterval(function() { console.error("oops");}, 3000);
+ // var script = document.createElement('script');
+ // script.src = "https://cdnjs.cloudflare.com/ajax/libs/firebug-lite/1.4.0/firebug-lite.js#startOpened=true";
+ // document.body.appendChild(script);
+
+})();
\ No newline at end of file
diff --git a/runtime/assets/wails.css b/runtime/assets/wails.css
new file mode 100644
index 000000000..b807abe69
--- /dev/null
+++ b/runtime/assets/wails.css
@@ -0,0 +1,39 @@
+/*
+ Some css from https://gist.github.com/abelaska/9c9eda70d31315f27a564be2ee490cf4
+ Many thanks!
+*/
+
+/* https://fonts.google.com/specimen/Roboto?selection.family=Roboto:300,400,500,700 https://www.fontsquirrel.com/tools/webfont-generator */
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 300;
+ src:url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
+}
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 400;
+ src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
+}
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 500;
+ src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
+}
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 700;
+ src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff');
+}
+
+html {
+ font-family: 'Roboto'
+}
+
+.wails-logo {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAMAAABIw9uxAAABs1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAAAAAAAAAAAAAAAAAABAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAAAAAAiHh8iHh8iHh8AAAAiHh8iHh8iHh8AAAAFBAQhHR4DAgMHBgYAAAAgHB0gHR4JCAgfHBwhHR4hHR4gHB0LCgoiHh8RDw8hHR4AAAAfGxwiHh8iHh8HBgYfHBwAAAAhHR4iHh8fGxwAAAAgHR4eGhshHR4hHR4hHh8AAAAJCAkfHB0gHB0hHR4gHB0gHB0hHR4fHBwiHh8fGxweGxsfHBwiHh8hHR4gHB0iHh8fGxwaFxcAAAAhHR4gHB0aFxcNCwwgHB0XFBUiHh8iHh8eGxwUEhMSEBAgHB0AAAAXFRUhHR4eGxweGxwhHR4ZFhcfHB0bGBgbGBkhHR4WFBUgHB0AAAAeGhsiHh8gHB0fGxwcGRkYFRYgHB0gHB0AAAAAAAAAAAAAAAAAAAAAAAAgHB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUEhIAAAAjHyAAAACekdHkAAAAj3RSTlMAAwYKGBsvFDcsMiUfEA46IggSJzU8DCn7+PXH7ejwhULUP0bnu7hRp+LQpFXzXL+jq+XXSZ34y+Cgl8KQ2sWIok1hmo1IMm9TsFizZoN1XJZEimt4aoRgLG/ctSdrZiB6d81PNt5/OpKWfnPH8q/JTEAaeyQ9f3Ba7OS9HbCIwp2MZanZlNPftpB1zY48md6yzrkAAIXESURBVHja7MGBAAAAAICg/akXqQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYPbgQAAAAAAAyP+1EVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVhT04EAAAAAAA8n9tBFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYe/uepOGwgCOs/JS0UkoU1YIENGAbKjEEbOZAYusvAqDGVAEIlG2oNPINt2dX8E8X9n29JVSHFMGVZ7fhU4vlt38n56etQdkepQFIbSUvLnawGtBCC0db25vd39gQQgtG+thNcOG9s4tCKEl02pkfABbDRf/9QrPghBaDisXXBt4yQqzIsMhgNAyYE6fBUEwLJLqcQQgtCw8tZIfiOgZ3ztOAISWxu1mggXR09aKhBLgBEDo/7ZyGvOBZHihpK+a7QTw/mjl80eDi7PD3Gm/WCzXihffGQtCaBFa3BbI2mUS/5i/GwArdHeQqxVOUvFOJhn0gWInyhX6edqCEFoIT2EbFIEDK0UwGhTxRwOA6eYqe/HMFoxhw0+rtbwXby8QWhyqX/KBwpeipfitGgxz9SUAxYdfJeEbSccOzly4x4jQYtEnI4WWuqR+KzFhAlgu1Ts9iSVZmGCn1MjRlAaOAIQWovXMBxrDM4oS4/fyXAL+b/IfUw4AJp9NJQIwUTBW+U7p4K8ZEFqAldMEaB3XxPpJ+ipxAjCX7gF4DxvxMAuT+SONo9EbC4KS4QRAaG68hTRoBRoupf7bKv5fUwwAT58bsvAb7DaXu201xPBwAiA0Tz0uBFq+1Dmpn8TvkakDgGEm9v+jVg/D720ffCe3FFpenmaHgcABgNAcdGMsjIi25Po9PJoQJ4BQ6uQFwPfs8zZcIl1t8d9YT54CoxMABwBC160b1+UfyFqtcv20ShoAk/qnDrk0XCZYPyNLCr3bAu0eI4MDAKE5+P6chVGxnlQ/LbAJaMHv+r9di4XgMqF4n/bQBjw8aQLgAEBofnrP9PlvFPn8pfptCnkAGPbfauyycKnjgp18Sx2aUCcADgCE5qS37wOdfZt88bdpTe5/wLVhCsmKTWIX2RSaCaCuAHATEKHrdV4fyz+dc2nq14Sq639F0q22YRqbWbvNPm5keYELAITmhznxgw67R5P8bbzRSvN2PlBSJ6P2f14YwlSGNbtgVctOTOofFwAIXaviDuilz25LF3/dVbpYU/uXw/TWoixMZbe4SrgV6gRQby882D9C89JKwJjn5x5N/Wqitf2ftH75fxF/DNP50pfKvyVyE+p3J/2P3P/jc4AIXStPnQW9UFbNX7tMz25XadK/uvz3VjZhSoHPSvlra2u3CGkATO4fXwVA6PpQhQCMGebl/Edu0yvh9CFNjyz/u1wAplW6w4cvWBeQL27e75crb0+4+vNSZJhMp5Phzc0P28PdTmyfOylk+4f5H/g+IELXJpcEMNj9o5X6lTX6ajMJz+w0uf23Sv33ozC1rSwp3ym4+eLtq6dftvwwBf/xbowrlAc9Co8FQGimPHEYt3Gq5K/u07kLaQjUaO3tv+tgB6aXuCO073j4OfX1QxD+BNvu7GWPbuMYQGg2ikYlltxS/m7FqvttGyCxru3fdXCVjNmU03nnzacvfvhrG1GuckHjGEDo75w/hXH+t2r+8ibdai0N4GvYyPJf7N9VCMIVBN5/JqeBzM5xrHDE4BRA6E/VAjBu41DO/5bE7b7RAYDwT5pW3v5xFbbgSh7DdfBnuHIPhwBCV/ejAwYy63L+Mvca5weAuF1a/nv5/JsbYB5bpcYF/ooAoSuphMDA81Ul/zWe0H/2GADYE5vSv6u5A2YTeFrp4RBAaErnETDga5D8pfrJALgbBV6orLz976q0wZySXM5L4QxA6FK5IBgIFKWrP3lSR+h/PeUDXnsg9+/KpsHE/NFCi8IhgNDvUBwYSQ7k/NcFfP+VHRAk3HL/5SSY3s7+Ic4AhCbqDcFIZ03Nn/R/PwJElGz/8f13O2AOvuNMpxONRBKJ3Uwm+djooEF8dQAhQ+UQGOHIzb/0qK7w50cfEB3SP+/gMSyeb7v+5A55MlF9fcj9s1zg4om0D1Qb3ABnAEJ61n0w4q+sild/J7G+/iAMoq/S4T+DbTCD9EPpLUJxCIyeIZCv7ZXSLEjae3k8QwAhrVYYjIT6bjfJX+7/mx9EJbF/G8eCSTy/SW5QdDNAPUfIdtZMJR4DET7p4jIAIVnRD0aCObL6d0rWf0ZA0iH9e4rHYB47L8iMIjOAjACjo4QGB6UgCLYrroWPgPxRz2tBaNGqYGjjrpvPX+2/EgTJ8aqQVPcpmIr/s8Mh/Jxra/oJMHqccKvyLA0AgVRrsXcCTRYAfOnSXhZfYUKL4y2BofQN6fLv4DmdN5+DzHco9NQMgNk8u+lwkFWAdgIQ+hPFe+XUBxYSZWZx0Z2ARiDzrNHv4kOLaO56m2Bo86acP+n/RRIUDb4mexxMKHPX4dCsAeyGE0A+VIw+rSba1d5iglupw7hQpJrDYw3QPJ0FwNAXB9n8c0j9v/aBosO3NDDpoz87D8kE0AwA/aeKjB4r6Mpxezlq/rFRMZiA3azX8E1GNCdNFgxFnCR/kfP+Lqj8t2i66QeT8n+WB4C+f+0EGDlZ9Huua51zad4o/FaYO2TwsUV03ag6GPu6TvqXvAuAxr5Jl/+yZw5+AMgLABsx/uGiurOF59yZZwiXCj3NnuOzy0hnPtehuJz/TQcvBSOOjsJgai8dZADI/SvxE7c1A2BRE+BHEqazXc3juefoutDbYOwT379QP8/huPNS/3CA+Xb/daLONfEOgOSvti9zCbwjE2CeiXU3YHqbjR7OAHQdeulJ/ZPL/02x//ubMCroA9OLOG+55f7l8tX2lf4XswQ4CsCVsJGsBz8BDc1aawuMxcT+CcejLfgXJZziAJDeV9bxElZi7h8wnHsMV/Y4dmqd/Q/psqDldRECY1GSv8jx1rS7/ZfYXRcHgMejbPnrLWYAlH3wR4Jcd9bLAKras6Al1Z9Udsah6T8F/6yEe5UMAPJxBZMxpP/5DYAm/DG2dDbj7YrzdNaCllKFBWPhOyR/gt/++4fF+CWAOgAYsXRmHDXPAVCFvzKseWc6Awa+Om4pLKMDmOD4Lun/zh3+j3ub8E/jpCWA9Hmlk81vh20P/tZGg2aY2Y2AJkRxJ2D5TOw/eE/sXxgAj4Lwj3trtysDQMl8Uv/GRZnq+i/y11tWZma/t3gO4e8WtFwm9h96QfIn/b/7V7f/frF3rk1Na1EYbgq9UEotlELaCR0uA+VahtsAw8VBFFFUlBEQEBzAg6iMNzzf/Atn1l8+OztJV5I2zaVJm9T9zJzzwUEdk7zvetfa2dkIvy9HAMkAOC169Xuv/y/gDvyzj65ZQPcVDPwbCrEVxr8IQ/3nS1Eif0n/5wFY7Tdl5gl1ADECoM4rCJr+CfzUe7Wp1fU2iAC5R2zr0V+Eof75C1H/lMSJb770VRcDUdEBSBMgqqXJ+n8LbpJb/+OSBTzhAR6y3Yd/Dcb6v0T9X7aG/gFmkxFsAtSPd8P1/wpcpvAlVq8FoDNNhZkF/B0YP4cHRP4SiXtoGY7KDoAGgDTuoX8K7jP/NOWGBXDfAWCVOcBfgbH+96j+43Gi/6NWqf8E/lp2ACUChKwRAP0TNvb76rYAjtvhAWA7LMEcoJV5ZPwoxRNE/lT/B9BKrHRIDqCMAULW8cX7fyZ8f+PYAjAFPQbC6zCzgFbnBw8G5LdE/VMDWIfGMzO/cDc6t3T7eWl1anxu7O5U4MEt9tL0dYBUEx3gBXjI9i+1BTgygK4VIDwm70kyB2hlPuXBiK+J3rik/8/QMPiJtdWjy4vSYvZBJVvHN+OTM+ACb5PoAE15sh+Bp0zspuqxAI6wCyIvy69GMwdoRd4JYMRUr6L/JWgIA3dTR8eDGuHHq/CgeL9XgDrJP6HvBOMgMGRCsPRPGP9A/m240mnfAMJnILIvbY9iDtCS/JkAI66I/im9t+A1/NDcwcVINmus/F6EmsD5NwHq4iotO0BTmoB9HjxnYD+lXhCwbwC/Z6hVfmoTYQ7QivQsgBHCiKL/G/CU/PLS+aBK+9VUn9Ag+8D5MtTD13RTBoE4d/GeZ+8d9AFoAOG3IDL/u69PsQDmAC1F23cwgr9Q9H/Og2cU1m6Psyh+nfITSFRN2QhKs+AcIYoO0OAH+2MeGoNwKO16cugAbZMgshxLpfqYA7Qe3BkYsq7ov5QHbxDGDkpa8aP29bLv0KAygulRcMxSp+gAOAYINYrfA9Awxt5ptj3ZjABvgDKeIvQxB2g1XoIhs3GZogBeMLR9nEX1V2hfK/v+CtAIEufz4BB+pLMpY4DYKTSQmae2QwAaQPcmUL50dZUdgBlAq3ANhkwMxiVGJsB1CmM3i1XFj9ov6z4j0llBhiD5QDQaH+fBGZudDW0CsO9qLFPtxAJshwBpb/Q1UPg39NvJzAFaifcFMCI3HZcYXgCXWVi9yBIqxY/ap9JH3aerItsANYHSCjjjRHaAVF8DmgDsuxrNVYfdEIARYBIop796pBDQzZqAFqGvhraPlBX3O3fT6OzRFpb+CvGj9lH4SZmImiRFcQHiAfE1cMR8tDMdiTS2CdiGxiPs9qhDgB0D2AeJJfopdeYArcMzMGQyLrMH7jEwfj5cpfSj+FH7aYJW9u0aIhKSC1AP6FgCR3zOdGqagFANfPcBEBvwL3tshQB0gLZTkHgeizEHaCG+gCE8aQCoRFfBLfJ751mCvvSj+JXML0tfL/uYBrURSB6QyXzNgQPyvXITUHslIEgvABrwLGIrBGAEOFQMvJM4gPRnsDFA8HnCgyFL8ThV6SW4A795P1ip/gShmvhR+niAZyVlI1A8IPN8BhywnsEmwMvChpe9WSx8lEKADQegBpCaVywkRmAO0Br8FsCQlWycCnU6D24wtL6YJejVj6Wfih8LP0ofj+3V0yUi2QD5YdkCigVHnwfDJsD7x3pnBppHYR+/gmTHAV6BzCG5LfIfwZqAYNM2CcZcSFIdXoH6GVidNlC/Rvx67asP7Ce0VUB+kfoA9QApBqTTu04c4L6fNAENmgN+GICm8hgLuIhFA4gJioPstLfLDsDGAMFmHIzZk9U6CvUy803b+OvVr4n9qH1Z+irdd1eg2AA1ASUGpK8dOMCGeGg4+dsbMAfs2YAmM5q21wZIY8BtkJlrlxyANQEB5xUYI4xIcr2FOpm8H84SDNSPpV8vflr08Yg+YyQbIB4g5gCpEbjOg20u+hs0BwyvQdPZ2LHcBmAEeAMKP+iNYmOAgPOGB2NuJMVeQH1sXhipnwZ/ufRHCCh+Vd3XKJ+rRGMCfTQGUAs4BNvcdWQkB/A6ArwGH7Dy0b4DtJWbwe/0bokpgo0BgssHoZYcJM1uCVAH/FhJL3+9+rH0y+Kvon3ODPpTsgXQRiCSXAfbFPszNSNA0BcAtcx/0gwCLEWAx6DwIlI+VolFgIDSfQXG5IpUtNkhcE5urpglaIs/Jn9Uv178qH3OOtQDJAugDnAGdtnrwDmgdxFgxy+Hqg38jMUsDQLQAT6CwoS4Ztr0r6ky6mEVanArVe05cMzMannVD4u/Vv1Y+qWev6b4Q0ZUt4BIp23v4gdJE+B1BIitgF8QntAajg5gHgHwmj6kN0/6liIzgCCyDzXYkIR76by83I6g/Gnxx+ivUb+u9FfRfsgCKg/oVkJArwA2+ex9BOBmwT8UfuC5SJYcoPsL+ntUvIGxGGsCAsqvmvI4fiCyWABnTBwMovw1xV+Z+WPw15d+e9JHVA7QJjnANQ/2WIh6HgEeg5/I71pyAIwA76HMVJI1AUGmZov8jerf6da6jfvhavLXF3+qflr66xM/ogkBZBZ4AjZZ7Oj3diHgOfiL3PN2K6NAjADfsWF6k1SaABYBgkfNUTQvTQCPwAnCUVYtf8z+Feo3LP11JGwRbAO2wR4HGAHQAFpxAFgm90izns+ZRYAvqioh5Tjyu1kECBx/CuYBoJgH++SmRkzkj30/Vb974kcHKLcBEZsd91VUjgDe7AjoOgXfwR/SKm7VAX5CGf4/ejtJgmBzwMBxZhYACJNgn7Vpvfwx+9Pir8z8afLXqz/kBhoH6BBsrgOQCODZGJAbAx/Cn5g7ABpAXx7KbJNbSpsANgcMGo+gFntUwutgm9OTqvLPZNTFn079vFI/OkBYcoBHYIsjGgEi7Z5EgIfgT17hLM/MAcJhVaYqdFAHEIcILAIEitoNAD8tang6Zzv939LZn7H8sfHXqz/kLhxBcYBxsMNmItrvVQTYBZ/CPzd1AIwALwG5SafJfWVzwMAxZh4Asgtgk6ESln+d/JXsrzT+3d169XvmALHkii0tZD0bA76fAb9S+BkxcQA0gCeA/EPvLbmzLAIEihdgHgC2HZf/8sKfXv5Y/D1Vv94B7B2+dR/FlUBXq1p4GfzLStSyA3TlAHkhTnZYBAgYtRsAGBNlXMw5LP+Y/lH+pPVXsr+x+r1zgHWwwWhCigBY1Vp8ACCxnDZxAIwA3wG56xTNMsIiQKAwmUVPi0LedFz+Mf3r5K8v/iFvKb8P0GPr8zv5uDQGxNl2yBV+8uBrnkUsOkD4Mai4Fu8x/Z0sAgSFFyYlUBTyJdhhQl3+K+WPrb+J+r1xgK6ep2CDEo4BXXymU/7ZAmTAutYBjC/qrsY3WAQIGh9moBZ8iUh5eB5ssDaiL//0tR+d/LvN5e+VA8SGwDpHnvQA4+Cc0eLN3AJ4zklNB8BrugMq+P86O1kECBSjFgLAEliH/6wM/7H8K6/9oPxV2T/UGNABUl3XYJ25hAc9wD44Z4Huqxo5WVrOg5fknkci6jeCjC5pn9YvadJjESAwHEJtSACwNQEUzrXlX0n/zZc/oTwIHAPLTCb0PYALqasAjhGK8tYKwvHnSfAO4T9LDhCeABVXGfodNfY6YEDoEaAmszYngJNbqH99+pcOj/C89TdfCuhLfeRtHBHkeg/AbYJjche4tUpk8H5PAI847RDvGn7nz+CSzmpfnCD3m0WAwPDaQgC4tJGXh3H6h+U/mawq/1DDwTHAGVimiD0APtJNOwWQbq3UHqIYL20PgSdspqkD4Of+q17RbVBz1I8RgNxrZgC+5h1vUtDJ02Z9Asivq+M/dv9i+feD/FVjgF2wzGVCvyUwVB8YP+zzTb2+QkhQensXbxfAA8aTFhzgFai568/Ih6qxMaD/GTOvN9Y3AfE3uviP5Z82/3Ty31T54xigax6ssoQ9gCuPdN8pOGZjEK8wnp+coBSXJsB1HuLmnrCRAejM9EE5ArAewO/8gNrMkOdtUABr5JWtfxj/pfJfmf5DzYMjiE3AQ7DKGhqAK4/0EjgmP60dsBI6CFECtYHS1AC4C3+dNFkM5Lh3oOGe+j5xfbYS6HfCZrFxLpu1vAlAOC7rnz6cyvC/vd1H8scmAJsfMwZ6XR0C7IJzjvAKK99UpHSIUA/o/XoHrnLab+IAHJfSGSa582wMGAiegvkIcHEGLDExnSWU239J/9j9+0X+2ATMglUGe+lCoDtDgNQAOGZMrf/yWSqETCZDXYAGga2pArjIqhTnjZcCOE7rpXxcvPesB/A/ZkuAMESet3GwxNCiOv6j/pXy3/zmH5GagBdglfOKIUBzjgFbGaHXGB1WnLBKdBLQBOI3Q+AeJXobjQeBHCdU6wGSbAzod0yfxYNsditnTf8juvafxn8fln9sAnbAKusuvgnwCRyTK5UjluKw6WQZ6gLoAaVRHlxiPqpaCqhqACugYZZkE9YD+B/TLjhPRoDfwAobWv1j+cePzNuQv8HPuewAfXnLIdi9IUC4jtJ8gPrHBRaEmABNAooHFPfcsoBn6ZpLARw3qXMq2gMoL0+zVwH8ypiFlrPIW8qm2vyvj/92y/+f9yHvwCZgGSzyLZ5IyDWtXgN4C46Zla8x1X95gQWJENQeEI0uzrlkAYfpWoNAjlsDLSckArAewO88ATMus9k5Sxlxq5r+29ttln9kO+QdGAGWwCKjouY63NgO8DvvPIhLIUu5xOXXK2M9hBihnaD3gJHxHLiAEFcGgdXGABx3po9M9BlgPYCv4RbAhPxwdiQP5gxMq/Qfda5/5OFhyDPQAZ6CRTbjbk0BR8Ep/AUOAGV5SZeYkBL/R11A7QGZjGgBg3PgArP01T6jMQA3BVqWyRCI9QA+hz7+ZhuBb8EcoYT6x3Darjtp2hb/Fv6EvAOPtLDIpNoAUiiBRm4CvlUNAMpD9hg9TqGvjfyXSlXxABoDiptQP5fppPGZP9xrfelIRFkP4HO6BDDjPpudB1MKev3L7T8eL0WwK0/hLOQV6AC/wCL/xN2ZAvYMgFM2NQMAqn95wlJGMgG1Bygx4GID6iU/jGMAdHQjA4BpEgFYD/A/e+f61LQShnFSKBVopS0Um07LcBluojBaGOiAMAIHr+DRQY+i6CggHD2jovjt/Atnnn/5JEvSN0mTdrO7gX7I7zMzbei+T573srttzRZaoY8UfqE1b/zi33PFbGhqeNwRESQAKXBSUSQAixClskAFAIp/a3cFI5frNHBqANmAnu7PZUiy1JcNPO1X24SHVxcqFc8CtS2pEk/V+SafNS0oin/iISr5jmjRtDw4KbGHk24DnEGYN64CgDP+Ew4cGmDbAFsCBmuQZJ2VAXyTAK0GD9+6HTlAXARoQ9bRks+Fv9CSicD4p/RfgCEdNTNGIzw2RNMy4KWf2gCmCRYTgEQVotzxFAAcgzkJzYaMgKkB+TzZACYBt6qQQl8ISAKoCEjs98Y5QFuTv82TAUxw7E4NiH9W/pMI3kngD42hXgJoDwsv4yr6gDsSHUBPASCZdMW/rwZkMh4J6F7VIcPojaAkQJtuvE8pzgHaGo7F+IljCGj4VlD8k/0X7lEcD0WsAEPgZUpBH/CDDlEO3AUA2pxDEusWgZwzFSAJGP8OGVbZJ/v0ArVH8PKeDlGJc4D2IzOMlmwWZjlTU3tpysc/8QHAlqZFKQFaJ3iZUyAAs4oSgL7AqTwfDfBKwK8SxCkV+vx7gdpbeDlluUpcBGhTeNzo+5GWKnGqPP6JUUA/1zQtwhuDc+Dl4Jr0dqBtiDJDCQAVAILn8kkCcswGuCVgfBTiPAk48V+bhJd5Y0XERYB25TqHAbhdeMHRmyZrypamkvinKuWslrCIQgG0BHj5KN8HnIcgRXcCEGTDCY8N8EhAzyLEueX/6dpNeKmwJREXAdqTL2jNSmEGzSlNeYZTs/LxT/yGwVEiUgW4RAH4IZcAUAcw+HQOQrOgTMAhAQNPyxJ1QPIfjo/XymhgwVEEiKeB24shnhWw+oZ3dyoNp8rHP5EYBjDBnGwuYXKVAnBw7ZqcAGhVCDIz4koAvAWAIAIkgM0F/LcGUX75JgHX0chX0wLERYC2ZA8czD1Bc266hlOVxj8Vln4Y4W9Pu2kGVyQAc7YApAUF4KlkAuDtAFL8B0MSkHNLQN/AKQQpD7Lf2fMNXqORF84iQLwdoJ3orKA1lbFhzuNpqABIZ0aouDxnj6lMrtOELhK9GgGYolFAIQEYqsglAN4OIKXgYSXAygM+FiFGzS5COnOQP3wPUYmLAG3KLjiY+IrmbHpSU+8FErJoRzDZ6LRgCqBYALijYERyFngHglRcHYC+hlk8TgmgcqBtAjZKEKI47rMn4CUambS/cSwAbUauAg5WJ9GU/bGA4VRVL2qNFQGwnBky6aSNBVchAP1yAvCzBEE+UgLQ0+PbAeSXAMoDTBNwWIEQkzcak4B1NPJpkN2pGo8CtR0PwMObEpry1ZOappNK45+Omdkdum4QiQL8BCfDLgHIhBaARQiyxtkB5JcAMgGFexDi/kCf14fMopGZwcG4CtiOJO6Ch1doSrXpcGqHNHTjXPnf6yZ0sniHMk7AyV1LAAbEBOB1EWLoU+4EwOu9ZSQgne5dggjzN1yVCJMVNKIPDsZVwHbkMXg4nmhhTX1TU5UhqmlHYGxlTAwFUG4BHoKTqpwATEKQvxsTAOeLV0QCnMXAAbEvdsvZi9BMluHDeFwFbEvW+GReRzPucaSm0gKQKMOk9M5SAOUWYBecLEkJwG8IcjzmtVkS/2U/E5BdhAArNwbcXiRRhA9zsQC0I0fg4iaa8os6ALypqcCCfQvGYt7EUgCVFmCV2/WKCIC8ATjgSACEJIAqAa+KCM+c59aPc/jxdbDXLpvEAtA+1MDFMppxTAbAszKVCsAuGPo7dvY1O/9OqQV4BE6eyAjAEQR54q6zehMAUQmwTcBFGvBSR2i+s29D84Bn8OPFtbgK2H6kdHAxjGZ8rltTWpk0Ha5MAbZxwVbKVgC1ScASOJmV6QI8ghjDC2SzghIAeROQfjyM0PzlbgVuwI9a0y3UPztiroIv4KKIZpTH3AbAuzIVFwFQTqZM8qqzjAp3MU5iDuC1zE3gHAmAsAmoK0ARYblpClLdkCTW4cdk41nqzoXYESOEfA9QnjtkTb31oA6VAlA/aGbPWKmmAKjtM+TAywsJAZiFGDc9jdaG+VtJE0BpwANZC7ACP5Zs6fI7EiC33BFzBTyGCt77GwBNuQDYfmW5y4BZgCGF1aR34OUze9oekb0A50UIUZxrrAAqckCaCaUByeQ6wrJibUtiFiDgktWZZluoTxDnAFfBGhRQccT/gGd3iloB+AMWTy0FUJppPOdXvH7h7cCLEGO64P0ve/fgKksDkskawnLf8cufwZdSMwHYxY+OmEtnGyqYpaVJqWBwTGTEK1YpWCx1WQqg0gJsgJcFYQH4V4cQw+OeBECtzfIUAtKTobNAx6n/e/Cn0CRtmsZuR8ylU4MK5hoNQLP38onEKj2GxSG7CFttHfAL/1aAftETgf6EGC9cCYB6m0WFAKYA2U8Ihz5G1Z+38GesyWUKy/izI+aySelQwLLXALSwphsSi/QOLCaTtgKoswCz4GRUQADktgEuj3lHAIKfXL4QYChAzz7CsUmFiTL8GQkeBPgJTNa/RTwhdFl8gQoWCwXLm9rHPjeNiKE9iTW6B4vif0wBUioHDvfDzAExAQh9yO06xPjK4j/aRqtbAU7KCMXwoC1MRwhgJHgQ4BC4d/ENYgm4PLQZqOCNuwXQygA8fCixRKlOV0saKLUAP8HLaf81n542B5nbEGLJpwJIjdYoFCDV9aOEULyylekBAhgPFoB1QDc+PVaAS+UPKGHckQGkW1vi+ddKru/Uuy0FUGYBHoKXj/S84cZa9yDGnB3/HHVWZaXAjSLCsGy7+2kOAfCq5jyAD7ECXDLTUMGMdwaguQE4KSVkFug92Gyl08mkylx4C7zcEhQAbRlCfCMDwD8CIK8AeyGdoPXdjhHAQmDepN0GcMY+nRErwGWQ16GCiXAG4O2k1Pok0Sr3pdNKLcAaONFZzUNgY8shhCjd96sAqo9/agZYClBDGP65+IccIYgFv8optaM32EdbxAoQPS+hhFMSAI4S4DZ2JTcEkhFPp9MKLUBOD9MEELoefFL4GJB+gUlLcQmwFSC7HO54UPbtdhDE/UABYL/qjpZwEAtA5HyCEg7Y+5D37tc72KaVJrA6z1BnP5tOu04e6ZDiN3iZ7hfbCnAOISojgpOW8gpwWEQINlkbaCmcAJAyTrPjyhmxAATSblOAwHiYDOA3Ku7Lq8MuzkwRdZ5mjWhQ5od3wctnwTGALQhAe605Ji0VK0Aq9WcopTJ3R1xDIFNB45OJEgy+s+oDI1aAS2AVSrjbmAEEB2OiilkKf4Z4FRBrhgCkk62yDvXb9G+JCUCuDBFGC8J7reUVIFkNZQYNh7LT4jYVv6sBzmBSMeLfJhcLQNQkylDCqLE6uTOAHWDDjn+b0MeCEQWmAIosQAWclAtic0AbklstewUqgNIK8FwHP/PG/2SpiXAGJU6WaGSMyK9f+hCfGhwxD6GGf1g48L0PX+vAB4p/IQXYArHal80KWADJBH2tX2wOaEmwySK011peAdhEUD61Dn6KIz2FooAArIFxbh5JYDIUW4DoeQs1THjDITgUtTWg2qHJCcBTEHf7+rKqNsY+Ay+L/UJdwCOIULzV0AKkGcDoqM8E5rs+gZ/T7lcILwDXi2CcmPHPDnweii1A1PwsQg3f+MNhD8CuxqBWT9gbLX7DwUZfnyoLwF/ueuPuAvIKQA0iPAk2AB3RQQqQyZ/o4Gamt4pg5nxPUqXxiB9DZvjnmQLEAhAxu1BEzVyevb31EkBnYDg8LwJ6isLfJLwApFzhYQiAqy8mvuCX+dvd7ifmDMd8ScwAyLYA5RXgC/g5RWgBoHL0MyP+8yYZQ+Piq4OiZRSKOOUtiX8oA5im+BcUAK0CQu+1LYBsY+woRNVTqAmwK2oA+rkPW4lCAcxC4Bq4KbYWgB6PANBafGCOHtjnPccXB0TKa6jiFWcJILcEgzMr/nMmIgNfmuZajp8HmALI3zWzHmL3M9cTe6lKGwD7QQXcsVwrYPs2lDDlFgB7fvIDLHbM+LcUIM4BfGmzkwDC7IzbhEHVOoU+x3q9OSEBWISD0YEBFhddsmXA/RDnAQY9sfqxq2/uGSCBcqe0ApiFwF0oYcxXAMgb/Zkxgv/ipLc4B4iYKlTxla8GuAeTXfs2OoaYAOx676YWKI1JxOftglATYAsCFKcEWoDqm4GZ1D4UUArYQfEdFtP5VJcJO+ZlKBYAD+2ZAeAVV0n8JUz0FEsr6+REBOAQTmo37LvIpW6b2gEv8ywkafDpuvUmi+LslW80AyTTApQvAzyFAmb8BeAcNm9TlgCYv2ZcBIiSHSjjlGdjzDMwZtl6co17JUILwDs4qRjFcQUW4B54eRFi8Ik4gwD6lFwLUJ56GWAU8oz6C8AebCaM+K+f8xRfHxol96CMaeqJBYbDjyIYv1n8D9G4l4gAJHQ4mbMEgD47Wkc01S9SA9yEAHcaDcBlp8Z2GeAZ5Fnp9+0CfKI/SHUlGUYOEAuAD223EdBksnVP7HkJjEfWPbTmuJfwvKemudVr9oatAFQei9ARzfCWAOQ3Xuj3hQ2A+jLAEqT55nulssPQfe+yBaArFoBIWYc6lloKwGMdjNK5dQ81gymAkABMqM8BRkO8ld2HAfAJwA8hb3X1BoDKAIeQpuZ7pfIX1PlkxP/FIS/sUeMLxCNjH+rYbyUAD2Cxww6bY9NeEuOemlaDizmmAFLTgO/AzYFQCWBajQEQr4zJlwHmIcuqX7XIqb3VLiP+6ZinWAActG0GAH2kaTxQ/2smY8S/Gf4MCQHw+JfZHukc4At4KRdESgCdJSEDQLuAxIeA5bGSgOeQ5ZefALwGsZ+MBSCQds0AgF/NBCAxC5tnOWb/89asl7gAvISLSrepAFJlwJv8eSwJQAjNESmhlRbawgBQEjAJSQ5IAOhK5R0QM0b8Z7PZi3PeYgGIjmOoZL6JALxzlHhZ/OfzKWkBOIRvDuBIyMPyshgiA6CuB/cnvpW6cbU7+MbF6KFOwAkkmaP5CVopVRCVZDprEgtAtBxBKfpYoAA8oyHy4hGLf2vWU2bYU9uGNweQLpKflXkzAJESQL4oawCYvMkZAPkk4A58kbpSeRsOykb899EZL/EoYERsQS0fAwKiswZi04r/LoaUAPyE8hxAOx/lywCESgAbCM+dtjEA9SRguwgpCj7l4nU4GE5n+wzMHCDuA0bIMngprQ2jNdV+XwF4XgVR/pel/10WKYEMgJaj3pAD9Ax4HXno04a5bPqB0BTAtNw2QDIAUvEvnwRMQ4ay3y7qfTgoZ/sY2VgAouQEXBSrswdj97lE/5unLc7mOx7ByeP66z/pFACxuwGO4aZmWgCxVUMrPLceMgNId3XxNR61YYRmpY0MQN0CPIcM+yQA9V/qCE7uGgIwMBALgDzyGcDo6v2CwSq4+Ox9JWa2dDhZteLfmvPqktnvqWkrcHOvm1kAoRzg5WE9y90ohcsA6OPU7wN4304G4H/2zrynbSQM47gHR2gJpaE4URJxKEBCCeISIC5RlnL0oqjQQjlEl7KloN79r19h9Xzltcd23tix4xkf4Kz8k3a1f3RZmZ3nmfeYeacSAizCBzM2o1TNa3FW0b+CfrI7NoCQKMKN/NzjbpVksgQu0qempPjdQhYm5ru08P8ug5UAtADAmwEMw0JvzVByXqQDLCt/Wlvf3yeEMgAqVAXede1zDACarh+qA36GDwq1YxRuzZqNvP1hT09PbAAuhJ0ByF+mdPknV2TuFVs2tuCW50uwkDuk7V/BXwBAk8GJc885wDcAgy3GabfDGZd7AHxvIPifvvYzWgGAEQL8kuGd8VoD+G1ZRLEBXANuUX3mWJO/yg74+TC+8PL58pcDGVbSa12a/O8paAbga+ST9BIWCs3N4jkAdehnz4z5l3c36z/SmfTQBDyBMBNVhwAjEQAYIcAgvPOp1gCmLUvoYU9bm+oAsQGEh+QW5l5U9N/aWkIAyK/Y9s/kzxzA78AX6Tks5AU3ZetrIOldbe5F1527q3BEXvHUBNz1oJWoBQBGCLAF7+zX9E9+pGHiqqdNITYAn/irSM32dysw+bcOIQh2K9t/p/IXjXvwbgAbsFL2lgNQQXTuvv4k/t1zGQ7MUwYQ7jHA0QHjGmBkAgA9BOjIwjNTNfNAXsDMvGEAnbEBhMa2m0+T/lNzCIA3+vbfqaIbgL+Rj9JrWFkVzgGsz3WW3j3qYJnKvd007Ln0lAE8yPg4BcysJgoBgBECLMArWeub6rfvW92k0BYbQNhIObeJdwqa/FOpLPyzTPLXDcBjBYCQzmClz1sO8AREduN+l+ZVW3nYkev2lAGseRwFaowCjkgAoIcAT310Aa2jo9ZhYbCtWTHyttgAQuS12xQ60n/iEv55YcifnfDq9BEAENIhrMj9zfz384gPpp+xrBtA58AEbJijU0AiVrMNUQYjcg3QNgSYgUeGrQcBby/CwpfYAEJn1a0CSPpvXoJf0s9J/graRU+PZwAIqQU1/Kw9nCPeES206+HKv5Oo5bEhS7EVegBR3mr/DyIWAOghwDo8smc1gI1ai22ODcAv/mbTFfuVtWfUn8rwS3aDyb9dgQIArxVAQrqFGp7Rxsw/F2gEFmZf39Ucq3keVmZIliLlxj8Q5UM0AwA9BGjJwBtvrenTEqwsNKvEBhAiG6jLpa5/ln6W4JPZ7/rm/1CFBQC+EgBaiKOwkqUzc9xFgJY0rKT39ZClZ7z2bE7SSwbwCqKc1gQAUXklh4UAw/CE3G8xgO82YVxzIhEbQKjMu91AoZeo9uGTQkJXP4MCAJ4EQPyVjRXe2hzxBjaMKDbFfOuTtQSY5LkH4L8EMHvzo4DrhgB/wxOz1gfVNlHDUYIZQE9sAGHhUsMt01O0zSsT8EXmc7um/h4VZgA+EwBCmkQN5/bdOfEHwUv/6FWLUxlVLFRSI7EDByUIstNtrTVE55Us1QFaZHihYMmf3tn8mMexAYTMnNtT1MYBlNSqDF+UBnTtK41d5e8m/fteztISahh3iM3F06HsK61p2f48gwqjvVptlGyG77/SIUOMfC8FAA+jFQDoIcASvLCaNL8KsI1aBhIKsQGEx4+0WwtQX+XHH+ELebu5rQolAKACACUAnpHeo4YDmp7P2QgswAH5L80A2quioJFWlVRCNNH45nEWMDlNFA4BVVANYBleODW/qHiSQS2phAILfKLle/8bNlGPET0AWFktwh9fHzcbtCmwC16d1AH0vZylTbsik2B2fghnCm2aBaSujJ8+xfSvy1LAZV6AG0rDdKeJXACghQCv4YUhcxfwBWrJJ1KGw8YzAYnragHk2RTKqb0SfHKg1XLIAQLSPyGNo5YjXZy8BjCNOnxc02KAHv1PDaYYCeHTBvMQoxTFU8BmA3iUgTij3aYa4J0caplNmQ0gngocLPdzLmeA+vdHJuGX7J66UTKaVUj/fo8AE9IwaplOJEQagS5X2zPrmgO076ehUE6wxSlcmb8lKpY9MoBIXopTHaAAcUrmr3oJGz7EBuAHaa3Jx5aH9MVVGr7JXvSzUFnzAAf9+zeAEdQyI3ZIfxouzD3UHGClCMw0J1TomBrvvvwbYmR6rT3ASAUAmgG8gThfTE2AwzxsKFQZQPwugCgd73fdbgGETulcDfQ0B6jW/71A9E9IdurNCF3T45htM9PKHOBhah5HbUY+I/gOqahWnkX0FLCpDPgPxNkxNQHs3XdcXTixAXjj3SJa6ncAigiX9LO3SQUyANK//twr6d8n0ipsKFPe7K6aabiT29KOMffstSlon9OuLk7+xHwTYhxH9hBQ9fzkPIQ5rm4C/CvDjk+V4mdsAKKc5TFZ/3nKGYSJ3PdpQJsiRvpX9EL9/5b7vvVPSNuwYUdg8ZzI4EDeYwbAJtW1KYjPq8tBiI+1h4CiJgM1B9iEMAPVTYB52LIfG4BX3mWBEfZPkopt4yw8MmN7U90kf60IaE3//eufkBZgw2CKv4W0yhuSt7XrFqBijKznfqTrh5dRYEwF0TsEVJUDrEOUieomwBPYM0QGoHU/Ivbl0eXXBIB1pn6DkF8DI3Lj++wKIcnftP3TCDDSv28k28+Z4C8CnMjgZHGtXYXuM7HTTLyJ+RqEkKciNwqwFnrSW4D5qspG5yxsSbM/YowMilj3I9J0HUDht2SiqZqXCIPc/PTlSjep35T9s3BZC/8D0j8hLcOOXiYcntWzCm4yu+0G7N1a/gBA/Pc+FvEeYCUHmIAgC1VNgGXYc6AdtWyLDcDTAT+5QzJjSpkDp+/iUgn77dSfqD78Uwn/b98i/ftGegE7jnnf7DtJQ4DVhxX5M/0LPNI1ByEuI94DrOQAwxCkTAMBExnYU1BXUHwVQJxdqGQkhVsaZgd49B7Bc9TNIPWbWn89dPuf0n9B/YsfsN1JcV4HWIUQV6l2GmgodDuvBBFyjRAAsBDgBcTIVY1THocD07EBeOKd5qiyIn+iSm2HHxA8o/0V/VvVz67+kFpU+XvSv/hjW+NuR4EoABAjt9FO+hcYaHIrDRGmG6AEqBnAK4gxSNMUN+DEeWwAXpAMfT9QdP9Ao8oBbi+nEQJjhv6t6mebP8lf2/79h/9cNc0S5/LZhijyOem/cp6Jw5sFhwGb7wHe6YpmJ0ySpKcQY48imxKcKKtLKb4LJMoT6Kwpb9voMMUxNmYRCjukf6v6WezPtBLI9i8g4Xwr1ZDraOdPGuJs9lBIQwFAoOPAZqozgOgGAKoDdECMlcqHLdSfGRYfBBTlUQ46c4r+HzFu66L783kWIaFPD1dg8jcif9r8q+TvS/9CSfwUVxVwG16Y/C7+qNEyRDhviBKgFgIUBc83GTXALRlOTLA/E58D8pwNZ7Zud3R03L/f0cEs4OzlexlhcWDon3r+LPKv2vx1+dP2HyQjsOWIJ4I8ScMT+SdkAJwpzaDQz+/XZRL5qZiSJBUgwrhRA2ydgCNX1QYQ1fQncnRlUEF+cXanq+vH2cbu8shYHmEyQtODmfxJ/cbmz+Qf8PZPjMOWBZ4iwDC8skApAN/anBBSifo7jewkEIsBbEOEU6MG+AzODLMFFZ8DEuMlzGSzCBVqApL+mfxJ/S7yD3O00RcOA/gH3plvM11qcHVniFA2lQAjvQdK0joEkAf0L/uJOpwn4yaAOAe4CTL9WgJg6L+z06x+LfinAwmieI2t++jCqaN8vsIHxW8UArh/2BkEOGiMQwC6AaxBgD79y6YyqMNQbADirOG6oXeyDf2rZX8j8mfq1zZ/kn8IjDkYk3sRaQu+SL9RHUD1N47YZkvwEEBjlACZAfwS+zSW26T6UIdcMhl3AYXZxI2wU3m+nu77095Pm39o/wOXYM+Q2wp6MAufDN9jMYDxiYFNAynXlgCjmgEoBiA06uxIs7Y5l3cD4i6gMH9k3ATyStXoeu2CnFn9JP+Q+AB7Lt1iyDfwTd+ZFgO4O8C2h/eAol8C1BygJJIwsi87ddlU4i6gOJ9xI1xRAMD0z3Z/q/pDXbuT8NYGaMnDP/ktzuuNg+BnrqoEGP02mCSNg5sl9mnHMu91oXgaAC/SBMJA/MQKq4yHr37CScfDLn2kVQTCX3w3nBbBz9vGKQEqCF0H+qR+WnnUdWx4srKkov75UeFv3AiZXlO6ajz38yBs9RMP4MBY/SDynYxgGGtvcb/jLKXBzURj3AMiA9gVqm60ruRcw4S4CSDMNoIlf/Vs+GJn52KuhHo8swYAbNxn+PInTuBAX/0y0iCCovjaPQ04AT/DlvfAop0BKAbwBLxklU/rPYALF3ETQJxFBEducKdcueE7jHocGYs1QTPyH4TZ9OM/zJOtu4bWEBzpfXIAh+/+Dn6OGyoDUAyAP/osdCd7P8CN47gJIMwfBMbiOd3vV7jiG1sxRdEq1+H48HMfOVknipT6ECQjd13uOj8RHAXCzlVE/xAAQ3ot8NbRVJ+7n/YbNcC4CcDNEwTEzKVluNdE3YVv6P+oTS/Y0nu/18RzODFUxwB2ESylp6Z+oJ8ezThlAJE/BMCQzsDL0OMiXCnpBhgPBBRgDoEwemkd7tWNejzWF+vbS4pWw92tBKR17NwG6MghYLL1+4HT4OaoQSYBVJAOwUnxbRbuzMU1QHGKCIKinvrTbK9UGXWY1KPVx1c3d2Jlu85YqZTTYPkFBI5ctx/4XqBORhtgY0TAEnf++TENDi4bzACjwCGCYGaqaraf/q7vKeqwp21WQ7nyzT1fvQknLhz7gE9lhEDh0Lkf+BW8fOlONtgGKHUgSEb743OAwuwiAMZZ8a9K/s0KF3CmqJUAj0YHqV597Qaw5Pw9TpVk6StCYda5HzgLXvYbLQNokiQZATJo/AIa4RhkVJiDfwaN8J+G+ygMur5fvyenB0wZwPWu1o9wYsmpD7iLkEivO/UDM/yzgBotA1AMII8AOY1LAOJ8hW8+Dljkz+Z6No+6BADJEWCVLdZrNQAeaS06LKOWPEJj9Y5tP/A2eHnWcBmA4gBFBEemv9EioCiQh1/S5Sr902i/U5cAoH8MyLWyxcp2q2s3gA44knfQ0QhCZKm9pau2EHACXi4bLgNQDGASwVHojksAN3EMaJj0n6ia7VWCI0WlZPD2I4BLY7GKnQIIv/zZ32r3utx3iOL/YPBTcJJpvAxAMYAZBMdPZgCJuAQgwmt4II0qRqeq9W8853fvW90AoH9aBvBFdYvOmzKA33BmqHoqmGEAtyYRLund2n7gGjgpNGAG0CQVEBjp3rgEIM46hElfDFyAuDDrX3/M++4IHJntLy9CoZisugh0/QawBWfKdpHkZ4TO6p0WSyHgFTg5bcAMoEnaRGCMNeIv4MbZBqOYFjvycy7TY/RJ0j/b/tlrnp0ZOLJ3IbN/s2y5CcjdBgzf+45t+oAnGYTPfDuLhuha1Dr3/mfcA2igDKBJeo/AOCcDaIiLENGgAJWlf8BLdqhb5WjUWLFM/jWj/f6CI+lJMD5pGrsxA3gBZy5rDcB5rYZZCJCWwcdVI2YATdIYAoAcsGGGIUWGRSbilifgRD7We/7lHBg/WxmkfzbZ828Zbsy0Jm7WAKbhzHntQYANXA+Z56ZCwDb4+NSQAXCARcD5hnTAGyfP9P9oFZyMV678rDDvyHQz+Zv13/VrAm7k1Rf4yABoGEDTdTEIZ3b0lURSul/EdbFdXQiYEx0G1tNIAXCAd6vPG9IBb5pbACZabj8qgQ/5P/bO/aeJLIrjnQItpVAoUJg2bUNLijyspCARAmIsysuuoMEXqxJRcFGjrq6/8S9szr+8cx+dMzNM25m5g527mU+yD9esu2C/33se95x7E+/8lb8AwOk4YcCs/6EZ6Mi7/m4bwJ125+mVo2QLXJADIRaxEBCtgSOKV88/GdZhKqvgE9lpvg1Npi5o97kEgPfpnh8qOKNivPPb/xVgYYBApMy1TKz3CDrylX1a0QB+fwqwBK1ZsxrAQ3DD289zIMLZv3oh4M//dQlcyYcZQFfZBliIpnt2wSHvzO/5X8DnGCMxadC/Cp1YLeODAH3dqQGMQhtq/f2m1bKjcy4fPS1f5PwoBPSkK+CIp1JmAJ03nv6PJ6ECwRNYimsGcA8ccoPqHyf+zqfZ4A/TP5Xy6Ap0JHdruOsG8De04ZHlhdkVcMHqVEbjfgVE2GCFgJ5N5ztz8fzrDfwuIE4UfOKM70KQzQG7zT4cKJoBzIIzikb9c+VTxpJN/Z8sQkfUz8NoAPRfGxK6COT/FajFftMDod/BDc1Gydu7IEAlSYOAU3BCQa5toDop8ImnctZAuk69kCYGsA7OuJPB93zpyB8jqcmf6T/emAOk7ftNhpYtDdh6fnMNcKvtF2q6UfJjDlwwi5sRnxaFCwHL4IQZSS/BXII/FKfCDMAT9XsKMYAzaEW+tmDdOcOb/uTYJ4xQBqn+d+cB6bQP1PgkwJDQRkD/56BLJjWtgQvmpgy7kabXwTvVuvZY2io44bmkl2Begz8syLYPOSg0tqkB5MCe01flzLRqSI2H8T1fNvPHIbf/E59OnXYSmr9KF5sAShXasGo0gF1wgfrWsBtJ47wK3nnQGy+AE25IWgLf9uudWdn2IQeFepQYwA+w5Ywt+t0EnRk9difSJcc+J/F45ZsKjiiVmT6wBNCVDOAfaEfBEE4OLnkcjh4fIAgFAZURR/6xJGsA/MuvTQCyfgO6zbZCDOAD2KBuZCjD56BT6dfQL/AQ7XL6KuCQ5dsZQwbQvVsAu9COnOHTtOA2AUD9xyjnOfDMsepsGZCkD2I8BF/AF5HCDMAdUWIA0fd2h8oL/dIv1qHW6QebH92DRP5xTu+MQ/1PM4GgjWAAEJBHAQgqGsA+uEB9Q75nuByFMbUJ18u5rJPwu+AHJUMAINk3oOtQA2jAFb7cHtZrWbdU4GwS/aNyyYv+nKHeikP9ByMAiOxBO7K6AYwtgQtqFv2zPsnk5HkOrpPbcjYBI5HH/vgf+aazj5QMjyIHCmoAO2BlZZjTT1gBztw4H/vRb/1rpBjxigOr5vo33QLCACDyG7nbyQC4Q82CC+Z4fIPLkThjU6dwfRxL2gT06WXqAl2GJmMTJAAoGtHHV6/Co/7HNWrAyQwMmEZ4UqkJSo/G6KIr/RtbAL9/I/CECu3INZtqdXADTwCwT0IbpNqfksnEClwbNWmX4c2AD1xkJL0GFQiIAXy6OqnTr8ufSn4WGG+1v+fF+152fw9JDy060T8KROAOgDAfoC1VbgAx1wkA5jfYJ9HapJoF8B0qSLgP35en6bO3wxKgoAH8ASYq4/0axld+EomfVSA8jeHcHx9ZTUc56fgRtKNSRv13qQWINJwZQA1csDRtym+wT0I9YCQ5vQzXw7SsPTBfZoG+GgKA8BKAa66unfpCJn0Z+jMfk2PlRyoAPEpoGHP3dLTJy8MitGMlY9F/V9aBOnzjs8gM4C244RUPADC/4X0S6gFaJpDYg+ugJO0+/BMQJ3dfz7omwxKgNwd4AAaO+weIAfDjnz/zQcisVOFsUoO3AHjxnp7eqd0KtCV7btV/NzoAyGInAyDzwP0FcMGCqQBA9c/bJMQDqAUkf2bBfxZ4CUC+EtgTEGcFAwA+CRlmAG4NYMO08yc2MEAMgDX8mPxHKMnEu70Ymf3Bs5vIP7p9VMlBe/J4QRb1L1AAECYPbclTRX0FF8yXMQEwtUlS2h+aB7AgYHcJfOeVtCWABghTmGI9wLAE6Bnz4sm1BDEAAn/mh9347xvU0DyAMoLJ+z/12QJ05OxGC/0LFACEuITOBjD+BlxQJF+i+YoztklSxAJYEDCwDj6jTslaAojcA2GeZ8ISoCCmCGAplojFBkz6xxv/xAIIg/R0G9u/V8mDEyrT9voXKABe8xXUgvb/Wi6AC94ZEwCDvzEmUnoQkNxQwxIApwaiLGfCHqAopiJgfYwYAIHqn5/2HOIBlL7kHzOO5VE9z9jqX6ABIMyRAwN45HIGyJoA6Gv9ouk0t4DeXmKijSz4SU3SUWCNeRDljU0AEBqASxS8B7CXHJtMxFD//LBnEAvg9CYfn4IzTllsbK9/gQaAELXOBvAKXFAq44hD85oE1z9DswAMAnbz4CPPpS0BRKogyKI5AAh7gJ5QHgMnHxshBhAj8jdWsocIo7oH9NLy1oeNAnRE/StjejzcHP8LFACvdxnt0vBU3lsBYMAy4kQNTvuLMQgYmZoD/3ghbQkgDoKoN40BQNgD9IjyDDhHg9QACAmufyJ1UspKpbSPLznCCHG2s3Zidy8LbTm+RZRh0n+y+/rvvIdibviRpwLAuN1zh2gBqWYtcLwEfqGWpS0BfPThZfphawAQlgBdo9SBURzRalSTiQTXv17JnmCFLGYBBO0fcv2elKA16lp5ODOM4T9zle7rv/MQ2upnjzOAlgfPo4pOMwhgaUBiHXziLrsFgCUAeULgOohRnA4DAF8fyj7sowZAmTRVstMazAKG2PQvW1of/TgPrVm8MczAS0X68vBu6B/5EzpQyINzlqcsOw54kxSfOtKDAEwDknvgD4/kLQEcghgbXP9hACDId6CoH4gBjE2a9Y+VLGYBKToBTPU/9ECFlpTe9uNEMW8qBkT/0Sr4SO6mXQfQ0uBQCOY04AJ8YUPaEoBoE2C1HAYAvuZiM73MAAimmzoaStMCevT53/TDY2hJgbwabDr+dVPpuv4jv8BPfpoSAPsRZwwCDGnAuQo+8EbaEkAKxHjX1H8YAPgzk7FLDYA4wJilU89gFsBJx1egJbkLeu5TWRiPf4P+o138jfoEPvI1c7UDiAlABFEIpjTgWda3UUAJdwGILgT8kgkDAJ9IA+Euv6nGbvubR3WNFkBJ7RSgFcW16fF+nXFL+E9sGsPjrrAO/nF3qt2SwwhikwYM7hdBlDntvy5pCeAPEKF4H2cv8Q5AGAB4b4sfsR41v+1viqiQKGXi2VxrRZwPj49b5Z8gnoJNhXRX9T+Rva4CwGSHEWdDGsAc4GEBBFmUtwRQEU4AwgDAH5QSAOT6RqkBjBD92yWy2M5qrEIrKg0ySmSVPx8p6A2E/jH09IHzlgUA/CS2TgP6+p7kQYwLNADJSgDRnHjmpXdewwBABGURAFboJ5LP+9gnsgqhp37cJvZnk4RM/XydWAKfDqXpv3f9B2cGzfIxtD5zYtW/XRrAHWC6CEJ8lrYG+AEEWJ0ytgDZHoBwCsArygIAHJColE78kcPavqSqKC838mBPrvJzvDlJ3M/miW2P/54o6r87nIJfzJfNBQAnB5G1EPC+CiLcNy0DkakEsAPeUV9YKoC8/BEGAJ6gK4FOuAEQeLJuTWR76ptgT2G2EWNjhOOcAZQ/CShM4X939T8EfjF3GwsAbTqALQsBzAF2c+CdvLw1wFnwzl+ZKxXAcBGQd5QjgGXtRIqziV8y7nO1kp3aXyiCLfMP3mu9gwQ1AJR/jMs/aZR/APQf2QWfqN7y9MoJpgG8FHiQFemFSVsDnBPuANpUACX52gOGcgBwSAyAOgDTvzmjer1TUcGGbKn2OMauD/E9AkT8RvnzfCIo4b/GIfiD+sq2AOCkEoUOQE23roJX1qStAZ4IhD33zRXA8A6QIMoTgAMWkfJxX1zWNfGxcbhZtIt/Zx7UP/by1iENAKgDoPqZ/AN2/Gssgz88zbgtANg7gJZ4NVTwyE9pa4AHYEJk+1KfZLFP4FBeAlwSA+DTvvy2Tnz7/dHMUg6M5JaWv83UtnYe/qCNLKp/PkAQ0yDi54e/Ln88/gOh/0sQBncAuSwAIJZ24I4K3rgpbQ3wELxSMycAYQtQGGUIlqP8s8j1b4hko6Mn27++7+8++fA6nlZwKojrn18fThAHYOLnsX9T/oE6/nEGVZDNstgzp5Z24BF4oipvDXAZPHJGOoCWBCBsAQqhKIWtKPsoEqhkrd9Q0zgAMQtd/3x+IEGhob+d/INx/GPxWZC709YCgKVw6tYBPoEX5qWtAb4Ej2Rv2SUAYQtQCGXzoBmOEgz9Onv9p0z6JwYwNslA9evyT/UE5/jX7gHnwAfyN4z6T3h45hjbgdwB7oEHatLWAD+BRzaa+g8TAP9Q1i6JsHHdh/UbinutmuE/0z83AI0xCrlGzA9/Lv9gHf+4/ESI7AvTM8deXzlTNNABNsA9z6WtAc6DNyr225fCFqAQyr7CJ9UpKQwA2ulfNwBmAVz8ePgHUP6RyB74wE8sANoXADw5wCy45hY3AFwHJsk5+Bq8UWqxfemyLskXHkyUtNIs7aH+DZq1yf/RAPj4QFP8+uFP8ggu/+DoP5UFcS5s9O8pD8VmAHGAwTvuF4LKWgM8Ak8U7ttvX3p5/CQwHzEZYfKm4tYg+35sKgBc/zwAwGcCBikofqL+gMofu89iW/gwDbUtAHp1gNiSWz1Iuw/wDngh1+Lu5a98IcwARFA0cOGX9hdLAGBvAMwBkF5UPzORwMkf14EKTQBZ9D9iGp1SIt4d4HsWXFGyLgOQpRR24sPdS3Te3SpsBexzJhuobwpXbqSVAaADmIhz9fPDP3jyjwxlfZkAwk8hhqFYABBwgB1wRUXWGuBj/+5eDqUaKsAvRVGC9mGTCYPA8eRubQDUAQgofSZ+pn7+SwTwd6QBohRv2jUAsADoOfxilcAtcMNCswY4xvcBylID3PTv7uXoPQCYVzRCD/AMJvmEKOrfagDcAXi/MN6E/ICJn6s/mPIX3EKFUajNM8CofyEH6Ft0dSQa3gSRqQRwKVp6Qf3/mgeNHUUJLcAXB+DQH9r8LHMAagFmWO0w2OrXHqNTRfV/bj6FxJ85tnYDk2fgnM+SNgF2wD2Vsk3ptY+9TFEdVUIHEEOhmPWPKEYHYKXClAHth83cgRDc34RnIMjzjvoXdoB/q+CYW5JeBF4E16yXTQVAFnp9vwuUrSgjwGdP4FEY+B208we0ACskcwi4+jXWxSeA7RsAeBFN3AEOVHDKlJwXgT0EYnemrpZeRrZUYHxA/Qfq1plUKGZa+AMlTeDCJ0Q5QQ/AfoAYG2b92zUAxB0g3nsPHFKVtAngPhCb1/WPpZfGHHC+kc8kJ3QAfxyg5U9G7ZEi/doBIf7KGKPQlg0AcQfYA2ccS9oEWAaXlKavpF71Eug00mlDKBqErVNyoiDtfzZqQKbiyzcQ4cKof9sGgLgDsB1BgyVwxKac20Aeutb/bav+D04BKY4S5fMnK7u/dV5m2qpYsSBd4fUERFi5Hv0jhk2h23lwQk3OJsCM2/zfcv6P7X8BI4ekNU3hw2eSXIeUD3m1TzkCAdZQ/+INAHsMVwLfq45yEikN4CW4Y53m/1h62V8HM9vNu2lDdAIlXA1yzUgnfI6yCt6ZdaV/cQc4BAf8lHIS4D/2zrWnaTCK45m71E0YsDFsF7ZsGCYMgTA004gzOMUL3jDiFTUgiIpRRHnnVzDnK9un7Xba0u7CTm2bnF+ihldy+/+fc3ue04S+WJ8y6z+1XQUb9/WxNKl1C4UNgHHkI5yeJQf9WxuAJOBIYHwZuvM+jF1ASenXeVH/pS8FOMHxsGkklQ2A8WAM+LtN/0JxLg1AqlbAhgxdWQ1jF/Brn5VX1P/CupN3VDJ4JUU4AL8OxjiyNfADACNm/dsagPQOcBu6obReAwhTF/BMof/JK/Gdv/CsCo5stB+mYANgOrACp6UyZdU/4QCAezMwE6tCF2aNMYBQdQFvQe/MHxj6L31qyODMoq7+9kIrfh+YcWSiCKdkumf90xYC/yrQmbo+BhCuJkAVemZ2QdP/1Pa0Aq58bL9JxQbAeHEPaNFN/2kcOfHGAR51y0xC2AX8CT1Tv5xTebfe0bkbQv3Gg5Rx3hDCuFKG09Fw0b9TA5C4FSDVoSNXQmgA96FXltRv/MuZAnTm45DxJrXYQc9Lwhk3juF0fCv1rH/6QuCuAp14Eb4xgC3oEflZ6WBptnt7RlO/vo9iKM4rQhg3ZkKj/5YDiELgc+jE6/CNAdzvVf/TDQW6o+TEQhp9HRW+zR78bwPzvzmS6ep/7vqnLwNchw5cCt0YwAbQ0hTqT+q7qHlJKEO9iW4p54P+LWWAtXynrSBhM4Azy0BKcVyoX19HzUsCGXfO1OAUfHHWv+SZ/pF2GeAxuDJpew0g+EffY6DlhbGSXjeAeKheRWOIod8I+swX/dvLAIvgxl7YXgM4mgdSrqYSOknte8A9AIbwLUBlG+f/7fr3fBUtTgQOvyqCC9WwdQHngJaDlEZCf50pXM+iMqTQ9wDz7zrp3/tTBssAm+BCI2QGcAy0NFTxj6t/8JFwDgAYonuAky991j+WATIVcKYSrjfBI1UgJb86rpFK4JYQHgNmHNiHfpnV9n/5qX9MAlzHgZZsBhDwJsBdoGVlXPx0dAPIiiAoxj1AhuQNOqhe9l//piRgBRy5Eqo5IKkIpCyrX73+8+EAgOnILvTJeikI+sckIF4AJz6HagzgCpAiLwj54wOtXAFg3HgLfSF/yQVD/8IB9CRg07kLHiYD2AcycC+ydU8oXwRmCAKA/HbO/Agl6j/z3/WPSUAdHNgO0RxQ5iqQUiuNmF9o5xkAhuACqqDw0q7/c77pH5OADXDgXXjeA4pUgJbXI6Z3wnkGgHFnDfqhvIrhPzaY/NI/JgFzLquBjd//oI8BPAJaKiOC9qJgvQLIAQAzaAAwbSv/JbO+6h+TgK15OMHF0MwBHQIttQsYo/EQIEO2hqaZ66R/HzZOYRLwFU5QOhsSA/hdBFLk97kcBgB8DZDpwFLf5T97+Z/g/i9BEiDVwIY8EhIDiFaBFtGm0fWvT0HwDBBDEADcuORc/tfqy37pH5OAu2BDCcuDYHNAS701pM0tQIauAvAd038H/fu1chqTgMwbsDIfkkngTaBlctWcAGT5GjBD8Ai18sIS/mP7T0jLT/1jEnDLLoSzoZgE3leAlgNrAoDvgIRuWy3jNZEd6I2rC07lPyEs//XfTgLKYKEQCgOQakDLjK5/rgAydGuopi9g+m8r//mv/1YSMPEALMyGwQAmfgAty1OWBIBfAmXcmaj1+AR97mT6j+V/3/XfuhicroOZPfWzDfpVgDS1/vOX7AkAVwCZwebPCi8t4T+W/4Ki/3Yd8NB6FopPN9gGQK5/+NXSPycATDf+5HsK/7W7/9b039L+81//7TrgIpgoWwwgiLvBh1H/tBMA9gSAK4DMKfvPxU94/JvT/2CU/+x1wA0wsaO/hmNcBgygAQzXgZj1HBYAOAFgBr8G/O2iNfwPWvnPXgesAPJt3IiDA3obeHgHiKlOmQsAvAqE6UgDuqFg9c/4pQpe+t9CDwGOAbmuRSyB3QqQqQIxs5etBQBOAJiBdoGUL9nDf3GqBFP/rRDgPrSZTqUCbAASuf7nF3T9my8BcwLAuDB2FTojX5myVv+s6X9Qyn+2OuCuDC0qJgMI3F0gqQzEyK9NBQC8BMwJAOPITejM3kv78a9KyZ7+E+g/QhwCzGBFLKF+1gG9DPi7DNQ8Q/2bCwAT0UDZNBMQXsnQCXmmhMc/Dv+Qh/9bt8aIQ4A1GQ1AkMwG0AA+FoGaGdQ/FwCYbkQ6d6CX35uOf1v4T6j/39eaEeoqQHTOYgDJABpA5CaQc73dADAmALgAwJx2C01+RZO/tfqH4T9V+v+nKT8S/xKHAK8U0KkkkyJvCZwBSItAzreSrQA4GucCAOPKh3lwZ3HV+fgXZwpd+n+0osib+CFhCHAFDUBFVMMDZQD7NUA80T8WADgBYPpdBVI7OHn804f/mZt5yN9r3+QhDQG2FNCYPi/0r2bDgTKAuwqQUzfrHwuAwxOcADD9jQDIc9bin7n6Rxf+px8VASaPhWIRuhDgqWEA2fMq2UAZADYpPNC/rQHABQDGmUwBXKgujGhg8R+Pf6z+D6jX6PNJAJhci9ggCwF+50GweC6r6j9QBrBVBnp2Sg4NAC4AMH2voSy+QPmbj38c/qHQ/5MaqBR3IycgCwGaILh+7lxW1X9wXsWPPJwHeqoX3PTPEwCME3fAEeVa6awhfz2YxOMfq38Dh/9rDRDkjyM6Z1SIHWAsuq8bwOg5wWhQDGANW68e6l/UPLkAyLgjFcABeX1Vlb0mf6fjnyj8l5qgkd/QxI9EdKiuBe+ASmNoVDOAoUAYQHpFBsQr/WPJgwuAjDMVxz7SpXEhfJP8sfeP1b8B9R+5WwQN5dBQP0KbBNzVvqahoVHBUDwABvCkAF5Q1vSPA0DcAGC68Nhx8C+VEgagM67doaE//jfKoCM/0OU/NjYWjUbVv8UHlAYwFssDwI+4cAAtAPDbALYWwRPqrH+mP3ZlsFPbTiRS44YBYPSPxz9J9e/DfTCQn+jqj7bQHIC2FTgn1BGPDwm0L8LPd3GjNxXwhPUpF/2nA3VTmwkO6T2wUXyWSCb0AADlj6N/ePwP9AsVfZSHFptC/lGVCUFUBQ2A6mWQDdEei+kOEPd5M87Hq+ANX3LO+ucGAOPGDFjJN1PZpBoAaBHAuFn+pMf/oUkDT4X8hfrTGsIC0F/IQoBlgOVYLC7w1wD2K+ANyi9n/XMDkHHlIVgoriTUYRktABCg/DH6pzj+h68BshjV5J9OD2uk05QGgCHAc4CaZDhAzD8D+DkNHjFpXNjETc08AMB04VUeTEzezKqNsmxSGEBKqF/TvzY7p8fNVMd/DZC9I3H4C/VnVAwHwP+CLAQ4UqAo6Q4QEwaQ9sMADhvgFXsX3fXPAwCMI9EyILPPVZGPagFAIqWRcIn+DXFSHP8wvxaNptOa+iVJUv+2GADl+6BLIGekmI7ky1zsvR2wQF/+d9Y/NwAYZ5rQ5sZmTIrFjQAgkXCQP47+UR3/ggfRCSF/yUA1ANrXBTEEeABw1HIAKfPfDSDypAze8X3KTf88AMT8Y+9Of5qG4ziOZ0PFaxOmw27ZFgbZOMaRiQsaRYLIQG4X5D4CyCXEC33mv2C+/7L7td0+tLZb59rSku/7gU8JhtfvWrtf/XcA+5dCEdX/UzEAiBT+OPvH6r/p6R8tlPnLc786MeOQIWD3ANAao20x0qg/Juyqi+D8JjmXdBpl/1yj7aVI6dVhSzjUeqs6ANwT+MEfq3+7p396r8z+t9T044y93wwyTEV1qSGGGTdh7H3uIweL72uO/3H+z/4584JfSZRaLra3dCgLAHUAkPVr+dty+Bd+Sdr6b6n8xdEcBgD4t3UAWKFf4ZB61OCijPbZLDla/4B6/Mf+uQaSNY7Oh4PBFiwAygOA3OMr/O2a/otHpC1xT+F/W0k5nLP7fhGMAEWaEhsOUci1o7HicoKcDLc1wL/6wjOf/3E1+kwUn/wkZsbKAkAZAOQe2MYffYyRrpkq/4fqAODEAgBLgKMZceIoF+5w47Px0PQBOVzfrm77f696VSP758ybpfR0SABQFwDKAND2QKnNdv4dOdLXq/B/KKr+OPtlYgRYfis+cqw+beSwjcBUb4ycbq5bf/zH/jkLFQu/8LJ8R7i8AFAGgDahX8M/3PL/Z/9ofYT0JbdV/uLn4awRf7W2DwDfPsqPHIo6HMYROBl/QY6Xuoxqlv/sn7NYUP63ugBQBgBhUdWv8m9u+kdLcfqnt/iJbZUnDZ3YAGAE6JhuxxtHDupoOSwkyYUOeoz9V59zYP9cTRPaAUBZjTvAv32S/m3rlsL/QTk8agj/TvyyJ8Errxw7xKN1/n2c3Eg6u/p17birUf2P5Of/uTphBxCK4DBeSNTxb9L/zwOj1esd7ZHDbedeW8Vv2y5+XdW/IwPN+sobidzpxZrRVe0PK4//8Pv/nPUBAJ/Gi39t4Y+mUmTQxsOHgv9j9Wt6XXhsLVBO/t4R1b/dQNq/vxsjt5LynaZXtbN/rqEBQGwB8Die8phMs/zRvEQGDasfOVaeOLjt9PdWYQQIttv1nWOo5cPCqxi51+iQfvqH/wj75xobAJTn8dUiIRv4o3EyKv6n7YHQ/1S9qMOVi2sCoiCySUgH8LtU33609lXt7J+zkuJB/TKOiJzyjIxt/IM5MuxYfuRQeeHAve+twAhg1wKgY2r8TYzcLX7WZXJVO47/2T9neQBo13wdjw38UXiYDBut8MfK1Y2LKzAC2PH7/Zwd/yqRa+G6Bt30r7+qnf1zDR6L4fEYWX+T/NHvNBkW68Y7h65+cbVdd4/tzYxnE3Qd9a+Bv/6qdt7+c02ci+PTMfBvruIzMq5Q5l/5ygFsAODfuZr3//twIZukayp5DP64rK0y/fP2n/sPD7oLOcC/2aYGybj4I+FfDgsAl76gp4kBIPDz8PMW7LtfrNCJzb/+qnZe/nNN7IkRaDTZjEQmTVa/c+werq536+bK//Ef2Pu28nI0TtealO3Rrf6x+9cs/9k/1yAH6IcM5/wPPpL5V+YvlzYAqKFf8vfUYuHrIF178V7wN7ytRfBn/1wT8yFg2NC3GJl1pvBXHl7FBsBjL660rB9OT75JkSdKnnWDv/6yNl7+c9d/Lm7df+qRuHNA/ROubAA8dHV968nS58KrZ+SdNle7jPlj+g+FefrnnN4UW28qRqad3cedQ+UFgHJTtwfeXA/sfZgfzx94ZM5Ho+eyfvA3uawtyP45j/QhTqZJ3bJ/5dtrPLEBKC/2F5ezYzHyYFJmzYz/A/AX0z8v/zmv9D1O5mWePKk+waacAEbcv6gbi/2Xnlrs1zj5A3/ddQ18+sd5q19xqtHuXREWANewAQitf5t+9957i31dz067NZO/lj9W/zz9c15quyaso7tyWAC4d0tn4PfJ7MryVr/X4csle3ejtfnjqmbe/XPeKbJJtSqpa1ntAsDR+atle2p+4eXwkSc3+YYl8hX9WPtr+fPqn/NmwWGqVbxTswBw9ASwtTgz/S4/miR/lfqyI+vXTf6G/Hn65zxWgWo2pz7JhgWA7SeAwb3vS58nsyNx8mGpuY2Kfkz+zJ/zS4tUu0vlBsv79x1YAISLsyuTwy8k8mvx7HmXTj/W/uCvva6B/XPe6bCOPqlTGQDsXQD8+LW0kPuaIF+XyOx3qfihH5O/lj9P/5wXKw5S7S6wA2hyAYBn9/o98KpOk72YWx0Afp1+de3P/Dmv19pHdSrZswAIbn+bfr014p9jffOkdO/+c4HfRD8mfx1/Xv1zXmuL6tUj/sabWQBEPixOZny8zdc0eDG506nFr9d/ZfJX9/7Mn/NoH6leY9HKEeDTx5pnAKys97dnFrb66KaUzJSGYB/4Vf2Vcz9M/uXRkvlzHm49TvXKRa/sAJTXgK0sAFo+zS+/8f9OXy02lilc9ujsAz/mfqFfv/Zn/pxHC/ZT3S6j2AFYWwD8mFrJjdyQFT8NpudOzweq9GEf+I30M3/OB72j+g2IP3rNEaD5AiCwPjue9dtTfGYlDvKljQnI19rX49fpx9qf+XNe7QPVL67fAZgtAAInK9mbseRPjQz3ru52G9CHfeBX9bdp9GPyZ/6cVws9o/qlrewAAp9Wtnzxvl6tpGQ6kyudD3Xq5YM+7KszP6Z+6JeX/pj8mT/n0XJkoS9ReQdw/58dAPAv+hu/9OxgrrC6M9BlDB/0VfuY+Mv4MfVr9YvJn/lzXu47WalUawcQKPoVv5QYu8j2nh5vDE1EDeFDPujDvpj4BX5M/Tr9vPbnPF6gn6y0a7oDaP343mf448mxi7mcUP8c6sFeAx/yQV+1j4lfM/VDP0/+nPebJkv1VAcAzQ4gNJ/x/gd9UqIvfZH5kjstXW6sDQA91NeGD/mgL+yXg31j/cyf83aRBFmqW/MhoPIUUHhpy1uP9MdSyRcj/Revsl96X56dlo73N3bXBiZwmmdCHuzhHvD19GEf+FuBn/VzfmqSLCVF9UcA4Zl8nK6leCrZt5kuO8/M5XOF09Lq5fnO2lDPc0A3xW6OHuzhHvCFfD191T7wi6mf9XO+qiiRpQZ1RwCHuRQ5mDT4bCx9cDGcncvLc/lqeTIvIx+YeN4pSFtmbk4e6MEe7gEf8kFfax/4y6lXNDN/zh+9IWslNUcAb9MO7dLPSsfnu+VF+92mg3VT8UAP9nr4kA/6Wvti2V/Bz/o5fzVDFjtSBgDxJmDbxxFqvkT6TTb/8nR1f2etp/uRorN55ubUIV6PHurhHvAhH/Rhv4K/vYKf9XN+apQslq48BvR0cZOaaHDzVf70eHdCvmRUXx3a1pnDugl4PXqhHu4BH/J19FX7jJ/zc1NktVH1Q4D9F/8rvz9f2umJCpmw35RwMK9HHeBBHuihvswe7gEf8rX0YT8YYP2cDxsmq6XlHUA0Q40njc2dbjyvim2IOoxbZw7p0A7wevNCPdjDPeBDPujL9hk/5+9OyHJ9YgAY6qPGin2d3O+pyrYk3hpyKIdzE+nwriEP9WAP94AP+ZVZH/SDAcbP+bj3ZLnB8gBQkqiR0oXduwb264m3NJeDublzYDcAD/RgD/eAD/l6+oyf83nr1EBddwtkvaPejWhN+sbi4d3SZG7uHNTNxAM91Av2cC/gQz7os33uZpSjBprYkchasexlN+jXfLoW5o281zQO55akAzyKQL1gD/eAr5XP9rkbVCRGDbSRIEuNlbrM8Bs/ZmcIvr5xMK9HHeAhHuahXu8e8pk+dwObpkbKkIXiX4YU+8jwRVq4NxBvBTmUm0MHdoAHeZiHetU94DN97uY2So0Up7qlV6NG+mH/Cv2r6ut4r4cczvXSoR3g9eaBHu5Z/l/27q2paSAM4/isjtoWtTo09jAtgzJAW1o6KE5lKDKiiOCJMgKeL1A84aigX8N5v7LdJM2bpJseIIFVn98t5fK/m8NuFv59zylUmW8VmXy3L2YOK1bWS116D2ycM2dXAkrn4Dl5RfUIH/4nixSmq9MyftUxGRy/L31V8p2xKxvnyoNL98eujh7hw/9JXKfw5D/EGB+R5Y2f0/eG723el3p/lXPnvXtH9gC8DSAEmeJojHH+sn6O390+l++uXlW8ovGBSkf1ACpTFJZ6RZG/dTZ2Ojb6YGnly+LnqVfz5Rv1+fHVqZvri4+fbC6tvWu+7lxzw80PFjmSBxhMgcKR+xlzsfNPTb5ozNSv53ptEFy9t7l8vlv5woTaAcJ1n8JRqlgf6LLzT1b2WuUXaBC3iisfr/hW36BzgCg9o1DUx2JJu39je+PhboYOp1pbbJ528kfjANGqURgeJi3TW9+Ofg54Yb15GbtsAY7B6SyFYKLVvrH9uZ6lkBTuLl/Gx7UAovaDQlAzKhvzVQpX4csBvqoPEK17dHTViQJFodr4hSP1AKJUp5OVfVlbnVtffLa59mO52Wzu7/9uvnu6+WxxfWq1trObm7qPEQAgMiJHJyRfn7n34sF71W4APmXjyq/9A5yqDeCm8U7A/mTKcyu/Vdv+eRBwH66LEQAgImt0zPKvttbSqg98+ccAHgLkCIABACACj+k45YvTw0M+PAr4D9rFCAAQsVcUOa5/KT7c4vnQb/Chm/4RAAMAQOhKdDxyM4/UZ/GpRgDfXQAuAQCicZGORfVuslW98ujtIYn7xwAA0NXf9xJg5HNSceQP98/5u/vHAAAQtSZFLv/JSDh4AOD8O+vn/jEAAEToKUUs30gm3M4GfyCQ43dO38drAIAoPaFozY95jwZw8uf6Fe3zMdx2/xgAADw02goULLuh+Dq4nT/Xr0qfz+DHWkCATtp9EFTh5bTrC4GcvzX5u+L3l+87i7uVPy4AAKIwTtEpGt7+7fy99Vvpq87i5uO4sSUYIBrfKSr5vaTk9O/Pn9/0X7DDV5/Vh/4BIrNDR5YbIYWF2aTJ07/Mn+vnTb/BpwDgq2AAESrTUYx8vfli1rhKna6Ocf/e/Ll+jl/RPb4EDBC9G3RoOxsVQ3qj+tuYYcj+efrn/Ll+X/v42D/AMVugwynMyfrNyOuK/kfl3+z+7emf87fr5/hx0gfAydilQ8jU9gwpKW1Th1tj3H97+r90qZ0/L+6TUDvAySkdZm3/rFW/lXiR/EYqnv7t6Z/zN+d+nPoBcPJe0oCyc2Ocv1Qin8xbX//29G/mb03+eLgPoIU6DSQzMenJP5WqkF8jKbX75+nfyR+nfQBookaDKLwxJPf6ngb57HT0z9O/zB/LegC0sUoDqDk3/7y8Z4K8cpOd/fP0j109ADqZG2RrnyH5VvctkNdWLBbQP/IH0IxoUL8K04r80/EseZRjkq//c+fM6R/5A+hFPKY+3Zrk/nlt//Aoeb1NKfo3L/8vI38AzYgl6s+NWe7fvbZ/jTxuJ1L240Hu3778x45+AN2Ij9SXsv3yn6d/e2PvEnlUEqlUu/+4u38c7AGgoQv99R+4tv8RuY2fTaQk+RtP/5j+AXQkrlNvhTuBa/tXyO3R2YQcAawHAK7+Mf0DaEmMU0/Zbf/a/iFnbf8KuZTi5gBgPwC03v+hfwB9iXXqacto4f6t6d/e179GLg05AEjWDcCZM+gfQGtik3qZMVrUa/svXNwnljFap/+Z/fMNwMUr6B9AW6JJPVy91tk/b+17TayWlgNA+yGB9QCg9f4f/QPoShxQd5ltRf+8te8isQ88AMhfmTcA8v0/+gfQlBB56uph+wEg929P/9ZnPUbIMTocPyvxDUDrN+gfQF9C3KZuqrPK/u2tfS0L1FYasgeAuP0E0LwBQP8A+hLiJnXTsG4A1Gt7Zdzz1FZsDQBx2b9zAYAbAAC9CfGEuti9Zj8ACFzbP0Ntm+0BIO1cAOAGAEBrovtugD3nBsC/ttfu37Wd8PfQcDoueS4A0D+AvoQ4VaBApfYNQCLh71/YlslWvWQNAM6FAi4AALQnRJECfeILAMXafiH/+0KGLGV7AOALADwBBNCdEE8pSMZ8BeA8AFCt7ReiTJaZ860BIO26AMAAAKA9IQ4yFGDc9QbAyZr7l4SzmWDrvLwEkAeAy5HCfASIJwAAmvvD3r21Og1EYRhm0taqNcFaSVVU9EJRPIEgCJ5AFM+Kgic8IbhRVFBRf8f6y86amexp2sRcD/M+V17Yy/XtbyZplzHmh/R4t10AYq9fe7fXmJvi3fQBsFhwAgCS4S7yu11u3QCGP+trj/aN+SPebQ0Aa8YVIJAM0/8g8EurAHS/22//fVGczzYAZm7+OQEAyTCmOCydzq8VgPjl3tbHz4izmNgEsDgBAAmxAXBGuhzuLgBmPQDuijow0QDw888zACAVpu9B4BUNgOYRQO+X+4ypj4p1dGwDYO+s+bEgTgBAEkzfg8Cr6wWg88t9JrxJdHmsFcDao/+VKwAgDcaY4qR0eBkCoL8AKBPOAMc1APa4+ecKAEiHDYD7suncQAGIAVCfE5FrY00ANRlzBQAkwxgzOiIbrq8UgL1dBSDmh54BtqYhACYEAJAQPQPckA1fYgB0v9sfA0DPAKenWgEmdv65AgASogHwVja8bp8Ampk2PQXiyFQrgBprWyAAgETYAChOyLqzayeAvlKv+fFI5PA+VwH8vqCS3wIBUmE6rwGPDZ4A4qf/HJBzpa0ANgLGWgC4AgCSoSNcH5G2c+E14LUTQE+BuCcHSlsBLAIASIyO8A1p24pXAL0ngJgfP0X+aAVQ0ym/Bw4kpOsa8Lud/5W3gMr/B8D8mvy1FcBGwNQtDeG7wEAy3AifkJYHO5sAWP16f2+BeCh3K5cAujK4ZCMQkBAd4dvScilsAxy6Agj5Mdq6U5XlvjD/NRtBgHS4CnBFVj3bOXAF0P508fxRrQlglQQAkBQXAO0K8NrO/3AAxI/Xj0Y2AZTOP0vBgYTo3/BRqwK89wEQ7wCL/wdA8XZkE6Cy41+5AkAAAMnYrADv9tv5H7oDDIxLgLlLADf/nACAlBhXAS6sB8COHcuhAIgfL0Y2AZTOPwEAJGSjArzebcVvAoUrgIEKEHACANLiAmB0oTMAhu4AYwLM/fjPCwIASIk/A6xUgG86/wMPASKjCo0ApfNPAAAJ8RXgTQyAHWrou8DtCtCgAACJ8RXg1SEJPrr5H/4mQLsDOBQAIDm+AtzYDoBdGgDLgdcAIuP58Wf+gcT4CjA9vR0AKq4EGvyBHxMRAEBy/JO8J+L9Xur8L2ZDrwFEzD+QMF8B6gvivFgsdy2HAqCN+QfSFV7m+XRQ1KXFws7/YAC0Mf5AskIFuCXq5GxhuWW/rPoEMhAqwPSaWMe3l/0TAEAWQgV4qsuCD+uub18Axiz6ATIQKkB1S6zZXodl30AuQgWYHheRX3v26Pyz6g/Ihn8dsPp7UOS+2/VLAAD5CBWg+iry2G37ZtcnkBFfAeryjdxaWfbL7/wDWfAVYFR92DoVl/2y6APIRHMI+Htyddcnv/IJZCE8Cqyrn3bNT7Prk0UfQCbCIaDWRV9KCwDLfoFMmOYaoLKLvtyqP64AgHzELR+6669k1R+QlZgAuuyPTT9AVuKmL131xaYfIC9NArDpB8hRs+ZjxKYfID9GFdZcsegDyEtIABZ9AFkyIQJY9AHkiD0fQNaYfyBnzD+QNeYfAPCPPTgQAAAAAADyf20EVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUVduzfOW0YigN4JUMosSA2bmFgoNf0LgtDhp6HTuRv6NCVf8ArSeCAQMnvDu9PrgUGoQe9VFSqzd37LF10ftaT3pfGhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQggh5Diwm/un8eycvSOEpE57Wy5TvRpbwSs/76xsoJXG3m89UHpWD3Tj4wKWpmW28S4XejMvHLzLQKvQZ0vFS77G2cX8ZXIn3/FAF0Xd2lFgC0AGbP9wNG/fWGluCroXx2MZbbbwWme5RgBDnXTwMm3YNmKMFW5QvK+TV/hn/SJu7Xh0AUlKnO9t6TUgSQWtNHQFuscmczqWpUfYGHPTWo4DQHEVAEWbkw/jGGzoc84LtrWj0gZkxldwR8MhIB200gx7AN2co+fZ5bXRLo2KOQwA3EjmIgD4SmHm5NMILBkUbWtH5jMgceCFYbhnNuaAJCW00sgN6B68UOKSi6PsgOY8lAyKuQwAtHMHAaAqFGFMylOwZuB5XoG2dnyeALnzpDDEHeULQCZ4pQH+CLqul1GPsymKUd7UjIq5DQAPNdJ+AKx7W4gx+XoL9gyazWZxtnaETgCJT+r1uuxpiDp6A0gi8Mq/9x100/pSUz3O7kmOAHnJqoXcoJqjAEA7dxAAq+Z6aLu5YB2waVCr1TZbowA4QA+Qn41G2lM1G5uTewVkplYa9t77ArrzhpSdpf2j7AKWROtqnPN8A2BSk1Aj7QZATUKHmpM7sKpTaaxuISXAgUQMyOdKJe3pzmywK0ASX600630fdOOKhOta4w1hx89NtdCgmpMAyHZeq6t3sRsA04q0nJO8x+Qa7OqUSnJr6+ZRAJhibAbIk+zp7lwz1gbkee/KtzVuQXdWktZPs36SE9ijuyyXx8XBAVCS0LvYDQCD5rp1BZZNyuWt5tF/AYwxVkoAuSivWrqe6/XKT4Ak1WwlulamA9krlSV5lPIkVZzY/AKILYRBOZcBMENbdxAABhWcEon1ABAi3VsBtnasGGN9QNpCthTPdfrvCJD7PSvfVkYDGZ+WhZSdpPVfqRHs1dEuTp4BILeu+ugiALbGxLi5xf0AkJoFgRDq3lAAHBAA9SEgP4TQWsrkwtQZIMmpEObN/wa6ZyECSZ6kgzvahf2S9wb1HAbAr2BzidWr2A0AkVZQJ5VbAPhg3cz3g2B5jmnz6CvAQQHA54A8+MHytmz/OLIUHwPyLcAr3+bHoLmtBoEvBUKgKbChOYQ/GAdZAqic+y8Yyj+5dfUqLgJgVUGozeYzJZdg3a9W5PtZ8+pN+hvgoADwFoB89wM8iyzFTwGJ3/sBulfG12CQjn+Uyg5ShYn1L4DYj/XNkQVzDABt76H9AGgbVHBIwH7JcOOLmeGwU21F0foOUgAcFgC8C8hjS+9phoeXgNzJ7hvF7wnohpEfRa1UlF1RlTq2vwBiiyrOOQPWAuA+aqlL7CYA9Aoe57lMyRx2xL3+zVk1dXKoapoAm58r+ghwWAB4bUD6sqlqGDOct2J8gh/TlUa/LE+gu/ajVjXVavl6TQdfALGZyrkwvwD4zd75LKWORGG8kD+aRLhAkCxcaF2pYuPChcXClTwDC3ZTvABbL0ghiAio1K3zyJOTdOjkJIFEuwu89m8xNQtmAibny9df9+nGH+8XgCPhAmDhn9e9Ufhb9yQAY6CMLk1rU/16WjYKYBj2m0MJwBcE4AoIjYJpoKqy0kDcT/4FwtRKZ8DOaN6A9e9gESmRkABSWr/xy+8eBEgXAI2Up2AB0HwaszejfNSnf/0Z3nte+6nxWQAlAF9RgEp2BYQLi5SGKwCVcqQFOE3+55/Qcbhh1793H4mSiE8AKSvy7MRcVLIA4K8PludXr0AFwH1Rsvu0JwE4A8Iz3npS/Z8aAuw/3vjGuC/2GhCGNfZEBi1AJbsGwjRV4bYBaD6laexGEsmRkQD2gTAzff5lPwJw5/x8S6YA4BWYwO6vTGY0gbFI/RfS4wwdVQj4dQHI3gPhzTKjLED2tB+y0RYZu6fZB6TJTCB7Bwp+QGlkMW8CYVnAq+7OAeULgClbALQ9C0APglxj/dMBfVq8dFMJwBfHAKGwvP+LFCQbA2QvgHCPpYt5QYIbMKAW3MD6R6SMAGgCODBugLDmX76yXwEw5AtAOX6iQT5r4jFNUv+soNNg2mzWj6i1wF+xAPVIa88n+PgYIDdMbQHi9wG5NDXPAFjiDcB1SG7Mk9C3v5SRA6YWAP5+liIAyL4F4Abo1EQhUP5uQRspKZdZ/VeLainwVyxAPlwZVpQFyGZ7n7cAHQhyI9UA1B9pXmlf7gEII3P3IECyAGABSBYAff8CsCL3no//nfJ3a7+cklMb1QwkxAL0gPASYwGOH0NSkdACZBuhYYbmCQD5X8hIANcYOOoLIMy49OxLAHTZAqAfgAC8kHkmxwDU3PrHJA9XK2/IJwf7HA+g0/kbE1PXcEuK0ksBOkC4SWgBqMa8STUANAF8tPByenfvOeAPFYBXIgC+97/FRvIeuVTgjiAHsNfJ94VZ+w4QxhqpSjYIqC4/ZwGqfRIDFUxN4hQATQBnhhs7jYHw5ssBlQBI44G8NQq++nfLn1d0KnBnp5LaEuzLFqA4B0IzxgL89zkLQC35g8kNABltSEgAJ+6yM12/DE1kXu3KAZUAiF8HcO4JgLeSh73LU1K0KR3EboffGGYBroEwirEApcVnLEC+RTuOZBqA0mMo7ffmnS5S54BKAMRvBzbcGABvIv+YlXNK6nW1LbgYC1A6B8IgxgJcf8YC3NEuIL8BEP4KpnbjjhkAm9p8Sw6oBEAWVfpwbQyAW//o5LGa01OpqINBvgar60sgnHt1TS3AedgC7GpnMyDIwpRpAGgC2NDcwNF55rpAeCzwbx+lAEoABJCZQID5ic8AsBwfqaTjyEUdDSZiKnAMhFmMBWgD4WWnBXiBIF1uACR0AYyimo5RAJwLxuSAbueDEgA5ZN6pKfMSALaQjw3kP4E6HVSQBdCBsIixAPVRWgtQCGVyAQOQzwutvgG1Mlj/vIHkLGUOqARAABmN/tHfdZ2rfzFF/Wc2HN65x98UVtdTIPwhde1ZgN9pLcAKgjRlGgCaAELbkxu26CxlDqgEQAARB0ucN30rwFiStxNV/RzhOaDRgiBL+nr2LMAq9A7dagGoYIyxF5zVv/hp+GcIcm9wucHrGWZ4G0SeAyoBkEImcwWU1stD9ywgAKr69wSr62cg9GhCzyzASToLcE5HDLwiqccQnwAOdS43jt6UT6+B8Khv+RpKAATAD5Yg9Ic2jZ0sP0bj6XPXPFLlT5DaE9TQmTmmFuAljQVo01ey1EXAIypiGwOgbVadjYHw6ssBlQBIwDlYQgDD1YOlBIAgsSfoIsICoFQUWsktQGZBz+TgFSm+C2gQse0glxu8nE0hPgcMK5ESAEEP2CuIYdWsHCkFYEjuCRrWoi1AdprcAtCSXEs0AOEE8NpLAPmqU5sHIEys2BxQCYCg56s+AkEsZ2r+TzCsrmcRm4MZURbATGwB6D4gQ03mGqBn+rUMYjfyzqLz/LYcUAkAIuH5ys1BFOP8EaIkQPSC4EWyzcGy2TcgxFmADu0CMiR2AWktOtwITjjg5ZyWk+aWHJB8GSUAInCeGq0Bomi0s5WKWgWIyO0JmsZZgIQTAdmG/C6g+ATwwmcANvVftKmGTzr0TXkqAUAkvGDKExDGIJvNqj4A4RZgErPpH7UAPaDcWmaEBaCf6xgSu4Bo3DA36YRj7tjtOCtSrwCt25gcUAmAwMfreA2iaP0uOa2ASgHEwFf5JdkcLN8HyphagKh9QD4MiQagRB3mIJgAuoeB2nqTrZeKiXNAJQAC55mK1cEcBNHQWAuB2gxEZk9QK3JzsFeALRaAV9BTqCQlLgKmCeCKJIAs5HMo5ZZA+MNzwIoSAESCBage52YLEMPC6SJU2wGJtQAFIIzJZB2Cy+1iLECeW4DwPiAjmV1AWujgskACuNk/vmJjK8A1EBrROaASALEW4DiX7676IILZcVVtCCixJyh+c7AxQCILcAdB2jIXAdP44jmoNuxqeDkUunppBYS/Pk1SAoCIf7+UirYAnJaN5vP5EL7KfLOVgFIAiT1B4c3BMChIYgEM+gHDlNcFNAi3MtG80b0ago9iIVkOqARAuAVABcCbc9bs/tnQ2cbDdLXoQ4hZzpnWUQog9BY97doc7OgDomkTC/BCc9vYRcAZ8QngjK4BZAdIIa4FeALCedS3UgIg3AJ4CsC7tB2s7WidORAWuLBLKYDsniC6OdgAIJEF0CHIlJckzxVkJYCjyASQZcb2P3bmgEoAEOHvF64ArgRoac4FfX+EIDo7GUwdDCLSArxv3xysvgRIZAHIILtfk3cacDgBvOTzDc1udzC4vm4227e3t1cu9r+1229JckAlAILI2DjOq+oqAJoAS0tHbQEBOmWuAMoCCOsJWm7ZHKxy1INYxpq1sQCVKxrKyTQANAG84xe7bEFipvhDmTIpAZDwdPkUwDEBaTXgrAF+brzjwUvqeHBJPUF0c7AsrgFKYgGy53TdhkQDMIDIiyHaClLQNmkOqARAkgKgCUAJMC2OtpMeWevpLfAsqUGAxJ6guW/W/hW2sNIs74N0nr0nrwsonAB2uAHoQgrCOaASAHFkNgpQPGYSgBpgmIlgEjEEP26/ujogXLAFaAKh51kAOnlGaXoWoEpEZGlKNADrLRsBLyAxNAdUAoDIUQDXBOBAwMZIiGkzoWtP8GFSFkCsBaiPQqdneH/nMUn2qAXwvMKMvpMlLgLWgHDLL/YA6WjUyLdTAiBBAbK2AqAEOBpgU06GYTMFPwPXTmI+pVIAGT1BdHOwNhGGQYwFyC9pFxAxACIX3E3i5xt/Mcd4KDngDxcArgCOCTh2NQA5TUa53AM/Hf447f23/RuQbT/p5mAfEODdGIUtAH6wJ70LKD4BHOo8AbwHh4PJAX+6ADAF8CTA1QAkn5DTJvjpkSklJQAi9m6J2vZz7fydOyQcNIxmpAUwh7FdQJbJZwrlJIDv/GLs2x1ODvjjBQAVgJkAlIBi1RYBJJeQ/C34ecJ1XsoCSLAAdxDazNcoG4/h5bbjKAvwHDco18R3Aa0hcrSBaOfwCd59OaASAESCAjAJqJdKuEETcpyEXO4P+Hlmk0qH8uP+BdjuTf2IzcGeSKmV7bq+bYUtQI38xy8Su4A0IDT5aKMDn2FIc0AlADIkgGkAigBSTADKxF8yBCho3jK1UlaNAQRagIvw5mAn/agdd17CFmBKO/MldgFNIFZs9Ef4FPckB8woARAL21YGqVSyjPpOHJ0I3u+OL1VSIYBQC5Cj+fmNdQMBJmXnXXsVsgB08e1fiYuAB+GhCm8CeH19e3tbPzPW6zfk1ccdMgVK058DKgGQQMbF6820ySainuuDn4Gv27tYUhOBIi3AH2oBZpHH7uo6LaDVC6lJXd4aoCJNAB/oNiC4UBTJeatPbfjaMg0//AKED+pTlAAIJ+NxlBi0C/ckXGI3WqWA4i1AdUkVAAKM3bBN1y/7sJUn05JmANbbNgI2DV7/UavPNYdC4Xd/Ww6oBEASGUYaIWiSR7KGN/ogf913hi0I7sJWsOHWEQD9FbbRKMjbCbgAW9YbeI0ix5gw+xvQEM0DP3sBhOEJyQG/uwBYB1kimbTkGkB+XaFwoPL2rWE9QR9ACYdtKADbl9v1fO9k0WPrSfymY5rF6x8VIJf3C4DGQRFb7MwB/ykBOJwayaRiQFPdN762TAmAeAvQBgpN9hlPEM9cw8/JMQCDmC/FFxxh/eP0kd8BmOH20+72HPAfEoDDyskyadAnQBn4F5cqAUBk9QRRpmxcv3O+raNpkhYBhxPAJ24AyFFgrAOddJ/yMGC8Mwf8pgLwcXl1dXn5++zXie5Y5dPTfCKqYo1CnaznS8nJ4OHmHEL0C0oACGJ7gn5BHP0TX/1rVg/i+MDPSToK4A2CLC062sAcz1lk4o0BInpPTeSsD4SeLwf8xgLwWe7EnsDfAxlMlQBQZPUEUdaB+jetOcQwwM/JMQAFIMwMMtpw67/uKoDXe1qmGMjuHPCnCkAmc6gC0Los2BxsxPm9cS0AP0OTVkcBYbVmGkYHohlhTGgjYxHwBCI6jojYFEv1Ous68VpPT8OgCsyBcENzwJ8lAEcOGZfDFIAXzG/VLABFRk8Q5cIta5a2G3YBLSCSpu4i4Sygbuh9gAMAehRYPYu4CnAcN/xEVbgGyrU/B/xpAvC3Uqn4NOAgBeBWKxzuKodvj5sDnvYhgseTTVlrlpssDSCKsY6EDICUBPDVsMJHgWH9V7I2Jd59TmGysDsH/EkCMM0ijghkkAMUgLVT/2olIEVCTxClV6vVfHXtBMvnEKZ1W7NhHyTVJDoBbEQdBcbOAkQJcJOAamybaa6wNQf8cQJQKuHoiR+/fXACMML6Z8+gagbiiO4JakCI+UnNLWzfevvcLYS5r7EPil8EXABCJ5wAevXPJaAU12WKGvCwMwf8SQJQrRYxQOUKcGACsDzhAuDcJrUl0P/sXU1P20AQlat8Og4EO+ADB6MWiUsOPSDO/AcOueYPcDW0Ei0NKojQH13P7phn7wY3dmclC/yk3Kzd2cy+59nx7C7gZE8QcDMajXJeo97ePn//+gs/aAQALjKAx1sygPrmaMYB9pzu2aAUQd/KAz6YecAPJADDDOGU79791DoB+Ham+N+lAFwBe4IsWrzsjzIoXqPcJgwPLSc9jwhOAoCllRAyagBxWYS599wGLxDCs+o84IcSgMtehmHp9u02CcDjCfgfR92RYPJAQbCV3luD11xvr8vtHsxV+WfjQThJPAN4b2YAEbgzqjaZ8gF14Z01z8zMxccRgD4FduRZHUe1SwDufOZ/IbjszgUHnO4J+jliXnPw1edyO+vOkGTEkYL8XUDPFQcBx0bqDqhUgUwCpnbZQ4rGHAnAoJ0C8DUIeG2nFKBVAnCakq99AvzdnQgGCBcEH5Zocb0ArwvlNnS0W1Jele8jADB2AYlnADdWBhAv7WpAGEgB3qoHRGvSAkAmt1IAJrNZELC689jbIQCnKb/+CYPuXgBnwJ6gkvfSQgDAl7Jwuc1ReFykznn+IIm0aDbdQwbQvnYEGcDy0vWfGkDBTu9pSx4wQPmCGwGIWQCO2iQAEdV30dCV29oiABfHqT/IYNWXdTcDuUDOiwQOSOb2wl59LKIYOsD2wacFHpTeBbS06g3NU4fRl1fjaKqsXGhLPaAdAsgLwKx1AvAQx9EE3DpogQBcPN6v5wPQH/VlXQDgBPgUOL194bB+6c/n+tOesdjWN74N0+96nXB5yA/6vvwuoMWVxk2GzWa1WlMAMOZoullfHo90b00Npmma5FiKvmE8MltZTb0ktwPxU229Bf4YNYwfQLI7lmMcqSRArjncRWaRYXWwuro93x9o+DlY79nfXQDgBBwCTIcnN7/+bHTxtS4BwMJecY0VIOwtVr+TtT9WzzH/jSha1rBeP5ixAID/9fvyeLkzDXuqRd2g7CsGedWQzGar2WgIgIhm6z8mipUjGCDODhjTakpg6LCK3QV/1YYPoA5d+7t1Zx29E+TTKdREi8tfX6C9BPBnEuFBOysnZhcJDgSAEE8yrWnWl/eGpMSS5oMIMJslRqySjT1WdBj4Xw1wC0N3IADgv98U2kDEKG076uj9AJ7LiV0+co8XABxCY2qbWXnE0C6EKYpjxaTGASGatDTFbFPEbpiNDkAzCQEgJWY/1GaXawHQ8RWhrmH0K5in7FO+6QKACoi/asELaC8ouY0/guW0mFDojaj0v1krz2gzoiEUG5UUgLDHfaADUQGAwzTVwOvdAP5LCgAJH+Kr5hjnal/gfxcA2HBHNZtrHkJozO0MPImQlZO2Cmd98nerxtF6IYjhoRLUQKUEAPRkuwkVqTah5fbYYM9OsH0sF7Fpq5ojzumvy1A7/jsEiK1898q1AMkXFgCTPxPQB5PIgSxpqxT97e/WDRcBPFQCXjNoVWhJpfqYGQIjq9haiesTLIbvMHKZQLKP09lrm0VQ0+q1Tonf/90CwBlKxO6r47NmPGkLCwCE0MwfelCimqyaq2wUdUZdNV0QolHmDg+VEGCoQgIAkSGgffxDYv7iKCOqCaaYoGX8GikpdtQAr6Kce3va8f8vO2ezmzAQA2E53SiHkqrpIe//qMW7C5PGagFrHJDq77yyxz9jcUAJBcZuvwF2SwuvWf8ofUpwD/kq9VxjTWV+EHovAKIiLPMALBCu4GwROmTH8Km8PwQuH0vZZokcoqBKwVTS/9Fgo/rwCnZ2QvP3q4eH5Yd5hK4JH/sci9EEHvo7ECpACYhLUA7hpctGi/jHcTy50MppyuxunNxg2HUq6f9ArNumouyO794/fbvx8DIlsiTdcqPJf2tQAUq1cZnNnC7ha4sQnjuu0UWXRlImOEv98nm5zuQsrWpL/0cjWKm6tfhYDI6v8Q8e4krTJeFjn5rJaOKUqoFRgzCb+WXC86/jVJy04eF4M0RhN4qX7aybtvR/NILptU/ozbOxhPFlf/hhH7KWvEnqqWZKqm2p1aHcGmyG2ejmj2vyQC7cNnZy0QbSlKm09H8kdno6PmV7ff98uK4YE11Rz8RLhQoWxDVfxyUqt9H545p9QBq77tWrStlMpDYt/R8Opoev6u121j607yRAUc+EVNDEKpVfhJgMi+klM8Pqg6rMNnZ1gpal/Y9EroaroP2/GdM+JCMmE1KxApvITOUKP7ot4c3JwjaZKBDlo3VMkbT/kUhlAFK5/VAacYroqcQZ2Z8A0WMy+Amoe2Agaf+jEQBuP8S7WEWAHRjEKo9qzuAjpG6SqrT/U7h7Zc27KOJcFG7Q+B4JiZcUleZ/DncP4LBBhVo0tgoEB692A85B/omqJEmSJEmSJEmSb/bgQAAAAAAAyP+1EVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFfbgQAAAAAAAyP+1EVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVhT04EAAAAAAA8n9tBFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaQ8OBAAAAAAE+VsPcgUAAAAAAAAAAAAATASoXnyNkfvyiwAAAABJRU5ErkJggg==);
+ background-size: cover;
+}
\ No newline at end of file
diff --git a/runtime/assets/wails.js b/runtime/assets/wails.js
new file mode 100644
index 000000000..ef6ae0fda
--- /dev/null
+++ b/runtime/assets/wails.js
@@ -0,0 +1 @@
+!function(n){var e={};function t(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,r){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:r})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(r,o,function(e){return n[e]}.bind(null,o));return r},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){"use strict";t.r(e),t.d(e,"Init",(function(){return T}));var r={};t.r(r),t.d(r,"Debug",(function(){return l})),t.d(r,"Info",(function(){return s})),t.d(r,"Warning",(function(){return d})),t.d(r,"Error",(function(){return f})),t.d(r,"Fatal",(function(){return w}));var o={};t.r(o),t.d(o,"OpenURL",(function(){return b})),t.d(o,"OpenFile",(function(){return y}));var i={};t.r(i),t.d(i,"New",(function(){return k}));var a=[];function c(n,e,t){var r={type:n,callbackID:t,payload:e};!function(n){if(window.wailsbridge?window.wailsbridge.websocket.send(n):window.external.invoke(n),a.length>0)for(var e=0;e0)var a=setTimeout((function(){o(Error("Call to "+n+" timed out. Request ID: "+i))}),t);v[i]={timeoutHandle:a,reject:o,resolve:r};try{c("call",{bindingName:n,data:JSON.stringify(e)},i)}catch(n){console.error(n)}}))}function h(n,e){return g(".wails."+n,e)}function b(n){return h("Browser.OpenURL",n)}function y(n){return h("Browser.OpenFile",n)}p=window.crypto?function(){var n=new Uint32Array(1);return window.crypto.getRandomValues(n)[0]}:function(){return 9007199254740991*Math.random()};var m=function n(e,t){!function(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")}(this,n),t=t||-1,this.Callback=function(n){return e.apply(null,n),-1!==t&&0===(t-=1)}},E={};function O(n,e,t){E[n]=E[n]||[];var r=new m(e,t);E[n].push(r)}function S(n){var e=JSON.stringify([].slice.apply(arguments).slice(1)),t={name:n,data:e};c("event",t)}var j={};function N(n){try{return new Function("var "+n),!0}catch(n){return!1}}function k(n,e){var t,r=this;if(!window.wails)throw Error("Wails is not initialised");var o=[];return this.subscribe=function(n){o.push(n)},this.set=function(e){t=e,window.wails.Events.Emit("wails:sync:store:updatedbyfrontend:"+n,JSON.stringify(t)),o.forEach((function(n){n(t)}))},this.update=function(n){var e=n(t);r.set(e)},window.wails.Events.On("wails:sync:store:updatedbybackend:"+n,(function(n){n=JSON.parse(n),t=n,o.forEach((function(n){n(t)}))})),e&&this.set(e),this}function C(){return(C=Object.assign||function(n){for(var e=1;e1)for(var r=0;r 0 {
+ title = params[0]
+ }
+ if len(params) > 1 {
+ filter = strings.Replace(params[1], " ", "", -1)
+ }
+ return r.renderer.SelectFile(title, filter)
+}
+
+// SelectDirectory prompts the user to select a directory
+func (r *Dialog) SelectDirectory() string {
+ return r.renderer.SelectDirectory()
+}
+
+// SelectSaveFile prompts the user to select a file for saving
+func (r *Dialog) SelectSaveFile(params ...string) string {
+ title := "Select Save"
+ filter := ""
+ if len(params) > 0 {
+ title = params[0]
+ }
+ if len(params) > 1 {
+ filter = strings.Replace(params[1], " ", "", -1)
+ }
+ return r.renderer.SelectSaveFile(title, filter)
+}
diff --git a/runtime/events.go b/runtime/events.go
new file mode 100644
index 000000000..70cff403e
--- /dev/null
+++ b/runtime/events.go
@@ -0,0 +1,25 @@
+package runtime
+
+import "github.com/wailsapp/wails/lib/interfaces"
+
+// Events exposes the events interface
+type Events struct {
+ eventManager interfaces.EventManager
+}
+
+// NewEvents creates a new Events struct
+func NewEvents(eventManager interfaces.EventManager) *Events {
+ return &Events{
+ eventManager: eventManager,
+ }
+}
+
+// On pass through
+func (r *Events) On(eventName string, callback func(optionalData ...interface{})) {
+ r.eventManager.On(eventName, callback)
+}
+
+// Emit pass through
+func (r *Events) Emit(eventName string, optionalData ...interface{}) {
+ r.eventManager.Emit(eventName, optionalData...)
+}
diff --git a/runtime/filesystem.go b/runtime/filesystem.go
new file mode 100644
index 000000000..ebb4feda0
--- /dev/null
+++ b/runtime/filesystem.go
@@ -0,0 +1,16 @@
+package runtime
+
+import "os"
+
+// FileSystem exposes file system utilities to the runtime
+type FileSystem struct{}
+
+// NewFileSystem creates a new FileSystem struct
+func NewFileSystem() *FileSystem {
+ return &FileSystem{}
+}
+
+// HomeDir returns the user's home directory
+func (r *FileSystem) HomeDir() (string, error) {
+ return os.UserHomeDir()
+}
diff --git a/runtime/js/.eslintrc b/runtime/js/.eslintrc
new file mode 100644
index 000000000..85326fe27
--- /dev/null
+++ b/runtime/js/.eslintrc
@@ -0,0 +1,28 @@
+{
+ "env": {
+ "browser": true,
+ "es6": true,
+ "amd": true,
+ "node": true,
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": 2016,
+ "sourceType": "module",
+ },
+ "rules": {
+ "indent": [
+ "error",
+ "tab"
+ ],
+ "linebreak-style": 0,
+ "quotes": [
+ "error",
+ "single"
+ ],
+ "semi": [
+ "error",
+ "always"
+ ]
+ }
+}
diff --git a/runtime/js/babel.config.js b/runtime/js/babel.config.js
new file mode 100644
index 000000000..39d867cc4
--- /dev/null
+++ b/runtime/js/babel.config.js
@@ -0,0 +1,22 @@
+/* eslint-disable */
+
+module.exports = function (api) {
+ api.cache(true);
+
+ const presets = [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "entry",
+ "corejs": {
+ "version": 3,
+ "proposals": true
+ }
+ }
+ ]
+ ];
+
+ return {
+ presets,
+ };
+}
\ No newline at end of file
diff --git a/runtime/js/core/bindings.js b/runtime/js/core/bindings.js
new file mode 100644
index 000000000..32df6161f
--- /dev/null
+++ b/runtime/js/core/bindings.js
@@ -0,0 +1,94 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+import { Call } from './calls';
+
+window.backend = {};
+
+/**
+ * Determines if the given identifier is valid Javascript
+ *
+ * @param {boolean} name
+ * @returns
+ */
+function isValidIdentifier(name) {
+ // Don't xss yourself :-)
+ try {
+ new Function('var ' + name);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+/**
+ * NewBinding creates a new binding from the given binding name
+ *
+ * @export
+ * @param {string} bindingName
+ * @returns
+ */
+// eslint-disable-next-line max-lines-per-function
+export function NewBinding(bindingName) {
+
+ // Get all the sections of the binding
+ var bindingSections = [].concat(bindingName.split('.').splice(1));
+ var pathToBinding = window.backend;
+
+ // Check if we have a path (IE Struct)
+ if (bindingSections.length > 1) {
+ // Iterate over binding sections, adding them to the window.backend object
+ for (let index = 0; index < bindingSections.length-1; index += 1) {
+ const name = bindingSections[index];
+ // Is name a valid javascript identifier?
+ if (!isValidIdentifier(name)) {
+ return new Error(`${name} is not a valid javascript identifier.`);
+ }
+ if (!pathToBinding[name]) {
+ pathToBinding[name] = {};
+ }
+ pathToBinding = pathToBinding[name];
+ }
+ }
+
+ // Get the actual function/method call name
+ const name = bindingSections.pop();
+
+ // Is name a valid javascript identifier?
+ if (!isValidIdentifier(name)) {
+ return new Error(`${name} is not a valid javascript identifier.`);
+ }
+
+ // Add binding call
+ pathToBinding[name] = function () {
+
+ // No timeout by default
+ var timeout = 0;
+
+ // Actual function
+ function dynamic() {
+ var args = [].slice.call(arguments);
+ return Call(bindingName, args, timeout);
+ }
+
+ // Allow setting timeout to function
+ dynamic.setTimeout = function (newTimeout) {
+ timeout = newTimeout;
+ };
+
+ // Allow getting timeout to function
+ dynamic.getTimeout = function () {
+ return timeout;
+ };
+
+ return dynamic;
+ }();
+}
diff --git a/runtime/js/core/browser.js b/runtime/js/core/browser.js
new file mode 100644
index 000000000..ccbe9f284
--- /dev/null
+++ b/runtime/js/core/browser.js
@@ -0,0 +1,34 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+import { SystemCall } from './calls';
+
+/**
+ * Opens the given URL in the system browser
+ *
+ * @export
+ * @param {string} url
+ * @returns
+ */
+export function OpenURL(url) {
+ return SystemCall('Browser.OpenURL', url);
+}
+
+/**
+ * Opens the given filename using the system's default file handler
+ *
+ * @export
+ * @param {sting} filename
+ * @returns
+ */
+export function OpenFile(filename) {
+ return SystemCall('Browser.OpenFile', filename);
+}
diff --git a/runtime/js/core/calls.js b/runtime/js/core/calls.js
new file mode 100644
index 000000000..ea3128e8a
--- /dev/null
+++ b/runtime/js/core/calls.js
@@ -0,0 +1,158 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+import { Debug } from './log';
+import { SendMessage } from './ipc';
+
+var callbacks = {};
+
+/**
+ * Returns a number from the native browser random function
+ *
+ * @returns number
+ */
+function cryptoRandom() {
+ var array = new Uint32Array(1);
+ return window.crypto.getRandomValues(array)[0];
+}
+
+/**
+ * Returns a number using da old-skool Math.Random
+ * I likes to call it LOLRandom
+ *
+ * @returns number
+ */
+function basicRandom() {
+ return Math.random() * 9007199254740991;
+}
+
+// Pick a random number function based on browser capability
+var randomFunc;
+if (window.crypto) {
+ randomFunc = cryptoRandom;
+} else {
+ randomFunc = basicRandom;
+}
+
+
+
+
+/**
+ * Call sends a message to the backend to call the binding with the
+ * given data. A promise is returned and will be completed when the
+ * backend responds. This will be resolved when the call was successful
+ * or rejected if an error is passed back.
+ * There is a timeout mechanism. If the call doesn't respond in the given
+ * time (in milliseconds) then the promise is rejected.
+ *
+ * @export
+ * @param {string} bindingName
+ * @param {string} data
+ * @param {number=} timeout
+ * @returns
+ */
+export function Call(bindingName, data, timeout) {
+
+ // Timeout infinite by default
+ if (timeout == null || timeout == undefined) {
+ timeout = 0;
+ }
+
+ // Create a promise
+ return new Promise(function (resolve, reject) {
+
+ // Create a unique callbackID
+ var callbackID;
+ do {
+ callbackID = bindingName + '-' + randomFunc();
+ } while (callbacks[callbackID]);
+
+ // Set timeout
+ if (timeout > 0) {
+ var timeoutHandle = setTimeout(function () {
+ reject(Error('Call to ' + bindingName + ' timed out. Request ID: ' + callbackID));
+ }, timeout);
+ }
+
+ // Store callback
+ callbacks[callbackID] = {
+ timeoutHandle: timeoutHandle,
+ reject: reject,
+ resolve: resolve
+ };
+
+ try {
+ const payload = {
+ bindingName: bindingName,
+ data: JSON.stringify(data),
+ };
+
+ // Make the call
+ SendMessage('call', payload, callbackID);
+ } catch (e) {
+ // eslint-disable-next-line
+ console.error(e);
+ }
+ });
+}
+
+
+
+/**
+ * Called by the backend to return data to a previously called
+ * binding invocation
+ *
+ * @export
+ * @param {string} incomingMessage
+ */
+export function Callback(incomingMessage) {
+
+ // Decode the message - Credit: https://stackoverflow.com/a/13865680
+ incomingMessage = decodeURIComponent(incomingMessage.replace(/\s+/g, '').replace(/[0-9a-f]{2}/g, '%$&'));
+
+ // Parse the message
+ var message;
+ try {
+ message = JSON.parse(incomingMessage);
+ } catch (e) {
+ const error = `Invalid JSON passed to callback: ${e.message}. Message: ${incomingMessage}`;
+ Debug(error);
+ throw new Error(error);
+ }
+ var callbackID = message.callbackid;
+ var callbackData = callbacks[callbackID];
+ if (!callbackData) {
+ const error = `Callback '${callbackID}' not registed!!!`;
+ console.error(error); // eslint-disable-line
+ throw new Error(error);
+ }
+ clearTimeout(callbackData.timeoutHandle);
+
+ delete callbacks[callbackID];
+
+ if (message.error) {
+ callbackData.reject(message.error);
+ } else {
+ callbackData.resolve(message.data);
+ }
+}
+
+/**
+ * SystemCall is used to call wails methods from the frontend
+ *
+ * @export
+ * @param {string} method
+ * @param {any[]=} data
+ * @returns
+ */
+export function SystemCall(method, data) {
+ return Call('.wails.' + method, data);
+}
\ No newline at end of file
diff --git a/runtime/js/core/events.js b/runtime/js/core/events.js
new file mode 100644
index 000000000..81a663bdc
--- /dev/null
+++ b/runtime/js/core/events.js
@@ -0,0 +1,194 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+import { Error } from './log';
+import { SendMessage } from './ipc';
+
+// Defines a single listener with a maximum number of times to callback
+/**
+ * The Listener class defines a listener! :-)
+ *
+ * @class Listener
+ */
+class Listener {
+ /**
+ * Creates an instance of Listener.
+ * @param {function} callback
+ * @param {number} maxCallbacks
+ * @memberof Listener
+ */
+ constructor(callback, maxCallbacks) {
+ // Default of -1 means infinite
+ maxCallbacks = maxCallbacks || -1;
+ // Callback invokes the callback with the given data
+ // Returns true if this listener should be destroyed
+ this.Callback = (data) => {
+ callback.apply(null, data);
+ // If maxCallbacks is infinite, return false (do not destroy)
+ if (maxCallbacks === -1) {
+ return false;
+ }
+ // Decrement maxCallbacks. Return true if now 0, otherwise false
+ maxCallbacks -= 1;
+ return maxCallbacks === 0;
+ };
+ }
+}
+
+var eventListeners = {};
+
+/**
+ * Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
+ *
+ * @export
+ * @param {string} eventName
+ * @param {function} callback
+ * @param {number} maxCallbacks
+ */
+export function OnMultiple(eventName, callback, maxCallbacks) {
+ eventListeners[eventName] = eventListeners[eventName] || [];
+ const thisListener = new Listener(callback, maxCallbacks);
+ eventListeners[eventName].push(thisListener);
+}
+
+/**
+ * Registers an event listener that will be invoked every time the event is emitted
+ *
+ * @export
+ * @param {string} eventName
+ * @param {function} callback
+ */
+export function On(eventName, callback) {
+ OnMultiple(eventName, callback);
+}
+
+/**
+ * Registers an event listener that will be invoked once then destroyed
+ *
+ * @export
+ * @param {string} eventName
+ * @param {function} callback
+ */
+export function Once(eventName, callback) {
+ OnMultiple(eventName, callback, 1);
+}
+
+/**
+ * Notify informs frontend listeners that an event was emitted with the given data
+ *
+ * @export
+ * @param {string} eventName
+ * @param {string} data
+ */
+export function Notify(eventName, data) {
+
+ // Check if we have any listeners for this event
+ if (eventListeners[eventName]) {
+
+ // Keep a list of listener indexes to destroy
+ const newEventListenerList = eventListeners[eventName].slice();
+
+ // Iterate listeners
+ for (let count = 0; count < eventListeners[eventName].length; count += 1) {
+
+ // Get next listener
+ const listener = eventListeners[eventName][count];
+
+ // Parse data if we have it
+ var parsedData = [];
+ if (data) {
+ try {
+ parsedData = JSON.parse(data);
+ } catch (e) {
+ Error('Invalid JSON data sent to notify. Event name = ' + eventName);
+ }
+ }
+ // Do the callback
+ const destroy = listener.Callback(parsedData);
+ if (destroy) {
+ // if the listener indicated to destroy itself, add it to the destroy list
+ newEventListenerList.splice(count, 1);
+ }
+ }
+
+ // Update callbacks with new list of listners
+ eventListeners[eventName] = newEventListenerList;
+ }
+}
+
+/**
+ * Emit an event with the given name and data
+ *
+ * @export
+ * @param {string} eventName
+ */
+export function Emit(eventName) {
+
+ // Calculate the data
+ var data = JSON.stringify([].slice.apply(arguments).slice(1));
+
+ // Notify backend
+ const payload = {
+ name: eventName,
+ data: data,
+ };
+ SendMessage('event', payload);
+}
+
+// Callbacks for the heartbeat calls
+const heartbeatCallbacks = {};
+
+/**
+ * Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
+ * the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
+ *
+ * @export
+ * @param {string} eventName
+ * @param {number} timeInMilliseconds
+ * @param {function} callback
+ */
+export function Heartbeat(eventName, timeInMilliseconds, callback) {
+
+ // Declare interval variable
+ let interval = null;
+
+ // Setup callback
+ function dynamicCallback() {
+ // Kill interval
+ clearInterval(interval);
+ // Callback
+ callback();
+ }
+
+ // Register callback
+ heartbeatCallbacks[eventName] = dynamicCallback;
+
+ // Start emitting the event
+ interval = setInterval(function () {
+ Emit(eventName);
+ }, timeInMilliseconds);
+}
+
+/**
+ * Acknowledges a heartbeat event by name
+ *
+ * @export
+ * @param {string} eventName
+ */
+export function Acknowledge(eventName) {
+ // If we are waiting for acknowledgement for this event type
+ if (heartbeatCallbacks[eventName]) {
+ // Acknowledge!
+ heartbeatCallbacks[eventName]();
+ } else {
+ throw new Error(`Cannot acknowledge unknown heartbeat '${eventName}'`);
+ }
+}
\ No newline at end of file
diff --git a/runtime/js/core/ipc.js b/runtime/js/core/ipc.js
new file mode 100644
index 000000000..1285255a7
--- /dev/null
+++ b/runtime/js/core/ipc.js
@@ -0,0 +1,59 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+// IPC Listeners
+var listeners = [];
+
+/**
+ * Adds a listener to IPC messages
+ * @param {function} callback
+ */
+export function AddIPCListener(callback) {
+ listeners.push(callback);
+}
+
+/**
+ * Invoke sends the given message to the backend
+ *
+ * @param {string} message
+ */
+function Invoke(message) {
+ if (window.wailsbridge) {
+ window.wailsbridge.websocket.send(message);
+ } else {
+ window.external.invoke(message);
+ }
+
+ // Also send to listeners
+ if (listeners.length > 0) {
+ for (var i = 0; i < listeners.length; i++) {
+ listeners[i](message);
+ }
+ }
+}
+
+/**
+ * Sends a message to the backend based on the given type, payload and callbackID
+ *
+ * @export
+ * @param {string} type
+ * @param {string} payload
+ * @param {string=} callbackID
+ */
+export function SendMessage(type, payload, callbackID) {
+ const message = {
+ type,
+ callbackID,
+ payload
+ };
+
+ Invoke(JSON.stringify(message));
+}
diff --git a/runtime/js/core/log.js b/runtime/js/core/log.js
new file mode 100644
index 000000000..e113e8731
--- /dev/null
+++ b/runtime/js/core/log.js
@@ -0,0 +1,80 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+
+/* jshint esversion: 6 */
+
+import { SendMessage } from './ipc';
+
+/**
+ * Sends a log message to the backend with the given level + message
+ *
+ * @param {string} level
+ * @param {string} message
+ */
+function sendLogMessage(level, message) {
+
+ // Log Message
+ const payload = {
+ level: level,
+ message: message,
+ };
+ SendMessage('log', payload);
+}
+
+/**
+ * Log the given debug message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+export function Debug(message) {
+ sendLogMessage('debug', message);
+}
+
+/**
+ * Log the given info message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+export function Info(message) {
+ sendLogMessage('info', message);
+}
+
+/**
+ * Log the given warning message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+export function Warning(message) {
+ sendLogMessage('warning', message);
+}
+
+/**
+ * Log the given error message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+export function Error(message) {
+ sendLogMessage('error', message);
+}
+
+/**
+ * Log the given fatal message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+export function Fatal(message) {
+ sendLogMessage('fatal', message);
+}
+
diff --git a/runtime/js/core/main.js b/runtime/js/core/main.js
new file mode 100644
index 000000000..f93ed2866
--- /dev/null
+++ b/runtime/js/core/main.js
@@ -0,0 +1,74 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+import * as Log from './log';
+import * as Browser from './browser';
+import { On, OnMultiple, Emit, Notify, Heartbeat, Acknowledge } from './events';
+import { NewBinding } from './bindings';
+import { Callback } from './calls';
+import { AddScript, InjectCSS, InjectFirebug } from './utils';
+import { AddIPCListener } from './ipc';
+import * as Store from './store';
+
+// Initialise global if not already
+window.wails = window.wails || {};
+window.backend = {};
+
+// Setup internal calls
+var internal = {
+ NewBinding,
+ Callback,
+ Notify,
+ AddScript,
+ InjectCSS,
+ Init,
+ AddIPCListener,
+};
+
+// Setup runtime structure
+var runtime = {
+ Log,
+ Browser,
+ Events: {
+ On,
+ OnMultiple,
+ Emit,
+ Heartbeat,
+ Acknowledge,
+ },
+ Store,
+ _: internal,
+};
+
+// Augment global
+Object.assign(window.wails, runtime);
+
+// Setup global error handler
+window.onerror = function (msg, url, lineNo, columnNo, error) {
+ window.wails.Log.Error('**** Caught Unhandled Error ****');
+ window.wails.Log.Error('Message: ' + msg);
+ window.wails.Log.Error('URL: ' + url);
+ window.wails.Log.Error('Line No: ' + lineNo);
+ window.wails.Log.Error('Column No: ' + columnNo);
+ window.wails.Log.Error('error: ' + error);
+};
+
+// Use firebug?
+if( window.usefirebug ) {
+ InjectFirebug();
+}
+
+// Emit loaded event
+Emit('wails:loaded');
+
+// Nothing to init in production
+export function Init(callback) {
+ callback();
+}
\ No newline at end of file
diff --git a/runtime/js/core/store.js b/runtime/js/core/store.js
new file mode 100644
index 000000000..58e471781
--- /dev/null
+++ b/runtime/js/core/store.js
@@ -0,0 +1,82 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+/**
+ * Creates a new sync store with the given name and optional default value
+ *
+ * @export
+ * @param {string} name
+ * @param {*} optionalDefault
+ */
+export function New(name, optionalDefault) {
+
+ var data;
+
+ // Check we are initialised
+ if( !window.wails) {
+ throw Error('Wails is not initialised');
+ }
+
+ // Store for the callbacks
+ let callbacks = [];
+
+ // Subscribe to updates by providing a callback
+ this.subscribe = (callback) => {
+ callbacks.push(callback);
+ };
+
+ // sets the store data to the provided `newdata` value
+ this.set = (newdata) => {
+
+ data = newdata;
+
+ // Emit a notification to back end
+ window.wails.Events.Emit('wails:sync:store:updatedbyfrontend:'+name, JSON.stringify(data));
+
+ // Notify callbacks
+ callbacks.forEach( function(callback) {
+ callback(data);
+ });
+ };
+
+ // update mutates the value in the store by calling the
+ // provided method with the current value. The value returned
+ // by the updater function will be set as the new store value
+ this.update = (updater) => {
+ var newValue = updater(data);
+ this.set(newValue);
+ };
+
+ // Setup event callback
+ window.wails.Events.On('wails:sync:store:updatedbybackend:'+name, function(result) {
+
+ // Parse data
+ result = JSON.parse(result);
+
+ // Todo: Potential preprocessing?
+
+ // Save data
+ data = result;
+
+ // Notify callbacks
+ callbacks.forEach( function(callback) {
+ callback(data);
+ });
+
+ });
+
+ // Set to the optional default if set
+ if( optionalDefault ) {
+ this.set(optionalDefault);
+ }
+
+ return this;
+}
\ No newline at end of file
diff --git a/runtime/js/core/utils.js b/runtime/js/core/utils.js
new file mode 100644
index 000000000..425fe1f04
--- /dev/null
+++ b/runtime/js/core/utils.js
@@ -0,0 +1,46 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+import { Emit } from './events';
+
+export function AddScript(js, callbackID) {
+ var script = document.createElement('script');
+ script.text = js;
+ document.body.appendChild(script);
+ if (callbackID) {
+ Emit(callbackID);
+ }
+}
+
+export function InjectFirebug() {
+ // set the debug attribute on HTML
+ var html = document.getElementsByTagName('html')[0];
+ html.setAttribute('debug', 'true');
+ var firebugURL = 'https://wails.app/assets/js/firebug-lite.js#startOpened=true,disableWhenFirebugActive=false';
+ var script = document.createElement('script');
+ script.src = firebugURL;
+ script.type = 'application/javascript';
+ document.head.appendChild(script);
+ window.wails.Log.Info('Injected firebug');
+}
+
+// Adapted from webview - thanks zserge!
+export function InjectCSS(css) {
+ var elem = document.createElement('style');
+ elem.setAttribute('type', 'text/css');
+ if (elem.styleSheet) {
+ elem.styleSheet.cssText = css;
+ } else {
+ elem.appendChild(document.createTextNode(css));
+ }
+ var head = document.head || document.getElementsByTagName('head')[0];
+ head.appendChild(elem);
+}
diff --git a/runtime/js/package-lock.json b/runtime/js/package-lock.json
new file mode 100644
index 000000000..13881aa46
--- /dev/null
+++ b/runtime/js/package-lock.json
@@ -0,0 +1,6633 @@
+{
+ "name": "wails-runtime",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/cli": {
+ "version": "7.11.6",
+ "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.11.6.tgz",
+ "integrity": "sha512-+w7BZCvkewSmaRM6H4L2QM3RL90teqEIHDIFXAmrW33+0jhlymnDAEdqVeCZATvxhQuio1ifoGVlJJbIiH9Ffg==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^2.1.8",
+ "commander": "^4.0.1",
+ "convert-source-map": "^1.1.0",
+ "fs-readdir-recursive": "^1.1.0",
+ "glob": "^7.0.0",
+ "lodash": "^4.17.19",
+ "make-dir": "^2.1.0",
+ "slash": "^2.0.0",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz",
+ "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.12.0",
+ "invariant": "^2.2.4",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/core": {
+ "version": "7.11.6",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
+ "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/generator": "^7.11.6",
+ "@babel/helper-module-transforms": "^7.11.0",
+ "@babel/helpers": "^7.10.4",
+ "@babel/parser": "^7.11.5",
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.11.5",
+ "@babel/types": "^7.11.5",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.1",
+ "json5": "^2.1.2",
+ "lodash": "^4.17.19",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.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
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.11.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz",
+ "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.5",
+ "jsesc": "^2.5.1",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
+ "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-builder-binary-assignment-operator-visitor": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz",
+ "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-explode-assignable-expression": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz",
+ "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.10.4",
+ "browserslist": "^4.12.0",
+ "invariant": "^2.2.4",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz",
+ "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-member-expression-to-functions": "^7.10.5",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.10.4"
+ }
+ },
+ "@babel/helper-create-regexp-features-plugin": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz",
+ "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-regex": "^7.10.4",
+ "regexpu-core": "^4.7.0"
+ }
+ },
+ "@babel/helper-define-map": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz",
+ "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/types": "^7.10.5",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-explode-assignable-expression": {
+ "version": "7.11.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz",
+ "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
+ "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
+ "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz",
+ "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz",
+ "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz",
+ "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz",
+ "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4",
+ "@babel/helper-simple-access": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.11.0",
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz",
+ "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
+ "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
+ "dev": true
+ },
+ "@babel/helper-regex": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz",
+ "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.19"
+ }
+ },
+ "@babel/helper-remap-async-to-generator": {
+ "version": "7.11.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz",
+ "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-wrap-function": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz",
+ "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.10.4",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/traverse": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz",
+ "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz",
+ "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
+ "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.11.0"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+ "dev": true
+ },
+ "@babel/helper-wrap-function": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz",
+ "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz",
+ "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.10.4",
+ "@babel/traverse": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+ "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz",
+ "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==",
+ "dev": true
+ },
+ "@babel/plugin-proposal-async-generator-functions": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz",
+ "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-remap-async-to-generator": "^7.10.4",
+ "@babel/plugin-syntax-async-generators": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-class-properties": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz",
+ "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-dynamic-import": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz",
+ "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-export-namespace-from": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz",
+ "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3"
+ }
+ },
+ "@babel/plugin-proposal-json-strings": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz",
+ "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-logical-assignment-operators": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz",
+ "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
+ },
+ "regexpu-core": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz",
+ "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==",
+ "requires": {
+ "regenerate": "^1.4.0",
+ "regenerate-unicode-properties": "^8.0.2",
+ "regjsgen": "^0.5.0",
+ "regjsparser": "^0.6.0",
+ "unicode-match-property-ecmascript": "^1.0.4",
+ "unicode-match-property-value-ecmascript": "^1.1.0"
+ },
+ "dependencies": {
+ "regenerate": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
+ "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A=="
+ },
+ "regenerate-unicode-properties": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
+ "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
+ "requires": {
+ "regenerate": "^1.4.0"
+ }
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^1.0.4",
+ "unicode-property-aliases-ecmascript": "^1.0.4"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
+ "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ=="
+ }
+ }
+ },
+ "regjsgen": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+ "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA=="
+ },
+ "regjsparser": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+ "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+ "requires": {
+ "jsesc": "~0.5.0"
+ }
+ }
+ }
+ },
+ "@babel/plugin-proposal-nullish-coalescing-operator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz",
+ "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-object-rest-spread": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz",
+ "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+ "@babel/plugin-transform-parameters": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-optional-catch-binding": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz",
+ "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-optional-chaining": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz",
+ "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+ }
+ },
+ "@babel/plugin-proposal-private-methods": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz",
+ "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-proposal-unicode-property-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz",
+ "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-class-properties": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz",
+ "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-export-namespace-from": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
+ "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.3"
+ }
+ },
+ "@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ }
+ },
+ "@babel/plugin-syntax-top-level-await": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz",
+ "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-arrow-functions": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz",
+ "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz",
+ "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-remap-async-to-generator": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz",
+ "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.11.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz",
+ "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz",
+ "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-define-map": "^7.10.4",
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-optimise-call-expression": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.10.4",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/plugin-transform-computed-properties": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz",
+ "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz",
+ "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz",
+ "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz",
+ "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz",
+ "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-for-of": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz",
+ "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-function-name": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz",
+ "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-literals": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz",
+ "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz",
+ "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz",
+ "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.10.5",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "babel-plugin-dynamic-import-node": "^2.3.3"
+ }
+ },
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz",
+ "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-simple-access": "^7.10.4",
+ "babel-plugin-dynamic-import-node": "^2.3.3"
+ }
+ },
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz",
+ "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.10.4",
+ "@babel/helper-module-transforms": "^7.10.5",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "babel-plugin-dynamic-import-node": "^2.3.3"
+ }
+ },
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz",
+ "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz",
+ "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-new-target": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz",
+ "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-object-assign": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.10.4.tgz",
+ "integrity": "sha512-6zccDhYEICfMeQqIjuY5G09/yhKzG30DKHJeYBQUHIsJH7c2jXSGvgwRalufLAXAq432OSlsEfAOLlzEsQzxVw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-object-super": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz",
+ "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-replace-supers": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-parameters": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz",
+ "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-property-literals": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz",
+ "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-regenerator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz",
+ "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==",
+ "dev": true,
+ "requires": {
+ "regenerator-transform": "^0.14.2"
+ }
+ },
+ "@babel/plugin-transform-reserved-words": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz",
+ "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-shorthand-properties": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz",
+ "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-spread": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz",
+ "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0"
+ }
+ },
+ "@babel/plugin-transform-sticky-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz",
+ "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/helper-regex": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-template-literals": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz",
+ "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-typeof-symbol": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz",
+ "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-unicode-escapes": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz",
+ "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/plugin-transform-unicode-regex": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz",
+ "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-regexp-features-plugin": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4"
+ }
+ },
+ "@babel/preset-env": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz",
+ "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.11.0",
+ "@babel/helper-compilation-targets": "^7.10.4",
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-proposal-async-generator-functions": "^7.10.4",
+ "@babel/plugin-proposal-class-properties": "^7.10.4",
+ "@babel/plugin-proposal-dynamic-import": "^7.10.4",
+ "@babel/plugin-proposal-export-namespace-from": "^7.10.4",
+ "@babel/plugin-proposal-json-strings": "^7.10.4",
+ "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0",
+ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
+ "@babel/plugin-proposal-numeric-separator": "^7.10.4",
+ "@babel/plugin-proposal-object-rest-spread": "^7.11.0",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.10.4",
+ "@babel/plugin-proposal-optional-chaining": "^7.11.0",
+ "@babel/plugin-proposal-private-methods": "^7.10.4",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.10.4",
+ "@babel/plugin-syntax-async-generators": "^7.8.0",
+ "@babel/plugin-syntax-class-properties": "^7.10.4",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.0",
+ "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+ "@babel/plugin-syntax-json-strings": "^7.8.0",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.0",
+ "@babel/plugin-syntax-top-level-await": "^7.10.4",
+ "@babel/plugin-transform-arrow-functions": "^7.10.4",
+ "@babel/plugin-transform-async-to-generator": "^7.10.4",
+ "@babel/plugin-transform-block-scoped-functions": "^7.10.4",
+ "@babel/plugin-transform-block-scoping": "^7.10.4",
+ "@babel/plugin-transform-classes": "^7.10.4",
+ "@babel/plugin-transform-computed-properties": "^7.10.4",
+ "@babel/plugin-transform-destructuring": "^7.10.4",
+ "@babel/plugin-transform-dotall-regex": "^7.10.4",
+ "@babel/plugin-transform-duplicate-keys": "^7.10.4",
+ "@babel/plugin-transform-exponentiation-operator": "^7.10.4",
+ "@babel/plugin-transform-for-of": "^7.10.4",
+ "@babel/plugin-transform-function-name": "^7.10.4",
+ "@babel/plugin-transform-literals": "^7.10.4",
+ "@babel/plugin-transform-member-expression-literals": "^7.10.4",
+ "@babel/plugin-transform-modules-amd": "^7.10.4",
+ "@babel/plugin-transform-modules-commonjs": "^7.10.4",
+ "@babel/plugin-transform-modules-systemjs": "^7.10.4",
+ "@babel/plugin-transform-modules-umd": "^7.10.4",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4",
+ "@babel/plugin-transform-new-target": "^7.10.4",
+ "@babel/plugin-transform-object-super": "^7.10.4",
+ "@babel/plugin-transform-parameters": "^7.10.4",
+ "@babel/plugin-transform-property-literals": "^7.10.4",
+ "@babel/plugin-transform-regenerator": "^7.10.4",
+ "@babel/plugin-transform-reserved-words": "^7.10.4",
+ "@babel/plugin-transform-shorthand-properties": "^7.10.4",
+ "@babel/plugin-transform-spread": "^7.11.0",
+ "@babel/plugin-transform-sticky-regex": "^7.10.4",
+ "@babel/plugin-transform-template-literals": "^7.10.4",
+ "@babel/plugin-transform-typeof-symbol": "^7.10.4",
+ "@babel/plugin-transform-unicode-escapes": "^7.10.4",
+ "@babel/plugin-transform-unicode-regex": "^7.10.4",
+ "@babel/preset-modules": "^0.1.3",
+ "@babel/types": "^7.11.5",
+ "browserslist": "^4.12.0",
+ "core-js-compat": "^3.6.2",
+ "invariant": "^2.2.2",
+ "levenary": "^1.1.1",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/preset-modules": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz",
+ "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
+ "@babel/plugin-transform-dotall-regex": "^7.4.4",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ },
+ "@babel/template": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
+ "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/parser": "^7.10.4",
+ "@babel/types": "^7.10.4"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz",
+ "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/generator": "^7.11.5",
+ "@babel/helper-function-name": "^7.10.4",
+ "@babel/helper-split-export-declaration": "^7.11.0",
+ "@babel/parser": "^7.11.5",
+ "@babel/types": "^7.11.5",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.19"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.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
+ }
+ }
+ },
+ "@babel/types": {
+ "version": "7.11.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz",
+ "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "lodash": "^4.17.19",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@eslint/eslintrc": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
+ "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.1.1",
+ "espree": "^7.3.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^3.13.1",
+ "lodash": "^4.17.19",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "globals": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.8.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
+ }
+ }
+ },
+ "@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
+ },
+ "@types/json-schema": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
+ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
+ "dev": true
+ },
+ "@webassemblyjs/ast": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
+ "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0"
+ }
+ },
+ "@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz",
+ "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-api-error": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz",
+ "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-buffer": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz",
+ "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-code-frame": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz",
+ "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/wast-printer": "1.9.0"
+ }
+ },
+ "@webassemblyjs/helper-fsm": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz",
+ "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-module-context": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz",
+ "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0"
+ },
+ "dependencies": {
+ "@webassemblyjs/ast": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
+ "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0"
+ }
+ },
+ "@webassemblyjs/helper-module-context": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz",
+ "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g=="
+ }
+ }
+ },
+ "@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz",
+ "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-wasm-section": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz",
+ "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0"
+ }
+ },
+ "@webassemblyjs/ieee754": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz",
+ "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==",
+ "dev": true,
+ "requires": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "@webassemblyjs/leb128": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz",
+ "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==",
+ "dev": true,
+ "requires": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/utf8": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz",
+ "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==",
+ "dev": true
+ },
+ "@webassemblyjs/wasm-edit": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz",
+ "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/helper-wasm-section": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0",
+ "@webassemblyjs/wasm-opt": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0",
+ "@webassemblyjs/wast-printer": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wasm-gen": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz",
+ "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/ieee754": "1.9.0",
+ "@webassemblyjs/leb128": "1.9.0",
+ "@webassemblyjs/utf8": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wasm-opt": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz",
+ "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wasm-parser": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz",
+ "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-api-error": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/ieee754": "1.9.0",
+ "@webassemblyjs/leb128": "1.9.0",
+ "@webassemblyjs/utf8": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wast-parser": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz",
+ "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/floating-point-hex-parser": "1.9.0",
+ "@webassemblyjs/helper-api-error": "1.9.0",
+ "@webassemblyjs/helper-code-frame": "1.9.0",
+ "@webassemblyjs/helper-fsm": "1.9.0",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/wast-printer": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz",
+ "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
+ },
+ "@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
+ },
+ "acorn": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+ "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
+ "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
+ "dev": true
+ },
+ "ajv": {
+ "version": "6.12.4",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
+ "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-errors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+ "dev": true
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true
+ },
+ "ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ }
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+ "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.1",
+ "util": "0.10.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ }
+ }
+ }
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "dev": true
+ },
+ "async-each": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
+ "dev": true,
+ "optional": true
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "dev": true
+ },
+ "babel-helper-evaluate-path": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz",
+ "integrity": "sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==",
+ "dev": true
+ },
+ "babel-helper-flip-expressions": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz",
+ "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=",
+ "dev": true
+ },
+ "babel-helper-is-nodes-equiv": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz",
+ "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=",
+ "dev": true
+ },
+ "babel-helper-is-void-0": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz",
+ "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=",
+ "dev": true
+ },
+ "babel-helper-mark-eval-scopes": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz",
+ "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=",
+ "dev": true
+ },
+ "babel-helper-remove-or-void": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz",
+ "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=",
+ "dev": true
+ },
+ "babel-helper-to-multiple-sequence-expressions": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz",
+ "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==",
+ "dev": true
+ },
+ "babel-loader": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
+ "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==",
+ "dev": true,
+ "requires": {
+ "find-cache-dir": "^2.1.0",
+ "loader-utils": "^1.4.0",
+ "mkdirp": "^0.5.3",
+ "pify": "^4.0.1",
+ "schema-utils": "^2.6.5"
+ }
+ },
+ "babel-plugin-dynamic-import-node": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
+ "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
+ "dev": true,
+ "requires": {
+ "object.assign": "^4.1.0"
+ }
+ },
+ "babel-plugin-minify-builtins": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz",
+ "integrity": "sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==",
+ "dev": true
+ },
+ "babel-plugin-minify-constant-folding": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz",
+ "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==",
+ "dev": true,
+ "requires": {
+ "babel-helper-evaluate-path": "^0.5.0"
+ }
+ },
+ "babel-plugin-minify-dead-code-elimination": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz",
+ "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==",
+ "dev": true,
+ "requires": {
+ "babel-helper-evaluate-path": "^0.5.0",
+ "babel-helper-mark-eval-scopes": "^0.4.3",
+ "babel-helper-remove-or-void": "^0.4.3",
+ "lodash": "^4.17.11"
+ }
+ },
+ "babel-plugin-minify-flip-comparisons": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz",
+ "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=",
+ "dev": true,
+ "requires": {
+ "babel-helper-is-void-0": "^0.4.3"
+ }
+ },
+ "babel-plugin-minify-guarded-expressions": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz",
+ "integrity": "sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==",
+ "dev": true,
+ "requires": {
+ "babel-helper-evaluate-path": "^0.5.0",
+ "babel-helper-flip-expressions": "^0.4.3"
+ }
+ },
+ "babel-plugin-minify-infinity": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz",
+ "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=",
+ "dev": true
+ },
+ "babel-plugin-minify-mangle-names": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz",
+ "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==",
+ "dev": true,
+ "requires": {
+ "babel-helper-mark-eval-scopes": "^0.4.3"
+ }
+ },
+ "babel-plugin-minify-numeric-literals": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz",
+ "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=",
+ "dev": true
+ },
+ "babel-plugin-minify-replace": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz",
+ "integrity": "sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==",
+ "dev": true
+ },
+ "babel-plugin-minify-simplify": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz",
+ "integrity": "sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==",
+ "dev": true,
+ "requires": {
+ "babel-helper-evaluate-path": "^0.5.0",
+ "babel-helper-flip-expressions": "^0.4.3",
+ "babel-helper-is-nodes-equiv": "^0.0.1",
+ "babel-helper-to-multiple-sequence-expressions": "^0.5.0"
+ }
+ },
+ "babel-plugin-minify-type-constructors": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz",
+ "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=",
+ "dev": true,
+ "requires": {
+ "babel-helper-is-void-0": "^0.4.3"
+ }
+ },
+ "babel-plugin-transform-inline-consecutive-adds": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz",
+ "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=",
+ "dev": true
+ },
+ "babel-plugin-transform-member-expression-literals": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz",
+ "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=",
+ "dev": true
+ },
+ "babel-plugin-transform-merge-sibling-variables": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz",
+ "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=",
+ "dev": true
+ },
+ "babel-plugin-transform-minify-booleans": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz",
+ "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=",
+ "dev": true
+ },
+ "babel-plugin-transform-property-literals": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz",
+ "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "babel-plugin-transform-regexp-constructors": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz",
+ "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=",
+ "dev": true
+ },
+ "babel-plugin-transform-remove-console": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz",
+ "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=",
+ "dev": true
+ },
+ "babel-plugin-transform-remove-debugger": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz",
+ "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=",
+ "dev": true
+ },
+ "babel-plugin-transform-remove-undefined": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz",
+ "integrity": "sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==",
+ "dev": true,
+ "requires": {
+ "babel-helper-evaluate-path": "^0.5.0"
+ }
+ },
+ "babel-plugin-transform-simplify-comparison-operators": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz",
+ "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=",
+ "dev": true
+ },
+ "babel-plugin-transform-undefined-to-void": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz",
+ "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=",
+ "dev": true
+ },
+ "babel-preset-minify": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz",
+ "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==",
+ "dev": true,
+ "requires": {
+ "babel-plugin-minify-builtins": "^0.5.0",
+ "babel-plugin-minify-constant-folding": "^0.5.0",
+ "babel-plugin-minify-dead-code-elimination": "^0.5.1",
+ "babel-plugin-minify-flip-comparisons": "^0.4.3",
+ "babel-plugin-minify-guarded-expressions": "^0.4.4",
+ "babel-plugin-minify-infinity": "^0.4.3",
+ "babel-plugin-minify-mangle-names": "^0.5.0",
+ "babel-plugin-minify-numeric-literals": "^0.4.3",
+ "babel-plugin-minify-replace": "^0.5.0",
+ "babel-plugin-minify-simplify": "^0.5.1",
+ "babel-plugin-minify-type-constructors": "^0.4.3",
+ "babel-plugin-transform-inline-consecutive-adds": "^0.4.3",
+ "babel-plugin-transform-member-expression-literals": "^6.9.4",
+ "babel-plugin-transform-merge-sibling-variables": "^6.9.4",
+ "babel-plugin-transform-minify-booleans": "^6.9.4",
+ "babel-plugin-transform-property-literals": "^6.9.4",
+ "babel-plugin-transform-regexp-constructors": "^0.4.3",
+ "babel-plugin-transform-remove-console": "^6.9.4",
+ "babel-plugin-transform-remove-debugger": "^6.9.4",
+ "babel-plugin-transform-remove-undefined": "^0.5.0",
+ "babel-plugin-transform-simplify-comparison-operators": "^6.9.4",
+ "babel-plugin-transform-undefined-to-void": "^6.9.4",
+ "lodash": "^4.17.11"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "base64-js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
+ },
+ "big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+ "dev": true,
+ "optional": true
+ },
+ "bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ },
+ "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"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.1",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.2",
+ "elliptic": "^6.0.0",
+ "inherits": "^2.0.1",
+ "parse-asn1": "^5.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "browserslist": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz",
+ "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30001111",
+ "electron-to-chromium": "^1.3.523",
+ "escalade": "^3.0.2",
+ "node-releases": "^1.1.60"
+ }
+ },
+ "buffer": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001124",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz",
+ "integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ }
+ }
+ },
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
+ "chrome-trace-event": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+ "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true,
+ "requires": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "copy-concurrently": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "fs-write-stream-atomic": "^1.0.8",
+ "iferr": "^0.1.5",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.0"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "core-js": {
+ "version": "3.6.5",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
+ "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
+ "dev": true
+ },
+ "core-js-compat": {
+ "version": "3.6.5",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
+ "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.8.5",
+ "semver": "7.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+ "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+ "dev": true
+ }
+ }
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "create-ecdh": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+ "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.5.3"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "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"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "cyclist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+ "dev": true
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "des.js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+ "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "detect-file": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "electron-to-chromium": {
+ "version": "1.3.562",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.562.tgz",
+ "integrity": "sha512-WhRe6liQ2q/w1MZc8mD8INkenHivuHdrr4r5EQHNomy3NJux+incP6M6lDMd0paShP3MD0WGe5R1TWmEClf+Bg==",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+ "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
+ "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.5.0",
+ "tapable": "^1.0.0"
+ },
+ "dependencies": {
+ "memory-fs": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
+ "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ }
+ }
+ },
+ "enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^4.1.1"
+ }
+ },
+ "errno": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "dev": true,
+ "requires": {
+ "prr": "~1.0.1"
+ }
+ },
+ "escalade": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz",
+ "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "eslint": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz",
+ "integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@eslint/eslintrc": "^0.1.3",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "eslint-scope": "^5.1.0",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^1.3.0",
+ "espree": "^7.3.0",
+ "esquery": "^1.2.0",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.0.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash": "^4.17.19",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.1.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.0",
+ "strip-json-comments": "^3.1.0",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "globals": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.8.1"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "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
+ },
+ "semver": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
+ "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz",
+ "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.4.0",
+ "acorn-jsx": "^5.2.0",
+ "eslint-visitor-keys": "^1.3.0"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "esquery": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
+ "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "events": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
+ "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==",
+ "dev": true
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "expand-tilde": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1"
+ }
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "figgy-pudding": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
+ "dev": true
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "optional": true
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "findup-sync": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
+ "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
+ "dev": true,
+ "requires": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "micromatch": "^3.0.4",
+ "resolve-dir": "^1.0.1"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ }
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+ "dev": true
+ },
+ "flush-write-stream": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+ "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.3.6"
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "fs-readdir-recursive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
+ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
+ "dev": true
+ },
+ "fs-write-stream-atomic": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "iferr": "^0.1.5",
+ "imurmurhash": "^0.1.4",
+ "readable-stream": "1 || 2"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1"
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "gensync": {
+ "version": "1.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
+ "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+ "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^3.0.0"
+ },
+ "dependencies": {
+ "global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "global-prefix": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.2",
+ "homedir-polyfill": "^1.0.1",
+ "ini": "^1.3.4",
+ "is-windows": "^1.0.1",
+ "which": "^1.2.14"
+ },
+ "dependencies": {
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true
+ },
+ "graceful-fs": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
+ "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+ "dev": true,
+ "requires": {
+ "parse-passwd": "^1.0.0"
+ }
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+ "dev": true
+ },
+ "iferr": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+ "dev": true
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+ "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "import-local": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+ "dev": true,
+ "requires": {
+ "pkg-dir": "^3.0.0",
+ "resolve-cwd": "^2.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "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": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "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": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
+ "interpret": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+ "dev": true
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+ "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ },
+ "json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+ "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
+ },
+ "leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true
+ },
+ "levenary": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz",
+ "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==",
+ "dev": true,
+ "requires": {
+ "leven": "^3.1.0"
+ }
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "loader-runner": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+ "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+ "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^1.0.1"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ }
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "memory-fs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "mississippi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+ "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+ "dev": true,
+ "requires": {
+ "concat-stream": "^1.5.0",
+ "duplexify": "^3.4.2",
+ "end-of-stream": "^1.1.0",
+ "flush-write-stream": "^1.0.0",
+ "from2": "^2.1.0",
+ "parallel-transform": "^1.1.0",
+ "pump": "^3.0.0",
+ "pumpify": "^1.3.3",
+ "stream-each": "^1.1.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "mixin-deep": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "move-concurrently": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "copy-concurrently": "^1.0.0",
+ "fs-write-stream-atomic": "^1.0.8",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.3"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.14.1",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
+ "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
+ "dev": true,
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ }
+ }
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
+ },
+ "node-libs-browser": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+ "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+ "dev": true,
+ "requires": {
+ "assert": "^1.1.1",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^4.3.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "crypto-browserify": "^3.11.0",
+ "domain-browser": "^1.1.1",
+ "events": "^3.0.0",
+ "https-browserify": "^1.0.0",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "0.0.1",
+ "process": "^0.11.10",
+ "punycode": "^1.2.4",
+ "querystring-es3": "^0.2.0",
+ "readable-stream": "^2.3.3",
+ "stream-browserify": "^2.0.1",
+ "stream-http": "^2.7.2",
+ "string_decoder": "^1.0.0",
+ "timers-browserify": "^2.0.4",
+ "tty-browserify": "0.0.0",
+ "url": "^0.11.0",
+ "util": "^0.11.0",
+ "vm-browserify": "^1.0.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ }
+ }
+ },
+ "node-releases": {
+ "version": "1.1.60",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz",
+ "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+ }
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "object.assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "function-bind": "^1.1.1",
+ "has-symbols": "^1.0.0",
+ "object-keys": "^1.0.11"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "dependencies": {
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ }
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
+ },
+ "parallel-transform": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+ "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+ "dev": true,
+ "requires": {
+ "cyclist": "^1.0.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.1.5"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+ "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^5.2.0",
+ "browserify-aes": "^1.0.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+ "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true,
+ "optional": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "pbkdf2": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+ "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "picomatch": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "dev": true,
+ "optional": true
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "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": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+ "dev": true
+ },
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "regenerate": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
+ "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==",
+ "dev": true
+ },
+ "regenerate-unicode-properties": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
+ "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "dev": true
+ },
+ "regenerator-transform": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz",
+ "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.4"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "regexpp": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
+ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+ "dev": true
+ },
+ "regexpu-core": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
+ "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0",
+ "regenerate-unicode-properties": "^8.2.0",
+ "regjsgen": "^0.5.1",
+ "regjsparser": "^0.6.4",
+ "unicode-match-property-ecmascript": "^1.0.4",
+ "unicode-match-property-value-ecmascript": "^1.2.0"
+ }
+ },
+ "regjsgen": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz",
+ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
+ "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ }
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true,
+ "optional": true
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ }
+ }
+ },
+ "resolve-dir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.0",
+ "global-modules": "^1.0.0"
+ },
+ "dependencies": {
+ "global-modules": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^1.0.1",
+ "is-windows": "^1.0.1",
+ "resolve-dir": "^1.0.0"
+ }
+ }
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "run-queue": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
+ "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.5",
+ "ajv": "^6.12.4",
+ "ajv-keywords": "^3.5.2"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+ },
+ "serialize-javascript": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-value": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "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
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ }
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "source-list-map": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+ "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+ "dev": true
+ },
+ "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-resolve": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+ "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.2",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "ssri": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+ "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+ "dev": true,
+ "requires": {
+ "figgy-pudding": "^3.5.1"
+ }
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "stream-browserify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "stream-each": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.6",
+ "to-arraybuffer": "^1.0.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "table": {
+ "version": "5.4.6",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.10.2",
+ "lodash": "^4.17.14",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ }
+ },
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ },
+ "terser": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+ "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.12",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz",
+ "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "timers-browserify": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
+ "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "^1.0.4"
+ }
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "tslib": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
+ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
+ "dev": true
+ },
+ "tty-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "unicode-canonical-property-names-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ=="
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+ "dev": true,
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^1.0.4",
+ "unicode-property-aliases-ecmascript": "^1.0.4"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
+ "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==",
+ "dev": true
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
+ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg=="
+ },
+ "union-value": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^2.0.1"
+ }
+ },
+ "unique-filename": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+ "dev": true,
+ "requires": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
+ "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
+ "dev": true,
+ "optional": true
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+ "dev": true
+ },
+ "util": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+ "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ }
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "v8-compile-cache": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+ "dev": true
+ },
+ "vm-browserify": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+ "dev": true
+ },
+ "watchpack": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz",
+ "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.4.1",
+ "graceful-fs": "^4.1.2",
+ "neo-async": "^2.5.0",
+ "watchpack-chokidar2": "^2.0.0"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "binary-extensions": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+ "dev": true,
+ "optional": true
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
+ }
+ }
+ },
+ "chokidar": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
+ "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.1.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.4.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "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,
+ "optional": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "optional": true
+ },
+ "glob-parent": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ },
+ "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,
+ "optional": true
+ },
+ "readdirp": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+ "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "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,
+ "optional": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "watchpack-chokidar2": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz",
+ "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chokidar": "^2.1.8"
+ }
+ },
+ "webpack": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz",
+ "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/wasm-edit": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0",
+ "acorn": "^6.4.1",
+ "ajv": "^6.10.2",
+ "ajv-keywords": "^3.4.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^4.3.0",
+ "eslint-scope": "^4.0.3",
+ "json-parse-better-errors": "^1.0.2",
+ "loader-runner": "^2.4.0",
+ "loader-utils": "^1.2.3",
+ "memory-fs": "^0.4.1",
+ "micromatch": "^3.1.10",
+ "mkdirp": "^0.5.3",
+ "neo-async": "^2.6.1",
+ "node-libs-browser": "^2.2.1",
+ "schema-utils": "^1.0.0",
+ "tapable": "^1.1.3",
+ "terser-webpack-plugin": "^1.4.3",
+ "watchpack": "^1.7.4",
+ "webpack-sources": "^1.4.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ },
+ "cacache": {
+ "version": "12.0.4",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+ "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.5",
+ "chownr": "^1.1.1",
+ "figgy-pudding": "^3.5.1",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.1.15",
+ "infer-owner": "^1.0.3",
+ "lru-cache": "^5.1.1",
+ "mississippi": "^3.0.0",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.6.3",
+ "ssri": "^6.0.1",
+ "unique-filename": "^1.1.1",
+ "y18n": "^4.0.0"
+ }
+ },
+ "eslint-scope": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+ "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "terser-webpack-plugin": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
+ "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
+ "dev": true,
+ "requires": {
+ "cacache": "^12.0.2",
+ "find-cache-dir": "^2.1.0",
+ "is-wsl": "^1.1.0",
+ "schema-utils": "^1.0.0",
+ "serialize-javascript": "^4.0.0",
+ "source-map": "^0.6.1",
+ "terser": "^4.1.2",
+ "webpack-sources": "^1.4.0",
+ "worker-farm": "^1.7.0"
+ }
+ }
+ }
+ },
+ "webpack-cli": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz",
+ "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "cross-spawn": "^6.0.5",
+ "enhanced-resolve": "^4.1.1",
+ "findup-sync": "^3.0.0",
+ "global-modules": "^2.0.0",
+ "import-local": "^2.0.0",
+ "interpret": "^1.4.0",
+ "loader-utils": "^1.4.0",
+ "supports-color": "^6.1.0",
+ "v8-compile-cache": "^2.1.1",
+ "yargs": "^13.3.2"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ }
+ }
+ },
+ "webpack-sources": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+ "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "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
+ }
+ }
+ },
+ "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"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
+ },
+ "worker-farm": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+ "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+ "dev": true,
+ "requires": {
+ "errno": "~0.1.7"
+ }
+ },
+ "wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "13.3.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "13.1.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ }
+ }
+}
diff --git a/runtime/js/package.json b/runtime/js/package.json
new file mode 100644
index 000000000..f5426cac0
--- /dev/null
+++ b/runtime/js/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "wails-runtime",
+ "version": "1.0.0",
+ "description": "The Javascript Wails Runtime",
+ "main": "index.js",
+ "scripts": {
+ "build": "./node_modules/.bin/eslint core/ && npm run build:prod",
+ "build:prod": "./node_modules/.bin/webpack --env prod --colors",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/wailsapp/runtime.git"
+ },
+ "keywords": [
+ "Wails",
+ "Go",
+ "Javascript",
+ "Runtime"
+ ],
+ "browserslist": [
+ "> 5%",
+ "IE 9"
+ ],
+ "author": "Lea Anthony ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/wailsapp/runtime/issues"
+ },
+ "homepage": "https://github.com/wailsapp/runtime#readme",
+ "devDependencies": {
+ "@babel/cli": "^7.11.5",
+ "@babel/core": "^7.11.5",
+ "@babel/plugin-transform-object-assign": "^7.10.4",
+ "@babel/preset-env": "^7.11.5",
+ "babel-loader": "^8.1.0",
+ "babel-preset-minify": "^0.5.1",
+ "core-js": "^3.6.5",
+ "eslint": "^7.8.1",
+ "webpack": "^4.44.1",
+ "webpack-cli": "^3.3.12"
+ }
+}
diff --git a/runtime/js/runtime/.npmignore b/runtime/js/runtime/.npmignore
new file mode 100644
index 000000000..d0a1d1fad
--- /dev/null
+++ b/runtime/js/runtime/.npmignore
@@ -0,0 +1 @@
+bridge.js
\ No newline at end of file
diff --git a/runtime/js/runtime/README.md b/runtime/js/runtime/README.md
new file mode 100644
index 000000000..bb608fdca
--- /dev/null
+++ b/runtime/js/runtime/README.md
@@ -0,0 +1,3 @@
+# Wails Runtime
+
+This module is the Javascript runtime library for the [Wails](https://wails.app) framework. It is intended to be installed as part of a [Wails](https://wails.app) project, not a standalone module.
diff --git a/runtime/js/runtime/browser.js b/runtime/js/runtime/browser.js
new file mode 100644
index 000000000..70167883f
--- /dev/null
+++ b/runtime/js/runtime/browser.js
@@ -0,0 +1,37 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+/**
+ * Opens the given URL in the system browser
+ *
+ * @export
+ * @param {string} url
+ * @returns
+ */
+function OpenURL(url) {
+ return window.wails.Browser.OpenURL(url);
+}
+
+/**
+ * Opens the given filename using the system's default file handler
+ *
+ * @export
+ * @param {sting} filename
+ * @returns
+ */
+function OpenFile(filename) {
+ return window.wails.Browser.OpenFile(filename);
+}
+
+module.exports = {
+ OpenURL: OpenURL,
+ OpenFile: OpenFile
+};
\ No newline at end of file
diff --git a/runtime/js/runtime/events.js b/runtime/js/runtime/events.js
new file mode 100644
index 000000000..30a23a01e
--- /dev/null
+++ b/runtime/js/runtime/events.js
@@ -0,0 +1,90 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+
+/**
+ * Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
+ *
+ * @export
+ * @param {string} eventName
+ * @param {function} callback
+ * @param {number} maxCallbacks
+ */
+function OnMultiple(eventName, callback, maxCallbacks) {
+ window.wails.Events.OnMultiple(eventName, callback, maxCallbacks);
+}
+
+/**
+ * Registers an event listener that will be invoked every time the event is emitted
+ *
+ * @export
+ * @param {string} eventName
+ * @param {function} callback
+ */
+function On(eventName, callback) {
+ OnMultiple(eventName, callback);
+}
+
+/**
+ * Registers an event listener that will be invoked once then destroyed
+ *
+ * @export
+ * @param {string} eventName
+ * @param {function} callback
+ */
+function Once(eventName, callback) {
+ OnMultiple(eventName, callback, 1);
+}
+
+
+/**
+ * Emit an event with the given name and data
+ *
+ * @export
+ * @param {string} eventName
+ */
+function Emit(eventName) {
+ var args = [eventName].slice.call(arguments);
+ return window.wails.Events.Emit.apply(null, args);
+}
+
+
+/**
+ * Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
+ * the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
+ *
+ * @export
+ * @param {string} eventName
+ * @param {number} timeInMilliseconds
+ * @param {function} callback
+ */
+function Heartbeat(eventName, timeInMilliseconds, callback) {
+ window.wails.Events.Heartbeat(eventName, timeInMilliseconds, callback);
+}
+
+/**
+ * Acknowledges a heartbeat event by name
+ *
+ * @export
+ * @param {string} eventName
+ */
+function Acknowledge(eventName) {
+ return window.wails.Events.Acknowledge(eventName);
+}
+
+module.exports = {
+ OnMultiple: OnMultiple,
+ On: On,
+ Once: Once,
+ Emit: Emit,
+ Heartbeat: Heartbeat,
+ Acknowledge: Acknowledge
+};
\ No newline at end of file
diff --git a/runtime/js/runtime/init.js b/runtime/js/runtime/init.js
new file mode 100644
index 000000000..14de18624
--- /dev/null
+++ b/runtime/js/runtime/init.js
@@ -0,0 +1,21 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+/**
+ * Initialises the Wails runtime
+ *
+ * @param {function} callback
+ */
+function Init(callback) {
+ window.wails._.Init(callback);
+}
+
+module.exports = Init;
diff --git a/runtime/js/runtime/log.js b/runtime/js/runtime/log.js
new file mode 100644
index 000000000..2defed8f7
--- /dev/null
+++ b/runtime/js/runtime/log.js
@@ -0,0 +1,70 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+
+/* jshint esversion: 6 */
+
+
+/**
+ * Log the given debug message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+function Debug(message) {
+ window.wails.Log.Debug(message);
+}
+
+/**
+ * Log the given info message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+function Info(message) {
+ window.wails.Log.Info(message);
+}
+
+/**
+ * Log the given warning message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+function Warning(message) {
+ window.wails.Log.Warning(message);
+}
+
+/**
+ * Log the given error message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+function Error(message) {
+ window.wails.Log.Error(message);
+}
+
+/**
+ * Log the given fatal message with the backend
+ *
+ * @export
+ * @param {string} message
+ */
+function Fatal(message) {
+ window.wails.Log.Fatal(message);
+}
+
+module.exports = {
+ Debug: Debug,
+ Info: Info,
+ Warning: Warning,
+ Error: Error,
+ Fatal: Fatal
+};
diff --git a/runtime/js/runtime/main.js b/runtime/js/runtime/main.js
new file mode 100644
index 000000000..9310b8099
--- /dev/null
+++ b/runtime/js/runtime/main.js
@@ -0,0 +1,24 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+/* jshint esversion: 6 */
+
+const Log = require('./log');
+const Browser = require('./browser');
+const Events = require('./events');
+const Init = require('./init');
+const Store = require('./store');
+
+module.exports = {
+ Log: Log,
+ Browser: Browser,
+ Events: Events,
+ Init: Init,
+ Store: Store,
+};
\ No newline at end of file
diff --git a/runtime/js/runtime/package-lock.json b/runtime/js/runtime/package-lock.json
new file mode 100644
index 000000000..7a13b4e3a
--- /dev/null
+++ b/runtime/js/runtime/package-lock.json
@@ -0,0 +1,492 @@
+{
+ "name": "@wailsapp/runtime",
+ "version": "1.1.1",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "dts-dom": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/dts-dom/-/dts-dom-3.6.0.tgz",
+ "integrity": "sha512-on5jxTgt+A6r0Zyyz6ZRHXaAO7J1VPnOd6+AmvI1vH440AlAZZNc5rUHzgPuTjGlrVr1rOWQYNl7ZJK6rDohbw==",
+ "dev": true
+ },
+ "dts-gen": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmjs.org/dts-gen/-/dts-gen-0.5.8.tgz",
+ "integrity": "sha512-kIAV6dlHaF7r5J+tIuOC1BJls2P72YM0cyWQUR88zcJEpX2ccRZe+HmXLfkkvfPwjvSO3FEqUiyC8On/grx5qw==",
+ "dev": true,
+ "requires": {
+ "dts-dom": "^3.6.0",
+ "parse-git-config": "^1.1.1",
+ "typescript": "^3.5.1",
+ "yargs": "^4.8.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "fs-exists-sync": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
+ "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=",
+ "dev": true
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "dev": true
+ },
+ "git-config-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz",
+ "integrity": "sha1-bTP37WPbDQ4RgTFQO6s6ykfVRmQ=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "fs-exists-sync": "^0.1.0",
+ "homedir-polyfill": "^1.0.0"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+ "dev": true
+ },
+ "homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+ "dev": true,
+ "requires": {
+ "parse-passwd": "^1.0.0"
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
+ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
+ "invert-kv": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+ "dev": true
+ },
+ "lcid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^1.0.0"
+ }
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "strip-bom": "^2.0.0"
+ }
+ },
+ "lodash.assign": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+ "dev": true
+ },
+ "normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "dev": true,
+ "requires": {
+ "lcid": "^1.0.0"
+ }
+ },
+ "parse-git-config": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-1.1.1.tgz",
+ "integrity": "sha1-06mYQxcTL1c5hxK7pDjhKVkN34w=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "fs-exists-sync": "^0.1.0",
+ "git-config-path": "^1.0.1",
+ "ini": "^1.3.4"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^1.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^1.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "dev": true,
+ "requires": {
+ "find-up": "^1.0.0",
+ "read-pkg": "^1.0.0"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "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.5",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "dev": true,
+ "requires": {
+ "is-utf8": "^0.2.0"
+ }
+ },
+ "typescript": {
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
+ "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
+ "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"
+ }
+ },
+ "which-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+ "dev": true
+ },
+ "window-size": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz",
+ "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ }
+ },
+ "y18n": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz",
+ "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=",
+ "dev": true,
+ "requires": {
+ "cliui": "^3.2.0",
+ "decamelize": "^1.1.1",
+ "get-caller-file": "^1.0.1",
+ "lodash.assign": "^4.0.3",
+ "os-locale": "^1.4.0",
+ "read-pkg-up": "^1.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^1.0.1",
+ "which-module": "^1.0.0",
+ "window-size": "^0.2.0",
+ "y18n": "^3.2.1",
+ "yargs-parser": "^2.4.1"
+ }
+ },
+ "yargs-parser": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz",
+ "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^3.0.0",
+ "lodash.assign": "^4.0.6"
+ }
+ }
+ }
+}
diff --git a/runtime/js/runtime/package.json b/runtime/js/runtime/package.json
new file mode 100644
index 000000000..835d176ac
--- /dev/null
+++ b/runtime/js/runtime/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@wailsapp/runtime",
+ "version": "1.1.1",
+ "description": "Wails Javascript runtime library",
+ "main": "main.js",
+ "types": "runtime.d.ts",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "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",
+ "devDependencies": {
+ "dts-gen": "^0.5.8"
+ }
+}
diff --git a/runtime/js/runtime/runtime.d.ts b/runtime/js/runtime/runtime.d.ts
new file mode 100644
index 000000000..21e07da5b
--- /dev/null
+++ b/runtime/js/runtime/runtime.d.ts
@@ -0,0 +1,29 @@
+export = wailsapp__runtime;
+
+declare const wailsapp__runtime: {
+ Browser: {
+ OpenFile(filename: string): Promise;
+ OpenURL(url: string): Promise;
+ };
+ Events: {
+ Acknowledge(eventName: string): void;
+ Emit(eventName: string, data?: any): void;
+ Heartbeat(eventName: string, timeInMilliseconds: number, callback: (data?: any) => void): void;
+ On(eventName: string, callback: (data?: any) => void): void;
+ OnMultiple(eventName: string, callback: (data?: any) => void, maxCallbacks: number): void;
+ Once(eventName: string, callback: (data?: any) => void): void;
+ };
+ Init(callback: () => void): void;
+ Log: {
+ Debug(message: string): void;
+ Error(message: string): void;
+ Fatal(message: string): void;
+ Info(message: string): void;
+ Warning(message: string): void;
+ };
+ Store: {
+ New(name: string, optionalDefault?: any): any;
+ };
+};
+
+
diff --git a/runtime/js/runtime/store.js b/runtime/js/runtime/store.js
new file mode 100644
index 000000000..3f12fe93b
--- /dev/null
+++ b/runtime/js/runtime/store.js
@@ -0,0 +1,27 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The lightweight framework for web-like apps
+(c) Lea Anthony 2019-present
+*/
+
+/* jshint esversion: 6 */
+
+
+/**
+ * Create a new Store with the given name and optional default value
+ *
+ * @export
+ * @param {string} name
+ * @param {*} optionalDefault
+ */
+function New(name, optionalDefault) {
+ return window.wails.Store.New(name, optionalDefault);
+}
+
+module.exports = {
+ New: New,
+};
diff --git a/runtime/js/webpack.config.js b/runtime/js/webpack.config.js
new file mode 100644
index 000000000..62cb81a20
--- /dev/null
+++ b/runtime/js/webpack.config.js
@@ -0,0 +1,4 @@
+/* eslint-disable */
+module.exports = (env) => {
+ return require(`./webpack.${env}.js`);
+};
\ No newline at end of file
diff --git a/runtime/js/webpack.prod.js b/runtime/js/webpack.prod.js
new file mode 100644
index 000000000..8821d9c56
--- /dev/null
+++ b/runtime/js/webpack.prod.js
@@ -0,0 +1,38 @@
+/* eslint-disable */
+
+const path = require('path');
+
+module.exports = {
+ entry: './core/main',
+ mode: 'production',
+ output: {
+ path: path.resolve(__dirname, '..', 'assets'),
+ filename: 'wails.js'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.m?js$/,
+ exclude: /(node_modules|bower_components)/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ plugins: ['@babel/plugin-transform-object-assign'],
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ 'useBuiltIns': 'entry',
+ 'corejs': {
+ 'version': 3,
+ 'proposals': true
+ }
+ }
+ ]
+ ]
+ }
+ }
+ }
+ ]
+ }
+};
diff --git a/runtime/log.go b/runtime/log.go
new file mode 100644
index 000000000..dd609ce51
--- /dev/null
+++ b/runtime/log.go
@@ -0,0 +1,16 @@
+package runtime
+
+import "github.com/wailsapp/wails/lib/logger"
+
+// Log exposes the logging interface to the runtime
+type Log struct{}
+
+// NewLog creates a new Log struct
+func NewLog() *Log {
+ return &Log{}
+}
+
+// New creates a new logger
+func (r *Log) New(prefix string) *logger.CustomLogger {
+ return logger.NewCustomLogger(prefix)
+}
diff --git a/runtime/runtime.go b/runtime/runtime.go
new file mode 100644
index 000000000..7097de7a1
--- /dev/null
+++ b/runtime/runtime.go
@@ -0,0 +1,29 @@
+package runtime
+
+import "github.com/wailsapp/wails/lib/interfaces"
+
+// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
+type Runtime struct {
+ Events *Events
+ Log *Log
+ Dialog *Dialog
+ Window *Window
+ Browser *Browser
+ FileSystem *FileSystem
+ Store *StoreProvider
+}
+
+// NewRuntime creates a new Runtime struct
+func NewRuntime(eventManager interfaces.EventManager, renderer interfaces.Renderer) *Runtime {
+ result := &Runtime{
+ Events: NewEvents(eventManager),
+ Log: NewLog(),
+ Dialog: NewDialog(renderer),
+ Window: NewWindow(renderer),
+ Browser: NewBrowser(),
+ FileSystem: NewFileSystem(),
+ }
+ // We need a reference to itself
+ result.Store = NewStoreProvider(result)
+ return result
+}
diff --git a/runtime/store.go b/runtime/store.go
new file mode 100644
index 000000000..4468296b1
--- /dev/null
+++ b/runtime/store.go
@@ -0,0 +1,298 @@
+// Package runtime contains all the methods and data structures related to the
+// runtime library of Wails. This includes both Go and JS runtimes.
+package runtime
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "reflect"
+ "sync"
+)
+
+// Options defines the optional data that may be used
+// when creating a Store
+type Options struct {
+
+ // The name of the store
+ Name string
+
+ // The runtime to attach the store to
+ Runtime *Runtime
+
+ // Indicates if notifying Go listeners should be notified of updates
+ // synchronously (on the current thread) or asynchronously using
+ // goroutines
+ NotifySynchronously bool
+}
+
+// StoreProvider is a struct that creates Stores
+type StoreProvider struct {
+ runtime *Runtime
+}
+
+// NewStoreProvider creates new stores using the provided Runtime reference.
+func NewStoreProvider(runtime *Runtime) *StoreProvider {
+ return &StoreProvider{
+ runtime: runtime,
+ }
+}
+
+// Store is where we keep named data
+type Store struct {
+ name string
+ data reflect.Value
+ dataType reflect.Type
+ eventPrefix string
+ callbacks []reflect.Value
+ runtime *Runtime
+ notifySynchronously bool
+
+ // Lock
+ mux sync.Mutex
+
+ // Error handler
+ errorHandler func(error)
+}
+
+// New creates a new store
+func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
+
+ dataType := reflect.TypeOf(defaultValue)
+
+ result := Store{
+ name: name,
+ runtime: p.runtime,
+ data: reflect.ValueOf(defaultValue),
+ dataType: dataType,
+ }
+
+ // Setup the sync listener
+ result.setupListener()
+
+ return &result
+}
+
+// OnError takes a function that will be called
+// whenever an error occurs
+func (s *Store) OnError(callback func(error)) {
+ s.errorHandler = callback
+}
+
+// Processes the updates sent by the front end
+func (s *Store) processUpdatedData(data string) error {
+
+ // Decode incoming data
+ var rawdata json.RawMessage
+ d := json.NewDecoder(bytes.NewBufferString(data))
+ err := d.Decode(&rawdata)
+ if err != nil {
+ return err
+ }
+
+ // Create a new instance of our data and unmarshal
+ // the received value into it
+ newData := reflect.New(s.dataType).Interface()
+ err = json.Unmarshal(rawdata, &newData)
+ if err != nil {
+ return err
+ }
+
+ // Lock mutex for writing
+ s.mux.Lock()
+
+ // Handle nulls
+ if newData == nil {
+ s.data = reflect.Zero(s.dataType)
+ } else {
+ // Store the resultant value in the data store
+ s.data = reflect.ValueOf(newData).Elem()
+ }
+
+ // Unlock mutex
+ s.mux.Unlock()
+
+ return nil
+}
+
+// Setup listener for front end changes
+func (s *Store) setupListener() {
+
+ // Listen for updates from the front end
+ s.runtime.Events.On("wails:sync:store:updatedbyfrontend:"+s.name, func(data ...interface{}) {
+
+ // Process the incoming data
+ err := s.processUpdatedData(data[0].(string))
+
+ if err != nil {
+ if s.errorHandler != nil {
+ s.errorHandler(err)
+ return
+ }
+ }
+
+ // Notify listeners
+ s.notify()
+ })
+}
+
+// notify the listeners of the current data state
+func (s *Store) notify() {
+
+ // Execute callbacks
+ for _, callback := range s.callbacks {
+
+ // Build args
+ args := []reflect.Value{s.data}
+
+ if s.notifySynchronously {
+ callback.Call(args)
+ } else {
+ go callback.Call(args)
+ }
+
+ }
+}
+
+// Set will update the data held by the store
+// and notify listeners of the change
+func (s *Store) Set(data interface{}) error {
+
+ inType := reflect.TypeOf(data)
+
+ if inType != s.dataType {
+ return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
+ }
+
+ // Save data
+ s.mux.Lock()
+ s.data = reflect.ValueOf(data)
+ s.mux.Unlock()
+
+ // Stringify data
+ newdata, err := json.Marshal(data)
+ if err != nil {
+ if s.errorHandler != nil {
+ return err
+ }
+ }
+
+ // Emit event to front end
+ s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
+
+ // Notify subscribers
+ s.notify()
+
+ return nil
+}
+
+// callbackCheck ensures the given function to Subscribe() is
+// of the correct signature. Absolutely cannot wait for
+// generics to land rather than writing this nonsense.
+func (s *Store) callbackCheck(callback interface{}) error {
+
+ // Get type
+ callbackType := reflect.TypeOf(callback)
+
+ // Check callback is a function
+ if callbackType.Kind() != reflect.Func {
+ return fmt.Errorf("invalid value given to store.Subscribe(). Expected 'func(%s)'", s.dataType.String())
+ }
+
+ // Check input param
+ if callbackType.NumIn() != 1 {
+ return fmt.Errorf("invalid number of parameters given in callback function. Expected 1")
+ }
+
+ // Check input data type
+ if callbackType.In(0) != s.dataType {
+ return fmt.Errorf("invalid type for input parameter given in callback function. Expected %s, got %s", s.dataType.String(), callbackType.In(0))
+ }
+
+ // Check output param
+ if callbackType.NumOut() != 0 {
+ return fmt.Errorf("invalid number of return parameters given in callback function. Expected 0")
+ }
+
+ return nil
+}
+
+// Subscribe will subscribe to updates to the store by
+// providing a callback. Any updates to the store are sent
+// to the callback
+func (s *Store) Subscribe(callback interface{}) {
+
+ err := s.callbackCheck(callback)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ callbackFunc := reflect.ValueOf(callback)
+
+ s.callbacks = append(s.callbacks, callbackFunc)
+}
+
+// updaterCheck ensures the given function to Update() is
+// of the correct signature. Absolutely cannot wait for
+// generics to land rather than writing this nonsense.
+func (s *Store) updaterCheck(updater interface{}) error {
+
+ // Get type
+ updaterType := reflect.TypeOf(updater)
+
+ // Check updater is a function
+ if updaterType.Kind() != reflect.Func {
+ return fmt.Errorf("invalid value given to store.Update(). Expected 'func(%s) %s'", s.dataType.String(), s.dataType.String())
+ }
+
+ // Check input param
+ if updaterType.NumIn() != 1 {
+ return fmt.Errorf("invalid number of parameters given in updater function. Expected 1")
+ }
+
+ // Check input data type
+ if updaterType.In(0) != s.dataType {
+ return fmt.Errorf("invalid type for input parameter given in updater function. Expected %s, got %s", s.dataType.String(), updaterType.In(0))
+ }
+
+ // Check output param
+ if updaterType.NumOut() != 1 {
+ return fmt.Errorf("invalid number of return parameters given in updater function. Expected 1")
+ }
+
+ // Check output data type
+ if updaterType.Out(0) != s.dataType {
+ return fmt.Errorf("invalid type for return parameter given in updater function. Expected %s, got %s", s.dataType.String(), updaterType.Out(0))
+ }
+
+ return nil
+}
+
+// Update takes a function that is passed the current state.
+// The result of that function is then set as the new state
+// of the store. This will notify listeners of the change
+func (s *Store) Update(updater interface{}) {
+
+ err := s.updaterCheck(updater)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Build args
+ args := []reflect.Value{s.data}
+
+ // Make call
+ results := reflect.ValueOf(updater).Call(args)
+
+ // We will only have 1 result. Set the store to it
+ err = s.Set(results[0].Interface())
+ if err != nil && s.errorHandler != nil {
+ s.errorHandler(err)
+ }
+}
+
+// Get returns the value of the data that's kept in the current state / Store
+func (s *Store) Get() interface{} {
+ return s.data.Interface()
+}
diff --git a/runtime/window.go b/runtime/window.go
new file mode 100644
index 000000000..960e02b7b
--- /dev/null
+++ b/runtime/window.go
@@ -0,0 +1,89 @@
+package runtime
+
+import (
+ "bytes"
+ "runtime"
+
+ "github.com/abadojack/whatlanggo"
+ "github.com/wailsapp/wails/lib/interfaces"
+ "golang.org/x/text/encoding"
+ "golang.org/x/text/encoding/japanese"
+ "golang.org/x/text/encoding/korean"
+ "golang.org/x/text/encoding/simplifiedchinese"
+ "golang.org/x/text/transform"
+)
+
+func detectEncoding(text string) (encoding.Encoding, string) {
+ // korean
+ var enc encoding.Encoding
+ info := whatlanggo.Detect(text)
+ //fmt.Println("Language:", info.Lang.String(), " Script:", whatlanggo.Scripts[info.Script], " Confidence: ", info.Confidence)
+ switch info.Lang.String() {
+ case "Korean":
+ enc = korean.EUCKR
+ case "Mandarin":
+ enc = simplifiedchinese.GBK
+ case "Japanese":
+ enc = japanese.EUCJP
+ }
+ return enc, info.Lang.String()
+}
+
+// ProcessEncoding attempts to convert CKJ strings to UTF-8
+func ProcessEncoding(text string) string {
+ if runtime.GOOS != "windows" {
+ return text
+ }
+
+ encoding, _ := detectEncoding(text)
+ if encoding != nil {
+ var bufs bytes.Buffer
+ wr := transform.NewWriter(&bufs, encoding.NewEncoder())
+ _, err := wr.Write([]byte(text))
+ defer wr.Close()
+ if err != nil {
+ return ""
+ }
+
+ return bufs.String()
+ }
+ return text
+}
+
+// Window exposes an interface for manipulating the window
+type Window struct {
+ renderer interfaces.Renderer
+}
+
+// NewWindow creates a new Window struct
+func NewWindow(renderer interfaces.Renderer) *Window {
+ return &Window{
+ renderer: renderer,
+ }
+}
+
+// SetColour sets the the window colour
+func (r *Window) SetColour(colour string) error {
+ return r.renderer.SetColour(colour)
+}
+
+// Fullscreen makes the window fullscreen
+func (r *Window) Fullscreen() {
+ r.renderer.Fullscreen()
+}
+
+// UnFullscreen attempts to restore the window to the size/position before fullscreen
+func (r *Window) UnFullscreen() {
+ r.renderer.UnFullscreen()
+}
+
+// SetTitle sets the the window title
+func (r *Window) SetTitle(title string) {
+ title = ProcessEncoding(title)
+ r.renderer.SetTitle(title)
+}
+
+// Close shuts down the window and therefore the app
+func (r *Window) Close() {
+ r.renderer.Close()
+}
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/build.go b/scripts/build.go
new file mode 100644
index 000000000..1b6f2e718
--- /dev/null
+++ b/scripts/build.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+)
+
+// Default target to run when none is specified
+// If not set, running mage will list available targets
+// var Default = Build
+
+/*
+# Build runtime
+echo "**** Building Runtime ****"
+cd runtime/js
+npm install
+npm run build
+cd ../..
+
+echo "**** Packing Assets ****"
+cd cmd
+mewn
+cd ..
+cd lib/renderer
+mewn
+cd ../..
+
+echo "**** Installing Wails locally ****"
+cd cmd/wails
+go install
+cd ../..
+
+echo "**** Tidying the mods! ****"
+go mod tidy
+
+echo "**** WE ARE DONE! ****"
+
+*/
+
+func runCommand(command string, args ...string) {
+ cmd := exec.Command(command, args...)
+ output, err := cmd.CombinedOutput()
+ fmt.Println(string(output))
+ if err != nil {
+ log.Println(string(output))
+ log.Fatal(err)
+ }
+ cmd.Run()
+ fmt.Println(string(output))
+}
+
+// A build step that requires additional params, or platform specific steps for example
+func main() {
+
+ dir, _ := os.Getwd()
+
+ // Build Runtime
+ fmt.Println("**** Building Runtime ****")
+ runtimeDir, _ := filepath.Abs(filepath.Join(dir, "..", "runtime", "js"))
+ os.Chdir(runtimeDir)
+ runCommand("npm", "install")
+ runCommand("npm", "run", "build")
+
+ // Pack assets
+ fmt.Println("**** Packing Assets ****")
+ rendererDir, _ := filepath.Abs(filepath.Join(dir, "..", "lib", "renderer"))
+ os.Chdir(rendererDir)
+ runCommand("mewn")
+ cmdDir, _ := filepath.Abs(filepath.Join(dir, "..", "cmd"))
+ os.Chdir(cmdDir)
+ runCommand("mewn")
+
+ // Install Wails
+ fmt.Println("**** Installing Wails locally ****")
+ execDir, _ := filepath.Abs(filepath.Join(dir, "..", "cmd", "wails"))
+ os.Chdir(execDir)
+ runCommand("go", "install")
+
+ baseDir, _ := filepath.Abs(filepath.Join(dir, ".."))
+ os.Chdir(baseDir)
+ runCommand("go", "mod", "tidy")
+}
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 000000000..fc838f2e5
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+echo "**** Checking if Wails passes unit tests ****"
+if ! go test ./...
+then
+ echo ""
+ echo "ERROR: Unit tests failed!"
+ exit 1;
+fi
+
+# Build runtime
+echo "**** Building Runtime ****"
+cd runtime/js
+npm install
+npm run build
+cd ../..
+
+echo "**** Packing Assets ****"
+cd cmd
+mewn
+cd ..
+cd lib/renderer
+mewn
+cd ../..
+
+
+cd cmd/wails
+echo "**** Checking if Wails compiles ****"
+if ! go build .
+then
+ echo ""
+ echo "ERROR: Build failed!"
+ exit 1;
+fi
+
+echo "**** Installing Wails locally ****"
+if ! go install
+then
+ echo ""
+ echo "ERROR: Install failed!"
+ exit 1;
+fi
+cd ../..
+
+echo "**** Tidying the mods! ****"
+go mod tidy
+
+echo "**** WE ARE DONE! ****"
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
deleted file mode 100755
index b034a0176..000000000
--- a/scripts/sponsors/generate-sponsor-image.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env bash
-
-npm install sponsorkit@16.4.2
-npx sponsorkit -o ../../website/static/img/
diff --git a/scripts/sponsors/package-lock.json b/scripts/sponsors/package-lock.json
deleted file mode 100644
index 2bb15b685..000000000
--- a/scripts/sponsors/package-lock.json
+++ /dev/null
@@ -1,723 +0,0 @@
-{
- "name": "sponsors",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "sponsors",
- "version": "1.0.0",
- "license": "ISC",
- "dependencies": {
- "sponsorkit": "^16.5.0"
- },
- "engines": {
- "node": ">=22.0.0"
- }
- },
- "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,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "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": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.1.0.tgz",
- "integrity": "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==",
- "license": "ISC",
- "engines": {
- "node": ">=14"
- }
- },
- "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",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
- },
- "engines": {
- "node": ">=12.5.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
- "node_modules/consola": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
- "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
- "license": "MIT",
- "engines": {
- "node": "^14.18.0 || >=16.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"
- },
- "node_modules/destr": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
- "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==",
- "license": "MIT"
- },
- "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",
- "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"
- },
- "funding": {
- "url": "https://dotenvx.com"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
- "license": "MIT"
- },
- "node_modules/jiti": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
- "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "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"
- },
- "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",
- "dependencies": {
- "destr": "^2.0.3",
- "node-fetch-native": "^1.6.4",
- "ufo": "^1.5.4"
- }
- },
- "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/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",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/sharp": {
- "version": "0.34.3",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
- "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.4",
- "semver": "^7.7.2"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.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-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/sponsorkit": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/sponsorkit/-/sponsorkit-16.5.0.tgz",
- "integrity": "sha512-GvlLg88eAEbKzROwAspT+PQTMfHN9KQ+zgPqBBvV1W2jQmKxOtnv9vjgByXvXA2dvTjnksdvbTuwqhJZllyLQA==",
- "license": "MIT",
- "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"
- },
- "bin": {
- "sponsorkit": "bin/sponsorkit.mjs"
- },
- "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/ufo": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
- "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
- "license": "MIT"
- },
- "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",
- "dependencies": {
- "@quansync/fs": "^0.1.1",
- "defu": "^6.1.4",
- "jiti": "^2.4.2",
- "quansync": "^0.2.8"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- }
- }
-}
diff --git a/scripts/sponsors/package.json b/scripts/sponsors/package.json
deleted file mode 100644
index c9f000b90..000000000
--- a/scripts/sponsors/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "name": "sponsors",
- "version": "1.0.0",
- "description": "",
- "main": "",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "",
- "license": "ISC",
- "dependencies": {
- "sponsorkit": "^16.5.0"
- },
- "engines": {
- "node": ">=22.0.0"
- }
-}
diff --git a/scripts/sponsors/sponsorkit.config.js b/scripts/sponsors/sponsorkit.config.js
deleted file mode 100644
index 6596348d1..000000000
--- a/scripts/sponsors/sponsorkit.config.js
+++ /dev/null
@@ -1,206 +0,0 @@
-import {defineConfig} from 'sponsorkit';
-
-const helpers = {
- avatar: {
- size: 45
- },
- boxWidth: 55,
- boxHeight: 55,
- container: {
- sidePadding: 30
- },
-};
-
-const coffee = {
- avatar: {
- size: 50
- },
- boxWidth: 65,
- boxHeight: 65,
- container: {
- sidePadding: 30
- },
-};
-
-const breakfast = {
- avatar: {
- size: 55
- },
- boxWidth: 75,
- boxHeight: 75,
- container: {
- sidePadding: 20
- },
- name: {
- maxLength: 10
- }
-};
-
-const costs = {
- avatar: {
- size: 65
- },
- boxWidth: 90,
- boxHeight: 80,
- container: {
- sidePadding: 30
- },
- name: {
- maxLength: 10
- }
-};
-
-const bronze = {
- avatar: {
- size: 85
- },
- boxWidth: 110,
- boxHeight: 100,
- container: {
- sidePadding: 30
- },
- name: {
- maxLength: 20
- }
-};
-
-const silver = {
- avatar: {
- size: 100
- },
- boxWidth: 110,
- boxHeight: 110,
- container: {
- sidePadding: 20
- },
- name: {
- maxLength: 20
- }
-};
-
-const gold = {
- avatar: {
- size: 150
- },
- boxWidth: 175,
- boxHeight: 175,
- container: {
- sidePadding: 25
- },
- name: {
- maxLength: 25
- }
-};
-
-const champion = {
- avatar: {
- size: 175
- },
- boxWidth: 200,
- boxHeight: 200,
- container: {
- sidePadding: 30
- },
- name: {
- maxLength: 30
- }
-};
-
-const partner = {
- avatar: {
- size: 200
- },
- boxWidth: 225,
- boxHeight: 225,
- container: {
- sidePadding: 40
- },
- name: {
- maxLength: 40
- },
-
-};
-
-export default defineConfig({
- github: {
- login: 'leaanthony',
- type: 'user',
- },
-
- // Rendering configs
- width: 800,
- formats: ['svg'],
- tiers: [
- {
- title: 'Helpers',
- preset: helpers,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Buying Coffee',
- monthlyDollars: 5,
- preset: coffee,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Buying Breakfast',
- monthlyDollars: 10,
- preset: breakfast,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Covering Costs',
- monthlyDollars: 20,
- preset: costs,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Bronze Sponsors',
- monthlyDollars: 50,
- preset: bronze,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Silver Sponsors',
- monthlyDollars: 100,
- preset: silver,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Gold Sponsors',
- monthlyDollars: 200,
- preset: gold,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Champion',
- monthlyDollars: 500,
- preset: champion,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- {
- title: 'Partner',
- monthlyDollars: 1000,
- preset: partner,
- composeAfter: function (composer, tierSponsors, config) {
- composer.addSpan(20);
- }
- },
- ],
-});
\ No newline at end of file
diff --git a/scripts/updateversion.sh b/scripts/updateversion.sh
new file mode 100755
index 000000000..6117b0525
--- /dev/null
+++ b/scripts/updateversion.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+if [ "$#" != "1" ]; then
+ echo "Tag required"
+ exit 1
+fi
+TAG=${1}
+cat << EOF > cmd/version.go
+package cmd
+
+// Version - Wails version
+const Version = "${TAG}"
+EOF
+
+# Build runtime
+cd runtime/js
+npm run build
+
+cd ../..
+mewn
+
+git add cmd/version.go
+git commit cmd/version.go -m "Bump to ${TAG}"
+git tag ${TAG}
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/.prettierignore b/v2/.prettierignore
deleted file mode 100644
index 94c6af38e..000000000
--- a/v2/.prettierignore
+++ /dev/null
@@ -1 +0,0 @@
-website
\ No newline at end of file
diff --git a/v2/.prettierrc.yml b/v2/.prettierrc.yml
deleted file mode 100644
index 685d8b6e7..000000000
--- a/v2/.prettierrc.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-overrides:
- - files:
- - "**/*.md"
- options:
- printWidth: 80
- proseWrap: always
diff --git a/v2/.vscode/settings.json b/v2/.vscode/settings.json
new file mode 100644
index 000000000..01d4ec2d2
--- /dev/null
+++ b/v2/.vscode/settings.json
@@ -0,0 +1,13 @@
+{
+ "files.associations": {
+ "ios": "c",
+ "typeinfo": "c",
+ "sstream": "c",
+ "__functional_03": "c",
+ "functional": "c",
+ "__locale": "c",
+ "locale": "c",
+ "chrono": "c",
+ "system_error": "c"
+ }
+}
\ No newline at end of file
diff --git a/v2/NOTES.md b/v2/NOTES.md
new file mode 100644
index 000000000..16bac7122
--- /dev/null
+++ b/v2/NOTES.md
@@ -0,0 +1,40 @@
+
+# Packing linux
+
+ * create app, app.desktop, app.png (512x512)
+ * chmod +x app!
+ * ./linuxdeploy-x86_64.AppImage --appdir AppDir -i react.png -d react.desktop -e react --output appimage
+
+
+# Wails Doctor
+
+Tested on:
+
+ * Debian 8
+ * Ubuntu 20.04
+ * Ubuntu 19.10
+ * Solus 4.1
+ * Centos 8
+ * Gentoo
+ * OpenSUSE/leap
+ * Fedora 31
+
+### Development
+
+Add a new package manager processor here: `v2/internal/system/packagemanager/`. IsAvailable should work even if the package is installed.
+Add your new package manager to the list of package managers in `v2/internal/system/packagemanager/packagemanager.go`:
+
+```
+var db = map[string]PackageManager{
+ "eopkg": NewEopkg(),
+ "apt": NewApt(),
+ "yum": NewYum(),
+ "pacman": NewPacman(),
+ "emerge": NewEmerge(),
+ "zypper": NewZypper(),
+}
+```
+
+## Gentoo
+
+ * Setup docker image using: emerge-webrsync -x -v
diff --git a/v2/README.md b/v2/README.md
index c69808f58..211b29696 100644
--- a/v2/README.md
+++ b/v2/README.md
@@ -1,238 +1,5 @@
-
-
-
+# Wails v2 ALPHA
-
- Build desktop applications using Go & Web Technologies.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+This branch contains WORK IN PROGRESS! There are no guarantees. Use at your peril!
-
-
-
-
-[English](README.md) · [简体中文](README.zh-Hans.md) · [日本語](README.ja.md)
-
-
-
-
-
-## Table of Contents
-
-
- Click me to Open/Close the directory listing
-
-- [Table of Contents](#table-of-contents)
-- [Introduction](#introduction)
- - [Roadmap](#roadmap)
-- [Features](#features)
-- [Sponsors](#sponsors)
-- [Getting Started](#getting-started)
-- [FAQ](#faq)
-- [Contributors](#contributors)
-- [License](#license)
-
-
-
-## 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!
-
-## Features
-
-- Use standard Go for the backend
-- Use any frontend technology you are already familiar with to build your UI
-- Quickly create rich frontends for your Go programs using pre-built templates
-- Easily call Go methods from Javascript
-- Auto-generated Typescript definitions for your Go structs and methods
-- Native Dialogs & Menus
-- Native Dark / Light mode support
-- Supports modern translucency and "frosted window" effects
-- Unified eventing system between Go and Javascript
-- Powerful cli tool to quickly generate and build your projects
-- Multiplatform
-- Uses native rendering engines - _no embedded browser_!
-
-### Roadmap
-
-The project roadmap may be found [here](https://github.com/wailsapp/wails/discussions/1484). Please consult
-this before open up an enhancement request.
-
-## Sponsors
-
-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).
-
-## 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.
-
-- 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.
-
-- 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.
-
-## Stargazers over time
-
-[](https://starchart.cc/wailsapp/wails)
-
-## 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
-
-[](https://app.fossa.com/projects/git%2Bgithub.com%2Fwailsapp%2Fwails?ref=badge_large)
-
-## Inspiration
-
-This project was mainly coded to the following albums:
-
-- [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)
+This document will be updated as progress is made.
\ No newline at end of file
diff --git a/v2/Taskfile.yaml b/v2/Taskfile.yaml
deleted file mode 100644
index d1893732b..000000000
--- a/v2/Taskfile.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-# https://taskfile.dev
-
-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}}
-
- format:md:
- cmds:
- - npx prettier --write "**/*.md"
-
- format:
- cmds:
- - task: format:md
diff --git a/v2/cmd/wails/build.go b/v2/cmd/wails/build.go
deleted file mode 100644
index 39ad00d2f..000000000
--- a/v2/cmd/wails/build.go
+++ /dev/null
@@ -1,276 +0,0 @@
-package main
-
-import (
- "fmt"
- "github.com/wailsapp/wails/v2/pkg/commands/buildtags"
- "os"
- "runtime"
- "strings"
- "time"
-
- "github.com/leaanthony/slicer"
- "github.com/pterm/pterm"
- "github.com/wailsapp/wails/v2/cmd/wails/flags"
- "github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
- "github.com/wailsapp/wails/v2/internal/colour"
- "github.com/wailsapp/wails/v2/internal/project"
- "github.com/wailsapp/wails/v2/pkg/clilogger"
- "github.com/wailsapp/wails/v2/pkg/commands/build"
-)
-
-func buildApplication(f *flags.Build) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- quiet := f.Verbosity == flags.Quiet
-
- // Create logger
- logger := clilogger.New(os.Stdout)
- logger.Mute(quiet)
-
- if quiet {
- pterm.DisableOutput()
- } else {
- app.PrintBanner()
- }
-
- err := f.Process()
- if err != nil {
- return err
- }
-
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- projectOptions, err := project.Load(cwd)
- if err != nil {
- 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,
- OutputType: "desktop",
- OutputFile: f.OutputFilename,
- CleanBinDirectory: f.Clean,
- Mode: f.GetBuildMode(),
- Devtools: f.Debug || f.Devtools,
- Pack: !f.NoPackage,
- LDFlags: f.LdFlags,
- Compiler: f.Compiler,
- SkipModTidy: f.SkipModTidy,
- Verbosity: f.Verbosity,
- ForceBuild: f.ForceBuild,
- IgnoreFrontend: f.SkipFrontend,
- Compress: f.Upx,
- CompressFlags: f.UpxFlags,
- UserTags: compiledTags,
- WebView2Strategy: f.GetWebView2Strategy(),
- TrimPath: f.TrimPath,
- RaceDetector: f.RaceDetector,
- WindowsConsole: f.WindowsConsole,
- Obfuscated: f.Obfuscated,
- GarbleArgs: f.GarbleArgs,
- SkipBindings: f.SkipBindings,
- ProjectData: projectOptions,
- SkipEmbedCreate: f.SkipEmbedCreate,
- }
-
- tableData := pterm.TableData{
- {"Platform(s)", f.Platform},
- {"Compiler", f.GetCompilerPath()},
- {"Skip Bindings", bool2Str(f.SkipBindings)},
- {"Build Mode", f.GetBuildModeAsString()},
- {"Devtools", bool2Str(buildOptions.Devtools)},
- {"Frontend Directory", projectOptions.GetFrontendDir()},
- {"Obfuscated", bool2Str(f.Obfuscated)},
- }
- if f.Obfuscated {
- tableData = append(tableData, []string{"Garble Args", f.GarbleArgs})
- }
- tableData = append(tableData, pterm.TableData{
- {"Skip Frontend", bool2Str(f.SkipFrontend)},
- {"Compress", bool2Str(f.Upx)},
- {"Package", bool2Str(!f.NoPackage)},
- {"Clean Bin Dir", bool2Str(f.Clean)},
- {"LDFlags", f.LdFlags},
- {"Tags", "[" + strings.Join(compiledTags, ",") + "]"},
- {"Race Detector", bool2Str(f.RaceDetector)},
- }...)
- if len(buildOptions.OutputFile) > 0 && f.GetTargets().Length() == 1 {
- tableData = append(tableData, []string{"Output File", f.OutputFilename})
- }
- pterm.DefaultSection.Println("Build Options")
-
- err = pterm.DefaultTable.WithData(tableData).Render()
- if err != nil {
- return err
- }
-
- if !f.NoSyncGoMod {
- err = gomod.SyncGoMod(logger, f.UpdateWailsVersionGoMod)
- if err != nil {
- return err
- }
- }
-
- // Check platform
- validPlatformArch := slicer.String([]string{
- "darwin",
- "darwin/amd64",
- "darwin/arm64",
- "darwin/universal",
- "linux",
- "linux/amd64",
- "linux/arm64",
- "linux/arm",
- "windows",
- "windows/amd64",
- "windows/arm64",
- "windows/386",
- })
-
- outputBinaries := map[string]string{}
-
- // Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs
- // returning an error.
- var targetErr error
- targets := f.GetTargets()
- targets.Each(func(platform string) {
- if targetErr != nil {
- return
- }
-
- if !validPlatformArch.Contains(platform) {
- buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(","))
- return
- }
-
- desiredFilename := projectOptions.OutputFilename
- if desiredFilename == "" {
- desiredFilename = projectOptions.Name
- }
- desiredFilename = strings.TrimSuffix(desiredFilename, ".exe")
-
- // Calculate platform and arch
- platformSplit := strings.Split(platform, "/")
- buildOptions.Platform = platformSplit[0]
- buildOptions.Arch = f.GetDefaultArch()
- if len(platformSplit) > 1 {
- buildOptions.Arch = platformSplit[1]
- }
- banner := "Building target: " + buildOptions.Platform + "/" + buildOptions.Arch
- pterm.DefaultSection.Println(banner)
-
- if f.Upx && platform == "darwin/universal" {
- pterm.Warning.Println("Warning: compress flag unsupported for universal binaries. Ignoring.")
- f.Upx = false
- }
-
- switch buildOptions.Platform {
- case "linux":
- if runtime.GOOS != "linux" {
- pterm.Warning.Println("Crosscompiling to Linux not currently supported.")
- return
- }
- case "darwin":
- if runtime.GOOS != "darwin" {
- pterm.Warning.Println("Crosscompiling to Mac not currently supported.")
- return
- }
- macTargets := targets.Filter(func(platform string) bool {
- return strings.HasPrefix(platform, "darwin")
- })
- if macTargets.Length() == 2 {
- buildOptions.BundleName = fmt.Sprintf("%s-%s.app", desiredFilename, buildOptions.Arch)
- }
- }
-
- if targets.Length() > 1 {
- // target filename
- switch buildOptions.Platform {
- case "windows":
- desiredFilename = fmt.Sprintf("%s-%s", desiredFilename, buildOptions.Arch)
- case "linux", "darwin":
- desiredFilename = fmt.Sprintf("%s-%s-%s", desiredFilename, buildOptions.Platform, buildOptions.Arch)
- }
- }
- if buildOptions.Platform == "windows" {
- desiredFilename += ".exe"
- }
- buildOptions.OutputFile = desiredFilename
-
- if f.OutputFilename != "" {
- buildOptions.OutputFile = f.OutputFilename
- }
-
- if f.Obfuscated && f.SkipBindings {
- pterm.Warning.Println("obfuscated flag overrides skipbindings flag.")
- buildOptions.SkipBindings = false
- }
-
- if !f.DryRun {
- // Start Time
- start := time.Now()
-
- compiledBinary, err := build.Build(buildOptions)
- if err != nil {
- pterm.Error.Println(err.Error())
- targetErr = err
- return
- }
-
- buildOptions.IgnoreFrontend = true
- buildOptions.CleanBinDirectory = false
-
- // Output stats
- buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", compiledBinary, time.Since(start).Round(time.Millisecond).String()))
-
- outputBinaries[buildOptions.Platform+"/"+buildOptions.Arch] = compiledBinary
- } else {
- pterm.Info.Println("Dry run: skipped build.")
- }
- })
-
- if targetErr != nil {
- return targetErr
- }
-
- if f.DryRun {
- return nil
- }
-
- if f.NSIS {
- amd64Binary := outputBinaries["windows/amd64"]
- arm64Binary := outputBinaries["windows/arm64"]
- if amd64Binary == "" && arm64Binary == "" {
- return fmt.Errorf("cannot build nsis installer - no windows targets")
- }
-
- if err := build.GenerateNSISInstaller(buildOptions, amd64Binary, arm64Binary); err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/v2/cmd/wails/dev.go b/v2/cmd/wails/dev.go
deleted file mode 100644
index 30213a68e..000000000
--- a/v2/cmd/wails/dev.go
+++ /dev/null
@@ -1,37 +0,0 @@
-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"
-)
-
-func devApplication(f *flags.Dev) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- quiet := f.Verbosity == flags.Quiet
-
- // Create logger
- logger := clilogger.New(os.Stdout)
- logger.Mute(quiet)
-
- if quiet {
- pterm.DisableOutput()
- } else {
- app.PrintBanner()
- }
-
- err := f.Process()
- if err != nil {
- return err
- }
-
- return dev.Application(f, logger)
-}
diff --git a/v2/cmd/wails/doctor.go b/v2/cmd/wails/doctor.go
deleted file mode 100644
index 7f453133d..000000000
--- a/v2/cmd/wails/doctor.go
+++ /dev/null
@@ -1,262 +0,0 @@
-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"
- "github.com/wailsapp/wails/v2/internal/system/packagemanager"
-)
-
-func diagnoseEnvironment(f *flags.Doctor) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- pterm.DefaultSection = *pterm.DefaultSection.
- WithBottomPadding(0).
- WithStyle(pterm.NewStyle(pterm.FgBlue, pterm.Bold))
-
- pterm.Println() // Spacer
- pterm.DefaultHeader.WithBackgroundStyle(pterm.NewStyle(pterm.BgLightBlue)).WithMargin(10).Println("Wails Doctor")
- pterm.Println() // Spacer
-
- spinner, _ := pterm.DefaultSpinner.WithRemoveWhenDone().Start("Scanning system - Please wait (this may take a long time)...")
-
- // Get system info
- info, err := system.GetInfo()
- if err != nil {
- spinner.Fail()
- pterm.Error.Println("Failed to get system information")
- return err
- }
- spinner.Success()
-
- pterm.DefaultSection.Println("Wails")
-
- wailsTableData := pterm.TableData{
- {"Version", app.Version()},
- }
-
- if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
- buildSettingToName := map[string]string{
- "vcs.revision": "Revision",
- "vcs.modified": "Modified",
- }
- for _, buildSetting := range buildInfo.Settings {
- name := buildSettingToName[buildSetting.Key]
- if name == "" {
- continue
- }
- wailsTableData = append(wailsTableData, []string{name, buildSetting.Value})
- }
- }
-
- // Exit early if PM not found
- if info.PM != nil {
- wailsTableData = append(wailsTableData, []string{"Package Manager", info.PM.Name()})
- }
-
- err = pterm.DefaultTable.WithData(wailsTableData).Render()
- if err != nil {
- return err
- }
-
- pterm.DefaultSection.Println("System")
-
- systemTabledata := pterm.TableData{
- {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
- }
-
- pterm.DefaultSection.Println("Dependencies")
-
- // Output Dependencies Status
- var dependenciesMissing []string
- var externalPackages []*packagemanager.Dependency
- dependenciesAvailableRequired := 0
- dependenciesAvailableOptional := 0
-
- dependenciesTableData := pterm.TableData{
- {"Dependency", "Package Name", "Status", "Version"},
- }
-
- hasOptionalDependencies := false
- // Loop over dependencies
- for _, dependency := range info.Dependencies {
- name := dependency.Name
-
- if dependency.Optional {
- name = pterm.Gray("*") + name
- hasOptionalDependencies = true
- }
-
- packageName := "Unknown"
- status := pterm.LightRed("Not Found")
-
- // If we found the package
- if dependency.PackageName != "" {
- packageName = dependency.PackageName
-
- // If it's installed, update the status
- if dependency.Installed {
- status = pterm.LightGreen("Installed")
- } else {
- // Generate meaningful status text
- status = pterm.LightMagenta("Available")
-
- if dependency.Optional {
- dependenciesAvailableOptional++
- } else {
- dependenciesAvailableRequired++
- }
- }
- } else {
- if !dependency.Optional {
- dependenciesMissing = append(dependenciesMissing, dependency.Name)
- }
-
- if dependency.External {
- externalPackages = append(externalPackages, dependency)
- }
- }
-
- dependenciesTableData = append(dependenciesTableData, []string{name, packageName, status, dependency.Version})
- }
-
- dependenciesTableString, _ := pterm.DefaultTable.WithHasHeader(true).WithData(dependenciesTableData).Srender()
- dependenciesBox := pterm.DefaultBox.WithTitleBottomCenter()
-
- if hasOptionalDependencies {
- dependenciesBox = dependenciesBox.WithTitle(pterm.Gray("*") + " - Optional Dependency")
- }
-
- dependenciesBox.Println(dependenciesTableString)
-
- pterm.DefaultSection.Println("Diagnosis")
-
- // Generate an appropriate diagnosis
-
- if dependenciesAvailableRequired != 0 {
- pterm.Println("Required package(s) installation details: \n" + info.Dependencies.InstallAllRequiredCommand())
- }
-
- if dependenciesAvailableOptional != 0 {
- pterm.Println("Optional package(s) installation details: \n" + info.Dependencies.InstallAllOptionalCommand())
- }
-
- if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
- pterm.Success.Println("Your system is ready for Wails development!")
- } else {
- pterm.Warning.Println("Your system has missing dependencies!")
- }
-
- if len(dependenciesMissing) != 0 {
- pterm.Println("Fatal:")
- pterm.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
- }
-
- pterm.Println() // Spacer for sponsor message
- return nil
-}
diff --git a/v2/cmd/wails/flags/build.go b/v2/cmd/wails/flags/build.go
deleted file mode 100644
index db05c9035..000000000
--- a/v2/cmd/wails/flags/build.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package flags
-
-import (
- "fmt"
- "os"
- "os/exec"
- "runtime"
- "strings"
-
- "github.com/leaanthony/slicer"
- "github.com/wailsapp/wails/v2/internal/system"
- "github.com/wailsapp/wails/v2/pkg/commands/build"
- "github.com/wailsapp/wails/v2/pkg/commands/buildtags"
-)
-
-const (
- Quiet int = 0
- Normal int = 1
- Verbose int = 2
-)
-
-// TODO: unify this and `build.Options`
-type Build struct {
- Common
- BuildCommon
-
- NoPackage bool `description:"Skips platform specific packaging"`
- 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"`
- OutputFilename string `name:"o" description:"Output filename"`
- Clean bool `description:"Clean the bin directory before building"`
- WebView2 string `description:"WebView2 installer strategy: download,embed,browser,error"`
- 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"`
- Obfuscated bool `description:"Code obfuscation of bound Wails methods"`
- GarbleArgs string `description:"Arguments to pass to garble"`
- DryRun bool `description:"Prints the build command without executing it"`
-
- // Build Specific
-
- // Internal state
- compilerPath string
- userTags []string
- wv2rtstrategy string // WebView2 runtime strategy
- defaultArch string // Default architecture
-}
-
-func (b *Build) Default() *Build {
- defaultPlatform := os.Getenv("GOOS")
- if defaultPlatform == "" {
- defaultPlatform = runtime.GOOS
- }
- defaultArch := os.Getenv("GOARCH")
- if defaultArch == "" {
- if system.IsAppleSilicon {
- defaultArch = "arm64"
- } else {
- defaultArch = runtime.GOARCH
- }
- }
-
- result := &Build{
- Platform: defaultPlatform + "/" + defaultArch,
- WebView2: "download",
- GarbleArgs: "-literals -tiny -seed=random",
-
- defaultArch: defaultArch,
- }
- result.BuildCommon = result.BuildCommon.Default()
- return result
-}
-
-func (b *Build) GetBuildMode() build.Mode {
- if b.Debug {
- return build.Debug
- }
- return build.Production
-}
-
-func (b *Build) GetWebView2Strategy() string {
- return b.wv2rtstrategy
-}
-
-func (b *Build) GetTargets() *slicer.StringSlicer {
- var targets slicer.StringSlicer
- targets.AddSlice(strings.Split(b.Platform, ","))
- targets.Deduplicate()
- return &targets
-}
-
-func (b *Build) GetCompilerPath() string {
- return b.compilerPath
-}
-
-func (b *Build) GetTags() []string {
- return b.userTags
-}
-
-func (b *Build) Process() error {
- // Lookup compiler path
- var err error
- b.compilerPath, err = exec.LookPath(b.Compiler)
- if err != nil {
- return fmt.Errorf("unable to find compiler: %s", b.Compiler)
- }
-
- // Process User Tags
- b.userTags, err = buildtags.Parse(b.Tags)
- if err != nil {
- return err
- }
-
- // WebView2 installer strategy (download by default)
- b.WebView2 = strings.ToLower(b.WebView2)
- if b.WebView2 != "" {
- validWV2Runtime := slicer.String([]string{"download", "embed", "browser", "error"})
- if !validWV2Runtime.Contains(b.WebView2) {
- return fmt.Errorf("invalid option for flag 'webview2': %s", b.WebView2)
- }
- b.wv2rtstrategy = "wv2runtime." + b.WebView2
- }
-
- return nil
-}
-
-func bool2Str(b bool) string {
- if b {
- return "true"
- }
- return "false"
-}
-
-func (b *Build) GetBuildModeAsString() string {
- if b.Debug {
- return "debug"
- }
- return "production"
-}
-
-func (b *Build) GetDefaultArch() string {
- return b.defaultArch
-}
-
-/*
- _, _ = fmt.Fprintf(w, "Frontend Directory: \t%s\n", projectOptions.GetFrontendDir())
- _, _ = fmt.Fprintf(w, "Obfuscated: \t%t\n", buildOptions.Obfuscated)
- if buildOptions.Obfuscated {
- _, _ = fmt.Fprintf(w, "Garble Args: \t%s\n", buildOptions.GarbleArgs)
- }
- _, _ = fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
- _, _ = fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
- _, _ = fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
- _, _ = fmt.Fprintf(w, "Clean Bin Dir: \t%t\n", buildOptions.CleanBinDirectory)
- _, _ = fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
- _, _ = fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
- _, _ = fmt.Fprintf(w, "Race Detector: \t%t\n", buildOptions.RaceDetector)
- if len(buildOptions.OutputFile) > 0 && targets.Length() == 1 {
- _, _ = fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
- }
-*/
diff --git a/v2/cmd/wails/flags/buildcommon.go b/v2/cmd/wails/flags/buildcommon.go
deleted file mode 100644
index a22f7a502..000000000
--- a/v2/cmd/wails/flags/buildcommon.go
+++ /dev/null
@@ -1,21 +0,0 @@
-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"`
-}
-
-func (c BuildCommon) Default() BuildCommon {
- return BuildCommon{
- Compiler: "go",
- Verbosity: 1,
- }
-}
diff --git a/v2/cmd/wails/flags/common.go b/v2/cmd/wails/flags/common.go
deleted file mode 100644
index e58eff411..000000000
--- a/v2/cmd/wails/flags/common.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package flags
-
-type Common struct {
- NoColour bool `description:"Disable colour in output"`
-}
diff --git a/v2/cmd/wails/flags/dev.go b/v2/cmd/wails/flags/dev.go
deleted file mode 100644
index d31d8bc87..000000000
--- a/v2/cmd/wails/flags/dev.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package flags
-
-import (
- "fmt"
- "net"
- "net/url"
- "os"
- "path/filepath"
- "runtime"
-
- "github.com/samber/lo"
- "github.com/wailsapp/wails/v2/internal/project"
- "github.com/wailsapp/wails/v2/pkg/commands/build"
-)
-
-type Dev struct {
- BuildCommon
-
- AssetDir string `flag:"assetdir" description:"Serve assets from the given directory instead of using the provided asset FS"`
- Extensions string `flag:"e" description:"Extensions to trigger rebuilds (comma separated) eg go"`
- ReloadDirs string `flag:"reloaddirs" description:"Additional directories to trigger reloads (comma separated)"`
- 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"`
- Debounce int `flag:"debounce" description:"The amount of time to wait to trigger a reload on change"`
- DevServer string `flag:"devserver" description:"The address of the wails dev server"`
- 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
- projectConfig *project.Project
-}
-
-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 {
- return err
- }
-
- if _, _, err := net.SplitHostPort(d.DevServer); err != nil {
- return fmt.Errorf("DevServer is not of the form 'host:port', please check your wails.json")
- }
-
- d.devServerURL, err = url.Parse("http://" + d.DevServer)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (d *Dev) loadAndMergeProjectConfig() error {
- var err error
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- d.projectConfig, err = project.Load(cwd)
- if err != nil {
- return err
- }
-
- d.AssetDir, _ = lo.Coalesce(d.AssetDir, d.projectConfig.AssetDirectory)
- d.projectConfig.AssetDirectory = filepath.ToSlash(d.AssetDir)
- if d.AssetDir != "" {
- d.AssetDir, err = filepath.Abs(d.AssetDir)
- if err != nil {
- return err
- }
- }
-
- d.ReloadDirs, _ = lo.Coalesce(d.ReloadDirs, d.projectConfig.ReloadDirectories)
- d.projectConfig.ReloadDirectories = filepath.ToSlash(d.ReloadDirs)
- d.DevServer, _ = lo.Coalesce(d.DevServer, d.projectConfig.DevServer)
- d.projectConfig.DevServer = d.DevServer
- d.FrontendDevServerURL, _ = lo.Coalesce(d.FrontendDevServerURL, d.projectConfig.FrontendDevServerURL)
- d.projectConfig.FrontendDevServerURL = d.FrontendDevServerURL
- d.WailsJSDir, _ = lo.Coalesce(d.WailsJSDir, d.projectConfig.GetWailsJSDir(), d.projectConfig.GetFrontendDir())
- d.projectConfig.WailsJSDir = filepath.ToSlash(d.WailsJSDir)
-
- if d.Debounce == 100 && d.projectConfig.DebounceMS != 100 {
- if d.projectConfig.DebounceMS == 0 {
- d.projectConfig.DebounceMS = 100
- }
- d.Debounce = d.projectConfig.DebounceMS
- }
- d.projectConfig.DebounceMS = d.Debounce
-
- 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 {
- return err
- }
- }
-
- 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,
- }
-
- return result
-}
-
-func (d *Dev) ProjectConfig() *project.Project {
- return d.projectConfig
-}
-
-func (d *Dev) DevServerURL() *url.URL {
- return d.devServerURL
-}
diff --git a/v2/cmd/wails/flags/doctor.go b/v2/cmd/wails/flags/doctor.go
deleted file mode 100644
index e4816b969..000000000
--- a/v2/cmd/wails/flags/doctor.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package flags
-
-type Doctor struct {
- Common
-}
-
-func (b *Doctor) Default() *Doctor {
- return &Doctor{}
-}
diff --git a/v2/cmd/wails/flags/generate.go b/v2/cmd/wails/flags/generate.go
deleted file mode 100644
index b14d67017..000000000
--- a/v2/cmd/wails/flags/generate.go
+++ /dev/null
@@ -1,21 +0,0 @@
-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)"`
-}
-
-type GenerateTemplate struct {
- Common
- Name string `description:"Name of the template to generate"`
- 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
deleted file mode 100644
index 16d56a207..000000000
--- a/v2/cmd/wails/flags/init.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package flags
-
-type Init struct {
- Common
-
- TemplateName string `name:"t" description:"Name of built-in template to use, path to template or template url"`
- ProjectName string `name:"n" description:"Name of project"`
- CIMode bool `name:"ci" description:"CI Mode"`
- ProjectDir string `name:"d" description:"Project directory"`
- Quiet bool `name:"q" description:"Suppress output to console"`
- InitGit bool `name:"g" description:"Initialise git repository"`
- IDE string `name:"ide" description:"Generate IDE project files"`
- List bool `name:"l" description:"List templates"`
-}
-
-func (i *Init) Default() *Init {
- result := &Init{
- TemplateName: "vanilla",
- }
- return result
-}
diff --git a/v2/cmd/wails/flags/show.go b/v2/cmd/wails/flags/show.go
deleted file mode 100644
index a8220f3cc..000000000
--- a/v2/cmd/wails/flags/show.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package flags
-
-type ShowReleaseNotes struct {
- Common
- Version string `description:"The version to show the release notes for"`
-}
diff --git a/v2/cmd/wails/flags/update.go b/v2/cmd/wails/flags/update.go
deleted file mode 100644
index ffd143a9f..000000000
--- a/v2/cmd/wails/flags/update.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package flags
-
-type Update struct {
- Common
- Version string `description:"The version to update to"`
- PreRelease bool `name:"pre" description:"Update to latest pre-release"`
-}
diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go
deleted file mode 100644
index 15a6b33d8..000000000
--- a/v2/cmd/wails/generate.go
+++ /dev/null
@@ -1,250 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/leaanthony/debme"
- "github.com/leaanthony/gosod"
- "github.com/pterm/pterm"
- "github.com/tidwall/sjson"
- "github.com/wailsapp/wails/v2/cmd/wails/flags"
- "github.com/wailsapp/wails/v2/cmd/wails/internal/template"
- "github.com/wailsapp/wails/v2/internal/colour"
- "github.com/wailsapp/wails/v2/internal/fs"
- "github.com/wailsapp/wails/v2/internal/project"
- "github.com/wailsapp/wails/v2/pkg/clilogger"
- "github.com/wailsapp/wails/v2/pkg/commands/bindings"
- "github.com/wailsapp/wails/v2/pkg/commands/buildtags"
-)
-
-func generateModule(f *flags.GenerateModule) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- quiet := f.Verbosity == flags.Quiet
- logger := clilogger.New(os.Stdout)
- logger.Mute(quiet)
-
- buildTags, err := buildtags.Parse(f.Tags)
- if err != nil {
- return err
- }
-
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- projectConfig, err := project.Load(cwd)
- if err != nil {
- 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,
- })
- if err != nil {
- return err
- }
- return nil
-}
-
-func generateTemplate(f *flags.GenerateTemplate) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- quiet := f.Quiet
- logger := clilogger.New(os.Stdout)
- logger.Mute(quiet)
-
- // name is mandatory
- if f.Name == "" {
- return fmt.Errorf("please provide a template name using the -name flag")
- }
-
- // If the current directory is not empty, we create a new directory
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- templateDir := filepath.Join(cwd, f.Name)
- if !fs.DirExists(templateDir) {
- err := os.MkdirAll(templateDir, 0o755)
- if err != nil {
- return err
- }
- }
- empty, err := fs.DirIsEmpty(templateDir)
- if err != nil {
- return err
- }
-
- pterm.DefaultSection.Println("Generating template")
-
- if !empty {
- templateDir = filepath.Join(cwd, f.Name)
- printBulletPoint("Creating new template directory:", f.Name)
- err = fs.Mkdir(templateDir)
- if err != nil {
- return err
- }
- }
-
- // Create base template
- baseTemplate, err := debme.FS(template.Base, "base")
- if err != nil {
- return err
- }
- g := gosod.New(baseTemplate)
- g.SetTemplateFilters([]string{".template"})
-
- err = os.Chdir(templateDir)
- if err != nil {
- return err
- }
-
- type templateData struct {
- Name string
- Description string
- TemplateDir string
- WailsVersion string
- }
-
- printBulletPoint("Extracting base template files...")
-
- err = g.Extract(templateDir, &templateData{
- Name: f.Name,
- TemplateDir: templateDir,
- WailsVersion: app.Version(),
- })
- if err != nil {
- return err
- }
-
- err = os.Chdir(cwd)
- if err != nil {
- return err
- }
-
- // If we aren't migrating the files, just exit
- if f.Frontend == "" {
- pterm.Println()
- pterm.Println()
- pterm.Info.Println("No frontend specified to migrate. Template created.")
- pterm.Println()
- return nil
- }
-
- // Remove frontend directory
- frontendDir := filepath.Join(templateDir, "frontend")
- err = os.RemoveAll(frontendDir)
- if err != nil {
- return err
- }
-
- // Copy the files into a new frontend directory
- printBulletPoint("Migrating existing project files to frontend directory...")
-
- sourceDir, err := filepath.Abs(f.Frontend)
- if err != nil {
- return err
- }
-
- newFrontendDir := filepath.Join(templateDir, "frontend")
- err = fs.CopyDirExtended(sourceDir, newFrontendDir, []string{f.Name, "node_modules"})
- if err != nil {
- return err
- }
-
- // Process package.json
- err = processPackageJSON(frontendDir)
- if err != nil {
- return err
- }
-
- // Process package-lock.json
- err = processPackageLockJSON(frontendDir)
- if err != nil {
- return err
- }
-
- // Remove node_modules - ignore error, eg it doesn't exist
- _ = os.RemoveAll(filepath.Join(frontendDir, "node_modules"))
-
- return nil
-}
-
-func processPackageJSON(frontendDir string) error {
- var err error
-
- packageJSON := filepath.Join(frontendDir, "package.json")
- if !fs.FileExists(packageJSON) {
- return fmt.Errorf("no package.json found - cannot process")
- }
-
- json, err := os.ReadFile(packageJSON)
- if err != nil {
- return err
- }
-
- // We will ignore these errors - it's not critical
- printBulletPoint("Updating package.json data...")
- json, _ = sjson.SetBytes(json, "name", "{{.ProjectName}}")
- json, _ = sjson.SetBytes(json, "author", "{{.AuthorName}}")
-
- err = os.WriteFile(packageJSON, json, 0o644)
- if err != nil {
- return err
- }
- baseDir := filepath.Dir(packageJSON)
- printBulletPoint("Renaming package.json -> package.tmpl.json...")
- err = os.Rename(packageJSON, filepath.Join(baseDir, "package.tmpl.json"))
- if err != nil {
- return err
- }
- return nil
-}
-
-func processPackageLockJSON(frontendDir string) error {
- var err error
-
- filename := filepath.Join(frontendDir, "package-lock.json")
- if !fs.FileExists(filename) {
- return fmt.Errorf("no package-lock.json found - cannot process")
- }
-
- data, err := os.ReadFile(filename)
- if err != nil {
- return err
- }
- json := string(data)
-
- // We will ignore these errors - it's not critical
- printBulletPoint("Updating package-lock.json data...")
- json, _ = sjson.Set(json, "name", "{{.ProjectName}}")
-
- err = os.WriteFile(filename, []byte(json), 0o644)
- if err != nil {
- return err
- }
- baseDir := filepath.Dir(filename)
- printBulletPoint("Renaming package-lock.json -> package-lock.tmpl.json...")
- err = os.Rename(filename, filepath.Join(baseDir, "package-lock.tmpl.json"))
- if err != nil {
- return err
- }
- return nil
-}
diff --git a/v2/cmd/wails/init.go b/v2/cmd/wails/init.go
deleted file mode 100644
index f79e37ffc..000000000
--- a/v2/cmd/wails/init.go
+++ /dev/null
@@ -1,295 +0,0 @@
-package main
-
-import (
- "bufio"
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/flytam/filenamify"
- "github.com/leaanthony/slicer"
- "github.com/pkg/errors"
- "github.com/pterm/pterm"
- "github.com/wailsapp/wails/v2/cmd/wails/flags"
- "github.com/wailsapp/wails/v2/internal/colour"
- "github.com/wailsapp/wails/v2/pkg/buildassets"
- "github.com/wailsapp/wails/v2/pkg/clilogger"
- "github.com/wailsapp/wails/v2/pkg/git"
- "github.com/wailsapp/wails/v2/pkg/templates"
-)
-
-func initProject(f *flags.Init) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- quiet := f.Quiet
-
- // Create logger
- logger := clilogger.New(os.Stdout)
- logger.Mute(quiet)
-
- // Are we listing templates?
- if f.List {
- app.PrintBanner()
- templateList, err := templates.List()
- if err != nil {
- return err
- }
-
- pterm.DefaultSection.Println("Available templates")
-
- table := pterm.TableData{{"Template", "Short Name", "Description"}}
- for _, template := range templateList {
- table = append(table, []string{template.Name, template.ShortName, template.Description})
- }
- err = pterm.DefaultTable.WithHasHeader(true).WithBoxed(true).WithData(table).Render()
- pterm.Println()
- return err
- }
-
- // Validate name
- if len(f.ProjectName) == 0 {
- return fmt.Errorf("please provide a project name using the -n flag")
- }
-
- // Validate IDE option
- supportedIDEs := slicer.String([]string{"vscode", "goland"})
- ide := strings.ToLower(f.IDE)
- if ide != "" {
- if !supportedIDEs.Contains(ide) {
- return fmt.Errorf("ide '%s' not supported. Valid values: %s", ide, supportedIDEs.Join(" "))
- }
- }
-
- if !quiet {
- app.PrintBanner()
- }
-
- pterm.DefaultSection.Printf("Initialising Project '%s'", f.ProjectName)
-
- projectFilename, err := filenamify.Filenamify(f.ProjectName, filenamify.Options{
- Replacement: "_",
- MaxLength: 255,
- })
- if err != nil {
- return err
- }
- goBinary, err := exec.LookPath("go")
- if err != nil {
- return fmt.Errorf("unable to find Go compiler. Please download and install Go: https://golang.org/dl/")
- }
-
- // Get base path and convert to forward slashes
- goPath := filepath.ToSlash(filepath.Dir(goBinary))
- // Trim bin directory
- goSDKPath := strings.TrimSuffix(goPath, "/bin")
-
- // Create Template Options
- options := &templates.Options{
- ProjectName: f.ProjectName,
- TargetDir: f.ProjectDir,
- TemplateName: f.TemplateName,
- Logger: logger,
- IDE: ide,
- InitGit: f.InitGit,
- ProjectNameFilename: projectFilename,
- WailsVersion: app.Version(),
- GoSDKPath: goSDKPath,
- }
-
- // Try to discover author details from git config
- findAuthorDetails(options)
-
- // Start Time
- start := time.Now()
-
- // Install the template
- remote, template, err := templates.Install(options)
- if err != nil {
- return err
- }
-
- // Install the default assets
- err = buildassets.Install(options.TargetDir)
- if err != nil {
- return err
- }
-
- err = os.Chdir(options.TargetDir)
- if err != nil {
- 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")
- cmd.Dir = options.TargetDir
- cmd.Stderr = os.Stderr
- if !quiet {
- cmd.Stdout = os.Stdout
- }
- err = cmd.Run()
- if err != nil {
- return err
- }
- } else {
- // Update go mod
- workspace := os.Getenv("GITHUB_WORKSPACE")
- pterm.Println("GitHub workspace:", workspace)
- if workspace == "" {
- os.Exit(1)
- }
- updateReplaceLine(workspace)
- }
-
- // Remove the `.git`` directory in the template project
- err = os.RemoveAll(".git")
- if err != nil {
- return err
- }
-
- if options.InitGit {
- err = initGit(options)
- if err != nil {
- return err
- }
- }
-
- if quiet {
- return nil
- }
-
- // Output stats
- elapsed := time.Since(start)
-
- // Create pterm table
- table := pterm.TableData{
- {"Project Name", options.ProjectName},
- {"Project Directory", options.TargetDir},
- {"Template", template.Name},
- {"Template Source", template.HelpURL},
- }
- err = pterm.DefaultTable.WithData(table).Render()
- if err != nil {
- return err
- }
-
- // IDE message
- switch options.IDE {
- case "vscode":
- pterm.Println()
- pterm.Info.Println("VSCode config files generated.")
- case "goland":
- pterm.Println()
- pterm.Info.Println("Goland config files generated.")
- }
-
- if options.InitGit {
- pterm.Info.Println("Git repository initialised.")
- }
-
- if remote {
- pterm.Warning.Println("NOTE: You have created a project using a remote template. The Wails project takes no responsibility for 3rd party templates. Only use remote templates that you trust.")
- }
-
- pterm.Println("")
- pterm.Printf("Initialised project '%s' in %s.\n", options.ProjectName, elapsed.Round(time.Millisecond).String())
- pterm.Println("")
-
- return nil
-}
-
-func initGit(options *templates.Options) error {
- err := git.InitRepo(options.TargetDir)
- if err != nil {
- return errors.Wrap(err, "Unable to initialise git repository:")
- }
-
- ignore := []string{
- "build/bin",
- "frontend/dist",
- "frontend/node_modules",
- }
- err = os.WriteFile(filepath.Join(options.TargetDir, ".gitignore"), []byte(strings.Join(ignore, "\n")), 0o644)
- if err != nil {
- return errors.Wrap(err, "Unable to create gitignore")
- }
-
- return nil
-}
-
-// findAuthorDetails tries to find the user's name and email
-// from gitconfig. If it finds them, it stores them in the project options
-func findAuthorDetails(options *templates.Options) {
- if git.IsInstalled() {
- name, err := git.Name()
- if err == nil {
- options.AuthorName = strings.TrimSpace(name)
- }
-
- email, err := git.Email()
- if err == nil {
- options.AuthorEmail = strings.TrimSpace(email)
- }
- }
-}
-
-func updateReplaceLine(targetPath string) {
- file, err := os.Open("go.mod")
- if err != nil {
- fatal(err.Error())
- }
-
- var lines []string
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- lines = append(lines, scanner.Text())
- }
-
- err = file.Close()
- if err != nil {
- fatal(err.Error())
- }
-
- if err := scanner.Err(); err != nil {
- fatal(err.Error())
- }
-
- for i, line := range lines {
- println(line)
- if strings.HasPrefix(line, "// replace") {
- pterm.Println("Found replace line")
- splitLine := strings.Split(line, " ")
- splitLine[5] = targetPath + "/v2"
- lines[i] = strings.Join(splitLine[1:], " ")
- continue
- }
- }
-
- err = os.WriteFile("go.mod", []byte(strings.Join(lines, "\n")), 0o644)
- 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/commands/build/build.go b/v2/cmd/wails/internal/commands/build/build.go
new file mode 100644
index 000000000..1ea3ec18f
--- /dev/null
+++ b/v2/cmd/wails/internal/commands/build/build.go
@@ -0,0 +1,119 @@
+package build
+
+import (
+ "fmt"
+ "io"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/leaanthony/clir"
+ "github.com/leaanthony/slicer"
+ "github.com/wailsapp/wails/v2/pkg/clilogger"
+ "github.com/wailsapp/wails/v2/pkg/commands/build"
+)
+
+// AddBuildSubcommand adds the `build` command for the Wails application
+func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
+
+ outputType := "desktop"
+
+ validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
+
+ command := app.NewSubCommand("build", "Builds the application")
+
+ // Setup target type flag
+ description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
+ command.StringFlag("t", description, &outputType)
+
+ // Setup production flag
+ production := false
+ command.BoolFlag("production", "Build in production mode", &production)
+
+ // Setup pack flag
+ pack := false
+ command.BoolFlag("package", "Create a platform specific package", &pack)
+
+ compilerCommand := "go"
+ command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
+
+ // Setup Platform flag
+ platform := runtime.GOOS
+ command.StringFlag("platform", "Platform to target", &platform)
+
+ // Quiet Build
+ quiet := false
+ command.BoolFlag("q", "Suppress output to console", &quiet)
+
+ // ldflags to pass to `go`
+ ldflags := ""
+ command.StringFlag("ldflags", "optional ldflags", &ldflags)
+
+ // Log to file
+ logFile := ""
+ command.StringFlag("l", "Log to file", &logFile)
+
+ // Retain assets
+ keepAssets := false
+ command.BoolFlag("k", "Keep generated assets", &keepAssets)
+
+ command.Action(func() error {
+
+ // Create logger
+ logger := clilogger.New(w)
+ logger.Mute(quiet)
+
+ // Validate output type
+ if !validTargetTypes.Contains(outputType) {
+ return fmt.Errorf("output type '%s' is not valid", outputType)
+ }
+
+ if !quiet {
+ app.PrintBanner()
+ }
+
+ task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
+ logger.Println(task)
+ logger.Println(strings.Repeat("-", len(task)))
+
+ // Setup mode
+ mode := build.Debug
+ if production {
+ mode = build.Production
+ }
+
+ // Create BuildOptions
+ buildOptions := &build.Options{
+ Logger: logger,
+ OutputType: outputType,
+ Mode: mode,
+ Pack: pack,
+ Platform: platform,
+ LDFlags: ldflags,
+ Compiler: compilerCommand,
+ KeepAssets: keepAssets,
+ }
+
+ return doBuild(buildOptions)
+ })
+}
+
+// doBuild is our main build command
+func doBuild(buildOptions *build.Options) error {
+
+ // Start Time
+ start := time.Now()
+
+ outputFilename, err := build.Build(buildOptions)
+ if err != nil {
+ return err
+ }
+
+ // Output stats
+ elapsed := time.Since(start)
+ buildOptions.Logger.Println("")
+ buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
+ buildOptions.Logger.Println("")
+
+ return nil
+}
diff --git a/v2/cmd/wails/internal/commands/debug/debug.go b/v2/cmd/wails/internal/commands/debug/debug.go
new file mode 100644
index 000000000..90e2c43f5
--- /dev/null
+++ b/v2/cmd/wails/internal/commands/debug/debug.go
@@ -0,0 +1,123 @@
+package debug
+
+import (
+ "fmt"
+ "github.com/wailsapp/wails/v2/internal/shell"
+ "io"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/leaanthony/clir"
+ "github.com/leaanthony/slicer"
+ "github.com/wailsapp/wails/v2/pkg/clilogger"
+ "github.com/wailsapp/wails/v2/pkg/commands/build"
+)
+
+// AddSubcommand adds the `debug` command for the Wails application
+func AddSubcommand(app *clir.Cli, w io.Writer) error {
+
+ outputType := "desktop"
+
+ validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
+
+ command := app.NewSubCommand("debug", "Builds the application then runs delve on the binary")
+
+ // Setup target type flag
+ description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
+ command.StringFlag("t", description, &outputType)
+
+ compilerCommand := "go"
+ command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
+
+ quiet := false
+ command.BoolFlag("q", "Suppress output to console", &quiet)
+
+ // ldflags to pass to `go`
+ ldflags := ""
+ command.StringFlag("ldflags", "optional ldflags", &ldflags)
+
+ // Log to file
+ logFile := ""
+ command.StringFlag("l", "Log to file", &logFile)
+
+ command.Action(func() error {
+
+ // Create logger
+ logger := clilogger.New(w)
+ logger.Mute(quiet)
+
+ // Validate output type
+ if !validTargetTypes.Contains(outputType) {
+ return fmt.Errorf("output type '%s' is not valid", outputType)
+ }
+
+ if !quiet {
+ app.PrintBanner()
+ }
+
+ task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
+ logger.Println(task)
+ logger.Println(strings.Repeat("-", len(task)))
+
+ // Setup mode
+ mode := build.Debug
+
+ // Create BuildOptions
+ buildOptions := &build.Options{
+ Logger: logger,
+ OutputType: outputType,
+ Mode: mode,
+ Pack: false,
+ Platform: runtime.GOOS,
+ LDFlags: ldflags,
+ Compiler: compilerCommand,
+ KeepAssets: false,
+ }
+
+ outputFilename, err := doDebugBuild(buildOptions)
+ if err != nil {
+ return err
+ }
+
+ // Check delve exists
+ delveExists := shell.CommandExists("dlv")
+ if !delveExists {
+ return fmt.Errorf("cannot launch delve (Is it installed?)")
+ }
+
+ // Get cwd
+ cwd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ // Launch delve
+ println("Launching Delve on port 2345...")
+ command := shell.CreateCommand(cwd, "dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", outputFilename)
+ return command.Run()
+ })
+
+ return nil
+}
+
+// doDebugBuild is our main build command
+func doDebugBuild(buildOptions *build.Options) (string, error) {
+
+ // Start Time
+ start := time.Now()
+
+ outputFilename, err := build.Build(buildOptions)
+ if err != nil {
+ return "", err
+ }
+
+ // Output stats
+ elapsed := time.Since(start)
+ buildOptions.Logger.Println("")
+ buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
+ buildOptions.Logger.Println("")
+
+ return outputFilename, nil
+}
diff --git a/v2/cmd/wails/internal/commands/dev/dev.go b/v2/cmd/wails/internal/commands/dev/dev.go
new file mode 100644
index 000000000..dabd431d2
--- /dev/null
+++ b/v2/cmd/wails/internal/commands/dev/dev.go
@@ -0,0 +1,270 @@
+package dev
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "os/signal"
+ "runtime"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/leaanthony/clir"
+ "github.com/leaanthony/slicer"
+ "github.com/wailsapp/wails/v2/internal/fs"
+ "github.com/wailsapp/wails/v2/internal/process"
+ "github.com/wailsapp/wails/v2/pkg/clilogger"
+ "github.com/wailsapp/wails/v2/pkg/commands/build"
+)
+
+// AddSubcommand adds the `dev` command for the Wails application
+func AddSubcommand(app *clir.Cli, w io.Writer) error {
+
+ command := app.NewSubCommand("dev", "Development mode")
+
+ outputType := "desktop"
+
+ validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
+
+ // Setup target type flag
+ description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
+ command.StringFlag("t", description, &outputType)
+
+ // Passthrough ldflags
+ ldflags := ""
+ command.StringFlag("ldflags", "optional ldflags", &ldflags)
+
+ // compiler command
+ compilerCommand := "go"
+ command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
+
+ // extensions to trigger rebuilds
+ extensions := "go"
+ command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
+
+ command.Action(func() error {
+
+ // Validate inputs
+ if !validTargetTypes.Contains(outputType) {
+ return fmt.Errorf("output type '%s' is not valid", outputType)
+ }
+
+ // Create logger
+ logger := clilogger.New(w)
+ app.PrintBanner()
+
+ // TODO: Check you are in a project directory
+
+ watcher, err := fsnotify.NewWatcher()
+ if err != nil {
+ return err
+ }
+ defer watcher.Close()
+
+ var debugBinaryProcess *process.Process = nil
+ var buildFrontend bool = true
+ var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
+
+ // Setup signal handler
+ quitChannel := make(chan os.Signal, 1)
+ signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
+
+ debounceQuit := make(chan bool, 1)
+
+ // Do initial build
+ logger.Println("Building application for development...")
+ debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
+
+ go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
+ // logger.Println("event: %+v", event)
+
+ // Check for new directories
+ if event.Op&fsnotify.Create == fsnotify.Create {
+ // If this is a folder, add it to our watch list
+ if fs.DirExists(event.Name) {
+ if !strings.Contains(event.Name, "node_modules") {
+ err := watcher.Add(event.Name)
+ if err != nil {
+ logger.Fatal("%s", err.Error())
+ }
+ logger.Println("Watching directory: %s", event.Name)
+ }
+ }
+ return
+ }
+
+ // Check for file writes
+ if event.Op&fsnotify.Write == fsnotify.Write {
+
+ // logger.Println("modified file: %s", event.Name)
+ var rebuild bool = false
+
+ // Iterate all file patterns
+ for _, pattern := range extensionsThatTriggerARebuild {
+ rebuild = strings.HasSuffix(event.Name, pattern)
+ if err != nil {
+ logger.Fatal(err.Error())
+ }
+ if rebuild {
+ // Only build frontend when the file isn't a Go file
+ buildFrontend = !strings.HasSuffix(event.Name, "go")
+ break
+ }
+ }
+
+ if !rebuild {
+ logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
+ return
+ }
+
+ if buildFrontend {
+ logger.Println("Full rebuild triggered: %s updated", event.Name)
+ } else {
+ logger.Println("Partial build triggered: %s updated", event.Name)
+ }
+
+ // Do a rebuild
+
+ // Try and build the app
+ newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
+
+ // If we have a new process, save it
+ if newBinaryProcess != nil {
+ debugBinaryProcess = newBinaryProcess
+ }
+
+ }
+ })
+
+ // Get project dir
+ dir, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ // Get all subdirectories
+ dirs, err := fs.GetSubdirectories(dir)
+ if err != nil {
+ return err
+ }
+
+ // Setup a watcher for non-node_modules directories
+ dirs.Each(func(dir string) {
+ if strings.Contains(dir, "node_modules") {
+ return
+ }
+ logger.Println("Watching directory: %s", dir)
+ err = watcher.Add(dir)
+ if err != nil {
+ logger.Fatal(err.Error())
+ }
+ })
+
+ // Wait until we get a quit signal
+ quit := false
+ for quit == false {
+ select {
+ case <-quitChannel:
+ println()
+ // Notify debouncer to quit
+ debounceQuit <- true
+ quit = true
+ }
+ }
+
+ // Kill the current program if running
+ if debugBinaryProcess != nil {
+ err := debugBinaryProcess.Kill()
+ if err != nil {
+ return err
+ }
+ }
+
+ logger.Println("Development mode exited")
+
+ return nil
+ })
+
+ return nil
+}
+
+// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
+func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
+ var item fsnotify.Event
+ timer := time.NewTimer(interval)
+exit:
+ for {
+ select {
+ case item = <-input:
+ timer.Reset(interval)
+ case <-timer.C:
+ if item.Name != "" {
+ cb(item)
+ }
+ case <-quitChannel:
+ break exit
+ }
+ }
+}
+
+func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
+
+ appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
+ println()
+ if err != nil {
+ logger.Println("[ERROR] Build Failed: %s", err.Error())
+ return nil
+ }
+ logger.Println("Build new binary: %s", appBinary)
+
+ // Kill existing binary if need be
+ if debugBinaryProcess != nil {
+ killError := debugBinaryProcess.Kill()
+
+ if killError != nil {
+ logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
+ }
+
+ debugBinaryProcess = nil
+ }
+
+ // TODO: Generate `backend.js`
+
+ // Start up new binary
+ newProcess := process.NewProcess(logger, appBinary)
+ err = newProcess.Start()
+ if err != nil {
+ // Remove binary
+ deleteError := fs.DeleteFile(appBinary)
+ if deleteError != nil {
+ logger.Fatal("Unable to delete app binary: " + appBinary)
+ }
+ logger.Fatal("Unable to start application: %s", err.Error())
+ }
+
+ return newProcess
+}
+
+func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
+
+ // Create random output file
+ outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
+
+ // Create BuildOptions
+ buildOptions := &build.Options{
+ Logger: logger,
+ OutputType: outputType,
+ Mode: build.Debug,
+ Pack: false,
+ Platform: runtime.GOOS,
+ LDFlags: ldflags,
+ Compiler: compilerCommand,
+ OutputFile: outputFile,
+ IgnoreFrontend: !buildFrontend,
+ }
+
+ return build.Build(buildOptions)
+
+}
diff --git a/v2/cmd/wails/internal/commands/doctor/doctor.go b/v2/cmd/wails/internal/commands/doctor/doctor.go
new file mode 100644
index 000000000..27d310c94
--- /dev/null
+++ b/v2/cmd/wails/internal/commands/doctor/doctor.go
@@ -0,0 +1,154 @@
+package doctor
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "runtime"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/leaanthony/clir"
+ "github.com/wailsapp/wails/v2/internal/system"
+ "github.com/wailsapp/wails/v2/internal/system/packagemanager"
+ "github.com/wailsapp/wails/v2/pkg/clilogger"
+)
+
+// AddSubcommand adds the `doctor` command for the Wails application
+func AddSubcommand(app *clir.Cli, w io.Writer) error {
+
+ command := app.NewSubCommand("doctor", "Diagnose your environment")
+
+ command.Action(func() error {
+
+ logger := clilogger.New(w)
+
+ app.PrintBanner()
+ logger.Print("Scanning system - please wait...")
+
+ // Get system info
+ info, err := system.GetInfo()
+ if err != nil {
+ return err
+ }
+ logger.Println("Done.")
+
+ // Start a new tabwriter
+ w := new(tabwriter.Writer)
+ w.Init(os.Stdout, 8, 8, 0, '\t', 0)
+
+ // Write out the system information
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "System\n")
+ fmt.Fprintf(w, "------\n")
+ fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
+ fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
+ fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
+
+ // Exit early if PM not found
+ if info.PM == nil {
+ fmt.Fprintf(w, "\n%s\t%s", "Package Manager:", "Not Found")
+ w.Flush()
+ println()
+ return nil
+ }
+ fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
+
+ // Output Go Information
+ fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
+ fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
+ fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
+
+ // Output Dependencies Status
+ var dependenciesMissing = []string{}
+ var externalPackages = []*packagemanager.Dependancy{}
+ var dependenciesAvailableRequired = 0
+ var dependenciesAvailableOptional = 0
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
+ fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
+
+ // Loop over dependencies
+ for _, dependency := range info.Dependencies {
+
+ name := dependency.Name
+ if dependency.Optional {
+ name += "*"
+ }
+ packageName := "Unknown"
+ status := "Not Found"
+
+ // If we found the package
+ if dependency.PackageName != "" {
+
+ packageName = dependency.PackageName
+
+ // If it's installed, update the status
+ if dependency.Installed {
+ status = "Installed"
+ } else {
+ // Generate meaningful status text
+ status = "Available"
+
+ if dependency.Optional {
+ dependenciesAvailableOptional++
+ } else {
+ dependenciesAvailableRequired++
+ }
+ }
+ } else {
+ if !dependency.Optional {
+ dependenciesMissing = append(dependenciesMissing, dependency.Name)
+ }
+
+ if dependency.External {
+ externalPackages = append(externalPackages, dependency)
+ }
+ }
+
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, packageName, status, dependency.Version)
+ }
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "* - Optional Dependency\n")
+ w.Flush()
+ logger.Println("")
+ logger.Println("Diagnosis")
+ logger.Println("---------\n")
+
+ // Generate an appropriate diagnosis
+
+ if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
+ logger.Println("Your system is ready for Wails development!")
+ }
+
+ if dependenciesAvailableRequired != 0 {
+ log.Println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
+ }
+
+ if dependenciesAvailableOptional != 0 {
+ log.Println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
+ }
+
+ if len(externalPackages) > 0 {
+ for _, p := range externalPackages {
+ if p.Optional {
+ print("[Optional] ")
+ }
+ log.Println("Install " + p.Name + ": " + p.InstallCommand)
+ }
+ }
+
+ if len(dependenciesMissing) != 0 {
+ // TODO: Check if apps are available locally and if so, adjust the diagnosis
+ log.Println("Fatal:")
+ log.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
+ log.Println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
+ }
+
+ log.Println("")
+ return nil
+ })
+
+ return nil
+}
diff --git a/v2/cmd/wails/internal/commands/generate/generate.go b/v2/cmd/wails/internal/commands/generate/generate.go
new file mode 100644
index 000000000..7e99a9997
--- /dev/null
+++ b/v2/cmd/wails/internal/commands/generate/generate.go
@@ -0,0 +1,91 @@
+package generate
+
+import (
+ "io"
+ "time"
+
+ "github.com/leaanthony/clir"
+ "github.com/wailsapp/wails/v2/pkg/clilogger"
+ "github.com/wailsapp/wails/v2/pkg/parser"
+)
+
+// AddSubcommand adds the `dev` command for the Wails application
+func AddSubcommand(app *clir.Cli, w io.Writer) error {
+
+ command := app.NewSubCommand("generate", "Code Generation Tools")
+
+ // Backend API
+ backendAPI := command.NewSubCommand("module", "Generates a JS module for the frontend to interface with the backend")
+
+ // Quiet Init
+ quiet := false
+ backendAPI.BoolFlag("q", "Supress output to console", &quiet)
+
+ backendAPI.Action(func() error {
+
+ // Create logger
+ logger := clilogger.New(w)
+ logger.Mute(quiet)
+
+ app.PrintBanner()
+
+ logger.Print("Generating Javascript module for Go code...")
+
+ // Start Time
+ start := time.Now()
+
+ p, err := parser.GenerateWailsFrontendPackage()
+ if err != nil {
+ return err
+ }
+
+ logger.Println("done.")
+ logger.Println("")
+
+ elapsed := time.Since(start)
+ packages := p.Packages
+
+ // Print report
+ for _, pkg := range p.Packages {
+ if pkg.ShouldGenerate() {
+ logPackage(pkg, logger)
+ }
+
+ }
+
+ logger.Println("%d packages parsed in %s.", len(packages), elapsed)
+
+ return nil
+
+ })
+ return nil
+}
+
+func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
+
+ logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
+ for _, strct := range pkg.Structs() {
+ logger.Println("")
+ logger.Println(" Processed struct '" + strct.Name + "'")
+ if strct.IsBound {
+ for _, method := range strct.Methods {
+ logger.Println(" Bound method '" + method.Name + "'")
+ }
+ }
+ if strct.IsUsedAsData {
+ for _, field := range strct.Fields {
+ if !field.Ignored {
+ logger.Print(" Processed ")
+ if field.IsOptional {
+ logger.Print("optional ")
+ }
+ logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
+ }
+ }
+ }
+ }
+ logger.Println("")
+
+ // logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
+ // logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
+}
diff --git a/v2/cmd/wails/internal/commands/initialise/initialise.go b/v2/cmd/wails/internal/commands/initialise/initialise.go
new file mode 100644
index 000000000..9b20a0e8b
--- /dev/null
+++ b/v2/cmd/wails/internal/commands/initialise/initialise.go
@@ -0,0 +1,182 @@
+package initialise
+
+import (
+ "fmt"
+ "io"
+ "strings"
+ "time"
+
+ "github.com/leaanthony/clir"
+ "github.com/pkg/errors"
+ "github.com/wailsapp/wails/v2/internal/templates"
+ "github.com/wailsapp/wails/v2/pkg/clilogger"
+ "github.com/wailsapp/wails/v2/pkg/git"
+)
+
+// AddSubcommand adds the `init` command for the Wails application
+func AddSubcommand(app *clir.Cli, w io.Writer) error {
+
+ // Load the template shortnames
+ validShortNames, err := templates.TemplateShortNames()
+ if err != nil {
+ return err
+ }
+
+ command := app.NewSubCommand("init", "Initialise a new Wails project")
+
+ // Setup template name flag
+ templateName := "vanilla"
+ description := "Name of template to use. Valid tempates: " + validShortNames.Join(" ")
+ command.StringFlag("t", description, &templateName)
+
+ // Setup project name
+ projectName := ""
+ command.StringFlag("n", "Name of project", &projectName)
+
+ // Setup project directory
+ projectDirectory := ""
+ command.StringFlag("d", "Project directory", &projectDirectory)
+
+ // Quiet Init
+ quiet := false
+ command.BoolFlag("q", "Supress output to console", &quiet)
+
+ initGit := false
+ gitInstalled := git.IsInstalled()
+ if gitInstalled {
+ // Git Init
+ command.BoolFlag("g", "Initialise git repository", &initGit)
+ }
+
+ // VSCode project files
+ vscode := false
+ command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
+
+ // List templates
+ list := false
+ command.BoolFlag("l", "List templates", &list)
+
+ command.Action(func() error {
+
+ // Create logger
+ logger := clilogger.New(w)
+ logger.Mute(quiet)
+
+ // Are we listing templates?
+ if list {
+ app.PrintBanner()
+ err := templates.OutputList(logger)
+ logger.Println("")
+ return err
+ }
+
+ // Validate output type
+ if !validShortNames.Contains(templateName) {
+ logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
+ logger.Println("")
+ command.PrintHelp()
+ return nil
+ }
+
+ // Validate name
+ if len(projectName) == 0 {
+ logger.Println("ERROR: Project name required")
+ logger.Println("")
+ command.PrintHelp()
+ return nil
+ }
+
+ if !quiet {
+ app.PrintBanner()
+ }
+
+ task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
+ logger.Println(task)
+ logger.Println(strings.Repeat("-", len(task)))
+
+ // Create Template Options
+ options := &templates.Options{
+ ProjectName: projectName,
+ TargetDir: projectDirectory,
+ TemplateName: templateName,
+ Logger: logger,
+ GenerateVSCode: vscode,
+ InitGit: initGit,
+ }
+
+ // Try to discover author details from git config
+ err := findAuthorDetails(options)
+ if err != nil {
+ return err
+ }
+
+ return initProject(options)
+ })
+
+ return nil
+}
+
+// initProject is our main init command
+func initProject(options *templates.Options) error {
+
+ // Start Time
+ start := time.Now()
+
+ // Install the template
+ err := templates.Install(options)
+ if err != nil {
+ return err
+ }
+
+ if options.InitGit {
+ err = initGit(options)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Output stats
+ elapsed := time.Since(start)
+ options.Logger.Println("")
+ options.Logger.Println("Project Name: " + options.ProjectName)
+ options.Logger.Println("Project Directory: " + options.TargetDir)
+ options.Logger.Println("Project Template: " + options.TemplateName)
+ if options.GenerateVSCode {
+ options.Logger.Println("VSCode config files generated.")
+ }
+ if options.InitGit {
+ options.Logger.Println("Git repository initialised.")
+ }
+ options.Logger.Println("")
+ options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
+ options.Logger.Println("")
+
+ return nil
+}
+
+func initGit(options *templates.Options) error {
+ err := git.InitRepo(options.TargetDir)
+ if err != nil {
+ return errors.Wrap(err, "Unable to initialise git repository:")
+ }
+
+ return nil
+}
+
+func findAuthorDetails(options *templates.Options) error {
+ if git.IsInstalled() {
+ name, err := git.Name()
+ if err != nil {
+ return err
+ }
+ options.AuthorName = strings.TrimSpace(name)
+
+ email, err := git.Email()
+ if err != nil {
+ return err
+ }
+ options.AuthorEmail = strings.TrimSpace(email)
+ }
+
+ return nil
+}
diff --git a/v2/cmd/wails/internal/dev/dev.go b/v2/cmd/wails/internal/dev/dev.go
deleted file mode 100644
index 9495b5bf2..000000000
--- a/v2/cmd/wails/internal/dev/dev.go
+++ /dev/null
@@ -1,525 +0,0 @@
-package dev
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "log"
- "net/http"
- "net/url"
- "os"
- "os/exec"
- "os/signal"
- "path"
- "path/filepath"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "github.com/samber/lo"
- "github.com/wailsapp/wails/v2/cmd/wails/flags"
- "github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
- "github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
- "golang.org/x/mod/semver"
-
- "github.com/wailsapp/wails/v2/pkg/commands/buildtags"
-
- "github.com/google/shlex"
-
- "github.com/pkg/browser"
-
- "github.com/fsnotify/fsnotify"
- "github.com/wailsapp/wails/v2/internal/fs"
- "github.com/wailsapp/wails/v2/internal/process"
- "github.com/wailsapp/wails/v2/pkg/clilogger"
- "github.com/wailsapp/wails/v2/pkg/commands/build"
-)
-
-const (
- viteMinVersion = "v3.0.0"
-)
-
-func sliceToMap(input []string) map[string]struct{} {
- result := map[string]struct{}{}
- for _, value := range input {
- result[value] = struct{}{}
- }
- return result
-}
-
-// 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
- err := gomod.SyncGoMod(logger, !f.NoSyncGoMod)
- if err != nil {
- 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
- }
- }
-
- buildOptions := f.GenerateBuildOptions()
- buildOptions.Logger = logger
-
- userTags, err := buildtags.Parse(f.Tags)
- if err != nil {
- return err
- }
-
- projectConfig := f.ProjectConfig()
-
- projectTags, err := buildtags.Parse(projectConfig.BuildTags)
- if err != nil {
- return err
- }
- compiledTags := append(projectTags, userTags...)
- buildOptions.UserTags = compiledTags
-
- // Setup signal handler
- quitChannel := make(chan os.Signal, 1)
- signal.Notify(quitChannel, os.Interrupt, syscall.SIGTERM)
- exitCodeChannel := make(chan int, 1)
-
- // Build the frontend if requested, but ignore building the application itself.
- ignoreFrontend := buildOptions.IgnoreFrontend
- if !ignoreFrontend {
- buildOptions.IgnoreApplication = true
- if _, err := build.Build(buildOptions); err != nil {
- return err
- }
- buildOptions.IgnoreApplication = false
- }
-
- legacyUseDevServerInsteadofCustomScheme := false
- // frontend:dev:watcher command.
- frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
- if command := projectConfig.DevWatcherCommand; command != "" {
- closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery, projectConfig.ViteServerTimeout)
- if err != nil {
- return err
- }
- if devServerURL != "" {
- projectConfig.FrontendDevServerURL = devServerURL
- f.FrontendDevServerURL = devServerURL
- }
- defer closer()
-
- if devServerViteVersion != "" && semver.Compare(devServerViteVersion, viteMinVersion) < 0 {
- logutils.LogRed("Please upgrade your Vite Server to at least '%s' future Wails versions will require at least Vite '%s'", viteMinVersion, viteMinVersion)
- time.Sleep(3 * time.Second)
- legacyUseDevServerInsteadofCustomScheme = true
- }
- } else if frontendDevAutoDiscovery {
- return fmt.Errorf("unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
- }
-
- // Do initial build but only for the application.
- logger.Println("Building application for development...")
- buildOptions.IgnoreFrontend = true
- debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
- buildOptions.IgnoreFrontend = ignoreFrontend || f.FrontendDevServerURL != ""
- if err != nil {
- return err
- }
- defer func() {
- if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
- logutils.LogDarkYellow("Unable to kill process and cleanup binary: %s", err)
- }
- }()
-
- // open browser
- if f.Browser {
- err = browser.OpenURL(f.DevServerURL().String())
- if err != nil {
- return err
- }
- }
-
- logutils.LogGreen("Using DevServer URL: %s", f.DevServerURL())
- if f.FrontendDevServerURL != "" {
- logutils.LogGreen("Using Frontend DevServer URL: %s", f.FrontendDevServerURL)
- }
- logutils.LogGreen("Using reload debounce setting of %d milliseconds", f.Debounce)
-
- // Show dev server URL in terminal after 3 seconds
- go func() {
- time.Sleep(3 * time.Second)
- logutils.LogGreen("\n\nTo develop in the browser and call your bound Go methods from Javascript, navigate to: %s", f.DevServerURL())
- }()
-
- // 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
- }
-
- // Kill the current program if running and remove dev binary
- if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
- return err
- }
-
- // Reset the process and the binary so defer knows about it and is a nop.
- debugBinaryProcess = nil
- appBinary = ""
-
- logutils.LogGreen("Development mode exited")
-
- return nil
-}
-
-func killProcessAndCleanupBinary(process *process.Process, binary string) error {
- if process != nil && process.Running {
- if err := process.Kill(); err != nil {
- return err
- }
- }
-
- if binary != "" {
- err := os.Remove(binary)
- if err != nil && !errors.Is(err, os.ErrNotExist) {
- return err
- }
- }
- return nil
-}
-
-func runCommand(dir string, exitOnError bool, command string, args ...string) error {
- logutils.LogGreen("Executing: " + command + " " + strings.Join(args, " "))
- cmd := exec.Command(command, args...)
- cmd.Dir = dir
- output, err := cmd.CombinedOutput()
- if err != nil {
- println(string(output))
- println(err.Error())
- if exitOnError {
- os.Exit(1)
- }
- return err
- }
- return nil
-}
-
-// 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) {
- ctx, cancel := context.WithCancel(context.Background())
- scanner := NewStdoutScanner()
- cmdSlice := strings.Split(devCommand, " ")
- cmd := exec.CommandContext(ctx, cmdSlice[0], cmdSlice[1:]...)
- cmd.Stderr = os.Stderr
- cmd.Stdout = scanner
- cmd.Dir = frontendDirectory
- setParentGID(cmd)
-
- if err := cmd.Start(); err != nil {
- cancel()
- return nil, "", "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
- }
-
- var viteServerURL string
- if discoverViteServerURL {
- select {
- case serverURL := <-scanner.ViteServerURLChan:
- viteServerURL = serverURL
- case <-time.After(time.Second * time.Duration(viteServerTimeout)):
- cancel()
- return nil, "", "", fmt.Errorf("failed to find Vite server URL: Timed out waiting for Vite to output a URL after %d seconds", viteServerTimeout)
- }
- }
-
- viteVersion := ""
- select {
- case version := <-scanner.ViteServerVersionC:
- viteVersion = version
-
- case <-time.After(time.Second * 5):
- // That's fine, then most probably it was not vite that was running
- }
-
- logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
- var wg sync.WaitGroup
- wg.Add(1)
-
- const (
- stateRunning int32 = 0
- stateCanceling int32 = 1
- stateStopped int32 = 2
- )
- state := stateRunning
- go func() {
- if err := cmd.Wait(); err != nil {
- wasRunning := atomic.CompareAndSwapInt32(&state, stateRunning, stateStopped)
- if err.Error() != "exit status 1" && wasRunning {
- logutils.LogRed("Error from DevWatcher '%s': %s", devCommand, err.Error())
- }
- }
- atomic.StoreInt32(&state, stateStopped)
- wg.Done()
- }()
-
- return func() {
- if atomic.CompareAndSwapInt32(&state, stateRunning, stateCanceling) {
- killProc(cmd, devCommand)
- }
- cancel()
- wg.Wait()
- }, viteServerURL, viteVersion, nil
-}
-
-// 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 {
- logutils.LogRed("Build error - " + err.Error())
-
- msg := "Continuing to run current version"
- if debugBinaryProcess == nil {
- msg = "No version running, build will be retriggered as soon as changes have been detected"
- }
- logutils.LogDarkYellow(msg)
- return nil, "", nil
- }
-
- // Kill existing binary if need be
- if debugBinaryProcess != nil {
- killError := debugBinaryProcess.Kill()
-
- if killError != nil {
- buildOptions.Logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
- }
-
- debugBinaryProcess = nil
- }
-
- // parse appargs if any
- args, err := shlex.Split(f.AppArgs)
- if err != nil {
- buildOptions.Logger.Fatal("Unable to parse appargs: %s", err.Error())
- }
-
- // Set environment variables accordingly
- os.Setenv("loglevel", f.LogLevel)
- os.Setenv("assetdir", f.AssetDir)
- os.Setenv("devserver", f.DevServer)
- os.Setenv("frontenddevserverurl", f.FrontendDevServerURL)
-
- // Start up new binary with correct args
- newProcess := process.NewProcess(appBinary, args...)
- err = newProcess.Start(exitCodeChannel)
- if err != nil {
- // Remove binary
- if fs.FileExists(appBinary) {
- deleteError := fs.DeleteFile(appBinary)
- if deleteError != nil {
- buildOptions.Logger.Fatal("Unable to delete app binary: " + appBinary)
- }
- }
- buildOptions.Logger.Fatal("Unable to start application: %s", err.Error())
- }
-
- return newProcess, appBinary, nil
-}
-
-// 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)
-
- // Main Loop
- extensionsThatTriggerARebuild := sliceToMap(strings.Split(f.Extensions, ","))
- var dirsThatTriggerAReload []string
- for _, dir := range strings.Split(f.ReloadDirs, ",") {
- if dir == "" {
- continue
- }
- thePath, err := filepath.Abs(dir)
- if err != nil {
- logutils.LogRed("Unable to expand reloadDir '%s': %s", dir, err)
- 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
- interval := time.Duration(f.Debounce) * time.Millisecond
- timer := time.NewTimer(interval)
- rebuild := false
- reload := false
- assetDir := ""
- changedPaths := map[string]struct{}{}
-
- // If we are using an external dev server, the reloading of the frontend part can be skipped or if the user requested it
- skipAssetsReload := f.FrontendDevServerURL != "" || f.NoReload
-
- assetDirURL := joinPath(devServerURL, "/wails/assetdir")
- reloadURL := joinPath(devServerURL, "/wails/reload")
- for !quit {
- // reload := false
- select {
- case exitCode := <-exitCodeChannel:
- if exitCode == 0 {
- quit = true
- }
- case err := <-watcher.Errors:
- logutils.LogDarkYellow(err.Error())
- case item := <-watcher.Events:
- isEligibleFile := func(fileName string) bool {
- // Iterate all file patterns
- ext := filepath.Ext(fileName)
- if ext != "" {
- ext = ext[1:]
- if _, exists := extensionsThatTriggerARebuild[ext]; exists {
- return true
- }
- }
- return false
- }
-
- // Handle write operations
- if item.Op&fsnotify.Write == fsnotify.Write {
- // Ignore directories
- itemName := item.Name
- if fs.DirExists(itemName) {
- continue
- }
-
- if isEligibleFile(itemName) {
- rebuild = true
- timer.Reset(interval)
- continue
- }
-
- for _, reloadDir := range dirsThatTriggerAReload {
- if strings.HasPrefix(itemName, reloadDir) {
- reload = true
- break
- }
- }
-
- if !reload {
- changedPaths[filepath.Dir(itemName)] = struct{}{}
- }
-
- timer.Reset(interval)
- }
-
- // Handle new fs entries that are created
- if item.Op&fsnotify.Create == fsnotify.Create {
- // If this is a folder, add it to our watch list
- if fs.DirExists(item.Name) {
- // node_modules is BANNED!
- if !strings.Contains(item.Name, "node_modules") {
- err := watcher.Add(item.Name)
- if err != nil {
- buildOptions.Logger.Fatal("%s", err.Error())
- }
- logutils.LogGreen("Added new directory to watcher: %s", item.Name)
- }
- } else if isEligibleFile(item.Name) {
- // Handle creation of new file.
- // Note: On some platforms an update to a file is represented as
- // REMOVE -> CREATE instead of WRITE, so this is not only new files
- // but also updates to existing files
- rebuild = true
- timer.Reset(interval)
- continue
- }
- }
- 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
- }
- }
- }
-
- if !skipAssetsReload && len(changedPaths) != 0 {
- if assetDir == "" {
- resp, err := http.Get(assetDirURL)
- if err != nil {
- logutils.LogRed("Error during retrieving assetdir: %s", err.Error())
- } else {
- content, err := io.ReadAll(resp.Body)
- if err != nil {
- logutils.LogRed("Error reading assetdir from devserver: %s", err.Error())
- } else {
- assetDir = string(content)
- }
- resp.Body.Close()
- }
- }
-
- if assetDir != "" {
- for thePath := range changedPaths {
- if strings.HasPrefix(thePath, assetDir) {
- reload = true
- break
- }
- }
- } else if len(dirsThatTriggerAReload) == 0 {
- logutils.LogRed("Reloading couldn't be triggered: Please specify -assetdir or -reloaddirs")
- }
- }
- if reload {
- reload = false
- _, err := http.Get(reloadURL)
- if err != nil {
- logutils.LogRed("Error during refresh: %s", err.Error())
- }
- }
- changedPaths = map[string]struct{}{}
- case <-quitChannel:
- logutils.LogGreen("\nCaught quit")
- quit = true
- }
- }
- return debugBinaryProcess, nil
-}
-
-func joinPath(url *url.URL, subPath string) string {
- u := *url
- u.Path = path.Join(u.Path, subPath)
- return u.String()
-}
diff --git a/v2/cmd/wails/internal/dev/dev_other.go b/v2/cmd/wails/internal/dev/dev_other.go
deleted file mode 100644
index 88e170ee3..000000000
--- a/v2/cmd/wails/internal/dev/dev_other.go
+++ /dev/null
@@ -1,37 +0,0 @@
-//go:build darwin || linux
-// +build darwin linux
-
-package dev
-
-import (
- "os/exec"
- "syscall"
-
- "github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
- "golang.org/x/sys/unix"
-)
-
-func setParentGID(cmd *exec.Cmd) {
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setpgid: true,
- }
-}
-
-func killProc(cmd *exec.Cmd, devCommand string) {
- if cmd == nil || cmd.Process == nil {
- return
- }
-
- // Experiencing the same issue on macOS BigSur
- // I'm using Vite, but I would imagine this could be an issue with Node (npm) in general
- // Also, after several edit/rebuild cycles any abnormal shutdown (crash or CTRL-C) may still leave Node running
- // Credit: https://stackoverflow.com/a/29552044/14764450 (same page as the Windows solution above)
- // Not tested on *nix
- pgid, err := syscall.Getpgid(cmd.Process.Pid)
- if err == nil {
- err := syscall.Kill(-pgid, unix.SIGTERM) // note the minus sign
- if err != nil {
- logutils.LogRed("Error from '%s' when attempting to kill the process: %s", devCommand, err.Error())
- }
- }
-}
diff --git a/v2/cmd/wails/internal/dev/dev_windows.go b/v2/cmd/wails/internal/dev/dev_windows.go
deleted file mode 100644
index e219e6519..000000000
--- a/v2/cmd/wails/internal/dev/dev_windows.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//go:build windows
-// +build windows
-
-package dev
-
-import (
- "bytes"
- "os/exec"
- "strconv"
-
- "github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
-)
-
-func setParentGID(_ *exec.Cmd) {}
-
-func killProc(cmd *exec.Cmd, devCommand string) {
- // Credit: https://stackoverflow.com/a/44551450
- // For whatever reason, killing an npm script on windows just doesn't exit properly with cancel
- if cmd != nil && cmd.Process != nil {
- kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
- var errorBuffer bytes.Buffer
- var stdoutBuffer bytes.Buffer
- kill.Stderr = &errorBuffer
- kill.Stdout = &stdoutBuffer
- err := kill.Run()
- if err != nil {
- if err.Error() != "exit status 1" {
- println(stdoutBuffer.String())
- println(errorBuffer.String())
- logutils.LogRed("Error from '%s': %s", devCommand, err.Error())
- }
- }
- }
-}
diff --git a/v2/cmd/wails/internal/dev/stdout_scanner.go b/v2/cmd/wails/internal/dev/stdout_scanner.go
deleted file mode 100644
index dad4e72cf..000000000
--- a/v2/cmd/wails/internal/dev/stdout_scanner.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package dev
-
-import (
- "bufio"
- "fmt"
- "net/url"
- "os"
- "strings"
-
- "github.com/acarl005/stripansi"
- "github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
- "golang.org/x/mod/semver"
-)
-
-// stdoutScanner acts as a stdout target that will scan the incoming
-// data to find out the vite server url
-type stdoutScanner struct {
- ViteServerURLChan chan string
- ViteServerVersionC chan string
- versionDetected bool
-}
-
-// NewStdoutScanner creates a new stdoutScanner
-func NewStdoutScanner() *stdoutScanner {
- return &stdoutScanner{
- ViteServerURLChan: make(chan string, 2),
- ViteServerVersionC: make(chan string, 2),
- }
-}
-
-// Write bytes to the scanner. Will copy the bytes to stdout
-func (s *stdoutScanner) Write(data []byte) (n int, err error) {
- input := stripansi.Strip(string(data))
- if !s.versionDetected {
- v, err := detectViteVersion(input)
- if v != "" || err != nil {
- if err != nil {
- logutils.LogRed("ViteStdoutScanner: %s", err)
- v = "v0.0.0"
- }
- s.ViteServerVersionC <- v
- s.versionDetected = true
- }
- }
-
- match := strings.Index(input, "Local:")
- if match != -1 {
- sc := bufio.NewScanner(strings.NewReader(input))
- for sc.Scan() {
- line := sc.Text()
- index := strings.Index(line, "Local:")
- if index == -1 || len(line) < 7 {
- continue
- }
- viteServerURL := strings.TrimSpace(line[index+6:])
- logutils.LogGreen("Vite Server URL: %s", viteServerURL)
- _, err := url.Parse(viteServerURL)
- if err != nil {
- logutils.LogRed(err.Error())
- } else {
- s.ViteServerURLChan <- viteServerURL
- }
- }
- }
- return os.Stdout.Write(data)
-}
-
-func detectViteVersion(line string) (string, error) {
- s := strings.Split(strings.TrimSpace(line), " ")
- if strings.ToLower(s[0]) != "vite" {
- return "", nil
- }
-
- if len(line) < 2 {
- return "", fmt.Errorf("unable to parse vite version")
- }
-
- v := s[1]
- if !semver.IsValid(v) {
- return "", fmt.Errorf("%s is not a valid vite version string", v)
- }
-
- return v, nil
-}
diff --git a/v2/cmd/wails/internal/dev/watcher.go b/v2/cmd/wails/internal/dev/watcher.go
deleted file mode 100644
index e1161f87c..000000000
--- a/v2/cmd/wails/internal/dev/watcher.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package dev
-
-import (
- "bufio"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/wailsapp/wails/v2/internal/fs"
-
- "github.com/fsnotify/fsnotify"
- gitignore "github.com/sabhiram/go-gitignore"
- "github.com/samber/lo"
-)
-
-type Watcher interface {
- Add(name string) error
-}
-
-// initialiseWatcher creates the project directory watcher that will trigger recompile
-func initialiseWatcher(cwd, reloadDirs string) (*fsnotify.Watcher, error) {
- // Ignore dot files, node_modules and build directories by default
- ignoreDirs := getIgnoreDirs(cwd)
-
- // Get all subdirectories
- dirs, err := fs.GetSubdirectories(cwd)
- if err != nil {
- 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) {
- err := watcher.Add(dir)
- if err != nil {
- return nil, err
- }
- }
- 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)
- }
- }
- }
-
- return lo.Uniq(ignoreDirs)
-}
-
-func processDirectories(dirs []string, ignoreDirs []string) []string {
- ignorer := gitignore.CompileIgnoreLines(ignoreDirs...)
- return lo.Filter(dirs, func(dir string, _ int) bool {
- return !ignorer.MatchesPath(dir)
- })
-}
diff --git a/v2/cmd/wails/internal/dev/watcher_test.go b/v2/cmd/wails/internal/dev/watcher_test.go
deleted file mode 100644
index ad228b66c..000000000
--- a/v2/cmd/wails/internal/dev/watcher_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package dev
-
-import (
- "github.com/samber/lo"
- "os"
- "path/filepath"
- "reflect"
- "testing"
-
- "github.com/stretchr/testify/require"
-
- "github.com/wailsapp/wails/v2/internal/fs"
-)
-
-func Test_processDirectories(t *testing.T) {
- tests := []struct {
- name string
- dirs []string
- ignoreDirs []string
- want []string
- }{
- {
- name: "Should ignore .git",
- ignoreDirs: []string{".git"},
- dirs: []string{".git", "some/path/to/nested/.git", "some/path/to/nested/.git/CHANGELOG"},
- want: []string{},
- },
- {
- name: "Should ignore node_modules",
- ignoreDirs: []string{"node_modules"},
- dirs: []string{"node_modules", "path/to/node_modules", "path/to/node_modules/some/other/path"},
- want: []string{},
- },
- {
- name: "Should ignore dirs starting with .",
- ignoreDirs: []string{".*"},
- dirs: []string{".test", ".gitignore", ".someother", "valid"},
- want: []string{"valid"},
- },
- {
- name: "Should ignore dirs in ignoreDirs",
- dirs: []string{"build", "build/my.exe", "build/my.app"},
- ignoreDirs: []string{"build"},
- want: []string{},
- },
- {
- name: "Should ignore subdirectories",
- dirs: []string{"build", "some/path/to/build", "some/path/to/CHANGELOG", "some/other/path"},
- ignoreDirs: []string{"some/**/*"},
- want: []string{"build"},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got := processDirectories(tt.dirs, tt.ignoreDirs)
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("processDirectories() got = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func Test_GetIgnoreDirs(t *testing.T) {
-
- // Remove testdir if it exists
- _ = os.RemoveAll("testdir")
-
- tests := []struct {
- name string
- files []string
- want []string
- shouldErr bool
- }{
- {
- name: "Should have defaults",
- files: []string{},
- want: []string{"testdir/build/*", ".*", "node_modules"},
- },
- {
- name: "Should ignore dotFiles",
- files: []string{".test1", ".wailsignore"},
- want: []string{"testdir/build/*", ".*", "node_modules"},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Create temporary file
- err := fs.Mkdir("testdir")
- require.NoError(t, err)
- defer func() {
- err := os.RemoveAll("testdir")
- require.NoError(t, err)
- }()
- for _, file := range tt.files {
- fs.MustWriteString(filepath.Join("testdir", file), "")
- }
-
- got := getIgnoreDirs("testdir")
-
- got = lo.Map(got, func(s string, _ int) string {
- return filepath.ToSlash(s)
- })
-
- if (err != nil) != tt.shouldErr {
- t.Errorf("initialiseWatcher() error = %v, shouldErr %v", err, tt.shouldErr)
- return
- }
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("initialiseWatcher() got = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/v2/cmd/wails/internal/gomod/gomod.go b/v2/cmd/wails/internal/gomod/gomod.go
deleted file mode 100644
index 5da14a5ff..000000000
--- a/v2/cmd/wails/internal/gomod/gomod.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package gomod
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/wailsapp/wails/v2/cmd/wails/internal"
- "github.com/wailsapp/wails/v2/internal/colour"
- "github.com/wailsapp/wails/v2/internal/fs"
- "github.com/wailsapp/wails/v2/internal/gomod"
- "github.com/wailsapp/wails/v2/internal/goversion"
- "github.com/wailsapp/wails/v2/pkg/clilogger"
-)
-
-func SyncGoMod(logger *clilogger.CLILogger, updateWailsVersion bool) error {
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- gomodFilename := fs.FindFileInParents(cwd, "go.mod")
- if gomodFilename == "" {
- return fmt.Errorf("no go.mod file found")
- }
- gomodData, err := os.ReadFile(gomodFilename)
- if err != nil {
- return err
- }
-
- gomodData, updated, err := gomod.SyncGoVersion(gomodData, goversion.MinRequirement)
- if err != nil {
- return err
- } else if updated {
- LogGreen("Updated go.mod to use Go '%s'", goversion.MinRequirement)
- }
-
- internalVersion := strings.TrimSpace(internal.Version)
- if outOfSync, err := gomod.GoModOutOfSync(gomodData, internalVersion); err != nil {
- return err
- } else if outOfSync {
- if updateWailsVersion {
- LogGreen("Updating go.mod to use Wails '%s'", internalVersion)
- gomodData, err = gomod.UpdateGoModVersion(gomodData, internalVersion)
- if err != nil {
- return err
- }
- updated = true
- } else {
- gomodversion, err := gomod.GetWailsVersionFromModFile(gomodData)
- if err != nil {
- return err
- }
-
- logger.Println("Warning: go.mod is using Wails '%s' but the CLI is '%s'. Consider updating your project's `go.mod` file.\n", gomodversion.String(), internal.Version)
- }
- }
-
- if updated {
- return os.WriteFile(gomodFilename, gomodData, 0o755)
- }
-
- return nil
-}
-
-func LogGreen(message string, args ...interface{}) {
- text := fmt.Sprintf(message, args...)
- println(colour.Green(text))
-}
diff --git a/v2/cmd/wails/internal/logutils/color-logs.go b/v2/cmd/wails/internal/logutils/color-logs.go
deleted file mode 100644
index 65553df3f..000000000
--- a/v2/cmd/wails/internal/logutils/color-logs.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package logutils
-
-import (
- "fmt"
-
- "github.com/wailsapp/wails/v2/internal/colour"
-)
-
-func LogGreen(message string, args ...interface{}) {
- if len(message) == 0 {
- return
- }
- text := fmt.Sprintf(message, args...)
- println(colour.Green(text))
-}
-
-func LogRed(message string, args ...interface{}) {
- if len(message) == 0 {
- return
- }
- text := fmt.Sprintf(message, args...)
- println(colour.Red(text))
-}
-
-func LogDarkYellow(message string, args ...interface{}) {
- if len(message) == 0 {
- return
- }
- text := fmt.Sprintf(message, args...)
- println(colour.DarkYellow(text))
-}
diff --git a/v2/cmd/wails/internal/template/base/NEXTSTEPS.md.template b/v2/cmd/wails/internal/template/base/NEXTSTEPS.md.template
deleted file mode 100644
index 5363d10f2..000000000
--- a/v2/cmd/wails/internal/template/base/NEXTSTEPS.md.template
+++ /dev/null
@@ -1,41 +0,0 @@
-# Next Steps
-
-Congratulations on generating your template!
-
-## Completing your template
-
-The next steps to complete the template are:
-
- 1. Complete the fields in the `template.json` file.
- - It is really important to ensure `helpurl` is valid as this is where users of the template will be directed for help.
- 2. Update `README.md`.
- 3. Edit `wails.json` and ensure all fields are correct, especially:
- - `wailsjsdir` - path to generate wailsjs modules
- - `frontend:install` - The command to install your frontend dependencies
- - `frontend:build` - The command to build your frontend
- 4. Remove any `public` or `dist` directories.
- 5. Delete this file.
-
-## Testing your template
-
-You can test your template by running this command:
-
-`wails init -n test -t {{.TemplateDir}}`
-
-### Checklist
-
-Once generated, do the following tests:
- - Change into the new project directory and run `wails build`. A working binary should be generated in the `build/bin` project directory.
- - Run `wails dev`. This will compile your app and run it.
- - You should be able to see your application running on http://localhost:34115/
-
-## Publishing your template
-
-You can publish a template to a git repository and use it as follows:
-
-`wails init -name test -t https://your/git/url`
-
-EG:
-
-`wails init -name test -t https://github.com/leaanthony/testtemplate`
-
diff --git a/v2/cmd/wails/internal/template/base/README.md b/v2/cmd/wails/internal/template/base/README.md
deleted file mode 100644
index ed259fcff..000000000
--- a/v2/cmd/wails/internal/template/base/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# README
-
-## About
-
-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
-browser and connect to your application.
-
-## Building
-
-To build a redistributable, production mode package, use `wails build`.
diff --git a/v2/cmd/wails/internal/template/base/app.tmpl.go b/v2/cmd/wails/internal/template/base/app.tmpl.go
deleted file mode 100644
index 224be7156..000000000
--- a/v2/cmd/wails/internal/template/base/app.tmpl.go
+++ /dev/null
@@ -1,44 +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 at application startup
-func (a *App) startup(ctx context.Context) {
- // Perform your setup here
- a.ctx = ctx
-}
-
-// domReady is called after front-end resources have been loaded
-func (a App) domReady(ctx context.Context) {
- // Add your action here
-}
-
-// beforeClose is called when the application is about to quit,
-// either by clicking the window close button or calling runtime.Quit.
-// Returning true will cause the application to continue, false will continue shutdown as normal.
-func (a *App) beforeClose(ctx context.Context) (prevent bool) {
- return false
-}
-
-// shutdown is called at application termination
-func (a *App) shutdown(ctx context.Context) {
- // Perform your teardown here
-}
-
-// 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/cmd/wails/internal/template/base/frontend/dist/assets/fonts/OFL.txt b/v2/cmd/wails/internal/template/base/frontend/dist/assets/fonts/OFL.txt
deleted file mode 100644
index 9cac04ce8..000000000
--- a/v2/cmd/wails/internal/template/base/frontend/dist/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/cmd/wails/internal/template/base/frontend/dist/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/cmd/wails/internal/template/base/frontend/dist/assets/fonts/nunito-v16-latin-regular.woff2
deleted file mode 100644
index 2f9cc5964..000000000
Binary files a/v2/cmd/wails/internal/template/base/frontend/dist/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ
diff --git a/v2/cmd/wails/internal/template/base/frontend/dist/assets/images/logo-universal.png b/v2/cmd/wails/internal/template/base/frontend/dist/assets/images/logo-universal.png
deleted file mode 100644
index 27ef13655..000000000
Binary files a/v2/cmd/wails/internal/template/base/frontend/dist/assets/images/logo-universal.png and /dev/null differ
diff --git a/v2/cmd/wails/internal/template/base/frontend/dist/index.html b/v2/cmd/wails/internal/template/base/frontend/dist/index.html
deleted file mode 100644
index e2a14c1b5..000000000
--- a/v2/cmd/wails/internal/template/base/frontend/dist/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
Please enter your name below 👇
-
-
- Greet
-
-
-
-
-
diff --git a/v2/cmd/wails/internal/template/base/frontend/dist/main.css b/v2/cmd/wails/internal/template/base/frontend/dist/main.css
deleted file mode 100644
index f35a69f99..000000000
--- a/v2/cmd/wails/internal/template/base/frontend/dist/main.css
+++ /dev/null
@@ -1,79 +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;
-}
-
-.logo {
- display: block;
- width: 50%;
- height: 50%;
- margin: auto;
- padding: 10% 0 0;
- background-position: center;
- background-repeat: no-repeat;
- background-image: url("./assets/images/logo-universal.png");
- 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);
-}
diff --git a/v2/cmd/wails/internal/template/base/frontend/dist/main.js b/v2/cmd/wails/internal/template/base/frontend/dist/main.js
deleted file mode 100644
index 98510cd39..000000000
--- a/v2/cmd/wails/internal/template/base/frontend/dist/main.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// Get input + focus
-let nameElement = document.getElementById("name");
-nameElement.focus();
-
-// 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 {
- window.go.main.App.Greet(name)
- .then((result) => {
- // Update result with data back from App.Greet()
- document.getElementById("result").innerText = result;
- })
- .catch((err) => {
- console.error(err);
- });
- } catch (err) {
- console.error(err);
- }
-};
-
-nameElement.onkeydown = function (e) {
- if (e.keyCode == 13) {
- window.greet();
- }
-};
diff --git a/v2/cmd/wails/internal/template/base/frontend/package.tmpl.json b/v2/cmd/wails/internal/template/base/frontend/package.tmpl.json
deleted file mode 100644
index 01780288d..000000000
--- a/v2/cmd/wails/internal/template/base/frontend/package.tmpl.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "{{.ProjectName}}",
- "version": "1.0.0",
- "description": "",
- "main": "",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "build": ""
- },
- "keywords": [],
- "author": "{{.AuthorName}}"
-}
diff --git a/v2/cmd/wails/internal/template/base/go.sum b/v2/cmd/wails/internal/template/base/go.sum
deleted file mode 100644
index 92f4d6d57..000000000
--- a/v2/cmd/wails/internal/template/base/go.sum
+++ /dev/null
@@ -1,180 +0,0 @@
-github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
-github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
-github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
-github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-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/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/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-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/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-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/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
-github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
-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/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
-github.com/google/uuid v1.1.1/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/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/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
-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.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
-github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
-github.com/labstack/echo/v4 v4.7.2/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/clir v1.0.4/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.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/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
-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/typescriptify-golang-structs v0.1.7 h1:yoznzWzyxkO/iWdlpq+aPcuJ5Y/hpjq/lmgMFmpjwl0=
-github.com/leaanthony/typescriptify-golang-structs v0.1.7/go.mod h1:cWtOkiVhMF77e6phAXUcfNwYmMwCJ67Sij24lfvi9Js=
-github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
-github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
-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-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
-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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-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/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A=
-github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
-github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
-github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-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/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/wailsapp/mimetype v1.4.1-beta.1.0.20220331112158-6df7e41671fe h1:FiWQ7XhDkc4zAH8SEx1BTte/6VHyceraUusH8jf5SQw=
-github.com/wailsapp/mimetype v1.4.1-beta.1.0.20220331112158-6df7e41671fe/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
-github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
-github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
-github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/ztrue/tracerr v0.3.0/go.mod h1:qEalzze4VN9O8tnhBXScfCrmoJo10o8TN5ciKjm6Mww=
-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-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-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-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-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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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-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-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
-golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-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.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 h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/v2/cmd/wails/internal/template/base/go.tmpl.mod b/v2/cmd/wails/internal/template/base/go.tmpl.mod
deleted file mode 100644
index 42478753c..000000000
--- a/v2/cmd/wails/internal/template/base/go.tmpl.mod
+++ /dev/null
@@ -1,32 +0,0 @@
-module changeme
-
- go 1.18
-
- require github.com/wailsapp/wails/v2 {{.WailsVersion}}
-
- require (
- github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/google/uuid v1.1.2 // indirect
- github.com/imdario/mergo v0.3.12 // indirect
- github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
- github.com/labstack/echo/v4 v4.7.2 // 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/leaanthony/typescriptify-golang-structs v0.1.7 // 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/tkrajina/go-reflector v0.5.5 // indirect
- github.com/valyala/bytebufferpool v1.0.0 // indirect
- github.com/valyala/fasttemplate v1.2.1 // indirect
- github.com/wailsapp/mimetype v1.4.1-beta.1.0.20220331112158-6df7e41671fe // indirect
- golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
- golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
- golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
- golang.org/x/text v0.3.7 // indirect
- )
-
- // replace github.com/wailsapp/wails/v2 {{.WailsVersion}} => {{.WailsDirectory}}
\ No newline at end of file
diff --git a/v2/cmd/wails/internal/template/base/main.go.tmpl b/v2/cmd/wails/internal/template/base/main.go.tmpl
deleted file mode 100644
index d8e902027..000000000
--- a/v2/cmd/wails/internal/template/base/main.go.tmpl
+++ /dev/null
@@ -1,87 +0,0 @@
-package main
-
-import (
- "embed"
- "log"
-
- "github.com/wailsapp/wails/v2"
- "github.com/wailsapp/wails/v2/pkg/logger"
- "github.com/wailsapp/wails/v2/pkg/options"
- "github.com/wailsapp/wails/v2/pkg/options/assetserver"
- "github.com/wailsapp/wails/v2/pkg/options/mac"
- "github.com/wailsapp/wails/v2/pkg/options/windows"
-)
-
-//go:embed all:frontend/dist
-var assets embed.FS
-
-//go:embed build/appicon.png
-var icon []byte
-
-func main() {
- // Create an instance of the app structure
- app := NewApp()
-
- // Create application with options
- err := wails.Run(&options.App{
- Title: "{{.ProjectName}}",
- Width: 1024,
- Height: 768,
- MinWidth: 1024,
- MinHeight: 768,
- MaxWidth: 1280,
- MaxHeight: 800,
- DisableResize: false,
- Fullscreen: false,
- Frameless: false,
- StartHidden: false,
- HideWindowOnClose: false,
- BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
- AssetServer: &assetserver.Options{
- Assets: assets,
- },
- Menu: nil,
- Logger: nil,
- LogLevel: logger.DEBUG,
- OnStartup: app.startup,
- OnDomReady: app.domReady,
- OnBeforeClose: app.beforeClose,
- OnShutdown: app.shutdown,
- WindowStartState: options.Normal,
- Bind: []interface{}{
- app,
- },
- // Windows platform specific options
- Windows: &windows.Options{
- WebviewIsTransparent: false,
- WindowIsTranslucent: false,
- DisableWindowIcon: false,
- // DisableFramelessWindowDecorations: false,
- WebviewUserDataPath: "",
- ZoomFactor: 1.0,
- },
- // Mac platform specific options
- Mac: &mac.Options{
- TitleBar: &mac.TitleBar{
- TitlebarAppearsTransparent: true,
- HideTitle: false,
- HideTitleBar: false,
- FullSizeContent: false,
- UseToolbar: false,
- HideToolbarSeparator: true,
- },
- Appearance: mac.NSAppearanceNameDarkAqua,
- WebviewIsTransparent: true,
- WindowIsTranslucent: true,
- About: &mac.AboutInfo{
- Title: "{{.ProjectName}}",
- Message: "",
- Icon: icon,
- },
- },
- })
-
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/v2/cmd/wails/internal/template/base/scripts/build-macos-arm.sh b/v2/cmd/wails/internal/template/base/scripts/build-macos-arm.sh
deleted file mode 100644
index bc6ee0acb..000000000
--- a/v2/cmd/wails/internal/template/base/scripts/build-macos-arm.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/bash
-
-echo -e "Start running the script..."
-cd ../
-
-echo -e "Start building the app for macos platform..."
-wails build --clean --platform darwin/arm64
-
-echo -e "End running the script!"
diff --git a/v2/cmd/wails/internal/template/base/scripts/build-macos-intel.sh b/v2/cmd/wails/internal/template/base/scripts/build-macos-intel.sh
deleted file mode 100644
index f359f633a..000000000
--- a/v2/cmd/wails/internal/template/base/scripts/build-macos-intel.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/bash
-
-echo -e "Start running the script..."
-cd ../
-
-echo -e "Start building the app for macos platform..."
-wails build --clean --platform darwin
-
-echo -e "End running the script!"
diff --git a/v2/cmd/wails/internal/template/base/scripts/build-macos.sh b/v2/cmd/wails/internal/template/base/scripts/build-macos.sh
deleted file mode 100644
index d61531fd7..000000000
--- a/v2/cmd/wails/internal/template/base/scripts/build-macos.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/bash
-
-echo -e "Start running the script..."
-cd ../
-
-echo -e "Start building the app for macos platform..."
-wails build --clean --platform darwin/universal
-
-echo -e "End running the script!"
diff --git a/v2/cmd/wails/internal/template/base/scripts/build-windows.sh b/v2/cmd/wails/internal/template/base/scripts/build-windows.sh
deleted file mode 100644
index 47b778970..000000000
--- a/v2/cmd/wails/internal/template/base/scripts/build-windows.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/bash
-
-echo -e "Start running the script..."
-cd ../
-
-echo -e "Start building the app for windows platform..."
-wails build --clean --platform windows/amd64
-
-echo -e "End running the script!"
diff --git a/v2/cmd/wails/internal/template/base/scripts/build.sh b/v2/cmd/wails/internal/template/base/scripts/build.sh
deleted file mode 100644
index 20ab7eb21..000000000
--- a/v2/cmd/wails/internal/template/base/scripts/build.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /bin/bash
-
-echo -e "Start running the script..."
-cd ../
-
-echo -e "Start building the app..."
-wails build --clean
-
-echo -e "End running the script!"
diff --git a/v2/cmd/wails/internal/template/base/scripts/install-wails-cli.sh b/v2/cmd/wails/internal/template/base/scripts/install-wails-cli.sh
deleted file mode 100644
index 7539d8e33..000000000
--- a/v2/cmd/wails/internal/template/base/scripts/install-wails-cli.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#! /bin/bash
-
-echo -e "Start running the script..."
-cd ../
-
-echo -e "Current Go version: \c"
-go version
-
-echo -e "Install the Wails command line tool..."
-go install github.com/wailsapp/wails/v2/cmd/wails@latest
-
-echo -e "Successful installation!"
-
-echo -e "End running the script!"
diff --git a/v2/cmd/wails/internal/template/base/template.json.template b/v2/cmd/wails/internal/template/base/template.json.template
deleted file mode 100644
index bde089e00..000000000
--- a/v2/cmd/wails/internal/template/base/template.json.template
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "Long name",
- "shortname": "{{.Name}}",
- "author": "",
- "description": "Description of the template",
- "helpurl": "URL for help with the template, eg homepage"
-}
\ No newline at end of file
diff --git a/v2/cmd/wails/internal/template/base/wails.tmpl.json b/v2/cmd/wails/internal/template/base/wails.tmpl.json
deleted file mode 100644
index cdb10e346..000000000
--- a/v2/cmd/wails/internal/template/base/wails.tmpl.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "$schema": "https://wails.io/schemas/config.v2.json",
- "name": "{{.ProjectName}}",
- "outputfilename": "{{.BinaryName}}",
- "frontend:install": "npm install",
- "frontend:build": "npm run build",
- "author": {
- "name": "{{.AuthorName}}",
- "email": "{{.AuthorEmail}}"
- }
-}
diff --git a/v2/cmd/wails/internal/template/template.go b/v2/cmd/wails/internal/template/template.go
deleted file mode 100644
index 6b4937db6..000000000
--- a/v2/cmd/wails/internal/template/template.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package template
-
-import (
- "embed"
-)
-
-//go:embed base
-var Base embed.FS
diff --git a/v2/cmd/wails/internal/version.go b/v2/cmd/wails/internal/version.go
deleted file mode 100644
index cfc37182c..000000000
--- a/v2/cmd/wails/internal/version.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package internal
-
-import _ "embed"
-
-//go:embed version.txt
-var Version string
diff --git a/v2/cmd/wails/internal/version.txt b/v2/cmd/wails/internal/version.txt
deleted file mode 100644
index 805579f30..000000000
--- a/v2/cmd/wails/internal/version.txt
+++ /dev/null
@@ -1 +0,0 @@
-v2.11.0
\ No newline at end of file
diff --git a/v2/cmd/wails/main.go b/v2/cmd/wails/main.go
index ccf1576e9..b989af219 100644
--- a/v2/cmd/wails/main.go
+++ b/v2/cmd/wails/main.go
@@ -1,102 +1,55 @@
package main
import (
- "fmt"
"os"
- "strings"
-
- "github.com/pterm/pterm"
- "github.com/wailsapp/wails/v2/cmd/wails/internal"
-
- "github.com/wailsapp/wails/v2/internal/colour"
"github.com/leaanthony/clir"
+ "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
+ "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
+ "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
+ "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
+ "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
+ "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
)
-func banner(_ *clir.Cli) string {
- return fmt.Sprintf("%s %s",
- colour.Green("Wails CLI"),
- colour.DarkRed(internal.Version))
-}
-
func fatal(message string) {
- printer := pterm.PrefixPrinter{
- MessageStyle: &pterm.ThemeDefault.FatalMessageStyle,
- Prefix: pterm.Prefix{
- Style: &pterm.ThemeDefault.FatalPrefixStyle,
- Text: " FATAL ",
- },
- }
- printer.Println(message)
+ println(message)
os.Exit(1)
}
-func printBulletPoint(text string, args ...any) {
- item := pterm.BulletListItem{
- Level: 2,
- Text: text,
- }
- t, err := pterm.DefaultBulletList.WithItems([]pterm.BulletListItem{item}).Srender()
+func main() {
+
+ var err error
+
+ app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
+
+ build.AddBuildSubcommand(app, os.Stdout)
+ err = initialise.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
- t = strings.Trim(t, "\n\r")
- pterm.Printfln(t, args...)
-}
-func printFooter() {
- printer := pterm.PrefixPrinter{
- MessageStyle: pterm.NewStyle(pterm.FgLightGreen),
- Prefix: pterm.Prefix{
- Style: pterm.NewStyle(pterm.FgRed, pterm.BgLightWhite),
- Text: "♥ ",
- },
+ err = debug.AddSubcommand(app, os.Stdout)
+ if err != nil {
+ fatal(err.Error())
}
- printer.Println("If Wails is useful to you or your company, please consider sponsoring the project:")
- pterm.Println("https://github.com/sponsors/leaanthony")
-}
-
-func bool2Str(b bool) string {
- if b {
- return "true"
+ err = doctor.AddSubcommand(app, os.Stdout)
+ if err != nil {
+ fatal(err.Error())
}
- return "false"
-}
-var app *clir.Cli
+ err = dev.AddSubcommand(app, os.Stdout)
+ if err != nil {
+ fatal(err.Error())
+ }
-func main() {
- var err error
-
- app = clir.NewCli("Wails", "Go/HTML Appkit", internal.Version)
-
- app.SetBannerFunction(banner)
- defer printFooter()
-
- app.NewSubCommandFunction("build", "Builds the application", buildApplication)
- app.NewSubCommandFunction("dev", "Runs the application in development mode", devApplication)
- app.NewSubCommandFunction("doctor", "Diagnose your environment", diagnoseEnvironment)
- app.NewSubCommandFunction("init", "Initialises a new Wails project", initProject)
- app.NewSubCommandFunction("update", "Update the Wails CLI", update)
-
- show := app.NewSubCommand("show", "Shows various information")
- show.NewSubCommandFunction("releasenotes", "Shows the release notes for the current version", showReleaseNotes)
-
- generate := app.NewSubCommand("generate", "Code Generation Tools")
- generate.NewSubCommandFunction("module", "Generates a new Wails module", generateModule)
- generate.NewSubCommandFunction("template", "Generates a new Wails template", generateTemplate)
-
- command := app.NewSubCommand("version", "The Wails CLI version")
- command.Action(func() error {
- pterm.Println(internal.Version)
- return nil
- })
+ err = generate.AddSubcommand(app, os.Stdout)
+ if err != nil {
+ fatal(err.Error())
+ }
err = app.Run()
if err != nil {
- pterm.Println()
- pterm.Error.Println(err.Error())
- printFooter()
- os.Exit(1)
+ println("\n\nERROR: " + err.Error())
}
}
diff --git a/v2/cmd/wails/show.go b/v2/cmd/wails/show.go
deleted file mode 100644
index c83900c8d..000000000
--- a/v2/cmd/wails/show.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package main
-
-import (
- "github.com/pterm/pterm"
- "github.com/wailsapp/wails/v2/cmd/wails/flags"
- "github.com/wailsapp/wails/v2/cmd/wails/internal"
- "github.com/wailsapp/wails/v2/internal/colour"
- "github.com/wailsapp/wails/v2/internal/github"
-)
-
-func showReleaseNotes(f *flags.ShowReleaseNotes) error {
- if f.NoColour {
- pterm.DisableColor()
- colour.ColourEnabled = false
- }
-
- version := internal.Version
- if f.Version != "" {
- version = f.Version
- }
-
- app.PrintBanner()
- releaseNotes := github.GetReleaseNotes(version, f.NoColour)
- pterm.Println(releaseNotes)
-
- return nil
-}
diff --git a/v2/cmd/wails/update.go b/v2/cmd/wails/update.go
deleted file mode 100644
index 9f8b6e604..000000000
--- a/v2/cmd/wails/update.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
-
- "github.com/labstack/gommon/color"
- "github.com/pterm/pterm"
- "github.com/wailsapp/wails/v2/cmd/wails/flags"
- "github.com/wailsapp/wails/v2/internal/colour"
- "github.com/wailsapp/wails/v2/internal/shell"
-
- "github.com/wailsapp/wails/v2/internal/github"
-)
-
-// AddSubcommand adds the `init` command for the Wails application
-func update(f *flags.Update) error {
- if f.NoColour {
- colour.ColourEnabled = false
- pterm.DisableColor()
-
- }
- // Print banner
- app.PrintBanner()
- pterm.Println("Checking for updates...")
-
- var desiredVersion *github.SemanticVersion
- var err error
- var valid bool
-
- if len(f.Version) > 0 {
- // Check if this is a valid version
- valid, err = github.IsValidTag(f.Version)
- if err == nil {
- if !valid {
- err = fmt.Errorf("version '%s' is invalid", f.Version)
- } else {
- desiredVersion, err = github.NewSemanticVersion(f.Version)
- }
- }
- } else {
- if f.PreRelease {
- desiredVersion, err = github.GetLatestPreRelease()
- } else {
- desiredVersion, err = github.GetLatestStableRelease()
- if err != nil {
- pterm.Println("")
- pterm.Println("No stable release found for this major version. To update to the latest pre-release (eg beta), run:")
- pterm.Println(" wails update -pre")
- return nil
- }
- }
- }
- if err != nil {
- return err
- }
- pterm.Println()
-
- pterm.Println(" Current Version : " + app.Version())
-
- if len(f.Version) > 0 {
- fmt.Printf(" Desired Version : v%s\n", desiredVersion)
- } else {
- if f.PreRelease {
- fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
- } else {
- fmt.Printf(" Latest Release : v%s\n", desiredVersion)
- }
- }
-
- return updateToVersion(desiredVersion, len(f.Version) > 0, app.Version())
-}
-
-func updateToVersion(targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
- targetVersionString := "v" + targetVersion.String()
-
- if targetVersionString == currentVersion {
- pterm.Println("\nLooks like you're up to date!")
- return nil
- }
-
- var desiredVersion string
-
- if !force {
-
- compareVersion := currentVersion
-
- currentVersion, err := github.NewSemanticVersion(compareVersion)
- if err != nil {
- return err
- }
-
- var success bool
-
- // Release -> Pre-Release = Massage current version to prerelease format
- if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
- testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
- if err != nil {
- return err
- }
- success, _ = targetVersion.IsGreaterThan(testVersion)
- }
- // Pre-Release -> Release = Massage target version to prerelease format
- if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
- // We are ok with greater than or equal
- mainversion := currentVersion.MainVersion()
- targetVersion, err = github.NewSemanticVersion(targetVersion.String())
- if err != nil {
- return err
- }
- success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
- }
-
- // Release -> Release = Standard check
- if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
- (targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
-
- success, _ = targetVersion.IsGreaterThan(currentVersion)
- }
-
- // Compare
- if !success {
- pterm.Println("Error: The requested version is lower than the current version.")
- pterm.Println(fmt.Sprintf("If this is what you really want to do, use `wails update -version "+"%s`", targetVersionString))
-
- return nil
- }
-
- desiredVersion = "v" + targetVersion.String()
-
- } else {
- desiredVersion = "v" + targetVersion.String()
- }
-
- pterm.Println()
- pterm.Print("Installing Wails CLI " + desiredVersion + "...")
-
- // Run command in non module directory
- homeDir, err := os.UserHomeDir()
- if err != nil {
- fatal("Cannot find home directory! Please file a bug report!")
- }
-
- sout, serr, err := shell.RunCommand(homeDir, "go", "install", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
- if err != nil {
- pterm.Println("Failed.")
- pterm.Error.Println(sout + `\n` + serr)
- return err
- }
- pterm.Println("Done.")
- pterm.Println(color.Green("\nMake sure you update your project go.mod file to use " + desiredVersion + ":"))
- pterm.Println(color.Green(" require github.com/wailsapp/wails/v2 " + desiredVersion))
- pterm.Println(color.Red("\nTo view the release notes, please run `wails show releasenotes`"))
-
- return nil
-}
diff --git a/v2/cmd/wails/version.go b/v2/cmd/wails/version.go
new file mode 100644
index 000000000..ad41342fa
--- /dev/null
+++ b/v2/cmd/wails/version.go
@@ -0,0 +1,3 @@
+package main
+
+var version = "v2.0.0-alpha.6"
diff --git a/v2/examples/customlayout/.gitignore b/v2/examples/customlayout/.gitignore
deleted file mode 100644
index 53e9ed8b5..000000000
--- a/v2/examples/customlayout/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-build/bin
-node_modules
-myfrontend/wailsjs
\ No newline at end of file
diff --git a/v2/examples/customlayout/README.md b/v2/examples/customlayout/README.md
deleted file mode 100644
index e4d79d4ec..000000000
--- a/v2/examples/customlayout/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# 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
diff --git a/v2/examples/customlayout/build/README.md b/v2/examples/customlayout/build/README.md
deleted file mode 100644
index 3018a06c4..000000000
--- a/v2/examples/customlayout/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/customlayout/build/appicon.png b/v2/examples/customlayout/build/appicon.png
deleted file mode 100644
index 63617fe4f..000000000
Binary files a/v2/examples/customlayout/build/appicon.png and /dev/null differ
diff --git a/v2/examples/customlayout/build/darwin/Info.dev.plist b/v2/examples/customlayout/build/darwin/Info.dev.plist
deleted file mode 100644
index 02e7358ee..000000000
--- a/v2/examples/customlayout/build/darwin/Info.dev.plist
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- CFBundlePackageType
- APPL
- CFBundleName
- {{.Info.ProductName}}
- CFBundleExecutable
- {{.Name}}
- CFBundleIdentifier
- com.wails.{{.Name}}
- CFBundleVersion
- {{.Info.ProductVersion}}
- CFBundleGetInfoString
- {{.Info.Comments}}
- CFBundleShortVersionString
- {{.Info.ProductVersion}}
- CFBundleIconFile
- iconfile
- LSMinimumSystemVersion
- 10.13.0
- NSHighResolutionCapable
- true
- NSHumanReadableCopyright
- {{.Info.Copyright}}
- NSAppTransportSecurity
-
- NSAllowsLocalNetworking
-
-
-
-
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/darwin/Info.plist b/v2/examples/customlayout/build/darwin/Info.plist
deleted file mode 100644
index e7819a7e8..000000000
--- a/v2/examples/customlayout/build/darwin/Info.plist
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
- CFBundlePackageType
- APPL
- CFBundleName
- {{.Info.ProductName}}
- CFBundleExecutable
- {{.Name}}
- CFBundleIdentifier
- com.wails.{{.Name}}
- CFBundleVersion
- {{.Info.ProductVersion}}
- CFBundleGetInfoString
- {{.Info.Comments}}
- CFBundleShortVersionString
- {{.Info.ProductVersion}}
- CFBundleIconFile
- iconfile
- LSMinimumSystemVersion
- 10.13.0
- NSHighResolutionCapable
- true
- NSHumanReadableCopyright
- {{.Info.Copyright}}
-
-
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/windows/icon.ico b/v2/examples/customlayout/build/windows/icon.ico
deleted file mode 100644
index f33479841..000000000
Binary files a/v2/examples/customlayout/build/windows/icon.ico and /dev/null differ
diff --git a/v2/examples/customlayout/build/windows/info.json b/v2/examples/customlayout/build/windows/info.json
deleted file mode 100644
index c23c173c9..000000000
--- a/v2/examples/customlayout/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/customlayout/build/windows/installer/project.nsi b/v2/examples/customlayout/build/windows/installer/project.nsi
deleted file mode 100644
index 2ccc0f3f3..000000000
--- a/v2/examples/customlayout/build/windows/installer/project.nsi
+++ /dev/null
@@ -1,104 +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.webview2runtime
-
- SetOutPath $INSTDIR
-
- !insertmacro wails.files
-
- CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
- CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
-
- !insertmacro wails.writeUninstaller
-SectionEnd
-
-Section "uninstall"
- RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
-
- RMDir /r $INSTDIR
-
- Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
- Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
-
- !insertmacro wails.deleteUninstaller
-SectionEnd
diff --git a/v2/examples/customlayout/build/windows/installer/wails_tools.nsh b/v2/examples/customlayout/build/windows/installer/wails_tools.nsh
deleted file mode 100644
index 66dc209a3..000000000
--- a/v2/examples/customlayout/build/windows/installer/wails_tools.nsh
+++ /dev/null
@@ -1,171 +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
-
-# 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
\ No newline at end of file
diff --git a/v2/examples/customlayout/build/windows/wails.exe.manifest b/v2/examples/customlayout/build/windows/wails.exe.manifest
deleted file mode 100644
index 17e1a2387..000000000
--- a/v2/examples/customlayout/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/customlayout/cmd/customlayout/app.go b/v2/examples/customlayout/cmd/customlayout/app.go
deleted file mode 100644
index af53038a1..000000000
--- a/v2/examples/customlayout/cmd/customlayout/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/customlayout/cmd/customlayout/main.go b/v2/examples/customlayout/cmd/customlayout/main.go
deleted file mode 100644
index dcb59a80c..000000000
--- a/v2/examples/customlayout/cmd/customlayout/main.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package main
-
-import (
- "changeme/myfrontend"
- "github.com/wailsapp/wails/v2"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func main() {
- // Create an instance of the app structure
- app := NewApp()
-
- // Create application with options
- err := wails.Run(&options.App{
- Title: "customlayout",
- Width: 1024,
- Height: 768,
- Assets: myfrontend.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/customlayout/cmd/customlayout/wails.json b/v2/examples/customlayout/cmd/customlayout/wails.json
deleted file mode 100644
index e37c2ec7d..000000000
--- a/v2/examples/customlayout/cmd/customlayout/wails.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "$schema": "https://wails.io/schemas/config.v2.json",
- "name": "customlayout",
- "outputfilename": "customlayout",
- "build:dir": "../../build",
- "frontend:dir": "../../myfrontend",
- "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/customlayout/go.mod b/v2/examples/customlayout/go.mod
deleted file mode 100644
index e1a17304e..000000000
--- a/v2/examples/customlayout/go.mod
+++ /dev/null
@@ -1,39 +0,0 @@
-module changeme
-
-go 1.22.0
-
-toolchain go1.24.1
-
-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/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/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
-)
-
-replace github.com/wailsapp/wails/v2 v2.1.0 => ../..
diff --git a/v2/examples/customlayout/go.sum b/v2/examples/customlayout/go.sum
deleted file mode 100644
index f1995affb..000000000
--- a/v2/examples/customlayout/go.sum
+++ /dev/null
@@ -1,111 +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.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/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/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/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/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/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/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/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/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/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/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/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/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/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=
-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-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/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/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/customlayout/myfrontend/assets.go b/v2/examples/customlayout/myfrontend/assets.go
deleted file mode 100644
index a6dec2f8f..000000000
--- a/v2/examples/customlayout/myfrontend/assets.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package myfrontend
-
-import "embed"
-
-//go:embed all:dist
-var Assets embed.FS
diff --git a/v2/examples/customlayout/myfrontend/index.html b/v2/examples/customlayout/myfrontend/index.html
deleted file mode 100644
index 1ceda7392..000000000
--- a/v2/examples/customlayout/myfrontend/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- customlayout
-
-
-
-
-
-
diff --git a/v2/examples/customlayout/myfrontend/package.json b/v2/examples/customlayout/myfrontend/package.json
deleted file mode 100644
index a1b6f8e1a..000000000
--- a/v2/examples/customlayout/myfrontend/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/customlayout/myfrontend/src/app.css b/v2/examples/customlayout/myfrontend/src/app.css
deleted file mode 100644
index 59d06f692..000000000
--- a/v2/examples/customlayout/myfrontend/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/customlayout/myfrontend/src/assets/fonts/OFL.txt b/v2/examples/customlayout/myfrontend/src/assets/fonts/OFL.txt
deleted file mode 100644
index 9cac04ce8..000000000
--- a/v2/examples/customlayout/myfrontend/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/customlayout/myfrontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/v2/examples/customlayout/myfrontend/src/assets/fonts/nunito-v16-latin-regular.woff2
deleted file mode 100644
index 2f9cc5964..000000000
Binary files a/v2/examples/customlayout/myfrontend/src/assets/fonts/nunito-v16-latin-regular.woff2 and /dev/null differ
diff --git a/v2/examples/customlayout/myfrontend/src/assets/images/logo-universal.png b/v2/examples/customlayout/myfrontend/src/assets/images/logo-universal.png
deleted file mode 100644
index d63303bfa..000000000
Binary files a/v2/examples/customlayout/myfrontend/src/assets/images/logo-universal.png and /dev/null differ
diff --git a/v2/examples/customlayout/myfrontend/src/main.js b/v2/examples/customlayout/myfrontend/src/main.js
deleted file mode 100644
index 6cb4ad78d..000000000
--- a/v2/examples/customlayout/myfrontend/src/main.js
+++ /dev/null
@@ -1,48 +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 👇
-
-
- Greet
-
-
-`;
-document.getElementById('logo').src = logo;
-document.addEventListener("keydown", (e) => {
- if (e.code === "Enter") {
- window.greet();
- }
-});
-
-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);
- }
-};
diff --git a/v2/examples/customlayout/myfrontend/src/style.css b/v2/examples/customlayout/myfrontend/src/style.css
deleted file mode 100644
index 3940d6c63..000000000
--- a/v2/examples/customlayout/myfrontend/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/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/appicon.png b/v2/examples/dragdrop-test/build/appicon.png
deleted file mode 100644
index 63617fe4f..000000000
Binary files a/v2/examples/dragdrop-test/build/appicon.png and /dev/null differ
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/icon.ico b/v2/examples/dragdrop-test/build/windows/icon.ico
deleted file mode 100644
index f33479841..000000000
Binary files a/v2/examples/dragdrop-test/build/windows/icon.ico and /dev/null differ
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
-
-
-
-
-`;
-
-// 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 👇
-
-
- Greet
-
-
-`;
-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 2eb753ee2..abe0a62af 100644
--- a/v2/go.mod
+++ b/v2/go.mod
@@ -1,112 +1,28 @@
module github.com/wailsapp/wails/v2
-go 1.22.0
+go 1.15
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/google/shlex v0.0.0-20191202100458-e7afc7fbc510
- github.com/google/uuid v1.6.0
- github.com/gorilla/websocket v1.5.3
+ github.com/davecgh/go-spew v1.1.1
+ github.com/fatih/structtag v1.2.0
+ github.com/fsnotify/fsnotify v1.4.9
+ github.com/imdario/mergo v0.3.11
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/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/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/pkg/errors v0.9.1
- github.com/pterm/pterm v0.12.80
- 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/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
-)
-
-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
- 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
- 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/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/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/muesli/reflow v0.3.0 // indirect
- github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
+ github.com/leaanthony/clir v1.0.4
+ github.com/leaanthony/gosod v0.0.4
+ github.com/leaanthony/slicer v1.5.0
+ github.com/matryer/is v1.4.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
- github.com/pjbgf/sha1cd v0.3.2 // 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/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
- 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
+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
+ github.com/olekukonko/tablewriter v0.0.4
+ github.com/pkg/errors v0.9.1
+ github.com/tdewolff/minify v2.3.6+incompatible
+ github.com/tdewolff/parse v2.3.4+incompatible // indirect
+ github.com/tdewolff/test v1.0.6 // indirect
+ github.com/xyproto/xpm v1.2.1
+ golang.org/x/net v0.0.0-20200822124328-c89045814202
+ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
+ golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
+ gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
+ nhooyr.io/websocket v1.8.6
)
diff --git a/v2/go.sum b/v2/go.sum
index f6df3507e..08f80d04b 100644
--- a/v2/go.sum
+++ b/v2/go.sum
@@ -1,351 +1,134 @@
-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=
-github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
-github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
-github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
-github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
-github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
-github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
-github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
-github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
-github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
-github.com/Masterminds/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/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/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/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/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/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/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/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
-github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
-github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
-github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
-github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
-github.com/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/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
+github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
+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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
+github.com/imdario/mergo v0.3.11/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/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/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/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
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/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
+github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
+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/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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+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.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/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
-github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/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/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/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
+github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+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 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
+github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
-github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
-github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
-github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
-github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
-github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
-github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
-github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=
-github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo=
-github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
-github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/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/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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
-github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
-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/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=
-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/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark v1.7.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/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
+github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
+github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
+github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
+github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
+github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
+github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.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/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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
-golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
-golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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/sync v0.0.0-20200625203802-6e8e738ad208/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-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-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
-golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
+golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.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/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
-golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
+golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
+golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
+gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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.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=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
+nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
diff --git a/v2/internal/app/app.go b/v2/internal/app/app.go
deleted file mode 100644
index 0cd6bf614..000000000
--- a/v2/internal/app/app.go
+++ /dev/null
@@ -1,45 +0,0 @@
-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"
- "github.com/wailsapp/wails/v2/pkg/menu"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-// App defines a Wails application structure
-type App struct {
- frontend frontend.Frontend
- logger *logger.Logger
- options *options.App
-
- menuManager *menumanager.Manager
-
- // 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)
- ctx context.Context
-}
-
-// Shutdown the application
-func (a *App) Shutdown() {
- if a.frontend != nil {
- a.frontend.Quit()
- }
-}
-
-// SetApplicationMenu sets the application menu
-func (a *App) SetApplicationMenu(menu *menu.Menu) {
- if a.frontend != nil {
- a.frontend.MenuSetApplicationMenu(menu)
- }
-}
diff --git a/v2/internal/app/app_bindings.go b/v2/internal/app/app_bindings.go
deleted file mode 100644
index be031819c..000000000
--- a/v2/internal/app/app_bindings.go
+++ /dev/null
@@ -1,124 +0,0 @@
-//go:build bindings
-
-package app
-
-import (
- "flag"
- "os"
- "path/filepath"
-
- "github.com/leaanthony/gosod"
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/frontend/runtime/wrapper"
- "github.com/wailsapp/wails/v2/internal/fs"
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/internal/project"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func (a *App) Run() error {
-
- // Create binding exemptions - Ugly hack. There must be a better way
- bindingExemptions := []interface{}{
- a.options.OnStartup,
- a.options.OnShutdown,
- a.options.OnDomReady,
- a.options.OnBeforeClose,
- }
-
- // Check for CLI Flags
- bindingFlags := flag.NewFlagSet("bindings", flag.ContinueOnError)
-
- var tsPrefixFlag *string
- var tsPostfixFlag *string
- var tsOutputTypeFlag *string
-
- tsPrefix := os.Getenv("tsprefix")
- if tsPrefix == "" {
- tsPrefixFlag = bindingFlags.String("tsprefix", "", "Prefix for generated typescript entities")
- }
-
- tsSuffix := os.Getenv("tssuffix")
- if tsSuffix == "" {
- 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
- }
- if tsPostfixFlag != nil {
- tsSuffix = *tsPostfixFlag
- }
- if tsOutputTypeFlag != nil {
- tsOutputType = *tsOutputTypeFlag
- }
-
- appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated(), a.options.EnumBind)
-
- appBindings.SetTsPrefix(tsPrefix)
- appBindings.SetTsSuffix(tsSuffix)
- appBindings.SetOutputType(tsOutputType)
-
- err := generateBindings(appBindings)
- if err != nil {
- return err
- }
- return nil
-}
-
-// CreateApp creates the app!
-func CreateApp(appoptions *options.App) (*App, error) {
- // Set up logger
- myLogger := logger.New(appoptions.Logger)
- myLogger.SetLogLevel(appoptions.LogLevel)
-
- result := &App{
- logger: myLogger,
- options: appoptions,
- }
-
- return result, nil
-
-}
-
-func generateBindings(bindings *binding.Bindings) error {
-
- cwd, err := os.Getwd()
- if err != nil {
- return err
- }
- projectConfig, err := project.Load(cwd)
- if err != nil {
- return err
- }
-
- wailsjsbasedir := filepath.Join(projectConfig.GetWailsJSDir(), "wailsjs")
-
- runtimeDir := filepath.Join(wailsjsbasedir, "runtime")
- _ = os.RemoveAll(runtimeDir)
- extractor := gosod.New(wrapper.RuntimeWrapper)
- err = extractor.Extract(runtimeDir, nil)
- if err != nil {
- return err
- }
-
- goBindingsDir := filepath.Join(wailsjsbasedir, "go")
- err = os.RemoveAll(goBindingsDir)
- if err != nil {
- return err
- }
- _ = fs.MkDirs(goBindingsDir)
-
- err = bindings.GenerateGoBindings(goBindingsDir)
- if err != nil {
- return err
- }
-
- return fs.SetPermissions(wailsjsbasedir, 0755)
-}
diff --git a/v2/internal/app/app_debug.go b/v2/internal/app/app_debug.go
deleted file mode 100644
index c14bedec1..000000000
--- a/v2/internal/app/app_debug.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//go:build debug
-
-package app
-
-func IsDebug() bool {
- return true
-}
diff --git a/v2/internal/app/app_debug_not.go b/v2/internal/app/app_debug_not.go
deleted file mode 100644
index 04f841ede..000000000
--- a/v2/internal/app/app_debug_not.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//go:build !debug
-
-package app
-
-func IsDebug() bool {
- return false
-}
diff --git a/v2/internal/app/app_default_unix.go b/v2/internal/app/app_default_unix.go
deleted file mode 100644
index 10d801285..000000000
--- a/v2/internal/app/app_default_unix.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build !dev && !production && !bindings && (linux || darwin)
-
-package app
-
-import (
- "fmt"
-
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func (a *App) Run() error {
- return nil
-}
-
-// CreateApp creates the app!
-func CreateApp(_ *options.App) (*App, error) {
- return nil, fmt.Errorf(`Wails applications will not build without the correct build tags.`)
-}
diff --git a/v2/internal/app/app_default_windows.go b/v2/internal/app/app_default_windows.go
deleted file mode 100644
index b1b66a081..000000000
--- a/v2/internal/app/app_default_windows.go
+++ /dev/null
@@ -1,27 +0,0 @@
-//go:build !dev && !production && !bindings && windows
-
-package app
-
-import (
- "os/exec"
-
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func (a *App) Run() error {
- return nil
-}
-
-// CreateApp creates the app!
-func CreateApp(_ *options.App) (*App, error) {
- result := w32.MessageBox(0,
- `Wails applications will not build without the correct build tags.
-Please use "wails build" or press "OK" to open the documentation on how to use "go build"`,
- "Error",
- w32.MB_ICONERROR|w32.MB_OKCANCEL)
- if result == 1 {
- exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://wails.io/docs/guides/manual-builds").Start()
- }
- return nil, nil
-}
diff --git a/v2/internal/app/app_dev.go b/v2/internal/app/app_dev.go
deleted file mode 100644
index 6de845f96..000000000
--- a/v2/internal/app/app_dev.go
+++ /dev/null
@@ -1,298 +0,0 @@
-//go:build dev
-
-package app
-
-import (
- "context"
- "embed"
- "flag"
- "fmt"
- iofs "io/fs"
- "net"
- "net/url"
- "os"
- "path/filepath"
- "time"
-
- "github.com/wailsapp/wails/v2/pkg/assetserver"
-
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop"
- "github.com/wailsapp/wails/v2/internal/frontend/devserver"
- "github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
- "github.com/wailsapp/wails/v2/internal/frontend/runtime"
- "github.com/wailsapp/wails/v2/internal/fs"
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/internal/menumanager"
- pkglogger "github.com/wailsapp/wails/v2/pkg/logger"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func (a *App) Run() error {
- err := a.frontend.Run(a.ctx)
- a.frontend.RunMainLoop()
- a.frontend.WindowClose()
- if a.shutdownCallback != nil {
- a.shutdownCallback(a.ctx)
- }
- return err
-}
-
-// CreateApp creates the app!
-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
- }
-
- myLogger := logger.New(appoptions.Logger)
- myLogger.SetLogLevel(appoptions.LogLevel)
-
- // Check for CLI Flags
- devFlags := flag.NewFlagSet("dev", flag.ContinueOnError)
-
- var assetdirFlag *string
- var devServerFlag *string
- var frontendDevServerURLFlag *string
- var loglevelFlag *string
-
- assetdir := os.Getenv("assetdir")
- if assetdir == "" {
- assetdirFlag = devFlags.String("assetdir", "", "Directory to serve assets")
- }
-
- devServer := os.Getenv("devserver")
- if devServer == "" {
- devServerFlag = devFlags.String("devserver", "", "Address to bind the wails dev server to")
- }
-
- frontendDevServerURL := os.Getenv("frontenddevserverurl")
- if frontendDevServerURL == "" {
- frontendDevServerURLFlag = devFlags.String("frontenddevserverurl", "", "URL of the external frontend dev server")
- }
-
- loglevel := os.Getenv("loglevel")
- appLogLevel := appoptions.LogLevel.String()
- if loglevel != "" {
- appLogLevel = loglevel
- }
- 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 == "" {
- // Parse args but ignore errors in case -appargs was used to pass in args for the app.
- _ = devFlags.Parse(os.Args[1:])
- if assetdirFlag != nil {
- assetdir = *assetdirFlag
- }
- if devServerFlag != nil {
- devServer = *devServerFlag
- }
- if frontendDevServerURLFlag != nil {
- frontendDevServerURL = *frontendDevServerURLFlag
- }
- if loglevelFlag != nil {
- loglevel = *loglevelFlag
- }
- }
-
- assetConfig, err := assetserver.BuildAssetServerConfig(appoptions)
- if err != nil {
- return nil, err
- }
-
- if assetConfig.Assets == nil && frontendDevServerURL != "" {
- myLogger.Warning("No AssetServer.Assets has been defined but a frontend DevServer, the frontend DevServer will not be used.")
- frontendDevServerURL = ""
- assetdir = ""
- }
-
- if frontendDevServerURL != "" {
- _, port, err := net.SplitHostPort(devServer)
- if err != nil {
- return nil, fmt.Errorf("unable to determine port of DevServer: %s", err)
- }
-
- ctx = context.WithValue(ctx, "assetserverport", port)
-
- ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
-
- externalURL, err := url.Parse(frontendDevServerURL)
- if err != nil {
- return nil, err
- }
-
- if externalURL.Host == "" {
- return nil, fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
- }
-
- waitCb := func() { myLogger.Debug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
- if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
- myLogger.Error("Timeout waiting for frontend DevServer")
- }
-
- handler := assetserver.NewExternalAssetsHandler(myLogger, assetConfig, externalURL)
- assetConfig.Assets = nil
- assetConfig.Handler = handler
- assetConfig.Middleware = nil
-
- myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL)
- } else {
- if assetdir == "" {
- // If no assetdir has been defined, let's try to infer it from the project root and the asset FS.
- assetdir, err = tryInferAssetDirFromFS(assetConfig.Assets)
- if err != nil {
- return nil, fmt.Errorf("unable to infer the AssetDir from your Assets fs.FS: %w", err)
- }
- }
-
- if assetdir != "" {
- // Let's override the assets to serve from on disk, if needed
- absdir, err := filepath.Abs(assetdir)
- if err != nil {
- return nil, err
- }
-
- myLogger.Info("Serving assets from disk: %s", absdir)
- assetConfig.Assets = os.DirFS(absdir)
-
- ctx = context.WithValue(ctx, "assetdir", assetdir)
- }
- }
-
- // Migrate deprecated options to the new AssetServer option
- appoptions.Assets = nil
- appoptions.AssetsHandler = nil
- appoptions.AssetServer = &assetConfig
-
- if devServer != "" {
- ctx = context.WithValue(ctx, "devserver", devServer)
- }
-
- if loglevel != "" {
- level, err := pkglogger.StringToLogLevel(loglevel)
- 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)
- }
- }
-
- // Attach logger to context
- ctx = context.WithValue(ctx, "logger", myLogger)
- ctx = context.WithValue(ctx, "buildtype", "dev")
-
- // Preflight checks
- err = PreflightChecks(appoptions, myLogger)
- if err != nil {
- return nil, err
- }
-
- // Merge default options
- options.MergeDefaults(appoptions)
-
- var menuManager *menumanager.Manager
-
- // Process the application menu
- if appoptions.Menu != nil {
- // Create the menu manager
- menuManager = menumanager.NewManager()
- err = menuManager.SetApplicationMenu(appoptions.Menu)
- if err != nil {
- return nil, err
- }
- }
-
- // Create binding exemptions - Ugly hack. There must be a better way
- bindingExemptions := []interface{}{
- appoptions.OnStartup,
- appoptions.OnShutdown,
- appoptions.OnDomReady,
- appoptions.OnBeforeClose,
- }
- appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, false, appoptions.EnumBind)
-
- eventHandler := runtime.NewEvents(myLogger)
- ctx = context.WithValue(ctx, "events", eventHandler)
- messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery)
-
- // Create the frontends and register to event handler
- desktopFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
- appFrontend := devserver.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher, menuManager, desktopFrontend)
- eventHandler.AddFrontend(appFrontend)
- eventHandler.AddFrontend(desktopFrontend)
-
- ctx = context.WithValue(ctx, "frontend", appFrontend)
- result := &App{
- ctx: ctx,
- frontend: appFrontend,
- logger: myLogger,
- menuManager: menuManager,
- startupCallback: appoptions.OnStartup,
- shutdownCallback: appoptions.OnShutdown,
- debug: true,
- devtoolsEnabled: true,
- }
-
- result.options = appoptions
-
- return result, nil
-
-}
-
-func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
- if _, isEmbedFs := assets.(embed.FS); !isEmbedFs {
- // We only infer the assetdir for embed.FS assets
- return "", nil
- }
-
- path, err := fs.FindPathToFile(assets, "index.html")
- if err != nil {
- return "", err
- }
-
- path, err = filepath.Abs(path)
- if err != nil {
- return "", err
- }
-
- if _, err := os.Stat(filepath.Join(path, "index.html")); err != nil {
- if os.IsNotExist(err) {
- err = fmt.Errorf(
- "inferred assetdir '%s' does not exist or does not contain an 'index.html' file, "+
- "please specify it with -assetdir or set it in wails.json",
- path)
- }
- return "", err
- }
-
- return path, nil
-}
-
-func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
- if timeout == 0 {
- timeout = time.Minute
- }
-
- deadline := time.Now().Add(timeout)
- for time.Now().Before(deadline) {
- conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
- if conn != nil {
- conn.Close()
- return true
- }
-
- waitCB()
- time.Sleep(1 * time.Second)
- }
- return false
-}
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_obfuscated.go b/v2/internal/app/app_obfuscated.go
deleted file mode 100644
index c78c10b87..000000000
--- a/v2/internal/app/app_obfuscated.go
+++ /dev/null
@@ -1,8 +0,0 @@
-//go:build obfuscated
-
-package app
-
-// IsObfuscated returns true if the obfuscated build tag is set
-func IsObfuscated() bool {
- return true
-}
diff --git a/v2/internal/app/app_obfuscated_not.go b/v2/internal/app/app_obfuscated_not.go
deleted file mode 100644
index 90cc8559f..000000000
--- a/v2/internal/app/app_obfuscated_not.go
+++ /dev/null
@@ -1,8 +0,0 @@
-//go:build !obfuscated
-
-package app
-
-// IsObfuscated returns false if the obfuscated build tag is not set
-func IsObfuscated() bool {
- return false
-}
diff --git a/v2/internal/app/app_preflight_unix.go b/v2/internal/app/app_preflight_unix.go
deleted file mode 100644
index f554df740..000000000
--- a/v2/internal/app/app_preflight_unix.go
+++ /dev/null
@@ -1,12 +0,0 @@
-//go:build (linux || darwin) && !bindings
-
-package app
-
-import (
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func PreflightChecks(_ *options.App, _ *logger.Logger) error {
- return nil
-}
diff --git a/v2/internal/app/app_preflight_windows.go b/v2/internal/app/app_preflight_windows.go
deleted file mode 100644
index 1b71b8b19..000000000
--- a/v2/internal/app/app_preflight_windows.go
+++ /dev/null
@@ -1,27 +0,0 @@
-//go:build windows && !bindings
-
-package app
-
-import (
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/internal/wv2installer"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func PreflightChecks(options *options.App, logger *logger.Logger) error {
-
- _ = options
-
- // Process the webview2 runtime situation. We can pass a strategy in via the `webview2` flag for `wails build`.
- // This will determine how wv2runtime.Process will handle a lack of valid runtime.
- installedVersion, err := wv2installer.Process(options)
- if installedVersion != "" {
- logger.Debug("WebView2 Runtime Version '%s' installed. Minimum version required: %s.",
- installedVersion, wv2installer.MinimumRuntimeVersion)
- }
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/v2/internal/app/app_production.go b/v2/internal/app/app_production.go
deleted file mode 100644
index 9eb0e5a66..000000000
--- a/v2/internal/app/app_production.go
+++ /dev/null
@@ -1,104 +0,0 @@
-//go:build production
-
-package app
-
-import (
- "context"
-
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop"
- "github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
- "github.com/wailsapp/wails/v2/internal/frontend/runtime"
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/internal/menumanager"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func (a *App) Run() error {
- err := a.frontend.Run(a.ctx)
- a.frontend.RunMainLoop()
- a.frontend.WindowClose()
- if a.shutdownCallback != nil {
- a.shutdownCallback(a.ctx)
- }
- return err
-}
-
-// CreateApp creates the app!
-func CreateApp(appoptions *options.App) (*App, error) {
- var err error
-
- ctx := context.Background()
-
- // Merge default options
- 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)
- if IsDebug() {
- myLogger.SetLogLevel(appoptions.LogLevel)
- } else {
- myLogger.SetLogLevel(appoptions.LogLevelProduction)
- }
- ctx = context.WithValue(ctx, "logger", myLogger)
- ctx = context.WithValue(ctx, "obfuscated", IsObfuscated())
-
- // Preflight Checks
- err = PreflightChecks(appoptions, myLogger)
- if err != nil {
- return nil, err
- }
-
- // Create the menu manager
- menuManager := menumanager.NewManager()
-
- // Process the application menu
- if appoptions.Menu != nil {
- err = menuManager.SetApplicationMenu(appoptions.Menu)
- if err != nil {
- return nil, err
- }
- }
-
- // Create binding exemptions - Ugly hack. There must be a better way
- bindingExemptions := []interface{}{
- appoptions.OnStartup,
- appoptions.OnShutdown,
- appoptions.OnDomReady,
- appoptions.OnBeforeClose,
- }
- appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions, IsObfuscated(), appoptions.EnumBind)
- eventHandler := runtime.NewEvents(myLogger)
- ctx = context.WithValue(ctx, "events", eventHandler)
- // Attach logger to context
- if debug {
- ctx = context.WithValue(ctx, "buildtype", "debug")
- } else {
- ctx = context.WithValue(ctx, "buildtype", "production")
- }
-
- messageDispatcher := dispatcher.NewDispatcher(ctx, myLogger, appBindings, eventHandler, appoptions.ErrorFormatter, appoptions.DisablePanicRecovery)
- appFrontend := desktop.NewFrontend(ctx, appoptions, myLogger, appBindings, messageDispatcher)
- eventHandler.AddFrontend(appFrontend)
-
- ctx = context.WithValue(ctx, "frontend", appFrontend)
- result := &App{
- ctx: ctx,
- frontend: appFrontend,
- logger: myLogger,
- menuManager: menuManager,
- startupCallback: appoptions.OnStartup,
- shutdownCallback: appoptions.OnShutdown,
- debug: debug,
- devtoolsEnabled: devtoolsEnabled,
- options: appoptions,
- }
-
- return result, nil
-
-}
diff --git a/v2/internal/app/debug.go b/v2/internal/app/debug.go
new file mode 100644
index 000000000..7bcabc3c9
--- /dev/null
+++ b/v2/internal/app/debug.go
@@ -0,0 +1,12 @@
+// +build debug
+
+package app
+
+// Init initialises the application for a debug environment
+func (a *App) Init() error {
+ // Indicate debug mode
+ a.debug = true
+ // Enable dev tools
+ a.options.DevTools = true
+ return nil
+}
diff --git a/v2/internal/app/default.go b/v2/internal/app/default.go
new file mode 100644
index 000000000..8af8e415e
--- /dev/null
+++ b/v2/internal/app/default.go
@@ -0,0 +1,44 @@
+// +build !desktop,!hybrid,!server
+
+package app
+
+// This is the default application that will get run if the user compiles using `go build`.
+// The reason we want to prevent that is that the `wails build` command does a lot of behind
+// the scenes work such as asset compilation. If we allow `go build`, the state of these assets
+// will be unknown and the application will not work as expected.
+
+import (
+ "github.com/wailsapp/wails/v2/internal/logger"
+ "os"
+
+ "github.com/wailsapp/wails/v2/pkg/options"
+)
+
+// App defines a Wails application structure
+type App struct {
+ Title string
+ Width int
+ Height int
+ Resizable bool
+
+ // Indicates if the app is running in debug mode
+ debug bool
+
+ logger *logger.Logger
+}
+
+// CreateApp returns a null application
+func CreateApp(_ *options.App) (*App, error) {
+ return &App{}, nil
+}
+
+// Run the application
+func (a *App) Run() error {
+ println(`FATAL: This application was built using "go build". This is unsupported. Please compile using "wails build".`)
+ os.Exit(1)
+ return nil
+}
+
+// Bind the dummy interface
+func (a *App) Bind(_ interface{}) {
+}
diff --git a/v2/internal/app/desktop.go b/v2/internal/app/desktop.go
new file mode 100644
index 000000000..96e88360d
--- /dev/null
+++ b/v2/internal/app/desktop.go
@@ -0,0 +1,220 @@
+// +build desktop,!server
+
+package app
+
+import (
+ "github.com/wailsapp/wails/v2/internal/binding"
+ "github.com/wailsapp/wails/v2/internal/ffenestri"
+ "github.com/wailsapp/wails/v2/internal/logger"
+ "github.com/wailsapp/wails/v2/internal/menumanager"
+ "github.com/wailsapp/wails/v2/internal/messagedispatcher"
+ "github.com/wailsapp/wails/v2/internal/runtime"
+ "github.com/wailsapp/wails/v2/internal/servicebus"
+ "github.com/wailsapp/wails/v2/internal/signal"
+ "github.com/wailsapp/wails/v2/internal/subsystem"
+ "github.com/wailsapp/wails/v2/pkg/options"
+)
+
+// App defines a Wails application structure
+type App struct {
+ window *ffenestri.Application
+ servicebus *servicebus.ServiceBus
+ logger *logger.Logger
+ signal *signal.Manager
+ options *options.App
+
+ // Subsystems
+ log *subsystem.Log
+ runtime *subsystem.Runtime
+ event *subsystem.Event
+ binding *subsystem.Binding
+ call *subsystem.Call
+ menu *subsystem.Menu
+ dispatcher *messagedispatcher.Dispatcher
+
+ menuManager *menumanager.Manager
+
+ // Indicates if the app is in debug mode
+ debug bool
+
+ // This is our binding DB
+ bindings *binding.Bindings
+
+ // Application Stores
+ loglevelStore *runtime.Store
+ appconfigStore *runtime.Store
+}
+
+// Create App
+func CreateApp(appoptions *options.App) (*App, error) {
+
+ // Merge default options
+ options.MergeDefaults(appoptions)
+
+ // Set up logger
+ myLogger := logger.New(appoptions.Logger)
+ myLogger.SetLogLevel(appoptions.LogLevel)
+
+ // Create the menu manager
+ menuManager := menumanager.NewManager()
+
+ // Process the application menu
+ menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
+
+ // Process context menus
+ contextMenus := options.GetContextMenus(appoptions)
+ for _, contextMenu := range contextMenus {
+ menuManager.AddContextMenu(contextMenu)
+ }
+
+ // Process tray menus
+ trayMenus := options.GetTrayMenus(appoptions)
+ for _, trayMenu := range trayMenus {
+ menuManager.AddTrayMenu(trayMenu)
+ }
+
+ window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
+
+ result := &App{
+ window: window,
+ servicebus: servicebus.New(myLogger),
+ logger: myLogger,
+ bindings: binding.NewBindings(myLogger),
+ menuManager: menuManager,
+ }
+
+ result.options = appoptions
+
+ // Initialise the app
+ err := result.Init()
+
+ return result, err
+
+}
+
+// Run the application
+func (a *App) Run() error {
+
+ var err error
+
+ // Setup signal handler
+ signalsubsystem, err := signal.NewManager(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.signal = signalsubsystem
+ a.signal.Start()
+
+ // Start the service bus
+ a.servicebus.Debug()
+ err = a.servicebus.Start()
+ if err != nil {
+ return err
+ }
+
+ runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.runtime = runtimesubsystem
+ err = a.runtime.Start()
+ if err != nil {
+ return err
+ }
+
+ // Application Stores
+ a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
+ a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
+
+ // Start the binding subsystem
+ bindingsubsystem, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
+ if err != nil {
+ return err
+ }
+ a.binding = bindingsubsystem
+ err = a.binding.Start()
+ if err != nil {
+ return err
+ }
+
+ // Start the logging subsystem
+ log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
+ if err != nil {
+ return err
+ }
+ a.log = log
+ err = a.log.Start()
+ if err != nil {
+ return err
+ }
+
+ // create the dispatcher
+ dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.dispatcher = dispatcher
+ err = dispatcher.Start()
+ if err != nil {
+ return err
+ }
+
+ // Start the eventing subsystem
+ event, err := subsystem.NewEvent(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.event = event
+ err = a.event.Start()
+ if err != nil {
+ return err
+ }
+
+ // Start the menu subsystem
+ menusubsystem, err := subsystem.NewMenu(a.servicebus, a.logger, a.menuManager)
+ if err != nil {
+ return err
+ }
+ a.menu = menusubsystem
+ err = a.menu.Start()
+ if err != nil {
+ return err
+ }
+
+ // Start the call subsystem
+ call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
+ if err != nil {
+ return err
+ }
+ a.call = call
+ err = a.call.Start()
+ if err != nil {
+ return err
+ }
+
+ // Dump bindings as a debug
+ bindingDump, err := a.bindings.ToJSON()
+ if err != nil {
+ return err
+ }
+
+ result := a.window.Run(dispatcher, bindingDump, a.debug)
+ a.logger.Trace("Ffenestri.Run() exited")
+ err = a.servicebus.Stop()
+ if err != nil {
+ return err
+ }
+
+ return result
+}
+
+// Bind a struct to the application by passing in
+// a pointer to it
+func (a *App) Bind(structPtr interface{}) {
+
+ // Add the struct to the bindings
+ err := a.bindings.Add(structPtr)
+ if err != nil {
+ a.logger.Fatal("Error during binding: " + err.Error())
+ }
+}
diff --git a/v2/internal/app/hybrid.go b/v2/internal/app/hybrid.go
new file mode 100644
index 000000000..d273e2610
--- /dev/null
+++ b/v2/internal/app/hybrid.go
@@ -0,0 +1,205 @@
+// +build !server,!desktop,hybrid
+
+package app
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/leaanthony/clir"
+ "github.com/wailsapp/wails/v2/internal/binding"
+ "github.com/wailsapp/wails/v2/internal/ffenestri"
+ "github.com/wailsapp/wails/v2/internal/logger"
+ "github.com/wailsapp/wails/v2/internal/messagedispatcher"
+ "github.com/wailsapp/wails/v2/internal/servicebus"
+ "github.com/wailsapp/wails/v2/internal/subsystem"
+ "github.com/wailsapp/wails/v2/internal/webserver"
+)
+
+// Config defines the Application's configuration
+type Config struct {
+ Title string // Title is the value to be displayed in the title bar
+ Width int // Width is the desired window width
+ Height int // Height is the desired window height
+ DevTools bool // DevTools enables or disables the browser development tools
+ Resizable bool // Resizable when False prevents window resizing
+ ServerEnabled bool // ServerEnabled when True allows remote connections
+}
+
+// App defines a Wails application structure
+type App struct {
+ config Config
+ window *ffenestri.Application
+ webserver *webserver.WebServer
+ binding *subsystem.Binding
+ call *subsystem.Call
+ event *subsystem.Event
+ log *subsystem.Log
+ runtime *subsystem.Runtime
+
+ bindings *binding.Bindings
+ logger *logger.Logger
+ dispatcher *messagedispatcher.Dispatcher
+ servicebus *servicebus.ServiceBus
+
+ debug bool
+}
+
+// Create App
+func CreateApp(options *Options) *App {
+
+ // Merge default options
+ options.mergeDefaults()
+
+ // Set up logger
+ myLogger := logger.New(os.Stdout)
+ myLogger.SetLogLevel(logger.INFO)
+
+ window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
+ Title: options.Title,
+ Width: options.Width,
+ Height: options.Height,
+ MinWidth: options.MinWidth,
+ MinHeight: options.MinHeight,
+ MaxWidth: options.MaxWidth,
+ MaxHeight: options.MaxHeight,
+ StartHidden: options.StartHidden,
+ DevTools: options.DevTools,
+
+ Resizable: !options.DisableResize,
+ Fullscreen: options.Fullscreen,
+ }, myLogger)
+
+ app := &App{
+ window: window,
+ webserver: webserver.NewWebServer(myLogger),
+ servicebus: servicebus.New(myLogger),
+ logger: myLogger,
+ bindings: binding.NewBindings(myLogger),
+ }
+
+ // Initialise the app
+ app.Init()
+
+ return app
+}
+
+// Run the application
+func (a *App) Run() error {
+
+ // Default app options
+ var port = 8080
+ var ip = "localhost"
+ var suppressLogging = false
+
+ // Create CLI
+ cli := clir.NewCli(filepath.Base(os.Args[0]), "Desktop/Server Build", "")
+
+ // Setup flags
+ cli.IntFlag("p", "Port to serve on", &port)
+ cli.StringFlag("i", "IP to serve on", &ip)
+ cli.BoolFlag("q", "Suppress logging", &suppressLogging)
+
+ // Setup main action
+ cli.Action(func() error {
+
+ // Set IP + Port
+ a.webserver.SetPort(port)
+ a.webserver.SetIP(ip)
+ a.webserver.SetBindings(a.bindings)
+ // Log information (if we aren't suppressing it)
+ if !suppressLogging {
+ cli.PrintBanner()
+ a.logger.Info("Running server at %s", a.webserver.URL())
+ }
+
+ a.servicebus.Start()
+ log, err := subsystem.NewLog(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.log = log
+ a.log.Start()
+ dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.dispatcher = dispatcher
+ a.dispatcher.Start()
+
+ // Start the runtime
+ runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.runtime = runtime
+ a.runtime.Start()
+
+ // Start the binding subsystem
+ binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
+ if err != nil {
+ return err
+ }
+ a.binding = binding
+ a.binding.Start()
+
+ // Start the eventing subsystem
+ event, err := subsystem.NewEvent(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.event = event
+ a.event.Start()
+
+ // Start the call subsystem
+ call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
+ if err != nil {
+ return err
+ }
+ a.call = call
+ a.call.Start()
+
+ // Required so that the WailsInit functions are fired!
+ runtime.GoRuntime().Events.Emit("wails:loaded")
+
+ // Set IP + Port
+ a.webserver.SetPort(port)
+ a.webserver.SetIP(ip)
+
+ // Log information (if we aren't suppressing it)
+ if !suppressLogging {
+ cli.PrintBanner()
+ println("Running server at " + a.webserver.URL())
+ }
+
+ // Dump bindings as a debug
+ bindingDump, err := a.bindings.ToJSON()
+ if err != nil {
+ return err
+ }
+
+ go func() {
+ if err := a.webserver.Start(dispatcher, event); err != nil {
+ a.logger.Error("Webserver failed to start %s", err)
+ }
+ }()
+
+ result := a.window.Run(dispatcher, bindingDump)
+ a.servicebus.Stop()
+
+ return result
+ })
+
+ return cli.Run()
+}
+
+// Bind a struct to the application by passing in
+// a pointer to it
+func (a *App) Bind(structPtr interface{}) {
+
+ // Add the struct to the bindings
+ err := a.bindings.Add(structPtr)
+ if err != nil {
+ a.logger.Fatal("Error during binding: " + err.Error())
+ }
+}
diff --git a/v2/internal/app/production.go b/v2/internal/app/production.go
new file mode 100644
index 000000000..ce083cb68
--- /dev/null
+++ b/v2/internal/app/production.go
@@ -0,0 +1,11 @@
+// +build !debug
+
+package app
+
+import "github.com/wailsapp/wails/v2/pkg/logger"
+
+// Init initialises the application for a production environment
+func (a *App) Init() error {
+ a.logger.SetLogLevel(logger.ERROR)
+ return nil
+}
diff --git a/v2/internal/app/server.go b/v2/internal/app/server.go
new file mode 100644
index 000000000..0530de58f
--- /dev/null
+++ b/v2/internal/app/server.go
@@ -0,0 +1,160 @@
+// +build server,!desktop
+
+package app
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/leaanthony/clir"
+ "github.com/wailsapp/wails/v2/internal/binding"
+ "github.com/wailsapp/wails/v2/internal/logger"
+ "github.com/wailsapp/wails/v2/internal/messagedispatcher"
+ "github.com/wailsapp/wails/v2/internal/servicebus"
+ "github.com/wailsapp/wails/v2/internal/subsystem"
+ "github.com/wailsapp/wails/v2/internal/webserver"
+)
+
+// App defines a Wails application structure
+type App struct {
+ binding *subsystem.Binding
+ call *subsystem.Call
+ event *subsystem.Event
+ log *subsystem.Log
+ runtime *subsystem.Runtime
+
+ bindings *binding.Bindings
+ logger *logger.Logger
+ dispatcher *messagedispatcher.Dispatcher
+ servicebus *servicebus.ServiceBus
+ webserver *webserver.WebServer
+
+ debug bool
+}
+
+// Create App
+func CreateApp(options *Options) *App {
+ options.mergeDefaults()
+ // We ignore the inputs (for now)
+
+ // TODO: Allow logger output override on CLI
+ myLogger := logger.New(os.Stdout)
+ myLogger.SetLogLevel(logger.TRACE)
+
+ result := &App{
+ bindings: binding.NewBindings(myLogger),
+ logger: myLogger,
+ servicebus: servicebus.New(myLogger),
+ webserver: webserver.NewWebServer(myLogger),
+ }
+
+ // Initialise app
+ result.Init()
+
+ return result
+}
+
+// Run the application
+func (a *App) Run() error {
+
+ // Default app options
+ var port = 8080
+ var ip = "localhost"
+ var supressLogging = false
+ var debugMode = false
+
+ // Create CLI
+ cli := clir.NewCli(filepath.Base(os.Args[0]), "Server Build", "")
+
+ // Setup flags
+ cli.IntFlag("p", "Port to serve on", &port)
+ cli.StringFlag("i", "IP to serve on", &ip)
+ cli.BoolFlag("d", "Debug mode", &debugMode)
+ cli.BoolFlag("q", "Supress logging", &supressLogging)
+
+ // Setup main action
+ cli.Action(func() error {
+
+ // Set IP + Port
+ a.webserver.SetPort(port)
+ a.webserver.SetIP(ip)
+ a.webserver.SetBindings(a.bindings)
+ // Log information (if we aren't supressing it)
+ if !supressLogging {
+ cli.PrintBanner()
+ a.logger.Info("Running server at %s", a.webserver.URL())
+ }
+
+ if debugMode {
+ a.servicebus.Debug()
+ }
+ a.servicebus.Start()
+ log, err := subsystem.NewLog(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.log = log
+ a.log.Start()
+ dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.dispatcher = dispatcher
+ a.dispatcher.Start()
+
+ // Start the runtime
+ runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.runtime = runtime
+ a.runtime.Start()
+
+ // Start the binding subsystem
+ binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
+ if err != nil {
+ return err
+ }
+ a.binding = binding
+ a.binding.Start()
+
+ // Start the eventing subsystem
+ event, err := subsystem.NewEvent(a.servicebus, a.logger)
+ if err != nil {
+ return err
+ }
+ a.event = event
+ a.event.Start()
+
+ // Start the call subsystem
+ call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
+ if err != nil {
+ return err
+ }
+ a.call = call
+ a.call.Start()
+
+ // Required so that the WailsInit functions are fired!
+ runtime.GoRuntime().Events.Emit("wails:loaded")
+
+ if err := a.webserver.Start(dispatcher, event); err != nil {
+ a.logger.Error("Webserver failed to start %s", err)
+ return err
+ }
+
+ return nil
+ })
+
+ return cli.Run()
+}
+
+// Bind a struct to the application by passing in
+// a pointer to it
+func (a *App) Bind(structPtr interface{}) {
+
+ // Add the struct to the bindings
+ err := a.bindings.Add(structPtr)
+ if err != nil {
+ a.logger.Fatal("Error during binding: " + err.Error())
+ }
+}
diff --git a/v2/internal/assetdb/assetdb.go b/v2/internal/assetdb/assetdb.go
new file mode 100644
index 000000000..54892952e
--- /dev/null
+++ b/v2/internal/assetdb/assetdb.go
@@ -0,0 +1,112 @@
+package assetdb
+
+import (
+ "fmt"
+ "strings"
+ "unsafe"
+)
+
+// AssetDB is a database for assets encoded as byte slices
+type AssetDB struct {
+ db map[string][]byte
+}
+
+// NewAssetDB creates a new AssetDB and initialises a blank db
+func NewAssetDB() *AssetDB {
+ return &AssetDB{
+ db: make(map[string][]byte),
+ }
+}
+
+// AddAsset saves the given byte slice under the given name
+func (a *AssetDB) AddAsset(name string, data []byte) {
+ a.db[name] = data
+}
+
+// Remove removes the named asset
+func (a *AssetDB) Remove(name string) {
+ delete(a.db, name)
+}
+
+// Asset retrieves the byte slice for the given name
+func (a *AssetDB) Read(name string) ([]byte, error) {
+ result := a.db[name]
+ if result == nil {
+ return nil, fmt.Errorf("asset '%s' not found", name)
+ }
+ return result, nil
+}
+
+// AssetAsString returns the asset as a string.
+// It also returns a boolean indicating whether the asset existed or not.
+func (a *AssetDB) String(name string) (string, error) {
+ asset, err := a.Read(name)
+ if err != nil {
+ return "", err
+ }
+ return *(*string)(unsafe.Pointer(&asset)), nil
+}
+
+func (a *AssetDB) Dump() {
+ fmt.Printf("Assets:\n")
+ for k, _ := range a.db {
+ fmt.Println(k)
+ }
+}
+
+// Serialize converts the entire database to a file that when compiled will
+// reconstruct the AssetDB during init()
+// name: name of the asset.AssetDB instance
+// pkg: package name placed at the top of the file
+func (a *AssetDB) Serialize(name, pkg string) string {
+ var cdata strings.Builder
+ // Set buffer size to 4k
+ cdata.Grow(4096)
+
+ // Write header
+ header := `// DO NOT EDIT - Generated automatically
+package %s
+
+import "github.com/wailsapp/wails/v2/internal/assetdb"
+
+var (
+ %s *assetdb.AssetDB = assetdb.NewAssetDB()
+)
+
+// AssetsDB is a clean interface to the assetdb.AssetDB struct
+type AssetsDB interface {
+ Read(string) ([]byte, error)
+ String(string) (string, error)
+}
+
+// Assets returns the asset database
+func Assets() AssetsDB {
+ return %s
+}
+
+func init() {
+`
+ cdata.WriteString(fmt.Sprintf(header, pkg, name, name))
+
+ for aname, bytes := range a.db {
+ cdata.WriteString(fmt.Sprintf("\t%s.AddAsset(\"%s\", []byte{",
+ name,
+ aname))
+
+ l := len(bytes)
+ if l == 0 {
+ cdata.WriteString("0x00})\n")
+ continue
+ }
+
+ // Convert each byte to hex
+ for _, b := range bytes[:l-1] {
+ cdata.WriteString(fmt.Sprintf("0x%x, ", b))
+ }
+ cdata.WriteString(fmt.Sprintf("0x%x})\n", bytes[l-1]))
+ }
+
+ cdata.WriteString(`}`)
+
+ return cdata.String()
+}
diff --git a/v2/internal/assetdb/assetdb_test.go b/v2/internal/assetdb/assetdb_test.go
new file mode 100644
index 000000000..b3f34b9fd
--- /dev/null
+++ b/v2/internal/assetdb/assetdb_test.go
@@ -0,0 +1,70 @@
+package assetdb
+
+import "testing"
+import "github.com/matryer/is"
+
+func TestExistsAsBytes(t *testing.T) {
+
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("hello", helloworld)
+
+ result, err := db.Read("hello")
+
+ is.True(err == nil)
+ is.Equal(result, helloworld)
+}
+
+func TestNotExistsAsBytes(t *testing.T) {
+
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("hello4", helloworld)
+
+ result, err := db.Read("hello")
+
+ is.True(err != nil)
+ is.True(result == nil)
+}
+
+func TestExistsAsString(t *testing.T) {
+
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("hello", helloworld)
+
+ result, err := db.String("hello")
+
+ // Ensure it exists
+ is.True(err == nil)
+
+ // Ensure the string is the same as the byte slice
+ is.Equal(result, "Hello, World!")
+}
+
+func TestNotExistsAsString(t *testing.T) {
+
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("hello", helloworld)
+
+ result, err := db.String("help")
+
+ // Ensure it doesn't exist
+ is.True(err != nil)
+
+ // Ensure the string is blank
+ is.Equal(result, "")
+}
diff --git a/v2/internal/assetdb/filesystem.go b/v2/internal/assetdb/filesystem.go
new file mode 100644
index 000000000..48acb713f
--- /dev/null
+++ b/v2/internal/assetdb/filesystem.go
@@ -0,0 +1,176 @@
+// +build !desktop
+package assetdb
+
+import (
+ "errors"
+ "io"
+ "net/http"
+ "os"
+ "path"
+ "sort"
+ "strings"
+ "time"
+)
+
+var errWhence = errors.New("Seek: invalid whence")
+var errOffset = errors.New("Seek: invalid offset")
+
+// Open implements the http.FileSystem interface for the AssetDB
+func (a *AssetDB) Open(name string) (http.File, error) {
+ if name == "/" || name == "" {
+ return &Entry{name: "/", dir: true, db: a}, nil
+ }
+
+ if data, ok := a.db[name]; ok {
+ return &Entry{name: name, data: data, size: len(data)}, nil
+ } else {
+ for n, _ := range a.db {
+ if strings.HasPrefix(n, name+"/") {
+ return &Entry{name: name, db: a, dir: true}, nil
+ }
+ }
+ }
+ return &Entry{}, os.ErrNotExist
+}
+
+// readdir returns the directory entries for the requested directory
+func (a *AssetDB) readdir(name string) ([]os.FileInfo, error) {
+ dir := name
+ var ents []string
+
+ fim := make(map[string]os.FileInfo)
+ for fn, data := range a.db {
+ if strings.HasPrefix(fn, dir) {
+ pieces := strings.Split(fn[len(dir)+1:], "/")
+ if len(pieces) == 1 {
+ fim[pieces[0]] = FI{name: pieces[0], dir: false, size: len(data)}
+ ents = append(ents, pieces[0])
+ } else {
+ fim[pieces[0]] = FI{name: pieces[0], dir: true, size: -1}
+ ents = append(ents, pieces[0])
+ }
+ }
+ }
+
+ if len(ents) == 0 {
+ return nil, os.ErrNotExist
+ }
+
+ sort.Strings(ents)
+ var list []os.FileInfo
+ for _, dir := range ents {
+ list = append(list, fim[dir])
+ }
+ return list, nil
+}
+
+// Entry implements the http.File interface to allow for
+// use in the http.FileSystem implementation of AssetDB
+type Entry struct {
+ name string
+ data []byte
+ dir bool
+ size int
+ db *AssetDB
+ off int
+}
+
+// Close is a noop
+func (e Entry) Close() error {
+ return nil
+}
+
+// Read fills the slice provided returning how many bytes were written
+// and any errors encountered
+func (e *Entry) Read(p []byte) (n int, err error) {
+ if e.off >= e.size {
+ return 0, io.EOF
+ }
+ numout := len(p)
+ if numout > e.size {
+ numout = e.size
+ }
+ for i := 0; i < numout; i++ {
+ p[i] = e.data[e.off+i]
+ }
+ e.off += numout
+ n = int(numout)
+ err = nil
+ return
+}
+
+// Seek seeks to the specified offset from the location specified by whence
+func (e *Entry) Seek(offset int64, whence int) (int64, error) {
+ switch whence {
+ default:
+ return 0, errWhence
+ case io.SeekStart:
+ offset += 0
+ case io.SeekCurrent:
+ offset += int64(e.off)
+ case io.SeekEnd:
+ offset += int64(e.size)
+ }
+
+ if offset < 0 {
+ return 0, errOffset
+ }
+ e.off = int(offset)
+ return offset, nil
+}
+
+// Readdir returns the directory entries inside this entry if it is a directory
+func (e Entry) Readdir(count int) ([]os.FileInfo, error) {
+ ents := []os.FileInfo{}
+ if !e.dir {
+ return ents, errors.New("Not a directory")
+ }
+ return e.db.readdir(e.name)
+}
+
+// Stat returns information about this directory entry
+func (e Entry) Stat() (os.FileInfo, error) {
+ return FI{e.name, e.size, e.dir}, nil
+}
+
+// FI is the AssetDB implementation of os.FileInfo.
+type FI struct {
+ name string
+ size int
+ dir bool
+}
+
+// IsDir returns true if this is a directory
+func (fi FI) IsDir() bool {
+ return fi.dir
+}
+
+// ModTime always returns now
+func (fi FI) ModTime() time.Time {
+ return time.Time{}
+}
+
+// Mode returns the file as readonly and directories
+// as world writeable and executable
+func (fi FI) Mode() os.FileMode {
+ if fi.IsDir() {
+ return 0755 | os.ModeDir
+ }
+ return 0444
+}
+
+// Name returns the name of this object without
+// any leading slashes
+func (fi FI) Name() string {
+ return path.Base(fi.name)
+}
+
+// Size returns the size of this item
+func (fi FI) Size() int64 {
+ return int64(fi.size)
+}
+
+// Sys returns nil
+func (fi FI) Sys() interface{} {
+ return nil
+}
diff --git a/v2/internal/assetdb/filesystem_test.go b/v2/internal/assetdb/filesystem_test.go
new file mode 100644
index 000000000..1c2ed94a4
--- /dev/null
+++ b/v2/internal/assetdb/filesystem_test.go
@@ -0,0 +1,108 @@
+package assetdb
+
+import (
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/matryer/is"
+)
+
+func TestOpenLeadingSlash(t *testing.T) {
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("/hello", helloworld)
+
+ file, err := db.Open("/hello")
+ // Ensure it does exist
+ is.True(err == nil)
+
+ buff := make([]byte, len(helloworld))
+ n, err := file.Read(buff)
+ fmt.Printf("Error %v\n", err)
+ is.True(err == nil)
+ is.Equal(n, len(helloworld))
+ result := string(buff)
+
+ // Ensure the string is blank
+ is.Equal(result, string(helloworld))
+}
+
+func TestOpen(t *testing.T) {
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("/hello", helloworld)
+
+ file, err := db.Open("/hello")
+
+ // Ensure it does exist
+ is.True(err == nil)
+
+ buff := make([]byte, len(helloworld))
+ n, err := file.Read(buff)
+ is.True(err == nil)
+ is.Equal(n, len(helloworld))
+ result := string(buff)
+
+ // Ensure the string is blank
+ is.Equal(result, string(helloworld))
+}
+
+func TestReaddir(t *testing.T) {
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("/hello", helloworld)
+ db.AddAsset("/directory/hello", helloworld)
+ db.AddAsset("/directory/subdirectory/hello", helloworld)
+
+ dir, err := db.Open("/doesntexist")
+ is.True(err == os.ErrNotExist)
+ ents, err := dir.Readdir(-1)
+ is.Equal([]os.FileInfo{}, ents)
+
+ dir, err = db.Open("/")
+ is.True(dir != nil)
+ is.True(err == nil)
+ ents, err = dir.Readdir(-1)
+ is.True(err == nil)
+ is.Equal(3, len(ents))
+}
+
+func TestReaddirSubdirectory(t *testing.T) {
+ is := is.New(t)
+
+ var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
+
+ db := NewAssetDB()
+ db.AddAsset("/hello", helloworld)
+ db.AddAsset("/directory/hello", helloworld)
+ db.AddAsset("/directory/subdirectory/hello", helloworld)
+
+ expected := []os.FileInfo{
+ FI{name: "hello", dir: false, size: len(helloworld)},
+ FI{name: "subdirectory", dir: true, size: -1},
+ }
+
+ dir, err := db.Open("/directory")
+ is.True(dir != nil)
+ is.True(err == nil)
+ ents, err := dir.Readdir(-1)
+ is.Equal(expected, ents)
+
+ // Check sub-subdirectory
+ dir, err = db.Open("/directory/subdirectory")
+ is.True(dir != nil)
+ is.True(err == nil)
+ ents, err = dir.Readdir(-1)
+ is.True(err == nil)
+ is.Equal([]os.FileInfo{FI{name: "hello", size: len(helloworld)}}, ents)
+}
diff --git a/v2/internal/bind/bind.go b/v2/internal/bind/bind.go
new file mode 100644
index 000000000..156fd4ce4
--- /dev/null
+++ b/v2/internal/bind/bind.go
@@ -0,0 +1,9 @@
+package bind
+
+func IsStructPointer(value interface{}) bool {
+ switch t := value.(type) {
+ default:
+ println(t)
+ }
+ return false
+}
diff --git a/v2/internal/binding/binding.go b/v2/internal/binding/binding.go
old mode 100644
new mode 100755
index b7bf07ae0..ca063c5ca
--- a/v2/internal/binding/binding.go
+++ b/v2/internal/binding/binding.go
@@ -1,80 +1,62 @@
package binding
import (
- "bufio"
- "bytes"
"fmt"
- "os"
- "path/filepath"
- "reflect"
- "runtime"
- "sort"
"strings"
- "github.com/wailsapp/wails/v2/internal/typescriptify"
-
- "github.com/leaanthony/slicer"
-
"github.com/wailsapp/wails/v2/internal/logger"
)
type Bindings struct {
- db *DB
- logger logger.CustomLogger
- exemptions slicer.StringSlicer
-
- structsToGenerateTS map[string]map[string]interface{}
- enumsToGenerateTS map[string]map[string]interface{}
- tsPrefix string
- tsSuffix string
- tsInterface bool
- obfuscate bool
+ db *DB
+ logger logger.CustomLogger
}
// NewBindings returns a new Bindings object
-func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}, obfuscate bool, enumsToBind []interface{}) *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,
+func NewBindings(logger *logger.Logger) *Bindings {
+ return &Bindings{
+ db: newDB(),
+ logger: logger.CustomLogger("Bindings"),
}
-
- for _, exemption := range exemptions {
- if exemption == nil {
- continue
- }
- name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
- // Yuk yuk yuk! Is there a better way?
- name = strings.TrimSuffix(name, "-fm")
- result.exemptions.Add(name)
- }
-
- for _, enum := range enumsToBind {
- result.AddEnumToGenerateTS(enum)
- }
-
- // Add the structs to bind
- for _, ptr := range structPointersToBind {
- err := result.Add(ptr)
- if err != nil {
- logger.Fatal("Error during binding: " + err.Error())
- }
- }
-
- return result
}
// Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error {
- methods, err := b.getMethods(structPtr)
+
+ methods, err := 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]
+
+ // Is this WailsInit?
+ if method.IsWailsInit() {
+ err := b.db.AddWailsInit(method)
+ if err != nil {
+ return err
+ }
+ b.logger.Trace("Registered WailsInit method: %s", method.Name)
+ continue
+ }
+
+ // Is this WailsShutdown?
+ if method.IsWailsShutdown() {
+ err := b.db.AddWailsShutdown(method)
+ if err != nil {
+ return err
+ }
+ b.logger.Trace("Registered WailsShutdown method: %s", method.Name)
+ continue
+ }
+
+ // Add it as a regular method
+ b.db.AddMethod(packageName, structName, methodName, method)
+
}
return nil
}
@@ -86,299 +68,3 @@ func (b *Bindings) DB() *DB {
func (b *Bindings) ToJSON() (string, error) {
return b.db.ToJSON()
}
-
-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 {
- structNames = append(structNames, structName)
- }
- sort.Strings(structNames)
- for _, structName := range structNames {
- fqstructname := packageName + "." + structName
- if seen.Contains(fqstructname) {
- continue
- }
- 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
- }
- thisPackageCode += str
- seen.AddSlice(w.GetGeneratedStructs())
- 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))
- for packageName := range models {
- sortedPackageNames = append(sortedPackageNames, packageName)
- }
- sort.Strings(sortedPackageNames)
-
- var modelsData bytes.Buffer
- for _, packageName := range sortedPackageNames {
- modelData := models[packageName]
- if strings.TrimSpace(modelData) == "" {
- continue
- }
- modelsData.WriteString("export namespace " + packageName + " {\n")
- sc := bufio.NewScanner(strings.NewReader(modelData))
- for sc.Scan() {
- modelsData.WriteString("\t" + sc.Text() + "\n")
- }
- modelsData.WriteString("\n}\n\n")
- }
- return modelsData.Bytes(), nil
-}
-
-func (b *Bindings) WriteModels(modelsDir string) error {
- modelsData, err := b.GenerateModels()
- if err != nil {
- return err
- }
- // Don't write if we don't have anything
- if len(modelsData) == 0 {
- return nil
- }
-
- filename := filepath.Join(modelsDir, "models.ts")
- err = os.WriteFile(filename, modelsData, 0o755)
- if err != nil {
- return err
- }
-
- 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{})
- }
- if b.structsToGenerateTS[packageName][structName] != nil {
- return
- }
- b.structsToGenerateTS[packageName][structName] = s
-
- // Iterate this struct and add any struct field references
- structType := reflect.TypeOf(s)
- for hasElements(structType) {
- structType = structType.Elem()
- }
-
- for i := 0; i < structType.NumField(); i++ {
- field := structType.Field(i)
- if field.Anonymous || !field.IsExported() {
- continue
- }
- kind := field.Type.Kind()
- if kind == reflect.Struct {
- fqname := field.Type.String()
- sNameSplit := strings.SplitN(fqname, ".", 2)
- if len(sNameSplit) < 2 {
- continue
- }
- sName := sNameSplit[1]
- pName := getPackageName(fqname)
- a := reflect.New(field.Type)
- if b.hasExportedJSONFields(field.Type) {
- s := reflect.Indirect(a).Interface()
- b.AddStructToGenerateTS(pName, sName, s)
- }
- } else {
- fType := field.Type
- for hasElements(fType) {
- fType = fType.Elem()
- }
- 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)
- }
- }
- }
- }
-}
-
-func (b *Bindings) SetTsPrefix(prefix string) *Bindings {
- b.tsPrefix = prefix
- return b
-}
-
-func (b *Bindings) SetTsSuffix(postfix string) *Bindings {
- b.tsSuffix = postfix
- 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 {
- for structName := range structsToGenerate {
- result.Add(packageName + "." + structName)
- }
- }
- 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
- }
- if len(jsonTag) == 0 {
- continue
- }
- jsonTagParts := strings.Split(jsonTag, ",")
- if len(jsonTagParts) > 0 {
- jsonFieldName = jsonTagParts[0]
- }
- for _, t := range jsonTagParts {
- if t == "-" {
- continue
- }
- }
- if jsonFieldName != "" {
- return true
- }
- }
- return false
-}
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
deleted file mode 100644
index 29777481b..000000000
--- a/v2/internal/binding/binding_test/binding_anonymous_sub_struct_multi_level_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package binding_test
-
-type StructWithAnonymousSubMultiLevelStruct struct {
- Name string `json:"name"`
- Meta struct {
- Age int `json:"age"`
- More struct {
- Info string `json:"info"`
- MoreInMore struct {
- Demo string `json:"demo"`
- } `json:"more_in_more"`
- } `json:"more"`
- } `json:"meta"`
-}
-
-func (s StructWithAnonymousSubMultiLevelStruct) Get() StructWithAnonymousSubMultiLevelStruct {
- return s
-}
-
-var AnonymousSubStructMultiLevelTest = BindingTest{
- name: "StructWithAnonymousSubMultiLevelStruct",
- structs: []interface{}{
- &StructWithAnonymousSubMultiLevelStruct{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class StructWithAnonymousSubMultiLevelStruct {
- name: string;
- // Go type: struct { Age int "json:\"age\""; More struct { Info string "json:\"info\""; MoreInMore struct { Demo string "json:\"demo\"" } "json:\"more_in_more\"" } "json:\"more\"" }
- meta: any;
-
- static createFrom(source: any = {}) {
- return new StructWithAnonymousSubMultiLevelStruct(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.meta = this.convertValues(source["meta"], Object);
- }
-
- 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_anonymous_sub_struct_test.go b/v2/internal/binding/binding_test/binding_anonymous_sub_struct_test.go
deleted file mode 100644
index 11afe4f0d..000000000
--- a/v2/internal/binding/binding_test/binding_anonymous_sub_struct_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package binding_test
-
-type StructWithAnonymousSubStruct struct {
- Name string `json:"name"`
- Meta struct {
- Age int `json:"age"`
- } `json:"meta"`
-}
-
-func (s StructWithAnonymousSubStruct) Get() StructWithAnonymousSubStruct {
- return s
-}
-
-var AnonymousSubStructTest = BindingTest{
- name: "StructWithAnonymousSubStruct",
- structs: []interface{}{
- &StructWithAnonymousSubStruct{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class StructWithAnonymousSubStruct {
- name: string;
- // Go type: struct { Age int "json:\"age\"" }
- meta: any;
-
- static createFrom(source: any = {}) {
- return new StructWithAnonymousSubStruct(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.meta = this.convertValues(source["meta"], Object);
- }
-
- 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_conflicting_package_name_test.go b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go
deleted file mode 100644
index b37334ec3..000000000
--- a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package binding_test
-
-import (
- "io/fs"
- "os"
- "testing"
-
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/float_package"
- "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/int_package"
- "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/map_package"
- "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/uint_package"
- "github.com/wailsapp/wails/v2/internal/logger"
-)
-
-const expectedBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {float_package} from '../models';
-import {int_package} from '../models';
-import {map_package} from '../models';
-import {uint_package} from '../models';
-
-export function StartingWithFloat(arg1:float_package.SomeStruct):Promise;
-
-export function StartingWithInt(arg1:int_package.SomeStruct):Promise;
-
-export function StartingWithMap(arg1:map_package.SomeStruct):Promise;
-
-export function StartingWithUint(arg1:uint_package.SomeStruct):Promise;
-`
-
-type HandlerTest struct{}
-
-func (h *HandlerTest) StartingWithInt(_ int_package.SomeStruct) {}
-func (h *HandlerTest) StartingWithFloat(_ float_package.SomeStruct) {}
-func (h *HandlerTest) StartingWithUint(_ uint_package.SomeStruct) {}
-func (h *HandlerTest) StartingWithMap(_ map_package.SomeStruct) {}
-
-func TestConflictingPackageName(t *testing.T) {
- // given
- generationDir := t.TempDir()
-
- // setup
- testLogger := &logger.Logger{}
- b := binding.NewBindings(testLogger, []interface{}{&HandlerTest{}}, []interface{}{}, false, []interface{}{})
-
- // then
- err := b.GenerateGoBindings(generationDir)
- if err != nil {
- t.Fatalf("could not generate the Go bindings: %v", err)
- }
-
- // then
- rawGeneratedBindings, err := fs.ReadFile(os.DirFS(generationDir), "binding_test/HandlerTest.d.ts")
- if err != nil {
- t.Fatalf("could not read the generated bindings: %v", err)
- }
-
- // then
- generatedBindings := string(rawGeneratedBindings)
- if generatedBindings != expectedBindings {
- t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedBindings, generatedBindings)
- }
-}
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
deleted file mode 100644
index ffb85e865..000000000
--- a/v2/internal/binding/binding_test/binding_emptystruct_test.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package binding_test
-
-type EmptyStruct struct {
- Empty struct{} `json:"empty"`
-}
-
-func (s EmptyStruct) Get() EmptyStruct {
- return s
-}
-
-var EmptyStructTest = BindingTest{
- name: "EmptyStruct",
- structs: []interface{}{
- &EmptyStruct{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class EmptyStruct {
- // Go type: struct {}
-
- empty: any;
-
- static createFrom(source: any = {}) {
- return new EmptyStruct(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.empty = this.convertValues(source["empty"], Object);
- }
- 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_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_escapedname_test.go b/v2/internal/binding/binding_test/binding_escapedname_test.go
deleted file mode 100644
index 1bf6ce3ad..000000000
--- a/v2/internal/binding/binding_test/binding_escapedname_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package binding_test
-
-type EscapedName struct {
- Name string `json:"n.a.m.e"`
-}
-
-func (s EscapedName) Get() EscapedName {
- return s
-}
-
-var EscapedNameTest = BindingTest{
- name: "EscapedName",
- structs: []interface{}{
- &EscapedName{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class EscapedName {
- "n.a.m.e": string;
- static createFrom(source: any = {}) {
- return new EscapedName(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this["n.a.m.e"] = source["n.a.m.e"];
- }
- }
-}
-`,
-}
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
deleted file mode 100644
index 4a4b2996c..000000000
--- a/v2/internal/binding/binding_test/binding_importedmap_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package binding_test
-
-import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
-
-type ImportedMap struct {
- AMapWrapperContainer binding_test_import.AMapWrapper `json:"AMapWrapperContainer"`
-}
-
-func (s ImportedMap) Get() ImportedMap {
- return s
-}
-
-var ImportedMapTest = BindingTest{
- name: "ImportedMap",
- structs: []interface{}{
- &ImportedMap{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class ImportedMap {
- AMapWrapperContainer: binding_test_import.AMapWrapper;
- static createFrom(source: any = {}) {
- return new ImportedMap(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.AMapWrapperContainer = this.convertValues(source["AMapWrapperContainer"], binding_test_import.AMapWrapper);
- }
- 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 binding_test_import {
- export class AMapWrapper {
- AMap: Record;
- static createFrom(source: any = {}) {
- return new AMapWrapper(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.AMap = this.convertValues(source["AMap"], binding_test_nestedimport.A, 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;
- }
- }
-}
-
-export namespace binding_test_nestedimport {
- export class A {
- A: string;
- static createFrom(source: any = {}) {
- return new A(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.A = source["A"];
- }
- }
-}
-`,
-}
diff --git a/v2/internal/binding/binding_test/binding_importedslice_test.go b/v2/internal/binding/binding_test/binding_importedslice_test.go
deleted file mode 100644
index 5abf55b43..000000000
--- a/v2/internal/binding/binding_test/binding_importedslice_test.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package binding_test
-
-import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
-
-type ImportedSlice struct {
- ASliceWrapperContainer binding_test_import.ASliceWrapper `json:"ASliceWrapperContainer"`
-}
-
-func (s ImportedSlice) Get() ImportedSlice {
- return s
-}
-
-var ImportedSliceTest = BindingTest{
- name: "ImportedSlice",
- structs: []interface{}{
- &ImportedSlice{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class ImportedSlice {
- ASliceWrapperContainer: binding_test_import.ASliceWrapper;
- static createFrom(source: any = {}) {
- return new ImportedSlice(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.ASliceWrapperContainer = this.convertValues(source["ASliceWrapperContainer"], binding_test_import.ASliceWrapper);
- }
- 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 binding_test_import {
- export class ASliceWrapper {
- ASlice: binding_test_nestedimport.A[];
- static createFrom(source: any = {}) {
- return new ASliceWrapper(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.ASlice = this.convertValues(source["ASlice"], binding_test_nestedimport.A);
- }
- 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 binding_test_nestedimport {
- export class A {
- A: string;
- static createFrom(source: any = {}) {
- return new A(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.A = source["A"];
- }
- }
-}
-`,
-}
diff --git a/v2/internal/binding/binding_test/binding_importedstruct_test.go b/v2/internal/binding/binding_test/binding_importedstruct_test.go
deleted file mode 100644
index 1e94453c2..000000000
--- a/v2/internal/binding/binding_test/binding_importedstruct_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package binding_test
-
-import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
-
-type ImportedStruct struct {
- AWrapperContainer binding_test_import.AWrapper `json:"AWrapperContainer"`
-}
-
-func (s ImportedStruct) Get() ImportedStruct {
- return s
-}
-
-var ImportedStructTest = BindingTest{
- name: "ImportedStruct",
- structs: []interface{}{
- &ImportedStruct{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class ImportedStruct {
- AWrapperContainer: binding_test_import.AWrapper;
- static createFrom(source: any = {}) {
- return new ImportedStruct(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.AWrapperContainer = this.convertValues(source["AWrapperContainer"], binding_test_import.AWrapper);
- }
- 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 binding_test_import {
- export class AWrapper {
- AWrapper: binding_test_nestedimport.A;
- static createFrom(source: any = {}) {
- return new AWrapper(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.AWrapper = this.convertValues(source["AWrapper"], binding_test_nestedimport.A);
- }
- 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 binding_test_nestedimport {
- export class A {
- A: string;
- static createFrom(source: any = {}) {
- return new A(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.A = source["A"];
- }
- }
-}
-`,
-}
diff --git a/v2/internal/binding/binding_test/binding_multiplestructs_test.go b/v2/internal/binding/binding_test/binding_multiplestructs_test.go
deleted file mode 100644
index 144867813..000000000
--- a/v2/internal/binding/binding_test/binding_multiplestructs_test.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package binding_test
-
-type Multistruct1 struct {
- Name string `json:"name"`
-}
-
-func (s Multistruct1) Get() Multistruct1 {
- return s
-}
-
-type Multistruct2 struct {
- Name string `json:"name"`
-}
-
-func (s Multistruct2) Get() Multistruct2 {
- return s
-}
-
-type Multistruct3 struct {
- Name string `json:"name"`
-}
-
-func (s Multistruct3) Get() Multistruct3 {
- return s
-}
-
-type Multistruct4 struct {
- Name string `json:"name"`
-}
-
-func (s Multistruct4) Get() Multistruct4 {
- return s
-}
-
-var MultistructTest = BindingTest{
- name: "Multistruct",
- structs: []interface{}{
- &Multistruct1{},
- &Multistruct2{},
- &Multistruct3{},
- &Multistruct4{},
- },
- exemptions: nil,
- shouldError: false,
- want: `export namespace binding_test {
- export class Multistruct1 {
- name: string;
- static createFrom(source: any = {}) {
- return new Multistruct1(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
- export class Multistruct2 {
- name: string;
- static createFrom(source: any = {}) {
- return new Multistruct2(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
- export class Multistruct3 {
- name: string;
- static createFrom(source: any = {}) {
- return new Multistruct3(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
- export class Multistruct4 {
- name: string;
- static createFrom(source: any = {}) {
- return new Multistruct4(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
-}
-`,
-}
diff --git a/v2/internal/binding/binding_test/binding_nestedfield_test.go b/v2/internal/binding/binding_test/binding_nestedfield_test.go
deleted file mode 100644
index 66dd11cbf..000000000
--- a/v2/internal/binding/binding_test/binding_nestedfield_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package binding_test
-
-type As struct {
- B Bs `json:"b"`
-}
-
-type Bs struct {
- Name string `json:"name"`
-}
-
-func (a As) Get() As {
- return a
-}
-
-var NestedFieldTest = BindingTest{
- name: "NestedField",
- structs: []interface{}{
- &As{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class Bs {
- name: string;
- static createFrom(source: any = {}) {
- return new Bs(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
- export class As {
- b: Bs;
- static createFrom(source: any = {}) {
- return new As(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.b = this.convertValues(source["b"], Bs);
- }
- 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_nonstringmapkey_test.go b/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go
deleted file mode 100644
index 9efee710f..000000000
--- a/v2/internal/binding/binding_test/binding_nonstringmapkey_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package binding_test
-
-type NonStringMapKey struct {
- NumberMap map[uint]any `json:"numberMap"`
-}
-
-func (s NonStringMapKey) Get() NonStringMapKey {
- return s
-}
-
-var NonStringMapKeyTest = BindingTest{
- name: "NonStringMapKey",
- structs: []interface{}{
- &NonStringMapKey{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class NonStringMapKey {
- numberMap: Record;
- static createFrom(source: any = {}) {
- return new NonStringMapKey(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.numberMap = source["numberMap"];
- }
- }
-}
-`,
-}
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
deleted file mode 100644
index 94941d0a3..000000000
--- a/v2/internal/binding/binding_test/binding_returned_promises_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package binding_test
-
-import (
- "io/fs"
- "os"
- "testing"
-
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/logger"
-)
-
-const expectedPromiseBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {binding_test} from '../models';
-
-export function ErrorReturn(arg1:number):Promise;
-
-export function NoReturn(arg1:string):Promise;
-
-export function SingleReturn(arg1:any):Promise;
-
-export function SingleReturnStruct(arg1:any):Promise;
-
-export function SingleReturnStructPointer(arg1:any):Promise;
-
-export function SingleReturnStructPointerSlice(arg1:any):Promise>;
-
-export function SingleReturnStructSlice(arg1:any):Promise>;
-
-export function SingleReturnWithError(arg1:number):Promise;
-
-export function TwoReturn(arg1:any):Promise;
-`
-
-type PromisesTest struct{}
-type PromisesTestReturnStruct struct{}
-
-func (h *PromisesTest) NoReturn(_ string) {}
-func (h *PromisesTest) ErrorReturn(_ int) error { return nil }
-func (h *PromisesTest) SingleReturn(_ interface{}) int { return 0 }
-func (h *PromisesTest) SingleReturnStructPointer(_ interface{}) *PromisesTestReturnStruct {
- return &PromisesTestReturnStruct{}
-}
-func (h *PromisesTest) SingleReturnStruct(_ interface{}) PromisesTestReturnStruct {
- return PromisesTestReturnStruct{}
-}
-func (h *PromisesTest) SingleReturnStructSlice(_ interface{}) []PromisesTestReturnStruct {
- return []PromisesTestReturnStruct{}
-}
-func (h *PromisesTest) SingleReturnStructPointerSlice(_ interface{}) []*PromisesTestReturnStruct {
- return []*PromisesTestReturnStruct{}
-}
-func (h *PromisesTest) SingleReturnWithError(_ int) (string, error) { return "", nil }
-func (h *PromisesTest) TwoReturn(_ interface{}) (string, int) { return "", 0 }
-
-func TestPromises(t *testing.T) {
- // given
- generationDir := t.TempDir()
-
- // setup
- testLogger := &logger.Logger{}
- b := binding.NewBindings(testLogger, []interface{}{&PromisesTest{}}, []interface{}{}, false, []interface{}{})
-
- // then
- err := b.GenerateGoBindings(generationDir)
- if err != nil {
- t.Fatalf("could not generate the Go bindings: %v", err)
- }
-
- // then
- rawGeneratedBindings, err := fs.ReadFile(os.DirFS(generationDir), "binding_test/PromisesTest.d.ts")
- if err != nil {
- t.Fatalf("could not read the generated bindings: %v", err)
- }
-
- // then
- generatedBindings := string(rawGeneratedBindings)
- if generatedBindings != expectedPromiseBindings {
- t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedPromiseBindings, generatedBindings)
- }
-}
diff --git a/v2/internal/binding/binding_test/binding_singlefield_test.go b/v2/internal/binding/binding_test/binding_singlefield_test.go
deleted file mode 100644
index 2919ab29e..000000000
--- a/v2/internal/binding/binding_test/binding_singlefield_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package binding_test
-
-type SingleField struct {
- Name string `json:"name"`
-}
-
-func (s SingleField) Get() SingleField {
- return s
-}
-
-var SingleFieldTest = BindingTest{
- name: "SingleField",
- structs: []interface{}{
- &SingleField{},
- },
- exemptions: nil,
- shouldError: false,
- want: `
-export namespace binding_test {
- export class SingleField {
- name: string;
- static createFrom(source: any = {}) {
- return new SingleField(source);
- }
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
-}
-`,
-}
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
deleted file mode 100644
index 41f0618ce..000000000
--- a/v2/internal/binding/binding_test/binding_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package binding_test
-
-import (
- "reflect"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/require"
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/logger"
-)
-
-type BindingTest struct {
- name string
- structs []interface{}
- enums []interface{}
- exemptions []interface{}
- want string
- shouldError bool
- TsGenerationOptionsTest
-}
-
-type TsGenerationOptionsTest struct {
- TsPrefix string
- TsSuffix string
- TsOutputType string
-}
-
-func TestBindings_GenerateModels(t *testing.T) {
-
- tests := []BindingTest{
- EscapedNameTest,
- 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,
- }
-
- 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)
- 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)
- return
- }
- if !reflect.DeepEqual(strings.Fields(string(got)), strings.Fields(tt.want)) {
- t.Errorf("GenerateModels() got = %v, want %v", string(got), tt.want)
- }
- })
- }
-}
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
deleted file mode 100644
index e7080c694..000000000
--- a/v2/internal/binding/binding_test/binding_test_import/binding_test_import.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package binding_test_import
-
-import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/binding_test_nestedimport"
-
-type AWrapper struct {
- AWrapper binding_test_nestedimport.A `json:"AWrapper"`
-}
-
-type ASliceWrapper struct {
- ASlice []binding_test_nestedimport.A `json:"ASlice"`
-}
-
-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_test_import/binding_test_nestedimport/binding_nestedimport.go b/v2/internal/binding/binding_test/binding_test_import/binding_test_nestedimport/binding_nestedimport.go
deleted file mode 100644
index 31c70ad3f..000000000
--- a/v2/internal/binding/binding_test/binding_test_import/binding_test_nestedimport/binding_nestedimport.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package binding_test_nestedimport
-
-type A struct {
- A string `json:"A"`
-}
diff --git a/v2/internal/binding/binding_test/binding_test_import/float_package/type.go b/v2/internal/binding/binding_test/binding_test_import/float_package/type.go
deleted file mode 100644
index 66a20304b..000000000
--- a/v2/internal/binding/binding_test/binding_test_import/float_package/type.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package float_package
-
-type SomeStruct struct {
- Name string `json:"string"`
-}
diff --git a/v2/internal/binding/binding_test/binding_test_import/int_package/type.go b/v2/internal/binding/binding_test/binding_test_import/int_package/type.go
deleted file mode 100644
index bff29946a..000000000
--- a/v2/internal/binding/binding_test/binding_test_import/int_package/type.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package int_package
-
-type SomeStruct struct {
- Name string `json:"string"`
-}
diff --git a/v2/internal/binding/binding_test/binding_test_import/map_package/type.go b/v2/internal/binding/binding_test/binding_test_import/map_package/type.go
deleted file mode 100644
index 34303757c..000000000
--- a/v2/internal/binding/binding_test/binding_test_import/map_package/type.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package map_package
-
-type SomeStruct struct {
- Name string `json:"string"`
-}
diff --git a/v2/internal/binding/binding_test/binding_test_import/uint_package/type.go b/v2/internal/binding/binding_test/binding_test_import/uint_package/type.go
deleted file mode 100644
index dd55675f6..000000000
--- a/v2/internal/binding/binding_test/binding_test_import/uint_package/type.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package uint_package
-
-type SomeStruct struct {
- Name string `json:"string"`
-}
diff --git a/v2/internal/binding/binding_test/binding_tsgeneration_test.go b/v2/internal/binding/binding_test/binding_tsgeneration_test.go
deleted file mode 100644
index 850bc778a..000000000
--- a/v2/internal/binding/binding_test/binding_tsgeneration_test.go
+++ /dev/null
@@ -1,509 +0,0 @@
-package binding_test
-
-import (
- "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import"
-)
-
-type GeneratedJsEntity struct {
- Name string `json:"name"`
-}
-
-func (s GeneratedJsEntity) Get() GeneratedJsEntity {
- return s
-}
-
-var GeneratedJsEntityTest = BindingTest{
- name: "GeneratedJsEntityTest",
- structs: []interface{}{
- &GeneratedJsEntity{},
- },
- exemptions: nil,
- shouldError: false,
- TsGenerationOptionsTest: TsGenerationOptionsTest{
- TsPrefix: "MY_PREFIX_",
- TsSuffix: "_MY_SUFFIX",
- },
- want: `
-export namespace binding_test {
-
- export class MY_PREFIX_GeneratedJsEntity_MY_SUFFIX {
- name: string;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_GeneratedJsEntity_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- }
- }
-
-}
-
-`,
-}
-
-type ParentEntity struct {
- Name string `json:"name"`
- Ref ChildEntity `json:"ref"`
- ParentProp string `json:"parentProp"`
-}
-
-func (p ParentEntity) Get() ParentEntity {
- return p
-}
-
-type ChildEntity struct {
- Name string `json:"name"`
- ChildProp int `json:"childProp"`
-}
-
-var GeneratedJsEntityWithNestedStructTest = BindingTest{
- name: "GeneratedJsEntityWithNestedStructTest",
- structs: []interface{}{
- &ParentEntity{},
- },
- exemptions: nil,
- shouldError: false,
- TsGenerationOptionsTest: TsGenerationOptionsTest{
- TsPrefix: "MY_PREFIX_",
- TsSuffix: "_MY_SUFFIX",
- },
- want: `
-export namespace binding_test {
-
- export class MY_PREFIX_ChildEntity_MY_SUFFIX {
- name: string;
- childProp: number;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_ChildEntity_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.childProp = source["childProp"];
- }
- }
- export class MY_PREFIX_ParentEntity_MY_SUFFIX {
- name: string;
- ref: MY_PREFIX_ChildEntity_MY_SUFFIX;
- parentProp: string;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_ParentEntity_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.ref = this.convertValues(source["ref"], MY_PREFIX_ChildEntity_MY_SUFFIX);
- this.parentProp = source["parentProp"];
- }
-
- 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;
- }
- }
-
- }
-`,
-}
-
-type ParentPackageEntity struct {
- Name string `json:"name"`
- Ref ChildPackageEntity `json:"ref"`
-}
-
-func (p ParentPackageEntity) Get() ParentPackageEntity {
- return p
-}
-
-type ChildPackageEntity struct {
- Name string `json:"name"`
- ImportedPackage binding_test_import.AWrapper `json:"importedPackage"`
-}
-
-var EntityWithDiffNamespacesTest = BindingTest{
- name: "EntityWithDiffNamespaces ",
- structs: []interface{}{
- &ParentPackageEntity{},
- },
- exemptions: nil,
- shouldError: false,
- TsGenerationOptionsTest: TsGenerationOptionsTest{
- TsPrefix: "MY_PREFIX_",
- TsSuffix: "_MY_SUFFIX",
- },
- want: `
-export namespace binding_test {
-
- export class MY_PREFIX_ChildPackageEntity_MY_SUFFIX {
- name: string;
- importedPackage: binding_test_import.MY_PREFIX_AWrapper_MY_SUFFIX;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_ChildPackageEntity_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.importedPackage = this.convertValues(source["importedPackage"], binding_test_import.MY_PREFIX_AWrapper_MY_SUFFIX);
- }
-
- 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 MY_PREFIX_ParentPackageEntity_MY_SUFFIX {
- name: string;
- ref: MY_PREFIX_ChildPackageEntity_MY_SUFFIX;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_ParentPackageEntity_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.name = source["name"];
- this.ref = this.convertValues(source["ref"], MY_PREFIX_ChildPackageEntity_MY_SUFFIX);
- }
-
- 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 binding_test_import {
-
- export class MY_PREFIX_AWrapper_MY_SUFFIX {
- AWrapper: binding_test_nestedimport.MY_PREFIX_A_MY_SUFFIX;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_AWrapper_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.AWrapper = this.convertValues(source["AWrapper"], binding_test_nestedimport.MY_PREFIX_A_MY_SUFFIX);
- }
-
- 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 binding_test_nestedimport {
-
- export class MY_PREFIX_A_MY_SUFFIX {
- A: string;
-
- static createFrom(source: any = {}) {
- return new MY_PREFIX_A_MY_SUFFIX(source);
- }
-
- constructor(source: any = {}) {
- if ('string' === typeof source) source = JSON.parse(source);
- this.A = source["A"];
- }
- }
-
- }
-
-`,
-}
-
-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
deleted file mode 100644
index 90b009c5f..000000000
--- a/v2/internal/binding/binding_test/binding_type_alias_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package binding_test
-
-import (
- "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/int_package"
- "io/fs"
- "os"
- "testing"
-
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/logger"
-)
-
-const expectedTypeAliasBindings = `// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-import {binding_test} from '../models';
-import {int_package} from '../models';
-
-export function Map():Promise>;
-
-export function MapAlias():Promise;
-
-export function MapWithImportedStructValue():Promise>;
-
-export function Slice():Promise>;
-
-export function SliceImportedStruct():Promise>;
-`
-
-type AliasTest struct{}
-type MapAlias map[string]string
-
-func (h *AliasTest) Map() map[string]string { return nil }
-func (h *AliasTest) MapAlias() MapAlias { return nil }
-func (h *AliasTest) MapWithImportedStructValue() map[string]int_package.SomeStruct { return nil }
-func (h *AliasTest) Slice() []string { return nil }
-func (h *AliasTest) SliceImportedStruct() []int_package.SomeStruct { return nil }
-
-func TestAliases(t *testing.T) {
- // given
- generationDir := t.TempDir()
-
- // setup
- testLogger := &logger.Logger{}
- b := binding.NewBindings(testLogger, []interface{}{&AliasTest{}}, []interface{}{}, false, []interface{}{})
-
- // then
- err := b.GenerateGoBindings(generationDir)
- if err != nil {
- t.Fatalf("could not generate the Go bindings: %v", err)
- }
-
- // then
- rawGeneratedBindings, err := fs.ReadFile(os.DirFS(generationDir), "binding_test/AliasTest.d.ts")
- if err != nil {
- t.Fatalf("could not read the generated bindings: %v", err)
- }
-
- // then
- generatedBindings := string(rawGeneratedBindings)
- if generatedBindings != expectedTypeAliasBindings {
- t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedTypeAliasBindings,
- generatedBindings)
- }
-}
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..7807bcfda 100644
--- a/v2/internal/binding/boundMethod.go
+++ b/v2/internal/binding/boundMethod.go
@@ -4,26 +4,69 @@ import (
"encoding/json"
"fmt"
"reflect"
+ "strings"
)
-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:"-"`
+}
+
+// IsWailsInit returns true if the method name is "WailsInit"
+func (b *BoundMethod) IsWailsInit() bool {
+ return strings.HasSuffix(b.Name, "WailsInit")
+}
+
+// IsWailsShutdown returns true if the method name is "WailsShutdown"
+func (b *BoundMethod) IsWailsShutdown() bool {
+ return strings.HasSuffix(b.Name, "WailsShutdown")
+}
+
+// VerifyWailsInit checks if the WailsInit signature is correct
+func (b *BoundMethod) VerifyWailsInit() error {
+ // Must only have 1 input
+ if b.InputCount() != 1 {
+ return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
+ }
+
+ // Check input type
+ if !b.Inputs[0].IsType("*runtime.Runtime") {
+ return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
+ }
+
+ // Must only have 1 output
+ if b.OutputCount() != 1 {
+ return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
+ }
+
+ // Check output type
+ if !b.Outputs[0].IsError() {
+ return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
+ }
+
+ // Input must be of type Runtime
+ return nil
+}
+
+// VerifyWailsShutdown checks if the WailsShutdown signature is correct
+func (b *BoundMethod) VerifyWailsShutdown() error {
+ // Must have no inputs
+ if b.InputCount() != 0 {
+ return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
+ }
+
+ // Must have no outputs
+ if b.OutputCount() != 0 {
+ return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
+ }
+
+ // Input must be of type Runtime
+ return nil
}
// InputCount returns the number of inputs this bound method has
@@ -38,10 +81,8 @@ 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())
- }
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()
@@ -64,7 +105,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..e2bc6f357 100644
--- a/v2/internal/binding/db.go
+++ b/v2/internal/binding/db.go
@@ -15,29 +15,25 @@ type DB struct {
// It used for performance gains at runtime
methodMap map[string]*BoundMethod
- // This uses ids to reference bound methods at runtime
- obfuscatedMethodArray []*ObfuscatedMethod
+ // These are slices of methods registered using WailsInit and WailsShutdown
+ wailsInitMethods []*BoundMethod
+ wailsShutdownMethods []*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),
}
}
// 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 +52,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()
@@ -63,21 +60,11 @@ func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod {
return d.methodMap[qualifiedMethodName]
}
-// GetObfuscatedMethod returns the method for the given ID
-func (d *DB) GetObfuscatedMethod(id int) *BoundMethod {
- // Lock the db whilst processing and unlock on return
- d.lock.RLock()
- defer d.lock.RUnlock()
-
- if len(d.obfuscatedMethodArray) <= id {
- return nil
- }
-
- return d.obfuscatedMethodArray[id].method
-}
-
// 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) {
+
+ // TODO: Validate inputs?
+
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
@@ -104,31 +91,60 @@ 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})
+
+}
+
+// AddWailsInit checks the given method is a WailsInit method and if it
+// is, adds it to the list of WailsInit methods
+func (d *DB) AddWailsInit(method *BoundMethod) error {
+ err := method.VerifyWailsInit()
+ if err != nil {
+ return err
+ }
+
+ // Lock the db whilst processing and unlock on return
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ d.wailsInitMethods = append(d.wailsInitMethods, method)
+ return nil
+}
+
+// AddWailsShutdown checks the given method is a WailsInit method and if it
+// is, adds it to the list of WailsShutdown methods
+func (d *DB) AddWailsShutdown(method *BoundMethod) error {
+ err := method.VerifyWailsShutdown()
+ if err != nil {
+ return err
+ }
+
+ // Lock the db whilst processing and unlock on return
+ d.lock.Lock()
+ defer d.lock.Unlock()
+
+ d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
+ return nil
}
// 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()
- d.UpdateObfuscatedCallMap()
-
bytes, err := json.Marshal(&d.store)
// Return zero copy string as this string will be read only
- result := *(*string)(unsafe.Pointer(&bytes))
- return result, err
+ return *(*string)(unsafe.Pointer(&bytes)), err
}
-// 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
- }
-
- return mappings
+// WailsInitMethods returns the list of registered WailsInit methods
+func (d *DB) WailsInitMethods() []*BoundMethod {
+ return d.wailsInitMethods
+}
+
+// WailsShutdownMethods returns the list of registered WailsInit methods
+func (d *DB) WailsShutdownMethods() []*BoundMethod {
+ return d.wailsShutdownMethods
}
diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go
deleted file mode 100644
index 77edc983d..000000000
--- a/v2/internal/binding/generate.go
+++ /dev/null
@@ -1,248 +0,0 @@
-package binding
-
-import (
- "bytes"
- _ "embed"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
-
- "github.com/wailsapp/wails/v2/internal/fs"
-
- "github.com/leaanthony/slicer"
-)
-
-var (
- mapRegex *regexp.Regexp
- keyPackageIndex int
- keyTypeIndex int
- valueArrayIndex int
- valuePackageIndex int
- valueTypeIndex int
-)
-
-func init() {
- mapRegex = regexp.MustCompile(`(?:map\[(?:(?P\w+)\.)?(?P\w+)])?(?P\[])?(?:\*?(?P\w+)\.)?(?P.+)`)
- keyPackageIndex = mapRegex.SubexpIndex("keyPackage")
- keyTypeIndex = mapRegex.SubexpIndex("keyType")
- valueArrayIndex = mapRegex.SubexpIndex("valueArray")
- valuePackageIndex = mapRegex.SubexpIndex("valuePackage")
- valueTypeIndex = mapRegex.SubexpIndex("valueType")
-}
-
-func (b *Bindings) GenerateGoBindings(baseDir string) error {
- store := b.db.store
- var obfuscatedBindings map[string]int
- if b.obfuscate {
- obfuscatedBindings = b.db.UpdateObfuscatedCallMap()
- }
- for packageName, structs := range store {
- packageDir := filepath.Join(baseDir, packageName)
- err := fs.Mkdir(packageDir)
- if err != nil {
- return err
- }
- for structName, methods := range structs {
- var jsoutput bytes.Buffer
- jsoutput.WriteString(`// @ts-check
-// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-`)
- var tsBody bytes.Buffer
- var tsContent bytes.Buffer
- tsContent.WriteString(`// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
-// This file is automatically generated. DO NOT EDIT
-`)
- // Sort the method names alphabetically
- methodNames := make([]string, 0, len(methods))
- for methodName := range methods {
- methodNames = append(methodNames, methodName)
- }
- sort.Strings(methodNames)
-
- var importNamespaces slicer.StringSlicer
- for _, methodName := range methodNames {
- // Get the method details
- methodDetails := methods[methodName]
-
- // Generate JS
- var args slicer.StringSlicer
- for count := range methodDetails.Inputs {
- arg := fmt.Sprintf("arg%d", count+1)
- args.Add(arg)
- }
- argsString := args.Join(", ")
- jsoutput.WriteString(fmt.Sprintf("\nexport function %s(%s) {", methodName, argsString))
- jsoutput.WriteString("\n")
- if b.obfuscate {
- id := obfuscatedBindings[strings.Join([]string{packageName, structName, methodName}, ".")]
- jsoutput.WriteString(fmt.Sprintf(" return ObfuscatedCall(%d, [%s]);", id, argsString))
- } else {
- jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString))
- }
- jsoutput.WriteString("\n}\n")
-
- // Generate TS
- tsBody.WriteString(fmt.Sprintf("\nexport function %s(", methodName))
-
- args.Clear()
- for count, input := range methodDetails.Inputs {
- arg := fmt.Sprintf("arg%d", count+1)
- entityName := entityFullReturnType(input.TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
- args.Add(arg + ":" + goTypeToTypescriptType(entityName, &importNamespaces))
- }
- tsBody.WriteString(args.Join(",") + "):")
- // now build Typescript return types
- // If there is no return value or only returning error, TS returns Promise
- // If returning single value, TS returns Promise
- // If returning single value or error, TS returns Promise
- // If returning two values, TS returns Promise
- // Otherwise, TS returns Promise (instead of throwing Go error?)
- var returnType string
- if methodDetails.OutputCount() == 0 {
- returnType = "Promise"
- } else if methodDetails.OutputCount() == 1 && methodDetails.Outputs[0].TypeName == "error" {
- returnType = "Promise"
- } else {
- outputTypeName := entityFullReturnType(methodDetails.Outputs[0].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
- firstType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
- returnType = "Promise<" + firstType
- if methodDetails.OutputCount() == 2 && methodDetails.Outputs[1].TypeName != "error" {
- outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
- secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
- returnType += "|" + secondType
- }
- returnType += ">"
- }
- tsBody.WriteString(returnType + ";\n")
- }
-
- importNamespaces.Deduplicate()
- importNamespaces.Each(func(namespace string) {
- tsContent.WriteString("import {" + namespace + "} from '../models';\n")
- })
- tsContent.WriteString(tsBody.String())
-
- jsfilename := filepath.Join(packageDir, structName+".js")
- err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0o755)
- if err != nil {
- return err
- }
- tsfilename := filepath.Join(packageDir, structName+".d.ts")
- err = os.WriteFile(tsfilename, tsContent.Bytes(), 0o755)
- if err != nil {
- return err
- }
- }
- }
- err := b.WriteModels(baseDir)
- if err != nil {
- return err
- }
- return nil
-}
-
-func fullyQualifiedName(packageName string, typeName string) string {
- if len(packageName) > 0 {
- return packageName + "." + typeName
- }
-
- switch true {
- case len(typeName) == 0:
- return ""
- case typeName == "interface{}" || typeName == "interface {}":
- return "any"
- case typeName == "string":
- return "string"
- case typeName == "error":
- return "Error"
- case
- strings.HasPrefix(typeName, "int"),
- strings.HasPrefix(typeName, "uint"),
- strings.HasPrefix(typeName, "float"):
- return "number"
- case typeName == "bool":
- return "boolean"
- default:
- return "any"
- }
-}
-
-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
- }
-
- return "Array<" + valueType + ">"
-}
-
-func goTypeToJSDocType(input string, importNamespaces *slicer.StringSlicer) string {
- matches := mapRegex.FindStringSubmatch(input)
- keyPackage := matches[keyPackageIndex]
- keyType := matches[keyTypeIndex]
- valueArray := matches[valueArrayIndex]
- valuePackage := matches[valuePackageIndex]
- valueType := matches[valueTypeIndex]
- // fmt.Printf("input=%s, keyPackage=%s, keyType=%s, valueArray=%s, valuePackage=%s, valueType=%s\n",
- // input,
- // keyPackage,
- // keyType,
- // valueArray,
- // valuePackage,
- // valueType)
-
- // byte array is special case
- if valueArray == "[]" && valueType == "byte" {
- return "string"
- }
-
- // if any packages, make sure they're saved
- if len(keyPackage) > 0 {
- importNamespaces.Add(keyPackage)
- }
-
- if len(valuePackage) > 0 {
- importNamespaces.Add(valuePackage)
- }
-
- key := fullyQualifiedName(keyPackage, keyType)
- var value string
- if strings.HasPrefix(valueType, "map") {
- value = goTypeToJSDocType(valueType, importNamespaces)
- } else {
- value = fullyQualifiedName(valuePackage, valueType)
- }
-
- if len(key) > 0 {
- return fmt.Sprintf("Record<%s, %s>", key, arrayifyValue(valueArray, value))
- }
-
- return arrayifyValue(valueArray, value)
-}
-
-func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer) string {
- return goTypeToJSDocType(input, importNamespaces)
-}
-
-func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string {
- if strings.ContainsRune(input, '.') {
- nameSpace, returnType := getSplitReturn(input)
- return nameSpace + "." + prefix + returnType + suffix
- }
-
- return input
-}
diff --git a/v2/internal/binding/generate_test.go b/v2/internal/binding/generate_test.go
deleted file mode 100644
index 26d7c70df..000000000
--- a/v2/internal/binding/generate_test.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package binding
-
-import (
- "testing"
-
- "github.com/leaanthony/slicer"
- "github.com/stretchr/testify/assert"
- "github.com/wailsapp/wails/v2/internal/logger"
-)
-
-type BindForTest struct {
-}
-
-func (b *BindForTest) GetA() A {
- return A{}
-}
-
-type A struct {
- B B `json:"B"`
-}
-
-type B struct {
- Name string `json:"name"`
-}
-
-func TestNestedStruct(t *testing.T) {
- bind := &BindForTest{}
- testBindings := NewBindings(logger.New(nil), []interface{}{bind}, []interface{}{}, false, []interface{}{})
-
- namesStrSlicer := testBindings.getAllStructNames()
- names := []string{}
- namesStrSlicer.Each(func(s string) {
- names = append(names, s)
- })
- assert.Contains(t, names, "binding.A")
- assert.Contains(t, names, "binding.B")
-}
-
-func Test_goTypeToJSDocType(t *testing.T) {
-
- tests := []struct {
- name string
- input string
- want string
- }{
- {
- name: "string",
- input: "string",
- want: "string",
- },
- {
- name: "error",
- input: "error",
- want: "Error",
- },
- {
- name: "int",
- input: "int",
- want: "number",
- },
- {
- name: "int32",
- input: "int32",
- want: "number",
- },
- {
- name: "uint",
- input: "uint",
- want: "number",
- },
- {
- name: "uint32",
- input: "uint32",
- want: "number",
- },
- {
- name: "float32",
- input: "float32",
- want: "number",
- },
- {
- name: "float64",
- input: "float64",
- want: "number",
- },
- {
- name: "bool",
- input: "bool",
- want: "boolean",
- },
- {
- name: "interface{}",
- input: "interface{}",
- want: "any",
- },
- {
- name: "[]byte",
- input: "[]byte",
- want: "string",
- },
- {
- name: "[]int",
- input: "[]int",
- want: "Array",
- },
- {
- name: "[]bool",
- input: "[]bool",
- want: "Array",
- },
- {
- name: "anything else",
- input: "foo",
- want: "any",
- },
- {
- name: "map",
- input: "map[string]float64",
- want: "Record",
- },
- {
- name: "map",
- input: "map[string]map[string]float64",
- want: "Record>",
- },
- {
- 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 {
- t.Run(tt.name, func(t *testing.T) {
- if got := goTypeToJSDocType(tt.input, &importNamespaces); got != tt.want {
- t.Errorf("goTypeToJSDocType() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/v2/internal/binding/reflect.go b/v2/internal/binding/reflect.go
old mode 100644
new mode 100755
index c254d0f0a..297c9bcdf
--- a/v2/internal/binding/reflect.go
+++ b/v2/internal/binding/reflect.go
@@ -4,7 +4,6 @@ import (
"fmt"
"reflect"
"runtime"
- "strings"
)
// isStructPtr returns true if the value given is a
@@ -19,32 +18,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 getMethods(value interface{}) ([]*BoundMethod, error) {
-func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder
var result []*BoundMethod
@@ -67,28 +47,18 @@ 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))
+ baseName := structType.String()[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()
- if b.exemptions.Contains(methodReflectName) {
- continue
- }
-
// Create new method
boundMethod := &BoundMethod{
- Path: &BoundedMethodPath{
- Package: pkgPath,
- Struct: structNameNormalized,
- Name: methodName,
- },
+ Name: fullMethodName,
Inputs: nil,
Outputs: nil,
Comments: "",
@@ -102,34 +72,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
input := methodType.In(inputIndex)
thisParam := newParameter("", input)
-
- thisInput := input
-
- if thisInput.Kind() == reflect.Slice {
- thisInput = thisInput.Elem()
- }
-
- // Process struct pointer params
- if thisInput.Kind() == reflect.Ptr {
- if thisInput.Elem().Kind() == reflect.Struct {
- typ := thisInput.Elem()
- a := reflect.New(typ)
- s := reflect.Indirect(a).Interface()
- name := typ.Name()
- packageName := getPackageName(thisInput.String())
- b.AddStructToGenerateTS(packageName, name, s)
- }
- }
-
- // Process struct params
- if thisInput.Kind() == reflect.Struct {
- a := reflect.New(thisInput)
- s := reflect.Indirect(a).Interface()
- name := thisInput.Name()
- packageName := getPackageName(thisInput.String())
- b.AddStructToGenerateTS(packageName, name, s)
- }
-
inputs = append(inputs, thisParam)
}
@@ -143,34 +85,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
output := methodType.Out(outputIndex)
thisParam := newParameter("", output)
-
- thisOutput := output
-
- if thisOutput.Kind() == reflect.Slice {
- thisOutput = thisOutput.Elem()
- }
-
- // Process struct pointer params
- if thisOutput.Kind() == reflect.Ptr {
- if thisOutput.Elem().Kind() == reflect.Struct {
- typ := thisOutput.Elem()
- a := reflect.New(typ)
- s := reflect.Indirect(a).Interface()
- name := typ.Name()
- packageName := getPackageName(thisOutput.String())
- b.AddStructToGenerateTS(packageName, name, s)
- }
- }
-
- // Process struct params
- if thisOutput.Kind() == reflect.Struct {
- a := reflect.New(thisOutput)
- s := reflect.Indirect(a).Interface()
- name := thisOutput.Name()
- packageName := getPackageName(thisOutput.String())
- b.AddStructToGenerateTS(packageName, name, s)
- }
-
outputs = append(outputs, thisParam)
}
boundMethod.Outputs = outputs
@@ -181,20 +95,3 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
}
return result, nil
}
-
-func getPackageName(in string) string {
- result := strings.Split(in, ".")[0]
- result = strings.ReplaceAll(result, "[]", "")
- result = strings.ReplaceAll(result, "*", "")
- return result
-}
-
-func getSplitReturn(in string) (string, string) {
- result := strings.SplitN(in, ".", 2)
- return result[0], result[1]
-}
-
-func hasElements(typ reflect.Type) bool {
- kind := typ.Kind()
- return kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map
-}
diff --git a/v2/internal/colour/colour.go b/v2/internal/colour/colour.go
deleted file mode 100644
index ee1ca9743..000000000
--- a/v2/internal/colour/colour.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package colour
-
-import (
- "fmt"
- "strings"
-
- "github.com/wzshiming/ctc"
-)
-
-var ColourEnabled = true
-
-func Col(col ctc.Color, text string) string {
- if !ColourEnabled {
- return text
- }
- return fmt.Sprintf("%s%s%s", col, text, ctc.Reset)
-}
-
-func Yellow(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightYellow, text)
-}
-
-func Red(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightRed, text)
-}
-
-func Blue(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightBlue, text)
-}
-
-func Green(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightGreen, text)
-}
-
-func Cyan(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightCyan, text)
-}
-
-func Magenta(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightMagenta, text)
-}
-
-func White(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightWhite, text)
-}
-
-func Black(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBrightBlack, text)
-}
-
-func DarkYellow(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundYellow, text)
-}
-
-func DarkRed(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundRed, text)
-}
-
-func DarkBlue(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBlue, text)
-}
-
-func DarkGreen(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundGreen, text)
-}
-
-func DarkCyan(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundCyan, text)
-}
-
-func DarkMagenta(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundMagenta, text)
-}
-
-func DarkWhite(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundWhite, text)
-}
-
-func DarkBlack(text string) string {
- if !ColourEnabled {
- return text
- }
- return Col(ctc.ForegroundBlack, text)
-}
-
-var rainbowCols = []func(string) string{Red, Yellow, Green, Cyan, Blue, Magenta}
-
-func Rainbow(text string) string {
- if !ColourEnabled {
- return text
- }
- var builder strings.Builder
-
- for i := 0; i < len(text); i++ {
- fn := rainbowCols[i%len(rainbowCols)]
- builder.WriteString(fn(text[i : i+1]))
- }
-
- return builder.String()
-}
diff --git a/v2/internal/crypto/crypto.go b/v2/internal/crypto/crypto.go
new file mode 100644
index 000000000..60a010c3c
--- /dev/null
+++ b/v2/internal/crypto/crypto.go
@@ -0,0 +1,17 @@
+package crypto
+
+import (
+ "crypto/rand"
+ "fmt"
+ "log"
+)
+
+// RandomID returns a random ID as a string
+func RandomID() string {
+ b := make([]byte, 16)
+ _, err := rand.Read(b)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return fmt.Sprintf("%x", b)
+}
diff --git a/v2/internal/ffenestri/LICENCES.md b/v2/internal/ffenestri/LICENCES.md
new file mode 100644
index 000000000..1b1bdf148
--- /dev/null
+++ b/v2/internal/ffenestri/LICENCES.md
@@ -0,0 +1,13 @@
+# 3rd Party Licenses
+
+## vec
+Homepage: https://github.com/rxi/vec
+License: https://github.com/rxi/vec/blob/master/LICENSE
+
+## json
+Homepage: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
+License: http://git.ozlabs.org/?p=ccan;a=blob;f=licenses/BSD-MIT;h=89de354795ec7a7cdab07c091029653d3618540d;hb=HEAD
+
+## hashmap
+Homepage: https://github.com/sheredom/hashmap.h
+License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
\ No newline at end of file
diff --git a/v2/internal/ffenestri/common.c b/v2/internal/ffenestri/common.c
new file mode 100644
index 000000000..dcd460834
--- /dev/null
+++ b/v2/internal/ffenestri/common.c
@@ -0,0 +1,88 @@
+//
+// Created by Lea Anthony on 6/1/21.
+//
+
+#include "common.h"
+
+// Credit: https://stackoverflow.com/a/8465083
+char* concat(const char *string1, const char *string2)
+{
+ const size_t len1 = strlen(string1);
+ const size_t len2 = strlen(string2);
+ char *result = malloc(len1 + len2 + 1);
+ strcpy(result, string1);
+ memcpy(result + len1, string2, len2 + 1);
+ return result;
+}
+
+// 10k is more than enough for a log message
+#define MAXMESSAGE 1024*10
+char abortbuffer[MAXMESSAGE];
+
+void ABORT(const char *message, ...) {
+ const char *temp = concat("FATAL: ", message);
+ va_list args;
+ va_start(args, message);
+ vsnprintf(abortbuffer, MAXMESSAGE, temp, args);
+ printf("%s\n", &abortbuffer[0]);
+ MEMFREE(temp);
+ va_end(args);
+ exit(1);
+}
+
+int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
+ free(e->data);
+ return -1;
+}
+
+const char* getJSONString(JsonNode *item, const char* key) {
+ // Get key
+ JsonNode *node = json_find_member(item, key);
+ const char *result = "";
+ if ( node != NULL && node->tag == JSON_STRING) {
+ result = node->string_;
+ }
+ return result;
+}
+
+void ABORT_JSON(JsonNode *node, const char* key) {
+ ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node));
+}
+
+const char* mustJSONString(JsonNode *node, const char* key) {
+ const char* result = getJSONString(node, key);
+ if ( result == NULL ) {
+ ABORT_JSON(node, key);
+ }
+ return result;
+}
+JsonNode* mustJSONObject(JsonNode *node, const char* key) {
+ struct JsonNode* result = getJSONObject(node, key);
+ if ( result == NULL ) {
+ ABORT_JSON(node, key);
+ }
+ return result;
+}
+
+JsonNode* getJSONObject(JsonNode* node, const char* key) {
+ return json_find_member(node, key);
+}
+
+bool getJSONBool(JsonNode *item, const char* key, bool *result) {
+ JsonNode *node = json_find_member(item, key);
+ if ( node != NULL && node->tag == JSON_BOOL) {
+ *result = node->bool_;
+ return true;
+ }
+ return false;
+}
+
+bool getJSONInt(JsonNode *item, const char* key, int *result) {
+ JsonNode *node = json_find_member(item, key);
+ if ( node != NULL && node->tag == JSON_NUMBER) {
+ *result = (int) node->number_;
+ return true;
+ }
+ return false;
+}
+
diff --git a/v2/internal/ffenestri/common.h b/v2/internal/ffenestri/common.h
new file mode 100644
index 000000000..b289555a2
--- /dev/null
+++ b/v2/internal/ffenestri/common.h
@@ -0,0 +1,38 @@
+//
+// Created by Lea Anthony on 6/1/21.
+//
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#define OBJC_OLD_DISPATCH_PROTOTYPES 1
+#include
+#include
+
+#include
+#include
+#include "string.h"
+#include "hashmap.h"
+#include "vec.h"
+#include "json.h"
+
+#define STREQ(a,b) strcmp(a, b) == 0
+#define STREMPTY(string) strlen(string) == 0
+#define STRCOPY(a) concat(a, "")
+#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0
+#define MEMFREE(input) free((void*)input); input = NULL;
+#define FREE_AND_SET(variable, value) if( variable != NULL ) { MEMFREE(variable); } variable = value
+
+// Credit: https://stackoverflow.com/a/8465083
+char* concat(const char *string1, const char *string2);
+void ABORT(const char *message, ...);
+int freeHashmapItem(void *const context, struct hashmap_element_s *const e);
+const char* getJSONString(JsonNode *item, const char* key);
+const char* mustJSONString(JsonNode *node, const char* key);
+JsonNode* getJSONObject(JsonNode* node, const char* key);
+JsonNode* mustJSONObject(JsonNode *node, const char* key);
+
+bool getJSONBool(JsonNode *item, const char* key, bool *result);
+bool getJSONInt(JsonNode *item, const char* key, int *result);
+
+#endif //ASSETS_C_COMMON_H
diff --git a/v2/internal/ffenestri/contextmenus_darwin.c b/v2/internal/ffenestri/contextmenus_darwin.c
new file mode 100644
index 000000000..deaaf397b
--- /dev/null
+++ b/v2/internal/ffenestri/contextmenus_darwin.c
@@ -0,0 +1,99 @@
+////
+//// Created by Lea Anthony on 6/1/21.
+////
+//
+
+#include "ffenestri_darwin.h"
+#include "common.h"
+#include "contextmenus_darwin.h"
+#include "menu_darwin.h"
+
+ContextMenu* NewContextMenu(const char* contextMenuJSON) {
+ ContextMenu* result = malloc(sizeof(ContextMenu));
+
+ JsonNode* processedJSON = json_decode(contextMenuJSON);
+ if( processedJSON == NULL ) {
+ ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
+ }
+ // Save reference to this json
+ result->processedJSON = processedJSON;
+
+ result->ID = mustJSONString(processedJSON, "ID");
+ JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
+
+ result->menu = NewMenu(processedMenu);
+ result->nsmenu = NULL;
+ result->menu->menuType = ContextMenuType;
+ result->menu->parentData = result;
+ result->contextMenuData = NULL;
+ return result;
+}
+
+ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
+ return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
+}
+
+void DeleteContextMenu(ContextMenu* contextMenu) {
+ // Free Menu
+ DeleteMenu(contextMenu->menu);
+
+ // Delete any context menu data we may have stored
+ if( contextMenu->contextMenuData != NULL ) {
+ MEMFREE(contextMenu->contextMenuData);
+ }
+
+ // Free JSON
+ if (contextMenu->processedJSON != NULL ) {
+ json_delete(contextMenu->processedJSON);
+ contextMenu->processedJSON = NULL;
+ }
+
+ // Free context menu
+ free(contextMenu);
+}
+
+int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
+ DeleteContextMenu(e->data);
+ return -1;
+}
+
+void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
+
+ // If no context menu ID was given, abort
+ if( contextMenuID == NULL ) {
+ return;
+ }
+
+ ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
+
+ // We don't need the ID now
+ MEMFREE(contextMenuID);
+
+ if( contextMenu == NULL ) {
+ // Free context menu data
+ if( contextMenuData != NULL ) {
+ MEMFREE(contextMenuData);
+ return;
+ }
+ }
+
+ // We need to store the context menu data. Free existing data if we have it
+ // and set to the new value.
+ FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
+
+ // Grab the content view and show the menu
+ id contentView = msg(mainWindow, s("contentView"));
+
+ // Get the triggering event
+ id menuEvent = msg(mainWindow, s("currentEvent"));
+
+ if( contextMenu->nsmenu == NULL ) {
+ // GetMenu creates the NSMenu
+ contextMenu->nsmenu = GetMenu(contextMenu->menu);
+ }
+
+ // Show popup
+ msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
+
+}
+
diff --git a/v2/internal/ffenestri/contextmenus_darwin.h b/v2/internal/ffenestri/contextmenus_darwin.h
new file mode 100644
index 000000000..a1e7b976a
--- /dev/null
+++ b/v2/internal/ffenestri/contextmenus_darwin.h
@@ -0,0 +1,33 @@
+////
+//// Created by Lea Anthony on 6/1/21.
+////
+//
+#ifndef CONTEXTMENU_DARWIN_H
+#define CONTEXTMENU_DARWIN_H
+
+#include "json.h"
+#include "menu_darwin.h"
+#include "contextmenustore_darwin.h"
+
+typedef struct {
+ const char* ID;
+ id nsmenu;
+ Menu* menu;
+
+ JsonNode* processedJSON;
+
+ // Context menu data is given by the frontend when clicking a context menu.
+ // We send this to the backend when an item is selected
+ const char* contextMenuData;
+} ContextMenu;
+
+
+ContextMenu* NewContextMenu(const char* contextMenuJSON);
+
+ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
+void DeleteContextMenu(ContextMenu* contextMenu);
+int freeContextMenu(void *const context, struct hashmap_element_s *const e);
+
+void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
+
+#endif //CONTEXTMENU_DARWIN_H
diff --git a/v2/internal/ffenestri/contextmenustore_darwin.c b/v2/internal/ffenestri/contextmenustore_darwin.c
new file mode 100644
index 000000000..c777f014e
--- /dev/null
+++ b/v2/internal/ffenestri/contextmenustore_darwin.c
@@ -0,0 +1,65 @@
+
+#include "contextmenus_darwin.h"
+#include "contextmenustore_darwin.h"
+
+ContextMenuStore* NewContextMenuStore() {
+
+ ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
+
+ // Allocate Context Menu Store
+ if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
+ ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
+ }
+
+ return result;
+}
+
+void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
+ ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
+
+ //TODO: check if there is already an entry for this menu
+ hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
+}
+
+ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
+ // Get the current menu
+ return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
+}
+
+void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
+ ContextMenu* newContextMenu = NewContextMenu(menuJSON);
+
+ // Get the current menu
+ ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
+ if ( currentMenu == NULL ) {
+ ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
+ }
+
+ hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
+
+ // Save the status bar reference
+ DeleteContextMenu(currentMenu);
+
+ hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
+
+}
+
+
+void DeleteContextMenuStore(ContextMenuStore* store) {
+
+ // Guard against NULLs
+ if( store == NULL ) {
+ return;
+ }
+
+ // Delete context menus
+ if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
+ if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
+ ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
+ }
+ }
+
+ // Free context menu hashmap
+ hashmap_destroy(&store->contextMenuMap);
+
+}
diff --git a/v2/internal/ffenestri/contextmenustore_darwin.h b/v2/internal/ffenestri/contextmenustore_darwin.h
new file mode 100644
index 000000000..793eef691
--- /dev/null
+++ b/v2/internal/ffenestri/contextmenustore_darwin.h
@@ -0,0 +1,27 @@
+//
+// Created by Lea Anthony on 7/1/21.
+//
+
+#ifndef CONTEXTMENUSTORE_DARWIN_H
+#define CONTEXTMENUSTORE_DARWIN_H
+
+#include "common.h"
+
+typedef struct {
+
+ int dummy;
+
+ // This is our context menu store which keeps track
+ // of all instances of ContextMenus
+ struct hashmap_s contextMenuMap;
+
+} ContextMenuStore;
+
+ContextMenuStore* NewContextMenuStore();
+
+void DeleteContextMenuStore(ContextMenuStore* store);
+void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
+
+void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
+
+#endif //CONTEXTMENUSTORE_DARWIN_H
diff --git a/v2/internal/ffenestri/defaultdialogicons_darwin.c b/v2/internal/ffenestri/defaultdialogicons_darwin.c
new file mode 100644
index 000000000..0c79a6b55
--- /dev/null
+++ b/v2/internal/ffenestri/defaultdialogicons_darwin.c
@@ -0,0 +1,41 @@
+// defaultdialogicons_darwin.c
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL.
+// This file was auto-generated. DO NOT MODIFY.
+
+const unsigned char defaultDialogIcon0Name[] = { 0x69, 0x6e, 0x66, 0x6f, 0x2d, 0x64, 0x61, 0x72, 0x6b, 0x00 };
+const unsigned char defaultDialogIcon0Length[] = { 0x37, 0x38, 0x30, 0x00 };
+const unsigned char defaultDialogIcon0Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x3, 0x0, 0x0, 0x0, 0x9d, 0xb7, 0x81, 0xec, 0x0, 0x0, 0x0, 0x84, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x20, 0xdb, 0xe6, 0xd1, 0x0, 0x0, 0x0, 0x2b, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xf9, 0x9c, 0xb1, 0xe7, 0x4b, 0x91, 0x3a, 0x1b, 0xe3, 0x1e, 0xa, 0xae, 0x51, 0xe0, 0x98, 0x32, 0x4, 0x66, 0x6d, 0x2b, 0x63, 0x5d, 0x18, 0x11, 0xd6, 0xd0, 0xc8, 0xc2, 0x40, 0x7, 0x76, 0x16, 0x48, 0xa0, 0x2f, 0x57, 0xf1, 0xf0, 0x85, 0x77, 0x2c, 0xf, 0x6d, 0x84, 0xfd, 0x3d, 0x0, 0x0, 0x2, 0xc, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x57, 0xd9, 0x96, 0x82, 0x30, 0xc, 0x15, 0x2c, 0x4b, 0x91, 0x5d, 0x40, 0x10, 0x5, 0xf7, 0x65, 0xfc, 0xff, 0xff, 0x9b, 0x63, 0xca, 0xa1, 0x40, 0xb, 0x61, 0xa6, 0xc7, 0x37, 0xf3, 0x18, 0x92, 0xdb, 0x66, 0xb9, 0x69, 0x58, 0x7c, 0xe5, 0xb3, 0x62, 0x87, 0x46, 0xfa, 0x78, 0xa4, 0x46, 0x68, 0xff, 0xc3, 0x79, 0xe3, 0x99, 0x25, 0xd5, 0x5e, 0x20, 0x1a, 0x2d, 0x4d, 0x6f, 0xf3, 0x27, 0x77, 0x2b, 0xf0, 0x5f, 0x3, 0xf1, 0x3, 0x6b, 0xb6, 0xfb, 0x7e, 0xa9, 0xbd, 0x24, 0xa2, 0x2d, 0xf7, 0xb3, 0xdc, 0x1d, 0x1d, 0xdc, 0xa5, 0x10, 0xba, 0x83, 0xfb, 0x1b, 0x7e, 0xd7, 0x83, 0x14, 0x5, 0xe9, 0xe2, 0xf9, 0x6, 0xe6, 0xbf, 0x6e, 0xcd, 0xa9, 0x1b, 0x5b, 0x89, 0x93, 0xe7, 0x4e, 0x62, 0xc5, 0x2e, 0x6d, 0x21, 0xd7, 0x93, 0xee, 0x2b, 0xb3, 0x8d, 0xd7, 0xb3, 0x7b, 0x15, 0xf5, 0xda, 0xbc, 0x98, 0xab, 0x9, 0x7f, 0xb7, 0x31, 0xda, 0x1d, 0x25, 0xb1, 0xed, 0x9a, 0x8f, 0xee, 0x38, 0x42, 0x73, 0x3e, 0xc9, 0x2a, 0xd9, 0xd7, 0x2a, 0x23, 0xcd, 0x1d, 0x46, 0xe3, 0x67, 0xdf, 0xb7, 0xe1, 0x98, 0x41, 0xb8, 0x65, 0x16, 0x23, 0x79, 0x30, 0x58, 0x94, 0xcb, 0xcd, 0x44, 0x7f, 0x2e, 0x59, 0x86, 0xc, 0x69, 0xfd, 0x29, 0xf3, 0x5f, 0x4d, 0x66, 0x99, 0x21, 0x50, 0x59, 0x3f, 0xe8, 0xec, 0xfe, 0x83, 0xf3, 0x9f, 0xcf, 0x1, 0xc1, 0x58, 0x14, 0xba, 0xa4, 0x7f, 0x21, 0x0, 0x72, 0xee, 0x1f, 0x78, 0xba, 0x5e, 0x4f, 0xfd, 0x2b, 0x9d, 0x9, 0x4, 0x21, 0x74, 0x75, 0xc4, 0xee, 0x96, 0xf5, 0xb5, 0xf1, 0x5b, 0x17, 0xf7, 0x75, 0x19, 0x8b, 0x34, 0x1a, 0xf2, 0x4f, 0x83, 0xfa, 0x57, 0x42, 0x5d, 0x85, 0xaa, 0x55, 0x3b, 0xb8, 0xc2, 0x90, 0x9b, 0x1, 0x68, 0x8f, 0xb, 0xc9, 0x69, 0x97, 0x81, 0xf2, 0x8, 0x67, 0x5, 0x7d, 0xe5, 0xc1, 0x87, 0x7b, 0x9, 0x81, 0xdd, 0x29, 0xbd, 0x47, 0x43, 0x2d, 0x44, 0xeb, 0x1f, 0x7a, 0x3a, 0xf, 0x2, 0xf3, 0xc4, 0xdc, 0x1e, 0x98, 0x1d, 0x6a, 0xc, 0xc1, 0x12, 0x7b, 0xe6, 0xac, 0x24, 0x42, 0x6a, 0xa2, 0x12, 0x58, 0x22, 0xda, 0x46, 0x96, 0x15, 0x89, 0x5a, 0xe0, 0x5c, 0x19, 0x75, 0x31, 0xa9, 0x50, 0x2f, 0x90, 0xdc, 0xd5, 0x34, 0xf7, 0x47, 0x50, 0x43, 0x75, 0xa9, 0xdd, 0x65, 0x9, 0x24, 0xd6, 0x12, 0xe9, 0x25, 0xa7, 0x8e, 0x5, 0x25, 0xb, 0xbb, 0x3c, 0x2, 0x4d, 0x22, 0x58, 0x6, 0x42, 0xc1, 0x40, 0x12, 0x38, 0xaf, 0xcb, 0xa8, 0x14, 0x72, 0xe8, 0xc8, 0xf9, 0xa1, 0x8b, 0xbc, 0x83, 0x2c, 0xa6, 0x1d, 0x4d, 0xfd, 0x56, 0x14, 0xf9, 0x5c, 0x80, 0xbc, 0x78, 0xeb, 0x6b, 0x65, 0x0, 0xe5, 0x10, 0xb0, 0x24, 0x72, 0x0, 0x3c, 0x89, 0xbc, 0x8c, 0x38, 0x0, 0x2f, 0x23, 0xd6, 0x48, 0x1c, 0x0, 0x6f, 0x24, 0xde, 0xca, 0x38, 0x0, 0x6f, 0x65, 0x84, 0x4c, 0x1c, 0x0, 0x27, 0x13, 0x67, 0x28, 0xe, 0xc0, 0x8d, 0xb1, 0x81, 0xc2, 0x1, 0xf0, 0x81, 0xc2, 0x47, 0x1a, 0xa, 0xc0, 0x47, 0x1a, 0x3a, 0x54, 0x39, 0x99, 0x90, 0xa1, 0x8a, 0x8e, 0xf5, 0x74, 0x62, 0xac, 0xe3, 0xf, 0xcb, 0xed, 0x86, 0x3c, 0x2c, 0xd8, 0xd3, 0x96, 0x24, 0xf8, 0xd3, 0xa6, 0xfe, 0xb8, 0xce, 0x7f, 0xde, 0x6d, 0xfe, 0xbc, 0xab, 0x2d, 0x18, 0xca, 0x2b, 0xe, 0xbe, 0x64, 0x19, 0xd8, 0x92, 0x85, 0xaf, 0x79, 0x17, 0xbb, 0x97, 0xe1, 0xb, 0xb2, 0xe6, 0x61, 0x8b, 0x26, 0x11, 0x17, 0x4d, 0xf5, 0x55, 0x57, 0x7d, 0xd9, 0x56, 0x5f, 0xf7, 0xd5, 0x7f, 0x38, 0xd4, 0x7f, 0x79, 0xf0, 0x9f, 0xae, 0xba, 0x86, 0x9f, 0xae, 0xaf, 0x7c, 0x54, 0x7e, 0x1, 0x7, 0x4e, 0x88, 0x96, 0x1d, 0xbb, 0xbc, 0x56, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon1Name[] = { 0x69, 0x6e, 0x66, 0x6f, 0x2d, 0x64, 0x61, 0x72, 0x6b, 0x32, 0x78, 0x00 };
+const unsigned char defaultDialogIcon1Length[] = { 0x31, 0x32, 0x39, 0x35, 0x00 };
+const unsigned char defaultDialogIcon1Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf4, 0xe0, 0x91, 0xf9, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x25, 0xc5, 0xa8, 0x0, 0x0, 0x0, 0x31, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xfb, 0x55, 0x3, 0x61, 0x95, 0xc, 0xd7, 0xb7, 0x8a, 0x82, 0x39, 0xe5, 0x40, 0x19, 0x44, 0x2b, 0x4e, 0xee, 0xc6, 0xbf, 0x48, 0x25, 0x21, 0xe2, 0xb3, 0x67, 0xe8, 0x33, 0xd0, 0x6c, 0x42, 0x9, 0xcb, 0xf, 0xf6, 0xc2, 0xbc, 0xb0, 0xa8, 0x71, 0x52, 0x14, 0x7, 0x90, 0x7c, 0x8f, 0x11, 0x7b, 0xbe, 0x95, 0xf5, 0x71, 0x0, 0x0, 0x3, 0xf7, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x97, 0xd9, 0x72, 0xaa, 0x40, 0x10, 0x86, 0xdb, 0xc, 0xc8, 0xe, 0xa2, 0x2c, 0xee, 0x68, 0xdc, 0xf5, 0x98, 0xd4, 0xbc, 0xff, 0xcb, 0x9d, 0x4a, 0x9d, 0x53, 0xa1, 0x41, 0x98, 0x9e, 0x19, 0x30, 0x95, 0xb, 0xbf, 0xeb, 0x6, 0x7a, 0xf9, 0x7b, 0x1, 0x5e, 0xbc, 0x78, 0xf1, 0xe2, 0x85, 0x3e, 0x9b, 0x30, 0x5b, 0x16, 0x71, 0x5c, 0x2c, 0xb3, 0x70, 0x3, 0x3f, 0xcb, 0x30, 0x5a, 0x9b, 0x7, 0x9f, 0x23, 0xfc, 0x83, 0xb9, 0x8e, 0x86, 0xf0, 0x13, 0xdc, 0x2, 0x77, 0x32, 0xe0, 0x8d, 0xc, 0x26, 0x6e, 0x70, 0x83, 0xa7, 0xc2, 0xd2, 0xd3, 0x8e, 0xb, 0xd9, 0x9d, 0x52, 0x6, 0xcf, 0x62, 0xe4, 0x2c, 0xb8, 0x4, 0xb, 0x67, 0x4, 0xcf, 0xc0, 0xb3, 0xb9, 0x34, 0xb6, 0x7, 0x7d, 0x53, 0x24, 0x5c, 0x89, 0xa4, 0xe8, 0x37, 0xf9, 0x9, 0x57, 0x26, 0xe9, 0xaf, 0x10, 0x43, 0x67, 0xc0, 0x35, 0x18, 0x38, 0x3d, 0xf5, 0x65, 0x3c, 0xe5, 0x9a, 0x4c, 0x63, 0xe8, 0xe, 0x73, 0x78, 0x7, 0x1c, 0xd6, 0x39, 0xfd, 0xb3, 0xd6, 0xc, 0xfb, 0xdb, 0xa3, 0x79, 0x37, 0x8c, 0xbb, 0x79, 0xdc, 0xfa, 0xad, 0x35, 0x9a, 0x75, 0x2c, 0x43, 0x6e, 0xf1, 0x26, 0x56, 0xae, 0x17, 0x32, 0x40, 0xb0, 0xd0, 0x73, 0x57, 0xbc, 0x9, 0x2b, 0x87, 0xe, 0x4, 0xe3, 0x86, 0xc8, 0xf7, 0xe7, 0xbc, 0xc5, 0xdb, 0xf3, 0xbe, 0x21, 0x13, 0xe3, 0x0, 0xb4, 0x99, 0x3f, 0xbe, 0xcf, 0x36, 0xae, 0x20, 0xe0, 0x6a, 0xd8, 0x8f, 0x1e, 0xcf, 0x75, 0xe5, 0x67, 0xf2, 0x3a, 0x13, 0xf, 0x48, 0xbc, 0x9, 0xaf, 0x63, 0x32, 0xad, 0xef, 0x3f, 0xc8, 0xcf, 0xba, 0x80, 0x14, 0x17, 0xeb, 0x41, 0x8a, 0x3a, 0x1e, 0xd4, 0xe3, 0x5f, 0xcc, 0x99, 0xb4, 0xef, 0xf3, 0x45, 0x3d, 0x7, 0x1a, 0xf5, 0xaf, 0x15, 0xd2, 0x1d, 0x2a, 0x75, 0xaf, 0x5b, 0x93, 0x8f, 0xb2, 0xe, 0x82, 0x41, 0x55, 0xca, 0x5, 0x28, 0x12, 0x8d, 0xab, 0x1, 0x4, 0x8a, 0xfd, 0x5f, 0x7d, 0xdc, 0xca, 0x40, 0x99, 0xcc, 0xaa, 0x86, 0x90, 0x2b, 0x65, 0xb0, 0xfa, 0xb0, 0xfd, 0xe, 0x1a, 0xbc, 0xdb, 0xd5, 0x20, 0x86, 0xda, 0xd, 0xf0, 0xc1, 0x40, 0xb, 0xf6, 0xa1, 0xdb, 0xa, 0x4e, 0xa5, 0x7a, 0x6, 0x68, 0x63, 0x54, 0x94, 0xe4, 0x80, 0x24, 0x31, 0xc7, 0x18, 0xd0, 0x1, 0x83, 0x63, 0x62, 0x49, 0x1, 0x4c, 0x2b, 0xf9, 0x87, 0x4e, 0xfc, 0xe1, 0x88, 0xe9, 0x46, 0xbd, 0x0, 0x36, 0x83, 0x4e, 0x30, 0x5b, 0xb9, 0x8, 0x23, 0x5c, 0x37, 0x4b, 0xac, 0xff, 0x4f, 0xc3, 0x34, 0x8d, 0x4f, 0x71, 0x2f, 0x58, 0x58, 0x4f, 0x23, 0xa0, 0x49, 0x70, 0xf3, 0x8a, 0xfb, 0x3f, 0xf5, 0xbf, 0x8c, 0xfc, 0x14, 0x44, 0x64, 0x78, 0xa4, 0x24, 0x40, 0x52, 0x60, 0x87, 0x23, 0x10, 0xb1, 0x1c, 0xfc, 0x37, 0x5b, 0x82, 0x88, 0x8, 0xa7, 0xb4, 0x50, 0x4a, 0x80, 0xb, 0x42, 0x56, 0xdf, 0x7, 0x12, 0x8, 0x71, 0x55, 0x52, 0xe0, 0xe1, 0xfd, 0x27, 0x9e, 0x5d, 0x61, 0x69, 0x19, 0x8a, 0xdb, 0xa, 0xef, 0x46, 0xf, 0xc4, 0xd8, 0xf2, 0x1b, 0x2c, 0x2d, 0x2d, 0x53, 0x10, 0xb2, 0xc6, 0x6d, 0x45, 0xb4, 0x0, 0xee, 0x0, 0x26, 0x6d, 0x4b, 0x88, 0x9b, 0x59, 0x1c, 0xd9, 0x4a, 0xcf, 0x80, 0xb, 0xd5, 0xe2, 0xdf, 0xf2, 0x1e, 0x33, 0xea, 0x46, 0x92, 0x9d, 0x5, 0xc, 0x55, 0x6b, 0x2, 0x14, 0x73, 0xf9, 0x6b, 0x3, 0xdd, 0x89, 0x3e, 0x23, 0xca, 0x8a, 0xd4, 0x42, 0x61, 0x4a, 0xdf, 0x5b, 0x1e, 0x97, 0x13, 0xcc, 0x9, 0x8b, 0x45, 0x82, 0x78, 0xe6, 0xfb, 0xb3, 0x18, 0x30, 0xb4, 0xb8, 0x4f, 0xd0, 0xca, 0x6d, 0x47, 0x2f, 0xc1, 0xee, 0x6b, 0x71, 0x77, 0x83, 0x36, 0x2, 0x34, 0x4, 0xaf, 0xd0, 0x2b, 0x57, 0x34, 0xe, 0x3, 0x99, 0x91, 0xb5, 0x87, 0x9e, 0xd9, 0xa3, 0x1, 0x2b, 0xa3, 0xd5, 0x33, 0xf4, 0xcc, 0x59, 0xa2, 0xbf, 0x86, 0x28, 0x4d, 0x39, 0xf4, 0x4c, 0x8e, 0xca, 0xdb, 0x36, 0xe2, 0x23, 0xf4, 0xff, 0x2d, 0x79, 0xf8, 0x7e, 0xdd, 0x3, 0x92, 0x27, 0x33, 0xfa, 0x7b, 0x8f, 0xe8, 0x91, 0xed, 0x82, 0xc, 0xd1, 0xbf, 0x7b, 0xa0, 0xf6, 0x3e, 0x5a, 0x60, 0x6b, 0x68, 0xc6, 0x24, 0xa6, 0x10, 0x71, 0xf, 0x10, 0x78, 0xf4, 0x9f, 0xe2, 0x1, 0xed, 0x57, 0xa5, 0xa4, 0x6e, 0x1, 0x43, 0x6f, 0xef, 0x3, 0x34, 0xe3, 0x97, 0x32, 0x61, 0x40, 0x13, 0xaa, 0xf9, 0xcb, 0x4a, 0x89, 0x4f, 0xa1, 0x91, 0xd, 0x5a, 0x18, 0x40, 0x40, 0xdc, 0x3, 0x44, 0x7c, 0x7c, 0x43, 0x44, 0x24, 0x97, 0xd3, 0xb7, 0xd2, 0xfe, 0xd, 0x24, 0xd8, 0x52, 0x19, 0xcb, 0x4a, 0x83, 0xe3, 0x33, 0x1c, 0x38, 0x96, 0xf6, 0x59, 0xb3, 0xa8, 0x91, 0x4c, 0x3b, 0x39, 0x40, 0x37, 0xd9, 0x92, 0x5a, 0x45, 0xf7, 0x67, 0x38, 0x70, 0xa7, 0xd6, 0x51, 0x4c, 0xec, 0x62, 0xc2, 0x1, 0x95, 0x8d, 0x1c, 0xff, 0x4e, 0x7, 0xfe, 0xb6, 0x6f, 0x6, 0x29, 0xc, 0x2, 0x41, 0x10, 0x3c, 0xc4, 0x63, 0xc4, 0x78, 0x8a, 0x10, 0x90, 0x24, 0x67, 0x21, 0xfe, 0xff, 0x75, 0x1, 0x73, 0x89, 0x18, 0x28, 0xb1, 0xc, 0xb3, 0x2b, 0x3b, 0x1f, 0xb0, 0x41, 0xdd, 0x9d, 0xe9, 0xae, 0x9, 0x7f, 0x5, 0xe1, 0x1f, 0x61, 0xf8, 0x6f, 0x18, 0x7e, 0x10, 0xc1, 0x51, 0xc, 0x2, 0xc4, 0x51, 0xcc, 0x97, 0x11, 0xb, 0x50, 0x97, 0x11, 0x5f, 0xc7, 0x2c, 0x40, 0x5d, 0xc7, 0xdc, 0x90, 0xb0, 0x0, 0xd5, 0x90, 0x70, 0x4b, 0xc6, 0x2, 0x54, 0x4b, 0xc6, 0x4d, 0x29, 0xb, 0x50, 0x4d, 0x29, 0xb7, 0xe5, 0x2c, 0x40, 0xb5, 0xe5, 0x3c, 0x98, 0xb0, 0x0, 0x35, 0x98, 0xf0, 0x68, 0xc6, 0x2, 0xd4, 0x68, 0xc6, 0xc3, 0x29, 0xb, 0x50, 0xc3, 0x29, 0x8f, 0xe7, 0x2c, 0xc0, 0x8d, 0xe7, 0x6c, 0x50, 0xb0, 0x0, 0x6f, 0x50, 0xb0, 0x45, 0xc3, 0x2, 0xbc, 0x45, 0xc3, 0x26, 0x15, 0xb, 0x60, 0x93, 0xca, 0xd8, 0x74, 0x2c, 0x80, 0x6d, 0x3a, 0x65, 0x54, 0xb2, 0x0, 0x36, 0x2a, 0xd3, 0xb6, 0x6a, 0xe3, 0xcd, 0xea, 0x4d, 0x76, 0xfd, 0xc3, 0xd9, 0xf5, 0x3e, 0xb0, 0xb8, 0x88, 0xc0, 0x42, 0x45, 0x36, 0x4d, 0x37, 0x35, 0x58, 0x8d, 0x88, 0x6c, 0x64, 0x68, 0xf5, 0xac, 0x86, 0xa1, 0x82, 0x5c, 0x6b, 0x1e, 0x5a, 0xe5, 0x10, 0xdb, 0xc5, 0x7, 0x97, 0xe1, 0xd1, 0x6d, 0x7c, 0x78, 0xbd, 0x5b, 0x7c, 0x3f, 0x40, 0x7c, 0xff, 0x67, 0x80, 0xe1, 0xb5, 0x0, 0x18, 0x42, 0x11, 0x8e, 0xdb, 0xd5, 0x41, 0x2c, 0x8d, 0x86, 0x58, 0x72, 0xc3, 0x78, 0x7e, 0x80, 0x4c, 0xfd, 0x7a, 0x90, 0xa9, 0x5f, 0x80, 0x4c, 0x39, 0xa2, 0x5c, 0x3b, 0xc2, 0x6c, 0xc3, 0x29, 0x4f, 0x9c, 0x2f, 0x1, 0xa0, 0x31, 0x1e, 0xe9, 0x44, 0xa8, 0x75, 0xac, 0xaa, 0x11, 0xa0, 0xd6, 0xdc, 0xb1, 0xde, 0x78, 0xb0, 0xd9, 0xa0, 0xdd, 0xf7, 0xa3, 0xc0, 0xed, 0x9, 0xe0, 0xfd, 0x9, 0x2c, 0x38, 0x7c, 0x56, 0x3c, 0xce, 0x6b, 0x9e, 0x7e, 0x5e, 0xae, 0x78, 0x1c, 0x65, 0xc9, 0x25, 0x81, 0x35, 0x9f, 0xef, 0x45, 0xa7, 0x6e, 0xf6, 0xc7, 0x8b, 0x45, 0x27, 0xb5, 0xea, 0x55, 0xb7, 0x6d, 0x3d, 0xad, 0x7a, 0x95, 0x2a, 0x55, 0xaa, 0x54, 0xa9, 0xcd, 0xf5, 0x6, 0xc2, 0x69, 0xab, 0xf3, 0x71, 0x7e, 0x8e, 0x8, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon2Name[] = { 0x69, 0x6e, 0x66, 0x6f, 0x2d, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x00 };
+const unsigned char defaultDialogIcon2Length[] = { 0x37, 0x36, 0x34, 0x00 };
+const unsigned char defaultDialogIcon2Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x3, 0x0, 0x0, 0x0, 0x9d, 0xb7, 0x81, 0xec, 0x0, 0x0, 0x0, 0x75, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x79, 0x59, 0x7d, 0x58, 0x0, 0x0, 0x0, 0x26, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xfc, 0xa5, 0xc9, 0xd, 0xf4, 0x7c, 0xd5, 0x8e, 0x7f, 0x26, 0x13, 0x78, 0xed, 0x47, 0xe0, 0xa0, 0x43, 0x8, 0xc5, 0xe9, 0xdc, 0xb4, 0xb0, 0x90, 0x88, 0x6b, 0x51, 0x29, 0x1f, 0xbe, 0x19, 0x94, 0x93, 0x37, 0x36, 0x1a, 0x58, 0x7b, 0xc0, 0x9a, 0x4f, 0x0, 0x0, 0x2, 0x10, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x57, 0xd9, 0xb2, 0x82, 0x30, 0xc, 0x15, 0x91, 0x4d, 0x40, 0x40, 0x94, 0x4d, 0xb9, 0xee, 0xfd, 0xff, 0x4f, 0xbc, 0x93, 0x14, 0xd, 0xd3, 0x2, 0x41, 0x3b, 0xbe, 0x99, 0xd1, 0x7, 0xd2, 0xe4, 0x34, 0x3d, 0x4d, 0xd3, 0x74, 0xf1, 0x93, 0xaf, 0x4a, 0xbb, 0x69, 0xea, 0x73, 0x9a, 0x9e, 0xeb, 0x66, 0xd3, 0xbe, 0xef, 0x9d, 0xf9, 0xb6, 0x23, 0x5e, 0xe2, 0xd8, 0x7e, 0xf6, 0x8e, 0x77, 0x18, 0x79, 0xe8, 0x67, 0x75, 0x82, 0x1f, 0x5e, 0x14, 0xce, 0x74, 0x5f, 0x5, 0x3b, 0x74, 0x7e, 0x21, 0x3c, 0xbf, 0x76, 0xc1, 0x6a, 0x8e, 0xff, 0x61, 0x8f, 0xf6, 0xf8, 0x97, 0x42, 0xdf, 0xfb, 0x3, 0xef, 0xef, 0xb, 0x81, 0x53, 0xe2, 0xd2, 0x13, 0xbb, 0x2c, 0xed, 0x4, 0xc9, 0xe8, 0x94, 0x3e, 0x17, 0x7e, 0x81, 0x33, 0xc1, 0x2f, 0x77, 0x6f, 0xe1, 0x16, 0x74, 0xdb, 0xf0, 0xe6, 0xe6, 0xa8, 0x84, 0xb1, 0x62, 0x72, 0x19, 0x6b, 0x4f, 0x48, 0xcb, 0xd8, 0x5d, 0x2b, 0x23, 0x6e, 0x2c, 0xe4, 0x98, 0xb7, 0x9e, 0xf0, 0x8f, 0xa5, 0x8d, 0x55, 0x9d, 0xf4, 0xc1, 0x53, 0x65, 0xc9, 0xd1, 0x78, 0x14, 0x61, 0xd5, 0xcd, 0x9f, 0x1f, 0x87, 0xc7, 0x8f, 0x76, 0x17, 0xc3, 0xd8, 0x2a, 0xa, 0x49, 0xdd, 0x72, 0x3c, 0xc4, 0xa5, 0xa4, 0xb3, 0x18, 0xe3, 0x1f, 0xfd, 0xdd, 0x29, 0x92, 0x2, 0x89, 0xe0, 0xf, 0xee, 0xbf, 0x8c, 0x2f, 0x58, 0x4c, 0x4a, 0x20, 0xad, 0xe, 0x3, 0x4, 0xec, 0x81, 0x3d, 0x2d, 0xfe, 0xcd, 0x46, 0x5d, 0x5, 0x9a, 0xed, 0x57, 0x3a, 0x34, 0xe, 0xd8, 0xa, 0xea, 0x9f, 0x10, 0x7f, 0x8a, 0x2d, 0x30, 0x39, 0x10, 0x68, 0xb8, 0xc3, 0xd8, 0x14, 0xfe, 0x5d, 0x48, 0x1d, 0x85, 0x94, 0x23, 0xae, 0x61, 0xa7, 0x9e, 0xac, 0x8, 0x71, 0x2b, 0x45, 0x9b, 0xa, 0xc7, 0x11, 0xa9, 0xa2, 0xac, 0xd0, 0x34, 0x52, 0xb4, 0x1e, 0xcc, 0x15, 0xab, 0xf9, 0xf3, 0x0, 0xd2, 0x1f, 0x8a, 0xf2, 0x1e, 0x83, 0xad, 0xa7, 0xd4, 0xf, 0xdc, 0x1d, 0x7d, 0x7, 0xa3, 0x24, 0x89, 0x34, 0xa5, 0x8b, 0xfb, 0x9d, 0xe9, 0x39, 0x60, 0xd, 0xe4, 0xe8, 0x76, 0x3b, 0x90, 0xf1, 0x96, 0x9e, 0xb, 0x36, 0x44, 0x95, 0x2f, 0x66, 0x4a, 0x2e, 0x2c, 0x65, 0xc3, 0x5a, 0x87, 0x56, 0x30, 0x9d, 0x7, 0xb4, 0x6, 0xa7, 0xed, 0x9b, 0x9, 0x90, 0x1b, 0x29, 0x94, 0x3c, 0x50, 0xe4, 0x8a, 0xe6, 0x7d, 0xe8, 0x6, 0x21, 0x43, 0x9d, 0x43, 0x88, 0x55, 0x67, 0x31, 0xc4, 0x80, 0x9b, 0x9e, 0xa6, 0x6, 0xc3, 0x44, 0xe7, 0x6b, 0x9, 0xfa, 0xa5, 0xce, 0x6c, 0x2, 0xfa, 0xba, 0xa7, 0x39, 0x13, 0x2b, 0x1c, 0x0, 0x71, 0x7e, 0xe9, 0x67, 0x1c, 0x28, 0xca, 0xf9, 0x0, 0x25, 0xe8, 0x53, 0x63, 0x0, 0xe3, 0x25, 0x18, 0x93, 0xc8, 0x6d, 0x23, 0x1, 0xf0, 0xdb, 0x48, 0x89, 0xc4, 0x3, 0x50, 0x22, 0x31, 0xa9, 0x4c, 0x0, 0x7c, 0x2a, 0xd3, 0x61, 0xe2, 0x1, 0xe8, 0x30, 0x71, 0xc7, 0x99, 0x0, 0xf8, 0xe3, 0x4c, 0x5, 0x85, 0x7, 0xa0, 0x82, 0xc2, 0x95, 0x34, 0x2, 0xe0, 0x4b, 0x1a, 0x15, 0x55, 0x16, 0x80, 0x8a, 0x2a, 0x5b, 0xd6, 0xe9, 0x38, 0x33, 0x65, 0xdd, 0xe8, 0x62, 0xe1, 0xaf, 0xb6, 0x2c, 0x63, 0xae, 0x36, 0xe3, 0xcb, 0x55, 0xbf, 0xde, 0x83, 0xf, 0xaf, 0x77, 0xf3, 0x6, 0x83, 0x5a, 0x1c, 0x9b, 0x6d, 0x71, 0x3e, 0x6a, 0xb2, 0xee, 0x4c, 0x93, 0xc5, 0xb5, 0x79, 0x81, 0xd6, 0xe6, 0xf1, 0x8d, 0xe6, 0xf5, 0xd9, 0x68, 0x5e, 0x99, 0x46, 0xd3, 0xa0, 0xd5, 0x35, 0x6a, 0xb6, 0xcd, 0xdb, 0x7d, 0xf3, 0x7, 0x87, 0xf9, 0x93, 0x87, 0x7f, 0x74, 0x5d, 0xd2, 0xf4, 0x82, 0x8f, 0xae, 0x9f, 0x7c, 0x53, 0xfe, 0x1, 0x6f, 0xc7, 0x5c, 0x26, 0x6e, 0x29, 0x2b, 0x15, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon3Name[] = { 0x69, 0x6e, 0x66, 0x6f, 0x2d, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x32, 0x78, 0x00 };
+const unsigned char defaultDialogIcon3Length[] = { 0x31, 0x32, 0x39, 0x33, 0x00 };
+const unsigned char defaultDialogIcon3Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf4, 0xe0, 0x91, 0xf9, 0x0, 0x0, 0x0, 0x93, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7a, 0x79, 0x23, 0x75, 0x0, 0x0, 0x0, 0x30, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xfb, 0x55, 0x5, 0xe4, 0x40, 0xa, 0xb7, 0xd7, 0x62, 0x44, 0xe, 0xc0, 0x94, 0xb2, 0x2b, 0x68, 0x4e, 0xee, 0x39, 0xc6, 0x81, 0x48, 0x21, 0x89, 0x18, 0xe8, 0x33, 0xd0, 0xbd, 0xcb, 0x96, 0x8f, 0x13, 0x8b, 0xf6, 0xe1, 0xa8, 0x83, 0x7c, 0x71, 0x6c, 0x52, 0x26, 0x25, 0x1a, 0x3b, 0x5d, 0x23, 0x0, 0xd6, 0x57, 0x0, 0x0, 0x3, 0xf9, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x97, 0xd7, 0x76, 0xab, 0x40, 0xc, 0x45, 0x35, 0xa1, 0x99, 0x6a, 0x63, 0xc, 0xb8, 0x13, 0xdc, 0x4b, 0x7c, 0xa3, 0xff, 0xff, 0xba, 0xbb, 0x6e, 0x59, 0x78, 0x26, 0x60, 0x24, 0x8a, 0xb3, 0xf2, 0xe0, 0xfd, 0x3c, 0x1e, 0xb, 0x95, 0x73, 0x34, 0xf0, 0xe2, 0xc5, 0x8b, 0x17, 0x2f, 0xda, 0x63, 0xde, 0xae, 0xb, 0x23, 0xc, 0x8d, 0xc5, 0xf5, 0x66, 0xc2, 0xf7, 0xe2, 0x3a, 0x67, 0x7f, 0xa5, 0xa3, 0x84, 0xbe, 0xf2, 0xcf, 0x8e, 0xb, 0xdf, 0x81, 0x66, 0x1c, 0x26, 0x2, 0x2b, 0x11, 0x93, 0x83, 0xa1, 0xc1, 0x53, 0xd1, 0x92, 0xfd, 0x16, 0x6b, 0xd9, 0xee, 0x93, 0xe7, 0xc5, 0x30, 0xdc, 0xcc, 0x91, 0xc1, 0x7c, 0x33, 0x84, 0x67, 0xf0, 0xcb, 0x42, 0x36, 0x56, 0x4, 0x7d, 0xe3, 0x78, 0xd8, 0x8, 0xcf, 0xe9, 0x37, 0xf9, 0x1e, 0x36, 0xc6, 0xeb, 0xaf, 0x10, 0xe6, 0x54, 0x60, 0xb, 0xc4, 0xb4, 0x27, 0x79, 0x8, 0x77, 0xd8, 0x92, 0x5d, 0x8, 0x3d, 0xb0, 0xc1, 0xe, 0x4c, 0xbb, 0x8b, 0x5e, 0xf0, 0x30, 0xc3, 0xfa, 0x3a, 0xc8, 0x8e, 0xb6, 0x7d, 0xcc, 0x82, 0xb5, 0xfe, 0xb0, 0x46, 0x41, 0x47, 0x79, 0x4c, 0x7, 0x58, 0xc5, 0x32, 0x8e, 0x66, 0xa0, 0x30, 0x8b, 0xe2, 0x25, 0x56, 0x31, 0x48, 0xa1, 0x3, 0xc6, 0x18, 0x4b, 0x8, 0xef, 0x94, 0x3e, 0x88, 0xf6, 0xe4, 0x9, 0x2c, 0x31, 0x36, 0xa0, 0x35, 0xa3, 0xf2, 0x7d, 0x96, 0x9d, 0x43, 0xd, 0xb9, 0x6d, 0x95, 0x23, 0x1e, 0xb5, 0x15, 0x7e, 0x1f, 0xbf, 0x32, 0x89, 0x18, 0x8a, 0x39, 0xc1, 0xaf, 0xf8, 0x5a, 0xab, 0xff, 0xb7, 0x4a, 0xe5, 0xbc, 0x0, 0x8b, 0xcb, 0xa0, 0x94, 0xb7, 0x36, 0x11, 0xf8, 0xa8, 0x32, 0x1f, 0x69, 0xec, 0xd8, 0x47, 0x73, 0x54, 0xf1, 0x5b, 0xd4, 0x1f, 0x15, 0x44, 0x6c, 0x36, 0x12, 0xcf, 0x58, 0xa0, 0x42, 0xe3, 0x3e, 0x30, 0x84, 0xda, 0xca, 0x4e, 0x63, 0xf7, 0x52, 0x7, 0x48, 0x18, 0xd, 0xe7, 0x7f, 0xac, 0x56, 0xff, 0x3, 0x1a, 0xf3, 0x31, 0x50, 0x3f, 0x21, 0x6d, 0xa4, 0x7f, 0xea, 0x8f, 0x2d, 0xb7, 0x95, 0x88, 0x5a, 0xea, 0x47, 0x34, 0xb9, 0x44, 0xd5, 0xdf, 0x77, 0xad, 0xe5, 0x1c, 0xbf, 0xab, 0xaa, 0xc, 0x6c, 0xa6, 0x4a, 0xf5, 0x6c, 0x68, 0x8d, 0x2d, 0x5a, 0x39, 0x53, 0x88, 0x32, 0x36, 0x74, 0xc0, 0x46, 0x99, 0x90, 0x39, 0x42, 0x3b, 0x25, 0xff, 0xd0, 0x9, 0xa5, 0xa, 0x3b, 0x93, 0x59, 0x0, 0xbe, 0x86, 0xf1, 0xf5, 0x94, 0x5f, 0x84, 0xa1, 0xe0, 0xb7, 0x6e, 0xfe, 0xe9, 0xfb, 0x9f, 0x39, 0x7f, 0xa0, 0xc4, 0x10, 0x68, 0x3c, 0x79, 0x78, 0xeb, 0xe7, 0x3f, 0xd1, 0x11, 0x11, 0xf5, 0xa4, 0x5e, 0xf, 0x64, 0x49, 0xf1, 0x80, 0xc4, 0x91, 0x3, 0xae, 0xd7, 0xbf, 0x85, 0xf8, 0x7f, 0x6c, 0x51, 0x7f, 0xa3, 0x9c, 0x52, 0xa3, 0x51, 0x2, 0x62, 0xa8, 0x65, 0x59, 0x2c, 0x48, 0x50, 0xcb, 0x1, 0xef, 0xac, 0x48, 0x37, 0x97, 0xfd, 0xaf, 0xbe, 0x69, 0x6f, 0x58, 0x30, 0xab, 0x1f, 0x2b, 0xd9, 0x1b, 0xa9, 0x8d, 0xc2, 0xe2, 0x3b, 0x58, 0x82, 0x5, 0x9, 0xdf, 0x59, 0x2d, 0x62, 0x4, 0xe4, 0x9, 0xd0, 0xd8, 0x67, 0x89, 0xe6, 0xd6, 0x6, 0xc8, 0x3d, 0xbb, 0xc1, 0x3b, 0x17, 0x6a, 0xc4, 0xc7, 0xc5, 0xb0, 0x50, 0x62, 0x71, 0xc1, 0x3b, 0x9b, 0xda, 0x3b, 0xa5, 0x6a, 0x4d, 0x80, 0x62, 0xc4, 0xdf, 0x36, 0xa4, 0x3d, 0x51, 0xd7, 0x78, 0x65, 0xc5, 0x8, 0x48, 0x32, 0xfc, 0x4b, 0xc6, 0xd8, 0x54, 0x91, 0xd7, 0x30, 0x7b, 0xa5, 0x59, 0x68, 0xc2, 0x40, 0xd7, 0x3, 0xd5, 0x62, 0xe8, 0xe6, 0xde, 0xd7, 0x54, 0x60, 0x4b, 0x9b, 0x60, 0x77, 0x5b, 0xdc, 0x3e, 0xae, 0x81, 0x21, 0x89, 0x60, 0xe, 0xbd, 0x92, 0xb, 0x8e, 0x1a, 0x1e, 0x68, 0xd1, 0xa6, 0xa1, 0x15, 0x36, 0xe6, 0xf4, 0xea, 0x9, 0x7a, 0xe6, 0xc4, 0x98, 0x2f, 0x57, 0x4a, 0x53, 0xa, 0x3d, 0x93, 0x4a, 0xe5, 0x35, 0x69, 0x23, 0x5c, 0x2, 0xb, 0xf7, 0xcf, 0x3e, 0xe0, 0x2, 0xb, 0xe9, 0xf5, 0xfe, 0xc8, 0x64, 0xcf, 0x6a, 0x99, 0x68, 0x9c, 0x7f, 0xfb, 0x80, 0x3, 0x1c, 0x62, 0x2c, 0x38, 0x43, 0x35, 0x3e, 0xa1, 0x42, 0xc4, 0x3e, 0x40, 0x10, 0xd1, 0x2f, 0xc5, 0x95, 0xea, 0xaf, 0xfc, 0xa4, 0xae, 0x81, 0xc1, 0x8d, 0x5e, 0xa, 0xf4, 0x7b, 0x9b, 0x0, 0x83, 0x59, 0xb3, 0x78, 0x35, 0x71, 0xb7, 0x3, 0xa8, 0xc4, 0x44, 0xe2, 0x4, 0x77, 0x1f, 0x20, 0xbf, 0xf, 0x4d, 0x22, 0x47, 0xbc, 0x9c, 0xbe, 0x61, 0xc1, 0x1b, 0x30, 0x58, 0x53, 0x19, 0xbb, 0x12, 0xcf, 0x38, 0x32, 0x0, 0xfe, 0x83, 0xf3, 0x5a, 0xdd, 0xd4, 0x58, 0x90, 0x3d, 0x23, 0x80, 0xc, 0xb, 0x16, 0x94, 0x15, 0x1d, 0x9f, 0x11, 0xc0, 0x91, 0xb2, 0xa3, 0x90, 0xf0, 0x62, 0x32, 0x0, 0xbe, 0x23, 0x87, 0x3f, 0x33, 0x80, 0xdf, 0xed, 0x5b, 0xcb, 0xe, 0x82, 0x30, 0x10, 0x6c, 0x2, 0xed, 0x85, 0x44, 0xf, 0x84, 0x28, 0x26, 0x18, 0x4e, 0xdc, 0x14, 0xfe, 0xff, 0xeb, 0x8c, 0x21, 0x5e, 0x1c, 0xc9, 0xd2, 0x4e, 0x65, 0x29, 0xe9, 0x7c, 0xc1, 0x24, 0x4a, 0x77, 0xe7, 0xb1, 0xea, 0x3f, 0x81, 0xfa, 0x9f, 0x50, 0xfd, 0x33, 0x54, 0x7f, 0x88, 0xf0, 0x29, 0xe6, 0x9, 0xc8, 0x4f, 0xb1, 0x3c, 0x8c, 0x78, 0x2, 0x38, 0x8c, 0xd8, 0x71, 0x8c, 0x4, 0xd8, 0x71, 0x8c, 0xb, 0x9, 0x43, 0x40, 0x5e, 0x48, 0xd8, 0x95, 0xc, 0x9, 0xb0, 0x2b, 0x19, 0x2e, 0xa5, 0xc, 0x1, 0x79, 0x29, 0x65, 0xd7, 0x72, 0x24, 0x40, 0xaf, 0xe5, 0x28, 0x4c, 0x8, 0x2, 0xa2, 0x30, 0xa1, 0xa5, 0x19, 0x12, 0xe0, 0xa5, 0x19, 0x8a, 0x53, 0x82, 0x80, 0x24, 0x4e, 0x69, 0x79, 0x8e, 0x4, 0x78, 0x79, 0x8e, 0x6, 0x5, 0x41, 0x40, 0x32, 0x28, 0x78, 0x8b, 0x6, 0x9, 0xf0, 0x16, 0xd, 0x9a, 0x54, 0xc, 0x1, 0xd9, 0xa4, 0xe2, 0x6d, 0x3a, 0x24, 0xc0, 0xdb, 0x74, 0x68, 0x54, 0xf2, 0x4, 0xd0, 0xa8, 0xdc, 0xb7, 0x55, 0x6b, 0xea, 0xf5, 0x66, 0x75, 0x4b, 0x98, 0xd5, 0x91, 0xed, 0xfa, 0x3b, 0x67, 0xd7, 0xf3, 0x81, 0xc5, 0x99, 0x8, 0x2c, 0x88, 0xc8, 0x6, 0x2c, 0x1a, 0xef, 0xc8, 0x86, 0xf, 0xad, 0xaa, 0xb1, 0xef, 0xc7, 0xca, 0x27, 0xb4, 0x4a, 0x21, 0xb6, 0xd3, 0xf, 0x2e, 0xd5, 0xa3, 0x5b, 0xfd, 0xf0, 0x5a, 0x3f, 0xbe, 0xd7, 0x2f, 0x30, 0xfc, 0xa1, 0xc2, 0x71, 0xbb, 0xa4, 0x55, 0x62, 0xc1, 0x1a, 0xcf, 0xe0, 0x57, 0xe3, 0x19, 0xbe, 0x6b, 0x3c, 0xe9, 0x15, 0x99, 0xa2, 0x56, 0xb9, 0x4e, 0x45, 0xac, 0x32, 0xdb, 0x33, 0xb0, 0xcc, 0x96, 0x66, 0x9d, 0x6f, 0x7, 0x85, 0x46, 0x8f, 0x4a, 0xe7, 0x63, 0xb9, 0xd2, 0x99, 0x76, 0xa9, 0x75, 0x9e, 0x4c, 0xe1, 0xb8, 0x1e, 0xa1, 0xd8, 0xac, 0x5f, 0xed, 0xd6, 0x2f, 0xb7, 0xbf, 0x61, 0xbb, 0xc9, 0xb, 0x9d, 0x35, 0xb1, 0x51, 0xf3, 0x7, 0xe, 0xfc, 0x89, 0x47, 0x39, 0xad, 0x40, 0x9, 0x27, 0x1e, 0x87, 0x39, 0x72, 0x99, 0x39, 0xd8, 0x66, 0xf9, 0xcc, 0xa7, 0xb1, 0x85, 0xd9, 0x2, 0xee, 0xf7, 0xa1, 0x93, 0x33, 0xdb, 0xc2, 0xb5, 0x9f, 0x53, 0xaf, 0xd6, 0x99, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x60, 0xbc, 0x0, 0xd9, 0xfc, 0x9e, 0xf5, 0xee, 0xae, 0x37, 0x12, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon4Name[] = { 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x64, 0x61, 0x72, 0x6b, 0x00 };
+const unsigned char defaultDialogIcon4Length[] = { 0x39, 0x34, 0x31, 0x00 };
+const unsigned char defaultDialogIcon4Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x3, 0x0, 0x0, 0x0, 0x9d, 0xb7, 0x81, 0xec, 0x0, 0x0, 0x0, 0x99, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0x1d, 0x4, 0x54, 0x0, 0x0, 0x0, 0x32, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xf9, 0x1c, 0xe6, 0xb1, 0x3b, 0x17, 0xc9, 0xad, 0x6, 0x8f, 0x4e, 0x4b, 0x40, 0xf6, 0xe3, 0xe0, 0xc1, 0x20, 0x11, 0x62, 0x54, 0x34, 0x30, 0x2b, 0x5d, 0xd6, 0xd0, 0x98, 0x76, 0x65, 0xb, 0x9b, 0x92, 0x6d, 0x48, 0x2, 0x9, 0x3, 0x9c, 0xb5, 0x9d, 0xed, 0xe8, 0xbb, 0x78, 0xdc, 0x83, 0x71, 0x9f, 0x76, 0x53, 0x1c, 0x31, 0x0, 0x0, 0x2, 0x91, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x57, 0x6b, 0x7b, 0xaa, 0x30, 0xc, 0x3e, 0xdc, 0x4, 0x41, 0x85, 0x71, 0x13, 0x54, 0x18, 0x53, 0xa7, 0xce, 0xcb, 0xb6, 0xb3, 0xff, 0xff, 0xe3, 0x8e, 0xa4, 0xa5, 0x29, 0x4, 0xe, 0xee, 0xe9, 0xb3, 0x6f, 0xcb, 0x27, 0xdb, 0x92, 0x34, 0x79, 0xf3, 0x26, 0x8d, 0x7f, 0x7e, 0xe5, 0x47, 0x65, 0xaf, 0xa7, 0x5e, 0x5c, 0x14, 0xb1, 0x97, 0xea, 0xfb, 0xef, 0x6b, 0x4f, 0x3c, 0xd7, 0xf6, 0xb5, 0x2f, 0x10, 0xcd, 0xb7, 0x5d, 0x6f, 0xf2, 0x2d, 0x75, 0x6b, 0x13, 0x7c, 0x75, 0x24, 0xd8, 0x58, 0xf, 0xab, 0xcf, 0xcd, 0xfa, 0x6a, 0x22, 0x9a, 0x39, 0x7f, 0x48, 0x3d, 0x3c, 0xa, 0x75, 0x62, 0xe2, 0x18, 0x8e, 0xeb, 0xcf, 0x2, 0x59, 0xc3, 0x58, 0xad, 0xc, 0xd9, 0x5e, 0x30, 0x1b, 0xd3, 0xdf, 0x2e, 0x9a, 0x6f, 0xd, 0xa7, 0xb4, 0x32, 0x3d, 0x8a, 0xf4, 0xcc, 0x2a, 0x1d, 0xa3, 0xd9, 0x5d, 0x6c, 0xff, 0xab, 0xfe, 0xfc, 0x22, 0xe2, 0x4d, 0x74, 0xf9, 0x40, 0x4f, 0x4, 0x2e, 0x2f, 0xcf, 0xc3, 0xfa, 0xaf, 0xe, 0xff, 0xe8, 0x69, 0xd7, 0x13, 0xdb, 0x13, 0x3f, 0x74, 0x5e, 0x7, 0xd, 0xf0, 0xfb, 0x8d, 0x7c, 0xda, 0x77, 0x3a, 0xcd, 0xd, 0xee, 0xc3, 0x60, 0xfc, 0xec, 0x7c, 0xbd, 0x1c, 0xfa, 0x60, 0xb9, 0x66, 0x5f, 0x6c, 0x7, 0xf0, 0x67, 0xf8, 0x99, 0x13, 0x54, 0x28, 0x1c, 0xdb, 0x36, 0x6f, 0x68, 0x70, 0x62, 0x32, 0x24, 0x7b, 0x73, 0x11, 0xfa, 0x4c, 0x5f, 0x60, 0xa4, 0x57, 0xb, 0x8e, 0xfc, 0xdf, 0x50, 0xa0, 0xcc, 0x2c, 0xf8, 0x7d, 0x7c, 0x78, 0x63, 0xfe, 0x8b, 0xfb, 0xd3, 0x15, 0xa6, 0xff, 0xdd, 0x12, 0x56, 0x59, 0x14, 0x6f, 0x3d, 0xfc, 0xd5, 0x0, 0x3f, 0xe1, 0x6e, 0xe6, 0xa3, 0xbe, 0xbc, 0xbf, 0x4, 0x24, 0x35, 0xc2, 0xea, 0x3, 0xf3, 0x2d, 0x17, 0x90, 0x43, 0xd2, 0x50, 0x6c, 0x91, 0xbb, 0x9c, 0x45, 0x7a, 0xe8, 0xd6, 0x9f, 0x6, 0xf9, 0x17, 0xf9, 0x4b, 0x38, 0x21, 0x5c, 0x97, 0x5b, 0x8a, 0x5, 0x59, 0x60, 0x43, 0xeb, 0xd6, 0xe6, 0x6, 0x76, 0x91, 0x3f, 0x27, 0x58, 0x7f, 0xde, 0xd, 0x4e, 0x3f, 0x99, 0x6d, 0xd1, 0x54, 0x76, 0xb0, 0xde, 0x74, 0xfa, 0x47, 0x0, 0x7e, 0x21, 0x75, 0xcf, 0xf5, 0xba, 0x62, 0x8b, 0xaa, 0xfe, 0x7d, 0x16, 0xc0, 0xef, 0x21, 0xda, 0xa0, 0xdd, 0x61, 0x3c, 0x70, 0x33, 0x11, 0xeb, 0x14, 0x32, 0x38, 0x6f, 0xf0, 0x45, 0x9f, 0x31, 0x3c, 0xaf, 0x65, 0xc0, 0x5, 0xa8, 0xf5, 0x76, 0x4e, 0xce, 0xfc, 0x92, 0x10, 0xac, 0x61, 0x78, 0x3a, 0x24, 0xc2, 0x6d, 0xd1, 0xdc, 0x86, 0x2a, 0x91, 0x42, 0x7a, 0xbf, 0xaf, 0x4f, 0x1c, 0xd2, 0x59, 0x17, 0x35, 0x7, 0xf2, 0x22, 0x17, 0x8c, 0xe, 0x49, 0x2f, 0x65, 0x5a, 0xd8, 0xe7, 0x53, 0xc8, 0x33, 0xc, 0x80, 0x5e, 0xa4, 0x98, 0x4b, 0x60, 0xa3, 0x5c, 0xef, 0x29, 0x0, 0x6b, 0xb5, 0x9c, 0x8a, 0xf6, 0xd, 0xa3, 0x59, 0x11, 0xcb, 0x39, 0x7, 0x97, 0xd2, 0x2e, 0x86, 0x5a, 0x46, 0xf9, 0x99, 0x7d, 0x9c, 0x2e, 0xac, 0x1e, 0x52, 0x79, 0x17, 0xee, 0x93, 0x2b, 0x2a, 0x66, 0x18, 0x52, 0x7d, 0x80, 0x8b, 0x90, 0x9f, 0xa1, 0x18, 0x4b, 0x3b, 0x45, 0xbd, 0xb1, 0x8a, 0x88, 0x81, 0x8f, 0x46, 0xff, 0xda, 0xca, 0x7a, 0x4, 0x75, 0x56, 0x3c, 0x60, 0xe0, 0xd8, 0x74, 0x31, 0x38, 0x22, 0x6, 0xc6, 0x43, 0xa8, 0x0, 0x9b, 0x75, 0x89, 0x19, 0x23, 0x21, 0x10, 0x10, 0xa9, 0x1, 0x7, 0xdb, 0x30, 0x1, 0x91, 0xa4, 0x91, 0x18, 0x20, 0x65, 0x43, 0xd2, 0x48, 0x88, 0x44, 0x30, 0x38, 0xe2, 0x9a, 0x12, 0x89, 0x52, 0x99, 0x94, 0x8, 0x92, 0x9e, 0x52, 0x99, 0x16, 0x53, 0x47, 0x76, 0x72, 0x15, 0x91, 0x62, 0x1a, 0x2e, 0x67, 0x94, 0xd8, 0xb6, 0x11, 0x6c, 0x5a, 0xce, 0xb4, 0xa1, 0x10, 0x39, 0xc8, 0xbd, 0x8f, 0x36, 0x14, 0xda, 0xd2, 0xc6, 0x85, 0xb6, 0x34, 0xda, 0x54, 0x51, 0xa2, 0x3c, 0x47, 0x12, 0x92, 0xa6, 0x3a, 0xd8, 0xd6, 0x11, 0x2e, 0xfb, 0x8e, 0x36, 0x42, 0x4b, 0xda, 0xfa, 0xc0, 0xc3, 0x82, 0x72, 0xab, 0xf7, 0x6e, 0xed, 0x7, 0xf6, 0x42, 0x1f, 0x16, 0xfa, 0xb4, 0x51, 0x1e, 0xd0, 0xa7, 0x6d, 0xfc, 0x71, 0x5, 0xb7, 0xf0, 0x32, 0xfa, 0xb8, 0xe, 0x3f, 0xef, 0x28, 0xc9, 0xf5, 0x9a, 0xc8, 0xf7, 0xe3, 0xf3, 0xae, 0x36, 0x60, 0x28, 0x8f, 0x38, 0xe3, 0x43, 0xd6, 0x6c, 0x64, 0xc8, 0x52, 0x1b, 0xf3, 0x14, 0x7, 0x4d, 0xd5, 0x51, 0x57, 0x6d, 0xd8, 0xae, 0x42, 0xc5, 0x71, 0x5f, 0xf1, 0xf, 0x87, 0xe2, 0x5f, 0x1e, 0xc5, 0x3f, 0x5d, 0xbf, 0xf2, 0x93, 0xf2, 0xf, 0x45, 0x13, 0xb6, 0x9d, 0x6, 0x38, 0x84, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon5Name[] = { 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x64, 0x61, 0x72, 0x6b, 0x32, 0x78, 0x00 };
+const unsigned char defaultDialogIcon5Length[] = { 0x31, 0x35, 0x35, 0x37, 0x00 };
+const unsigned char defaultDialogIcon5Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf4, 0xe0, 0x91, 0xf9, 0x0, 0x0, 0x0, 0x9f, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa, 0x77, 0x79, 0xb8, 0x0, 0x0, 0x0, 0x34, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xfb, 0x2, 0xa, 0x40, 0x62, 0xe5, 0x8a, 0x82, 0xe, 0x6, 0xb8, 0x2b, 0x19, 0x44, 0xed, 0xd9, 0x26, 0xb1, 0x94, 0x4e, 0x39, 0xc6, 0xf6, 0xbf, 0x55, 0x48, 0x21, 0x67, 0x33, 0xe0, 0xa8, 0x6c, 0xe8, 0xc2, 0x97, 0x90, 0x14, 0xd4, 0xb5, 0x3b, 0xd1, 0xcb, 0x7b, 0x52, 0xbc, 0x9e, 0xef, 0x71, 0x72, 0x5c, 0x1d, 0xd8, 0xaf, 0x95, 0x64, 0x0, 0x0, 0x4, 0xf1, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x97, 0xdb, 0x96, 0xa2, 0x40, 0xc, 0x45, 0x23, 0x20, 0x20, 0x88, 0x82, 0x22, 0xd8, 0xde, 0xef, 0xed, 0xa5, 0x45, 0xa7, 0x27, 0xff, 0xff, 0x6d, 0xf3, 0x30, 0x6b, 0x69, 0xa, 0x52, 0x1d, 0x40, 0xed, 0x27, 0xf7, 0x6b, 0x61, 0xaa, 0xea, 0x24, 0x27, 0x29, 0xe1, 0xcd, 0x9b, 0x37, 0x6f, 0xde, 0xd4, 0xc7, 0xc, 0xa2, 0xa9, 0x15, 0xc7, 0xd6, 0x34, 0xa, 0x4c, 0xf8, 0x5d, 0x5a, 0x9d, 0x45, 0xff, 0x6c, 0x23, 0xc1, 0x3e, 0xf7, 0x17, 0x9d, 0x16, 0xfc, 0x6, 0x9e, 0xe5, 0x26, 0xd, 0x64, 0x69, 0x24, 0xae, 0xe5, 0xc1, 0x4b, 0x31, 0x46, 0xf3, 0x14, 0x7f, 0x24, 0x9d, 0x8f, 0xc, 0x78, 0x15, 0x6d, 0x67, 0x89, 0x25, 0x58, 0x3a, 0x6d, 0x78, 0x5, 0x7b, 0x1f, 0x4b, 0xe3, 0xf, 0xe0, 0xd9, 0x74, 0x86, 0x58, 0x89, 0x61, 0xe7, 0xb9, 0xe2, 0xf, 0xb1, 0x32, 0xc3, 0xe7, 0x25, 0xc2, 0x74, 0x1a, 0x58, 0x83, 0x86, 0x63, 0xc2, 0x53, 0x88, 0x67, 0x58, 0x93, 0x59, 0xfc, 0xc, 0xe7, 0x39, 0xf8, 0x0, 0xce, 0xc3, 0x9e, 0x6c, 0x7d, 0x6a, 0x15, 0xb6, 0x8f, 0xbd, 0xf5, 0xa9, 0xd9, 0x3c, 0xad, 0x7b, 0x47, 0x5b, 0x9b, 0xa3, 0xde, 0x83, 0xed, 0x71, 0xbc, 0x43, 0x8e, 0x83, 0x3b, 0x8, 0xc, 0x45, 0xa7, 0x60, 0xe0, 0x1e, 0x90, 0x63, 0x37, 0x86, 0x7, 0xb0, 0xba, 0xcc, 0xcd, 0x57, 0x57, 0x4d, 0xcc, 0xf1, 0x75, 0xc5, 0x28, 0xd1, 0xb5, 0xa0, 0x36, 0x93, 0x62, 0x3c, 0xbf, 0xb9, 0x85, 0x1f, 0xd8, 0x36, 0xfd, 0xe2, 0x89, 0x27, 0x75, 0xc7, 0x4e, 0x1f, 0xf3, 0x24, 0x3, 0x10, 0xd9, 0x27, 0x98, 0xa7, 0xef, 0xd5, 0xda, 0xbf, 0x50, 0x7e, 0xe1, 0x17, 0x94, 0xe2, 0x2b, 0xc4, 0x1c, 0x9f, 0x75, 0x4e, 0x90, 0xbf, 0xff, 0x72, 0x52, 0x3a, 0x8a, 0x37, 0x59, 0xe6, 0x35, 0xa8, 0x91, 0xff, 0x5c, 0x22, 0x5d, 0x13, 0x2a, 0x60, 0xba, 0xb9, 0xf2, 0xa9, 0x5c, 0x7, 0x96, 0x1a, 0xa0, 0xdb, 0xa9, 0x3c, 0xbd, 0xba, 0xea, 0x5, 0xac, 0x8a, 0xfe, 0x57, 0x7f, 0x1e, 0x46, 0x50, 0x99, 0x28, 0x54, 0xaf, 0x30, 0xae, 0xd4, 0xff, 0x76, 0xaa, 0xf7, 0x5a, 0xb5, 0x9a, 0xa8, 0xea, 0xc8, 0x5d, 0x85, 0x20, 0x86, 0x6a, 0x80, 0x8d, 0xa1, 0xf1, 0x7c, 0x36, 0xc, 0xd3, 0x74, 0xb6, 0x9a, 0x2f, 0x2, 0x3e, 0xcc, 0x6, 0x29, 0x3d, 0x3, 0xca, 0xe2, 0x28, 0xd9, 0x6b, 0xb2, 0xc1, 0x9b, 0x4a, 0xd7, 0x3b, 0x2e, 0x58, 0x8b, 0x34, 0x95, 0x4a, 0x72, 0xa0, 0x24, 0x31, 0x52, 0xd8, 0xfd, 0x47, 0xbb, 0xc2, 0xe8, 0xfd, 0x60, 0x4f, 0x80, 0x94, 0xb8, 0xa4, 0x85, 0x66, 0x8a, 0xfe, 0x5c, 0x76, 0x7b, 0xc8, 0xb0, 0xe2, 0x12, 0xb1, 0x51, 0xe, 0x69, 0x56, 0x4f, 0x80, 0x6f, 0x8, 0x23, 0x92, 0x30, 0x9b, 0x32, 0xa9, 0xf2, 0x2b, 0x27, 0xa1, 0x4d, 0xf3, 0x16, 0x32, 0xa5, 0x3b, 0xed, 0xa2, 0x86, 0xb4, 0xc3, 0xa8, 0x15, 0xd2, 0x7a, 0x6a, 0x83, 0xcc, 0x90, 0x9a, 0x97, 0xf1, 0x7f, 0x60, 0xa3, 0x96, 0xb, 0xf3, 0x7d, 0x44, 0xcf, 0x3b, 0x4, 0x91, 0xe, 0x3d, 0x30, 0x73, 0x23, 0xf3, 0x88, 0x3f, 0x10, 0x6e, 0x99, 0x88, 0x54, 0x52, 0xab, 0x92, 0x0, 0xae, 0x30, 0xa3, 0x18, 0xe6, 0x50, 0xc4, 0xad, 0x22, 0xc1, 0x9e, 0xce, 0x3f, 0x93, 0x11, 0x54, 0x7a, 0xa1, 0xf, 0x18, 0xd1, 0x96, 0xec, 0x3a, 0x8f, 0x2f, 0x4c, 0xb0, 0x9c, 0x1, 0xd3, 0x24, 0x49, 0x73, 0x66, 0x84, 0x22, 0xb, 0xfa, 0x34, 0x10, 0x2c, 0x40, 0xf3, 0xe9, 0x31, 0xe, 0x40, 0xca, 0xa6, 0x6d, 0x0, 0x18, 0xed, 0xd, 0x52, 0x98, 0xba, 0xf1, 0xa8, 0x13, 0xda, 0xa5, 0x7b, 0xc0, 0x97, 0xb0, 0x3e, 0xbb, 0x15, 0x94, 0x45, 0x5b, 0xd7, 0x9a, 0x7b, 0x23, 0x95, 0xed, 0x5, 0x6, 0xc9, 0x56, 0x2, 0xc, 0x21, 0xb1, 0x68, 0x40, 0xac, 0x49, 0xac, 0xb6, 0xe4, 0x66, 0xe, 0x79, 0x27, 0xda, 0x6, 0xe8, 0x19, 0x9, 0xd5, 0x12, 0x91, 0xf5, 0x6f, 0x20, 0x7c, 0xd3, 0x1f, 0xa, 0xb5, 0x3d, 0x2, 0x3d, 0x73, 0xd2, 0x83, 0x81, 0xe1, 0x43, 0xbb, 0xee, 0xb, 0xaf, 0x2f, 0x9f, 0x3a, 0x55, 0x8b, 0x97, 0xa, 0x43, 0xf0, 0xf, 0xde, 0xb8, 0x6a, 0x57, 0x5c, 0x61, 0x2c, 0xa6, 0x1e, 0xe8, 0xb0, 0x48, 0x13, 0xdc, 0xa, 0x35, 0xba, 0xd7, 0x6a, 0x9c, 0x1, 0xc3, 0xb6, 0x51, 0xa6, 0x1b, 0xba, 0xd4, 0xce, 0x1c, 0xd9, 0xfd, 0x80, 0x26, 0x28, 0xb4, 0x24, 0xa7, 0xaf, 0x58, 0x89, 0xf4, 0xb5, 0x7a, 0x15, 0x4e, 0xb8, 0xd3, 0x37, 0x90, 0x39, 0x8, 0xd9, 0x4b, 0x40, 0x43, 0x8b, 0xc8, 0x34, 0x16, 0x5c, 0x92, 0xe9, 0x73, 0xbc, 0x1, 0x8e, 0x31, 0x12, 0xf5, 0xe4, 0x41, 0x78, 0x10, 0x3a, 0xf5, 0xe5, 0x2f, 0xa8, 0xac, 0xf1, 0xc6, 0x9, 0x58, 0xe, 0xb4, 0x59, 0xf2, 0x2c, 0xe4, 0x34, 0x5, 0x87, 0xff, 0xfb, 0xc7, 0xf9, 0xc, 0xa4, 0x24, 0xbc, 0x58, 0x60, 0xb, 0xf9, 0xdf, 0xe0, 0x40, 0xeb, 0xd4, 0xd3, 0xea, 0xb2, 0xcb, 0xf2, 0xf7, 0x37, 0x8e, 0xb2, 0xc0, 0x3, 0xf9, 0x9f, 0xe2, 0x19, 0x6f, 0x4, 0x50, 0x5, 0x33, 0xc3, 0x3b, 0x67, 0x9d, 0x78, 0xf2, 0x27, 0xf6, 0xfd, 0x16, 0x6, 0x94, 0x80, 0xce, 0x22, 0xd9, 0xe5, 0xc6, 0xbd, 0xc4, 0x6d, 0xcd, 0x3d, 0x90, 0xf9, 0x42, 0xc0, 0xdc, 0x5f, 0xe7, 0x21, 0x52, 0x7c, 0x0, 0xf1, 0x7e, 0x68, 0x4a, 0x1a, 0x1d, 0xa1, 0x14, 0xad, 0xac, 0x81, 0x79, 0xa6, 0xa0, 0xe3, 0x28, 0x64, 0x98, 0x8e, 0xba, 0x5e, 0xb9, 0xfd, 0x67, 0xf2, 0x9b, 0x90, 0x7f, 0x4c, 0x45, 0xc0, 0x31, 0x55, 0x1f, 0x15, 0x32, 0x19, 0x16, 0x68, 0x44, 0xa0, 0x65, 0x2d, 0xc9, 0x64, 0x29, 0xbd, 0x44, 0xc6, 0x2c, 0xea, 0x9f, 0x8e, 0x40, 0xcf, 0x49, 0x2a, 0xd4, 0x58, 0x99, 0xc5, 0x32, 0xfb, 0xe2, 0xfe, 0x4c, 0x60, 0xb6, 0x5b, 0xc7, 0x4f, 0x39, 0xc0, 0x7, 0xe6, 0xb8, 0xfc, 0x6b, 0xdf, 0x8e, 0x5a, 0x12, 0x6, 0xa3, 0x30, 0x8e, 0x4f, 0x93, 0x5, 0x61, 0x8, 0xda, 0x8, 0x86, 0x91, 0xa0, 0x92, 0x17, 0x51, 0x24, 0x7d, 0xff, 0xcf, 0x16, 0xc6, 0x18, 0xc1, 0x5e, 0xfb, 0xb9, 0x8e, 0x31, 0x27, 0xfb, 0x5f, 0x7b, 0x71, 0x64, 0x7b, 0xcf, 0x7b, 0xce, 0xf3, 0x3c, 0xdb, 0x65, 0x81, 0x2, 0xf4, 0x8, 0x5c, 0xc0, 0xec, 0xd8, 0x1, 0xf0, 0x23, 0xf0, 0x4b, 0xe8, 0x2, 0x46, 0x6f, 0x5b, 0xfc, 0x1e, 0x2f, 0x21, 0x8e, 0x21, 0xa, 0x78, 0x7c, 0xc6, 0xdf, 0xc7, 0x31, 0x44, 0x23, 0x62, 0x1, 0x1b, 0x8, 0x50, 0x68, 0x44, 0x6a, 0xc5, 0x2e, 0xa0, 0x21, 0xcf, 0xa8, 0x15, 0xfb, 0x32, 0xa, 0x15, 0xe0, 0xcb, 0xc8, 0xd7, 0x71, 0xa8, 0x0, 0x5f, 0xc7, 0x1e, 0x48, 0x42, 0x5, 0x78, 0x20, 0xf1, 0x48, 0x16, 0x2b, 0xc0, 0x23, 0x99, 0x87, 0x52, 0xf3, 0xfa, 0xcb, 0x22, 0xed, 0xa1, 0xd4, 0x63, 0x39, 0x99, 0x62, 0xe7, 0xc7, 0x58, 0xce, 0xc5, 0x84, 0x8c, 0x67, 0x75, 0x13, 0x6e, 0x1c, 0x1a, 0x2f, 0x26, 0x5e, 0xcd, 0xcc, 0x12, 0x77, 0x17, 0x56, 0x33, 0x2e, 0xa7, 0x66, 0xd, 0x3f, 0x6, 0xcb, 0x29, 0xd7, 0x73, 0x53, 0xce, 0xf3, 0x7c, 0x5e, 0x66, 0x0, 0xeb, 0x39, 0x4, 0x8a, 0x0, 0x10, 0x28, 0x2c, 0xd1, 0x84, 0xb0, 0x44, 0x63, 0x91, 0x2a, 0x86, 0x45, 0x2a, 0xcb, 0x74, 0x31, 0x2c, 0xd3, 0x59, 0xa8, 0xc, 0x61, 0xa1, 0xd2, 0x52, 0xad, 0xd8, 0xee, 0x17, 0x8b, 0xbd, 0xe, 0xac, 0xa5, 0xda, 0x9f, 0x14, 0x69, 0xb1, 0x3a, 0x4d, 0x99, 0x7f, 0x3f, 0x54, 0x9c, 0x43, 0x8b, 0xd5, 0x96, 0xeb, 0xd3, 0x3c, 0x8d, 0xaa, 0xa6, 0x85, 0x79, 0x10, 0x72, 0x3d, 0xc, 0xb, 0xdc, 0x6f, 0xbe, 0x3b, 0x6d, 0x58, 0xd8, 0xb2, 0x49, 0x73, 0xef, 0x1, 0xca, 0x96, 0x8d, 0x4d, 0x2b, 0x74, 0x2d, 0xf4, 0x16, 0x9b, 0x56, 0xb6, 0xed, 0x62, 0xf3, 0x80, 0x6d, 0x3b, 0x1b, 0x97, 0xc7, 0x18, 0xbf, 0xd7, 0x4b, 0x61, 0x5d, 0xa5, 0x8c, 0xcb, 0x88, 0x75, 0xdb, 0x64, 0x89, 0x86, 0x1, 0xeb, 0x36, 0x62, 0x5e, 0x57, 0xac, 0xb1, 0x49, 0xc2, 0xbc, 0x8e, 0xd8, 0xf7, 0x15, 0xf, 0x87, 0x79, 0xa0, 0xfe, 0x5f, 0xb4, 0xef, 0xfb, 0x13, 0x60, 0xe8, 0x3e, 0xc2, 0xd1, 0x7d, 0x88, 0xa5, 0xfb, 0x18, 0x4f, 0x22, 0xc8, 0xb4, 0x3a, 0x3d, 0xc8, 0xb4, 0x6a, 0x4, 0x99, 0xba, 0x8e, 0x72, 0x8d, 0xcf, 0x15, 0x66, 0xdb, 0xfd, 0x29, 0xcc, 0xb6, 0xb9, 0xeb, 0x67, 0x9c, 0xaf, 0x7d, 0xa0, 0xf1, 0x23, 0x1d, 0x68, 0xec, 0x73, 0xa4, 0xb3, 0x4e, 0xec, 0x4, 0x42, 0xad, 0x7d, 0x8f, 0xf5, 0x76, 0x1f, 0x6c, 0xbe, 0x80, 0x68, 0x77, 0xf7, 0xe1, 0xf6, 0x3, 0x93, 0xb6, 0xf1, 0xfe, 0x49, 0x76, 0x6e, 0x8a, 0xba, 0x35, 0x9b, 0x97, 0x22, 0xfb, 0xf, 0xa6, 0xb7, 0xf9, 0xe7, 0x9, 0xe4, 0xc9, 0x4f, 0x3c, 0xae, 0xe2, 0x23, 0x97, 0xb, 0xf8, 0xcc, 0xa7, 0xe2, 0x26, 0xfd, 0xa1, 0x53, 0x9b, 0x53, 0xd7, 0xf3, 0x4f, 0xbd, 0x6, 0x6, 0x6, 0x6, 0xae, 0x8a, 0x2f, 0x2e, 0x60, 0x3a, 0x41, 0x7b, 0x3f, 0x8e, 0x53, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon6Name[] = { 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x00 };
+const unsigned char defaultDialogIcon6Length[] = { 0x39, 0x33, 0x30, 0x00 };
+const unsigned char defaultDialogIcon6Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x3, 0x0, 0x0, 0x0, 0x9d, 0xb7, 0x81, 0xec, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3a, 0xb, 0xcd, 0x2a, 0x0, 0x0, 0x0, 0x31, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xf9, 0x1c, 0xe6, 0xb1, 0x4d, 0x3b, 0x17, 0xc9, 0xad, 0x49, 0x6, 0x8f, 0x40, 0xf6, 0xe3, 0xe0, 0xc1, 0x20, 0x11, 0x62, 0x54, 0x34, 0x30, 0x2b, 0x5d, 0xd6, 0xd0, 0x98, 0x76, 0x65, 0xb, 0x9b, 0x92, 0x6d, 0x2, 0x9, 0x3, 0x9c, 0xb5, 0x9d, 0xed, 0xe8, 0xbb, 0x79, 0xdc, 0x83, 0x71, 0x9f, 0x7a, 0xba, 0x9d, 0xd4, 0x0, 0x0, 0x2, 0x8a, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xec, 0x53, 0xdb, 0x62, 0x82, 0x30, 0xc, 0x5d, 0xcb, 0x5d, 0x51, 0x10, 0x10, 0x11, 0x1, 0x2f, 0xa8, 0x88, 0x9b, 0x3a, 0xfe, 0xff, 0xe7, 0xa6, 0x69, 0x9, 0x2d, 0x97, 0xa9, 0xf, 0x7b, 0x5b, 0x9e, 0x48, 0xd2, 0x13, 0x92, 0x73, 0x92, 0x8f, 0x7f, 0xfb, 0x53, 0xcb, 0x69, 0xa8, 0x5, 0x69, 0x1a, 0x68, 0x21, 0xcd, 0xdf, 0x47, 0x1b, 0x9a, 0x6d, 0x3a, 0xa4, 0x2, 0x23, 0x8e, 0x69, 0x6b, 0xc6, 0x5b, 0x70, 0x3d, 0x71, 0xab, 0x96, 0xb9, 0x89, 0xfe, 0x32, 0x7c, 0xa2, 0x92, 0xaa, 0xc7, 0x88, 0x3a, 0x79, 0x9, 0xee, 0x1d, 0x11, 0xde, 0x29, 0x71, 0xf4, 0x9e, 0xe3, 0x35, 0x57, 0x44, 0x28, 0x71, 0xac, 0x10, 0x71, 0x10, 0xed, 0x19, 0x7e, 0x37, 0xad, 0xdf, 0x2a, 0x56, 0xa6, 0x47, 0xd4, 0xf7, 0x69, 0xa4, 0x67, 0x96, 0x52, 0x47, 0xa7, 0xbb, 0x5f, 0xe1, 0xeb, 0x2d, 0xce, 0x3b, 0xa7, 0x62, 0x82, 0xce, 0x91, 0x97, 0xed, 0x7a, 0x18, 0xbf, 0xb7, 0xf8, 0xa3, 0xd9, 0xa8, 0x9b, 0x1c, 0xcd, 0x78, 0xd2, 0xda, 0xf, 0x16, 0xd8, 0xf2, 0xe6, 0x97, 0xe3, 0xbe, 0xec, 0x78, 0xa9, 0xf0, 0x1e, 0x6, 0xe7, 0x67, 0xf9, 0xd5, 0x62, 0xe8, 0xc1, 0x62, 0xc5, 0x5e, 0xc, 0xf0, 0x30, 0x62, 0xfc, 0xa9, 0x46, 0x3, 0x48, 0x2d, 0xd3, 0x54, 0xbf, 0x9a, 0x82, 0x86, 0xca, 0x98, 0x1c, 0xf5, 0xea, 0xef, 0x30, 0x3c, 0x72, 0x44, 0x37, 0x53, 0xce, 0xfc, 0xb7, 0x87, 0x2c, 0xb3, 0xa, 0x4e, 0xdf, 0x3e, 0x9c, 0x58, 0xff, 0xf8, 0xff, 0x30, 0xae, 0xd0, 0x2e, 0x3a, 0x56, 0x65, 0x53, 0x9c, 0x7a, 0xf6, 0x97, 0x0, 0x7f, 0xd8, 0x6e, 0xe4, 0x20, 0x5c, 0x8a, 0x2f, 0x80, 0x49, 0xd2, 0xd9, 0xea, 0x3, 0xeb, 0x6d, 0x89, 0x94, 0x33, 0xd1, 0xd0, 0x4c, 0xd4, 0x6e, 0xc9, 0x26, 0x3d, 0xb4, 0xef, 0x8f, 0x80, 0xfe, 0xa8, 0xdf, 0x9c, 0x2f, 0x84, 0x6d, 0xf3, 0x4a, 0x1, 0x2e, 0xb, 0x4, 0x48, 0xfb, 0x36, 0x13, 0x88, 0x36, 0xec, 0x16, 0xe0, 0xdf, 0xee, 0x5, 0xc7, 0x37, 0x56, 0x3b, 0x47, 0xb5, 0xc0, 0x4f, 0x64, 0xbc, 0xe1, 0x42, 0x5f, 0xe8, 0xd3, 0xf2, 0xe1, 0x6f, 0x98, 0xb3, 0x79, 0x7c, 0x97, 0x48, 0x7c, 0xe, 0xd3, 0xba, 0x86, 0x7c, 0x83, 0xd0, 0xe6, 0x1c, 0xfd, 0x10, 0x14, 0x9c, 0x20, 0xbf, 0xd8, 0x73, 0x33, 0x9e, 0x7c, 0x97, 0x36, 0x50, 0x4d, 0x65, 0x4d, 0x4a, 0xfe, 0x13, 0xf, 0xaa, 0x35, 0xe3, 0x51, 0x10, 0xc2, 0x96, 0xd6, 0xdc, 0x84, 0x2b, 0x11, 0x46, 0xba, 0xdc, 0xfd, 0x62, 0xcc, 0x87, 0x6e, 0xb3, 0x66, 0x81, 0x2e, 0xe2, 0xc1, 0x50, 0x10, 0x3d, 0x13, 0xd7, 0xc2, 0x2c, 0xb, 0x8f, 0x2b, 0xc, 0x84, 0x9e, 0x85, 0x99, 0x33, 0xd8, 0x46, 0xf1, 0xde, 0x43, 0x20, 0x56, 0x97, 0x9a, 0xf2, 0xf3, 0x7a, 0xa3, 0xd9, 0x11, 0x8b, 0x9a, 0x43, 0x4b, 0x61, 0x9b, 0x43, 0x12, 0xa1, 0x8f, 0x16, 0x5d, 0x8b, 0x33, 0xbb, 0x87, 0x50, 0x8c, 0x92, 0x36, 0x8b, 0x1, 0x72, 0xd8, 0xc2, 0x3, 0x5d, 0xb8, 0xfc, 0x32, 0x8b, 0x81, 0x10, 0x49, 0x1f, 0x81, 0xd8, 0xef, 0x14, 0xb8, 0xd6, 0xf8, 0x4f, 0x49, 0x75, 0x1f, 0xee, 0x2c, 0x7d, 0xa1, 0xc0, 0x4f, 0xfb, 0x65, 0xaf, 0x2, 0x20, 0xc, 0xc4, 0xe0, 0xb5, 0xae, 0xfe, 0xc, 0x22, 0x88, 0x8b, 0xe, 0xda, 0xf7, 0x7f, 0x3e, 0xb5, 0x20, 0xd1, 0x7e, 0x48, 0x87, 0xe0, 0x66, 0x36, 0x15, 0x5a, 0x9b, 0xde, 0x25, 0xb9, 0xe5, 0x52, 0xb1, 0xf4, 0x9, 0xb, 0x94, 0x8f, 0x30, 0x25, 0x6e, 0x86, 0x51, 0x37, 0x86, 0x23, 0x80, 0x44, 0x2e, 0x50, 0x49, 0x86, 0x41, 0x22, 0xae, 0x11, 0xb, 0xa0, 0x6d, 0x70, 0x8d, 0x28, 0x24, 0x70, 0xb0, 0xe8, 0x99, 0x85, 0xc4, 0x52, 0x46, 0x8b, 0xa8, 0xe8, 0x51, 0xca, 0x6c, 0x26, 0x8, 0xb5, 0xba, 0x48, 0x40, 0x33, 0xb1, 0x9d, 0x85, 0x36, 0x4, 0x91, 0xcd, 0x76, 0xa6, 0xa0, 0x0, 0x73, 0xa6, 0x7d, 0x10, 0x14, 0x48, 0x5a, 0x11, 0x94, 0x34, 0x8a, 0xaa, 0xd0, 0xf4, 0x7d, 0x5e, 0x9f, 0x14, 0x55, 0xca, 0xba, 0xe8, 0xa, 0x7, 0xdb, 0x19, 0xb5, 0x90, 0x75, 0x1a, 0x8b, 0x10, 0xcf, 0x77, 0xf1, 0x69, 0xb0, 0x2b, 0x8d, 0x85, 0xd6, 0xc6, 0x3a, 0x80, 0xb5, 0x15, 0xcc, 0x55, 0xbf, 0xa5, 0xcd, 0x60, 0xae, 0xef, 0xf6, 0x2e, 0x74, 0xdb, 0xd6, 0xdd, 0xf7, 0x97, 0xbd, 0x7b, 0x1, 0xc3, 0x8d, 0x38, 0x76, 0xc8, 0x72, 0x63, 0x9e, 0x19, 0x34, 0xdd, 0xa8, 0xeb, 0x85, 0xed, 0xa9, 0x36, 0xe3, 0xbe, 0x39, 0x70, 0x98, 0x23, 0x8f, 0x39, 0x74, 0xfd, 0xf8, 0x12, 0x3b, 0xca, 0x8, 0xb2, 0x9d, 0xe6, 0x23, 0xa2, 0x27, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon7Name[] = { 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x32, 0x78, 0x00 };
+const unsigned char defaultDialogIcon7Length[] = { 0x31, 0x35, 0x33, 0x38, 0x00 };
+const unsigned char defaultDialogIcon7Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf4, 0xe0, 0x91, 0xf9, 0x0, 0x0, 0x0, 0x9c, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf8, 0x8d, 0x4c, 0xde, 0x0, 0x0, 0x0, 0x33, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xfb, 0x42, 0x2, 0xa, 0x62, 0xb7, 0x82, 0xe6, 0x95, 0xe, 0x5, 0xb2, 0x19, 0x2b, 0xef, 0xe2, 0x3b, 0xd9, 0x4e, 0xc6, 0xf6, 0xbe, 0x55, 0x48, 0x91, 0x67, 0x33, 0x6c, 0x27, 0x23, 0xc2, 0xba, 0x20, 0x14, 0xd4, 0xa8, 0x89, 0x71, 0x38, 0xea, 0xd1, 0xcb, 0x8c, 0x7b, 0x52, 0x9c, 0xde, 0xa3, 0x88, 0x5c, 0x25, 0x3a, 0xec, 0xc2, 0x0, 0x0, 0x4, 0xe2, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x97, 0xdb, 0x76, 0xb2, 0x30, 0x10, 0x85, 0x87, 0x70, 0x52, 0x11, 0x1, 0x1, 0x6b, 0x55, 0xea, 0x59, 0xeb, 0xa1, 0x6a, 0x3b, 0xef, 0xff, 0x6e, 0xff, 0xc5, 0xbf, 0x96, 0x24, 0x30, 0x61, 0x0, 0x6d, 0xaf, 0xfc, 0x6e, 0x61, 0x85, 0x64, 0xcf, 0xec, 0xd9, 0x1, 0x5e, 0xbc, 0x78, 0xf1, 0xe2, 0x45, 0x7b, 0x2c, 0x3f, 0x9b, 0x8a, 0x38, 0x16, 0xd3, 0xcc, 0xb7, 0xe0, 0x6f, 0xe9, 0x89, 0x59, 0x77, 0x11, 0xa2, 0x44, 0xb8, 0xe8, 0xce, 0x44, 0xf, 0xfe, 0x2, 0x4f, 0xb8, 0x89, 0x81, 0x24, 0x46, 0xe2, 0xa, 0xf, 0x7e, 0x15, 0x73, 0xbc, 0x49, 0xb1, 0x92, 0x74, 0x33, 0x36, 0xe1, 0xb7, 0x18, 0x9c, 0x3a, 0x58, 0x83, 0xce, 0x69, 0x0, 0xbf, 0x41, 0xe4, 0x60, 0x6d, 0x9c, 0x8, 0x9e, 0x8d, 0x18, 0x61, 0x23, 0x46, 0xe2, 0xb9, 0xe2, 0x8f, 0xb0, 0x31, 0xa3, 0xe7, 0x15, 0xc2, 0xda, 0x1a, 0xd8, 0x2, 0x63, 0x6b, 0xc1, 0x53, 0x88, 0x43, 0x6c, 0x49, 0x18, 0x3f, 0xc3, 0x79, 0x27, 0x7c, 0x80, 0xed, 0xc3, 0x9e, 0xec, 0xf5, 0xb5, 0xa, 0x87, 0xc7, 0x7e, 0xf7, 0x6c, 0xdb, 0xe7, 0x6e, 0xff, 0x18, 0x6a, 0x6b, 0xd4, 0x7f, 0x70, 0x3c, 0x7e, 0xac, 0x91, 0xe2, 0xe0, 0xee, 0x7d, 0x53, 0xd1, 0xc9, 0xdf, 0xbb, 0x7, 0xa4, 0x58, 0x7f, 0x3c, 0x64, 0xbe, 0x4f, 0xe2, 0xe4, 0xf3, 0xdd, 0x12, 0x48, 0x96, 0xbb, 0x39, 0xa1, 0xc4, 0xa7, 0x80, 0xd6, 0x4c, 0xca, 0xeb, 0x39, 0xf6, 0xa, 0x2a, 0x58, 0xd9, 0x4e, 0x79, 0xc7, 0x93, 0xb6, 0xb1, 0xd3, 0xc5, 0x22, 0x49, 0x4, 0x2c, 0x51, 0x82, 0x45, 0xde, 0xbd, 0x56, 0xdf, 0x2f, 0x9d, 0x65, 0xf8, 0x5, 0xb5, 0xf8, 0x1a, 0x96, 0x74, 0x6b, 0xb3, 0x83, 0xe2, 0xf9, 0x3b, 0x93, 0xda, 0xab, 0x78, 0x93, 0x4e, 0x51, 0x83, 0x16, 0xf5, 0x2f, 0x14, 0xd2, 0xb5, 0xa0, 0x1, 0x96, 0x6b, 0xa0, 0x42, 0xe3, 0x3e, 0x10, 0xea, 0x2, 0x81, 0x68, 0xbc, 0x40, 0xa0, 0x1e, 0x40, 0x34, 0xf4, 0xbf, 0xea, 0xbf, 0x61, 0xb, 0x33, 0x7f, 0xc, 0x55, 0x37, 0x2e, 0x1b, 0xcd, 0xbf, 0xb5, 0xda, 0x43, 0xbd, 0x56, 0x43, 0xd4, 0x51, 0x27, 0x52, 0x83, 0x45, 0x4c, 0x75, 0xfe, 0x9e, 0x4c, 0x8d, 0xe7, 0x6f, 0xa3, 0x61, 0x9a, 0x5e, 0xe7, 0x9b, 0x99, 0x4f, 0x2f, 0xf3, 0xae, 0x4e, 0xe5, 0xfa, 0xb9, 0xb0, 0x55, 0xaa, 0x67, 0x93, 0x8b, 0xdb, 0xca, 0xd4, 0x3b, 0xce, 0x48, 0x8b, 0xd8, 0x86, 0x92, 0x4c, 0xb5, 0xf3, 0x17, 0x65, 0xc8, 0xef, 0x8f, 0x4b, 0x19, 0x71, 0x7d, 0x23, 0x77, 0x80, 0x32, 0x71, 0x4d, 0xb, 0x85, 0x94, 0x83, 0xf9, 0x88, 0x9c, 0x53, 0x85, 0x50, 0xaa, 0x10, 0x5a, 0xcd, 0xb, 0xe0, 0x98, 0xb5, 0x23, 0x12, 0xc3, 0x29, 0x51, 0x2a, 0xa5, 0x13, 0xbf, 0xa1, 0x6, 0x3, 0x43, 0xf6, 0x1f, 0xd1, 0xba, 0xd3, 0x0, 0x35, 0xa4, 0x82, 0x50, 0x4b, 0x76, 0xa3, 0x31, 0x0, 0x9e, 0x11, 0xe6, 0x4, 0x84, 0xff, 0xfd, 0xe, 0x6a, 0x9, 0x32, 0x42, 0x2f, 0x79, 0xbf, 0x23, 0x60, 0x11, 0xcc, 0xf8, 0xb2, 0x8e, 0x58, 0xc1, 0x70, 0xc5, 0xc, 0x55, 0xd1, 0x48, 0x0, 0x97, 0xc9, 0x28, 0x82, 0xd, 0x94, 0x71, 0x9b, 0x48, 0x10, 0xc9, 0xf9, 0x47, 0x34, 0x6d, 0x66, 0x60, 0x35, 0x7b, 0x42, 0x34, 0x79, 0xae, 0x47, 0x50, 0x8d, 0xc3, 0x24, 0x58, 0xc1, 0x80, 0x69, 0x92, 0xa4, 0x5, 0x33, 0x42, 0x99, 0x99, 0x6c, 0x2b, 0xc6, 0x2, 0x72, 0x3d, 0x3d, 0xc2, 0x1, 0x6a, 0xca, 0xf, 0x4c, 0x0, 0x73, 0x20, 0x79, 0x9d, 0xae, 0xb2, 0x27, 0x3b, 0xa1, 0xda, 0x8, 0x27, 0xcc, 0xa1, 0xee, 0x3f, 0xdf, 0xf2, 0xec, 0xbb, 0x7f, 0x4a, 0x5c, 0x31, 0xe7, 0x42, 0xdd, 0x91, 0xe4, 0x64, 0xa9, 0x4c, 0x21, 0xc9, 0x62, 0x9, 0x10, 0x48, 0x47, 0x9, 0x7c, 0xc9, 0x9a, 0x81, 0xd4, 0x39, 0x54, 0xe6, 0x24, 0xc4, 0x73, 0x8a, 0x31, 0xd3, 0x2d, 0x19, 0xe6, 0xfc, 0x80, 0xc4, 0x8f, 0xdc, 0x86, 0x4c, 0x6f, 0x8f, 0x41, 0xcf, 0x86, 0x69, 0x96, 0x37, 0xed, 0xf3, 0x5, 0x73, 0xfb, 0x72, 0x64, 0xa7, 0x6a, 0xf1, 0x52, 0x26, 0x4, 0x77, 0x78, 0x67, 0xa7, 0x7d, 0xe2, 0x32, 0xb1, 0x98, 0x7a, 0x75, 0xa6, 0xa0, 0xb1, 0x62, 0x7a, 0x34, 0xd2, 0x6a, 0x7c, 0x1, 0x82, 0x95, 0x51, 0x67, 0x1a, 0xba, 0xb2, 0x9d, 0x29, 0x6e, 0xf9, 0x6, 0xad, 0x42, 0xe6, 0x48, 0x37, 0x1f, 0xa0, 0x98, 0x93, 0x12, 0xe9, 0x7b, 0x75, 0xc7, 0xec, 0x70, 0xad, 0x1f, 0x20, 0x1b, 0x60, 0xaa, 0x97, 0x80, 0x86, 0x9e, 0x24, 0xd3, 0x92, 0x76, 0x89, 0x56, 0x67, 0x9b, 0xfb, 0x9, 0x59, 0xa2, 0xa4, 0x1e, 0xdf, 0x2, 0x7, 0xc8, 0xa1, 0x9a, 0x3d, 0x58, 0xea, 0x33, 0xea, 0xc, 0x24, 0x7, 0xbe, 0x9, 0x66, 0x7c, 0x99, 0xfc, 0xff, 0xcb, 0x4, 0x31, 0xa8, 0x64, 0xa9, 0xb4, 0x3c, 0xdb, 0x60, 0x33, 0xfe, 0x6f, 0x70, 0xaf, 0x75, 0xea, 0x79, 0x1e, 0xac, 0x2f, 0xc5, 0xf3, 0x9b, 0x47, 0x5e, 0xe0, 0x3d, 0xde, 0xe9, 0x6a, 0xf5, 0xbd, 0xe3, 0x43, 0x13, 0xac, 0x1b, 0xe6, 0x2c, 0x74, 0xe2, 0xf1, 0xaf, 0x84, 0xf9, 0x29, 0x4c, 0x68, 0x40, 0x74, 0x45, 0xe4, 0xb, 0x6c, 0xe6, 0x2d, 0x1e, 0x6a, 0xce, 0x81, 0xc4, 0x1b, 0xc, 0x56, 0xb4, 0xbb, 0xd, 0x51, 0x66, 0x1, 0x3a, 0xa4, 0xa0, 0xb3, 0x38, 0x8d, 0x8e, 0x50, 0x8b, 0xde, 0xc5, 0xc0, 0x22, 0x53, 0xd0, 0x71, 0xe4, 0x2a, 0x9c, 0xa9, 0xc3, 0x8c, 0xa7, 0x77, 0xe5, 0xef, 0x84, 0xf4, 0x65, 0x2a, 0x3, 0x8a, 0xa9, 0xda, 0xa6, 0x3c, 0x17, 0x2c, 0x61, 0x64, 0xa0, 0xa5, 0xcb, 0xc9, 0x24, 0x94, 0x59, 0xc2, 0x63, 0x95, 0xf5, 0x4f, 0xab, 0xb2, 0xfe, 0xcc, 0x35, 0x6a, 0xac, 0x64, 0x31, 0x4f, 0x54, 0xf1, 0x67, 0xc4, 0x24, 0x72, 0xfc, 0x94, 0xd, 0xbc, 0x61, 0x81, 0xe0, 0x5f, 0xfb, 0x76, 0xb3, 0x9a, 0x30, 0x14, 0x44, 0x1, 0xb8, 0x12, 0x17, 0x37, 0x20, 0x95, 0x2e, 0xa, 0xa6, 0x75, 0xd1, 0xa, 0x5, 0x37, 0x95, 0xd6, 0xf7, 0x7f, 0xb8, 0x62, 0x11, 0x5, 0xaf, 0xf1, 0x53, 0xc7, 0x9f, 0x1b, 0xc9, 0x59, 0x67, 0x31, 0x9a, 0xcc, 0xdf, 0x39, 0x67, 0x5e, 0x9e, 0x2, 0x1, 0xe8, 0x15, 0x38, 0x80, 0x71, 0x5b, 0xf9, 0xf4, 0x2b, 0xf0, 0x47, 0xe8, 0x0, 0x6, 0x8b, 0x19, 0x9e, 0xc7, 0x47, 0x88, 0x34, 0x44, 0x0, 0xaf, 0x3f, 0x6d, 0xf9, 0xef, 0x34, 0x74, 0x21, 0x72, 0x0, 0xd, 0x8, 0x28, 0x14, 0x22, 0x95, 0x62, 0x7, 0x90, 0xd1, 0x33, 0x2a, 0xc5, 0x6e, 0x46, 0xa1, 0x0, 0xdc, 0x8c, 0xdc, 0x8e, 0x43, 0x1, 0xb8, 0x1d, 0x7b, 0x20, 0x9, 0x5, 0xe0, 0x81, 0xc4, 0x23, 0x59, 0x2c, 0x0, 0x8f, 0x64, 0x1e, 0x4a, 0x8d, 0xef, 0x3, 0x8b, 0xb4, 0x87, 0x52, 0x8f, 0xe5, 0xc4, 0x33, 0xf2, 0x1a, 0x63, 0x39, 0x17, 0x13, 0xa2, 0x1a, 0x6f, 0x8a, 0x70, 0x96, 0x34, 0x5e, 0x4c, 0xbc, 0x9a, 0x19, 0x6f, 0xe8, 0x5d, 0x58, 0xcd, 0xb8, 0x9c, 0x1a, 0x35, 0x3a, 0x7, 0x96, 0x53, 0xae, 0xe7, 0xc6, 0x64, 0x94, 0xd2, 0x68, 0x82, 0x87, 0xb0, 0x9e, 0x83, 0xa0, 0x8, 0x40, 0x4, 0x85, 0x29, 0x9a, 0x10, 0x4c, 0xd1, 0x98, 0xa4, 0x8a, 0xc1, 0x24, 0x95, 0x69, 0xba, 0x18, 0x4c, 0xd3, 0x99, 0xa8, 0xc, 0xc1, 0x44, 0xa5, 0xa9, 0x5a, 0x61, 0xb6, 0xac, 0xeb, 0x25, 0x12, 0x16, 0x54, 0x2d, 0xc8, 0x6a, 0x64, 0x61, 0xfa, 0x7f, 0x4c, 0x79, 0x38, 0x9c, 0x83, 0x45, 0x4, 0x5d, 0xdf, 0x8a, 0xf7, 0xc1, 0xba, 0x68, 0x61, 0x1e, 0x4, 0x5d, 0xf, 0xc1, 0x2, 0xfd, 0xd, 0xbd, 0x53, 0x82, 0x85, 0x25, 0x1b, 0x8c, 0x38, 0x18, 0xa0, 0x2c, 0xd9, 0x58, 0xb4, 0x42, 0xd5, 0x42, 0x6d, 0xb1, 0x68, 0x65, 0xd9, 0xe, 0x29, 0x8b, 0xd4, 0xa2, 0x6c, 0x67, 0xe1, 0x32, 0x36, 0xf, 0x58, 0xb8, 0xb4, 0x74, 0x8b, 0x79, 0x20, 0x2b, 0x18, 0x90, 0x6e, 0x3, 0xe2, 0x75, 0x86, 0x45, 0xc6, 0x9a, 0x42, 0xbc, 0x3e, 0x53, 0xbe, 0x6f, 0xaa, 0xd6, 0xff, 0x6a, 0x35, 0xf, 0x64, 0xbf, 0xb, 0xf2, 0xfd, 0xed, 0xc, 0xc, 0x1f, 0x3b, 0x6, 0x86, 0x2e, 0x59, 0x38, 0xee, 0x6f, 0x62, 0xb9, 0xbf, 0x8d, 0x67, 0x8f, 0x91, 0x69, 0x7a, 0xbc, 0x91, 0x69, 0x3a, 0x87, 0x91, 0xe9, 0x6, 0x56, 0x2e, 0x27, 0x80, 0x23, 0x68, 0x2e, 0x67, 0x66, 0xeb, 0xa6, 0x9d, 0xaf, 0x0, 0x43, 0xe3, 0x45, 0x2c, 0x9d, 0x9f, 0x57, 0x33, 0xb5, 0xa6, 0xad, 0xa9, 0x35, 0xc1, 0xd4, 0x1a, 0x41, 0xf5, 0x15, 0xb3, 0xf5, 0x76, 0xdf, 0xd8, 0x1c, 0xb0, 0x76, 0xff, 0xae, 0x8a, 0xe7, 0x43, 0x98, 0xdb, 0xb, 0xb0, 0xf7, 0x17, 0x70, 0xe0, 0x50, 0xc0, 0x89, 0x47, 0x1, 0x47, 0x2e, 0x5, 0x9c, 0xf9, 0xac, 0x31, 0xdc, 0x7f, 0xe8, 0x74, 0x42, 0xd6, 0x75, 0xfd, 0xd4, 0xab, 0x47, 0x8f, 0x1e, 0x3d, 0x1e, 0xa, 0x7f, 0x3b, 0x1f, 0x29, 0xf, 0xbf, 0x29, 0x8c, 0x32, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon8Name[] = { 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x64, 0x61, 0x72, 0x6b, 0x00 };
+const unsigned char defaultDialogIcon8Length[] = { 0x38, 0x33, 0x30, 0x00 };
+const unsigned char defaultDialogIcon8Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x3, 0x0, 0x0, 0x0, 0x9d, 0xb7, 0x81, 0xec, 0x0, 0x0, 0x0, 0x78, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc6, 0xa8, 0xe4, 0xac, 0x0, 0x0, 0x0, 0x27, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x3, 0xfb, 0xa5, 0x7e, 0xc9, 0x8e, 0xeb, 0xb2, 0x1c, 0x13, 0xc, 0x7b, 0xa4, 0x43, 0x92, 0xf6, 0xe1, 0xa0, 0x77, 0x47, 0x25, 0x8, 0xc5, 0xf2, 0xdc, 0xd6, 0xd4, 0x88, 0x6b, 0x51, 0x29, 0x27, 0xe, 0xbe, 0x37, 0x36, 0xbf, 0x58, 0xbe, 0xe, 0x1c, 0x70, 0x0, 0x0, 0x2, 0x4e, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xdc, 0x54, 0x6b, 0x8f, 0x82, 0x40, 0xc, 0x64, 0x91, 0xe5, 0xa5, 0x82, 0xf2, 0x46, 0x5e, 0xea, 0x99, 0xf4, 0xff, 0xff, 0xc3, 0x4b, 0xbb, 0x1e, 0x50, 0xea, 0x5, 0x3c, 0xbf, 0x5d, 0xa3, 0x89, 0x3b, 0x9d, 0x19, 0xda, 0x6e, 0xd1, 0xfa, 0xdf, 0xa1, 0xc6, 0xf8, 0x93, 0x58, 0x9c, 0xdf, 0x9, 0x62, 0x7, 0x59, 0xd7, 0xc6, 0x49, 0x12, 0xb7, 0x5d, 0x16, 0x10, 0xf8, 0x8e, 0x5c, 0x45, 0xae, 0x3e, 0xc1, 0x18, 0x27, 0xed, 0x46, 0x88, 0x6e, 0x95, 0x87, 0x69, 0x43, 0x3a, 0xfb, 0x19, 0x74, 0x68, 0xd2, 0x90, 0x92, 0xeb, 0xfa, 0xbd, 0xe3, 0x91, 0x78, 0x74, 0xf8, 0x39, 0x79, 0xce, 0x7e, 0xcd, 0x1, 0xf3, 0x7d, 0x4d, 0x7c, 0xfa, 0x9a, 0x98, 0xce, 0x75, 0x2f, 0x8a, 0x10, 0x7a, 0x17, 0x0, 0x95, 0xa4, 0xae, 0xce, 0xda, 0xf7, 0xf5, 0xb9, 0x2, 0x18, 0x41, 0x97, 0x3b, 0xc8, 0xf2, 0x7d, 0x7a, 0x12, 0x7e, 0xca, 0xc3, 0x23, 0x2c, 0x70, 0x72, 0x45, 0xf8, 0x38, 0x94, 0x4, 0x62, 0xce, 0x9f, 0xb5, 0x21, 0xf5, 0xd7, 0xb, 0xd1, 0xb0, 0xdd, 0x9c, 0xe7, 0x72, 0x1c, 0xc, 0xe5, 0x2e, 0xd7, 0xc9, 0x41, 0xe8, 0x3d, 0xc3, 0xb1, 0xdd, 0x80, 0xed, 0xe, 0xfd, 0xa, 0x5c, 0xdb, 0x64, 0xbd, 0xd1, 0x41, 0xd4, 0xff, 0x7c, 0x7e, 0x39, 0xbc, 0xde, 0xc4, 0x41, 0x3f, 0x6b, 0xf8, 0xad, 0xb, 0xdf, 0x8c, 0xee, 0xa8, 0x26, 0x39, 0xb7, 0x50, 0x47, 0x33, 0x4e, 0xff, 0x75, 0x1, 0xae, 0xd1, 0x3b, 0x6c, 0xce, 0x93, 0x15, 0xc1, 0x8e, 0x71, 0x70, 0x2d, 0x25, 0xf5, 0xbd, 0xa9, 0xcf, 0x61, 0x1a, 0x51, 0x84, 0x63, 0x58, 0xbd, 0x74, 0xd8, 0xd7, 0x80, 0x43, 0x3a, 0xf2, 0x8c, 0xca, 0x32, 0x7e, 0xb6, 0x8e, 0x44, 0xab, 0xf7, 0xa2, 0x0, 0x87, 0x12, 0x5a, 0x31, 0x83, 0xdb, 0x1d, 0xe0, 0x7e, 0x63, 0x3c, 0xa5, 0x89, 0xe8, 0x2c, 0x4b, 0x8, 0xcd, 0xd, 0xe, 0x88, 0x4f, 0xec, 0x3, 0xae, 0xce, 0x81, 0x63, 0x83, 0xb9, 0xcb, 0x70, 0x51, 0x40, 0xa, 0xb6, 0x18, 0xe, 0xd5, 0x8b, 0x5d, 0x71, 0x30, 0x26, 0x6a, 0xba, 0xe8, 0xb5, 0x41, 0xaa, 0x17, 0x2c, 0xb8, 0x3b, 0x44, 0x77, 0xb, 0x30, 0xf0, 0x10, 0x6d, 0x38, 0x18, 0x1, 0x4c, 0x8d, 0x49, 0x3, 0x39, 0x2e, 0x80, 0x8, 0x61, 0xbe, 0x3, 0x76, 0xbe, 0xcd, 0x20, 0xb7, 0xe5, 0x2e, 0x68, 0x64, 0x96, 0xd3, 0x59, 0x18, 0xb0, 0x28, 0x11, 0xd6, 0x73, 0x24, 0x38, 0x1, 0xf0, 0x71, 0x4b, 0x3, 0x7e, 0x39, 0x0, 0xa7, 0x60, 0x86, 0x64, 0x80, 0xf1, 0xd8, 0x6a, 0xf0, 0x45, 0xf4, 0x8c, 0x70, 0x83, 0x74, 0x68, 0x59, 0x85, 0x5b, 0x5b, 0x8, 0x2b, 0x2c, 0xb8, 0x9b, 0x19, 0xb4, 0x48, 0x3c, 0x17, 0x5b, 0xd, 0x8a, 0x33, 0xe2, 0xed, 0xcc, 0x20, 0xa6, 0xa9, 0xa8, 0xad, 0x6, 0x8a, 0x66, 0x1e, 0xcf, 0xc, 0x12, 0x4, 0x7c, 0x6b, 0xab, 0x81, 0xe5, 0x23, 0x9e, 0x7c, 0x6c, 0xf0, 0x71, 0xb, 0xeb, 0x43, 0x94, 0x2f, 0x93, 0x1c, 0xa2, 0xbc, 0x46, 0xf9, 0x3a, 0xaf, 0x5c, 0xa3, 0x5c, 0x24, 0xf9, 0x87, 0xb2, 0xb2, 0x48, 0x72, 0x95, 0x27, 0x72, 0xf4, 0xdd, 0x7d, 0xb9, 0xa3, 0x0, 0xc, 0xc3, 0x30, 0x74, 0xea, 0x9a, 0xe6, 0x73, 0x84, 0x90, 0xf8, 0xfe, 0x37, 0x6c, 0x31, 0x18, 0xf, 0xa2, 0x8, 0xaa, 0x2d, 0x86, 0x40, 0x8, 0x64, 0x31, 0xb6, 0xf4, 0x54, 0xf2, 0xd, 0x47, 0x19, 0x97, 0x9, 0x55, 0xd4, 0xf, 0x5b, 0x26, 0x5c, 0xe7, 0xec, 0x97, 0x77, 0x96, 0xac, 0x33, 0x8, 0x4a, 0xd6, 0x68, 0x6d, 0xc4, 0x1d, 0x5, 0x85, 0x4b, 0xda, 0xb6, 0xb7, 0xf6, 0xa7, 0xa4, 0x71, 0x51, 0xed, 0x56, 0xab, 0x75, 0x22, 0xaa, 0x8a, 0xac, 0x73, 0x63, 0x59, 0x66, 0x8b, 0x18, 0xb, 0xb1, 0xb6, 0x52, 0x88, 0xb5, 0xc9, 0xe6, 0xaa, 0xdb, 0xbb, 0xe, 0x18, 0x88, 0x38, 0xd7, 0x2f, 0xc4, 0xe1, 0x90, 0x75, 0x7, 0x64, 0xf9, 0xc4, 0xb, 0x98, 0x17, 0xff, 0x39, 0x68, 0xce, 0x0, 0xcd, 0x9, 0xa0, 0xa9, 0xa1, 0xae, 0xa, 0xdb, 0x22, 0xee, 0xab, 0x81, 0x43, 0x8c, 0x3c, 0x62, 0xe8, 0x12, 0x63, 0x9f, 0x16, 0x3c, 0x8f, 0xae, 0x7, 0x37, 0xe7, 0x62, 0x42, 0xa9, 0x5c, 0xc2, 0xf9, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon9Name[] = { 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x64, 0x61, 0x72, 0x6b, 0x32, 0x78, 0x00 };
+const unsigned char defaultDialogIcon9Length[] = { 0x31, 0x32, 0x37, 0x34, 0x00 };
+const unsigned char defaultDialogIcon9Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf4, 0xe0, 0x91, 0xf9, 0x0, 0x0, 0x0, 0x96, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x25, 0xc5, 0xa8, 0x0, 0x0, 0x0, 0x31, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x55, 0xfb, 0x3, 0xe3, 0xc, 0x43, 0x92, 0x8a, 0xd7, 0xb7, 0x82, 0x62, 0x40, 0x19, 0x48, 0x4e, 0x2b, 0xee, 0xe7, 0x39, 0xbf, 0x21, 0x97, 0x67, 0xb4, 0x33, 0xd0, 0x6c, 0x9, 0xcb, 0xf, 0xc5, 0xb1, 0xf6, 0xc2, 0xbc, 0xa8, 0x71, 0x26, 0x25, 0x14, 0x7, 0xc6, 0x3b, 0x7c, 0x5d, 0x11, 0x7b, 0x95, 0x98, 0x75, 0x67, 0x0, 0x0, 0x3, 0xe2, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x96, 0xd9, 0x8e, 0xb3, 0x30, 0xc, 0x85, 0x5d, 0x2, 0x94, 0xb2, 0xb7, 0x65, 0x69, 0xe9, 0xbe, 0x4f, 0xb7, 0x91, 0xf2, 0xfe, 0x2f, 0xf7, 0x4b, 0xbf, 0x46, 0xc2, 0x50, 0x1a, 0x13, 0x12, 0x46, 0x73, 0xd1, 0xef, 0xda, 0xa2, 0x27, 0x3e, 0x3e, 0x76, 0xe1, 0xc3, 0x87, 0xf, 0x1f, 0x3e, 0x74, 0x67, 0x15, 0x5e, 0xa, 0x3b, 0x8e, 0xed, 0xe2, 0x12, 0xae, 0xe0, 0x77, 0xb1, 0xec, 0x65, 0x7e, 0x34, 0x39, 0xc2, 0x3c, 0xe6, 0x4b, 0xdb, 0x82, 0xdf, 0xe0, 0x16, 0x78, 0x77, 0x83, 0x37, 0x62, 0xdc, 0xbd, 0xe0, 0x6, 0xbd, 0xc2, 0xa2, 0xd3, 0x9e, 0xb, 0xd9, 0x9f, 0x22, 0x6, 0x7d, 0x31, 0x76, 0xa6, 0xbc, 0x5, 0x53, 0x67, 0xc, 0x7d, 0xf0, 0x70, 0x79, 0x6b, 0xdc, 0x4, 0x74, 0x63, 0xa7, 0x5c, 0x8a, 0xd4, 0xd6, 0xdb, 0xfc, 0x94, 0x4b, 0x93, 0xea, 0x33, 0xc2, 0x72, 0xc, 0xde, 0x1, 0xc3, 0xd1, 0x94, 0xcb, 0xd8, 0xe4, 0x1d, 0x31, 0x63, 0x50, 0x87, 0x39, 0x5c, 0x1, 0x87, 0x29, 0xb7, 0x7f, 0xfe, 0xb6, 0xc3, 0xe6, 0x7a, 0x97, 0x3f, 0x7d, 0xff, 0x99, 0xef, 0xd6, 0xe6, 0x5b, 0x8f, 0xe6, 0x8a, 0x36, 0x64, 0x23, 0xde, 0xc4, 0xc2, 0x4b, 0x42, 0x6, 0x8, 0x16, 0x26, 0xde, 0x82, 0x37, 0x31, 0xca, 0x40, 0x81, 0x60, 0xd2, 0xf0, 0xf2, 0xc3, 0x39, 0x7b, 0xa3, 0xf6, 0x7c, 0x68, 0xe8, 0xc4, 0x24, 0x80, 0xce, 0xcc, 0x5e, 0xbf, 0xe7, 0xfa, 0x57, 0x10, 0x70, 0xf5, 0xdd, 0x57, 0xc5, 0x33, 0xe8, 0x6, 0x1b, 0xf2, 0x3a, 0xdb, 0x4, 0x48, 0x1e, 0x5b, 0x5e, 0x67, 0xc8, 0x3a, 0xfd, 0xfe, 0xfc, 0xc5, 0xce, 0x96, 0xa9, 0x1a, 0x8c, 0x5e, 0x46, 0xb1, 0x8b, 0x82, 0xfa, 0xfb, 0xa7, 0x33, 0xd6, 0x5a, 0xfb, 0x6c, 0x5a, 0xef, 0x41, 0x7, 0xff, 0x6b, 0x46, 0x7a, 0x96, 0x54, 0x7a, 0xbd, 0xda, 0xf8, 0x48, 0xcf, 0x41, 0x60, 0x54, 0x47, 0xd9, 0x6, 0x49, 0xec, 0x6a, 0x80, 0x8c, 0x40, 0x32, 0xff, 0x93, 0xaa, 0xfb, 0x5f, 0x20, 0xcd, 0xd7, 0xa8, 0xfa, 0x84, 0x4c, 0xaa, 0x83, 0xa3, 0x6a, 0xf6, 0x36, 0xd0, 0x81, 0x8d, 0x5b, 0x7d, 0x84, 0xd5, 0x39, 0x0, 0xe, 0x53, 0xcb, 0xb1, 0x7c, 0x14, 0x9c, 0x8a, 0x7b, 0x3e, 0x74, 0xc6, 0x37, 0x2a, 0xf, 0x81, 0x96, 0xc4, 0x1c, 0xe3, 0x83, 0x2, 0x3e, 0xc7, 0xc4, 0x2d, 0x7, 0xc0, 0x14, 0x24, 0x58, 0x69, 0x9b, 0x98, 0x2b, 0x79, 0x3, 0x5c, 0x6, 0x4a, 0x30, 0x57, 0xda, 0x84, 0x31, 0xf6, 0x6d, 0xb4, 0x11, 0xf, 0xfa, 0xf7, 0x70, 0xf8, 0x4d, 0x94, 0xe0, 0x40, 0x19, 0x63, 0xa0, 0x49, 0x71, 0x78, 0xc5, 0xf9, 0x8f, 0xfe, 0x9b, 0x65, 0x46, 0xe2, 0x7d, 0x80, 0x57, 0x4a, 0xa, 0x24, 0x36, 0x16, 0x6c, 0x83, 0x88, 0xc2, 0xf8, 0x29, 0x2b, 0xc4, 0x5f, 0xc4, 0x2d, 0xb5, 0xa5, 0x1a, 0xe0, 0x81, 0x90, 0x5, 0xff, 0x61, 0xd, 0x42, 0x3c, 0x99, 0x16, 0x3c, 0xf0, 0xfd, 0xb3, 0x40, 0x44, 0x58, 0x56, 0x86, 0xe2, 0x58, 0xe1, 0xdb, 0x98, 0x80, 0x18, 0xb7, 0xfd, 0x5, 0x8b, 0xca, 0xca, 0x8, 0x84, 0x2c, 0x71, 0xac, 0x88, 0x8, 0xe0, 0x4, 0x10, 0x9, 0x1c, 0x94, 0xa5, 0x3, 0x22, 0x8b, 0x38, 0x9, 0xe3, 0xd6, 0x3b, 0x60, 0x0, 0x84, 0x0, 0xe9, 0x52, 0x7a, 0x17, 0x30, 0xe4, 0xd6, 0x16, 0xf4, 0x9, 0x80, 0x3b, 0x9a, 0x2c, 0xd6, 0xce, 0x56, 0x9e, 0xe8, 0x14, 0xf0, 0xe0, 0xed, 0x6, 0xe6, 0x44, 0xd, 0xb, 0x2d, 0x80, 0x1e, 0xee, 0x13, 0xbc, 0xe5, 0xb6, 0xa7, 0x8e, 0x20, 0x2d, 0x80, 0x3e, 0x8b, 0xfb, 0x1b, 0xbc, 0x23, 0x40, 0x4b, 0xf0, 0xaa, 0x57, 0xc0, 0x15, 0xad, 0xc3, 0xa0, 0xcd, 0xca, 0x3a, 0x80, 0x5e, 0x1, 0x70, 0xa8, 0x2c, 0x58, 0x7a, 0x56, 0xcf, 0xba, 0x5, 0x9c, 0x5b, 0xe4, 0xcb, 0x42, 0x6d, 0xca, 0x74, 0xb, 0xc8, 0x90, 0xbd, 0x16, 0x7d, 0x8, 0x17, 0xa0, 0x5b, 0x0, 0x2c, 0xe8, 0x93, 0xb8, 0x44, 0x36, 0x29, 0x8, 0xa0, 0x7, 0x6c, 0x9, 0xcd, 0xe4, 0x68, 0xb, 0xe9, 0x17, 0x90, 0xd0, 0xff, 0x33, 0x8f, 0xe8, 0xbe, 0x2a, 0x8, 0xa0, 0xaf, 0xf7, 0x11, 0x9a, 0x31, 0xcb, 0x31, 0x61, 0xfa, 0x5, 0xb0, 0x72, 0xc4, 0x4d, 0x68, 0x64, 0xc5, 0x51, 0x85, 0x8a, 0x0, 0xf2, 0x7d, 0x7c, 0x45, 0xf5, 0x68, 0xdd, 0x87, 0x80, 0x35, 0xe5, 0xf0, 0xa5, 0x2c, 0xd8, 0xf5, 0x21, 0x60, 0x57, 0xd6, 0x5f, 0xa0, 0x89, 0xa2, 0x2c, 0xc8, 0x95, 0x4, 0xd0, 0x21, 0x2b, 0xa8, 0x53, 0xf4, 0xec, 0x43, 0xc0, 0x93, 0x3a, 0x47, 0x31, 0xba, 0xc5, 0x4a, 0x2, 0xe8, 0x8b, 0x1c, 0xff, 0x4d, 0x1, 0xff, 0xda, 0x37, 0x83, 0x15, 0x4, 0xa1, 0x28, 0x88, 0x1a, 0x12, 0x6e, 0x14, 0x34, 0x5d, 0x24, 0x19, 0xd8, 0x3a, 0x41, 0xff, 0xff, 0xeb, 0xc2, 0xa0, 0x20, 0x31, 0x4f, 0x3a, 0xd2, 0x7d, 0xca, 0x9b, 0xb5, 0xbb, 0xd2, 0x7b, 0x67, 0xee, 0x19, 0xf3, 0x9f, 0xc0, 0xfc, 0x4f, 0x68, 0xfe, 0x1a, 0x9a, 0x7f, 0x88, 0xe0, 0x53, 0xc, 0xd6, 0x4c, 0xf8, 0x14, 0xf3, 0x30, 0x62, 0x73, 0x2a, 0xd, 0x23, 0x1e, 0xc7, 0x6c, 0xcf, 0x85, 0x71, 0xcc, 0xb, 0x9, 0x7, 0x14, 0x59, 0x20, 0x2c, 0x24, 0xb0, 0x92, 0x9, 0x11, 0xd, 0xac, 0x64, 0xca, 0x52, 0xca, 0x21, 0x15, 0x2f, 0xa5, 0xbc, 0x96, 0x4b, 0xe2, 0xb5, 0x9c, 0x8d, 0x89, 0x24, 0x36, 0x26, 0x6c, 0xcd, 0x34, 0xb1, 0x35, 0x63, 0x73, 0x2a, 0x89, 0xcd, 0x29, 0xdb, 0x73, 0x4d, 0x6c, 0xcf, 0x39, 0xa0, 0x60, 0xa5, 0x65, 0x9e, 0x97, 0x90, 0x81, 0x43, 0x40, 0x1, 0x11, 0xd, 0xc, 0x58, 0x18, 0xde, 0x10, 0xd1, 0x60, 0x48, 0x45, 0x3a, 0x43, 0xa0, 0x8, 0x21, 0x95, 0x1c, 0xd3, 0x85, 0xa7, 0x77, 0xa6, 0x1d, 0x2a, 0x31, 0x1d, 0x7, 0x95, 0xfc, 0x6c, 0x36, 0x27, 0xa8, 0x74, 0x3b, 0xaa, 0xd, 0xa, 0x83, 0xb0, 0x5a, 0x8f, 0xeb, 0x2f, 0x33, 0xe2, 0x7a, 0xf7, 0xf, 0x16, 0xe6, 0x27, 0x9b, 0x59, 0x47, 0xab, 0xb6, 0xdf, 0x7, 0xda, 0xe9, 0x47, 0x3e, 0x8f, 0x56, 0x5b, 0x38, 0xdb, 0xd9, 0x1f, 0x2e, 0xcd, 0x4f, 0xb7, 0xf6, 0xc7, 0xeb, 0xe1, 0xf9, 0x3e, 0x5a, 0xeb, 0x7c, 0xff, 0x67, 0x80, 0xa1, 0x1d, 0x2, 0xc, 0x5b, 0x42, 0x38, 0xec, 0x21, 0x16, 0x7b, 0x8c, 0x67, 0x4, 0x64, 0xaa, 0x7e, 0x7, 0x99, 0x2a, 0x0, 0x99, 0x96, 0xa2, 0x5c, 0x7, 0x15, 0xe5, 0xd2, 0x61, 0xb6, 0x26, 0x40, 0x35, 0xf7, 0x11, 0x98, 0x6d, 0x9b, 0x38, 0x9f, 0x3, 0x40, 0xa3, 0x3d, 0xd2, 0x89, 0x50, 0x6b, 0x17, 0xc7, 0xdd, 0x24, 0xd4, 0x7a, 0x3b, 0x6e, 0x1d, 0xeb, 0xb5, 0x7, 0x9b, 0x15, 0xb4, 0xfb, 0xba, 0x17, 0xb8, 0xdd, 0x1, 0xbc, 0xbf, 0x57, 0xb1, 0xb4, 0xe0, 0xb0, 0x9b, 0x8a, 0x87, 0x3, 0x25, 0x97, 0x57, 0xcd, 0xa7, 0xfe, 0x56, 0xf3, 0xa9, 0xa1, 0xe6, 0xb3, 0x66, 0xd1, 0x29, 0x1a, 0x16, 0x9d, 0x22, 0xa1, 0xe8, 0x24, 0x54, 0xbd, 0x92, 0x34, 0x4d, 0x9e, 0x55, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0xaf, 0xc5, 0x7a, 0x0, 0xb3, 0x43, 0xaa, 0xfb, 0xee, 0x9f, 0xf1, 0x9c, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon10Name[] = { 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x00 };
+const unsigned char defaultDialogIcon10Length[] = { 0x37, 0x32, 0x30, 0x00 };
+const unsigned char defaultDialogIcon10Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x40, 0x8, 0x3, 0x0, 0x0, 0x0, 0x9d, 0xb7, 0x81, 0xec, 0x0, 0x0, 0x0, 0x75, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x79, 0x59, 0x7d, 0x58, 0x0, 0x0, 0x0, 0x26, 0x74, 0x52, 0x4e, 0x53, 0x0, 0x7, 0xd2, 0xab, 0x12, 0x22, 0x73, 0x50, 0x7d, 0x25, 0xcb, 0xa7, 0xb8, 0x7c, 0x60, 0x2e, 0xfa, 0x8f, 0xeb, 0xe3, 0xd9, 0xc3, 0x9e, 0x84, 0x69, 0x1d, 0xf1, 0xcd, 0xb2, 0x91, 0xb9, 0xed, 0x49, 0x48, 0x36, 0x35, 0x93, 0xf2, 0x7e, 0x13, 0xd0, 0xd1, 0x0, 0x0, 0x1, 0xe4, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x57, 0xd9, 0x96, 0xc2, 0x20, 0xc, 0x2d, 0x4c, 0x37, 0xb5, 0x8b, 0xb5, 0xda, 0xd5, 0xba, 0xf, 0xff, 0xff, 0x89, 0x73, 0xa, 0x8c, 0xa9, 0x8, 0xd, 0x1e, 0x4e, 0xdf, 0xcc, 0x63, 0x48, 0xd2, 0xe6, 0xde, 0x24, 0x4, 0xef, 0x2b, 0x8b, 0x8a, 0x7f, 0x8b, 0x9a, 0xba, 0xaa, 0xea, 0x26, 0xba, 0xf9, 0x9f, 0x7b, 0x7, 0xe1, 0x81, 0x4d, 0xe4, 0x10, 0x6, 0x9f, 0x78, 0x93, 0x90, 0xb2, 0x37, 0xa1, 0x21, 0xb1, 0xf5, 0xcf, 0x4a, 0xa6, 0x95, 0x32, 0xb3, 0x72, 0xbf, 0x50, 0x66, 0x14, 0x7a, 0xc1, 0xfd, 0x87, 0xa9, 0xc3, 0x8e, 0x26, 0x9, 0xdd, 0x4d, 0x35, 0x5, 0x86, 0xfc, 0xe9, 0x69, 0xba, 0x4e, 0x73, 0x22, 0x21, 0xc9, 0xd3, 0xf5, 0x53, 0x7d, 0x9a, 0x65, 0x64, 0xb5, 0x97, 0x66, 0x5d, 0xda, 0xbe, 0x9e, 0xb4, 0x71, 0x27, 0x8f, 0xf6, 0xab, 0x19, 0xff, 0xb3, 0xb0, 0xd9, 0xe, 0x9a, 0xcf, 0xf8, 0xc3, 0x56, 0x9c, 0x9e, 0x8d, 0x11, 0x7c, 0xf9, 0xfd, 0x75, 0x60, 0x88, 0x2f, 0x13, 0xd9, 0x9b, 0xb2, 0x90, 0xf9, 0x6f, 0x8c, 0x7c, 0x93, 0x8d, 0xb0, 0x48, 0xf4, 0xc7, 0x85, 0x38, 0x4d, 0xe7, 0x40, 0x4a, 0x67, 0xb8, 0xb8, 0x20, 0xfe, 0x42, 0x62, 0x61, 0x75, 0xd5, 0x1c, 0x51, 0xf1, 0xff, 0x6a, 0x4f, 0xa8, 0x78, 0x88, 0x2c, 0xa8, 0xa6, 0x7e, 0x5, 0x7e, 0x4a, 0xfe, 0x47, 0xc6, 0x8e, 0xa, 0xe, 0x2, 0xc9, 0xec, 0xd, 0x9f, 0x92, 0xf3, 0xa7, 0x7c, 0x2f, 0x1a, 0x95, 0x91, 0xc2, 0x5, 0x67, 0xb3, 0x54, 0x91, 0xe, 0x79, 0xdc, 0x41, 0xfd, 0x5f, 0x5d, 0x56, 0xf, 0x6e, 0x1a, 0xea, 0x10, 0xe8, 0x54, 0x82, 0x7f, 0x46, 0xed, 0x8f, 0x5a, 0x2e, 0x9d, 0x6, 0x85, 0x40, 0x32, 0x80, 0x6, 0x0, 0x26, 0x2, 0x4d, 0x6, 0xad, 0x5d, 0x80, 0x56, 0x93, 0xc3, 0x81, 0x53, 0xe0, 0xd9, 0x5, 0xf0, 0x7a, 0x3e, 0xe5, 0x5e, 0xd2, 0x82, 0xc, 0xb0, 0x0, 0x90, 0xc3, 0x14, 0xb0, 0x1b, 0xd7, 0xe4, 0xb6, 0x1, 0x72, 0x6e, 0x7e, 0x57, 0xf9, 0x66, 0xc4, 0x36, 0x0, 0x61, 0x6a, 0x7d, 0x34, 0x7c, 0x7e, 0x79, 0x48, 0x0, 0x10, 0x3e, 0xe5, 0x9a, 0x89, 0xa2, 0x6, 0x66, 0xb1, 0x0, 0x50, 0x35, 0xf5, 0x44, 0x51, 0x41, 0x97, 0x5b, 0x5, 0x48, 0x46, 0x7d, 0xe5, 0x1c, 0xc0, 0x39, 0x5, 0xc, 0x44, 0x68, 0x26, 0x1c, 0x44, 0xa0, 0x11, 0x6f, 0x67, 0xa0, 0x11, 0x29, 0x24, 0x18, 0x28, 0x48, 0x21, 0x29, 0xa5, 0x8c, 0x8c, 0x34, 0x28, 0xe5, 0xad, 0x8f, 0x34, 0x13, 0x8, 0xde, 0x4c, 0xd0, 0xce, 0x6, 0xc1, 0xdb, 0x39, 0x30, 0xe4, 0x90, 0xf5, 0x7d, 0x66, 0x68, 0xc6, 0x0, 0x19, 0x69, 0x80, 0x56, 0x8e, 0x8f, 0x34, 0x18, 0xaa, 0xba, 0xcb, 0xaa, 0xc0, 0x87, 0x2a, 0x8c, 0xf5, 0x95, 0xfd, 0x58, 0xb7, 0xba, 0x58, 0xc8, 0x58, 0x7, 0x4, 0xb9, 0x58, 0x5c, 0xaf, 0x36, 0xf7, 0xcb, 0x55, 0xbd, 0xde, 0x63, 0xb, 0xff, 0xc2, 0x75, 0xc1, 0x58, 0x64, 0xc5, 0xc1, 0x97, 0xac, 0x7, 0x2c, 0x59, 0x16, 0x6b, 0x5e, 0xfc, 0xb6, 0xe6, 0xfd, 0xc2, 0x9a, 0x67, 0xb7, 0x68, 0xf6, 0x31, 0x2c, 0x9a, 0x71, 0xff, 0x54, 0x27, 0xd8, 0xea, 0x5f, 0x60, 0xab, 0x2e, 0x2a, 0xd7, 0xb9, 0x65, 0xfb, 0xea, 0xbe, 0xee, 0xbb, 0x3f, 0x38, 0x96, 0x7f, 0xf2, 0x0, 0x23, 0xf7, 0xff, 0x47, 0xd7, 0xdd, 0xf7, 0xbe, 0xb2, 0xa4, 0xfc, 0x1, 0xb3, 0x34, 0x7e, 0xa8, 0x9d, 0x48, 0x16, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char defaultDialogIcon11Name[] = { 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x32, 0x78, 0x00 };
+const unsigned char defaultDialogIcon11Length[] = { 0x31, 0x32, 0x36, 0x32, 0x00 };
+const unsigned char defaultDialogIcon11Data[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x80, 0x8, 0x3, 0x0, 0x0, 0x0, 0xf4, 0xe0, 0x91, 0xf9, 0x0, 0x0, 0x0, 0x93, 0x50, 0x4c, 0x54, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7a, 0x79, 0x23, 0x75, 0x0, 0x0, 0x0, 0x30, 0x74, 0x52, 0x4e, 0x53, 0x0, 0xfb, 0x55, 0x42, 0xc, 0xd7, 0xb7, 0x8a, 0x82, 0x7, 0x19, 0xe5, 0xc0, 0x48, 0x2b, 0x68, 0x62, 0x4e, 0xee, 0x94, 0x39, 0x6f, 0x21, 0xe8, 0xe2, 0xb4, 0x90, 0x33, 0xd0, 0xbd, 0x9, 0xcb, 0x97, 0x3c, 0xf, 0xc5, 0xb1, 0xf6, 0xa8, 0x7c, 0x52, 0x26, 0x25, 0x14, 0xc6, 0x61, 0x5d, 0x11, 0x8d, 0xf, 0xdf, 0x6c, 0x0, 0x0, 0x3, 0xda, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xec, 0x97, 0xd9, 0x72, 0xb3, 0x30, 0xc, 0x85, 0xe5, 0x42, 0xc0, 0x6c, 0x9, 0x94, 0xa5, 0x49, 0xc9, 0xbe, 0xa7, 0x49, 0x3a, 0x7e, 0xff, 0xa7, 0xfb, 0xff, 0x8b, 0xce, 0xd8, 0x10, 0x62, 0xd9, 0xc6, 0x74, 0x7a, 0x91, 0xef, 0x5a, 0x4c, 0xe, 0xd2, 0x39, 0x52, 0x80, 0x17, 0x2f, 0x5e, 0xbc, 0x78, 0x61, 0xce, 0x32, 0xbc, 0xd4, 0x4e, 0x96, 0x39, 0xf5, 0x25, 0x5c, 0xc2, 0xef, 0xe2, 0x3a, 0x65, 0xbe, 0x88, 0x98, 0x40, 0xb4, 0xc8, 0x4b, 0xc7, 0x85, 0xdf, 0x20, 0x70, 0xe8, 0x8d, 0xb0, 0x4e, 0xc8, 0x8d, 0x3a, 0x1, 0xc, 0x4b, 0x72, 0x3c, 0x30, 0x29, 0x87, 0x63, 0x2, 0x83, 0x31, 0xf6, 0x3f, 0x98, 0x2, 0x1f, 0xfe, 0x18, 0x86, 0x60, 0xe3, 0x31, 0x65, 0xbc, 0x2, 0x6c, 0xe3, 0xc4, 0x4c, 0x8b, 0xd8, 0xb1, 0xdb, 0xfc, 0x98, 0x69, 0x13, 0xdb, 0x1b, 0x84, 0xeb, 0x13, 0x66, 0x0, 0xf1, 0x2d, 0xe5, 0x32, 0x9b, 0x32, 0x43, 0xa6, 0x19, 0x58, 0xc0, 0x67, 0x3d, 0xf0, 0xfb, 0xb7, 0x7f, 0xf6, 0xb4, 0xc3, 0xd1, 0x6a, 0xbf, 0x3e, 0xa5, 0xe9, 0x69, 0xbd, 0x5f, 0x45, 0x4f, 0x67, 0x34, 0xeb, 0x39, 0x86, 0x6a, 0xc4, 0xba, 0x98, 0xd3, 0x22, 0x84, 0x6, 0x61, 0x41, 0xe7, 0xac, 0x8b, 0x51, 0xd5, 0x2b, 0x7c, 0xef, 0x1d, 0x6f, 0x1e, 0x97, 0xd5, 0x13, 0xb5, 0x65, 0xdc, 0xd1, 0x89, 0xf7, 0x1e, 0x81, 0x9c, 0x10, 0xd6, 0xc6, 0xbb, 0x5f, 0x41, 0xc2, 0xf5, 0xee, 0x3d, 0x2a, 0x9e, 0x80, 0x19, 0x41, 0xce, 0xda, 0xec, 0xa, 0x40, 0xd9, 0xec, 0x58, 0x9b, 0x3c, 0x30, 0xfa, 0xfd, 0xd9, 0xc3, 0x38, 0xcf, 0xa0, 0xc4, 0x79, 0xf4, 0x60, 0x45, 0x13, 0x5, 0x79, 0xfb, 0xc6, 0x4c, 0x2, 0x65, 0xed, 0x93, 0x8f, 0x76, 0xf, 0xc, 0xe6, 0xdf, 0x1a, 0x24, 0x75, 0xb5, 0xd2, 0x4b, 0x5b, 0xf6, 0xd1, 0xf6, 0x81, 0x43, 0x10, 0x2b, 0x6b, 0x6, 0x88, 0x6c, 0x34, 0xf3, 0xdf, 0x7c, 0x7c, 0xf4, 0x5, 0xda, 0x7c, 0x8d, 0x9a, 0xaf, 0x50, 0x69, 0x75, 0xb0, 0xf9, 0xb0, 0xb7, 0x5, 0x3, 0xb6, 0x5e, 0xf3, 0x25, 0x74, 0x66, 0xd8, 0xc, 0xc0, 0x27, 0x18, 0xf2, 0xd9, 0x8c, 0x82, 0xe1, 0xfd, 0x21, 0x29, 0x18, 0x93, 0x12, 0xa3, 0xcb, 0x94, 0x31, 0x91, 0x14, 0x7a, 0x90, 0x32, 0x91, 0x4c, 0xd1, 0x0, 0x53, 0xa4, 0xff, 0xc6, 0x53, 0x98, 0x2e, 0xf5, 0x7, 0xe0, 0x41, 0x4f, 0x3c, 0xed, 0x21, 0x8c, 0x89, 0x68, 0xdd, 0xad, 0xdc, 0xe8, 0xdf, 0x79, 0xfe, 0x8d, 0x94, 0x88, 0x81, 0x22, 0x63, 0xc0, 0x89, 0xc5, 0xf0, 0xca, 0xf3, 0x9f, 0x44, 0xec, 0x3f, 0x51, 0x22, 0xdf, 0x7, 0xe2, 0x4a, 0x89, 0x1, 0xc5, 0x11, 0x5, 0x3b, 0x20, 0xa3, 0x26, 0x3f, 0x65, 0xb5, 0xfa, 0x52, 0x75, 0xb4, 0x1a, 0x40, 0x41, 0xca, 0x9c, 0xfd, 0xb0, 0x2, 0x29, 0x94, 0x71, 0x16, 0xe8, 0x35, 0x17, 0xef, 0x9f, 0xb, 0x32, 0x42, 0x5e, 0x19, 0xca, 0x63, 0x25, 0xde, 0xc6, 0x2, 0x33, 0xad, 0xfa, 0x5, 0x4b, 0x78, 0x65, 0xa2, 0x7e, 0x59, 0x3d, 0x24, 0x2, 0x62, 0x2, 0x90, 0xfb, 0xff, 0xc6, 0x4b, 0xdf, 0x40, 0x4a, 0x20, 0x26, 0x61, 0xac, 0xbc, 0x3, 0xce, 0x60, 0x4b, 0x0, 0x9c, 0x95, 0x77, 0x81, 0x30, 0xad, 0x1d, 0xd8, 0x13, 0x0, 0x37, 0x5e, 0x1b, 0x29, 0x8e, 0x95, 0x15, 0x36, 0x5, 0x6c, 0x98, 0x9a, 0x61, 0x8e, 0x98, 0x59, 0x70, 0x1, 0xb8, 0xb9, 0x8f, 0x12, 0xb3, 0x1c, 0x78, 0xd9, 0xdd, 0x50, 0x0, 0x7e, 0x16, 0xf, 0x81, 0xca, 0x16, 0x24, 0x57, 0xbb, 0x2, 0xae, 0x44, 0x65, 0x1b, 0x52, 0x7c, 0x69, 0xe3, 0x2, 0xf0, 0xd, 0x4b, 0x55, 0xbc, 0x5a, 0xda, 0x16, 0x50, 0x2a, 0xe4, 0xcb, 0x15, 0xda, 0x54, 0xd9, 0x16, 0x50, 0x9, 0xe3, 0x75, 0x71, 0xb, 0xcc, 0xc1, 0xb6, 0x0, 0x98, 0xb, 0x26, 0xc0, 0xbb, 0x44, 0xed, 0xb, 0xa0, 0xf8, 0x7c, 0x73, 0x61, 0xb, 0xd9, 0x17, 0x50, 0xe0, 0x5f, 0x8a, 0xb, 0xe1, 0xbe, 0xda, 0x17, 0x10, 0xe2, 0x7f, 0xa, 0x22, 0x6e, 0x13, 0xb0, 0x2f, 0x0, 0xb8, 0xc5, 0xa7, 0xd0, 0xc9, 0x12, 0x39, 0x18, 0x7d, 0x5, 0x44, 0xbc, 0x7e, 0x89, 0xf5, 0x68, 0x35, 0x84, 0x80, 0x15, 0x36, 0xe1, 0xb, 0x2f, 0xd8, 0xf, 0x21, 0x60, 0xcf, 0xeb, 0x2f, 0xd0, 0x45, 0xcd, 0xb, 0xd6, 0x43, 0x8, 0x58, 0xf3, 0xfa, 0x1a, 0xdb, 0x43, 0xa7, 0x21, 0x4, 0x9c, 0xb0, 0x4d, 0x94, 0xf1, 0x82, 0xb4, 0x97, 0x0, 0xfc, 0x22, 0x67, 0x7f, 0x53, 0xc0, 0xbf, 0xf6, 0xcd, 0x6e, 0x5, 0x41, 0x20, 0xa, 0xc2, 0x7, 0xea, 0x26, 0xa2, 0x5f, 0x22, 0x4b, 0x2a, 0x92, 0xba, 0x2c, 0xf4, 0xfd, 0x9f, 0x2e, 0xc, 0xa, 0x44, 0xf2, 0x6b, 0x99, 0x85, 0xe3, 0x8a, 0x73, 0x6d, 0x37, 0x95, 0xbb, 0x67, 0xbe, 0x33, 0xe3, 0xfe, 0x13, 0xb8, 0xff, 0x9, 0xdd, 0x5f, 0x43, 0xf7, 0x83, 0x8, 0x8e, 0x62, 0xb0, 0x66, 0xc2, 0x51, 0xcc, 0x97, 0x11, 0x9b, 0x53, 0xe9, 0x32, 0xe2, 0xeb, 0x98, 0xed, 0xb9, 0x70, 0x1d, 0xf3, 0x40, 0xc2, 0x80, 0x62, 0x69, 0xc2, 0x40, 0xd2, 0x31, 0x92, 0xa1, 0x16, 0x87, 0xf7, 0x17, 0xba, 0x30, 0x13, 0x46, 0x32, 0x1a, 0x4a, 0x1, 0x52, 0xe5, 0x39, 0x40, 0x2a, 0x18, 0x4a, 0x61, 0x2c, 0x17, 0x44, 0x63, 0x39, 0x1b, 0x13, 0x49, 0x6c, 0x4c, 0xd8, 0x9a, 0x69, 0x62, 0x6b, 0xc6, 0xe6, 0x54, 0x12, 0x9b, 0x53, 0xb6, 0xe7, 0x9a, 0xd8, 0x9e, 0x33, 0xa0, 0x60, 0x6d, 0x8e, 0x59, 0x76, 0x4, 0x6, 0xe, 0x80, 0x42, 0x40, 0x34, 0x66, 0x57, 0xb8, 0xbc, 0x1, 0xd1, 0xa8, 0x90, 0xca, 0x56, 0x0, 0x14, 0x1, 0x52, 0xc9, 0x98, 0xce, 0xb6, 0x5f, 0xa6, 0x6d, 0xa, 0xa6, 0x63, 0x50, 0xc9, 0xcf, 0x2e, 0x43, 0x40, 0x65, 0xbf, 0x51, 0xad, 0xed, 0x1c, 0x60, 0xb5, 0x8e, 0xeb, 0x4f, 0x1, 0xb8, 0xbe, 0xff, 0xb, 0xb, 0xf7, 0x95, 0x4d, 0xd0, 0xd2, 0xaa, 0xac, 0xe7, 0x81, 0xb2, 0xfb, 0x91, 0xe6, 0xd2, 0x2a, 0x85, 0xb5, 0x9d, 0xff, 0xe2, 0xd2, 0x7d, 0x75, 0xeb, 0xbf, 0xbc, 0xf6, 0x5f, 0xdf, 0xc7, 0x9, 0x30, 0x94, 0xad, 0x0, 0x83, 0x6b, 0x84, 0x23, 0xdb, 0xa7, 0x15, 0x62, 0xf1, 0x8f, 0xf1, 0xf8, 0x7, 0x99, 0xe2, 0x46, 0xb9, 0xa2, 0x85, 0xd9, 0xa, 0x43, 0x15, 0x8f, 0xd6, 0xc7, 0xf2, 0x59, 0xc4, 0x38, 0xdf, 0xba, 0x3b, 0xce, 0xb7, 0x86, 0x38, 0x9f, 0x47, 0xa0, 0xb1, 0x48, 0x39, 0xd2, 0xc9, 0xa1, 0xd6, 0xe7, 0xb3, 0x3b, 0xd4, 0x7a, 0x9f, 0xa6, 0x1e, 0xeb, 0xed, 0x41, 0xb0, 0x59, 0x88, 0x76, 0x5f, 0x86, 0x12, 0x6e, 0xaf, 0x35, 0x39, 0x57, 0x41, 0x3a, 0x4f, 0x2c, 0xb6, 0x76, 0x7a, 0xc1, 0x41, 0xaf, 0x78, 0xcc, 0xab, 0x3f, 0x34, 0x6f, 0x57, 0x3c, 0x86, 0x52, 0x72, 0xf9, 0xd4, 0x7c, 0x6e, 0xbf, 0x6a, 0x3e, 0x37, 0xa8, 0xf9, 0xc4, 0x2d, 0x3a, 0x1d, 0x1a, 0x6f, 0x7c, 0x50, 0xd1, 0x29, 0xfd, 0xaa, 0xd7, 0xa8, 0x51, 0xa3, 0x46, 0xd, 0x4a, 0x2f, 0xb9, 0x87, 0x9d, 0xa4, 0x64, 0xb5, 0x51, 0x8a, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 0x00 };
+const unsigned char *defaultDialogIcons[] = { defaultDialogIcon0Name, defaultDialogIcon0Length, defaultDialogIcon0Data, defaultDialogIcon1Name, defaultDialogIcon1Length, defaultDialogIcon1Data, defaultDialogIcon2Name, defaultDialogIcon2Length, defaultDialogIcon2Data, defaultDialogIcon3Name, defaultDialogIcon3Length, defaultDialogIcon3Data, defaultDialogIcon4Name, defaultDialogIcon4Length, defaultDialogIcon4Data, defaultDialogIcon5Name, defaultDialogIcon5Length, defaultDialogIcon5Data, defaultDialogIcon6Name, defaultDialogIcon6Length, defaultDialogIcon6Data, defaultDialogIcon7Name, defaultDialogIcon7Length, defaultDialogIcon7Data, defaultDialogIcon8Name, defaultDialogIcon8Length, defaultDialogIcon8Data, defaultDialogIcon9Name, defaultDialogIcon9Length, defaultDialogIcon9Data, defaultDialogIcon10Name, defaultDialogIcon10Length, defaultDialogIcon10Data, defaultDialogIcon11Name, defaultDialogIcon11Length, defaultDialogIcon11Data, 0x00 };
diff --git a/v2/internal/ffenestri/ffenestri.go b/v2/internal/ffenestri/ffenestri.go
new file mode 100644
index 000000000..28c0c4bd8
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri.go
@@ -0,0 +1,185 @@
+package ffenestri
+
+import (
+ "github.com/wailsapp/wails/v2/internal/menumanager"
+ "runtime"
+ "strings"
+ "unsafe"
+
+ "github.com/wailsapp/wails/v2/internal/logger"
+ "github.com/wailsapp/wails/v2/internal/messagedispatcher"
+ "github.com/wailsapp/wails/v2/pkg/options"
+)
+
+/*
+
+#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
+#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
+
+#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
+#cgo darwin LDFLAGS: -framework WebKit -lobjc
+
+#include
+#include "ffenestri.h"
+
+
+*/
+import "C"
+
+// Application is our main application object
+type Application struct {
+ config *options.App
+ memory []unsafe.Pointer
+
+ // This is the main app pointer
+ app *C.struct_Application
+
+ // Manages menus
+ menuManager *menumanager.Manager
+
+ // Logger
+ logger logger.CustomLogger
+}
+
+func (a *Application) saveMemoryReference(mem unsafe.Pointer) {
+ a.memory = append(a.memory, mem)
+}
+
+func (a *Application) string2CString(str string) *C.char {
+ result := C.CString(str)
+ a.saveMemoryReference(unsafe.Pointer(result))
+ return result
+}
+
+func init() {
+ runtime.LockOSThread()
+}
+
+// NewApplicationWithConfig creates a new application based on the given config
+func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuManager *menumanager.Manager) *Application {
+ return &Application{
+ config: config,
+ logger: logger.CustomLogger("Ffenestri"),
+ menuManager: menuManager,
+ }
+}
+
+// NewApplication creates a new Application with the default config
+func NewApplication(logger *logger.Logger) *Application {
+ return &Application{
+ config: options.Default,
+ logger: logger.CustomLogger("Ffenestri"),
+ }
+}
+
+func (a *Application) freeMemory() {
+ for _, mem := range a.memory {
+ // fmt.Printf("Freeing memory: %+v\n", mem)
+ C.free(mem)
+ }
+}
+
+// bool2Cint converts a Go boolean to a C integer
+func (a *Application) bool2Cint(value bool) C.int {
+ if value {
+ return C.int(1)
+ }
+ return C.int(0)
+}
+
+// dispatcher is the interface to send messages to
+var dispatcher *messagedispatcher.DispatchClient
+
+// Dispatcher is what we register out client with
+type Dispatcher interface {
+ RegisterClient(client messagedispatcher.Client) *messagedispatcher.DispatchClient
+}
+
+// DispatchClient is the means for passing messages to the backend
+type DispatchClient interface {
+ SendMessage(string)
+}
+
+func intToColour(colour int) (C.int, C.int, C.int, C.int) {
+ var alpha = C.int(colour & 0xFF)
+ var blue = C.int((colour >> 8) & 0xFF)
+ var green = C.int((colour >> 16) & 0xFF)
+ var red = C.int((colour >> 24) & 0xFF)
+ return red, green, blue, alpha
+}
+
+// Run the application
+func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug bool) error {
+ title := a.string2CString(a.config.Title)
+ width := C.int(a.config.Width)
+ height := C.int(a.config.Height)
+ resizable := a.bool2Cint(!a.config.DisableResize)
+ devtools := a.bool2Cint(a.config.DevTools)
+ fullscreen := a.bool2Cint(a.config.Fullscreen)
+ startHidden := a.bool2Cint(a.config.StartHidden)
+ logLevel := C.int(a.config.LogLevel)
+ app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
+
+ // Save app reference
+ a.app = (*C.struct_Application)(app)
+
+ // Set Min Window Size
+ minWidth := C.int(a.config.MinWidth)
+ minHeight := C.int(a.config.MinHeight)
+ C.SetMinWindowSize(a.app, minWidth, minHeight)
+
+ // Set Max Window Size
+ maxWidth := C.int(a.config.MaxWidth)
+ maxHeight := C.int(a.config.MaxHeight)
+ C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
+
+ // Set debug if needed
+ C.SetDebug(app, a.bool2Cint(debug))
+
+ // TODO: Move frameless to Linux options
+ // if a.config.Frameless {
+ // C.DisableFrame(a.app)
+ // }
+
+ if a.config.RGBA != 0 {
+ r, g, b, alpha := intToColour(a.config.RGBA)
+ C.SetColour(a.app, r, g, b, alpha)
+ }
+
+ // Escape bindings so C doesn't freak out
+ bindings = strings.ReplaceAll(bindings, `"`, `\"`)
+
+ // Set bindings
+ C.SetBindings(app, a.string2CString(bindings))
+
+ // save the dispatcher in a package variable so that the C callbacks
+ // can access it
+ dispatcher = incomingDispatcher.RegisterClient(newClient(a))
+
+ // Process platform settings
+ err := a.processPlatformSettings()
+ if err != nil {
+ return err
+ }
+
+ // Check we could initialise the application
+ if app != nil {
+ // Yes - Save memory reference and run app, cleaning up afterwards
+ a.saveMemoryReference(unsafe.Pointer(app))
+ C.Run(app, 0, nil)
+ } else {
+ // Oh no! We couldn't initialise the application
+ a.logger.Fatal("Cannot initialise Application.")
+ }
+
+ a.freeMemory()
+ return nil
+}
+
+// messageFromWindowCallback is called by any messages sent in
+// webkit to window.external.invoke. It relays the message on to
+// the dispatcher.
+//export messageFromWindowCallback
+func messageFromWindowCallback(data *C.char) {
+ dispatcher.DispatchMessage(C.GoString(data))
+}
diff --git a/v2/internal/ffenestri/ffenestri.h b/v2/internal/ffenestri/ffenestri.h
new file mode 100644
index 000000000..3e4573158
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri.h
@@ -0,0 +1,43 @@
+#ifndef __FFENESTRI_H__
+#define __FFENESTRI_H__
+
+#include
+struct Application;
+
+extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
+extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
+extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
+extern void Run(struct Application*, int argc, char **argv);
+extern void DestroyApplication(struct Application*);
+extern void SetDebug(struct Application*, int flag);
+extern void SetBindings(struct Application*, const char *bindings);
+extern void ExecJS(struct Application*, const char *script);
+extern void Hide(struct Application*);
+extern void Show(struct Application*);
+extern void Center(struct Application*);
+extern void Maximise(struct Application*);
+extern void Unmaximise(struct Application*);
+extern void ToggleMaximise(struct Application*);
+extern void Minimise(struct Application*);
+extern void Unminimise(struct Application*);
+extern void ToggleMinimise(struct Application*);
+extern void SetColour(struct Application*, int red, int green, int blue, int alpha);
+extern void SetSize(struct Application*, int width, int height);
+extern void SetPosition(struct Application*, int x, int y);
+extern void Quit(struct Application*);
+extern void SetTitle(struct Application*, const char *title);
+extern void Fullscreen(struct Application*);
+extern void UnFullscreen(struct Application*);
+extern void ToggleFullscreen(struct Application*);
+extern void DisableFrame(struct Application*);
+extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
+extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
+extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
+extern void DarkModeEnabled(struct Application*, char *callbackID);
+extern void SetApplicationMenu(struct Application*, const char *);
+extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
+extern void UpdateTrayMenu(struct Application*, const char *menuTrayJSON);
+extern void AddContextMenu(struct Application*, char *contextMenuJSON);
+extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
+
+#endif
diff --git a/v2/internal/ffenestri/ffenestri_client.go b/v2/internal/ffenestri/ffenestri_client.go
new file mode 100644
index 000000000..8bbe0578f
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri_client.go
@@ -0,0 +1,197 @@
+package ffenestri
+
+/*
+
+#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
+#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
+
+#include
+#include "ffenestri.h"
+
+*/
+import "C"
+
+import (
+ "strconv"
+
+ "github.com/wailsapp/wails/v2/internal/logger"
+ "github.com/wailsapp/wails/v2/pkg/options"
+)
+
+// Client is our implentation of messageDispatcher.Client
+type Client struct {
+ app *Application
+ logger logger.CustomLogger
+}
+
+func newClient(app *Application) *Client {
+ return &Client{
+ app: app,
+ logger: app.logger,
+ }
+}
+
+// Quit the application
+func (c *Client) Quit() {
+ c.app.logger.Trace("Got shutdown message")
+ C.Quit(c.app.app)
+}
+
+// NotifyEvent will pass on the event message to the frontend
+func (c *Client) NotifyEvent(message string) {
+ eventMessage := `window.wails._.Notify(` + strconv.Quote(message) + `);`
+ c.app.logger.Trace("eventMessage = %+v", eventMessage)
+ C.ExecJS(c.app.app, c.app.string2CString(eventMessage))
+}
+
+// CallResult contains the result of the call from JS
+func (c *Client) CallResult(message string) {
+ callbackMessage := `window.wails._.Callback(` + strconv.Quote(message) + `);`
+ c.app.logger.Trace("callbackMessage = %+v", callbackMessage)
+ C.ExecJS(c.app.app, c.app.string2CString(callbackMessage))
+}
+
+// WindowSetTitle sets the window title to the given string
+func (c *Client) WindowSetTitle(title string) {
+ C.SetTitle(c.app.app, c.app.string2CString(title))
+}
+
+// WindowFullscreen will set the window to be fullscreen
+func (c *Client) WindowFullscreen() {
+ C.Fullscreen(c.app.app)
+}
+
+// WindowUnFullscreen will unfullscreen the window
+func (c *Client) WindowUnFullscreen() {
+ C.UnFullscreen(c.app.app)
+}
+
+// WindowShow will show the window
+func (c *Client) WindowShow() {
+ C.Show(c.app.app)
+}
+
+// WindowHide will hide the window
+func (c *Client) WindowHide() {
+ C.Hide(c.app.app)
+}
+
+// WindowCenter will hide the window
+func (c *Client) WindowCenter() {
+ C.Center(c.app.app)
+}
+
+// WindowMaximise will maximise the window
+func (c *Client) WindowMaximise() {
+ C.Maximise(c.app.app)
+}
+
+// WindowMinimise will minimise the window
+func (c *Client) WindowMinimise() {
+ C.Minimise(c.app.app)
+}
+
+// WindowUnmaximise will unmaximise the window
+func (c *Client) WindowUnmaximise() {
+ C.Unmaximise(c.app.app)
+}
+
+// WindowUnminimise will unminimise the window
+func (c *Client) WindowUnminimise() {
+ C.Unminimise(c.app.app)
+}
+
+// WindowPosition will position the window to x,y on the
+// monitor that the window is mostly on
+func (c *Client) WindowPosition(x int, y int) {
+ C.SetPosition(c.app.app, C.int(x), C.int(y))
+}
+
+// WindowSize will resize the window to the given
+// width and height
+func (c *Client) WindowSize(width int, height int) {
+ C.SetSize(c.app.app, C.int(width), C.int(height))
+}
+
+// WindowSetColour sets the window colour
+func (c *Client) WindowSetColour(colour int) {
+ r, g, b, a := intToColour(colour)
+ C.SetColour(c.app.app, r, g, b, a)
+}
+
+// OpenDialog will open a dialog with the given title and filter
+func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
+ C.OpenDialog(c.app.app,
+ c.app.string2CString(callbackID),
+ c.app.string2CString(dialogOptions.Title),
+ c.app.string2CString(dialogOptions.Filters),
+ c.app.string2CString(dialogOptions.DefaultFilename),
+ c.app.string2CString(dialogOptions.DefaultDirectory),
+ c.app.bool2Cint(dialogOptions.AllowFiles),
+ c.app.bool2Cint(dialogOptions.AllowDirectories),
+ c.app.bool2Cint(dialogOptions.AllowMultiple),
+ c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
+ c.app.bool2Cint(dialogOptions.CanCreateDirectories),
+ c.app.bool2Cint(dialogOptions.ResolvesAliases),
+ c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
+ )
+}
+
+// SaveDialog will open a dialog with the given title and filter
+func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
+ C.SaveDialog(c.app.app,
+ c.app.string2CString(callbackID),
+ c.app.string2CString(dialogOptions.Title),
+ c.app.string2CString(dialogOptions.Filters),
+ c.app.string2CString(dialogOptions.DefaultFilename),
+ c.app.string2CString(dialogOptions.DefaultDirectory),
+ c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
+ c.app.bool2Cint(dialogOptions.CanCreateDirectories),
+ c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
+ )
+}
+
+// MessageDialog will open a message dialog with the given options
+func (c *Client) MessageDialog(dialogOptions *options.MessageDialog, callbackID string) {
+
+ // Sanity check button length
+ if len(dialogOptions.Buttons) > 4 {
+ c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons))
+ return
+ }
+
+ // Process buttons
+ buttons := []string{"", "", "", ""}
+ for i, button := range dialogOptions.Buttons {
+ buttons[i] = button
+ }
+
+ C.MessageDialog(c.app.app,
+ c.app.string2CString(callbackID),
+ c.app.string2CString(string(dialogOptions.Type)),
+ c.app.string2CString(dialogOptions.Title),
+ c.app.string2CString(dialogOptions.Message),
+ c.app.string2CString(dialogOptions.Icon),
+ c.app.string2CString(buttons[0]),
+ c.app.string2CString(buttons[1]),
+ c.app.string2CString(buttons[2]),
+ c.app.string2CString(buttons[3]),
+ c.app.string2CString(dialogOptions.DefaultButton),
+ c.app.string2CString(dialogOptions.CancelButton))
+}
+
+func (c *Client) DarkModeEnabled(callbackID string) {
+ C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
+}
+
+func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
+ C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
+}
+
+func (c *Client) UpdateTrayMenu(trayMenuJSON string) {
+ C.UpdateTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
+}
+
+func (c *Client) UpdateContextMenu(contextMenuJSON string) {
+ C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
+}
diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c
new file mode 100644
index 000000000..5367032ec
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri_darwin.c
@@ -0,0 +1,1724 @@
+
+#ifdef FFENESTRI_DARWIN
+
+#include "ffenestri_darwin.h"
+#include "menu_darwin.h"
+#include "contextmenus_darwin.h"
+#include "traymenustore_darwin.h"
+#include "traymenu_darwin.h"
+
+// References to assets
+#include "assets.h"
+extern const unsigned char runtime;
+
+// Dialog icons
+extern const unsigned char *defaultDialogIcons[];
+#include "userdialogicons.h"
+
+// MAIN DEBUG FLAG
+int debug;
+
+// A cache for all our dialog icons
+struct hashmap_s dialogIconCache;
+
+// Context menu data is given by the frontend when clicking a context menu.
+// We send this to the backend when an item is selected;
+const char *contextMenuData;
+
+// Dispatch Method
+typedef void (^dispatchMethod)(void);
+
+// dispatch will execute the given `func` pointer
+void dispatch(dispatchMethod func) {
+ dispatch_async(dispatch_get_main_queue(), func);
+}
+// yes command simply returns YES!
+BOOL yes(id self, SEL cmd)
+{
+ return YES;
+}
+
+// Prints a hashmap entry
+int hashmap_log(void *const context, struct hashmap_element_s *const e) {
+ printf("%s: %p ", (char*)e->key, e->data);
+ return 0;
+}
+
+// Utility function to visualise a hashmap
+void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
+ printf("%s = { ", name);
+ if (0!=hashmap_iterate_pairs(hashmap, hashmap_log, NULL)) {
+ fprintf(stderr, "Failed to dump hashmap entries\n");
+ }
+ printf("}\n");
+}
+
+extern void messageFromWindowCallback(const char *);
+typedef void (*ffenestriCallback)(const char *);
+
+void HideMouse() {
+ msg(c("NSCursor"), s("hide"));
+}
+
+void ShowMouse() {
+ msg(c("NSCursor"), s("unhide"));
+}
+
+struct Application {
+
+ // Cocoa data
+ id application;
+ id delegate;
+ id mainWindow;
+ id wkwebview;
+ id manager;
+ id config;
+ id mouseEvent;
+ id mouseDownMonitor;
+ id mouseUpMonitor;
+
+ // Window Data
+ const char *title;
+ int width;
+ int height;
+ int minWidth;
+ int minHeight;
+ int maxWidth;
+ int maxHeight;
+ int resizable;
+ int devtools;
+ int fullscreen;
+ int red;
+ int green;
+ int blue;
+ int alpha;
+ int webviewIsTranparent;
+ const char *appearance;
+ int decorations;
+ int logLevel;
+
+ // Features
+ int frame;
+ int startHidden;
+ int maximised;
+ int titlebarAppearsTransparent;
+ int hideTitle;
+ int hideTitleBar;
+ int fullSizeContent;
+ int useToolBar;
+ int hideToolbarSeparator;
+ int windowBackgroundIsTranslucent;
+
+ // Menu
+ Menu *applicationMenu;
+
+ // Tray
+ TrayMenuStore* trayMenuStore;
+
+ // Context Menus
+ ContextMenuStore *contextMenuStore;
+
+ // Callback
+ ffenestriCallback sendMessageToBackend;
+
+ // Bindings
+ const char *bindings;
+
+};
+
+// Debug works like sprintf but mutes if the global debug flag is true
+// Credit: https://stackoverflow.com/a/20639708
+
+#define MAXMESSAGE 1024*10
+char logbuffer[MAXMESSAGE];
+
+void Debug(struct Application *app, const char *message, ... ) {
+ if ( debug ) {
+ const char *temp = concat("LTFfenestri (C) | ", message);
+ va_list args;
+ va_start(args, message);
+ vsnprintf(logbuffer, MAXMESSAGE, temp, args);
+ app->sendMessageToBackend(&logbuffer[0]);
+ MEMFREE(temp);
+ va_end(args);
+ }
+}
+
+void Fatal(struct Application *app, const char *message, ... ) {
+ const char *temp = concat("LFFfenestri (C) | ", message);
+ va_list args;
+ va_start(args, message);
+ vsnprintf(logbuffer, MAXMESSAGE, temp, args);
+ app->sendMessageToBackend(&logbuffer[0]);
+ MEMFREE(temp);
+ va_end(args);
+}
+
+bool isRetina(struct Application *app) {
+ CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow);
+ if( (int)scale == 1 ) {
+ return false;
+ }
+ return true;
+}
+
+void TitlebarAppearsTransparent(struct Application* app) {
+ app->titlebarAppearsTransparent = 1;
+}
+
+void HideTitle(struct Application *app) {
+ app->hideTitle = 1;
+}
+
+void HideTitleBar(struct Application *app) {
+ app->hideTitleBar = 1;
+}
+
+void HideToolbarSeparator(struct Application *app) {
+ app->hideToolbarSeparator = 1;
+}
+
+void UseToolbar(struct Application *app) {
+ app->useToolBar = 1;
+}
+
+// WebviewIsTransparent will make the webview transparent
+// revealing the Cocoa window underneath
+void WebviewIsTransparent(struct Application *app) {
+ app->webviewIsTranparent = 1;
+}
+
+// SetAppearance will set the window's Appearance to the
+// given value
+void SetAppearance(struct Application *app, const char *appearance) {
+ app->appearance = appearance;
+}
+
+
+void applyWindowColour(struct Application *app) {
+ // Apply the colour only if the window has been created
+ if( app->mainWindow != NULL ) {
+ ON_MAIN_THREAD(
+ id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
+ (float)app->red / 255.0,
+ (float)app->green / 255.0,
+ (float)app->blue / 255.0,
+ (float)app->alpha / 255.0);
+ msg(app->mainWindow, s("setBackgroundColor:"), colour);
+ );
+ }
+}
+
+void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
+ app->red = red;
+ app->green = green;
+ app->blue = blue;
+ app->alpha = alpha;
+
+ applyWindowColour(app);
+}
+
+void FullSizeContent(struct Application *app) {
+ app->fullSizeContent = 1;
+}
+
+void Hide(struct Application *app) {
+ ON_MAIN_THREAD(
+ msg(app->application, s("hide:"))
+ );
+}
+
+void Show(struct Application *app) {
+ ON_MAIN_THREAD(
+ msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL);
+ msg(app->application, s("activateIgnoringOtherApps:"), YES);
+ );
+}
+
+void WindowBackgroundIsTranslucent(struct Application *app) {
+ app->windowBackgroundIsTranslucent = 1;
+}
+
+// Sends messages to the backend
+void messageHandler(id self, SEL cmd, id contentController, id message) {
+ struct Application *app = (struct Application *)objc_getAssociatedObject(
+ self, "application");
+ const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
+ if( strcmp(name, "completed") == 0) {
+ // Delete handler
+ msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
+
+ // TODO: Notify backend we're ready and get them to call back for the Show()
+ if (app->startHidden == 0) {
+ Show(app);
+ }
+
+ // TODO: Check this actually does reduce flicker
+ msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
+ } else if( strcmp(name, "windowDrag") == 0 ) {
+ // Guard against null events
+ if( app->mouseEvent != NULL ) {
+ HideMouse();
+ ON_MAIN_THREAD(
+ msg(app->mainWindow, s("performWindowDragWithEvent:"), app->mouseEvent);
+ );
+ }
+ } else if( strcmp(name, "contextMenu") == 0 ) {
+
+ // Did we get a context menu selector?
+ if( message == NULL) {
+ return;
+ }
+
+ const char *contextMenuMessage = cstr(msg(message, s("body")));
+
+ if( contextMenuMessage == NULL ) {
+ Debug(app, "EMPTY CONTEXT MENU MESSAGE!!\n");
+ return;
+ }
+
+ // Parse the message
+ JsonNode *contextMenuMessageJSON = json_decode(contextMenuMessage);
+ if( contextMenuMessageJSON == NULL ) {
+ Debug(app, "Error decoding context menu message: %s", contextMenuMessage);
+ return;
+ }
+
+ // Get menu ID
+ JsonNode *contextMenuIDNode = json_find_member(contextMenuMessageJSON, "id");
+ if( contextMenuIDNode == NULL ) {
+ Debug(app, "Error decoding context menu ID: %s", contextMenuMessage);
+ json_delete(contextMenuMessageJSON);
+ return;
+ }
+ if( contextMenuIDNode->tag != JSON_STRING ) {
+ Debug(app, "Error decoding context menu ID (Not a string): %s", contextMenuMessage);
+ json_delete(contextMenuMessageJSON);
+ return;
+ }
+
+ // Get menu Data
+ JsonNode *contextMenuDataNode = json_find_member(contextMenuMessageJSON, "data");
+ if( contextMenuDataNode == NULL ) {
+ Debug(app, "Error decoding context menu data: %s", contextMenuMessage);
+ json_delete(contextMenuMessageJSON);
+ return;
+ }
+ if( contextMenuDataNode->tag != JSON_STRING ) {
+ Debug(app, "Error decoding context menu data (Not a string): %s", contextMenuMessage);
+ json_delete(contextMenuMessageJSON);
+ return;
+ }
+
+ // We need to copy these as the JSON node will be destroyed on this thread and the
+ // string data will become corrupt. These need to be freed by the context menu code.
+ const char* contextMenuID = STRCOPY(contextMenuIDNode->string_);
+ const char* contextMenuData = STRCOPY(contextMenuDataNode->string_);
+
+ ON_MAIN_THREAD(
+ ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
+ );
+
+ json_delete(contextMenuMessageJSON);
+
+ } else {
+ // const char *m = (const char *)msg(msg(message, s("body")), s("UTF8String"));
+ const char *m = cstr(msg(message, s("body")));
+ app->sendMessageToBackend(m);
+ }
+}
+
+// closeWindow is called when the close button is pressed
+void closeWindow(id self, SEL cmd, id sender) {
+ struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
+ app->sendMessageToBackend("WC");
+}
+
+bool isDarkMode(struct Application *app) {
+ id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults"));
+ const char *mode = cstr(msg(userDefaults, s("stringForKey:"), str("AppleInterfaceStyle")));
+ return ( mode != NULL && strcmp(mode, "Dark") == 0 );
+}
+
+void ExecJS(struct Application *app, const char *js) {
+ ON_MAIN_THREAD(
+ msg(app->wkwebview,
+ s("evaluateJavaScript:completionHandler:"),
+ str(js),
+ NULL);
+ );
+}
+
+void willFinishLaunching(id self, SEL cmd, id sender) {
+ struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
+ messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
+}
+
+void emitThemeChange(struct Application *app) {
+ bool currentThemeIsDark = isDarkMode(app);
+ if (currentThemeIsDark) {
+ messageFromWindowCallback("Ej{\"name\":\"wails:system:themechange\",\"data\":[true]}");
+ } else {
+ messageFromWindowCallback("Ej{\"name\":\"wails:system:themechange\",\"data\":[false]}");
+ }
+}
+
+void themeChanged(id self, SEL cmd, id sender) {
+ struct Application *app = (struct Application *)objc_getAssociatedObject(
+ self, "application");
+// emitThemeChange(app);
+ bool currentThemeIsDark = isDarkMode(app);
+ if ( currentThemeIsDark ) {
+ ExecJS(app, "window.wails.Events.Emit( 'wails:system:themechange', true );");
+ } else {
+ ExecJS(app, "window.wails.Events.Emit( 'wails:system:themechange', false );");
+ }
+}
+
+// void willFinishLaunching(id self) {
+// struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
+// Debug(app, "willFinishLaunching called!");
+// }
+
+
+int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
+ msg(e->data, s("release"));
+ return -1;
+}
+
+void destroyContextMenus(struct Application *app) {
+ DeleteContextMenuStore(app->contextMenuStore);
+}
+
+void freeDialogIconCache(struct Application *app) {
+ // Release the dialog cache images
+ if( hashmap_num_entries(&dialogIconCache) > 0 ) {
+ if (0!=hashmap_iterate_pairs(&dialogIconCache, releaseNSObject, NULL)) {
+ Fatal(app, "failed to release hashmap entries!");
+ }
+ }
+
+ //Free radio groups hashmap
+ hashmap_destroy(&dialogIconCache);
+}
+
+void DestroyApplication(struct Application *app) {
+ Debug(app, "Destroying Application");
+
+ // Free the bindings
+ if (app->bindings != NULL) {
+ MEMFREE(app->bindings);
+ } else {
+ Debug(app, "Almost a double free for app->bindings");
+ }
+
+ // Remove mouse monitors
+ if( app->mouseDownMonitor != NULL ) {
+ msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor);
+ }
+ if( app->mouseUpMonitor != NULL ) {
+ msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
+ }
+
+ // Delete the application menu if we have one
+ if( app->applicationMenu != NULL ) {
+ DeleteMenu(app->applicationMenu);
+ }
+
+ // Delete the tray menu store
+ DeleteTrayMenuStore(app->trayMenuStore);
+
+ // Delete the context menu store
+ DeleteContextMenuStore(app->contextMenuStore);
+
+ // Destroy the context menus
+ destroyContextMenus(app);
+
+ // Free dialog icon cache
+ freeDialogIconCache(app);
+
+ // Unload the tray Icons
+ UnloadTrayIcons();
+
+ // Remove script handlers
+ msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
+ msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
+ msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
+
+ // Close main window
+ msg(app->mainWindow, s("close"));
+
+ // Terminate app
+ msg(c("NSApp"), s("terminate:"), NULL);
+ Debug(app, "Finished Destroying Application");
+}
+
+// Quit will stop the cocoa application and free up all the memory
+// used by the application
+void Quit(struct Application *app) {
+ Debug(app, "Quit Called");
+ DestroyApplication(app);
+}
+
+// SetTitle sets the main window title to the given string
+void SetTitle(struct Application *app, const char *title) {
+ Debug(app, "SetTitle Called");
+ ON_MAIN_THREAD(
+ msg(app->mainWindow, s("setTitle:"), str(title));
+ );
+}
+
+void ToggleFullscreen(struct Application *app) {
+ ON_MAIN_THREAD(
+ app->fullscreen = !app->fullscreen;
+ MAIN_WINDOW_CALL("toggleFullScreen:");
+ );
+}
+
+bool isFullScreen(struct Application *app) {
+ int mask = (int)msg(app->mainWindow, s("styleMask"));
+ bool result = (mask & NSWindowStyleMaskFullscreen) == NSWindowStyleMaskFullscreen;
+ return result;
+}
+
+// Fullscreen sets the main window to be fullscreen
+void Fullscreen(struct Application *app) {
+ Debug(app, "Fullscreen Called");
+ if( ! isFullScreen(app) ) {
+ ToggleFullscreen(app);
+ }
+}
+
+// UnFullscreen resets the main window after a fullscreen
+void UnFullscreen(struct Application *app) {
+ Debug(app, "UnFullscreen Called");
+ if( isFullScreen(app) ) {
+ ToggleFullscreen(app);
+ }
+}
+
+void Center(struct Application *app) {
+ Debug(app, "Center Called");
+ ON_MAIN_THREAD(
+ MAIN_WINDOW_CALL("center");
+ );
+}
+
+void ToggleMaximise(struct Application *app) {
+ ON_MAIN_THREAD(
+ app->maximised = !app->maximised;
+ MAIN_WINDOW_CALL("zoom:");
+ );
+}
+
+void Maximise(struct Application *app) {
+ if( app->maximised == 0) {
+ ToggleMaximise(app);
+ }
+}
+
+void Unmaximise(struct Application *app) {
+ if( app->maximised == 1) {
+ ToggleMaximise(app);
+ }
+}
+
+void Minimise(struct Application *app) {
+ ON_MAIN_THREAD(
+ MAIN_WINDOW_CALL("miniaturize:");
+ );
+ }
+void Unminimise(struct Application *app) {
+ ON_MAIN_THREAD(
+ MAIN_WINDOW_CALL("deminiaturize:");
+ );
+}
+
+id getCurrentScreen(struct Application *app) {
+ id screen = NULL;
+ screen = msg(app->mainWindow, s("screen"));
+ if( screen == NULL ) {
+ screen = msg(c("NSScreen"), u("mainScreen"));
+ }
+ return screen;
+}
+
+void dumpFrame(struct Application *app, const char *message, CGRect frame) {
+ Debug(app, message);
+ Debug(app, "origin.x %f", frame.origin.x);
+ Debug(app, "origin.y %f", frame.origin.y);
+ Debug(app, "size.width %f", frame.size.width);
+ Debug(app, "size.height %f", frame.size.height);
+}
+
+void SetSize(struct Application *app, int width, int height) {
+ ON_MAIN_THREAD(
+ id screen = getCurrentScreen(app);
+
+ // Get the rect for the window
+ CGRect frame = GET_FRAME(app->mainWindow);
+
+ // Credit: https://github.com/patr0nus/DeskGap/blob/73c0ac9f2c73f55b6e81f64f6673a7962b5719cd/lib/src/platform/mac/util/NSScreen%2BGeometry.m
+ frame.origin.y = (frame.origin.y + frame.size.height) - (float)height;
+ frame.size.width = (float)width;
+ frame.size.height = (float)height;
+
+ msg(app->mainWindow, s("setFrame:display:animate:"), frame, 1, 0);
+ );
+}
+
+void SetPosition(struct Application *app, int x, int y) {
+ ON_MAIN_THREAD(
+ id screen = getCurrentScreen(app);
+ CGRect screenFrame = GET_FRAME(screen);
+ CGRect windowFrame = GET_FRAME(app->mainWindow);
+
+ windowFrame.origin.x = screenFrame.origin.x + (float)x;
+ windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y;
+ msg(app->mainWindow, s("setFrame:display:animate:"), windowFrame, 1, 0);
+ );
+}
+
+void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *defaultButton) {
+ // If this button is set
+ if( STR_HAS_CHARS(buttonTitle) ) {
+ id button = msg(alert, s("addButtonWithTitle:"), str(buttonTitle));
+ if ( STREQ( buttonTitle, defaultButton) ) {
+ msg(button, s("setKeyEquivalent:"), str("\r"));
+ }
+ if ( STREQ( buttonTitle, cancelButton) ) {
+ msg(button, s("setKeyEquivalent:"), str("\033"));
+ }
+ }
+}
+
+extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
+ ON_MAIN_THREAD(
+ id alert = ALLOC_INIT("NSAlert");
+ char *dialogType = type;
+ char *dialogIcon = type;
+
+ // Default to info type
+ if( dialogType == NULL ) {
+ dialogType = "info";
+ }
+
+ // Set the dialog style
+ if( STREQ(dialogType, "info") || STREQ(dialogType, "question") ) {
+ msg(alert, s("setAlertStyle:"), NSAlertStyleInformational);
+ } else if( STREQ(dialogType, "warning") ) {
+ msg(alert, s("setAlertStyle:"), NSAlertStyleWarning);
+ } else if( STREQ(dialogType, "error") ) {
+ msg(alert, s("setAlertStyle:"), NSAlertStyleCritical);
+ }
+
+ // Set title if given
+ if( strlen(title) > 0 ) {
+ msg(alert, s("setMessageText:"), str(title));
+ }
+
+ // Set message if given
+ if( strlen(message) > 0) {
+ msg(alert, s("setInformativeText:"), str(message));
+ }
+
+ // Process buttons
+ processDialogButton(alert, button1, cancelButton, defaultButton);
+ processDialogButton(alert, button2, cancelButton, defaultButton);
+ processDialogButton(alert, button3, cancelButton, defaultButton);
+ processDialogButton(alert, button4, cancelButton, defaultButton);
+
+ // Check for custom dialog icon
+ if( strlen(icon) > 0 ) {
+ dialogIcon = icon;
+ }
+
+ // Determine what dialog icon we are looking for
+ id dialogImage = NULL;
+ // Look for `name-theme2x` first
+ char *themeIcon = concat(dialogIcon, (isDarkMode(app) ? "-dark" : "-light") );
+ if( isRetina(app) ) {
+ char *dialogIcon2x = concat(themeIcon, "2x");
+ dialogImage = hashmap_get(&dialogIconCache, dialogIcon2x, strlen(dialogIcon2x));
+// if (dialogImage != NULL ) printf("Using %s\n", dialogIcon2x);
+ MEMFREE(dialogIcon2x);
+
+ // Now look for non-themed icon `name2x`
+ if ( dialogImage == NULL ) {
+ dialogIcon2x = concat(dialogIcon, "2x");
+ dialogImage = hashmap_get(&dialogIconCache, dialogIcon2x, strlen(dialogIcon2x));
+// if (dialogImage != NULL ) printf("Using %s\n", dialogIcon2x);
+ MEMFREE(dialogIcon2x);
+ }
+ }
+
+ // If we don't have a retina icon, try the 1x name-theme icon
+ if( dialogImage == NULL ) {
+ dialogImage = hashmap_get(&dialogIconCache, themeIcon, strlen(themeIcon));
+// if (dialogImage != NULL ) printf("Using %s\n", themeIcon);
+ }
+
+ // Free the theme icon memory
+ MEMFREE(themeIcon);
+
+ // Finally try the name itself
+ if( dialogImage == NULL ) {
+ dialogImage = hashmap_get(&dialogIconCache, dialogIcon, strlen(dialogIcon));
+// if (dialogImage != NULL ) printf("Using %s\n", dialogIcon);
+ }
+
+ if (dialogImage != NULL ) {
+ msg(alert, s("setIcon:"), dialogImage);
+ }
+
+ // Run modal
+ char *buttonPressed;
+ int response = (int)msg(alert, s("runModal"));
+ if( response == NSAlertFirstButtonReturn ) {
+ buttonPressed = button1;
+ }
+ else if( response == NSAlertSecondButtonReturn ) {
+ buttonPressed = button2;
+ }
+ else if( response == NSAlertThirdButtonReturn ) {
+ buttonPressed = button3;
+ }
+ else {
+ buttonPressed = button4;
+ }
+
+ // Construct callback message. Format "DS|"
+ const char *callback = concat("DM", callbackID);
+ const char *header = concat(callback, "|");
+ const char *responseMessage = concat(header, buttonPressed);
+
+ // Send message to backend
+ app->sendMessageToBackend(responseMessage);
+
+ // Free memory
+ MEMFREE(header);
+ MEMFREE(callback);
+ MEMFREE(responseMessage);
+ );
+}
+
+// OpenDialog opens a dialog to select files/directories
+void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
+ Debug(app, "OpenDialog Called with callback id: %s", callbackID);
+
+ // Create an open panel
+ ON_MAIN_THREAD(
+
+ // Create the dialog
+ id dialog = msg(c("NSOpenPanel"), s("openPanel"));
+
+ // Valid but appears to do nothing.... :/
+ msg(dialog, s("setTitle:"), str(title));
+
+ // Filters
+ if( filters != NULL && strlen(filters) > 0) {
+ id filterString = msg(str(filters), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str(""));
+ filterString = msg(filterString, s("stringByReplacingOccurrencesOfString:withString:"), str(" "), str(""));
+ id filterList = msg(filterString, s("componentsSeparatedByString:"), str(","));
+ msg(dialog, s("setAllowedFileTypes:"), filterList);
+ } else {
+ msg(dialog, s("setAllowsOtherFileTypes:"), YES);
+ }
+
+ // Default Directory
+ if( defaultDir != NULL && strlen(defaultDir) > 0 ) {
+ msg(dialog, s("setDirectoryURL:"), url(defaultDir));
+ }
+
+ // Default Filename
+ if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) {
+ msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename));
+ }
+
+ // Setup Options
+ msg(dialog, s("setCanChooseFiles:"), allowFiles);
+ msg(dialog, s("setCanChooseDirectories:"), allowDirs);
+ msg(dialog, s("setAllowsMultipleSelection:"), allowMultiple);
+ msg(dialog, s("setShowsHiddenFiles:"), showHiddenFiles);
+ msg(dialog, s("setCanCreateDirectories:"), canCreateDirectories);
+ msg(dialog, s("setResolvesAliases:"), resolvesAliases);
+ msg(dialog, s("setTreatsFilePackagesAsDirectories:"), treatPackagesAsDirectories);
+
+ // Setup callback handler
+ msg(dialog, s("beginSheetModalForWindow:completionHandler:"), app->mainWindow, ^(id result) {
+
+ // Create the response JSON object
+ JsonNode *response = json_mkarray();
+
+ // If the user selected some files
+ if( result == (id)1 ) {
+ // Grab the URLs returned
+ id urls = msg(dialog, s("URLs"));
+
+ // Iterate over all the selected files
+ long noOfResults = (long)msg(urls, s("count"));
+ for( int index = 0; index < noOfResults; index++ ) {
+
+ // Extract the filename
+ id url = msg(urls, s("objectAtIndex:"), index);
+ const char *filename = (const char *)msg(msg(url, s("path")), s("UTF8String"));
+
+ // Add the the response array
+ json_append_element(response, json_mkstring(filename));
+ }
+ }
+
+ // Create JSON string and free json memory
+ char *encoded = json_stringify(response, "");
+ json_delete(response);
+
+ // Construct callback message. Format "D|"
+ const char *callback = concat("DO", callbackID);
+ const char *header = concat(callback, "|");
+ const char *responseMessage = concat(header, encoded);
+
+ // Send message to backend
+ app->sendMessageToBackend(responseMessage);
+
+ // Free memory
+ MEMFREE(header);
+ MEMFREE(callback);
+ MEMFREE(responseMessage);
+ });
+
+ msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
+ );
+}
+
+// SaveDialog opens a dialog to select files/directories
+void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
+ Debug(app, "SaveDialog Called with callback id: %s", callbackID);
+
+ // Create an open panel
+ ON_MAIN_THREAD(
+
+ // Create the dialog
+ id dialog = msg(c("NSSavePanel"), s("savePanel"));
+
+ // Valid but appears to do nothing.... :/
+ msg(dialog, s("setTitle:"), str(title));
+
+ // Filters
+ if( filters != NULL && strlen(filters) > 0) {
+ id filterString = msg(str(filters), s("stringByReplacingOccurrencesOfString:withString:"), str("*."), str(""));
+ filterString = msg(filterString, s("stringByReplacingOccurrencesOfString:withString:"), str(" "), str(""));
+ id filterList = msg(filterString, s("componentsSeparatedByString:"), str(","));
+ msg(dialog, s("setAllowedFileTypes:"), filterList);
+ } else {
+ msg(dialog, s("setAllowsOtherFileTypes:"), YES);
+ }
+
+ // Default Directory
+ if( defaultDir != NULL && strlen(defaultDir) > 0 ) {
+ msg(dialog, s("setDirectoryURL:"), url(defaultDir));
+ }
+
+ // Default Filename
+ if( defaultFilename != NULL && strlen(defaultFilename) > 0 ) {
+ msg(dialog, s("setNameFieldStringValue:"), str(defaultFilename));
+ }
+
+ // Setup Options
+ msg(dialog, s("setShowsHiddenFiles:"), showHiddenFiles);
+ msg(dialog, s("setCanCreateDirectories:"), canCreateDirectories);
+ msg(dialog, s("setTreatsFilePackagesAsDirectories:"), treatPackagesAsDirectories);
+
+ // Setup callback handler
+ msg(dialog, s("beginSheetModalForWindow:completionHandler:"), app->mainWindow, ^(id result) {
+
+ // Default is blank
+ const char *filename = "";
+
+ // If the user selected some files
+ if( result == (id)1 ) {
+ // Grab the URL returned
+ id url = msg(dialog, s("URL"));
+ filename = (const char *)msg(msg(url, s("path")), s("UTF8String"));
+ }
+
+ // Construct callback message. Format "DS|"
+ const char *callback = concat("DS", callbackID);
+ const char *header = concat(callback, "|");
+ const char *responseMessage = concat(header, filename);
+
+ // Send message to backend
+ app->sendMessageToBackend(responseMessage);
+
+ // Free memory
+ MEMFREE(header);
+ MEMFREE(callback);
+ MEMFREE(responseMessage);
+ });
+
+ msg( c("NSApp"), s("runModalForWindow:"), app->mainWindow);
+ );
+}
+
+const char *invoke = "window.external={invoke:function(x){window.webkit.messageHandlers.external.postMessage(x);}};";
+
+// DisableFrame disables the window frame
+void DisableFrame(struct Application *app)
+{
+ app->frame = 0;
+}
+
+void setMinMaxSize(struct Application *app)
+{
+ if (app->maxHeight > 0 && app->maxWidth > 0)
+ {
+ msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight));
+ }
+ if (app->minHeight > 0 && app->minWidth > 0)
+ {
+ msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
+ }
+}
+
+void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
+{
+ app->minWidth = minWidth;
+ app->minHeight = minHeight;
+
+ // Apply if the window is created
+ if( app->mainWindow != NULL ) {
+ ON_MAIN_THREAD(
+ setMinMaxSize(app);
+ );
+ }
+}
+
+void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
+{
+ app->maxWidth = maxWidth;
+ app->maxHeight = maxHeight;
+
+ // Apply if the window is created
+ if( app->mainWindow != NULL ) {
+ ON_MAIN_THREAD(
+ setMinMaxSize(app);
+ );
+ }
+}
+
+
+void SetDebug(void *applicationPointer, int flag) {
+ debug = flag;
+}
+
+
+// SetContextMenus sets the context menu map for this application
+void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
+ AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
+}
+
+void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
+ UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
+}
+
+void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
+ AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
+}
+
+void UpdateTrayMenu(struct Application *app, const char* trayMenuJSON) {
+ UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
+}
+
+void SetBindings(struct Application *app, const char *bindings) {
+ const char* temp = concat("window.wailsbindings = \"", bindings);
+ const char* jscall = concat(temp, "\";");
+ MEMFREE(temp);
+ app->bindings = jscall;
+}
+
+void makeWindowBackgroundTranslucent(struct Application *app) {
+ id contentView = msg(app->mainWindow, s("contentView"));
+ id effectView = msg(c("NSVisualEffectView"), s("alloc"));
+ CGRect bounds = GET_BOUNDS(contentView);
+ effectView = msg(effectView, s("initWithFrame:"), bounds);
+
+ msg(effectView, s("setAutoresizingMask:"), NSViewWidthSizable | NSViewHeightSizable);
+ msg(effectView, s("setBlendingMode:"), NSVisualEffectBlendingModeBehindWindow);
+ msg(effectView, s("setState:"), NSVisualEffectStateActive);
+ msg(contentView, s("addSubview:positioned:relativeTo:"), effectView, NSWindowBelow, NULL);
+}
+
+void enableBoolConfig(id config, const char *setting) {
+ msg(msg(config, s("preferences")), s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 1), str(setting));
+}
+
+void disableBoolConfig(id config, const char *setting) {
+ msg(msg(config, s("preferences")), s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str(setting));
+}
+
+void processDecorations(struct Application *app) {
+
+ int decorations = 0;
+
+ if (app->frame == 1 ) {
+ if( app->hideTitleBar == 0) {
+ decorations |= NSWindowStyleMaskTitled;
+ }
+ decorations |= NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+ }
+
+ if (app->resizable) {
+ decorations |= NSWindowStyleMaskResizable;
+ }
+
+ if (app->fullscreen) {
+ decorations |= NSWindowStyleMaskFullscreen;
+ }
+
+ if( app->fullSizeContent || app->frame == 0) {
+ decorations |= NSWindowStyleMaskFullSizeContentView;
+ }
+
+ app->decorations = decorations;
+}
+
+void createApplication(struct Application *app) {
+ id application = msg(c("NSApplication"), s("sharedApplication"));
+ app->application = application;
+ msg(application, s("setActivationPolicy:"), 0);
+}
+
+void DarkModeEnabled(struct Application *app, const char *callbackID) {
+ ON_MAIN_THREAD(
+ const char *result = isDarkMode(app) ? "T" : "F";
+
+ // Construct callback message. Format "SD|"
+ const char *callback = concat("SD", callbackID);
+ const char *header = concat(callback, "|");
+ const char *responseMessage = concat(header, result);
+ // Send message to backend
+ app->sendMessageToBackend(responseMessage);
+
+ // Free memory
+ MEMFREE(header);
+ MEMFREE(callback);
+ MEMFREE(responseMessage);
+ );
+}
+
+void createDelegate(struct Application *app) {
+ // Define delegate
+ Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
+ bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
+ class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
+ class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
+ class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
+
+ // All Menu Items use a common callback
+ class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
+
+ // Script handler
+ class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
+ objc_registerClassPair(delegateClass);
+
+ // Create delegate
+ id delegate = msg((id)delegateClass, s("new"));
+ objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
+
+ // Theme change listener
+ class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
+
+ // Get defaultCenter
+ id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
+ msg(defaultCenter, s("addObserver:selector:name:object:"), delegate, s("themeChanged:"), str("AppleInterfaceThemeChangedNotification"), NULL);
+
+ app->delegate = delegate;
+
+ msg(app->application, s("setDelegate:"), delegate);
+}
+
+void createMainWindow(struct Application *app) {
+ // Create main window
+ id mainWindow = ALLOC("NSWindow");
+ mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"),
+ CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO);
+ msg(mainWindow, s("autorelease"));
+
+ // Set Appearance
+ if( app->appearance != NULL ) {
+ msg(mainWindow, s("setAppearance:"),
+ msg(c("NSAppearance"), s("appearanceNamed:"), str(app->appearance))
+ );
+ }
+
+ // Set Title appearance
+ msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
+ msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
+
+ app->mainWindow = mainWindow;
+}
+
+const char* getInitialState(struct Application *app) {
+ const char *result = "";
+ if( isDarkMode(app) ) {
+ result = "window.wails.System.IsDarkMode.set(true);";
+ } else {
+ result = "window.wails.System.IsDarkMode.set(false);";
+ }
+ char buffer[999];
+ snprintf(&buffer[0], sizeof(buffer), "window.wails.System.LogLevel.set(%d);", app->logLevel);
+ result = concat(result, &buffer[0]);
+ Debug(app, "initialstate = %s", result);
+ return result;
+}
+
+void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
+ const char *roleName = item->string_;
+
+ if ( STREQ(roleName, "appMenu") ) {
+ createDefaultAppMenu(parentMenu);
+ return;
+ }
+ if ( STREQ(roleName, "editMenu")) {
+ createDefaultEditMenu(parentMenu);
+ return;
+ }
+ if ( STREQ(roleName, "hide")) {
+ addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "hideothers")) {
+ id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
+ msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
+ return;
+ }
+ if ( STREQ(roleName, "unhide")) {
+ addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "front")) {
+ addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "undo")) {
+ addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "redo")) {
+ addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "cut")) {
+ addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "copy")) {
+ addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "paste")) {
+ addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "delete")) {
+ addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
+ return;
+ }
+ if( STREQ(roleName, "pasteandmatchstyle")) {
+ id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
+ msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
+ }
+ if ( STREQ(roleName, "selectall")) {
+ addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "minimize")) {
+ addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "zoom")) {
+ addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "quit")) {
+ addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "togglefullscreen")) {
+ addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
+ return;
+ }
+
+}
+
+id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) {
+ id item = ALLOC("NSMenuItem");
+ id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
+ msg(item, s("setRepresentedObject:"), wrappedId);
+
+ id key = processAcceleratorKey(acceleratorkey);
+ msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
+ s(menuCallback), key);
+
+ msg(item, s("setEnabled:"), !disabled);
+ msg(item, s("autorelease"));
+
+ // Process modifiers
+ if( modifiers != NULL ) {
+ unsigned long modifierFlags = parseModifiers(modifiers);
+ msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
+ }
+ msg(parentMenu, s("addItem:"), item);
+
+ return item;
+}
+
+id parseCheckboxMenuItem(struct Application *app, id parentmenu, const char
+*title, const char *menuid, bool disabled, bool checked, const char *key,
+struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction) {
+ id item = ALLOC("NSMenuItem");
+
+ // Store the item in the menu item map
+ hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item);
+
+ id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
+ msg(item, s("setRepresentedObject:"), wrappedId);
+ msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(checkboxCallbackFunction), str(key));
+ msg(item, s("setEnabled:"), !disabled);
+ msg(item, s("autorelease"));
+ msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
+ msg(parentmenu, s("addItem:"), item);
+ return item;
+}
+
+id parseRadioMenuItem(struct Application *app, id parentmenu, const char *title,
+ const char *menuid, bool disabled, bool checked, const char *acceleratorkey,
+ struct hashmap_s *menuItemMap, const char *radioCallbackFunction) {
+ id item = ALLOC("NSMenuItem");
+
+ // Store the item in the menu item map
+ hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item);
+
+ id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
+ msg(item, s("setRepresentedObject:"), wrappedId);
+
+ id key = processAcceleratorKey(acceleratorkey);
+
+ msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(radioCallbackFunction), key);
+
+ msg(item, s("setEnabled:"), !disabled);
+ msg(item, s("autorelease"));
+ msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
+
+ msg(parentmenu, s("addItem:"), item);
+ return item;
+
+}
+
+void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item,
+struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
+*radioCallbackFunction, const char *menuCallbackFunction) {
+
+ // Check if this item is hidden and if so, exit early!
+ bool hidden = false;
+ getJSONBool(item, "Hidden", &hidden);
+ if( hidden ) {
+ return;
+ }
+
+ // Get the role
+ JsonNode *role = json_find_member(item, "Role");
+ if( role != NULL ) {
+ parseMenuRole(app, parentMenu, role);
+ return;
+ }
+
+ // Check if this is a submenu
+ JsonNode *submenu = json_find_member(item, "SubMenu");
+ if( submenu != NULL ) {
+ // Get the label
+ JsonNode *menuNameNode = json_find_member(item, "Label");
+ const char *name = "";
+ if ( menuNameNode != NULL) {
+ name = menuNameNode->string_;
+ }
+
+ id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
+ id thisMenu = createMenu(str(name));
+
+ msg(thisMenuItem, s("setSubmenu:"), thisMenu);
+ msg(parentMenu, s("addItem:"), thisMenuItem);
+
+ // Loop over submenu items
+ JsonNode *item;
+ json_foreach(item, submenu) {
+ // Get item label
+ parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
+ }
+
+ return;
+ }
+
+ // This is a user menu. Get the common data
+ // Get the label
+ const char *label = getJSONString(item, "Label");
+ if ( label == NULL) {
+ label = "(empty)";
+ }
+
+ const char *menuid = getJSONString(item, "ID");
+ if ( menuid == NULL) {
+ menuid = "";
+ }
+
+ bool disabled = false;
+ getJSONBool(item, "Disabled", &disabled);
+
+ // Get the Accelerator
+ JsonNode *accelerator = json_find_member(item, "Accelerator");
+ const char *acceleratorkey = NULL;
+ const char **modifiers = NULL;
+
+ // If we have an accelerator
+ if( accelerator != NULL ) {
+ // Get the key
+ acceleratorkey = getJSONString(accelerator, "Key");
+ // Check if there are modifiers
+ JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
+ if ( modifiersList != NULL ) {
+ // Allocate an array of strings
+ int noOfModifiers = json_array_length(modifiersList);
+
+ // Do we have any?
+ if (noOfModifiers > 0) {
+ modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
+ JsonNode *modifier;
+ int count = 0;
+ // Iterate the modifiers and save a reference to them in our new array
+ json_foreach(modifier, modifiersList) {
+ // Get modifier name
+ modifiers[count] = modifier->string_;
+ count++;
+ }
+ // Null terminate the modifier list
+ modifiers[count] = NULL;
+ }
+ }
+ }
+
+
+ // Get the Type
+ JsonNode *type = json_find_member(item, "Type");
+ if( type != NULL ) {
+
+ if( STREQ(type->string_, "Text")) {
+ parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, menuCallbackFunction);
+ }
+ else if ( STREQ(type->string_, "Separator")) {
+ addSeparator(parentMenu);
+ }
+ else if ( STREQ(type->string_, "Checkbox")) {
+ // Get checked state
+ bool checked = false;
+ getJSONBool(item, "Checked", &checked);
+
+ parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, checkboxCallbackFunction);
+ }
+ else if ( STREQ(type->string_, "Radio")) {
+ // Get checked state
+ bool checked = false;
+ getJSONBool(item, "Checked", &checked);
+
+ parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, radioCallbackFunction);
+ }
+
+ if ( modifiers != NULL ) {
+ free(modifiers);
+ }
+
+ return;
+ }
+}
+
+void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) {
+ JsonNode *items = json_find_member(menu, "Items");
+ if( items == NULL ) {
+ // Parse error!
+ Fatal(app, "Unable to find Items in Menu");
+ return;
+ }
+
+ // Iterate items
+ JsonNode *item;
+ json_foreach(item, items) {
+ // Get item label
+ parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
+ }
+}
+
+void dumpMemberList(const char *name, id *memberList) {
+ void *member = memberList[0];
+ int count = 0;
+ printf("%s = %p -> [ ", name, memberList);
+ while( member != NULL ) {
+ printf("%p ", member);
+ count = count + 1;
+ member = memberList[count];
+ }
+ printf("]\n");
+}
+
+void processRadioGroup(JsonNode *radioGroup, struct hashmap_s *menuItemMap,
+struct hashmap_s *radioGroupMap) {
+
+ int groupLength;
+ getJSONInt(radioGroup, "Length", &groupLength);
+ JsonNode *members = json_find_member(radioGroup, "Members");
+ JsonNode *member;
+
+ // Allocate array
+ size_t arrayLength = sizeof(id)*(groupLength+1);
+ id memberList[arrayLength];
+
+ // Build the radio group items
+ int count=0;
+ json_foreach(member, members) {
+ // Get menu by id
+ id menuItem = (id)hashmap_get(menuItemMap, (char*)member->string_, strlen(member->string_));
+ // Save Member
+ memberList[count] = menuItem;
+ count = count + 1;
+ }
+ // Null terminate array
+ memberList[groupLength] = 0;
+
+ // dumpMemberList("memberList", memberList);
+
+ // Store the members
+ json_foreach(member, members) {
+ // Copy the memberList
+ char *newMemberList = (char *)malloc(arrayLength);
+ memcpy(newMemberList, memberList, arrayLength);
+ // add group to each member of group
+ hashmap_put(radioGroupMap, member->string_, strlen(member->string_), newMemberList);
+ }
+
+}
+
+// updateMenu replaces the current menu with the given one
+void updateMenu(struct Application *app, const char *menuAsJSON) {
+ Debug(app, "Menu is now: %s", menuAsJSON);
+ ON_MAIN_THREAD (
+ DeleteMenu(app->applicationMenu);
+ Menu* newMenu = NewApplicationMenu(menuAsJSON);
+ id menu = GetMenu(newMenu);
+ app->applicationMenu = newMenu;
+ msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
+ );
+}
+
+// SetApplicationMenu sets the initial menu for the application
+void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
+ if ( app->applicationMenu == NULL ) {
+ app->applicationMenu = NewApplicationMenu(menuAsJSON);
+ return;
+ }
+
+ // Update menu
+ updateMenu(app, menuAsJSON);
+}
+
+void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
+
+ unsigned int count = 0;
+ while( 1 ) {
+ const unsigned char *name = dialogIcons[count++];
+ if( name == 0x00 ) {
+ break;
+ }
+ const unsigned char *lengthAsString = dialogIcons[count++];
+ if( name == 0x00 ) {
+ break;
+ }
+ const unsigned char *data = dialogIcons[count++];
+ if( data == 0x00 ) {
+ break;
+ }
+ int length = atoi((const char *)lengthAsString);
+
+ // Create the icon and add to the hashmap
+ id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
+ id dialogImage = ALLOC("NSImage");
+ msg(dialogImage, s("initWithData:"), imageData);
+ hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
+ }
+
+}
+
+void processUserDialogIcons(struct Application *app) {
+
+ // Allocate the Dialog icon hashmap
+ if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
+ // Couldn't allocate map
+ Fatal(app, "Not enough memory to allocate dialogIconCache!");
+ return;
+ }
+
+ processDialogIcons(&dialogIconCache, defaultDialogIcons);
+ processDialogIcons(&dialogIconCache, userDialogIcons);
+
+}
+
+
+void Run(struct Application *app, int argc, char **argv) {
+
+ // Process window decorations
+ processDecorations(app);
+
+ // Create the application
+ createApplication(app);
+
+ // Define delegate
+ createDelegate(app);
+
+ // Create the main window
+ createMainWindow(app);
+
+ // Create Content View
+ id contentView = msg( ALLOC("NSView"), s("init") );
+ msg(app->mainWindow, s("setContentView:"), contentView);
+
+ // Set the main window title
+ SetTitle(app, app->title);
+
+ // Center Window
+ Center(app);
+
+ // Set Colour
+ applyWindowColour(app);
+
+ // Process translucency
+ if (app->windowBackgroundIsTranslucent) {
+ makeWindowBackgroundTranslucent(app);
+ }
+
+ // Setup webview
+ id config = msg(c("WKWebViewConfiguration"), s("new"));
+ msg(config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 1), str("suppressesIncrementalRendering"));
+ if (app->devtools) {
+ Debug(app, "Enabling devtools");
+ enableBoolConfig(config, "developerExtrasEnabled");
+ }
+ app->config = config;
+
+ id manager = msg(config, s("userContentController"));
+ msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
+ msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
+ app->manager = manager;
+
+ id wkwebview = msg(c("WKWebView"), s("alloc"));
+ app->wkwebview = wkwebview;
+
+ msg(wkwebview, s("initWithFrame:configuration:"), CGRectMake(0, 0, 0, 0), config);
+
+ msg(contentView, s("addSubview:"), wkwebview);
+ msg(wkwebview, s("setAutoresizingMask:"), NSViewWidthSizable | NSViewHeightSizable);
+ CGRect contentViewBounds = GET_BOUNDS(contentView);
+ msg(wkwebview, s("setFrame:"), contentViewBounds );
+
+ // Disable damn smart quotes
+ // Credit: https://stackoverflow.com/a/31640511
+ id userDefaults = msg(c("NSUserDefaults"), s("standardUserDefaults"));
+ msg(userDefaults, s("setBool:forKey:"), NO, str("NSAutomaticQuoteSubstitutionEnabled"));
+
+ // Setup drag message handler
+ msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("windowDrag"));
+ // Add mouse event hooks
+ app->mouseDownMonitor = msg(c("NSEvent"), u("addLocalMonitorForEventsMatchingMask:handler:"), NSEventMaskLeftMouseDown, ^(id incomingEvent) {
+ // Make sure the mouse click was in the window, not the tray
+ id window = msg(incomingEvent, s("window"));
+ if (window == app->mainWindow) {
+ app->mouseEvent = incomingEvent;
+ }
+ return incomingEvent;
+ });
+ app->mouseUpMonitor = msg(c("NSEvent"), u("addLocalMonitorForEventsMatchingMask:handler:"), NSEventMaskLeftMouseUp, ^(id incomingEvent) {
+ app->mouseEvent = NULL;
+ ShowMouse();
+ return incomingEvent;
+ });
+
+ // Setup context menu message handler
+ msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("contextMenu"));
+
+ // Toolbar
+ if( app->useToolBar ) {
+ Debug(app, "Setting Toolbar");
+ id toolbar = msg(c("NSToolbar"),s("alloc"));
+ msg(toolbar, s("initWithIdentifier:"), str("wails.toolbar"));
+ msg(toolbar, s("autorelease"));
+
+ // Separator
+ if( app->hideToolbarSeparator ) {
+ msg(toolbar, s("setShowsBaselineSeparator:"), NO);
+ }
+
+ msg(app->mainWindow, s("setToolbar:"), toolbar);
+ }
+
+ // Fix up resizing
+ if (app->resizable == 0) {
+ app->minHeight = app->maxHeight = app->height;
+ app->minWidth = app->maxWidth = app->width;
+ }
+ setMinMaxSize(app);
+
+ // Load HTML
+ id html = msg(c("NSURL"), s("URLWithString:"), str(assets[0]));
+ msg(wkwebview, s("loadRequest:"), msg(c("NSURLRequest"), s("requestWithURL:"), html));
+
+ Debug(app, "Loading Internal Code");
+ // We want to evaluate the internal code plus runtime before the assets
+ const char *temp = concat(invoke, app->bindings);
+ const char *internalCode = concat(temp, (const char*)&runtime);
+ MEMFREE(temp);
+
+ // Add code that sets up the initial state, EG: State Stores.
+ temp = concat(internalCode, getInitialState(app));
+ MEMFREE(internalCode);
+ internalCode = temp;
+
+ // Loop over assets and build up one giant Mother Of All Evals
+ int index = 1;
+ while(1) {
+ // Get next asset pointer
+ const unsigned char *asset = assets[index];
+
+ // If we have no more assets, break
+ if (asset == 0x00) {
+ break;
+ }
+
+ temp = concat(internalCode, (const char *)asset);
+ MEMFREE(internalCode);
+ internalCode = temp;
+ index++;
+ };
+
+ // Disable context menu if not in debug mode
+ if( debug != 1 ) {
+ temp = concat(internalCode, "wails._.DisableDefaultContextMenu();");
+ MEMFREE(internalCode);
+ internalCode = temp;
+ }
+
+ // class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "@@:@");
+ // Include callback after evaluation
+ temp = concat(internalCode, "webkit.messageHandlers.completed.postMessage(true);");
+ MEMFREE(internalCode);
+ internalCode = temp;
+
+ // const char *viewportScriptString = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); meta.setAttribute('initial-scale', '1.0'); meta.setAttribute('maximum-scale', '1.0'); meta.setAttribute('minimum-scale', '1.0'); meta.setAttribute('user-scalable', 'no'); document.getElementsByTagName('head')[0].appendChild(meta);";
+ // ExecJS(app, viewportScriptString);
+
+
+ // This evaluates the MOAE once the Dom has finished loading
+ msg(manager,
+ s("addUserScript:"),
+ msg(msg(c("WKUserScript"), s("alloc")),
+ s("initWithSource:injectionTime:forMainFrameOnly:"),
+ str(internalCode),
+ 1,
+ 1));
+
+
+ // Emit theme change event to notify of current system them
+ emitThemeChange(app);
+
+ // If we want the webview to be transparent...
+ if( app->webviewIsTranparent == 1 ) {
+ msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
+ }
+
+ // If we have an application menu, process it
+ if( app->applicationMenu != NULL ) {
+ id menu = GetMenu(app->applicationMenu);
+ msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
+ }
+
+ // Setup initial trays
+ ShowTrayMenusInStore(app->trayMenuStore);
+
+ // Process dialog icons
+ processUserDialogIcons(app);
+
+ // We set it to be invisible by default. It will become visible when everything has initialised
+ msg(app->mainWindow, s("setIsVisible:"), NO);
+
+ // Finally call run
+ Debug(app, "Run called");
+ msg(app->application, s("run"));
+
+ MEMFREE(internalCode);
+}
+
+
+void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
+
+ // Load the tray icons
+ LoadTrayIcons();
+
+ // Setup main application struct
+ struct Application *result = malloc(sizeof(struct Application));
+ result->title = title;
+ result->width = width;
+ result->height = height;
+ result->minWidth = 0;
+ result->minHeight = 0;
+ result->maxWidth = 0;
+ result->maxHeight = 0;
+ result->resizable = resizable;
+ result->devtools = devtools;
+ result->fullscreen = fullscreen;
+ result->maximised = 0;
+ result->startHidden = startHidden;
+ result->decorations = 0;
+ result->logLevel = logLevel;
+
+ result->mainWindow = NULL;
+ result->mouseEvent = NULL;
+ result->mouseDownMonitor = NULL;
+ result->mouseUpMonitor = NULL;
+
+ // Features
+ result->frame = 1;
+ result->hideTitle = 0;
+ result->hideTitleBar = 0;
+ result->fullSizeContent = 0;
+ result->useToolBar = 0;
+ result->hideToolbarSeparator = 0;
+ result->appearance = NULL;
+ result->windowBackgroundIsTranslucent = 0;
+
+ // Window data
+ result->delegate = NULL;
+
+ // Menu
+ result->applicationMenu = NULL;
+
+ // Tray
+ result->trayMenuStore = NewTrayMenuStore();
+
+ // Context Menus
+ result->contextMenuStore = NewContextMenuStore();
+
+ // Window Appearance
+ result->titlebarAppearsTransparent = 0;
+ result->webviewIsTranparent = 0;
+
+ result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
+
+ return (void*) result;
+}
+
+
+#endif
diff --git a/v2/internal/ffenestri/ffenestri_darwin.go b/v2/internal/ffenestri/ffenestri_darwin.go
new file mode 100644
index 000000000..fafb391b8
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri_darwin.go
@@ -0,0 +1,91 @@
+package ffenestri
+
+/*
+#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
+#cgo darwin LDFLAGS: -framework WebKit -lobjc
+
+#include "ffenestri.h"
+#include "ffenestri_darwin.h"
+
+*/
+import "C"
+
+func (a *Application) processPlatformSettings() error {
+
+ mac := a.config.Mac
+ titlebar := mac.TitleBar
+
+ // HideTitle
+ if titlebar.HideTitle {
+ C.HideTitle(a.app)
+ }
+
+ // HideTitleBar
+ if titlebar.HideTitleBar {
+ C.HideTitleBar(a.app)
+ }
+
+ // Full Size Content
+ if titlebar.FullSizeContent {
+ C.FullSizeContent(a.app)
+ }
+
+ // Toolbar
+ if titlebar.UseToolbar {
+ C.UseToolbar(a.app)
+ }
+
+ if titlebar.HideToolbarSeparator {
+ C.HideToolbarSeparator(a.app)
+ }
+
+ if titlebar.TitlebarAppearsTransparent {
+ C.TitlebarAppearsTransparent(a.app)
+ }
+
+ // Process window Appearance
+ if mac.Appearance != "" {
+ C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
+ }
+
+ // Check if the webview should be transparent
+ if mac.WebviewIsTransparent {
+ C.WebviewIsTransparent(a.app)
+ }
+
+ // Check if window should be translucent
+ if mac.WindowBackgroundIsTranslucent {
+ C.WindowBackgroundIsTranslucent(a.app)
+ }
+
+ // Process menu
+ //applicationMenu := options.GetApplicationMenu(a.config)
+ applicationMenu := a.menuManager.GetApplicationMenuJSON()
+ if applicationMenu != "" {
+ C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
+ }
+
+ // Process tray
+ trays, err := a.menuManager.GetTrayMenus()
+ if err != nil {
+ return err
+ }
+ if trays != nil {
+ for _, tray := range trays {
+ C.AddTrayMenu(a.app, a.string2CString(tray))
+ }
+ }
+
+ // Process context menus
+ contextMenus, err := a.menuManager.GetContextMenus()
+ if err != nil {
+ return err
+ }
+ if contextMenus != nil {
+ for _, contextMenu := range contextMenus {
+ C.AddContextMenu(a.app, a.string2CString(contextMenu))
+ }
+ }
+
+ return nil
+}
diff --git a/v2/internal/ffenestri/ffenestri_darwin.h b/v2/internal/ffenestri/ffenestri_darwin.h
new file mode 100644
index 000000000..95dead60e
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri_darwin.h
@@ -0,0 +1,113 @@
+
+#ifndef FFENESTRI_DARWIN_H
+#define FFENESTRI_DARWIN_H
+
+
+#define OBJC_OLD_DISPATCH_PROTOTYPES 1
+#include
+#include
+#include "json.h"
+#include "hashmap.h"
+#include "stdlib.h"
+
+// Macros to make it slightly more sane
+#define msg objc_msgSend
+
+#define c(str) (id)objc_getClass(str)
+#define s(str) sel_registerName(str)
+#define u(str) sel_getUid(str)
+#define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input)
+#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
+#define cstr(input) (const char *)msg(input, s("UTF8String"))
+#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
+
+#define ALLOC(classname) msg(c(classname), s("alloc"))
+#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
+#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
+#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
+#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
+
+#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
+#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
+
+#define NSBackingStoreBuffered 2
+
+#define NSWindowStyleMaskBorderless 0
+#define NSWindowStyleMaskTitled 1
+#define NSWindowStyleMaskClosable 2
+#define NSWindowStyleMaskMiniaturizable 4
+#define NSWindowStyleMaskResizable 8
+#define NSWindowStyleMaskFullscreen 1 << 14
+
+#define NSVisualEffectMaterialWindowBackground 12
+#define NSVisualEffectBlendingModeBehindWindow 0
+#define NSVisualEffectStateFollowsWindowActiveState 0
+#define NSVisualEffectStateActive 1
+#define NSVisualEffectStateInactive 2
+
+#define NSViewWidthSizable 2
+#define NSViewHeightSizable 16
+
+#define NSWindowBelow -1
+#define NSWindowAbove 1
+
+#define NSSquareStatusItemLength -2.0
+#define NSVariableStatusItemLength -1.0
+
+#define NSWindowTitleHidden 1
+#define NSWindowStyleMaskFullSizeContentView 1 << 15
+
+#define NSEventModifierFlagCommand 1 << 20
+#define NSEventModifierFlagOption 1 << 19
+#define NSEventModifierFlagControl 1 << 18
+#define NSEventModifierFlagShift 1 << 17
+
+#define NSControlStateValueMixed -1
+#define NSControlStateValueOff 0
+#define NSControlStateValueOn 1
+
+// Unbelievably, if the user swaps their button preference
+// then right buttons are reported as left buttons
+#define NSEventMaskLeftMouseDown 1 << 1
+#define NSEventMaskLeftMouseUp 1 << 2
+#define NSEventMaskRightMouseDown 1 << 3
+#define NSEventMaskRightMouseUp 1 << 4
+
+#define NSEventTypeLeftMouseDown 1
+#define NSEventTypeLeftMouseUp 2
+#define NSEventTypeRightMouseDown 3
+#define NSEventTypeRightMouseUp 4
+
+#define NSNoImage 0
+#define NSImageOnly 1
+#define NSImageLeft 2
+#define NSImageRight 3
+#define NSImageBelow 4
+#define NSImageAbove 5
+#define NSImageOverlaps 6
+
+#define NSAlertStyleWarning 0
+#define NSAlertStyleInformational 1
+#define NSAlertStyleCritical 2
+
+#define NSAlertFirstButtonReturn 1000
+#define NSAlertSecondButtonReturn 1001
+#define NSAlertThirdButtonReturn 1002
+
+struct Application;
+int releaseNSObject(void *const context, struct hashmap_element_s *const e);
+void TitlebarAppearsTransparent(struct Application* app);
+void HideTitle(struct Application* app);
+void HideTitleBar(struct Application* app);
+void FullSizeContent(struct Application* app);
+void UseToolbar(struct Application* app);
+void HideToolbarSeparator(struct Application* app);
+void DisableFrame(struct Application* app);
+void SetAppearance(struct Application* app, const char *);
+void WebviewIsTransparent(struct Application* app);
+void WindowBackgroundIsTranslucent(struct Application* app);
+void SetTray(struct Application* app, const char *, const char *, const char *);
+void SetContextMenus(struct Application* app, const char *);
+void AddTrayMenu(struct Application* app, const char *);
+
+#endif
\ No newline at end of file
diff --git a/v2/internal/ffenestri/ffenestri_linux.c b/v2/internal/ffenestri/ffenestri_linux.c
new file mode 100644
index 000000000..b06a4d681
--- /dev/null
+++ b/v2/internal/ffenestri/ffenestri_linux.c
@@ -0,0 +1,984 @@
+
+#ifndef __FFENESTRI_LINUX_H__
+#define __FFENESTRI_LINUX_H__
+
+#include "gtk/gtk.h"
+#include "webkit2/webkit2.h"
+#include
+#include
+#include
+#include
+#include
+
+// References to assets
+extern const unsigned char *assets[];
+extern const unsigned char runtime;
+extern const char *icon[];
+
+// Constants
+#define PRIMARY_MOUSE_BUTTON 1
+#define MIDDLE_MOUSE_BUTTON 2
+#define SECONDARY_MOUSE_BUTTON 3
+
+// MAIN DEBUG FLAG
+int debug;
+
+// Credit: https://stackoverflow.com/a/8465083
+char *concat(const char *s1, const char *s2)
+{
+ const size_t len1 = strlen(s1);
+ const size_t len2 = strlen(s2);
+ char *result = malloc(len1 + len2 + 1);
+ memcpy(result, s1, len1);
+ memcpy(result + len1, s2, len2 + 1);
+ return result;
+}
+
+// Debug works like sprintf but mutes if the global debug flag is true
+// Credit: https://stackoverflow.com/a/20639708
+void Debug(char *message, ...)
+{
+ if (debug)
+ {
+ char *temp = concat("TRACE | Ffenestri (C) | ", message);
+ message = concat(temp, "\n");
+ free(temp);
+ va_list args;
+ va_start(args, message);
+ vprintf(message, args);
+ free(message);
+ va_end(args);
+ }
+}
+
+extern void messageFromWindowCallback(const char *);
+typedef void (*ffenestriCallback)(const char *);
+
+struct Application
+{
+
+ // Gtk Data
+ GtkApplication *application;
+ GtkWindow *mainWindow;
+ GtkWidget *webView;
+ int signalInvoke;
+ int signalWindowDrag;
+ int signalButtonPressed;
+ int signalButtonReleased;
+ int signalLoadChanged;
+
+ // Saves the events for the drag mouse button
+ GdkEventButton *dragButtonEvent;
+
+ // The number of the default drag button
+ int dragButton;
+
+ // Window Data
+ const char *title;
+ char *id;
+ int width;
+ int height;
+ int resizable;
+ int devtools;
+ int startHidden;
+ int fullscreen;
+ int minWidth;
+ int minHeight;
+ int maxWidth;
+ int maxHeight;
+ int frame;
+
+ // User Data
+ char *HTML;
+
+ // Callback
+ ffenestriCallback sendMessageToBackend;
+
+ // Bindings
+ const char *bindings;
+
+ // Lock - used for sync operations (Should we be using g_mutex?)
+ int lock;
+};
+
+void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
+{
+ // Setup main application struct
+ struct Application *result = malloc(sizeof(struct Application));
+ result->title = title;
+ result->width = width;
+ result->height = height;
+ result->resizable = resizable;
+ result->devtools = devtools;
+ result->fullscreen = fullscreen;
+ result->minWidth = 0;
+ result->minHeight = 0;
+ result->maxWidth = 0;
+ result->maxHeight = 0;
+ result->frame = 1;
+ result->startHidden = startHidden;
+
+ // Default drag button is PRIMARY
+ result->dragButton = PRIMARY_MOUSE_BUTTON;
+
+ result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
+
+ // Create a unique ID based on the current unix timestamp
+ char temp[11];
+ sprintf(&temp[0], "%d", (int)time(NULL));
+ result->id = concat("wails.app", &temp[0]);
+
+ // Create the main GTK application
+ GApplicationFlags flags = G_APPLICATION_FLAGS_NONE;
+ result->application = gtk_application_new(result->id, flags);
+
+ // Return the application struct
+ return (void *)result;
+}
+
+void DestroyApplication(struct Application *app)
+{
+ Debug("Destroying Application");
+
+ g_application_quit(G_APPLICATION(app->application));
+
+ // Release the GTK ID string
+ if (app->id != NULL)
+ {
+ free(app->id);
+ app->id = NULL;
+ }
+ else
+ {
+ Debug("Almost a double free for app->id");
+ }
+
+ // Free the bindings
+ if (app->bindings != NULL)
+ {
+ free((void *)app->bindings);
+ app->bindings = NULL;
+ }
+ else
+ {
+ Debug("Almost a double free for app->bindings");
+ }
+
+ // Disconnect signal handlers
+ WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
+ g_signal_handler_disconnect(manager, app->signalInvoke);
+ if( app->frame == 0) {
+ g_signal_handler_disconnect(manager, app->signalWindowDrag);
+ g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
+ g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
+ }
+ g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
+
+ // Release the main GTK Application
+ if (app->application != NULL)
+ {
+ g_object_unref(app->application);
+ app->application = NULL;
+ }
+ else
+ {
+ Debug("Almost a double free for app->application");
+ }
+ Debug("Finished Destroying Application");
+}
+
+// Quit will stop the gtk application and free up all the memory
+// used by the application
+void Quit(struct Application *app)
+{
+ Debug("Quit Called");
+ gtk_window_close((GtkWindow *)app->mainWindow);
+ g_application_quit((GApplication *)app->application);
+ DestroyApplication(app);
+}
+
+// SetTitle sets the main window title to the given string
+void SetTitle(struct Application *app, const char *title)
+{
+ gtk_window_set_title(app->mainWindow, title);
+}
+
+// Fullscreen sets the main window to be fullscreen
+void Fullscreen(struct Application *app)
+{
+ gtk_window_fullscreen(app->mainWindow);
+}
+
+// UnFullscreen resets the main window after a fullscreen
+void UnFullscreen(struct Application *app)
+{
+ gtk_window_unfullscreen(app->mainWindow);
+}
+
+void setMinMaxSize(struct Application *app)
+{
+ GdkGeometry size;
+ size.min_width = size.min_height = size.max_width = size.max_height = 0;
+ int flags = 0;
+ if (app->maxHeight > 0 && app->maxWidth > 0)
+ {
+ size.max_height = app->maxHeight;
+ size.max_width = app->maxWidth;
+ flags |= GDK_HINT_MAX_SIZE;
+ }
+ if (app->minHeight > 0 && app->minWidth > 0)
+ {
+ size.min_height = app->minHeight;
+ size.min_width = app->minWidth;
+ flags |= GDK_HINT_MIN_SIZE;
+ }
+ gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
+}
+
+char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
+ GtkFileChooserNative *native;
+ GtkFileChooserAction action = chooserAction;
+ gint res;
+ char *filename;
+
+ char *title = args[0];
+ char *filter = args[1];
+
+ native = gtk_file_chooser_native_new(title,
+ app->mainWindow,
+ action,
+ "_Open",
+ "_Cancel");
+
+ GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
+
+ // If we have filters, process them
+ if (filter[0] != '\0') {
+ GtkFileFilter *file_filter = gtk_file_filter_new();
+ gchar **filters = g_strsplit(filter, ",", -1);
+ gint i;
+ for(i = 0; filters && filters[i]; i++) {
+ gtk_file_filter_add_pattern(file_filter, filters[i]);
+ // Debug("Adding filter pattern: %s\n", filters[i]);
+ }
+ gtk_file_filter_set_name(file_filter, filter);
+ gtk_file_chooser_add_filter(chooser, file_filter);
+ g_strfreev(filters);
+ }
+
+ res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
+ if (res == GTK_RESPONSE_ACCEPT)
+ {
+ filename = gtk_file_chooser_get_filename(chooser);
+ }
+
+ g_object_unref(native);
+
+ return filename;
+}
+
+// openFileDialogInternal opens a dialog to select a file
+// NOTE: The result is a string that will need to be freed!
+char *openFileDialogInternal(struct Application *app, char **args)
+{
+ return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
+}
+
+// saveFileDialogInternal opens a dialog to select a file
+// NOTE: The result is a string that will need to be freed!
+char *saveFileDialogInternal(struct Application *app, char **args)
+{
+ return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
+}
+
+
+// openDirectoryDialogInternal opens a dialog to select a directory
+// NOTE: The result is a string that will need to be freed!
+char *openDirectoryDialogInternal(struct Application *app, char **args)
+{
+ return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, args);
+}
+
+void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
+{
+ app->minWidth = minWidth;
+ app->minHeight = minHeight;
+}
+
+void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
+{
+ app->maxWidth = maxWidth;
+ app->maxHeight = maxHeight;
+}
+
+// SetColour sets the colour of the webview to the given colour string
+int SetColour(struct Application *app, const char *colourString)
+{
+ GdkRGBA rgba;
+ gboolean result = gdk_rgba_parse(&rgba, colourString);
+ if (result == FALSE)
+ {
+ return 0;
+ }
+ // Debug("Setting webview colour to: %s", colourString);
+ webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
+ return 1;
+}
+
+// DisableFrame disables the window frame
+void DisableFrame(struct Application *app)
+{
+ app->frame = 0;
+}
+
+void syncCallback(GObject *source_object,
+ GAsyncResult *res,
+ void *data)
+{
+ struct Application *app = (struct Application *)data;
+ app->lock = 0;
+}
+
+void syncEval(struct Application *app, const gchar *script)
+{
+
+ WebKitWebView *webView = (WebKitWebView *)(app->webView);
+
+ // wait for lock to free
+ while (app->lock == 1)
+ {
+ g_main_context_iteration(0, true);
+ }
+ // Set lock
+ app->lock = 1;
+
+ webkit_web_view_run_javascript(
+ webView,
+ script,
+ NULL, syncCallback, (void*)app);
+
+ while (app->lock == 1)
+ {
+ g_main_context_iteration(0, true);
+ }
+}
+
+void asyncEval(WebKitWebView *webView, const gchar *script)
+{
+ webkit_web_view_run_javascript(
+ webView,
+ script,
+ NULL, NULL, NULL);
+}
+
+typedef void (*dispatchMethod)(struct Application *app, void *);
+
+struct dispatchData
+{
+ struct Application *app;
+ dispatchMethod method;
+ void *args;
+};
+
+gboolean executeMethod(gpointer data)
+{
+ struct dispatchData *d = (struct dispatchData *)data;
+ (d->method)(d->app, d->args);
+ g_free(d);
+ return FALSE;
+}
+
+void ExecJS(struct Application *app, char *js)
+{
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)syncEval;
+ data->args = js;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+typedef char *(*dialogMethod)(struct Application *app, void *);
+
+struct dialogCall
+{
+ struct Application *app;
+ dialogMethod method;
+ void *args;
+ void *filter;
+ char *result;
+ int done;
+};
+
+gboolean executeMethodWithReturn(gpointer data)
+{
+ struct dialogCall *d = (struct dialogCall *)data;
+
+ d->result = (d->method)(d->app, d->args);
+ d->done = 1;
+ return FALSE;
+}
+
+char *OpenFileDialog(struct Application *app, char *title, char *filter)
+{
+ struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
+ data->result = NULL;
+ data->done = 0;
+ data->method = (dialogMethod)openFileDialogInternal;
+ const char* dialogArgs[]={ title, filter };
+ data->args = dialogArgs;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethodWithReturn, data);
+
+ while (data->done == 0)
+ {
+ usleep(100000);
+ }
+ g_free(data);
+ return data->result;
+}
+
+char *SaveFileDialog(struct Application *app, char *title, char *filter)
+{
+ struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
+ data->result = NULL;
+ data->done = 0;
+ data->method = (dialogMethod)saveFileDialogInternal;
+ const char* dialogArgs[]={ title, filter };
+ data->args = dialogArgs;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethodWithReturn, data);
+
+ while (data->done == 0)
+ {
+ usleep(100000);
+ }
+ Debug("Dialog done");
+ Debug("Result = %s\n", data->result);
+
+ g_free(data);
+ // Fingers crossed this wasn't freed by g_free above
+ return data->result;
+}
+
+char *OpenDirectoryDialog(struct Application *app, char *title, char *filter)
+{
+ struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
+ data->result = NULL;
+ data->done = 0;
+ data->method = (dialogMethod)openDirectoryDialogInternal;
+ const char* dialogArgs[]={ title, filter };
+ data->args = dialogArgs;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethodWithReturn, data);
+
+ while (data->done == 0)
+ {
+ usleep(100000);
+ }
+ Debug("Directory Dialog done");
+ Debug("Result = %s\n", data->result);
+ g_free(data);
+ // Fingers crossed this wasn't freed by g_free above
+ return data->result;
+}
+
+// Sets the icon to the XPM stored in icon
+void setIcon(struct Application *app)
+{
+ GdkPixbuf *appIcon = gdk_pixbuf_new_from_xpm_data((const char **)icon);
+ gtk_window_set_icon(app->mainWindow, appIcon);
+}
+
+static void load_finished_cb(WebKitWebView *webView,
+ WebKitLoadEvent load_event,
+ struct Application *app)
+{
+ switch (load_event)
+ {
+ // case WEBKIT_LOAD_STARTED:
+ // /* New load, we have now a provisional URI */
+ // // printf("Start downloading %s\n", webkit_web_view_get_uri(web_view));
+ // /* Here we could start a spinner or update the
+ // * location bar with the provisional URI */
+ // break;
+ // case WEBKIT_LOAD_REDIRECTED:
+ // // printf("Redirected to: %s\n", webkit_web_view_get_uri(web_view));
+ // break;
+ // case WEBKIT_LOAD_COMMITTED:
+ // /* The load is being performed. Current URI is
+ // * the final one and it won't change unless a new
+ // * load is requested or a navigation within the
+ // * same page is performed */
+ // // printf("Loading: %s\n", webkit_web_view_get_uri(web_view));
+ // break;
+ case WEBKIT_LOAD_FINISHED:
+ /* Load finished, we can now stop the spinner */
+ // printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
+
+ // Bindings
+ Debug("Binding Methods");
+ syncEval(app, app->bindings);
+
+ // Runtime
+ Debug("Setting up Wails runtime");
+ syncEval(app, &runtime);
+
+ // Loop over assets
+ int index = 1;
+ while (1)
+ {
+ // Get next asset pointer
+ const char *asset = assets[index];
+
+ // If we have no more assets, break
+ if (asset == 0x00)
+ {
+ break;
+ }
+
+ // sync eval the asset
+ syncEval(app, asset);
+ index++;
+ };
+
+ // Set the icon
+ setIcon(app);
+
+ // Setup fullscreen
+ if (app->fullscreen)
+ {
+ Debug("Going fullscreen");
+ Fullscreen(app);
+ }
+
+ // Setup resize
+ gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
+
+ if (app->resizable)
+ {
+ gtk_window_set_default_size(GTK_WINDOW(app->mainWindow), app->width, app->height);
+ }
+ else
+ {
+ gtk_widget_set_size_request(GTK_WIDGET(app->mainWindow), app->width, app->height);
+ gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
+ // Fix the min/max to the window size for good measure
+ app->minHeight = app->maxHeight = app->height;
+ app->minWidth = app->maxWidth = app->width;
+ }
+ gtk_window_set_resizable(GTK_WINDOW(app->mainWindow), app->resizable ? TRUE : FALSE);
+ setMinMaxSize(app);
+
+ // Centre by default
+ gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
+
+ // Show window and focus
+ if( app->startHidden == 0) {
+ gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
+ gtk_widget_grab_focus(app->webView);
+ }
+ break;
+ }
+}
+
+static gboolean disable_context_menu_cb(
+ WebKitWebView *web_view,
+ WebKitContextMenu *context_menu,
+ GdkEvent *event,
+ WebKitHitTestResult *hit_test_result,
+ gpointer user_data)
+{
+ return TRUE;
+}
+
+static void printEvent(const char *message, GdkEventButton *event)
+{
+ Debug("%s: [button:%d] [x:%f] [y:%f] [time:%d]",
+ message,
+ event->button,
+ event->x_root,
+ event->y_root,
+ event->time);
+}
+
+
+static void dragWindow(WebKitUserContentManager *contentManager,
+ WebKitJavascriptResult *result,
+ struct Application *app)
+{
+ // If we get this message erroneously, ignore
+ if (app->dragButtonEvent == NULL)
+ {
+ return;
+ }
+
+ // Ignore non-toplevel widgets
+ GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(app->webView));
+ if (!GTK_IS_WINDOW(window))
+ {
+ return;
+ }
+
+ // Initiate the drag
+ printEvent("Starting drag with event", app->dragButtonEvent);
+
+ gtk_window_begin_move_drag(app->mainWindow,
+ app->dragButton,
+ app->dragButtonEvent->x_root,
+ app->dragButtonEvent->y_root,
+ app->dragButtonEvent->time);
+ // Clear the event
+ app->dragButtonEvent = NULL;
+
+ return;
+}
+
+gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
+ {
+ app->dragButtonEvent = event;
+ }
+ return FALSE;
+}
+
+gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
+{
+ if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
+ {
+ app->dragButtonEvent = NULL;
+ }
+ return FALSE;
+}
+
+static void sendMessageToBackend(WebKitUserContentManager *contentManager,
+ WebKitJavascriptResult *result,
+ struct Application *app)
+{
+#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);
+#else
+ JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
+ JSValueRef value = webkit_javascript_result_get_value(result);
+ JSStringRef js = JSValueToStringCopy(context, value, NULL);
+ size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
+ char *message = g_new(char, messageSize);
+ JSStringGetUTF8CString(js, message, messageSize);
+ JSStringRelease(js);
+#endif
+ app->sendMessageToBackend(message);
+ g_free(message);
+}
+
+void SetDebug(struct Application *app, int flag)
+{
+ debug = flag;
+}
+
+// getCurrentMonitorGeometry gets the geometry of the monitor
+// that the window is mostly on.
+GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
+ // Get the monitor that the window is currently on
+ GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
+ GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
+
+ // Get the geometry of the monitor
+ GdkRectangle result;
+ gdk_monitor_get_geometry (monitor,&result);
+
+ return result;
+}
+
+/*******************
+ * Window Position *
+ *******************/
+
+// Position holds an x/y corrdinate
+struct Position {
+ int x;
+ int y;
+};
+
+// Internal call for setting the position of the window.
+void setPositionInternal(struct Application *app, struct Position *pos) {
+
+ // Get the monitor geometry
+ GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
+
+ // Move the window relative to the monitor
+ gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
+
+ // Free memory
+ free(pos);
+}
+
+// SetPosition sets the position of the window to the given x/y
+// coordinates. The x/y values are relative to the monitor
+// the window is mostly on.
+void SetPosition(struct Application *app, int x, int y) {
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)setPositionInternal;
+ struct Position *pos = malloc(sizeof(struct Position));
+ pos->x = x;
+ pos->y = y;
+ data->args = pos;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+/***************
+ * Window Size *
+ ***************/
+
+// Size holds a width/height
+struct Size {
+ int width;
+ int height;
+};
+
+// Internal call for setting the size of the window.
+void setSizeInternal(struct Application *app, struct Size *size) {
+ gtk_window_resize(app->mainWindow, size->width, size->height);
+ free(size);
+}
+
+// SetSize sets the size of the window to the given width/height
+void SetSize(struct Application *app, int width, int height) {
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)setSizeInternal;
+ struct Size *size = malloc(sizeof(struct Size));
+ size->width = width;
+ size->height = height;
+ data->args = size;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+
+// centerInternal will center the main window on the monitor it is mostly in
+void centerInternal(struct Application *app)
+{
+ // Get the geometry of the monitor
+ GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
+
+ // Get the window width/height
+ int windowWidth, windowHeight;
+ gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
+
+ // Place the window at the center of the monitor
+ gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
+}
+
+// Center the window
+void Center(struct Application *app) {
+
+ // Setup a call to centerInternal on the main thread
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)centerInternal;
+ data->app = app;
+
+ // Add call to main thread
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+// hideInternal hides the main window
+void hideInternal(struct Application *app) {
+ gtk_widget_hide (GTK_WIDGET(app->mainWindow));
+}
+
+// Hide places the hideInternal method onto the main thread for execution
+void Hide(struct Application *app) {
+
+ // Setup a call to hideInternal on the main thread
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)hideInternal;
+ data->app = app;
+
+ // Add call to main thread
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+// showInternal shows the main window
+void showInternal(struct Application *app) {
+ gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
+ gtk_widget_grab_focus(app->webView);
+}
+
+// Show places the showInternal method onto the main thread for execution
+void Show(struct Application *app) {
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)showInternal;
+ data->app = app;
+
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+
+// maximiseInternal maximises the main window
+void maximiseInternal(struct Application *app) {
+ gtk_window_maximize(GTK_WIDGET(app->mainWindow));
+}
+
+// Maximise places the maximiseInternal method onto the main thread for execution
+void Maximise(struct Application *app) {
+
+ // Setup a call to maximiseInternal on the main thread
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)maximiseInternal;
+ data->app = app;
+
+ // Add call to main thread
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+// unmaximiseInternal unmaximises the main window
+void unmaximiseInternal(struct Application *app) {
+ gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
+}
+
+// Unmaximise places the unmaximiseInternal method onto the main thread for execution
+void Unmaximise(struct Application *app) {
+
+ // Setup a call to unmaximiseInternal on the main thread
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)unmaximiseInternal;
+ data->app = app;
+
+ // Add call to main thread
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+
+// minimiseInternal minimises the main window
+void minimiseInternal(struct Application *app) {
+ gtk_window_iconify(app->mainWindow);
+}
+
+// Minimise places the minimiseInternal method onto the main thread for execution
+void Minimise(struct Application *app) {
+
+ // Setup a call to minimiseInternal on the main thread
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)minimiseInternal;
+ data->app = app;
+
+ // Add call to main thread
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+// unminimiseInternal unminimises the main window
+void unminimiseInternal(struct Application *app) {
+ gtk_window_present(app->mainWindow);
+}
+
+// Unminimise places the unminimiseInternal method onto the main thread for execution
+void Unminimise(struct Application *app) {
+
+ // Setup a call to unminimiseInternal on the main thread
+ struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
+ data->method = (dispatchMethod)unminimiseInternal;
+ data->app = app;
+
+ // Add call to main thread
+ gdk_threads_add_idle(executeMethod, data);
+}
+
+
+void SetBindings(struct Application *app, const char *bindings)
+{
+ const char *temp = concat("window.wailsbindings = \"", bindings);
+ const char *jscall = concat(temp, "\";");
+ free((void *)temp);
+ app->bindings = jscall;
+}
+
+// This is called when the close button on the window is pressed
+gboolean close_button_pressed(GtkWidget *widget,
+ GdkEvent *event,
+ struct Application *app)
+{
+ app->sendMessageToBackend("WC"); // Window Close
+ return TRUE;
+}
+
+static void setupWindow(struct Application *app)
+{
+
+ // Create the window
+ GtkWidget *mainWindow = gtk_application_window_new(app->application);
+ // Save reference
+ app->mainWindow = GTK_WINDOW(mainWindow);
+
+ // Setup frame
+ gtk_window_set_decorated((GtkWindow *)mainWindow, app->frame);
+
+ // Setup title
+ gtk_window_set_title(GTK_WINDOW(mainWindow), app->title);
+
+ // Setup script handler
+ WebKitUserContentManager *contentManager = webkit_user_content_manager_new();
+
+ // Setup the invoke handler
+ webkit_user_content_manager_register_script_message_handler(contentManager, "external");
+ app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app);
+
+ // Setup the window drag handler if this is a frameless app
+ if ( app->frame == 0 ) {
+ webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
+ app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
+ // Setup the mouse handlers
+ app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
+ app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
+ }
+ GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
+
+ // Save reference
+ app->webView = webView;
+
+ // Add the webview to the window
+ gtk_container_add(GTK_CONTAINER(mainWindow), webView);
+
+
+ // Load default HTML
+ app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
+
+ // Load the user's HTML
+ // assets[0] is the HTML because the asset array is bundled like that by convention
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webView), assets[0]);
+
+ // Check if we want to enable the dev tools
+ if (app->devtools)
+ {
+ WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webView));
+ // webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
+ webkit_settings_set_enable_developer_extras(settings, true);
+ }
+ else
+ {
+ g_signal_connect(G_OBJECT(webView), "context-menu", G_CALLBACK(disable_context_menu_cb), app);
+ }
+
+ // Listen for close button signal
+ g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
+}
+
+static void activate(GtkApplication* _, struct Application *app)
+{
+ setupWindow(app);
+}
+
+void Run(struct Application *app, int argc, char **argv)
+{
+ g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
+ g_application_run(G_APPLICATION(app->application), argc, argv);
+}
+
+#endif
diff --git a/v2/internal/ffenestri/hashmap.h b/v2/internal/ffenestri/hashmap.h
new file mode 100644
index 000000000..0278bc3d6
--- /dev/null
+++ b/v2/internal/ffenestri/hashmap.h
@@ -0,0 +1,518 @@
+/*
+ The latest version of this library is available on GitHub;
+ https://github.com/sheredom/hashmap.h
+*/
+
+/*
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ 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 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.
+
+ For more information, please refer to
+*/
+#ifndef SHEREDOM_HASHMAP_H_INCLUDED
+#define SHEREDOM_HASHMAP_H_INCLUDED
+
+#if defined(_MSC_VER)
+// Workaround a bug in the MSVC runtime where it uses __cplusplus when not
+// defined.
+#pragma warning(push, 0)
+#pragma warning(disable : 4668)
+#endif
+#include
+#include
+
+#if (defined(_MSC_VER) && defined(__AVX__)) || \
+ (!defined(_MSC_VER) && defined(__SSE4_2__))
+#define HASHMAP_SSE42
+#endif
+
+#if defined(HASHMAP_SSE42)
+#include
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+// Stop MSVC complaining about not inlining functions.
+#pragma warning(disable : 4710)
+// Stop MSVC complaining about inlining functions!
+#pragma warning(disable : 4711)
+#elif defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+#endif
+
+#if defined(_MSC_VER)
+#define HASHMAP_USED
+#elif defined(__GNUC__)
+#define HASHMAP_USED __attribute__((used))
+#else
+#define HASHMAP_USED
+#endif
+
+/* We need to keep keys and values. */
+struct hashmap_element_s {
+ const char *key;
+ unsigned key_len;
+ int in_use;
+ void *data;
+};
+
+/* A hashmap has some maximum size and current size, as well as the data to
+ * hold. */
+struct hashmap_s {
+ unsigned table_size;
+ unsigned size;
+ struct hashmap_element_s *data;
+};
+
+#define HASHMAP_MAX_CHAIN_LENGTH (8)
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/// @brief Create a hashmap.
+/// @param initial_size The initial size of the hashmap. Must be a power of two.
+/// @param out_hashmap The storage for the created hashmap.
+/// @return On success 0 is returned.
+///
+/// Note that the initial size of the hashmap must be a power of two, and
+/// creation of the hashmap will fail if this is not the case.
+static int hashmap_create(const unsigned initial_size,
+ struct hashmap_s *const out_hashmap) HASHMAP_USED;
+
+/// @brief Put an element into the hashmap.
+/// @param hashmap The hashmap to insert into.
+/// @param key The string key to use.
+/// @param len The length of the string key.
+/// @param value The value to insert.
+/// @return On success 0 is returned.
+///
+/// The key string slice is not copied when creating the hashmap entry, and thus
+/// must remain a valid pointer until the hashmap entry is removed or the
+/// hashmap is destroyed.
+static int hashmap_put(struct hashmap_s *const hashmap, const char *const key,
+ const unsigned len, void *const value) HASHMAP_USED;
+
+/// @brief Get an element from the hashmap.
+/// @param hashmap The hashmap to get from.
+/// @param key The string key to use.
+/// @param len The length of the string key.
+/// @return The previously set element, or NULL if none exists.
+static void *hashmap_get(const struct hashmap_s *const hashmap,
+ const char *const key,
+ const unsigned len) HASHMAP_USED;
+
+/// @brief Remove an element from the hashmap.
+/// @param hashmap The hashmap to remove from.
+/// @param key The string key to use.
+/// @param len The length of the string key.
+/// @return On success 0 is returned.
+static int hashmap_remove(struct hashmap_s *const hashmap,
+ const char *const key,
+ const unsigned len) HASHMAP_USED;
+
+/// @brief Iterate over all the elements in a hashmap.
+/// @param hashmap The hashmap to iterate over.
+/// @param f The function pointer to call on each element.
+/// @param context The context to pass as the first argument to f.
+/// @return If the entire hashmap was iterated then 0 is returned. Otherwise if
+/// the callback function f returned non-zero then non-zero is returned.
+static int hashmap_iterate(const struct hashmap_s *const hashmap,
+ int (*f)(void *const context, void *const value),
+ void *const context) HASHMAP_USED;
+
+/// @brief Iterate over all the elements in a hashmap.
+/// @param hashmap The hashmap to iterate over.
+/// @param f The function pointer to call on each element.
+/// @param context The context to pass as the first argument to f.
+/// @return If the entire hashmap was iterated then 0 is returned.
+/// Otherwise if the callback function f returned positive then the positive
+/// value is returned. If the callback function returns -1, the current item
+/// is removed and iteration continues.
+static int hashmap_iterate_pairs(struct hashmap_s *const hashmap,
+ int (*f)(void *const, struct hashmap_element_s *const),
+ void *const context) HASHMAP_USED;
+
+/// @brief Get the size of the hashmap.
+/// @param hashmap The hashmap to get the size of.
+/// @return The size of the hashmap.
+static unsigned
+hashmap_num_entries(const struct hashmap_s *const hashmap) HASHMAP_USED;
+
+/// @brief Destroy the hashmap.
+/// @param hashmap The hashmap to destroy.
+static void hashmap_destroy(struct hashmap_s *const hashmap) HASHMAP_USED;
+
+static unsigned hashmap_crc32_helper(const char *const s,
+ const unsigned len) HASHMAP_USED;
+static unsigned
+hashmap_hash_helper_int_helper(const struct hashmap_s *const m,
+ const char *const keystring,
+ const unsigned len) HASHMAP_USED;
+static int hashmap_match_helper(const struct hashmap_element_s *const element,
+ const char *const key,
+ const unsigned len) HASHMAP_USED;
+static int hashmap_hash_helper(const struct hashmap_s *const m,
+ const char *const key, const unsigned len,
+ unsigned *const out_index) HASHMAP_USED;
+static int hashmap_rehash_iterator(void *const new_hash,
+ struct hashmap_element_s *const e) HASHMAP_USED;
+static int hashmap_rehash_helper(struct hashmap_s *const m) HASHMAP_USED;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#if defined(__cplusplus)
+#define HASHMAP_CAST(type, x) static_cast(x)
+#define HASHMAP_PTR_CAST(type, x) reinterpret_cast(x)
+#define HASHMAP_NULL NULL
+#else
+#define HASHMAP_CAST(type, x) ((type)x)
+#define HASHMAP_PTR_CAST(type, x) ((type)x)
+#define HASHMAP_NULL 0
+#endif
+
+int hashmap_create(const unsigned initial_size,
+ struct hashmap_s *const out_hashmap) {
+ if (0 == initial_size || 0 != (initial_size & (initial_size - 1))) {
+ return 1;
+ }
+
+ out_hashmap->data =
+ HASHMAP_CAST(struct hashmap_element_s *,
+ calloc(initial_size, sizeof(struct hashmap_element_s)));
+ if (!out_hashmap->data) {
+ return 1;
+ }
+
+ out_hashmap->table_size = initial_size;
+ out_hashmap->size = 0;
+
+ return 0;
+}
+
+int hashmap_put(struct hashmap_s *const m, const char *const key,
+ const unsigned len, void *const value) {
+ unsigned int index;
+
+ /* Find a place to put our value. */
+ while (!hashmap_hash_helper(m, key, len, &index)) {
+ if (hashmap_rehash_helper(m)) {
+ return 1;
+ }
+ }
+
+ /* Set the data. */
+ m->data[index].data = value;
+ m->data[index].key = key;
+ m->data[index].key_len = len;
+ m->data[index].in_use = 1;
+ m->size++;
+
+ return 0;
+}
+
+void *hashmap_get(const struct hashmap_s *const m, const char *const key,
+ const unsigned len) {
+ unsigned int curr;
+ unsigned int i;
+
+ /* Find data location */
+ curr = hashmap_hash_helper_int_helper(m, key, len);
+
+ /* Linear probing, if necessary */
+ for (i = 0; i < HASHMAP_MAX_CHAIN_LENGTH; i++) {
+ if (m->data[curr].in_use) {
+ if (hashmap_match_helper(&m->data[curr], key, len)) {
+ return m->data[curr].data;
+ }
+ }
+
+ curr = (curr + 1) % m->table_size;
+ }
+
+ /* Not found */
+ return HASHMAP_NULL;
+}
+
+int hashmap_remove(struct hashmap_s *const m, const char *const key,
+ const unsigned len) {
+ unsigned int i;
+ unsigned int curr;
+
+ /* Find key */
+ curr = hashmap_hash_helper_int_helper(m, key, len);
+
+ /* Linear probing, if necessary */
+ for (i = 0; i < HASHMAP_MAX_CHAIN_LENGTH; i++) {
+ if (m->data[curr].in_use) {
+ if (hashmap_match_helper(&m->data[curr], key, len)) {
+ /* Blank out the fields including in_use */
+ memset(&m->data[curr], 0, sizeof(struct hashmap_element_s));
+
+ /* Reduce the size */
+ m->size--;
+ return 0;
+ }
+ }
+ curr = (curr + 1) % m->table_size;
+ }
+
+ return 1;
+}
+
+int hashmap_iterate(const struct hashmap_s *const m,
+ int (*f)(void *const, void *const), void *const context) {
+ unsigned int i;
+
+ /* Linear probing */
+ for (i = 0; i < m->table_size; i++) {
+ if (m->data[i].in_use) {
+ if (!f(context, m->data[i].data)) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+int hashmap_iterate_pairs(struct hashmap_s *const hashmap,
+ int (*f)(void *const, struct hashmap_element_s *const),
+ void *const context) {
+ unsigned int i;
+ struct hashmap_element_s *p;
+ int r;
+
+ /* Linear probing */
+ for (i = 0; i < hashmap->table_size; i++) {
+ p=&hashmap->data[i];
+ if (p->in_use) {
+ r=f(context, p);
+ switch (r)
+ {
+ case -1: /* remove item */
+ memset(p, 0, sizeof(struct hashmap_element_s));
+ hashmap->size--;
+ break;
+ case 0: /* continue iterating */
+ break;
+ default: /* early exit */
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+void hashmap_destroy(struct hashmap_s *const m) {
+ free(m->data);
+ memset(m, 0, sizeof(struct hashmap_s));
+}
+
+unsigned hashmap_num_entries(const struct hashmap_s *const m) {
+ return m->size;
+}
+
+unsigned hashmap_crc32_helper(const char *const s, const unsigned len) {
+ unsigned i;
+ unsigned crc32val = 0;
+
+#if defined(HASHMAP_SSE42)
+ for (i = 0; i < len; i++) {
+ crc32val = _mm_crc32_u8(crc32val, HASHMAP_CAST(unsigned char, s[i]));
+ }
+
+ return crc32val;
+#else
+ // Using polynomial 0x11EDC6F41 to match SSE 4.2's crc function.
+ static const unsigned crc32_tab[] = {
+ 0x00000000U, 0xF26B8303U, 0xE13B70F7U, 0x1350F3F4U, 0xC79A971FU,
+ 0x35F1141CU, 0x26A1E7E8U, 0xD4CA64EBU, 0x8AD958CFU, 0x78B2DBCCU,
+ 0x6BE22838U, 0x9989AB3BU, 0x4D43CFD0U, 0xBF284CD3U, 0xAC78BF27U,
+ 0x5E133C24U, 0x105EC76FU, 0xE235446CU, 0xF165B798U, 0x030E349BU,
+ 0xD7C45070U, 0x25AFD373U, 0x36FF2087U, 0xC494A384U, 0x9A879FA0U,
+ 0x68EC1CA3U, 0x7BBCEF57U, 0x89D76C54U, 0x5D1D08BFU, 0xAF768BBCU,
+ 0xBC267848U, 0x4E4DFB4BU, 0x20BD8EDEU, 0xD2D60DDDU, 0xC186FE29U,
+ 0x33ED7D2AU, 0xE72719C1U, 0x154C9AC2U, 0x061C6936U, 0xF477EA35U,
+ 0xAA64D611U, 0x580F5512U, 0x4B5FA6E6U, 0xB93425E5U, 0x6DFE410EU,
+ 0x9F95C20DU, 0x8CC531F9U, 0x7EAEB2FAU, 0x30E349B1U, 0xC288CAB2U,
+ 0xD1D83946U, 0x23B3BA45U, 0xF779DEAEU, 0x05125DADU, 0x1642AE59U,
+ 0xE4292D5AU, 0xBA3A117EU, 0x4851927DU, 0x5B016189U, 0xA96AE28AU,
+ 0x7DA08661U, 0x8FCB0562U, 0x9C9BF696U, 0x6EF07595U, 0x417B1DBCU,
+ 0xB3109EBFU, 0xA0406D4BU, 0x522BEE48U, 0x86E18AA3U, 0x748A09A0U,
+ 0x67DAFA54U, 0x95B17957U, 0xCBA24573U, 0x39C9C670U, 0x2A993584U,
+ 0xD8F2B687U, 0x0C38D26CU, 0xFE53516FU, 0xED03A29BU, 0x1F682198U,
+ 0x5125DAD3U, 0xA34E59D0U, 0xB01EAA24U, 0x42752927U, 0x96BF4DCCU,
+ 0x64D4CECFU, 0x77843D3BU, 0x85EFBE38U, 0xDBFC821CU, 0x2997011FU,
+ 0x3AC7F2EBU, 0xC8AC71E8U, 0x1C661503U, 0xEE0D9600U, 0xFD5D65F4U,
+ 0x0F36E6F7U, 0x61C69362U, 0x93AD1061U, 0x80FDE395U, 0x72966096U,
+ 0xA65C047DU, 0x5437877EU, 0x4767748AU, 0xB50CF789U, 0xEB1FCBADU,
+ 0x197448AEU, 0x0A24BB5AU, 0xF84F3859U, 0x2C855CB2U, 0xDEEEDFB1U,
+ 0xCDBE2C45U, 0x3FD5AF46U, 0x7198540DU, 0x83F3D70EU, 0x90A324FAU,
+ 0x62C8A7F9U, 0xB602C312U, 0x44694011U, 0x5739B3E5U, 0xA55230E6U,
+ 0xFB410CC2U, 0x092A8FC1U, 0x1A7A7C35U, 0xE811FF36U, 0x3CDB9BDDU,
+ 0xCEB018DEU, 0xDDE0EB2AU, 0x2F8B6829U, 0x82F63B78U, 0x709DB87BU,
+ 0x63CD4B8FU, 0x91A6C88CU, 0x456CAC67U, 0xB7072F64U, 0xA457DC90U,
+ 0x563C5F93U, 0x082F63B7U, 0xFA44E0B4U, 0xE9141340U, 0x1B7F9043U,
+ 0xCFB5F4A8U, 0x3DDE77ABU, 0x2E8E845FU, 0xDCE5075CU, 0x92A8FC17U,
+ 0x60C37F14U, 0x73938CE0U, 0x81F80FE3U, 0x55326B08U, 0xA759E80BU,
+ 0xB4091BFFU, 0x466298FCU, 0x1871A4D8U, 0xEA1A27DBU, 0xF94AD42FU,
+ 0x0B21572CU, 0xDFEB33C7U, 0x2D80B0C4U, 0x3ED04330U, 0xCCBBC033U,
+ 0xA24BB5A6U, 0x502036A5U, 0x4370C551U, 0xB11B4652U, 0x65D122B9U,
+ 0x97BAA1BAU, 0x84EA524EU, 0x7681D14DU, 0x2892ED69U, 0xDAF96E6AU,
+ 0xC9A99D9EU, 0x3BC21E9DU, 0xEF087A76U, 0x1D63F975U, 0x0E330A81U,
+ 0xFC588982U, 0xB21572C9U, 0x407EF1CAU, 0x532E023EU, 0xA145813DU,
+ 0x758FE5D6U, 0x87E466D5U, 0x94B49521U, 0x66DF1622U, 0x38CC2A06U,
+ 0xCAA7A905U, 0xD9F75AF1U, 0x2B9CD9F2U, 0xFF56BD19U, 0x0D3D3E1AU,
+ 0x1E6DCDEEU, 0xEC064EEDU, 0xC38D26C4U, 0x31E6A5C7U, 0x22B65633U,
+ 0xD0DDD530U, 0x0417B1DBU, 0xF67C32D8U, 0xE52CC12CU, 0x1747422FU,
+ 0x49547E0BU, 0xBB3FFD08U, 0xA86F0EFCU, 0x5A048DFFU, 0x8ECEE914U,
+ 0x7CA56A17U, 0x6FF599E3U, 0x9D9E1AE0U, 0xD3D3E1ABU, 0x21B862A8U,
+ 0x32E8915CU, 0xC083125FU, 0x144976B4U, 0xE622F5B7U, 0xF5720643U,
+ 0x07198540U, 0x590AB964U, 0xAB613A67U, 0xB831C993U, 0x4A5A4A90U,
+ 0x9E902E7BU, 0x6CFBAD78U, 0x7FAB5E8CU, 0x8DC0DD8FU, 0xE330A81AU,
+ 0x115B2B19U, 0x020BD8EDU, 0xF0605BEEU, 0x24AA3F05U, 0xD6C1BC06U,
+ 0xC5914FF2U, 0x37FACCF1U, 0x69E9F0D5U, 0x9B8273D6U, 0x88D28022U,
+ 0x7AB90321U, 0xAE7367CAU, 0x5C18E4C9U, 0x4F48173DU, 0xBD23943EU,
+ 0xF36E6F75U, 0x0105EC76U, 0x12551F82U, 0xE03E9C81U, 0x34F4F86AU,
+ 0xC69F7B69U, 0xD5CF889DU, 0x27A40B9EU, 0x79B737BAU, 0x8BDCB4B9U,
+ 0x988C474DU, 0x6AE7C44EU, 0xBE2DA0A5U, 0x4C4623A6U, 0x5F16D052U,
+ 0xAD7D5351U};
+
+ for (i = 0; i < len; i++) {
+ crc32val = crc32_tab[(HASHMAP_CAST(unsigned char, crc32val) ^
+ HASHMAP_CAST(unsigned char, s[i]))] ^
+ (crc32val >> 8);
+ }
+ return crc32val;
+#endif
+}
+
+unsigned hashmap_hash_helper_int_helper(const struct hashmap_s *const m,
+ const char *const keystring,
+ const unsigned len) {
+ unsigned key = hashmap_crc32_helper(keystring, len);
+
+ /* Robert Jenkins' 32 bit Mix Function */
+ key += (key << 12);
+ key ^= (key >> 22);
+ key += (key << 4);
+ key ^= (key >> 9);
+ key += (key << 10);
+ key ^= (key >> 2);
+ key += (key << 7);
+ key ^= (key >> 12);
+
+ /* Knuth's Multiplicative Method */
+ key = (key >> 3) * 2654435761;
+
+ return key % m->table_size;
+}
+
+int hashmap_match_helper(const struct hashmap_element_s *const element,
+ const char *const key, const unsigned len) {
+ return (element->key_len == len) && (0 == memcmp(element->key, key, len));
+}
+
+int hashmap_hash_helper(const struct hashmap_s *const m, const char *const key,
+ const unsigned len, unsigned *const out_index) {
+ unsigned int curr;
+ unsigned int i;
+
+ /* If full, return immediately */
+ if (m->size >= m->table_size) {
+ return 0;
+ }
+
+ /* Find the best index */
+ curr = hashmap_hash_helper_int_helper(m, key, len);
+
+ /* Linear probing */
+ for (i = 0; i < HASHMAP_MAX_CHAIN_LENGTH; i++) {
+ if (!m->data[curr].in_use) {
+ *out_index = curr;
+ return 1;
+ }
+
+ if (m->data[curr].in_use &&
+ hashmap_match_helper(&m->data[curr], key, len)) {
+ *out_index = curr;
+ return 1;
+ }
+
+ curr = (curr + 1) % m->table_size;
+ }
+
+ return 0;
+}
+
+int hashmap_rehash_iterator(void *const new_hash,
+ struct hashmap_element_s *const e) {
+ int temp=hashmap_put(HASHMAP_PTR_CAST(struct hashmap_s *, new_hash),
+ e->key, e->key_len, e->data);
+ if (0table_size;
+
+ struct hashmap_s new_hash;
+
+ int flag = hashmap_create(new_size, &new_hash);
+ if (0!=flag) {
+ return flag;
+ }
+
+ /* copy the old elements to the new table */
+ flag = hashmap_iterate_pairs(m, hashmap_rehash_iterator, HASHMAP_PTR_CAST(void *, &new_hash));
+ if (0!=flag) {
+ return flag;
+ }
+
+ hashmap_destroy(m);
+ /* put new hash into old hash structure by copying */
+ memcpy(m, &new_hash, sizeof(struct hashmap_s));
+
+ return 0;
+}
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#elif defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#endif
\ No newline at end of file
diff --git a/v2/internal/ffenestri/json.c b/v2/internal/ffenestri/json.c
new file mode 100644
index 000000000..34f8696d0
--- /dev/null
+++ b/v2/internal/ffenestri/json.c
@@ -0,0 +1,1401 @@
+/*
+ Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
+ All rights reserved.
+
+ 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.
+
+ Source: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
+*/
+
+#include "json.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#define out_of_memory() do { \
+ fprintf(stderr, "Out of memory.\n"); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+/* Sadly, strdup is not portable. */
+static char *json_strdup(const char *str)
+{
+ char *ret = (char*) malloc(strlen(str) + 1);
+ if (ret == NULL)
+ out_of_memory();
+ strcpy(ret, str);
+ return ret;
+}
+
+/* String buffer */
+
+typedef struct
+{
+ char *cur;
+ char *end;
+ char *start;
+} SB;
+
+static void sb_init(SB *sb)
+{
+ sb->start = (char*) malloc(17);
+ if (sb->start == NULL)
+ out_of_memory();
+ sb->cur = sb->start;
+ sb->end = sb->start + 16;
+}
+
+/* sb and need may be evaluated multiple times. */
+#define sb_need(sb, need) do { \
+ if ((sb)->end - (sb)->cur < (need)) \
+ sb_grow(sb, need); \
+ } while (0)
+
+static void sb_grow(SB *sb, int need)
+{
+ size_t length = sb->cur - sb->start;
+ size_t alloc = sb->end - sb->start;
+
+ do {
+ alloc *= 2;
+ } while (alloc < length + need);
+
+ sb->start = (char*) realloc(sb->start, alloc + 1);
+ if (sb->start == NULL)
+ out_of_memory();
+ sb->cur = sb->start + length;
+ sb->end = sb->start + alloc;
+}
+
+static void sb_put(SB *sb, const char *bytes, int count)
+{
+ sb_need(sb, count);
+ memcpy(sb->cur, bytes, count);
+ sb->cur += count;
+}
+
+#define sb_putc(sb, c) do { \
+ if ((sb)->cur >= (sb)->end) \
+ sb_grow(sb, 1); \
+ *(sb)->cur++ = (c); \
+ } while (0)
+
+static void sb_puts(SB *sb, const char *str)
+{
+ sb_put(sb, str, strlen(str));
+}
+
+static char *sb_finish(SB *sb)
+{
+ *sb->cur = 0;
+ assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start));
+ return sb->start;
+}
+
+static void sb_free(SB *sb)
+{
+ free(sb->start);
+}
+
+/*
+ * Unicode helper functions
+ *
+ * These are taken from the ccan/charset module and customized a bit.
+ * Putting them here means the compiler can (choose to) inline them,
+ * and it keeps ccan/json from having a dependency.
+ */
+
+/*
+ * Type for Unicode codepoints.
+ * We need our own because wchar_t might be 16 bits.
+ */
+typedef uint32_t uchar_t;
+
+/*
+ * Validate a single UTF-8 character starting at @s.
+ * The string must be null-terminated.
+ *
+ * If it's valid, return its length (1 thru 4).
+ * If it's invalid or clipped, return 0.
+ *
+ * This function implements the syntax given in RFC3629, which is
+ * the same as that given in The Unicode Standard, Version 6.0.
+ *
+ * It has the following properties:
+ *
+ * * All codepoints U+0000..U+10FFFF may be encoded,
+ * except for U+D800..U+DFFF, which are reserved
+ * for UTF-16 surrogate pair encoding.
+ * * UTF-8 byte sequences longer than 4 bytes are not permitted,
+ * as they exceed the range of Unicode.
+ * * The sixty-six Unicode "non-characters" are permitted
+ * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF).
+ */
+static int utf8_validate_cz(const char *s)
+{
+ unsigned char c = *s++;
+
+ if (c <= 0x7F) { /* 00..7F */
+ return 1;
+ } else if (c <= 0xC1) { /* 80..C1 */
+ /* Disallow overlong 2-byte sequence. */
+ return 0;
+ } else if (c <= 0xDF) { /* C2..DF */
+ /* Make sure subsequent byte is in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+
+ return 2;
+ } else if (c <= 0xEF) { /* E0..EF */
+ /* Disallow overlong 3-byte sequence. */
+ if (c == 0xE0 && (unsigned char)*s < 0xA0)
+ return 0;
+
+ /* Disallow U+D800..U+DFFF. */
+ if (c == 0xED && (unsigned char)*s > 0x9F)
+ return 0;
+
+ /* Make sure subsequent bytes are in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+
+ return 3;
+ } else if (c <= 0xF4) { /* F0..F4 */
+ /* Disallow overlong 4-byte sequence. */
+ if (c == 0xF0 && (unsigned char)*s < 0x90)
+ return 0;
+
+ /* Disallow codepoints beyond U+10FFFF. */
+ if (c == 0xF4 && (unsigned char)*s > 0x8F)
+ return 0;
+
+ /* Make sure subsequent bytes are in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+
+ return 4;
+ } else { /* F5..FF */
+ return 0;
+ }
+}
+
+/* Validate a null-terminated UTF-8 string. */
+static bool utf8_validate(const char *s)
+{
+ int len;
+
+ for (; *s != 0; s += len) {
+ len = utf8_validate_cz(s);
+ if (len == 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Read a single UTF-8 character starting at @s,
+ * returning the length, in bytes, of the character read.
+ *
+ * This function assumes input is valid UTF-8,
+ * and that there are enough characters in front of @s.
+ */
+static int utf8_read_char(const char *s, uchar_t *out)
+{
+ const unsigned char *c = (const unsigned char*) s;
+
+ assert(utf8_validate_cz(s));
+
+ if (c[0] <= 0x7F) {
+ /* 00..7F */
+ *out = c[0];
+ return 1;
+ } else if (c[0] <= 0xDF) {
+ /* C2..DF (unless input is invalid) */
+ *out = ((uchar_t)c[0] & 0x1F) << 6 |
+ ((uchar_t)c[1] & 0x3F);
+ return 2;
+ } else if (c[0] <= 0xEF) {
+ /* E0..EF */
+ *out = ((uchar_t)c[0] & 0xF) << 12 |
+ ((uchar_t)c[1] & 0x3F) << 6 |
+ ((uchar_t)c[2] & 0x3F);
+ return 3;
+ } else {
+ /* F0..F4 (unless input is invalid) */
+ *out = ((uchar_t)c[0] & 0x7) << 18 |
+ ((uchar_t)c[1] & 0x3F) << 12 |
+ ((uchar_t)c[2] & 0x3F) << 6 |
+ ((uchar_t)c[3] & 0x3F);
+ return 4;
+ }
+}
+
+/*
+ * Write a single UTF-8 character to @s,
+ * returning the length, in bytes, of the character written.
+ *
+ * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF.
+ *
+ * This function will write up to 4 bytes to @out.
+ */
+static int utf8_write_char(uchar_t unicode, char *out)
+{
+ unsigned char *o = (unsigned char*) out;
+
+ assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF));
+
+ if (unicode <= 0x7F) {
+ /* U+0000..U+007F */
+ *o++ = unicode;
+ return 1;
+ } else if (unicode <= 0x7FF) {
+ /* U+0080..U+07FF */
+ *o++ = 0xC0 | unicode >> 6;
+ *o++ = 0x80 | (unicode & 0x3F);
+ return 2;
+ } else if (unicode <= 0xFFFF) {
+ /* U+0800..U+FFFF */
+ *o++ = 0xE0 | unicode >> 12;
+ *o++ = 0x80 | (unicode >> 6 & 0x3F);
+ *o++ = 0x80 | (unicode & 0x3F);
+ return 3;
+ } else {
+ /* U+10000..U+10FFFF */
+ *o++ = 0xF0 | unicode >> 18;
+ *o++ = 0x80 | (unicode >> 12 & 0x3F);
+ *o++ = 0x80 | (unicode >> 6 & 0x3F);
+ *o++ = 0x80 | (unicode & 0x3F);
+ return 4;
+ }
+}
+
+/*
+ * Compute the Unicode codepoint of a UTF-16 surrogate pair.
+ *
+ * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF.
+ * If they aren't, this function returns false.
+ */
+static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uchar_t *unicode)
+{
+ if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) {
+ *unicode = 0x10000 + ((((uchar_t)uc & 0x3FF) << 10) | (lc & 0x3FF));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*
+ * Construct a UTF-16 surrogate pair given a Unicode codepoint.
+ *
+ * @unicode must be U+10000..U+10FFFF.
+ */
+static void to_surrogate_pair(uchar_t unicode, uint16_t *uc, uint16_t *lc)
+{
+ uchar_t n;
+
+ assert(unicode >= 0x10000 && unicode <= 0x10FFFF);
+
+ n = unicode - 0x10000;
+ *uc = ((n >> 10) & 0x3FF) | 0xD800;
+ *lc = (n & 0x3FF) | 0xDC00;
+}
+
+#define is_space(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == ' ')
+#define is_digit(c) ((c) >= '0' && (c) <= '9')
+
+static bool parse_value (const char **sp, JsonNode **out);
+static bool parse_string (const char **sp, char **out);
+static bool parse_number (const char **sp, double *out);
+static bool parse_array (const char **sp, JsonNode **out);
+static bool parse_object (const char **sp, JsonNode **out);
+static bool parse_hex16 (const char **sp, uint16_t *out);
+
+static bool expect_literal (const char **sp, const char *str);
+static void skip_space (const char **sp);
+
+static void emit_value (SB *out, const JsonNode *node);
+static void emit_value_indented (SB *out, const JsonNode *node, const char *space, int indent_level);
+static void emit_string (SB *out, const char *str);
+static void emit_number (SB *out, double num);
+static void emit_array (SB *out, const JsonNode *array);
+static void emit_array_indented (SB *out, const JsonNode *array, const char *space, int indent_level);
+static void emit_object (SB *out, const JsonNode *object);
+static void emit_object_indented (SB *out, const JsonNode *object, const char *space, int indent_level);
+
+static int write_hex16(char *out, uint16_t val);
+
+static JsonNode *mknode(JsonTag tag);
+static void append_node(JsonNode *parent, JsonNode *child);
+static void prepend_node(JsonNode *parent, JsonNode *child);
+static void append_member(JsonNode *object, char *key, JsonNode *value);
+
+/* Assertion-friendly validity checks */
+static bool tag_is_valid(unsigned int tag);
+static bool number_is_valid(const char *num);
+
+JsonNode *json_decode(const char *json)
+{
+ const char *s = json;
+ JsonNode *ret;
+
+ skip_space(&s);
+ if (!parse_value(&s, &ret))
+ return NULL;
+
+ skip_space(&s);
+ if (*s != 0) {
+ json_delete(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+char *json_encode(const JsonNode *node)
+{
+ return json_stringify(node, NULL);
+}
+
+char *json_encode_string(const char *str)
+{
+ SB sb;
+ sb_init(&sb);
+
+ emit_string(&sb, str);
+
+ return sb_finish(&sb);
+}
+
+char *json_stringify(const JsonNode *node, const char *space)
+{
+ SB sb;
+ sb_init(&sb);
+
+ if (space != NULL)
+ emit_value_indented(&sb, node, space, 0);
+ else
+ emit_value(&sb, node);
+
+ return sb_finish(&sb);
+}
+
+void json_delete(JsonNode *node)
+{
+ if (node != NULL) {
+ json_remove_from_parent(node);
+
+ switch (node->tag) {
+ case JSON_STRING:
+ free(node->string_);
+ break;
+ case JSON_ARRAY:
+ case JSON_OBJECT:
+ {
+ JsonNode *child, *next;
+ for (child = node->children.head; child != NULL; child = next) {
+ next = child->next;
+ json_delete(child);
+ }
+ break;
+ }
+ default:;
+ }
+
+ free(node);
+ }
+}
+
+bool json_validate(const char *json)
+{
+ const char *s = json;
+
+ skip_space(&s);
+ if (!parse_value(&s, NULL))
+ return false;
+
+ skip_space(&s);
+ if (*s != 0)
+ return false;
+
+ return true;
+}
+
+// We return the number of elements or -1 if there was a problem
+int json_array_length(JsonNode *array) {
+
+ int result = 0;
+
+ // The node should not be null and it should be an array
+ if (array == NULL || array->tag != JSON_ARRAY)
+ return -1;
+
+ // Loop and count!
+ JsonNode *element;
+ json_foreach(element, array) {
+ result++;
+ }
+
+ return result;
+}
+
+JsonNode *json_find_element(JsonNode *array, int index)
+{
+ JsonNode *element;
+ int i = 0;
+
+ if (array == NULL || array->tag != JSON_ARRAY)
+ return NULL;
+
+ json_foreach(element, array) {
+ if (i == index)
+ return element;
+ i++;
+ }
+
+ return NULL;
+}
+
+JsonNode *json_find_member(JsonNode *object, const char *name)
+{
+ JsonNode *member;
+
+ if (object == NULL || object->tag != JSON_OBJECT)
+ return NULL;
+
+ json_foreach(member, object)
+ if (strcmp(member->key, name) == 0)
+ return member;
+
+ return NULL;
+}
+
+JsonNode *json_first_child(const JsonNode *node)
+{
+ if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT))
+ return node->children.head;
+ return NULL;
+}
+
+static JsonNode *mknode(JsonTag tag)
+{
+ JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode));
+ if (ret == NULL)
+ out_of_memory();
+ ret->tag = tag;
+ return ret;
+}
+
+JsonNode *json_mknull(void)
+{
+ return mknode(JSON_NULL);
+}
+
+JsonNode *json_mkbool(bool b)
+{
+ JsonNode *ret = mknode(JSON_BOOL);
+ ret->bool_ = b;
+ return ret;
+}
+
+static JsonNode *mkstring(char *s)
+{
+ JsonNode *ret = mknode(JSON_STRING);
+ ret->string_ = s;
+ return ret;
+}
+
+JsonNode *json_mkstring(const char *s)
+{
+ return mkstring(json_strdup(s));
+}
+
+JsonNode *json_mknumber(double n)
+{
+ JsonNode *node = mknode(JSON_NUMBER);
+ node->number_ = n;
+ return node;
+}
+
+JsonNode *json_mkarray(void)
+{
+ return mknode(JSON_ARRAY);
+}
+
+JsonNode *json_mkobject(void)
+{
+ return mknode(JSON_OBJECT);
+}
+
+static void append_node(JsonNode *parent, JsonNode *child)
+{
+ child->parent = parent;
+ child->prev = parent->children.tail;
+ child->next = NULL;
+
+ if (parent->children.tail != NULL)
+ parent->children.tail->next = child;
+ else
+ parent->children.head = child;
+ parent->children.tail = child;
+}
+
+static void prepend_node(JsonNode *parent, JsonNode *child)
+{
+ child->parent = parent;
+ child->prev = NULL;
+ child->next = parent->children.head;
+
+ if (parent->children.head != NULL)
+ parent->children.head->prev = child;
+ else
+ parent->children.tail = child;
+ parent->children.head = child;
+}
+
+static void append_member(JsonNode *object, char *key, JsonNode *value)
+{
+ value->key = key;
+ append_node(object, value);
+}
+
+void json_append_element(JsonNode *array, JsonNode *element)
+{
+ assert(array->tag == JSON_ARRAY);
+ assert(element->parent == NULL);
+
+ append_node(array, element);
+}
+
+void json_prepend_element(JsonNode *array, JsonNode *element)
+{
+ assert(array->tag == JSON_ARRAY);
+ assert(element->parent == NULL);
+
+ prepend_node(array, element);
+}
+
+void json_append_member(JsonNode *object, const char *key, JsonNode *value)
+{
+ assert(object->tag == JSON_OBJECT);
+ assert(value->parent == NULL);
+
+ append_member(object, json_strdup(key), value);
+}
+
+void json_prepend_member(JsonNode *object, const char *key, JsonNode *value)
+{
+ assert(object->tag == JSON_OBJECT);
+ assert(value->parent == NULL);
+
+ value->key = json_strdup(key);
+ prepend_node(object, value);
+}
+
+void json_remove_from_parent(JsonNode *node)
+{
+ JsonNode *parent = node->parent;
+
+ if (parent != NULL) {
+ if (node->prev != NULL)
+ node->prev->next = node->next;
+ else
+ parent->children.head = node->next;
+ if (node->next != NULL)
+ node->next->prev = node->prev;
+ else
+ parent->children.tail = node->prev;
+
+ free(node->key);
+
+ node->parent = NULL;
+ node->prev = node->next = NULL;
+ node->key = NULL;
+ }
+}
+
+static bool parse_value(const char **sp, JsonNode **out)
+{
+ const char *s = *sp;
+
+ switch (*s) {
+ case 'n':
+ if (expect_literal(&s, "null")) {
+ if (out)
+ *out = json_mknull();
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case 'f':
+ if (expect_literal(&s, "false")) {
+ if (out)
+ *out = json_mkbool(false);
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case 't':
+ if (expect_literal(&s, "true")) {
+ if (out)
+ *out = json_mkbool(true);
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case '"': {
+ char *str;
+ if (parse_string(&s, out ? &str : NULL)) {
+ if (out)
+ *out = mkstring(str);
+ *sp = s;
+ return true;
+ }
+ return false;
+ }
+
+ case '[':
+ if (parse_array(&s, out)) {
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case '{':
+ if (parse_object(&s, out)) {
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ default: {
+ double num;
+ if (parse_number(&s, out ? &num : NULL)) {
+ if (out)
+ *out = json_mknumber(num);
+ *sp = s;
+ return true;
+ }
+ return false;
+ }
+ }
+}
+
+static bool parse_array(const char **sp, JsonNode **out)
+{
+ const char *s = *sp;
+ JsonNode *ret = out ? json_mkarray() : NULL;
+ JsonNode *element;
+
+ if (*s++ != '[')
+ goto failure;
+ skip_space(&s);
+
+ if (*s == ']') {
+ s++;
+ goto success;
+ }
+
+ for (;;) {
+ if (!parse_value(&s, out ? &element : NULL))
+ goto failure;
+ skip_space(&s);
+
+ if (out)
+ json_append_element(ret, element);
+
+ if (*s == ']') {
+ s++;
+ goto success;
+ }
+
+ if (*s++ != ',')
+ goto failure;
+ skip_space(&s);
+ }
+
+success:
+ *sp = s;
+ if (out)
+ *out = ret;
+ return true;
+
+failure:
+ json_delete(ret);
+ return false;
+}
+
+static bool parse_object(const char **sp, JsonNode **out)
+{
+ const char *s = *sp;
+ JsonNode *ret = out ? json_mkobject() : NULL;
+ char *key;
+ JsonNode *value;
+
+ if (*s++ != '{')
+ goto failure;
+ skip_space(&s);
+
+ if (*s == '}') {
+ s++;
+ goto success;
+ }
+
+ for (;;) {
+ if (!parse_string(&s, out ? &key : NULL))
+ goto failure;
+ skip_space(&s);
+
+ if (*s++ != ':')
+ goto failure_free_key;
+ skip_space(&s);
+
+ if (!parse_value(&s, out ? &value : NULL))
+ goto failure_free_key;
+ skip_space(&s);
+
+ if (out)
+ append_member(ret, key, value);
+
+ if (*s == '}') {
+ s++;
+ goto success;
+ }
+
+ if (*s++ != ',')
+ goto failure;
+ skip_space(&s);
+ }
+
+success:
+ *sp = s;
+ if (out)
+ *out = ret;
+ return true;
+
+failure_free_key:
+ if (out)
+ free(key);
+failure:
+ json_delete(ret);
+ return false;
+}
+
+bool parse_string(const char **sp, char **out)
+{
+ const char *s = *sp;
+ SB sb;
+ char throwaway_buffer[4];
+ /* enough space for a UTF-8 character */
+ char *b;
+
+ if (*s++ != '"')
+ return false;
+
+ if (out) {
+ sb_init(&sb);
+ sb_need(&sb, 4);
+ b = sb.cur;
+ } else {
+ b = throwaway_buffer;
+ }
+
+ while (*s != '"') {
+ unsigned char c = *s++;
+
+ /* Parse next character, and write it to b. */
+ if (c == '\\') {
+ c = *s++;
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ *b++ = c;
+ break;
+ case 'b':
+ *b++ = '\b';
+ break;
+ case 'f':
+ *b++ = '\f';
+ break;
+ case 'n':
+ *b++ = '\n';
+ break;
+ case 'r':
+ *b++ = '\r';
+ break;
+ case 't':
+ *b++ = '\t';
+ break;
+ case 'u':
+ {
+ uint16_t uc, lc;
+ uchar_t unicode;
+
+ if (!parse_hex16(&s, &uc))
+ goto failed;
+
+ if (uc >= 0xD800 && uc <= 0xDFFF) {
+ /* Handle UTF-16 surrogate pair. */
+ if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc))
+ goto failed; /* Incomplete surrogate pair. */
+ if (!from_surrogate_pair(uc, lc, &unicode))
+ goto failed; /* Invalid surrogate pair. */
+ } else if (uc == 0) {
+ /* Disallow "\u0000". */
+ goto failed;
+ } else {
+ unicode = uc;
+ }
+
+ b += utf8_write_char(unicode, b);
+ break;
+ }
+ default:
+ /* Invalid escape */
+ goto failed;
+ }
+ } else if (c <= 0x1F) {
+ /* Control characters are not allowed in string literals. */
+ goto failed;
+ } else {
+ /* Validate and echo a UTF-8 character. */
+ int len;
+
+ s--;
+ len = utf8_validate_cz(s);
+ if (len == 0)
+ goto failed; /* Invalid UTF-8 character. */
+
+ while (len--)
+ *b++ = *s++;
+ }
+
+ /*
+ * Update sb to know about the new bytes,
+ * and set up b to write another character.
+ */
+ if (out) {
+ sb.cur = b;
+ sb_need(&sb, 4);
+ b = sb.cur;
+ } else {
+ b = throwaway_buffer;
+ }
+ }
+ s++;
+
+ if (out)
+ *out = sb_finish(&sb);
+ *sp = s;
+ return true;
+
+failed:
+ if (out)
+ sb_free(&sb);
+ return false;
+}
+
+/*
+ * The JSON spec says that a number shall follow this precise pattern
+ * (spaces and quotes added for readability):
+ * '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)?
+ *
+ * However, some JSON parsers are more liberal. For instance, PHP accepts
+ * '.5' and '1.'. JSON.parse accepts '+3'.
+ *
+ * This function takes the strict approach.
+ */
+bool parse_number(const char **sp, double *out)
+{
+ const char *s = *sp;
+
+ /* '-'? */
+ if (*s == '-')
+ s++;
+
+ /* (0 | [1-9][0-9]*) */
+ if (*s == '0') {
+ s++;
+ } else {
+ if (!is_digit(*s))
+ return false;
+ do {
+ s++;
+ } while (is_digit(*s));
+ }
+
+ /* ('.' [0-9]+)? */
+ if (*s == '.') {
+ s++;
+ if (!is_digit(*s))
+ return false;
+ do {
+ s++;
+ } while (is_digit(*s));
+ }
+
+ /* ([Ee] [+-]? [0-9]+)? */
+ if (*s == 'E' || *s == 'e') {
+ s++;
+ if (*s == '+' || *s == '-')
+ s++;
+ if (!is_digit(*s))
+ return false;
+ do {
+ s++;
+ } while (is_digit(*s));
+ }
+
+ if (out)
+ *out = strtod(*sp, NULL);
+
+ *sp = s;
+ return true;
+}
+
+static void skip_space(const char **sp)
+{
+ const char *s = *sp;
+ while (is_space(*s))
+ s++;
+ *sp = s;
+}
+
+static void emit_value(SB *out, const JsonNode *node)
+{
+ assert(tag_is_valid(node->tag));
+ switch (node->tag) {
+ case JSON_NULL:
+ sb_puts(out, "null");
+ break;
+ case JSON_BOOL:
+ sb_puts(out, node->bool_ ? "true" : "false");
+ break;
+ case JSON_STRING:
+ emit_string(out, node->string_);
+ break;
+ case JSON_NUMBER:
+ emit_number(out, node->number_);
+ break;
+ case JSON_ARRAY:
+ emit_array(out, node);
+ break;
+ case JSON_OBJECT:
+ emit_object(out, node);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level)
+{
+ assert(tag_is_valid(node->tag));
+ switch (node->tag) {
+ case JSON_NULL:
+ sb_puts(out, "null");
+ break;
+ case JSON_BOOL:
+ sb_puts(out, node->bool_ ? "true" : "false");
+ break;
+ case JSON_STRING:
+ emit_string(out, node->string_);
+ break;
+ case JSON_NUMBER:
+ emit_number(out, node->number_);
+ break;
+ case JSON_ARRAY:
+ emit_array_indented(out, node, space, indent_level);
+ break;
+ case JSON_OBJECT:
+ emit_object_indented(out, node, space, indent_level);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+static void emit_array(SB *out, const JsonNode *array)
+{
+ const JsonNode *element;
+
+ sb_putc(out, '[');
+ json_foreach(element, array) {
+ emit_value(out, element);
+ if (element->next != NULL)
+ sb_putc(out, ',');
+ }
+ sb_putc(out, ']');
+}
+
+static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level)
+{
+ const JsonNode *element = array->children.head;
+ int i;
+
+ if (element == NULL) {
+ sb_puts(out, "[]");
+ return;
+ }
+
+ sb_puts(out, "[\n");
+ while (element != NULL) {
+ for (i = 0; i < indent_level + 1; i++)
+ sb_puts(out, space);
+ emit_value_indented(out, element, space, indent_level + 1);
+
+ element = element->next;
+ sb_puts(out, element != NULL ? ",\n" : "\n");
+ }
+ for (i = 0; i < indent_level; i++)
+ sb_puts(out, space);
+ sb_putc(out, ']');
+}
+
+static void emit_object(SB *out, const JsonNode *object)
+{
+ const JsonNode *member;
+
+ sb_putc(out, '{');
+ json_foreach(member, object) {
+ emit_string(out, member->key);
+ sb_putc(out, ':');
+ emit_value(out, member);
+ if (member->next != NULL)
+ sb_putc(out, ',');
+ }
+ sb_putc(out, '}');
+}
+
+static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level)
+{
+ const JsonNode *member = object->children.head;
+ int i;
+
+ if (member == NULL) {
+ sb_puts(out, "{}");
+ return;
+ }
+
+ sb_puts(out, "{\n");
+ while (member != NULL) {
+ for (i = 0; i < indent_level + 1; i++)
+ sb_puts(out, space);
+ emit_string(out, member->key);
+ sb_puts(out, ": ");
+ emit_value_indented(out, member, space, indent_level + 1);
+
+ member = member->next;
+ sb_puts(out, member != NULL ? ",\n" : "\n");
+ }
+ for (i = 0; i < indent_level; i++)
+ sb_puts(out, space);
+ sb_putc(out, '}');
+}
+
+void emit_string(SB *out, const char *str)
+{
+ bool escape_unicode = false;
+ const char *s = str;
+ char *b;
+
+ assert(utf8_validate(str));
+
+ /*
+ * 14 bytes is enough space to write up to two
+ * \uXXXX escapes and two quotation marks.
+ */
+ sb_need(out, 14);
+ b = out->cur;
+
+ *b++ = '"';
+ while (*s != 0) {
+ unsigned char c = *s++;
+
+ /* Encode the next character, and write it to b. */
+ switch (c) {
+ case '"':
+ *b++ = '\\';
+ *b++ = '"';
+ break;
+ case '\\':
+ *b++ = '\\';
+ *b++ = '\\';
+ break;
+ case '\b':
+ *b++ = '\\';
+ *b++ = 'b';
+ break;
+ case '\f':
+ *b++ = '\\';
+ *b++ = 'f';
+ break;
+ case '\n':
+ *b++ = '\\';
+ *b++ = 'n';
+ break;
+ case '\r':
+ *b++ = '\\';
+ *b++ = 'r';
+ break;
+ case '\t':
+ *b++ = '\\';
+ *b++ = 't';
+ break;
+ default: {
+ int len;
+
+ s--;
+ len = utf8_validate_cz(s);
+
+ if (len == 0) {
+ /*
+ * Handle invalid UTF-8 character gracefully in production
+ * by writing a replacement character (U+FFFD)
+ * and skipping a single byte.
+ *
+ * This should never happen when assertions are enabled
+ * due to the assertion at the beginning of this function.
+ */
+ assert(false);
+ if (escape_unicode) {
+ strcpy(b, "\\uFFFD");
+ b += 6;
+ } else {
+ *b++ = 0xEF;
+ *b++ = 0xBF;
+ *b++ = 0xBD;
+ }
+ s++;
+ } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) {
+ /* Encode using \u.... */
+ uint32_t unicode;
+
+ s += utf8_read_char(s, &unicode);
+
+ if (unicode <= 0xFFFF) {
+ *b++ = '\\';
+ *b++ = 'u';
+ b += write_hex16(b, unicode);
+ } else {
+ /* Produce a surrogate pair. */
+ uint16_t uc, lc;
+ assert(unicode <= 0x10FFFF);
+ to_surrogate_pair(unicode, &uc, &lc);
+ *b++ = '\\';
+ *b++ = 'u';
+ b += write_hex16(b, uc);
+ *b++ = '\\';
+ *b++ = 'u';
+ b += write_hex16(b, lc);
+ }
+ } else {
+ /* Write the character directly. */
+ while (len--)
+ *b++ = *s++;
+ }
+
+ break;
+ }
+ }
+
+ /*
+ * Update *out to know about the new bytes,
+ * and set up b to write another encoded character.
+ */
+ out->cur = b;
+ sb_need(out, 14);
+ b = out->cur;
+ }
+ *b++ = '"';
+
+ out->cur = b;
+}
+
+static void emit_number(SB *out, double num)
+{
+ /*
+ * This isn't exactly how JavaScript renders numbers,
+ * but it should produce valid JSON for reasonable numbers
+ * preserve precision well enough, and avoid some oddities
+ * like 0.3 -> 0.299999999999999988898 .
+ */
+ char buf[64];
+ sprintf(buf, "%.16g", num);
+
+ if (number_is_valid(buf))
+ sb_puts(out, buf);
+ else
+ sb_puts(out, "null");
+}
+
+static bool tag_is_valid(unsigned int tag)
+{
+ return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT);
+}
+
+static bool number_is_valid(const char *num)
+{
+ return (parse_number(&num, NULL) && *num == '\0');
+}
+
+static bool expect_literal(const char **sp, const char *str)
+{
+ const char *s = *sp;
+
+ while (*str != '\0')
+ if (*s++ != *str++)
+ return false;
+
+ *sp = s;
+ return true;
+}
+
+/*
+ * Parses exactly 4 hex characters (capital or lowercase).
+ * Fails if any input chars are not [0-9A-Fa-f].
+ */
+static bool parse_hex16(const char **sp, uint16_t *out)
+{
+ const char *s = *sp;
+ uint16_t ret = 0;
+ uint16_t i;
+ uint16_t tmp;
+ char c;
+
+ for (i = 0; i < 4; i++) {
+ c = *s++;
+ if (c >= '0' && c <= '9')
+ tmp = c - '0';
+ else if (c >= 'A' && c <= 'F')
+ tmp = c - 'A' + 10;
+ else if (c >= 'a' && c <= 'f')
+ tmp = c - 'a' + 10;
+ else
+ return false;
+
+ ret <<= 4;
+ ret += tmp;
+ }
+
+ if (out)
+ *out = ret;
+ *sp = s;
+ return true;
+}
+
+/*
+ * Encodes a 16-bit number into hexadecimal,
+ * writing exactly 4 hex chars.
+ */
+static int write_hex16(char *out, uint16_t val)
+{
+ const char *hex = "0123456789ABCDEF";
+
+ *out++ = hex[(val >> 12) & 0xF];
+ *out++ = hex[(val >> 8) & 0xF];
+ *out++ = hex[(val >> 4) & 0xF];
+ *out++ = hex[ val & 0xF];
+
+ return 4;
+}
+
+bool json_check(const JsonNode *node, char errmsg[256])
+{
+ #define problem(...) do { \
+ if (errmsg != NULL) \
+ snprintf(errmsg, 256, __VA_ARGS__); \
+ return false; \
+ } while (0)
+
+ if (node->key != NULL && !utf8_validate(node->key))
+ problem("key contains invalid UTF-8");
+
+ if (!tag_is_valid(node->tag))
+ problem("tag is invalid (%u)", node->tag);
+
+ if (node->tag == JSON_BOOL) {
+ if (node->bool_ != false && node->bool_ != true)
+ problem("bool_ is neither false (%d) nor true (%d)", (int)false, (int)true);
+ } else if (node->tag == JSON_STRING) {
+ if (node->string_ == NULL)
+ problem("string_ is NULL");
+ if (!utf8_validate(node->string_))
+ problem("string_ contains invalid UTF-8");
+ } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) {
+ JsonNode *head = node->children.head;
+ JsonNode *tail = node->children.tail;
+
+ if (head == NULL || tail == NULL) {
+ if (head != NULL)
+ problem("tail is NULL, but head is not");
+ if (tail != NULL)
+ problem("head is NULL, but tail is not");
+ } else {
+ JsonNode *child;
+ JsonNode *last = NULL;
+
+ if (head->prev != NULL)
+ problem("First child's prev pointer is not NULL");
+
+ for (child = head; child != NULL; last = child, child = child->next) {
+ if (child == node)
+ problem("node is its own child");
+ if (child->next == child)
+ problem("child->next == child (cycle)");
+ if (child->next == head)
+ problem("child->next == head (cycle)");
+
+ if (child->parent != node)
+ problem("child does not point back to parent");
+ if (child->next != NULL && child->next->prev != child)
+ problem("child->next does not point back to child");
+
+ if (node->tag == JSON_ARRAY && child->key != NULL)
+ problem("Array element's key is not NULL");
+ if (node->tag == JSON_OBJECT && child->key == NULL)
+ problem("Object member's key is NULL");
+
+ if (!json_check(child, errmsg))
+ return false;
+ }
+
+ if (last != tail)
+ problem("tail does not match pointer found by starting at head and following next links");
+ }
+ }
+
+ return true;
+
+ #undef problem
+}
\ No newline at end of file
diff --git a/v2/internal/ffenestri/json.h b/v2/internal/ffenestri/json.h
new file mode 100644
index 000000000..aaf711f8a
--- /dev/null
+++ b/v2/internal/ffenestri/json.h
@@ -0,0 +1,122 @@
+/*
+ Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
+ All rights reserved.
+
+ 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.
+
+ Source: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
+*/
+
+#ifndef CCAN_JSON_H
+#define CCAN_JSON_H
+
+#include
+#include
+
+typedef enum {
+ JSON_NULL,
+ JSON_BOOL,
+ JSON_STRING,
+ JSON_NUMBER,
+ JSON_ARRAY,
+ JSON_OBJECT,
+} JsonTag;
+
+typedef struct JsonNode JsonNode;
+
+struct JsonNode
+{
+ /* only if parent is an object or array (NULL otherwise) */
+ JsonNode *parent;
+ JsonNode *prev, *next;
+
+ /* only if parent is an object (NULL otherwise) */
+ char *key; /* Must be valid UTF-8. */
+
+ JsonTag tag;
+ union {
+ /* JSON_BOOL */
+ bool bool_;
+
+ /* JSON_STRING */
+ char *string_; /* Must be valid UTF-8. */
+
+ /* JSON_NUMBER */
+ double number_;
+
+ /* JSON_ARRAY */
+ /* JSON_OBJECT */
+ struct {
+ JsonNode *head, *tail;
+ } children;
+ };
+};
+
+/*** Encoding, decoding, and validation ***/
+
+JsonNode *json_decode (const char *json);
+char *json_encode (const JsonNode *node);
+char *json_encode_string (const char *str);
+char *json_stringify (const JsonNode *node, const char *space);
+void json_delete (JsonNode *node);
+
+bool json_validate (const char *json);
+
+/*** Lookup and traversal ***/
+
+JsonNode *json_find_element (JsonNode *array, int index);
+JsonNode *json_find_member (JsonNode *object, const char *key);
+
+JsonNode *json_first_child (const JsonNode *node);
+
+#define json_foreach(i, object_or_array) \
+ for ((i) = json_first_child(object_or_array); \
+ (i) != NULL; \
+ (i) = (i)->next)
+
+/*** Construction and manipulation ***/
+
+JsonNode *json_mknull(void);
+JsonNode *json_mkbool(bool b);
+JsonNode *json_mkstring(const char *s);
+JsonNode *json_mknumber(double n);
+JsonNode *json_mkarray(void);
+JsonNode *json_mkobject(void);
+
+void json_append_element(JsonNode *array, JsonNode *element);
+void json_prepend_element(JsonNode *array, JsonNode *element);
+void json_append_member(JsonNode *object, const char *key, JsonNode *value);
+void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
+
+void json_remove_from_parent(JsonNode *node);
+
+/*** Debugging ***/
+
+/*
+ * Look for structure and encoding problems in a JsonNode or its descendents.
+ *
+ * If a problem is detected, return false, writing a description of the problem
+ * to errmsg (unless errmsg is NULL).
+ */
+bool json_check(const JsonNode *node, char errmsg[256]);
+
+// Added by Lea Anthony 28/11/2020
+int json_array_length(JsonNode *array);
+
+#endif
\ No newline at end of file
diff --git a/v2/internal/ffenestri/menu_darwin.c b/v2/internal/ffenestri/menu_darwin.c
new file mode 100644
index 000000000..c687a011f
--- /dev/null
+++ b/v2/internal/ffenestri/menu_darwin.c
@@ -0,0 +1,808 @@
+//
+// Created by Lea Anthony on 6/1/21.
+//
+
+#include "ffenestri_darwin.h"
+#include "menu_darwin.h"
+#include "contextmenus_darwin.h"
+
+// NewMenu creates a new Menu struct, saving the given menu structure as JSON
+Menu* NewMenu(JsonNode *menuData) {
+
+ Menu *result = malloc(sizeof(Menu));
+
+ result->processedMenu = menuData;
+
+ // No title by default
+ result->title = "";
+
+ // Initialise menuCallbackDataCache
+ vec_init(&result->callbackDataCache);
+
+ // Allocate MenuItem Map
+ if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
+ ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
+ }
+ // Allocate the Radio Group Map
+ if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) {
+ ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!");
+ }
+
+ // Init other members
+ result->menu = NULL;
+ result->parentData = NULL;
+
+ return result;
+}
+
+Menu* NewApplicationMenu(const char *menuAsJSON) {
+
+ // Parse the menu json
+ JsonNode *processedMenu = json_decode(menuAsJSON);
+ if( processedMenu == NULL ) {
+ // Parse error!
+ ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
+ }
+
+ Menu *result = NewMenu(processedMenu);
+ result->menuType = ApplicationMenuType;
+ return result;
+}
+
+MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType) {
+ MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
+
+ result->menu = menu;
+ result->menuID = menuID;
+ result->menuItem = menuItem;
+ result->menuItemType = menuItemType;
+
+ // Store reference to this so we can destroy later
+ vec_push(&menu->callbackDataCache, result);
+
+ return result;
+}
+
+
+
+void DeleteMenu(Menu *menu) {
+
+ // Free menu item hashmap
+ hashmap_destroy(&menu->menuItemMap);
+
+ // Free radio group members
+ if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) {
+ if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) {
+ ABORT("[DeleteMenu] Failed to release radioGroupMap entries!");
+ }
+ }
+
+ // Free radio groups hashmap
+ hashmap_destroy(&menu->radioGroupMap);
+
+ // Free up the processed menu memory
+ if (menu->processedMenu != NULL) {
+ json_delete(menu->processedMenu);
+ menu->processedMenu = NULL;
+ }
+
+ // Release the vector memory
+ vec_deinit(&menu->callbackDataCache);
+
+ // Free nsmenu if we have it
+ if ( menu->menu != NULL ) {
+ msg(menu->menu, s("release"));
+ }
+
+ free(menu);
+}
+
+// Creates a JSON message for the given menuItemID and data
+const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
+ JsonNode *jsonObject = json_mkobject();
+ json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
+ json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType]));
+ if (data != NULL) {
+ json_append_member(jsonObject, "data", json_mkstring(data));
+ }
+ if (parentID != NULL) {
+ json_append_member(jsonObject, "parentID", json_mkstring(parentID));
+ }
+ const char *payload = json_encode(jsonObject);
+ json_delete(jsonObject);
+ const char *result = concat("MC", payload);
+ MEMFREE(payload);
+ return result;
+}
+
+// Callback for text menu items
+void menuItemCallback(id self, SEL cmd, id sender) {
+ MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
+ const char *message;
+
+ // Update checkbox / radio item
+ if( callbackData->menuItemType == Checkbox) {
+ // Toggle state
+ bool state = msg(callbackData->menuItem, s("state"));
+ msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
+ } else if( callbackData->menuItemType == Radio ) {
+ // Check the menu items' current state
+ bool selected = msg(callbackData->menuItem, s("state"));
+
+ // If it's already selected, exit early
+ if (selected) return;
+
+ // Get this item's radio group members and turn them off
+ id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuID, strlen(callbackData->menuID));
+
+ // Uncheck all members of the group
+ id thisMember = members[0];
+ int count = 0;
+ while(thisMember != NULL) {
+ msg(thisMember, s("setState:"), NSControlStateValueOff);
+ count = count + 1;
+ thisMember = members[count];
+ }
+
+ // check the selected menu item
+ msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
+ }
+
+ const char *menuID = callbackData->menuID;
+ const char *data = NULL;
+ enum MenuType menuType = callbackData->menu->menuType;
+ const char *parentID = NULL;
+
+ // Generate message to send to backend
+ if( menuType == ContextMenuType ) {
+ // Get the context menu data from the menu
+ ContextMenu* contextMenu = (ContextMenu*) callbackData->menu->parentData;
+ data = contextMenu->contextMenuData;
+ parentID = contextMenu->ID;
+ } else if ( menuType == TrayMenuType ) {
+ parentID = (const char*) callbackData->menu->parentData;
+ }
+
+ message = createMenuClickedMessage(menuID, data, menuType, parentID);
+
+ // Notify the backend
+ messageFromWindowCallback(message);
+ MEMFREE(message);
+}
+
+id processAcceleratorKey(const char *key) {
+
+ // Guard against no accelerator key
+ if( key == NULL ) {
+ return str("");
+ }
+
+ if( STREQ(key, "Backspace") ) {
+ return strunicode(0x0008);
+ }
+ if( STREQ(key, "Tab") ) {
+ return strunicode(0x0009);
+ }
+ if( STREQ(key, "Return") ) {
+ return strunicode(0x000d);
+ }
+ if( STREQ(key, "Escape") ) {
+ return strunicode(0x001b);
+ }
+ if( STREQ(key, "Left") ) {
+ return strunicode(0x001c);
+ }
+ if( STREQ(key, "Right") ) {
+ return strunicode(0x001d);
+ }
+ if( STREQ(key, "Up") ) {
+ return strunicode(0x001e);
+ }
+ if( STREQ(key, "Down") ) {
+ return strunicode(0x001f);
+ }
+ if( STREQ(key, "Space") ) {
+ return strunicode(0x0020);
+ }
+ if( STREQ(key, "Delete") ) {
+ return strunicode(0x007f);
+ }
+ if( STREQ(key, "Home") ) {
+ return strunicode(0x2196);
+ }
+ if( STREQ(key, "End") ) {
+ return strunicode(0x2198);
+ }
+ if( STREQ(key, "Page Up") ) {
+ return strunicode(0x21de);
+ }
+ if( STREQ(key, "Page Down") ) {
+ return strunicode(0x21df);
+ }
+ if( STREQ(key, "F1") ) {
+ return strunicode(0xf704);
+ }
+ if( STREQ(key, "F2") ) {
+ return strunicode(0xf705);
+ }
+ if( STREQ(key, "F3") ) {
+ return strunicode(0xf706);
+ }
+ if( STREQ(key, "F4") ) {
+ return strunicode(0xf707);
+ }
+ if( STREQ(key, "F5") ) {
+ return strunicode(0xf708);
+ }
+ if( STREQ(key, "F6") ) {
+ return strunicode(0xf709);
+ }
+ if( STREQ(key, "F7") ) {
+ return strunicode(0xf70a);
+ }
+ if( STREQ(key, "F8") ) {
+ return strunicode(0xf70b);
+ }
+ if( STREQ(key, "F9") ) {
+ return strunicode(0xf70c);
+ }
+ if( STREQ(key, "F10") ) {
+ return strunicode(0xf70d);
+ }
+ if( STREQ(key, "F11") ) {
+ return strunicode(0xf70e);
+ }
+ if( STREQ(key, "F12") ) {
+ return strunicode(0xf70f);
+ }
+ if( STREQ(key, "F13") ) {
+ return strunicode(0xf710);
+ }
+ if( STREQ(key, "F14") ) {
+ return strunicode(0xf711);
+ }
+ if( STREQ(key, "F15") ) {
+ return strunicode(0xf712);
+ }
+ if( STREQ(key, "F16") ) {
+ return strunicode(0xf713);
+ }
+ if( STREQ(key, "F17") ) {
+ return strunicode(0xf714);
+ }
+ if( STREQ(key, "F18") ) {
+ return strunicode(0xf715);
+ }
+ if( STREQ(key, "F19") ) {
+ return strunicode(0xf716);
+ }
+ if( STREQ(key, "F20") ) {
+ return strunicode(0xf717);
+ }
+ if( STREQ(key, "F21") ) {
+ return strunicode(0xf718);
+ }
+ if( STREQ(key, "F22") ) {
+ return strunicode(0xf719);
+ }
+ if( STREQ(key, "F23") ) {
+ return strunicode(0xf71a);
+ }
+ if( STREQ(key, "F24") ) {
+ return strunicode(0xf71b);
+ }
+ if( STREQ(key, "F25") ) {
+ return strunicode(0xf71c);
+ }
+ if( STREQ(key, "F26") ) {
+ return strunicode(0xf71d);
+ }
+ if( STREQ(key, "F27") ) {
+ return strunicode(0xf71e);
+ }
+ if( STREQ(key, "F28") ) {
+ return strunicode(0xf71f);
+ }
+ if( STREQ(key, "F29") ) {
+ return strunicode(0xf720);
+ }
+ if( STREQ(key, "F30") ) {
+ return strunicode(0xf721);
+ }
+ if( STREQ(key, "F31") ) {
+ return strunicode(0xf722);
+ }
+ if( STREQ(key, "F32") ) {
+ return strunicode(0xf723);
+ }
+ if( STREQ(key, "F33") ) {
+ return strunicode(0xf724);
+ }
+ if( STREQ(key, "F34") ) {
+ return strunicode(0xf725);
+ }
+ if( STREQ(key, "F35") ) {
+ return strunicode(0xf726);
+ }
+// if( STREQ(key, "Insert") ) {
+// return strunicode(0xf727);
+// }
+// if( STREQ(key, "PrintScreen") ) {
+// return strunicode(0xf72e);
+// }
+// if( STREQ(key, "ScrollLock") ) {
+// return strunicode(0xf72f);
+// }
+ if( STREQ(key, "NumLock") ) {
+ return strunicode(0xf739);
+ }
+
+ return str(key);
+}
+
+
+void addSeparator(id menu) {
+ id item = msg(c("NSMenuItem"), s("separatorItem"));
+ msg(menu, s("addItem:"), item);
+}
+
+id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
+ id item = ALLOC("NSMenuItem");
+ msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
+ return item;
+}
+
+id createMenuItem(id title, const char *action, const char *key) {
+ id item = ALLOC("NSMenuItem");
+ msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
+ msg(item, s("autorelease"));
+ return item;
+}
+
+id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
+ id item = createMenuItem(str(title), action, key);
+ msg(item, s("setEnabled:"), !disabled);
+ msg(menu, s("addItem:"), item);
+ return item;
+}
+
+id createMenu(id title) {
+ id menu = ALLOC("NSMenu");
+ msg(menu, s("initWithTitle:"), title);
+ msg(menu, s("setAutoenablesItems:"), NO);
+// msg(menu, s("autorelease"));
+ return menu;
+}
+
+void createDefaultAppMenu(id parentMenu) {
+// App Menu
+ id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
+ id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
+ id appMenu = createMenu(appName);
+
+ msg(appMenuItem, s("setSubmenu:"), appMenu);
+ msg(parentMenu, s("addItem:"), appMenuItem);
+
+ id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
+ id item = createMenuItem(title, "hide:", "h");
+ msg(appMenu, s("addItem:"), item);
+
+ id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
+ msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
+
+ addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
+
+ addSeparator(appMenu);
+
+ title = msg(str("Quit "), s("stringByAppendingString:"), appName);
+ item = createMenuItem(title, "terminate:", "q");
+ msg(appMenu, s("addItem:"), item);
+}
+
+void createDefaultEditMenu(id parentMenu) {
+ // Edit Menu
+ id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
+ id editMenu = createMenu(str("Edit"));
+
+ msg(editMenuItem, s("setSubmenu:"), editMenu);
+ msg(parentMenu, s("addItem:"), editMenuItem);
+
+ addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
+ addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
+ addSeparator(editMenu);
+ addMenuItem(editMenu, "Cut", "cut:", "x", FALSE);
+ addMenuItem(editMenu, "Copy", "copy:", "c", FALSE);
+ addMenuItem(editMenu, "Paste", "paste:", "v", FALSE);
+ addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE);
+}
+
+void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
+ const char *roleName = item->string_;
+
+ if ( STREQ(roleName, "appMenu") ) {
+ createDefaultAppMenu(parentMenu);
+ return;
+ }
+ if ( STREQ(roleName, "editMenu")) {
+ createDefaultEditMenu(parentMenu);
+ return;
+ }
+ if ( STREQ(roleName, "hide")) {
+ addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "hideothers")) {
+ id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
+ msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
+ return;
+ }
+ if ( STREQ(roleName, "unhide")) {
+ addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "front")) {
+ addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "undo")) {
+ addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "redo")) {
+ addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "cut")) {
+ addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "copy")) {
+ addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "paste")) {
+ addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "delete")) {
+ addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
+ return;
+ }
+ if( STREQ(roleName, "pasteandmatchstyle")) {
+ id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
+ msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
+ }
+ if ( STREQ(roleName, "selectall")) {
+ addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "minimize")) {
+ addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "zoom")) {
+ addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "quit")) {
+ addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
+ return;
+ }
+ if ( STREQ(roleName, "togglefullscreen")) {
+ addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
+ return;
+ }
+
+}
+
+// This converts a string array of modifiers into the
+// equivalent MacOS Modifier Flags
+unsigned long parseModifiers(const char **modifiers) {
+
+ // Our result is a modifier flag list
+ unsigned long result = 0;
+
+ const char *thisModifier = modifiers[0];
+ int count = 0;
+ while( thisModifier != NULL ) {
+ // Determine flags
+ if( STREQ(thisModifier, "CmdOrCtrl") ) {
+ result |= NSEventModifierFlagCommand;
+ }
+ if( STREQ(thisModifier, "OptionOrAlt") ) {
+ result |= NSEventModifierFlagOption;
+ }
+ if( STREQ(thisModifier, "Shift") ) {
+ result |= NSEventModifierFlagShift;
+ }
+ if( STREQ(thisModifier, "Super") ) {
+ result |= NSEventModifierFlagCommand;
+ }
+ if( STREQ(thisModifier, "Control") ) {
+ result |= NSEventModifierFlagControl;
+ }
+ count++;
+ thisModifier = modifiers[count];
+ }
+ return result;
+}
+
+id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) {
+ id item = ALLOC("NSMenuItem");
+
+ // Store the item in the menu item map
+ hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
+
+ // Create a MenuItemCallbackData
+ MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
+
+ id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
+ msg(item, s("setRepresentedObject:"), wrappedId);
+
+ id key = processAcceleratorKey(acceleratorkey);
+
+ msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
+
+ msg(item, s("setEnabled:"), !disabled);
+ msg(item, s("autorelease"));
+ msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
+
+ msg(parentmenu, s("addItem:"), item);
+ return item;
+
+}
+
+id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key) {
+
+ id item = ALLOC("NSMenuItem");
+
+ // Store the item in the menu item map
+ hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
+
+ // Create a MenuItemCallbackData
+ MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
+
+ id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
+ msg(item, s("setRepresentedObject:"), wrappedId);
+ msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
+ msg(item, s("setEnabled:"), !disabled);
+ msg(item, s("autorelease"));
+ msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
+ msg(parentmenu, s("addItem:"), item);
+ return item;
+}
+
+id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) {
+ id item = ALLOC("NSMenuItem");
+
+ // Create a MenuItemCallbackData
+ MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
+
+ id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
+ msg(item, s("setRepresentedObject:"), wrappedId);
+
+ id key = processAcceleratorKey(acceleratorkey);
+ msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
+ s("menuItemCallback:"), key);
+
+ msg(item, s("setEnabled:"), !disabled);
+ msg(item, s("autorelease"));
+
+ // Process modifiers
+ if( modifiers != NULL ) {
+ unsigned long modifierFlags = parseModifiers(modifiers);
+ msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
+ }
+ msg(parentMenu, s("addItem:"), item);
+
+ return item;
+}
+
+void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
+
+ // Check if this item is hidden and if so, exit early!
+ bool hidden = false;
+ getJSONBool(item, "Hidden", &hidden);
+ if( hidden ) {
+ return;
+ }
+
+ // Get the role
+ JsonNode *role = json_find_member(item, "Role");
+ if( role != NULL ) {
+ processMenuRole(menu, parentMenu, role);
+ return;
+ }
+
+ // Check if this is a submenu
+ JsonNode *submenu = json_find_member(item, "SubMenu");
+ if( submenu != NULL ) {
+ // Get the label
+ JsonNode *menuNameNode = json_find_member(item, "Label");
+ const char *name = "";
+ if ( menuNameNode != NULL) {
+ name = menuNameNode->string_;
+ }
+
+ id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
+ id thisMenu = createMenu(str(name));
+
+ msg(thisMenuItem, s("setSubmenu:"), thisMenu);
+ msg(parentMenu, s("addItem:"), thisMenuItem);
+
+ JsonNode *submenuItems = json_find_member(submenu, "Items");
+ // If we have no items, just return
+ if ( submenuItems == NULL ) {
+ return;
+ }
+
+ // Loop over submenu items
+ JsonNode *item;
+ json_foreach(item, submenuItems) {
+ // Get item label
+ processMenuItem(menu, thisMenu, item);
+ }
+
+ return;
+ }
+
+ // This is a user menu. Get the common data
+ // Get the label
+ const char *label = getJSONString(item, "Label");
+ if ( label == NULL) {
+ label = "(empty)";
+ }
+
+ const char *menuid = getJSONString(item, "ID");
+ if ( menuid == NULL) {
+ menuid = "";
+ }
+
+ bool disabled = false;
+ getJSONBool(item, "Disabled", &disabled);
+
+ // Get the Accelerator
+ JsonNode *accelerator = json_find_member(item, "Accelerator");
+ const char *acceleratorkey = NULL;
+ const char **modifiers = NULL;
+
+ // If we have an accelerator
+ if( accelerator != NULL ) {
+ // Get the key
+ acceleratorkey = getJSONString(accelerator, "Key");
+ // Check if there are modifiers
+ JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
+ if ( modifiersList != NULL ) {
+ // Allocate an array of strings
+ int noOfModifiers = json_array_length(modifiersList);
+
+ // Do we have any?
+ if (noOfModifiers > 0) {
+ modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
+ JsonNode *modifier;
+ int count = 0;
+ // Iterate the modifiers and save a reference to them in our new array
+ json_foreach(modifier, modifiersList) {
+ // Get modifier name
+ modifiers[count] = modifier->string_;
+ count++;
+ }
+ // Null terminate the modifier list
+ modifiers[count] = NULL;
+ }
+ }
+ }
+
+ // Get the Type
+ JsonNode *type = json_find_member(item, "Type");
+ if( type != NULL ) {
+
+ if( STREQ(type->string_, "Text")) {
+ processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers);
+ }
+ else if ( STREQ(type->string_, "Separator")) {
+ addSeparator(parentMenu);
+ }
+ else if ( STREQ(type->string_, "Checkbox")) {
+ // Get checked state
+ bool checked = false;
+ getJSONBool(item, "Checked", &checked);
+
+ processCheckboxMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
+ }
+ else if ( STREQ(type->string_, "Radio")) {
+ // Get checked state
+ bool checked = false;
+ getJSONBool(item, "Checked", &checked);
+
+ processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
+ }
+
+ }
+
+ if ( modifiers != NULL ) {
+ free(modifiers);
+ }
+
+ return;
+}
+
+void processMenuData(Menu *menu, JsonNode *menuData) {
+ JsonNode *items = json_find_member(menuData, "Items");
+ if( items == NULL ) {
+ // Parse error!
+ ABORT("Unable to find 'Items' in menu JSON!");
+ }
+
+ // Iterate items
+ JsonNode *item;
+ json_foreach(item, items) {
+ // Process each menu item
+ processMenuItem(menu, menu->menu, item);
+ }
+}
+
+void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
+
+ int groupLength;
+ getJSONInt(radioGroup, "Length", &groupLength);
+ JsonNode *members = json_find_member(radioGroup, "Members");
+ JsonNode *member;
+
+ // Allocate array
+ size_t arrayLength = sizeof(id)*(groupLength+1);
+ id memberList[arrayLength];
+
+ // Build the radio group items
+ int count=0;
+ json_foreach(member, members) {
+ // Get menu by id
+ id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
+ // Save Member
+ memberList[count] = menuItem;
+ count = count + 1;
+ }
+ // Null terminate array
+ memberList[groupLength] = 0;
+
+ // Store the members
+ json_foreach(member, members) {
+ // Copy the memberList
+ char *newMemberList = (char *)malloc(arrayLength);
+ memcpy(newMemberList, memberList, arrayLength);
+ // add group to each member of group
+ hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
+ }
+
+}
+
+id GetMenu(Menu *menu) {
+
+ // Pull out the menu data
+ JsonNode *menuData = json_find_member(menu->processedMenu, "Menu");
+ if( menuData == NULL ) {
+ ABORT("Unable to find Menu data: %s", menu->processedMenu);
+ }
+
+ menu->menu = createMenu(str(""));
+
+ // Process the menu data
+ processMenuData(menu, menuData);
+
+ // Create the radiogroup cache
+ JsonNode *radioGroups = json_find_member(menu->processedMenu, "RadioGroups");
+ if( radioGroups == NULL ) {
+ // Parse error!
+ ABORT("Unable to find RadioGroups data: %s", menu->processedMenu);
+ }
+
+ // Iterate radio groups
+ JsonNode *radioGroup;
+ json_foreach(radioGroup, radioGroups) {
+ // Get item label
+ processRadioGroupJSON(menu, radioGroup);
+ }
+
+ return menu->menu;
+}
+
diff --git a/v2/internal/ffenestri/menu_darwin.h b/v2/internal/ffenestri/menu_darwin.h
new file mode 100644
index 000000000..274a2376d
--- /dev/null
+++ b/v2/internal/ffenestri/menu_darwin.h
@@ -0,0 +1,100 @@
+//
+// Created by Lea Anthony on 6/1/21.
+//
+
+#ifndef MENU_DARWIN_H
+#define MENU_DARWIN_H
+
+#include "common.h"
+#include "ffenestri_darwin.h"
+
+enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
+enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
+static const char *MenuTypeAsString[] = {
+ "ApplicationMenu", "ContextMenu", "TrayMenu",
+};
+
+extern void messageFromWindowCallback(const char *);
+
+typedef struct {
+
+ const char *title;
+
+ /*** Internal ***/
+
+ // The decoded version of the Menu JSON
+ JsonNode *processedMenu;
+
+ struct hashmap_s menuItemMap;
+ struct hashmap_s radioGroupMap;
+
+ // Vector to keep track of callback data memory
+ vec_void_t callbackDataCache;
+
+ // The NSMenu for this menu
+ id menu;
+
+ // The parent data, eg ContextMenuStore or Tray
+ void *parentData;
+
+ // The commands for the menu callbacks
+ const char *callbackCommand;
+
+ // This indicates if we are an Application Menu, tray menu or context menu
+ enum MenuType menuType;
+
+
+} Menu;
+
+
+typedef struct {
+ id menuItem;
+ Menu *menu;
+ const char *menuID;
+ enum MenuItemType menuItemType;
+} MenuItemCallbackData;
+
+
+
+// NewMenu creates a new Menu struct, saving the given menu structure as JSON
+Menu* NewMenu(JsonNode *menuData);
+
+Menu* NewApplicationMenu(const char *menuAsJSON);
+MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType);
+
+void DeleteMenu(Menu *menu);
+
+// Creates a JSON message for the given menuItemID and data
+const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID);
+// Callback for text menu items
+void menuItemCallback(id self, SEL cmd, id sender);
+id processAcceleratorKey(const char *key);
+
+
+void addSeparator(id menu);
+id createMenuItemNoAutorelease( id title, const char *action, const char *key);
+
+id createMenuItem(id title, const char *action, const char *key);
+
+id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
+
+id createMenu(id title);
+void createDefaultAppMenu(id parentMenu);
+void createDefaultEditMenu(id parentMenu);
+
+void processMenuRole(Menu *menu, id parentMenu, JsonNode *item);
+// This converts a string array of modifiers into the
+// equivalent MacOS Modifier Flags
+unsigned long parseModifiers(const char **modifiers);
+id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey);
+
+id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
+
+id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers);
+
+void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
+void processMenuData(Menu *menu, JsonNode *menuData);
+
+void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
+id GetMenu(Menu *menu);
+#endif //ASSETS_C_MENU_DARWIN_H
diff --git a/v2/internal/ffenestri/runtime_darwin.c b/v2/internal/ffenestri/runtime_darwin.c
new file mode 100644
index 000000000..990919ee7
--- /dev/null
+++ b/v2/internal/ffenestri/runtime_darwin.c
@@ -0,0 +1,5 @@
+
+// runtime.c (c) 2019-Present Lea Anthony.
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file was auto-generated. DO NOT MODIFY.
+const unsigned char runtime[]={0x76, 0x61, 0x72, 0x20, 0x57, 0x61, 0x69, 0x6c, 0x73, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x7b, 0x7d, 0x3b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x28, 0x72, 0x29, 0x7b, 0x69, 0x66, 0x28, 0x74, 0x5b, 0x72, 0x5d, 0x29, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x5b, 0x72, 0x5d, 0x2e, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x69, 0x3d, 0x74, 0x5b, 0x72, 0x5d, 0x3d, 0x7b, 0x69, 0x3a, 0x72, 0x2c, 0x6c, 0x3a, 0x21, 0x31, 0x2c, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x3a, 0x7b, 0x7d, 0x7d, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x5b, 0x72, 0x5d, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x69, 0x2e, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2c, 0x69, 0x2c, 0x69, 0x2e, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2c, 0x65, 0x29, 0x2c, 0x69, 0x2e, 0x6c, 0x3d, 0x21, 0x30, 0x2c, 0x69, 0x2e, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x7d, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x2e, 0x6d, 0x3d, 0x6e, 0x2c, 0x65, 0x2e, 0x63, 0x3d, 0x74, 0x2c, 0x65, 0x2e, 0x64, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x72, 0x29, 0x7b, 0x65, 0x2e, 0x6f, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7c, 0x7c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x7b, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x21, 0x30, 0x2c, 0x67, 0x65, 0x74, 0x3a, 0x72, 0x7d, 0x29, 0x7d, 0x2c, 0x65, 0x2e, 0x72, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x22, 0x75, 0x6e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x22, 0x21, 0x3d, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x26, 0x26, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x26, 0x26, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6e, 0x2c, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x2c, 0x7b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x7d, 0x29, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6e, 0x2c, 0x22, 0x5f, 0x5f, 0x65, 0x73, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x2c, 0x7b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x21, 0x30, 0x7d, 0x29, 0x7d, 0x2c, 0x65, 0x2e, 0x74, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x69, 0x66, 0x28, 0x31, 0x26, 0x74, 0x26, 0x26, 0x28, 0x6e, 0x3d, 0x65, 0x28, 0x6e, 0x29, 0x29, 0x2c, 0x38, 0x26, 0x74, 0x29, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x3b, 0x69, 0x66, 0x28, 0x34, 0x26, 0x74, 0x26, 0x26, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3d, 0x3d, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6e, 0x26, 0x26, 0x6e, 0x26, 0x26, 0x6e, 0x2e, 0x5f, 0x5f, 0x65, 0x73, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x29, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x29, 0x3b, 0x69, 0x66, 0x28, 0x65, 0x2e, 0x72, 0x28, 0x72, 0x29, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x72, 0x2c, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0x2c, 0x7b, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x21, 0x30, 0x2c, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x6e, 0x7d, 0x29, 0x2c, 0x32, 0x26, 0x74, 0x26, 0x26, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x21, 0x3d, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6e, 0x29, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x69, 0x6e, 0x20, 0x6e, 0x29, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x69, 0x2c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x74, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x5b, 0x74, 0x5d, 0x7d, 0x2e, 0x62, 0x69, 0x6e, 0x64, 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x69, 0x29, 0x29, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x7d, 0x2c, 0x65, 0x2e, 0x6e, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x6e, 0x26, 0x26, 0x6e, 0x2e, 0x5f, 0x5f, 0x65, 0x73, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x3f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x7d, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x7d, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x2e, 0x64, 0x28, 0x74, 0x2c, 0x22, 0x61, 0x22, 0x2c, 0x74, 0x29, 0x2c, 0x74, 0x7d, 0x2c, 0x65, 0x2e, 0x6f, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x68, 0x61, 0x73, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7d, 0x2c, 0x65, 0x2e, 0x70, 0x3d, 0x22, 0x22, 0x2c, 0x65, 0x28, 0x65, 0x2e, 0x73, 0x3d, 0x31, 0x29, 0x7d, 0x28, 0x5b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x29, 0x7b, 0x22, 0x75, 0x73, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x22, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x74, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x74, 0x2c, 0x22, 0x41, 0x70, 0x70, 0x54, 0x79, 0x70, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x22, 0x64, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x22, 0x7d, 0x2c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x29, 0x7b, 0x22, 0x75, 0x73, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x22, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x74, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x7b, 0x7d, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x72, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x54, 0x72, 0x61, 0x63, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x62, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x79, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x44, 0x65, 0x62, 0x75, 0x67, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x67, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6d, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x4f, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x68, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x53, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x45, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x72, 0x2c, 0x22, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6a, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x69, 0x3d, 0x7b, 0x7d, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x69, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x69, 0x2c, 0x22, 0x4f, 0x70, 0x65, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x44, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x3d, 0x7b, 0x7d, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x6f, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x4d, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x53, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x78, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x46, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x54, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x55, 0x6e, 0x46, 0x75, 0x6c, 0x6c, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x49, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x53, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x4a, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x53, 0x65, 0x74, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x4c, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x48, 0x69, 0x64, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x41, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x53, 0x68, 0x6f, 0x77, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x52, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x69, 0x73, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x48, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x55, 0x6e, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x69, 0x73, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x5f, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x69, 0x73, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x46, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x55, 0x6e, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x69, 0x73, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x55, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x6f, 0x2c, 0x22, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x42, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x61, 0x3d, 0x7b, 0x7d, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x61, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x61, 0x2c, 0x22, 0x4f, 0x70, 0x65, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x47, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x61, 0x2c, 0x22, 0x53, 0x61, 0x76, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x71, 0x7d, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x61, 0x2c, 0x22, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x7a, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x75, 0x3d, 0x7b, 0x7d, 0x3b, 0x65, 0x2e, 0x72, 0x28, 0x75, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x75, 0x2c, 0x22, 0x4e, 0x65, 0x77, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x6e, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x63, 0x3d, 0x7b, 0x7d, 0x3b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x28, 0x6e, 0x29, 0x3b, 0x69, 0x66, 0x28, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x28, 0x6e, 0x29, 0x3b, 0x74, 0x26, 0x26, 0x28, 0x72, 0x3d, 0x72, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x74, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x2e, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x7d, 0x29, 0x29, 0x29, 0x2c, 0x65, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x28, 0x65, 0x2c, 0x72, 0x29, 0x7d, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x6e, 0x3f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x7b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x65, 0x2c, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x21, 0x30, 0x2c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x21, 0x30, 0x2c, 0x77, 0x72, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x21, 0x30, 0x7d, 0x29, 0x3a, 0x6e, 0x5b, 0x74, 0x5d, 0x3d, 0x65, 0x2c, 0x6e, 0x7d, 0x65, 0x2e, 0x72, 0x28, 0x63, 0x29, 0x2c, 0x65, 0x2e, 0x64, 0x28, 0x63, 0x2c, 0x22, 0x53, 0x65, 0x74, 0x49, 0x63, 0x6f, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x61, 0x6e, 0x7d, 0x29, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x66, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x31, 0x3b, 0x74, 0x3c, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x74, 0x2b, 0x2b, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3d, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5b, 0x74, 0x5d, 0x3f, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5b, 0x74, 0x5d, 0x3a, 0x7b, 0x7d, 0x3b, 0x74, 0x25, 0x32, 0x3f, 0x73, 0x28, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x65, 0x29, 0x2c, 0x21, 0x30, 0x29, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x74, 0x29, 0x7b, 0x6c, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x5b, 0x74, 0x5d, 0x29, 0x7d, 0x29, 0x29, 0x3a, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x3f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x28, 0x6e, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x28, 0x65, 0x29, 0x29, 0x3a, 0x73, 0x28, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x65, 0x29, 0x29, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x74, 0x29, 0x7b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x28, 0x65, 0x2c, 0x74, 0x29, 0x29, 0x7d, 0x29, 0x29, 0x7d, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x7d, 0x28, 0x7b, 0x7d, 0x2c, 0x65, 0x28, 0x30, 0x29, 0x2c, 0x7b, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x22, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x22, 0x7d, 0x7d, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x64, 0x3d, 0x5b, 0x5d, 0x3b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x28, 0x6e, 0x29, 0x7b, 0x64, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x28, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x70, 0x28, 0x6e, 0x29, 0x7b, 0x69, 0x66, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x6f, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x6e, 0x29, 0x7d, 0x28, 0x6e, 0x29, 0x2c, 0x64, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3e, 0x30, 0x29, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x30, 0x3b, 0x74, 0x3c, 0x64, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x74, 0x2b, 0x2b, 0x29, 0x64, 0x5b, 0x74, 0x5d, 0x28, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x76, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x4c, 0x22, 0x2b, 0x6e, 0x2b, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x62, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x54, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x79, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x50, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x67, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x44, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x49, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4f, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x57, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x45, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x46, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x45, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x28, 0x22, 0x53, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x76, 0x61, 0x72, 0x20, 0x6b, 0x2c, 0x6a, 0x3d, 0x7b, 0x54, 0x52, 0x41, 0x43, 0x45, 0x3a, 0x31, 0x2c, 0x44, 0x45, 0x42, 0x55, 0x47, 0x3a, 0x32, 0x2c, 0x49, 0x4e, 0x46, 0x4f, 0x3a, 0x33, 0x2c, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x3a, 0x34, 0x2c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x3a, 0x35, 0x7d, 0x2c, 0x50, 0x3d, 0x7b, 0x7d, 0x3b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x65, 0x26, 0x26, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x65, 0x7c, 0x7c, 0x28, 0x65, 0x3d, 0x30, 0x29, 0x2c, 0x6e, 0x65, 0x77, 0x20, 0x50, 0x72, 0x6f, 0x6d, 0x69, 0x73, 0x65, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x72, 0x2c, 0x69, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x3b, 0x64, 0x6f, 0x7b, 0x6f, 0x3d, 0x6e, 0x2b, 0x22, 0x2d, 0x22, 0x2b, 0x6b, 0x28, 0x29, 0x7d, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x28, 0x50, 0x5b, 0x6f, 0x5d, 0x29, 0x3b, 0x69, 0x66, 0x28, 0x65, 0x3e, 0x30, 0x29, 0x76, 0x61, 0x72, 0x20, 0x61, 0x3d, 0x73, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x69, 0x28, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x22, 0x43, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x22, 0x2b, 0x6e, 0x2b, 0x22, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x64, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x20, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x49, 0x44, 0x3a, 0x20, 0x22, 0x2b, 0x6f, 0x29, 0x29, 0x7d, 0x29, 0x2c, 0x65, 0x29, 0x3b, 0x50, 0x5b, 0x6f, 0x5d, 0x3d, 0x7b, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x3a, 0x61, 0x2c, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x3a, 0x69, 0x2c, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x3a, 0x72, 0x7d, 0x3b, 0x74, 0x72, 0x79, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x75, 0x3d, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x6e, 0x2c, 0x61, 0x72, 0x67, 0x73, 0x3a, 0x74, 0x2c, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x49, 0x44, 0x3a, 0x6f, 0x7d, 0x3b, 0x70, 0x28, 0x22, 0x43, 0x22, 0x2b, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x75, 0x29, 0x29, 0x7d, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x6e, 0x29, 0x7b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x6e, 0x29, 0x7d, 0x7d, 0x29, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3b, 0x74, 0x72, 0x79, 0x7b, 0x74, 0x3d, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x28, 0x6e, 0x29, 0x7d, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x74, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3d, 0x22, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x70, 0x61, 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x20, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x22, 0x2e, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x22, 0x29, 0x2e, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x6e, 0x29, 0x3b, 0x74, 0x68, 0x72, 0x6f, 0x77, 0x20, 0x67, 0x28, 0x65, 0x29, 0x2c, 0x6e, 0x65, 0x77, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x65, 0x29, 0x7d, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x74, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x69, 0x64, 0x2c, 0x69, 0x3d, 0x50, 0x5b, 0x72, 0x5d, 0x3b, 0x69, 0x66, 0x28, 0x21, 0x69, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x3d, 0x22, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x20, 0x27, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x28, 0x72, 0x2c, 0x22, 0x27, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x21, 0x21, 0x21, 0x22, 0x29, 0x3b, 0x74, 0x68, 0x72, 0x6f, 0x77, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x6f, 0x29, 0x2c, 0x6e, 0x65, 0x77, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x6f, 0x29, 0x7d, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x28, 0x69, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x29, 0x2c, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x50, 0x5b, 0x72, 0x5d, 0x2c, 0x74, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x3f, 0x69, 0x2e, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x28, 0x74, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x29, 0x3a, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x28, 0x74, 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x57, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x5b, 0x5d, 0x2e, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x28, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x29, 0x2e, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x28, 0x31, 0x29, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x43, 0x28, 0x22, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x22, 0x2b, 0x6e, 0x2c, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x44, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x70, 0x28, 0x22, 0x52, 0x42, 0x4f, 0x22, 0x2b, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4d, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x63, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x78, 0x28, 0x6e, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x54, 0x22, 0x2b, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x46, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x66, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4a, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x73, 0x3a, 0x22, 0x2b, 0x6e, 0x2b, 0x22, 0x3a, 0x22, 0x2b, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4c, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x70, 0x3a, 0x22, 0x2b, 0x6e, 0x2b, 0x22, 0x3a, 0x22, 0x2b, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x48, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x52, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x53, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x48, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x4d, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x5f, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x55, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x46, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x6d, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x55, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x75, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x42, 0x28, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x57, 0x43, 0x22, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x47, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x57, 0x28, 0x22, 0x44, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x71, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x57, 0x28, 0x22, 0x44, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x53, 0x61, 0x76, 0x65, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7a, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x57, 0x28, 0x22, 0x44, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x6e, 0x29, 0x7d, 0x6b, 0x3d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x3f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x6e, 0x3d, 0x6e, 0x65, 0x77, 0x20, 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x41, 0x72, 0x72, 0x61, 0x79, 0x28, 0x31, 0x29, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x67, 0x65, 0x74, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x28, 0x6e, 0x29, 0x5b, 0x30, 0x5d, 0x7d, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x39, 0x30, 0x30, 0x37, 0x31, 0x39, 0x39, 0x32, 0x35, 0x34, 0x37, 0x34, 0x30, 0x39, 0x39, 0x31, 0x2a, 0x4d, 0x61, 0x74, 0x68, 0x2e, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x28, 0x29, 0x7d, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x3d, 0x7b, 0x7d, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x56, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x28, 0x74, 0x2c, 0x65, 0x29, 0x7b, 0x21, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x69, 0x66, 0x28, 0x21, 0x28, 0x6e, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x6f, 0x66, 0x20, 0x74, 0x29, 0x29, 0x74, 0x68, 0x72, 0x6f, 0x77, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x54, 0x79, 0x70, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x22, 0x43, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x29, 0x7d, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2c, 0x6e, 0x29, 0x2c, 0x65, 0x3d, 0x65, 0x7c, 0x7c, 0x2d, 0x31, 0x2c, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x6e, 0x29, 0x2c, 0x2d, 0x31, 0x21, 0x3d, 0x3d, 0x65, 0x26, 0x26, 0x30, 0x3d, 0x3d, 0x3d, 0x28, 0x65, 0x2d, 0x3d, 0x31, 0x29, 0x7d, 0x7d, 0x2c, 0x4b, 0x3d, 0x7b, 0x7d, 0x3b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x51, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x29, 0x7b, 0x4b, 0x5b, 0x6e, 0x5d, 0x3d, 0x4b, 0x5b, 0x6e, 0x5d, 0x7c, 0x7c, 0x5b, 0x5d, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x6e, 0x65, 0x77, 0x20, 0x56, 0x28, 0x74, 0x2c, 0x65, 0x29, 0x3b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x50, 0x75, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x3a, 0x20, 0x22, 0x2b, 0x6e, 0x29, 0x2c, 0x4b, 0x5b, 0x6e, 0x5d, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x28, 0x72, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x58, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x51, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x59, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x51, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x31, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x5a, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x6e, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x69, 0x66, 0x28, 0x4b, 0x5b, 0x74, 0x5d, 0x29, 0x7b, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3d, 0x4b, 0x5b, 0x74, 0x5d, 0x2e, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x28, 0x29, 0x2c, 0x72, 0x3d, 0x30, 0x3b, 0x72, 0x3c, 0x4b, 0x5b, 0x74, 0x5d, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x72, 0x2b, 0x3d, 0x31, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x69, 0x3d, 0x4b, 0x5b, 0x74, 0x5d, 0x5b, 0x72, 0x5d, 0x2c, 0x6f, 0x3d, 0x6e, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x3b, 0x69, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x6f, 0x29, 0x26, 0x26, 0x65, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x63, 0x65, 0x28, 0x72, 0x2c, 0x31, 0x29, 0x7d, 0x4b, 0x5b, 0x74, 0x5d, 0x3d, 0x65, 0x7d, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x24, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3b, 0x74, 0x72, 0x79, 0x7b, 0x74, 0x3d, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x28, 0x6e, 0x29, 0x7d, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x74, 0x29, 0x7b, 0x74, 0x68, 0x72, 0x6f, 0x77, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x68, 0x28, 0x22, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x70, 0x61, 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x3a, 0x20, 0x22, 0x2b, 0x6e, 0x29, 0x7d, 0x5a, 0x28, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x6e, 0x2c, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x5b, 0x5d, 0x2e, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x28, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x29, 0x2e, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x28, 0x31, 0x29, 0x7d, 0x3b, 0x5a, 0x28, 0x74, 0x29, 0x2c, 0x70, 0x28, 0x22, 0x45, 0x6a, 0x22, 0x2b, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x74, 0x29, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3d, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x29, 0x3b, 0x65, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x3d, 0x6e, 0x2c, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x62, 0x6f, 0x64, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x65, 0x29, 0x2c, 0x74, 0x26, 0x26, 0x6e, 0x6e, 0x28, 0x74, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x65, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x74, 0x72, 0x79, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x22, 0x29, 0x3b, 0x74, 0x2e, 0x73, 0x65, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x63, 0x73, 0x73, 0x22, 0x29, 0x2c, 0x74, 0x2e, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x53, 0x68, 0x65, 0x65, 0x74, 0x3f, 0x74, 0x2e, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x53, 0x68, 0x65, 0x65, 0x74, 0x2e, 0x63, 0x73, 0x73, 0x54, 0x65, 0x78, 0x74, 0x3d, 0x6e, 0x3a, 0x74, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x6e, 0x29, 0x29, 0x2c, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x7c, 0x7c, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x54, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x28, 0x22, 0x68, 0x65, 0x61, 0x64, 0x22, 0x29, 0x5b, 0x30, 0x5d, 0x29, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x74, 0x29, 0x7d, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x6e, 0x29, 0x7b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x6e, 0x29, 0x7d, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x6e, 0x28, 0x29, 0x7b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x57, 0x61, 0x69, 0x6c, 0x73, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x3d, 0x21, 0x30, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3b, 0x69, 0x66, 0x28, 0x21, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x29, 0x74, 0x68, 0x72, 0x6f, 0x77, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x22, 0x57, 0x61, 0x69, 0x6c, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x73, 0x65, 0x64, 0x22, 0x29, 0x3b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x5b, 0x5d, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x4f, 0x6e, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x73, 0x79, 0x6e, 0x63, 0x3a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x62, 0x79, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x3a, 0x22, 0x2b, 0x6e, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x28, 0x6e, 0x29, 0x3b, 0x65, 0x3d, 0x74, 0x2c, 0x72, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x6e, 0x28, 0x65, 0x29, 0x7d, 0x29, 0x29, 0x7d, 0x29, 0x29, 0x2c, 0x74, 0x26, 0x26, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x28, 0x74, 0x29, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x69, 0x74, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x73, 0x79, 0x6e, 0x63, 0x3a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3a, 0x72, 0x65, 0x73, 0x79, 0x6e, 0x63, 0x3a, 0x22, 0x2b, 0x6e, 0x29, 0x2c, 0x7b, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x28, 0x6e, 0x29, 0x7d, 0x2c, 0x67, 0x65, 0x74, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x65, 0x7d, 0x2c, 0x73, 0x65, 0x74, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x74, 0x29, 0x7b, 0x65, 0x3d, 0x74, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x69, 0x74, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x73, 0x79, 0x6e, 0x63, 0x3a, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x62, 0x79, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x3a, 0x22, 0x2b, 0x6e, 0x2c, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x65, 0x29, 0x29, 0x2c, 0x72, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x6e, 0x28, 0x65, 0x29, 0x7d, 0x29, 0x29, 0x7d, 0x2c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x3a, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x6e, 0x28, 0x65, 0x29, 0x3b, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x28, 0x74, 0x29, 0x7d, 0x7d, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x70, 0x28, 0x22, 0x54, 0x49, 0x22, 0x2b, 0x6e, 0x29, 0x7d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x28, 0x75, 0x6e, 0x3d, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x7c, 0x7c, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x31, 0x3b, 0x74, 0x3c, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x74, 0x2b, 0x2b, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x65, 0x3d, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x5b, 0x74, 0x5d, 0x3b, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x29, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x68, 0x61, 0x73, 0x4f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x65, 0x2c, 0x72, 0x29, 0x26, 0x26, 0x28, 0x6e, 0x5b, 0x72, 0x5d, 0x3d, 0x65, 0x5b, 0x72, 0x5d, 0x29, 0x7d, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6e, 0x7d, 0x29, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2c, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x29, 0x7d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x2c, 0x74, 0x2c, 0x65, 0x2c, 0x72, 0x2c, 0x69, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x6f, 0x3d, 0x7b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x6e, 0x2c, 0x75, 0x72, 0x6c, 0x3a, 0x74, 0x2c, 0x6c, 0x69, 0x6e, 0x65, 0x3a, 0x65, 0x2c, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x3a, 0x72, 0x2c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x3a, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x69, 0x29, 0x2c, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x3a, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x28, 0x6e, 0x65, 0x77, 0x20, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x29, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x29, 0x7d, 0x3b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x6f, 0x29, 0x29, 0x7d, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x3d, 0x7b, 0x7d, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3d, 0x7b, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a, 0x66, 0x2c, 0x4c, 0x6f, 0x67, 0x3a, 0x72, 0x2c, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x3a, 0x69, 0x2c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x3a, 0x6f, 0x2c, 0x54, 0x72, 0x61, 0x79, 0x3a, 0x63, 0x2c, 0x44, 0x69, 0x61, 0x6c, 0x6f, 0x67, 0x3a, 0x61, 0x2c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x7b, 0x4f, 0x6e, 0x3a, 0x58, 0x2c, 0x4f, 0x6e, 0x63, 0x65, 0x3a, 0x59, 0x2c, 0x4f, 0x6e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x3a, 0x51, 0x2c, 0x45, 0x6d, 0x69, 0x74, 0x3a, 0x6e, 0x6e, 0x7d, 0x2c, 0x5f, 0x3a, 0x7b, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x3a, 0x4e, 0x2c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x3a, 0x24, 0x2c, 0x41, 0x64, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3a, 0x74, 0x6e, 0x2c, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x53, 0x53, 0x3a, 0x65, 0x6e, 0x2c, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x3a, 0x72, 0x6e, 0x2c, 0x41, 0x64, 0x64, 0x49, 0x50, 0x43, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x3a, 0x77, 0x2c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x57, 0x7d, 0x2c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x3a, 0x75, 0x7d, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3d, 0x7b, 0x49, 0x73, 0x44, 0x61, 0x72, 0x6b, 0x4d, 0x6f, 0x64, 0x65, 0x3a, 0x6f, 0x6e, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x69, 0x73, 0x64, 0x61, 0x72, 0x6b, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x29, 0x2c, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x6f, 0x6e, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x6c, 0x6f, 0x67, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x29, 0x2c, 0x41, 0x70, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x3a, 0x6f, 0x6e, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x61, 0x70, 0x70, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x29, 0x7d, 0x2c, 0x75, 0x6e, 0x28, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x66, 0x29, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x22, 0x6d, 0x6f, 0x75, 0x73, 0x65, 0x64, 0x6f, 0x77, 0x6e, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x74, 0x3d, 0x6e, 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x3b, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x74, 0x26, 0x26, 0x21, 0x74, 0x2e, 0x68, 0x61, 0x73, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x22, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2d, 0x6e, 0x6f, 0x2d, 0x64, 0x72, 0x61, 0x67, 0x22, 0x29, 0x3b, 0x29, 0x7b, 0x69, 0x66, 0x28, 0x74, 0x2e, 0x68, 0x61, 0x73, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x22, 0x64, 0x61, 0x74, 0x61, 0x2d, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2d, 0x64, 0x72, 0x61, 0x67, 0x22, 0x29, 0x29, 0x7b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x44, 0x72, 0x61, 0x67, 0x2e, 0x70, 0x6f, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x29, 0x3b, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x7d, 0x74, 0x3d, 0x74, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x7d, 0x7d, 0x29, 0x29, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x22, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x6d, 0x65, 0x6e, 0x75, 0x22, 0x2c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x66, 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x74, 0x2c, 0x65, 0x3d, 0x6e, 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x3b, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x65, 0x26, 0x26, 0x6e, 0x75, 0x6c, 0x6c, 0x3d, 0x3d, 0x28, 0x74, 0x3d, 0x65, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x5b, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x6d, 0x65, 0x6e, 0x75, 0x2d, 0x69, 0x64, 0x22, 0x5d, 0x29, 0x3b, 0x29, 0x65, 0x3d, 0x65, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x3b, 0x69, 0x66, 0x28, 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x74, 0x7c, 0x7c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x57, 0x61, 0x69, 0x6c, 0x73, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x29, 0x26, 0x26, 0x6e, 0x2e, 0x70, 0x72, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x28, 0x29, 0x2c, 0x6e, 0x75, 0x6c, 0x6c, 0x21, 0x3d, 0x74, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x7b, 0x69, 0x64, 0x3a, 0x74, 0x2c, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x65, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x5b, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x6d, 0x65, 0x6e, 0x75, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x22, 0x5d, 0x7c, 0x7c, 0x22, 0x22, 0x7d, 0x3b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x2e, 0x70, 0x6f, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x72, 0x29, 0x29, 0x7d, 0x7d, 0x29, 0x29, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x3d, 0x21, 0x30, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x26, 0x26, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x74, 0x72, 0x79, 0x7b, 0x6e, 0x3d, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x70, 0x61, 0x72, 0x73, 0x65, 0x28, 0x6e, 0x29, 0x7d, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x6e, 0x29, 0x7b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x6e, 0x29, 0x7d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x3d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x7c, 0x7c, 0x7b, 0x7d, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x28, 0x6e, 0x29, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x74, 0x29, 0x7b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5b, 0x74, 0x5d, 0x3d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5b, 0x74, 0x5d, 0x7c, 0x7c, 0x7b, 0x7d, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x28, 0x6e, 0x5b, 0x74, 0x5d, 0x29, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x65, 0x29, 0x7b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5b, 0x74, 0x5d, 0x5b, 0x65, 0x5d, 0x3d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5b, 0x74, 0x5d, 0x5b, 0x65, 0x5d, 0x7c, 0x7c, 0x7b, 0x7d, 0x2c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x28, 0x6e, 0x5b, 0x74, 0x5d, 0x5b, 0x65, 0x5d, 0x29, 0x2e, 0x66, 0x6f, 0x72, 0x45, 0x61, 0x63, 0x68, 0x28, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x5b, 0x74, 0x5d, 0x5b, 0x65, 0x5d, 0x5b, 0x6e, 0x5d, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x72, 0x3d, 0x30, 0x3b, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x28, 0x29, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x69, 0x3d, 0x5b, 0x5d, 0x2e, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x61, 0x6c, 0x6c, 0x28, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x29, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x43, 0x28, 0x5b, 0x74, 0x2c, 0x65, 0x2c, 0x6e, 0x5d, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x28, 0x22, 0x2e, 0x22, 0x29, 0x2c, 0x69, 0x2c, 0x72, 0x29, 0x7d, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x69, 0x2e, 0x73, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x29, 0x7b, 0x72, 0x3d, 0x6e, 0x7d, 0x2c, 0x69, 0x2e, 0x67, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x3d, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x7b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x7d, 0x2c, 0x69, 0x7d, 0x28, 0x29, 0x7d, 0x29, 0x29, 0x7d, 0x29, 0x29, 0x7d, 0x29, 0x29, 0x7d, 0x28, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x62, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x29, 0x2c, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x45, 0x6d, 0x69, 0x74, 0x28, 0x22, 0x77, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x22, 0x29, 0x7d, 0x5d, 0x29, 0x3b, 0x00};
\ No newline at end of file
diff --git a/v2/internal/ffenestri/traymenu_darwin.c b/v2/internal/ffenestri/traymenu_darwin.c
new file mode 100644
index 000000000..c2afaffce
--- /dev/null
+++ b/v2/internal/ffenestri/traymenu_darwin.c
@@ -0,0 +1,199 @@
+//
+// Created by Lea Anthony on 12/1/21.
+//
+
+#include "common.h"
+#include "traymenu_darwin.h"
+#include "trayicons.h"
+
+// A cache for all our tray menu icons
+// Global because it's a singleton
+struct hashmap_s trayIconCache;
+
+TrayMenu* NewTrayMenu(const char* menuJSON) {
+ TrayMenu* result = malloc(sizeof(TrayMenu));
+
+/*
+ {"ID":"0","Label":"Test Tray Label","Icon":"","ProcessedMenu":{"Menu":{"Items":[{"ID":"0","Label":"Show Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"1","Label":"Hide Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"2","Label":"Minimise Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0},{"ID":"3","Label":"Unminimise Window","Type":"Text","Disabled":false,"Hidden":false,"Checked":false,"Foreground":0,"Background":0}]},"RadioGroups":null}}
+*/
+ JsonNode* processedJSON = json_decode(menuJSON);
+ if( processedJSON == NULL ) {
+ ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
+ }
+
+ // Save reference to this json
+ result->processedJSON = processedJSON;
+
+ // TODO: Make this configurable
+ result->trayIconPosition = NSImageLeft;
+
+ result->ID = mustJSONString(processedJSON, "ID");
+ result->label = mustJSONString(processedJSON, "Label");
+ result->icon = mustJSONString(processedJSON, "Icon");
+ JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
+
+ // Create the menu
+ result->menu = NewMenu(processedMenu);
+
+ // Init tray status bar item
+ result->statusbaritem = NULL;
+
+ // Set the menu type and store the tray ID in the parent data
+ result->menu->menuType = TrayMenuType;
+ result->menu->parentData = (void*) result->ID;
+
+ return result;
+}
+
+void DumpTrayMenu(TrayMenu* trayMenu) {
+ printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem );
+}
+
+void ShowTrayMenu(TrayMenu* trayMenu) {
+
+ // Create a status bar item if we don't have one
+ if( trayMenu->statusbaritem == NULL ) {
+ id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
+ trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
+ msg(trayMenu->statusbaritem, s("retain"));
+
+ }
+
+ id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
+ msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
+
+ // Update the icon if needed
+ UpdateTrayMenuIcon(trayMenu);
+
+ // Update the label if needed
+ UpdateTrayMenuLabel(trayMenu);
+
+ // Update the menu
+ id menu = GetMenu(trayMenu->menu);
+ msg(trayMenu->statusbaritem, s("setMenu:"), menu);
+}
+
+void UpdateTrayMenuLabel(TrayMenu *trayMenu) {
+
+ // Exit early if NULL
+ if( trayMenu->label == NULL ) {
+ return;
+ }
+ // We don't check for a
+ id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
+ msg(statusBarButton, s("setTitle:"), str(trayMenu->label));
+}
+
+void UpdateTrayMenuIcon(TrayMenu *trayMenu) {
+
+ // Exit early if NULL
+ if( trayMenu->icon == NULL ) {
+ return;
+ }
+
+ id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
+
+ // Empty icon means remove it
+ if( STREMPTY(trayMenu->icon) ) {
+ // Remove image
+ msg(statusBarButton, s("setImage:"), NULL);
+ return;
+ }
+
+ id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
+ msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
+ msg(statusBarButton, s("setImage:"), trayImage);
+}
+
+// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
+// updated with the data from the new menu.
+void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
+
+ // Delete the old menu
+ DeleteMenu(currentMenu->menu);
+
+ // Set the new one
+ currentMenu->menu = newMenu->menu;
+
+ // Delete the old JSON
+ json_delete(currentMenu->processedJSON);
+
+ // Set the new JSON
+ currentMenu->processedJSON = newMenu->processedJSON;
+
+ // Copy the other data
+ currentMenu->ID = newMenu->ID;
+ currentMenu->label = newMenu->label;
+ currentMenu->trayIconPosition = newMenu->trayIconPosition;
+ currentMenu->icon = newMenu->icon;
+
+}
+
+void DeleteTrayMenu(TrayMenu* trayMenu) {
+
+// printf("Freeing TrayMenu:\n");
+// DumpTrayMenu(trayMenu);
+
+ // Delete the menu
+ DeleteMenu(trayMenu->menu);
+
+ // Free JSON
+ if (trayMenu->processedJSON != NULL ) {
+ json_delete(trayMenu->processedJSON);
+ }
+
+ // Free the status item
+ if ( trayMenu->statusbaritem != NULL ) {
+ id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
+ msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
+ msg(trayMenu->statusbaritem, s("release"));
+ trayMenu->statusbaritem = NULL;
+ }
+
+ // Free the tray menu memory
+ MEMFREE(trayMenu);
+}
+
+void LoadTrayIcons() {
+
+ // Allocate the Tray Icons
+ if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) {
+ // Couldn't allocate map
+ ABORT("Not enough memory to allocate trayIconCache!");
+ }
+
+ unsigned int count = 0;
+ while( 1 ) {
+ const unsigned char *name = trayIcons[count++];
+ if( name == 0x00 ) {
+ break;
+ }
+ const unsigned char *lengthAsString = trayIcons[count++];
+ if( name == 0x00 ) {
+ break;
+ }
+ const unsigned char *data = trayIcons[count++];
+ if( data == 0x00 ) {
+ break;
+ }
+ int length = atoi((const char *)lengthAsString);
+
+ // Create the icon and add to the hashmap
+ id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
+ id trayImage = ALLOC("NSImage");
+ msg(trayImage, s("initWithData:"), imageData);
+ hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
+ }
+}
+
+void UnloadTrayIcons() {
+ // Release the tray cache images
+ if( hashmap_num_entries(&trayIconCache) > 0 ) {
+ if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) {
+ ABORT("failed to release hashmap entries!");
+ }
+ }
+
+ //Free radio groups hashmap
+ hashmap_destroy(&trayIconCache);
+}
\ No newline at end of file
diff --git a/v2/internal/ffenestri/traymenu_darwin.h b/v2/internal/ffenestri/traymenu_darwin.h
new file mode 100644
index 000000000..8464a1e04
--- /dev/null
+++ b/v2/internal/ffenestri/traymenu_darwin.h
@@ -0,0 +1,38 @@
+//
+// Created by Lea Anthony on 12/1/21.
+//
+
+#ifndef TRAYMENU_DARWIN_H
+#define TRAYMENU_DARWIN_H
+
+#include "common.h"
+#include "menu_darwin.h"
+
+typedef struct {
+
+ const char *label;
+ const char *icon;
+ const char *ID;
+
+ Menu* menu;
+
+ id statusbaritem;
+ int trayIconPosition;
+
+ JsonNode* processedJSON;
+
+} TrayMenu;
+
+TrayMenu* NewTrayMenu(const char *trayJSON);
+void DumpTrayMenu(TrayMenu* trayMenu);
+void ShowTrayMenu(TrayMenu* trayMenu);
+void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
+void UpdateTrayMenuIcon(TrayMenu *trayMenu);
+void UpdateTrayMenuLabel(TrayMenu *trayMenu);
+
+void LoadTrayIcons();
+void UnloadTrayIcons();
+
+void DeleteTrayMenu(TrayMenu* trayMenu);
+
+#endif //TRAYMENU_DARWIN_H
diff --git a/v2/internal/ffenestri/traymenustore_darwin.c b/v2/internal/ffenestri/traymenustore_darwin.c
new file mode 100644
index 000000000..ae916a303
--- /dev/null
+++ b/v2/internal/ffenestri/traymenustore_darwin.c
@@ -0,0 +1,107 @@
+//
+// Created by Lea Anthony on 12/1/21.
+//
+
+#include "common.h"
+#include "traymenustore_darwin.h"
+#include "traymenu_darwin.h"
+#include
+
+TrayMenuStore* NewTrayMenuStore() {
+
+ TrayMenuStore* result = malloc(sizeof(TrayMenuStore));
+
+ // Allocate Tray Menu Store
+ if( 0 != hashmap_create((const unsigned)4, &result->trayMenuMap)) {
+ ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
+ }
+
+ return result;
+}
+
+int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
+ DumpTrayMenu(e->data);
+ return 0;
+}
+
+void DumpTrayMenuStore(TrayMenuStore* store) {
+ hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
+}
+
+void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
+ TrayMenu* newMenu = NewTrayMenu(menuJSON);
+
+ //TODO: check if there is already an entry for this menu
+ hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
+
+}
+
+int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
+ ShowTrayMenu(e->data);
+ // 0 to retain element, -1 to delete.
+ return 0;
+}
+
+void ShowTrayMenusInStore(TrayMenuStore* store) {
+ if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
+ hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
+ }
+}
+
+
+int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
+ DeleteTrayMenu(e->data);
+ return -1;
+}
+
+void DeleteTrayMenuStore(TrayMenuStore *store) {
+
+ // Delete context menus
+ if (hashmap_num_entries(&store->trayMenuMap) > 0) {
+ if (0 != hashmap_iterate_pairs(&store->trayMenuMap, freeTrayMenu, NULL)) {
+ ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
+ }
+ }
+
+ // Destroy tray menu map
+ hashmap_destroy(&store->trayMenuMap);
+}
+
+TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
+ // Get the current menu
+ return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
+}
+
+void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
+ TrayMenu* newMenu = NewTrayMenu(menuJSON);
+
+ // Get the current menu
+ TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
+ if ( currentMenu == NULL ) {
+ ABORT("Attempted to update unknown tray menu with ID '%s'.", newMenu->ID);
+ }
+
+ // Save the status bar reference
+ newMenu->statusbaritem = currentMenu->statusbaritem;
+
+ hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
+
+ // Delete the current menu
+ DeleteMenu(currentMenu->menu);
+ currentMenu->menu = NULL;
+
+ // Free JSON
+ if (currentMenu->processedJSON != NULL ) {
+ json_delete(currentMenu->processedJSON);
+ currentMenu->processedJSON = NULL;
+ }
+
+ // Free the tray menu memory
+ MEMFREE(currentMenu);
+
+ hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
+
+ // Show the updated menu
+ ShowTrayMenu(newMenu);
+
+}
diff --git a/v2/internal/ffenestri/traymenustore_darwin.h b/v2/internal/ffenestri/traymenustore_darwin.h
new file mode 100644
index 000000000..704cb60a3
--- /dev/null
+++ b/v2/internal/ffenestri/traymenustore_darwin.h
@@ -0,0 +1,25 @@
+//
+// Created by Lea Anthony on 7/1/21.
+//
+
+#ifndef TRAYMENUSTORE_DARWIN_H
+#define TRAYMENUSTORE_DARWIN_H
+
+typedef struct {
+
+ int dummy;
+
+ // This is our tray menu map
+ // It maps tray IDs to TrayMenu*
+ struct hashmap_s trayMenuMap;
+
+} TrayMenuStore;
+
+TrayMenuStore* NewTrayMenuStore();
+
+void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON);
+void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
+void ShowTrayMenusInStore(TrayMenuStore* store);
+void DeleteTrayMenuStore(TrayMenuStore* store);
+
+#endif //TRAYMENUSTORE_DARWIN_H
diff --git a/v2/internal/ffenestri/vec.c b/v2/internal/ffenestri/vec.c
new file mode 100644
index 000000000..957d91d1b
--- /dev/null
+++ b/v2/internal/ffenestri/vec.c
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2014 rxi
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include "vec.h"
+
+
+int vec_expand_(char **data, int *length, int *capacity, int memsz) {
+ if (*length + 1 > *capacity) {
+ void *ptr;
+ int n = (*capacity == 0) ? 1 : *capacity << 1;
+ ptr = realloc(*data, n * memsz);
+ if (ptr == NULL) return -1;
+ *data = ptr;
+ *capacity = n;
+ }
+ return 0;
+}
+
+
+int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n) {
+ (void) length;
+ if (n > *capacity) {
+ void *ptr = realloc(*data, n * memsz);
+ if (ptr == NULL) return -1;
+ *data = ptr;
+ *capacity = n;
+ }
+ return 0;
+}
+
+
+int vec_reserve_po2_(
+ char **data, int *length, int *capacity, int memsz, int n
+) {
+ int n2 = 1;
+ if (n == 0) return 0;
+ while (n2 < n) n2 <<= 1;
+ return vec_reserve_(data, length, capacity, memsz, n2);
+}
+
+
+int vec_compact_(char **data, int *length, int *capacity, int memsz) {
+ if (*length == 0) {
+ free(*data);
+ *data = NULL;
+ *capacity = 0;
+ return 0;
+ } else {
+ void *ptr;
+ int n = *length;
+ ptr = realloc(*data, n * memsz);
+ if (ptr == NULL) return -1;
+ *capacity = n;
+ *data = ptr;
+ }
+ return 0;
+}
+
+
+int vec_insert_(char **data, int *length, int *capacity, int memsz,
+ int idx
+) {
+ int err = vec_expand_(data, length, capacity, memsz);
+ if (err) return err;
+ memmove(*data + (idx + 1) * memsz,
+ *data + idx * memsz,
+ (*length - idx) * memsz);
+ return 0;
+}
+
+
+void vec_splice_(char **data, int *length, int *capacity, int memsz,
+ int start, int count
+) {
+ (void) capacity;
+ memmove(*data + start * memsz,
+ *data + (start + count) * memsz,
+ (*length - start - count) * memsz);
+}
+
+
+void vec_swapsplice_(char **data, int *length, int *capacity, int memsz,
+ int start, int count
+) {
+ (void) capacity;
+ memmove(*data + start * memsz,
+ *data + (*length - count) * memsz,
+ count * memsz);
+}
+
+
+void vec_swap_(char **data, int *length, int *capacity, int memsz,
+ int idx1, int idx2
+) {
+ unsigned char *a, *b, tmp;
+ int count;
+ (void) length;
+ (void) capacity;
+ if (idx1 == idx2) return;
+ a = (unsigned char*) *data + idx1 * memsz;
+ b = (unsigned char*) *data + idx2 * memsz;
+ count = memsz;
+ while (count--) {
+ tmp = *a;
+ *a = *b;
+ *b = tmp;
+ a++, b++;
+ }
+}
diff --git a/v2/internal/ffenestri/vec.h b/v2/internal/ffenestri/vec.h
new file mode 100644
index 000000000..2871a09e2
--- /dev/null
+++ b/v2/internal/ffenestri/vec.h
@@ -0,0 +1,181 @@
+/**
+ * Copyright (c) 2014 rxi
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef VEC_H
+#define VEC_H
+
+#include
+#include
+
+#define VEC_VERSION "0.2.1"
+
+
+#define vec_unpack_(v)\
+ (char**)&(v)->data, &(v)->length, &(v)->capacity, sizeof(*(v)->data)
+
+
+#define vec_t(T)\
+ struct { T *data; int length, capacity; }
+
+
+#define vec_init(v)\
+ memset((v), 0, sizeof(*(v)))
+
+
+#define vec_deinit(v)\
+ ( free((v)->data),\
+ vec_init(v) )
+
+
+#define vec_push(v, val)\
+ ( vec_expand_(vec_unpack_(v)) ? -1 :\
+ ((v)->data[(v)->length++] = (val), 0), 0 )
+
+
+#define vec_pop(v)\
+ (v)->data[--(v)->length]
+
+
+#define vec_splice(v, start, count)\
+ ( vec_splice_(vec_unpack_(v), start, count),\
+ (v)->length -= (count) )
+
+
+#define vec_swapsplice(v, start, count)\
+ ( vec_swapsplice_(vec_unpack_(v), start, count),\
+ (v)->length -= (count) )
+
+
+#define vec_insert(v, idx, val)\
+ ( vec_insert_(vec_unpack_(v), idx) ? -1 :\
+ ((v)->data[idx] = (val), 0), (v)->length++, 0 )
+
+
+#define vec_sort(v, fn)\
+ qsort((v)->data, (v)->length, sizeof(*(v)->data), fn)
+
+
+#define vec_swap(v, idx1, idx2)\
+ vec_swap_(vec_unpack_(v), idx1, idx2)
+
+
+#define vec_truncate(v, len)\
+ ((v)->length = (len) < (v)->length ? (len) : (v)->length)
+
+
+#define vec_clear(v)\
+ ((v)->length = 0)
+
+
+#define vec_first(v)\
+ (v)->data[0]
+
+
+#define vec_last(v)\
+ (v)->data[(v)->length - 1]
+
+
+#define vec_reserve(v, n)\
+ vec_reserve_(vec_unpack_(v), n)
+
+
+#define vec_compact(v)\
+ vec_compact_(vec_unpack_(v))
+
+
+#define vec_pusharr(v, arr, count)\
+ do {\
+ int i__, n__ = (count);\
+ if (vec_reserve_po2_(vec_unpack_(v), (v)->length + n__) != 0) break;\
+ for (i__ = 0; i__ < n__; i__++) {\
+ (v)->data[(v)->length++] = (arr)[i__];\
+ }\
+ } while (0)
+
+
+#define vec_extend(v, v2)\
+ vec_pusharr((v), (v2)->data, (v2)->length)
+
+
+#define vec_find(v, val, idx)\
+ do {\
+ for ((idx) = 0; (idx) < (v)->length; (idx)++) {\
+ if ((v)->data[(idx)] == (val)) break;\
+ }\
+ if ((idx) == (v)->length) (idx) = -1;\
+ } while (0)
+
+
+#define vec_remove(v, val)\
+ do {\
+ int idx__;\
+ vec_find(v, val, idx__);\
+ if (idx__ != -1) vec_splice(v, idx__, 1);\
+ } while (0)
+
+
+#define vec_reverse(v)\
+ do {\
+ int i__ = (v)->length / 2;\
+ while (i__--) {\
+ vec_swap((v), i__, (v)->length - (i__ + 1));\
+ }\
+ } while (0)
+
+
+#define vec_foreach(v, var, iter)\
+ if ( (v)->length > 0 )\
+ for ( (iter) = 0;\
+ (iter) < (v)->length && (((var) = (v)->data[(iter)]), 1);\
+ ++(iter))
+
+
+#define vec_foreach_rev(v, var, iter)\
+ if ( (v)->length > 0 )\
+ for ( (iter) = (v)->length - 1;\
+ (iter) >= 0 && (((var) = (v)->data[(iter)]), 1);\
+ --(iter))
+
+
+#define vec_foreach_ptr(v, var, iter)\
+ if ( (v)->length > 0 )\
+ for ( (iter) = 0;\
+ (iter) < (v)->length && (((var) = &(v)->data[(iter)]), 1);\
+ ++(iter))
+
+
+#define vec_foreach_ptr_rev(v, var, iter)\
+ if ( (v)->length > 0 )\
+ for ( (iter) = (v)->length - 1;\
+ (iter) >= 0 && (((var) = &(v)->data[(iter)]), 1);\
+ --(iter))
+
+
+
+int vec_expand_(char **data, int *length, int *capacity, int memsz);
+int vec_reserve_(char **data, int *length, int *capacity, int memsz, int n);
+int vec_reserve_po2_(char **data, int *length, int *capacity, int memsz,
+ int n);
+int vec_compact_(char **data, int *length, int *capacity, int memsz);
+int vec_insert_(char **data, int *length, int *capacity, int memsz,
+ int idx);
+void vec_splice_(char **data, int *length, int *capacity, int memsz,
+ int start, int count);
+void vec_swapsplice_(char **data, int *length, int *capacity, int memsz,
+ int start, int count);
+void vec_swap_(char **data, int *length, int *capacity, int memsz,
+ int idx1, int idx2);
+
+
+typedef vec_t(void*) vec_void_t;
+typedef vec_t(char*) vec_str_t;
+typedef vec_t(int) vec_int_t;
+typedef vec_t(char) vec_char_t;
+typedef vec_t(float) vec_float_t;
+typedef vec_t(double) vec_double_t;
+
+#endif
\ No newline at end of file
diff --git a/v2/internal/frontend/calls.go b/v2/internal/frontend/calls.go
deleted file mode 100644
index 5401106bc..000000000
--- a/v2/internal/frontend/calls.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package frontend
-
-type Calls interface {
- Callback(message string)
-}
diff --git a/v2/internal/frontend/desktop/darwin/AppDelegate.h b/v2/internal/frontend/desktop/darwin/AppDelegate.h
deleted file mode 100644
index a8d10f647..000000000
--- a/v2/internal/frontend/desktop/darwin/AppDelegate.h
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// AppDelegate.h
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#ifndef AppDelegate_h
-#define AppDelegate_h
-
-#import
-#import "WailsContext.h"
-
-@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
deleted file mode 100644
index a73ec3ec3..000000000
--- a/v2/internal/frontend/desktop/darwin/AppDelegate.m
+++ /dev/null
@@ -1,100 +0,0 @@
-//
-// AppDelegate.m
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#import
-#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;
-}
-
-- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
- processMessage("Q");
- return NSTerminateCancel;
-}
-
-- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
- [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
- if (self.alwaysOnTop) {
- [self.mainWindow setLevel:NSFloatingWindowLevel];
- }
- if ( !self.startHidden ) {
- [self.mainWindow makeKeyAndOrderFront:self];
- }
-}
-
-- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
- [NSApp activateIgnoringOtherApps:YES];
- if ( self.startFullscreen ) {
- NSWindowCollectionBehavior behaviour = [self.mainWindow collectionBehavior];
- behaviour |= NSWindowCollectionBehaviorFullScreenPrimary;
- [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 {
- [super dealloc];
-}
-
-@synthesize touchBar;
-
-@end
diff --git a/v2/internal/frontend/desktop/darwin/Application.h b/v2/internal/frontend/desktop/darwin/Application.h
deleted file mode 100644
index 4d8bbd37b..000000000
--- a/v2/internal/frontend/desktop/darwin/Application.h
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-// Application.h
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#ifndef Application_h
-#define Application_h
-
-#import
-#import
-#import "WailsContext.h"
-
-#define WindowStartsNormal 0
-#define WindowStartsMaximised 1
-#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);
-void Run(void*, const char* url);
-
-void SetTitle(void* ctx, const char *title);
-void Center(void* ctx);
-void SetSize(void* ctx, int width, int height);
-void SetAlwaysOnTop(void* ctx, int onTop);
-void SetMinSize(void* ctx, int width, int height);
-void SetMaxSize(void* ctx, int width, int height);
-void SetPosition(void* ctx, int x, int y);
-void Fullscreen(void* ctx);
-void UnFullscreen(void* ctx);
-void Minimise(void* ctx);
-void UnMinimise(void* ctx);
-void ToggleMaximise(void* ctx);
-void Maximise(void* ctx);
-void UnMaximise(void* ctx);
-void Hide(void* ctx);
-void Show(void* ctx);
-void HideApplication(void* ctx);
-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);
-const bool IsFullScreen(void *ctx);
-const bool IsMinimised(void *ctx);
-const bool IsMaximised(void *ctx);
-
-/* Dialogs */
-
-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);
-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);
-void SaveFileDialog(void *inctx, const char* title, const char* defaultFilename, const char* defaultDirectory, int canCreateDirectories, int treatPackagesAsDirectories, int showHiddenFiles, const char* filters);
-
-/* Application Menu */
-void* NewMenu(const char* name);
-void AppendSubmenu(void* parent, void* child);
-void AppendRole(void *inctx, void *inMenu, int role);
-void SetAsApplicationMenu(void *inctx, void *inMenu);
-void UpdateApplicationMenu(void *inctx);
-
-void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen);
-void* AppendMenuItem(void* inctx, void* nsmenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID);
-void AppendSeparator(void* inMenu);
-void UpdateMenuItem(void* nsmenuitem, int checked);
-void RunMainLoop(void);
-void ReleaseContext(void *inctx);
-
-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
deleted file mode 100644
index 38d349c2c..000000000
--- a/v2/internal/frontend/desktop/darwin/Application.m
+++ /dev/null
@@ -1,433 +0,0 @@
-//go:build darwin
-//
-// Application.m
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#import
-#import
-#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) {
-
- [NSApplication sharedApplication];
-
- WailsContext *result = [WailsContext new];
-
- result.devtoolsEnabled = devtoolsEnabled;
- result.defaultContextMenuEnabled = defaultContextMenuEnabled;
-
- 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 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];
- break;
- case WindowStartsMinimised:
- //TODO: Can you start a mac app minimised?
- break;
- }
-
- 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;
-}
-
-void ExecJS(void* inctx, const char *script) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSString *nsscript = safeInit(script);
- ON_MAIN_THREAD(
- [ctx ExecJS:nsscript];
- [nsscript release];
- );
-}
-
-void SetTitle(void* inctx, const char *title) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSString *_title = safeInit(title);
- ON_MAIN_THREAD(
- [ctx SetTitle:_title];
- );
-}
-
-
-void SetBackgroundColour(void *inctx, int r, int g, int b, int a) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx SetBackgroundColour:r :g :b :a];
- );
-}
-
-void SetSize(void* inctx, int width, int height) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx SetSize:width :height];
- );
-}
-
-void SetAlwaysOnTop(void* inctx, int onTop) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx SetAlwaysOnTop:onTop];
- );
-}
-
-void SetMinSize(void* inctx, int width, int height) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx SetMinSize:width :height];
- );
-}
-
-void SetMaxSize(void* inctx, int width, int height) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx SetMaxSize:width :height];
- );
-}
-
-void SetPosition(void* inctx, int x, int y) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx SetPosition:x :y];
- );
-}
-
-void Center(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx Center];
- );
-}
-
-void Fullscreen(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx Fullscreen];
- );
-}
-
-void UnFullscreen(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx UnFullscreen];
- );
-}
-
-void Minimise(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx Minimise];
- );
-}
-
-void UnMinimise(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx UnMinimise];
- );
-}
-
-void Maximise(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx Maximise];
- );
-}
-
-void ToggleMaximise(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx ToggleMaximise];
- );
-}
-
-const char* GetSize(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSRect frame = [ctx.mainWindow frame];
- NSString *result = [NSString stringWithFormat:@"%d,%d", (int)frame.size.width, (int)frame.size.height];
- return [result UTF8String];
-}
-
-const char* GetPosition(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSScreen* screen = [ctx getCurrentScreen];
- NSRect windowFrame = [ctx.mainWindow frame];
- NSRect screenFrame = [screen visibleFrame];
- int x = windowFrame.origin.x - screenFrame.origin.x;
- int y = windowFrame.origin.y - screenFrame.origin.y;
- y = screenFrame.size.height - y - windowFrame.size.height;
- NSString *result = [NSString stringWithFormat:@"%d,%d",x,y];
- return [result UTF8String];
-}
-
-const bool IsFullScreen(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- return [ctx IsFullScreen];
-}
-
-const bool IsMinimised(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- return [ctx IsMinimised];
-}
-
-const bool IsMaximised(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- return [ctx IsMaximised];
-}
-
-void UnMaximise(void* inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx UnMaximise];
- );
-}
-
-void Quit(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- [NSApp stop:ctx];
- [NSApp abortModal];
-}
-
-void Hide(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx Hide];
- );
-}
-
-void Show(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx Show];
- );
-}
-
-
-void HideApplication(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx HideApplication];
- );
-}
-
-void ShowApplication(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- [ctx ShowApplication];
- );
-}
-
-NSString* safeInit(const char* input) {
- NSString *result = nil;
- if (input != nil) {
- result = [NSString stringWithUTF8String:input];
- }
- return result;
-}
-
-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);
- NSString *_button1 = safeInit(button1);
- NSString *_button2 = safeInit(button2);
- NSString *_button3 = safeInit(button3);
- 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];
- )
-}
-
-void AppendRole(void *inctx, void *inMenu, int role) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- WailsMenu *menu = (__bridge WailsMenu*) inMenu;
- [menu appendRole :ctx :role];
-}
-
-void* NewMenu(const char *name) {
- NSString *title = @"";
- if (name != nil) {
- title = [NSString stringWithUTF8String:name];
- }
- WailsMenu *result = [[WailsMenu new] initWithNSTitle:title];
- return result;
-}
-
-void AppendSubmenu(void* inparent, void* inchild) {
- WailsMenu *parent = (__bridge WailsMenu*) inparent;
- WailsMenu *child = (__bridge WailsMenu*) inchild;
- [parent appendSubmenu:child];
-}
-
-void SetAsApplicationMenu(void *inctx, void *inMenu) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- WailsMenu *menu = (__bridge WailsMenu*) inMenu;
- ctx.applicationMenu = menu;
-}
-
-void UpdateApplicationMenu(void *inctx) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- ON_MAIN_THREAD(
- NSApplication *app = [NSApplication sharedApplication];
- [app setMainMenu:ctx.applicationMenu];
- )
-}
-
-void SetAbout(void *inctx, const char* title, const char* description, void* imagedata, int datalen) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSString *_title = safeInit(title);
- NSString *_description = safeInit(description);
-
- [ctx SetAbout :_title :_description :imagedata :datalen];
-}
-
-void* AppendMenuItem(void* inctx, void* inMenu, const char* label, const char* shortcutKey, int modifiers, int disabled, int checked, int menuItemID) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- WailsMenu *menu = (__bridge WailsMenu*) inMenu;
- NSString *_label = safeInit(label);
- NSString *_shortcutKey = safeInit(shortcutKey);
-
- return [menu AppendMenuItem:ctx :_label :_shortcutKey :modifiers :disabled :checked :menuItemID];
-}
-
-void UpdateMenuItem(void* nsmenuitem, int checked) {
- ON_MAIN_THREAD(
- WailsMenuItem *menuItem = (__bridge WailsMenuItem*) nsmenuitem;
- [menuItem setState:(checked == 1?NSControlStateValueOn:NSControlStateValueOff)];
- )
-}
-
-
-void AppendSeparator(void* inMenu) {
- WailsMenu *menu = (__bridge WailsMenu*) inMenu;
- [menu AppendSeparator];
-}
-
-
-
-void Run(void *inctx, const char* url) {
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSApplication *app = [NSApplication sharedApplication];
- AppDelegate* delegate = [AppDelegate new];
- [app setDelegate:(id)delegate];
- ctx.appdelegate = delegate;
- 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);
- [ctx loadRequest:_url];
- [_url release];
-
- [app setMainMenu:ctx.applicationMenu];
-}
-
-void RunMainLoop(void) {
- NSApplication *app = [NSApplication sharedApplication];
- [app run];
-}
-
-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/Role.h b/v2/internal/frontend/desktop/darwin/Role.h
deleted file mode 100644
index 6b8877a09..000000000
--- a/v2/internal/frontend/desktop/darwin/Role.h
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// Role.h
-// test
-//
-// Created by Lea Anthony on 24/10/21.
-//
-
-#ifndef Role_h
-#define Role_h
-
-typedef int Role;
-
-static const Role AppMenu = 1;
-static const Role EditMenu = 2;
-static const Role WindowMenu = 3;
-
-#endif /* Role_h */
diff --git a/v2/internal/frontend/desktop/darwin/WailsAlert.h b/v2/internal/frontend/desktop/darwin/WailsAlert.h
deleted file mode 100644
index 29dc839f6..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsAlert.h
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// WailsAlert.h
-// test
-//
-// Created by Lea Anthony on 20/10/21.
-//
-
-#ifndef WailsAlert_h
-#define WailsAlert_h
-
-#import
-
-@interface WailsAlert : NSAlert
-- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton;
-@end
-
-
-#endif /* WailsAlert_h */
diff --git a/v2/internal/frontend/desktop/darwin/WailsAlert.m b/v2/internal/frontend/desktop/darwin/WailsAlert.m
deleted file mode 100644
index 3c8b7305a..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsAlert.m
+++ /dev/null
@@ -1,31 +0,0 @@
-//go:build darwin
-//
-// WailsAlert.m
-// test
-//
-// Created by Lea Anthony on 20/10/21.
-//
-
-#import
-
-#import "WailsAlert.h"
-
-@implementation WailsAlert
-
-- (void)addButton:(NSString*)text :(NSString*)defaultButton :(NSString*)cancelButton {
- if( text == nil ) {
- return;
- }
- NSButton *button = [self addButtonWithTitle:text];
- if( defaultButton != nil && [text isEqualToString:defaultButton]) {
- [button setKeyEquivalent:@"\r"];
- } else if( cancelButton != nil && [text isEqualToString:cancelButton]) {
- [button setKeyEquivalent:@"\033"];
- } else {
- [button setKeyEquivalent:@""];
- }
-}
-
-@end
-
-
diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.h b/v2/internal/frontend/desktop/darwin/WailsContext.h
deleted file mode 100644
index 2ec6d8707..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsContext.h
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-// WailsContext.h
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#ifndef WailsContext_h
-#define WailsContext_h
-
-#import
-#import
-#import "WailsWebView.h"
-
-#if __has_include()
-#define USE_NEW_FILTERS
-#import
-#endif
-
-#define ON_MAIN_THREAD(str) dispatch_async(dispatch_get_main_queue(), ^{ str; });
-#define unicode(input) [NSString stringWithFormat:@"%C", input]
-
-@interface WailsWindow : NSWindow
-
-@property NSSize userMinSize;
-@property NSSize userMaxSize;
-
-- (BOOL) canBecomeKeyWindow;
-- (void) applyWindowConstraints;
-- (void) disableWindowConstraints;
-@end
-
-@interface WailsContext : NSObject
-
-@property (retain) WailsWindow* mainWindow;
-@property (retain) WailsWebView* webview;
-@property (nonatomic, assign) id appdelegate;
-
-@property bool hideOnClose;
-@property bool shuttingDown;
-@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 (retain) WKUserContentController* userContentController;
-
-@property (retain) NSMenu* applicationMenu;
-
-@property (retain) NSImage* aboutImage;
-@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) SetSize:(int)width :(int)height;
-- (void) SetPosition:(int)x :(int) y;
-- (void) SetMinSize:(int)minWidth :(int)minHeight;
-- (void) SetMaxSize:(int)maxWidth :(int)maxHeight;
-- (void) SetTitle:(NSString*)title;
-- (void) SetAlwaysOnTop:(int)onTop;
-- (void) Center;
-- (void) Fullscreen;
-- (void) UnFullscreen;
-- (bool) IsFullScreen;
-- (void) Minimise;
-- (void) UnMinimise;
-- (bool) IsMinimised;
-- (void) Maximise;
-- (void) ToggleMaximise;
-- (void) UnMaximise;
-- (bool) IsMaximised;
-- (void) SetBackgroundColour:(int)r :(int)g :(int)b :(int)a;
-- (void) HideMouse;
-- (void) ShowMouse;
-- (void) Hide;
-- (void) Show;
-- (void) HideApplication;
-- (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) 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;
-
-- (void) loadRequest:(NSString*)url;
-- (void) ExecJS:(NSString*)script;
-- (NSScreen*) getCurrentScreen;
-
-- (void) SetAbout :(NSString*)title :(NSString*)description :(void*)imagedata :(int)datalen;
-- (void) dealloc;
-
-@end
-
-
-#endif /* WailsContext_h */
diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.m b/v2/internal/frontend/desktop/darwin/WailsContext.m
deleted file mode 100644
index 7c9660d54..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsContext.m
+++ /dev/null
@@ -1,755 +0,0 @@
-//
-// WailsContext.m
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#import
-#import
-#import "WailsContext.h"
-#import "WailsAlert.h"
-#import "WailsMenu.h"
-#import "WailsWebView.h"
-#import "WindowDelegate.h"
-#import "message.h"
-#import "Role.h"
-
-typedef void (^schemeTaskCaller)(id);
-
-@implementation WailsWindow
-
-- (BOOL)canBecomeKeyWindow
-{
- return YES;
-}
-
-- (void) applyWindowConstraints {
- [self setMinSize:self.userMinSize];
- [self setMaxSize:self.userMaxSize];
-}
-
-- (void) disableWindowConstraints {
- [self setMinSize:NSMakeSize(0, 0)];
- [self setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
-}
-
-@end
-
-@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;
- frame.size.height = height;
- [self.mainWindow setFrame:frame display:TRUE animate:FALSE];
-}
-
-- (void) SetPosition:(int)x :(int)y {
-
- if (self.shuttingDown) return;
-
- NSScreen* screen = [self getCurrentScreen];
- NSRect windowFrame = [self.mainWindow frame];
- NSRect screenFrame = [screen visibleFrame];
- 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];
- [self adjustWindowSize];
-}
-
-
-- (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];
-}
-
-
-- (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 {
- [self.appdelegate release];
- [self.mainWindow release];
- [self.mouseEvent release];
- [self.userContentController release];
- [self.applicationMenu release];
- [super dealloc];
-}
-
-- (NSScreen*) getCurrentScreen {
- NSScreen* screen = [self.mainWindow screen];
- if( screen == NULL ) {
- screen = [NSScreen mainScreen];
- }
- return screen;
-}
-
-- (void) SetTitle:(NSString*)title {
- [self.mainWindow setTitle:title];
-}
-
-- (void) Center {
- [self.mainWindow center];
-}
-
-- (BOOL) isFullscreen {
- NSWindowStyleMask masks = [self.mainWindow styleMask];
- if ( masks & NSWindowStyleMaskFullScreen ) {
- return YES;
- }
- 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 {
- NSWindowStyleMask styleMask = 0;
-
- if( !frameless ) {
- if (!hideTitleBar) {
- styleMask |= NSWindowStyleMaskTitled;
- }
- styleMask |= NSWindowStyleMaskClosable;
- }
-
- styleMask |= NSWindowStyleMaskMiniaturizable;
-
- if( fullSizeContent || frameless || titlebarAppearsTransparent ) {
- styleMask |= NSWindowStyleMaskFullSizeContentView;
- }
-
- 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];
- NSRect bounds = [contentView bounds];
- [effectView initWithFrame:bounds];
- [effectView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
- [effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
- [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 };
- if (maxSize.width == 0) {
- maxSize.width = FLT_MAX;
- }
- if (maxSize.height == 0) {
- maxSize.height = FLT_MAX;
- }
- 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.applicationNameForUserAgent = @"wails.io";
- [config setURLSchemeHandler:self forURLScheme:@"wails"];
-
- 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) {
- [config.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
- }
-
- if (!self.defaultContextMenuEnabled) {
- // Disable default context menus
- WKUserScript *initScript = [WKUserScript new];
- [initScript initWithSource:@"window.wails.flags.disableDefaultContextMenu = true;"
- injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
- forMainFrameOnly:false];
- [userContentController addUserScript:initScript];
- }
-
- self.webview = [WailsWebView alloc];
- self.webview.enableDragAndDrop = enableDragAndDrop;
- self.webview.disableWebViewDragAndDrop = disableWebViewDragAndDrop;
-
- 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];
- if (window == self.mainWindow) {
- self.mouseEvent = event;
- }
- return event;
- }];
-
- [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
- id window = [event window];
- if (window == self.mainWindow) {
- self.mouseEvent = nil;
- [self ShowMouse];
- }
- return event;
- }];
-
- self.applicationMenu = [NSMenu new];
-
-}
-
-- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
- NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
- if( flags != 0 ) {
- [result setKeyEquivalentModifierMask:flags];
- }
- return result;
-}
-
-- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key {
- return [self newMenuItem :title :selector :key :0];
-}
-
-- (NSMenu*) newMenu :(NSString*)title {
- WailsMenu *result = [[WailsMenu new] initWithTitle:title];
- [result setAutoenablesItems:NO];
- return result;
-}
-
-- (void) Quit {
- processMessage("Q");
-}
-
-- (void) loadRequest :(NSString*)url {
- NSURL *wkUrl = [NSURL URLWithString:url];
- NSURLRequest *wkRequest = [NSURLRequest requestWithURL:wkUrl];
- [self.webview loadRequest:wkRequest];
-}
-
-- (void) SetBackgroundColour:(int)r :(int)g :(int)b :(int)a {
- float red = r/255.0;
- 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];
-}
-
-- (void) HideMouse {
- [NSCursor hide];
-}
-
-- (void) ShowMouse {
- [NSCursor unhide];
-}
-
-- (bool) IsFullScreen {
- long mask = [self.mainWindow styleMask];
- return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
-}
-
-// Fullscreen sets the main window to be fullscreen
-- (void) Fullscreen {
- if( ! [self IsFullScreen] ) {
- [self.mainWindow disableWindowConstraints];
- [self.mainWindow toggleFullScreen:nil];
- }
-}
-
-// UnFullscreen resets the main window after a fullscreen
-- (void) UnFullscreen {
- if( [self IsFullScreen] ) {
- [self.mainWindow applyWindowConstraints];
- [self.mainWindow toggleFullScreen:nil];
- }
-}
-
-- (void) Minimise {
- [self.mainWindow miniaturize:nil];
-}
-
-- (void) UnMinimise {
- [self.mainWindow deminiaturize:nil];
-}
-
-- (bool) IsMinimised {
- return [self.mainWindow isMiniaturized];
-}
-
-- (void) Hide {
- [self.mainWindow orderOut:nil];
-}
-
-- (void) Show {
- [self.mainWindow makeKeyAndOrderFront:nil];
- [NSApp activateIgnoringOtherApps:YES];
-}
-
-- (void) HideApplication {
- [[NSApplication sharedApplication] hide:self];
-}
-
-- (void) ShowApplication {
- [[NSApplication sharedApplication] unhide:self];
- [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE];
-
-}
-
-- (void) Maximise {
- if (![self.mainWindow isZoomed]) {
- [self.mainWindow zoom:nil];
- }
-}
-
-- (void) ToggleMaximise {
- [self.mainWindow zoom:nil];
-}
-
-- (void) UnMaximise {
- if ([self.mainWindow isZoomed]) {
- [self.mainWindow zoom:nil];
- }
-}
-
-- (void) SetAlwaysOnTop:(int)onTop {
- if (onTop) {
- [self.mainWindow setLevel:NSFloatingWindowLevel];
- } else {
- [self.mainWindow setLevel:NSNormalWindowLevel];
- }
-}
-
-- (bool) IsMaximised {
- return [self.mainWindow isZoomed];
-}
-
-- (void) ExecJS:(NSString*)script {
- [self.webview evaluateJavaScript:script completionHandler:nil];
-}
-
-- (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
- beginSheetModalForWindow:webView.window
- completionHandler:^(NSInteger result) {
- if (result == NSModalResponseOK)
- completionHandler(openPanel.URLs);
- else
- completionHandler(nil);
- }];
-}
-
-- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask {
- // This callback is run with an autorelease pool
- processURLRequest(self, 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];
- }
- }
-}
-
-- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
- processMessage("DomReady");
-}
-
-- (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] ) {
- return;
- }
- if( self.mouseEvent != nil ) {
- [self.mainWindow performWindowDragWithEvent:self.mouseEvent];
- }
- return;
- }
-
- const char *_m = [m UTF8String];
- const char *_origin = [origin UTF8String];
-
- processBindingMessage(_m, _origin, message.frameInfo.isMainFrame);
-}
-
-/***** 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"] ) {
- style = NSAlertStyleWarning;
- }
- if( [dialogType isEqualToString:@"error"] ) {
- style = NSAlertStyleCritical;
- }
- }
- [alert setAlertStyle:style];
- if( title != nil ) {
- [alert setMessageText:title];
- }
- 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];
- icon = [[NSImage alloc] initWithData:imageData];
- }
- if( icon != nil) {
- [alert setIcon:icon];
- }
- [alert.window setLevel:NSFloatingWindowLevel];
-
- 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;
- }
- processMessageDialogResponse(result);
-}
-
--(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];
-
- // Valid but appears to do nothing.... :/
- if( title != nil ) {
- [dialog setTitle:title];
- }
-
- // Filters - semicolon delimited list of file extensions
- if( allowFiles ) {
- if( filters != nil && [filters length] > 0) {
- filters = [filters stringByReplacingOccurrencesOfString:@"*." withString:@""];
- filters = [filters stringByReplacingOccurrencesOfString:@" " withString:@""];
- NSArray *filterList = [filters componentsSeparatedByString:@";"];
-#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
- } else {
- [dialog setAllowsOtherFileTypes:true];
- }
- // Default Filename
- if( defaultFilename != nil ) {
- [dialog setNameFieldStringValue:defaultFilename];
- }
-
- [dialog setAllowsMultipleSelection: allowMultipleSelection];
- }
- [dialog setShowsHiddenFiles: showHiddenFiles];
-
- // Default Directory
- if( defaultDirectory != nil ) {
- NSURL *url = [NSURL fileURLWithPath:defaultDirectory];
- [dialog setDirectoryURL:url];
- }
-
-
- // Setup Options
- [dialog setCanChooseFiles: allowFiles];
- [dialog setCanChooseDirectories: allowDirectories];
- [dialog setCanCreateDirectories: canCreateDirectories];
- [dialog setResolvesAliases: resolveAliases];
- [dialog setTreatsFilePackagesAsDirectories: treatPackagesAsDirectories];
-
- // Setup callback handler
- [dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
- if ( returnCode != NSModalResponseOK) {
- processOpenFileDialogResponse("[]");
- return;
- }
- NSMutableArray *arr = [NSMutableArray new];
- for (NSURL *url in [dialog URLs]) {
- [arr addObject:[url path]];
- }
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arr options:0 error:nil];
- NSString *nsjson = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
- processOpenFileDialogResponse([nsjson UTF8String]);
- [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];
- }
-
- // Filters - semicolon delimited list of file extensions
- if( filters != nil && [filters length] > 0) {
- filters = [filters stringByReplacingOccurrencesOfString:@"*." withString:@""];
- filters = [filters stringByReplacingOccurrencesOfString:@" " withString:@""];
- NSArray *filterList = [filters componentsSeparatedByString:@";"];
-#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
- [dialog setAllowedFileTypes:filterList];
-#endif
- } else {
- [dialog setAllowsOtherFileTypes:true];
- }
- // Default Filename
- if( defaultFilename != nil ) {
- [dialog setNameFieldStringValue:defaultFilename];
- }
-
- // Default Directory
- if( defaultDirectory != nil ) {
- NSURL *url = [NSURL fileURLWithPath:defaultDirectory];
- [dialog setDirectoryURL:url];
- }
-
- // Setup Options
- [dialog setCanSelectHiddenExtension:true];
-// dialog.isExtensionHidden = false;
- [dialog setCanCreateDirectories: canCreateDirectories];
- [dialog setTreatsFilePackagesAsDirectories: treatPackagesAsDirectories];
- [dialog setShowsHiddenFiles: showHiddenFiles];
-
- // Setup callback handler
- [dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
- if ( returnCode == NSModalResponseOK ) {
- NSURL *url = [dialog URL];
- if ( url != nil ) {
- processSaveFileDialogResponse([url.path UTF8String]);
- return;
- }
- }
- processSaveFileDialogResponse("");
- }];
-
-}
-
-- (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 {
-
- WailsAlert *alert = [WailsAlert new];
- [alert setAlertStyle:NSAlertStyleInformational];
- if( self.aboutTitle != nil ) {
- [alert setMessageText:self.aboutTitle];
- }
- if( self.aboutDescription != nil ) {
- [alert setInformativeText:self.aboutDescription];
- }
-
-
- [alert.window setLevel:NSFloatingWindowLevel];
- if ( self.aboutImage != nil) {
- [alert setIcon:self.aboutImage];
- }
-
- [alert runModal];
-}
-
-@end
-
diff --git a/v2/internal/frontend/desktop/darwin/WailsMenu.h b/v2/internal/frontend/desktop/darwin/WailsMenu.h
deleted file mode 100644
index 8ef120356..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsMenu.h
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// WailsMenu.h
-// test
-//
-// Created by Lea Anthony on 25/10/21.
-//
-
-#ifndef WailsMenu_h
-#define WailsMenu_h
-
-#import
-#import "Role.h"
-#import "WailsMenu.h"
-#import "WailsContext.h"
-
-@interface WailsMenu : NSMenu
-
-//- (void) AddMenuByRole :(Role)role;
-- (WailsMenu*) initWithNSTitle :(NSString*)title;
-- (void) appendSubmenu :(WailsMenu*)child;
-- (void) appendRole :(WailsContext*)ctx :(Role)role;
-
-- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags;
-- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID;
-- (void) AppendSeparator;
-
-@end
-
-
-#endif /* WailsMenu_h */
diff --git a/v2/internal/frontend/desktop/darwin/WailsMenu.m b/v2/internal/frontend/desktop/darwin/WailsMenu.m
deleted file mode 100644
index 66e5dd399..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsMenu.m
+++ /dev/null
@@ -1,340 +0,0 @@
-//go:build darwin
-//
-// WailsMenu.m
-// test
-//
-// Created by Lea Anthony on 25/10/21.
-//
-
-#import
-#import "WailsMenu.h"
-#import "WailsMenuItem.h"
-#import "Role.h"
-
-@implementation WailsMenu
-
-- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
- NSMenuItem *result = [[[NSMenuItem alloc] initWithTitle:title action:selector keyEquivalent:key] autorelease];
- [result setKeyEquivalentModifierMask:flags];
- return result;
-}
-
-- (NSMenuItem*) newMenuItemWithContext :(WailsContext*)ctx :(NSString*)title :(SEL)selector :(NSString*)key :(NSEventModifierFlags)flags {
- NSMenuItem *result = [NSMenuItem new];
- if ( title != nil ) {
- [result setTitle:title];
- }
- if (selector != nil) {
- [result setAction:selector];
- }
- if (key) {
- [result setKeyEquivalent:key];
- }
- if( flags != 0 ) {
- [result setKeyEquivalentModifierMask:flags];
- }
- result.target = ctx;
- return result;
-}
-
-- (NSMenuItem*) newMenuItem :(NSString*)title :(SEL)selector :(NSString*)key {
- return [self newMenuItem :title :selector :key :0];
-}
-
-- (WailsMenu*) initWithNSTitle:(NSString *)title {
- if( title != nil ) {
- [super initWithTitle:title];
- } else {
- [self init];
- }
- [self setAutoenablesItems:NO];
- return self;
-}
-
-- (void) appendSubmenu :(WailsMenu*)child {
- NSMenuItem *childMenuItem = [[NSMenuItem new] autorelease];
- [childMenuItem setTitle:child.title];
- [self addItem:childMenuItem];
- [childMenuItem setSubmenu:child];
-}
-
-- (void) appendRole :(WailsContext*)ctx :(Role)role {
-
- switch(role) {
- case AppMenu:
- {
- NSString *appName = [NSRunningApplication currentApplication].localizedName;
- if( appName == nil ) {
- appName = [[NSProcessInfo processInfo] processName];
- }
- WailsMenu *appMenu = [[[WailsMenu new] initWithNSTitle:appName] autorelease];
-
- if (ctx.aboutTitle != nil) {
- [appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
- [appMenu addItem:[NSMenuItem separatorItem]];
- }
-
- [appMenu addItem:[self newMenuItem:[@"Hide " stringByAppendingString:appName] :@selector(hide:) :@"h" :NSEventModifierFlagCommand]];
- [appMenu addItem:[self newMenuItem:@"Hide Others" :@selector(hideOtherApplications:) :@"h" :(NSEventModifierFlagOption | NSEventModifierFlagCommand)]];
- [appMenu addItem:[self newMenuItem:@"Show All" :@selector(unhideAllApplications:) :@""]];
- [appMenu addItem:[NSMenuItem separatorItem]];
-
- id quitTitle = [@"Quit " stringByAppendingString:appName];
- NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
- quitMenuItem.target = ctx;
- [appMenu addItem:quitMenuItem];
- [self appendSubmenu:appMenu];
- break;
- }
- case EditMenu:
- {
- WailsMenu *editMenu = [[[WailsMenu new] initWithNSTitle:@"Edit"] autorelease];
- [editMenu addItem:[self newMenuItem:@"Undo" :@selector(undo:) :@"z" :NSEventModifierFlagCommand]];
- [editMenu addItem:[self newMenuItem:@"Redo" :@selector(redo:) :@"z" :(NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
- [editMenu addItem:[NSMenuItem separatorItem]];
- [editMenu addItem:[self newMenuItem:@"Cut" :@selector(cut:) :@"x" :NSEventModifierFlagCommand]];
- [editMenu addItem:[self newMenuItem:@"Copy" :@selector(copy:) :@"c" :NSEventModifierFlagCommand]];
- [editMenu addItem:[self newMenuItem:@"Paste" :@selector(paste:) :@"v" :NSEventModifierFlagCommand]];
- [editMenu addItem:[self newMenuItem:@"Paste and Match Style" :@selector(pasteAsRichText:) :@"v" :(NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand)]];
- [editMenu addItem:[self newMenuItem:@"Delete" :@selector(delete:) :[self accel:@"backspace"] :0]];
- [editMenu addItem:[self newMenuItem:@"Select All" :@selector(selectAll:) :@"a" :NSEventModifierFlagCommand]];
- [editMenu addItem:[NSMenuItem separatorItem]];
-// NSMenuItem *speechMenuItem = [[NSMenuItem new] autorelease];
-// [speechMenuItem setTitle:@"Speech"];
-// [editMenu addItem:speechMenuItem];
- WailsMenu *speechMenu = [[[WailsMenu new] initWithNSTitle:@"Speech"] autorelease];
- [speechMenu addItem:[self newMenuItem:@"Start Speaking" :@selector(startSpeaking:) :@""]];
- [speechMenu addItem:[self newMenuItem:@"Stop Speaking" :@selector(stopSpeaking:) :@""]];
- [editMenu appendSubmenu:speechMenu];
- [self appendSubmenu:editMenu];
-
- break;
- }
- case WindowMenu:
- {
- WailsMenu *windowMenu = [[[WailsMenu new] initWithNSTitle:@"Window"] autorelease];
- [windowMenu addItem:[self newMenuItem:@"Minimize" :@selector(performMiniaturize:) :@"m" :NSEventModifierFlagCommand]];
- [windowMenu addItem:[self newMenuItem:@"Zoom" :@selector(performZoom:) :@""]];
- [windowMenu addItem:[NSMenuItem separatorItem]];
- [windowMenu addItem:[self newMenuItem:@"Full Screen" :@selector(enterFullScreenMode:) :@"f" :(NSEventModifierFlagControl | NSEventModifierFlagCommand)]];
- [self appendSubmenu:windowMenu];
-
- break;
- }
- }
-}
-
-- (void*) AppendMenuItem :(WailsContext*)ctx :(NSString*)label :(NSString *)shortcutKey :(int)modifiers :(bool)disabled :(bool)checked :(int)menuItemID {
-
- NSString *nslabel = @"";
- if (label != nil ) {
- nslabel = label;
- }
- WailsMenuItem *menuItem = [WailsMenuItem new];
-
- // Label
- menuItem.title = nslabel;
-
- // Process callback
- menuItem.menuItemID = menuItemID;
- menuItem.action = @selector(handleClick);
- menuItem.target = menuItem;
-
- // Shortcut
- if (shortcutKey != nil) {
- [menuItem setKeyEquivalent:[self accel:shortcutKey]];
- [menuItem setKeyEquivalentModifierMask:modifiers];
- }
-
- // Enabled/Disabled
- [menuItem setEnabled:!disabled];
-
- // Checked
- [menuItem setState:(checked ? NSControlStateValueOn : NSControlStateValueOff)];
-
- [self addItem:menuItem];
- return menuItem;
-}
-
-- (void) AppendSeparator {
- [self addItem:[NSMenuItem separatorItem]];
-}
-
-
-- (NSString*) accel :(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:@"Insert"] ) {
-// return unicode(0xf727);
-// }
-// if( [key isEqualToString:@"PrintScreen"] ) {
-// return unicode(0xf72e);
-// }
-// if( [key isEqualToString:@"ScrollLock"] ) {
-// return unicode(0xf72f);
-// }
- if( [key isEqualToString:@"numLock"] ) {
- return unicode(0xf739);
- }
-
- return key;
-}
-
-
-@end
-
-
diff --git a/v2/internal/frontend/desktop/darwin/WailsMenuItem.h b/v2/internal/frontend/desktop/darwin/WailsMenuItem.h
deleted file mode 100644
index 278bac80f..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsMenuItem.h
+++ /dev/null
@@ -1,22 +0,0 @@
-//
-// WailsMenuItem.h
-// test
-//
-// Created by Lea Anthony on 27/10/21.
-//
-
-#ifndef WailsMenuItem_h
-#define WailsMenuItem_h
-
-#import
-
-@interface WailsMenuItem : NSMenuItem
-
-@property int menuItemID;
-
-- (void) handleClick;
-
-@end
-
-
-#endif /* WailsMenuItem_h */
diff --git a/v2/internal/frontend/desktop/darwin/WailsMenuItem.m b/v2/internal/frontend/desktop/darwin/WailsMenuItem.m
deleted file mode 100644
index a34a67239..000000000
--- a/v2/internal/frontend/desktop/darwin/WailsMenuItem.m
+++ /dev/null
@@ -1,21 +0,0 @@
-//go:build darwin
-//
-// WailsMenuItem.m
-// test
-//
-// Created by Lea Anthony on 27/10/21.
-//
-
-#import
-
-#import "WailsMenuItem.h"
-#include "message.h"
-
-
-@implementation WailsMenuItem
-
-- (void) handleClick {
- processCallback(self.menuItemID);
-}
-
-@end
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/WindowDelegate.h b/v2/internal/frontend/desktop/darwin/WindowDelegate.h
deleted file mode 100644
index 6f83e0e48..000000000
--- a/v2/internal/frontend/desktop/darwin/WindowDelegate.h
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-// WindowDelegate.h
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#ifndef WindowDelegate_h
-#define WindowDelegate_h
-
-#import "WailsContext.h"
-
-@interface WindowDelegate : NSObject
-
-@property bool hideOnClose;
-
-@property (assign) WailsContext* ctx;
-
-- (void)windowDidExitFullScreen:(NSNotification *)notification;
-
-
-@end
-
-
-#endif /* WindowDelegate_h */
diff --git a/v2/internal/frontend/desktop/darwin/WindowDelegate.m b/v2/internal/frontend/desktop/darwin/WindowDelegate.m
deleted file mode 100644
index 915f12853..000000000
--- a/v2/internal/frontend/desktop/darwin/WindowDelegate.m
+++ /dev/null
@@ -1,38 +0,0 @@
-//go:build darwin
-//
-// WindowDelegate.m
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-#import
-#import
-#import "WindowDelegate.h"
-#import "message.h"
-#import "WailsContext.h"
-
-@implementation WindowDelegate
-- (BOOL)windowShouldClose:(WailsWindow *)sender {
- if( self.hideOnClose ) {
- [NSApp hide:nil];
- return false;
- }
- processMessage("Q");
- return false;
-}
-
-- (void)windowDidExitFullScreen:(NSNotification *)notification {
- [self.ctx.mainWindow applyWindowConstraints];
-}
-
-- (void)windowWillEnterFullScreen:(NSNotification *)notification {
- [self.ctx.mainWindow disableWindowConstraints];
-}
-
-- (NSApplicationPresentationOptions)window:(WailsWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
- return NSApplicationPresentationAutoHideToolbar | NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationFullScreen;
-}
-
-
-@end
diff --git a/v2/internal/frontend/desktop/darwin/browser.go b/v2/internal/frontend/desktop/darwin/browser.go
deleted file mode 100644
index c865ab6d9..000000000
--- a/v2/internal/frontend/desktop/darwin/browser.go
+++ /dev/null
@@ -1,24 +0,0 @@
-//go:build darwin
-// +build darwin
-
-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
- }
-
- // Specific method implementation
- if err := browser.OpenURL(url); err != nil {
- f.logger.Error("Unable to open default system browser")
- }
-}
diff --git a/v2/internal/frontend/desktop/darwin/callbacks.go b/v2/internal/frontend/desktop/darwin/callbacks.go
deleted file mode 100644
index ab0d18e47..000000000
--- a/v2/internal/frontend/desktop/darwin/callbacks.go
+++ /dev/null
@@ -1,51 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
-#import
-#import "Application.h"
-
-#include
-*/
-import "C"
-
-import (
- "errors"
- "strconv"
-
- "github.com/wailsapp/wails/v2/pkg/menu"
-)
-
-func (f *Frontend) handleCallback(menuItemID uint) error {
- menuItem := getMenuItemForID(menuItemID)
- if menuItem == nil {
- return errors.New("unknown menuItem ID: " + strconv.Itoa(int(menuItemID)))
- }
-
- wailsMenuItem := menuItem.wailsMenuItem
- if wailsMenuItem.Type == menu.CheckboxType {
- wailsMenuItem.Checked = !wailsMenuItem.Checked
- C.UpdateMenuItem(menuItem.nsmenuitem, bool2Cint(wailsMenuItem.Checked))
- }
- if wailsMenuItem.Type == menu.RadioType {
- // Ignore if we clicked the item that is already checked
- if !wailsMenuItem.Checked {
- for _, item := range menuItem.radioGroupMembers {
- if item.wailsMenuItem.Checked {
- item.wailsMenuItem.Checked = false
- C.UpdateMenuItem(item.nsmenuitem, C.int(0))
- }
- }
- wailsMenuItem.Checked = true
- C.UpdateMenuItem(menuItem.nsmenuitem, C.int(1))
- }
- }
- if wailsMenuItem.Click != nil {
- go wailsMenuItem.Click(&menu.CallbackData{MenuItem: wailsMenuItem})
- }
- return nil
-}
diff --git a/v2/internal/frontend/desktop/darwin/calloc.go b/v2/internal/frontend/desktop/darwin/calloc.go
deleted file mode 100644
index afd0a9115..000000000
--- a/v2/internal/frontend/desktop/darwin/calloc.go
+++ /dev/null
@@ -1,34 +0,0 @@
-//go:build darwin
-
-package darwin
-
-/*
-#include
-*/
-import "C"
-import "unsafe"
-
-// Calloc handles alloc/dealloc of C data
-type Calloc struct {
- pool []unsafe.Pointer
-}
-
-// NewCalloc creates a new allocator
-func NewCalloc() Calloc {
- return Calloc{}
-}
-
-// String creates a new C string and retains a reference to it
-func (c Calloc) String(in string) *C.char {
- result := C.CString(in)
- c.pool = append(c.pool, unsafe.Pointer(result))
- return result
-}
-
-// Free frees all allocated C memory
-func (c Calloc) Free() {
- for _, str := range c.pool {
- C.free(str)
- }
- c.pool = []unsafe.Pointer{}
-}
diff --git a/v2/internal/frontend/desktop/darwin/clipboard.go b/v2/internal/frontend/desktop/darwin/clipboard.go
deleted file mode 100644
index c40ba8771..000000000
--- a/v2/internal/frontend/desktop/darwin/clipboard.go
+++ /dev/null
@@ -1,35 +0,0 @@
-//go:build darwin
-
-package darwin
-
-import (
- "os/exec"
-)
-
-func (f *Frontend) ClipboardGetText() (string, error) {
- pasteCmd := exec.Command("pbpaste")
- out, err := pasteCmd.Output()
- if err != nil {
- return "", err
- }
- return string(out), nil
-}
-
-func (f *Frontend) ClipboardSetText(text string) error {
- copyCmd := exec.Command("pbcopy")
- in, err := copyCmd.StdinPipe()
- if err != nil {
- return err
- }
-
- if err := copyCmd.Start(); err != nil {
- return err
- }
- if _, err := in.Write([]byte(text)); err != nil {
- return err
- }
- if err := in.Close(); err != nil {
- return err
- }
- return copyCmd.Wait()
-}
diff --git a/v2/internal/frontend/desktop/darwin/dialog.go b/v2/internal/frontend/desktop/darwin/dialog.go
deleted file mode 100644
index 66bb2f13a..000000000
--- a/v2/internal/frontend/desktop/darwin/dialog.go
+++ /dev/null
@@ -1,196 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
-#import
-#import "Application.h"
-#import "WailsContext.h"
-*/
-import "C"
-
-import (
- "encoding/json"
- "fmt"
- "strings"
- "sync"
- "unsafe"
-
- "github.com/leaanthony/slicer"
- "github.com/wailsapp/wails/v2/internal/frontend"
-)
-
-// 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
-)
-
-// OpenDirectoryDialog prompts the user to select a directory
-func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
- results, err := f.openDialog(&options, false, false, true)
- if err != nil {
- return "", err
- }
- var selected string
- if len(results) > 0 {
- selected = results[0]
- }
- return selected, nil
-}
-
-func (f *Frontend) openDialog(options *frontend.OpenDialogOptions, multiple bool, allowfiles bool, allowdirectories bool) ([]string, error) {
- dialogLock.Lock()
- defer dialogLock.Unlock()
-
- c := NewCalloc()
- defer c.Free()
- title := c.String(options.Title)
- defaultFilename := c.String(options.DefaultFilename)
- defaultDirectory := c.String(options.DefaultDirectory)
- allowDirectories := bool2Cint(allowdirectories)
- allowFiles := bool2Cint(allowfiles)
- canCreateDirectories := bool2Cint(options.CanCreateDirectories)
- treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
- resolveAliases := bool2Cint(options.ResolvesAliases)
- showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
- allowMultipleFileSelection := bool2Cint(multiple)
-
- var filterStrings slicer.StringSlicer
- if options.Filters != nil {
- for _, filter := range options.Filters {
- thesePatterns := strings.Split(filter.Pattern, ";")
- for _, pattern := range thesePatterns {
- pattern = strings.TrimSpace(pattern)
- if pattern != "" {
- filterStrings.Add(pattern)
- }
- }
- }
- filterStrings.Deduplicate()
- }
- filters := filterStrings.Join(";")
- C.OpenFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, allowDirectories, allowFiles, canCreateDirectories, treatPackagesAsDirectories, resolveAliases, showHiddenFiles, allowMultipleFileSelection, c.String(filters))
-
- result := <-openFileDialogResponse
-
- var parsedResults []string
- err := json.Unmarshal([]byte(result), &parsedResults)
-
- return parsedResults, err
-}
-
-// OpenFileDialog prompts the user to select a file
-func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
- results, err := f.openDialog(&options, false, true, false)
- if err != nil {
- return "", err
- }
- var selected string
- if len(results) > 0 {
- selected = results[0]
- }
- return selected, nil
-}
-
-// OpenMultipleFilesDialog prompts the user to select a file
-func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
- return f.openDialog(&options, true, true, false)
-}
-
-// SaveFileDialog prompts the user to select a file
-func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
- dialogLock.Lock()
- defer dialogLock.Unlock()
-
- c := NewCalloc()
- defer c.Free()
- title := c.String(options.Title)
- defaultFilename := c.String(options.DefaultFilename)
- defaultDirectory := c.String(options.DefaultDirectory)
- canCreateDirectories := bool2Cint(options.CanCreateDirectories)
- treatPackagesAsDirectories := bool2Cint(options.TreatPackagesAsDirectories)
- showHiddenFiles := bool2Cint(options.ShowHiddenFiles)
-
- var filterStrings slicer.StringSlicer
- if options.Filters != nil {
- for _, filter := range options.Filters {
- thesePatterns := strings.Split(filter.Pattern, ";")
- for _, pattern := range thesePatterns {
- pattern = strings.TrimSpace(pattern)
- if pattern != "" {
- filterStrings.Add(pattern)
- }
- }
- }
- filterStrings.Deduplicate()
- }
- filters := filterStrings.Join(";")
- C.SaveFileDialog(f.mainWindow.context, title, defaultFilename, defaultDirectory, canCreateDirectories, treatPackagesAsDirectories, showHiddenFiles, c.String(filters))
-
- result := <-saveFileDialogResponse
-
- return result, nil
-}
-
-// MessageDialog show a message dialog to the user
-func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
- dialogLock.Lock()
- defer dialogLock.Unlock()
-
- c := NewCalloc()
- defer c.Free()
- dialogType := c.String(string(options.Type))
- title := c.String(options.Title)
- message := c.String(options.Message)
- defaultButton := c.String(options.DefaultButton)
- cancelButton := c.String(options.CancelButton)
- const MaxButtons = 4
- var buttons [MaxButtons]*C.char
- for index, buttonText := range options.Buttons {
- if index == MaxButtons {
- return "", fmt.Errorf("max %d buttons supported (%d given)", MaxButtons, len(options.Buttons))
- }
- buttons[index] = c.String(buttonText)
- }
-
- var iconData unsafe.Pointer
- var iconDataLength C.int
- if options.Icon != nil {
- iconData = unsafe.Pointer(&options.Icon[0])
- iconDataLength = C.int(len(options.Icon))
- }
-
- C.MessageDialog(f.mainWindow.context, dialogType, title, message, buttons[0], buttons[1], buttons[2], buttons[3], defaultButton, cancelButton, iconData, iconDataLength)
-
- result := <-messageDialogResponse
-
- selectedC := buttons[result]
- var selected string
- if selectedC != nil {
- selected = options.Buttons[result]
- }
- return selected, nil
-}
-
-//export processMessageDialogResponse
-func processMessageDialogResponse(selection int) {
- messageDialogResponse <- selection
-}
-
-//export processOpenFileDialogResponse
-func processOpenFileDialogResponse(cselection *C.char) {
- selection := C.GoString(cselection)
- openFileDialogResponse <- selection
-}
-
-//export processSaveFileDialogResponse
-func processSaveFileDialogResponse(cselection *C.char) {
- selection := C.GoString(cselection)
- saveFileDialogResponse <- selection
-}
diff --git a/v2/internal/frontend/desktop/darwin/frontend.go b/v2/internal/frontend/desktop/darwin/frontend.go
deleted file mode 100644
index 6566445d5..000000000
--- a/v2/internal/frontend/desktop/darwin/frontend.go
+++ /dev/null
@@ -1,525 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#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"
- "fmt"
- "html/template"
- "log"
- "net"
- "net/url"
- "os"
- "unsafe"
-
- "github.com/wailsapp/wails/v2/pkg/assetserver"
- "github.com/wailsapp/wails/v2/pkg/assetserver/webview"
-
- "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"
-)
-
-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)
-)
-
-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
- startURL *url.URL
-
- // main window handle
- mainWindow *Window
- bindings *binding.Bindings
- dispatcher frontend.Dispatcher
-
- originValidator *originvalidator.OriginValidator
-}
-
-func (f *Frontend) RunMainLoop() {
- C.RunMainLoop()
-}
-
-func (f *Frontend) WindowClose() {
- C.ReleaseContext(f.mainWindow.context)
-}
-
-func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
- result := &Frontend{
- frontendOptions: appoptions,
- logger: myLogger,
- bindings: appBindings,
- dispatcher: dispatcher,
- 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
- var err error
- if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
- bindings, err = appBindings.ToJSON()
- if err != nil {
- log.Fatal(err)
- }
- } else {
- appBindings.DB().UpdateObfuscatedCallMap()
- }
-
- assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
- if err != nil {
- log.Fatal(err)
- }
- assets.ExpectedWebViewHost = result.startURL.Host
- result.assets = assets
-
- go result.startRequestProcessor()
- }
-
- 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)
- if err != nil {
- println(err.Error())
- }
- }
-}
-
-func (f *Frontend) WindowReload() {
- f.ExecJS("runtime.WindowReload();")
-}
-
-func (f *Frontend) WindowReloadApp() {
- f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
-}
-
-func (f *Frontend) WindowSetSystemDefaultTheme() {
-}
-
-func (f *Frontend) WindowSetLightTheme() {
-}
-
-func (f *Frontend) WindowSetDarkTheme() {
-}
-
-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")
-
- if _debug != nil {
- f.debug = _debug.(bool)
- }
- if _devtoolsEnabled != nil {
- f.devtoolsEnabled = _devtoolsEnabled.(bool)
- }
-
- mainWindow := NewWindow(f.frontendOptions, f.debug, f.devtoolsEnabled)
- f.mainWindow = mainWindow
- f.mainWindow.Center()
-
- go func() {
- if f.frontendOptions.OnStartup != nil {
- f.frontendOptions.OnStartup(f.ctx)
- }
- }()
- mainWindow.Run(f.startURL.String())
- return nil
-}
-
-func (f *Frontend) WindowCenter() {
- f.mainWindow.Center()
-}
-
-func (f *Frontend) WindowSetAlwaysOnTop(onTop bool) {
- f.mainWindow.SetAlwaysOnTop(onTop)
-}
-
-func (f *Frontend) WindowSetPosition(x, y int) {
- f.mainWindow.SetPosition(x, y)
-}
-
-func (f *Frontend) WindowGetPosition() (int, int) {
- return f.mainWindow.GetPosition()
-}
-
-func (f *Frontend) WindowSetSize(width, height int) {
- f.mainWindow.SetSize(width, height)
-}
-
-func (f *Frontend) WindowGetSize() (int, int) {
- return f.mainWindow.Size()
-}
-
-func (f *Frontend) WindowSetTitle(title string) {
- f.mainWindow.SetTitle(title)
-}
-
-func (f *Frontend) WindowFullscreen() {
- f.mainWindow.Fullscreen()
-}
-
-func (f *Frontend) WindowUnfullscreen() {
- f.mainWindow.UnFullscreen()
-}
-
-func (f *Frontend) WindowShow() {
- f.mainWindow.Show()
-}
-
-func (f *Frontend) WindowHide() {
- f.mainWindow.Hide()
-}
-
-func (f *Frontend) Show() {
- f.mainWindow.ShowApplication()
-}
-
-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()
-}
-
-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)
-}
-
-func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
- if col == nil {
- return
- }
- f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
-}
-
-func (f *Frontend) ScreenGetAll() ([]frontend.Screen, error) {
- return GetAllScreens(f.mainWindow.context)
-}
-
-func (f *Frontend) WindowIsMaximised() bool {
- return f.mainWindow.IsMaximised()
-}
-
-func (f *Frontend) WindowIsMinimised() bool {
- return f.mainWindow.IsMinimised()
-}
-
-func (f *Frontend) WindowIsNormal() bool {
- return f.mainWindow.IsNormal()
-}
-
-func (f *Frontend) WindowIsFullscreen() bool {
- return f.mainWindow.IsFullScreen()
-}
-
-func (f *Frontend) Quit() {
- if f.frontendOptions.OnBeforeClose != nil {
- go func() {
- if !f.frontendOptions.OnBeforeClose(f.ctx) {
- f.mainWindow.Quit()
- }
- }()
- return
- }
- f.mainWindow.Quit()
-}
-
-func (f *Frontend) WindowPrint() {
- f.mainWindow.Print()
-}
-
-type EventNotify struct {
- Name string `json:"name"`
- Data []interface{} `json:"data"`
-}
-
-func (f *Frontend) Notify(name string, data ...interface{}) {
- notification := EventNotify{
- Name: name,
- Data: data,
- }
- payload, err := json.Marshal(notification)
- if err != nil {
- f.logger.Error(err.Error())
- return
- }
- f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
-}
-
-func (f *Frontend) processMessage(message string) {
- if message == "DomReady" {
- if f.frontendOptions.OnDomReady != nil {
- f.frontendOptions.OnDomReady(f.ctx)
- }
- return
- }
-
- 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
- }
-
- //if strings.HasPrefix(message, "systemevent:") {
- // f.processSystemEvent(message)
- // return
- //}
-
- go func() {
- 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) 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) {
- escaped, err := json.Marshal(message)
- if err != nil {
- panic(err)
- }
- f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
-}
-
-func (f *Frontend) ExecJS(js string) {
- f.mainWindow.ExecJS(js)
-}
-
-//func (f *Frontend) processSystemEvent(message string) {
-// sl := strings.Split(message, ":")
-// if len(sl) != 2 {
-// f.logger.Error("Invalid system message: %s", message)
-// return
-// }
-// switch sl[1] {
-// case "fullscreen":
-// f.mainWindow.DisableSizeConstraints()
-// case "unfullscreen":
-// f.mainWindow.EnableSizeConstraints()
-// default:
-// f.logger.Error("Unknown system message: %s", message)
-// }
-//}
-
-//export processMessage
-func processMessage(message *C.char) {
- goMessage := C.GoString(message)
- 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) {
- 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
deleted file mode 100644
index dc3f08969..000000000
--- a/v2/internal/frontend/desktop/darwin/inspector.go
+++ /dev/null
@@ -1,10 +0,0 @@
-//go:build darwin && !(dev || debug || devtools)
-
-package darwin
-
-import (
- "unsafe"
-)
-
-func showInspector(_ unsafe.Pointer) {
-}
diff --git a/v2/internal/frontend/desktop/darwin/inspector_dev.go b/v2/internal/frontend/desktop/darwin/inspector_dev.go
deleted file mode 100644
index e79b9c3e7..000000000
--- a/v2/internal/frontend/desktop/darwin/inspector_dev.go
+++ /dev/null
@@ -1,78 +0,0 @@
-//go:build darwin && (dev || debug || devtools)
-
-package darwin
-
-// We are using private APIs here, make sure this is only included in a dev/debug build and not in a production build.
-// Otherwise the binary might get rejected by the AppReview-Team when pushing it to the AppStore.
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
-#import
-#import "WailsContext.h"
-
-extern void processMessage(const char *message);
-
-@interface _WKInspector : NSObject
-- (void)show;
-- (void)detach;
-@end
-
-@interface WKWebView ()
-- (_WKInspector *)_inspector;
-@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;
-
- @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 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;
- }];
-}
-*/
-import "C"
-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
deleted file mode 100644
index 75a84dc76..000000000
--- a/v2/internal/frontend/desktop/darwin/main.m
+++ /dev/null
@@ -1,243 +0,0 @@
-//go:build ignore
-// main.m
-// test
-//
-// Created by Lea Anthony on 10/10/21.
-//
-
-// ****** This file is used for testing purposes only ******
-
-#import
-#import "Application.h"
-
-void processMessage(const char*t) {
- NSLog(@"processMessage called");
-}
-
-void processMessageDialogResponse(int t) {
- NSLog(@"processMessage called");
-}
-
-void processOpenFileDialogResponse(const char *t) {
- NSLog(@"processMessage called %s", t);
-}
-void processSaveFileDialogResponse(const char *t) {
- NSLog(@"processMessage called %s", t);
-}
-
-void processCallback(int callbackID) {
- NSLog(@"Process callback %d", callbackID);
-}
-
-void processURLRequest(void *ctx, unsigned long long requestId, const char* url, const char *method, const char *headers, const void *body, int bodyLen) {
- NSLog(@"processURLRequest called");
- const char myByteArray[] = { 0x3c,0x68,0x31,0x3e,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21,0x3c,0x2f,0x68,0x31,0x3e };
- // void *inctx, const char *url, int statusCode, const char *headers, void* data, int datalength
- ProcessURLResponse(ctx, requestId, 200, "{\"Content-Type\": \"text/html\"}", (void*)myByteArray, 21);
-}
-
-unsigned char _Users_username_Pictures_SaltBae_png[] = {
-
-0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
-0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
-0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00,
-0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61,
-0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, 0x00, 0x00, 0x7a,
-0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80,
-0xe8, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a,
-0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, 0x00,
-0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b,
-0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x01, 0xd5, 0x69, 0x54,
-0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64,
-0x6f, 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
-0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x20, 0x78,
-0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62,
-0x65, 0x3a, 0x6e, 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20,
-0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, 0x4d, 0x50,
-0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22,
-0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44,
-0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d,
-0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
-0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f,
-0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79,
-0x6e, 0x74, 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20,
-0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65,
-0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64,
-0x66, 0x3a, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20,
-0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78,
-0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68,
-0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f,
-0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f,
-0x31, 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
-0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f,
-0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
-0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65,
-0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
-0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72,
-0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c,
-0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74,
-0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
-0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68,
-0x6f, 0x74, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74,
-0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e,
-0x32, 0x3c, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x3a, 0x50, 0x68, 0x6f, 0x74,
-0x6f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x49, 0x6e, 0x74, 0x65, 0x72,
-0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20,
-0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44,
-0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a,
-0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46,
-0x3e, 0x0a, 0x3c, 0x2f, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74,
-0x61, 0x3e, 0x0a, 0x02, 0xd8, 0x80, 0x05, 0x00, 0x00, 0x04, 0xdc, 0x49,
-0x44, 0x41, 0x54, 0x38, 0x11, 0x1d, 0x94, 0x49, 0x6c, 0x1b, 0x65, 0x18,
-0x86, 0x9f, 0x99, 0xf9, 0x67, 0xc6, 0x6b, 0xbc, 0x26, 0xce, 0xda, 0xa4,
-0x25, 0x69, 0x0b, 0x2d, 0x28, 0x34, 0x2c, 0x95, 0x00, 0x89, 0x45, 0x08,
-0x5a, 0x95, 0x03, 0x08, 0x09, 0x21, 0xe0, 0x80, 0x38, 0xc3, 0x85, 0x03,
-0xe2, 0x00, 0x47, 0xc4, 0x1d, 0x38, 0x70, 0xe3, 0xc6, 0x01, 0x01, 0x42,
-0x20, 0x54, 0x7a, 0x2a, 0x6b, 0x0b, 0x94, 0xd2, 0xd2, 0x25, 0x69, 0x9b,
-0xa4, 0x0d, 0x2d, 0xa9, 0xb3, 0x78, 0x89, 0x9d, 0xf1, 0x2c, 0x9e, 0x85,
-0x2f, 0xb5, 0x35, 0xb6, 0x35, 0x96, 0xde, 0x79, 0xdf, 0xef, 0x7f, 0x9f,
-0x4f, 0xfb, 0xe0, 0xad, 0x37, 0x12, 0xfd, 0xf0, 0xb3, 0x9c, 0xfb, 0xb7,
-0xc5, 0x8d, 0x46, 0x9b, 0x71, 0x5b, 0xf1, 0xd0, 0xf4, 0x18, 0xdb, 0xeb,
-0x4b, 0x1c, 0xff, 0xf1, 0x57, 0x98, 0xdc, 0x87, 0x72, 0x3a, 0x8c, 0x3a,
-0xcb, 0x8c, 0xea, 0x31, 0x35, 0xb7, 0xc3, 0x99, 0xba, 0xc3, 0xd7, 0xab,
-0x3e, 0x87, 0x2a, 0x8a, 0xb3, 0xff, 0xdc, 0xe0, 0x9b, 0x8f, 0x5f, 0xa2,
-0x1c, 0xc5, 0xfc, 0x72, 0xc9, 0x41, 0x99, 0x71, 0x48, 0xca, 0x84, 0x3c,
-0x3e, 0xda, 0xd2, 0x05, 0x9a, 0xb1, 0xc7, 0x35, 0x67, 0x1c, 0xdd, 0x4c,
-0x68, 0xeb, 0x26, 0xd9, 0x30, 0x26, 0x09, 0x23, 0x5c, 0x3f, 0xc2, 0xd3,
-0x43, 0xc2, 0x24, 0x21, 0x4e, 0x34, 0x40, 0x27, 0x89, 0x13, 0xf9, 0x1e,
-0x22, 0x6e, 0xd5, 0x45, 0x43, 0x63, 0xc6, 0xd2, 0x50, 0xa9, 0xc4, 0x67,
-0x24, 0x15, 0x72, 0xa9, 0x7e, 0x95, 0xfa, 0x4f, 0x27, 0x78, 0x64, 0x76,
-0x86, 0x23, 0x61, 0xc0, 0xf0, 0x58, 0x15, 0xc3, 0x29, 0x71, 0x06, 0x45,
-0x2e, 0xa5, 0x48, 0xbb, 0x0a, 0x3d, 0x89, 0xa0, 0x8f, 0x08, 0x8a, 0x8e,
-0x08, 0xbb, 0xc1, 0x8e, 0xb0, 0x8d, 0xdd, 0x0f, 0xc9, 0x84, 0x06, 0x65,
-0x34, 0xf4, 0xed, 0x8d, 0xff, 0x58, 0xbd, 0xfc, 0x27, 0x17, 0x2f, 0x9e,
-0xe3, 0xf0, 0x81, 0x49, 0x5e, 0xde, 0x5f, 0xe1, 0x9e, 0x82, 0xcd, 0xdc,
-0x78, 0x8d, 0xd9, 0xb2, 0xc9, 0x56, 0x12, 0x32, 0x94, 0x4f, 0x91, 0xcb,
-0x88, 0x68, 0xda, 0x42, 0x13, 0x77, 0x11, 0xa2, 0xa8, 0xc3, 0x5a, 0x5f,
-0x46, 0x30, 0x65, 0x52, 0x29, 0xe4, 0x24, 0x4d, 0x8e, 0xcc, 0x68, 0x19,
-0xe5, 0x76, 0xbb, 0xac, 0x5c, 0x98, 0xa7, 0xb3, 0xed, 0xd0, 0x37, 0x62,
-0xa2, 0xb0, 0xc7, 0x89, 0xe5, 0x2e, 0x03, 0x0d, 0x97, 0x95, 0x46, 0x8f,
-0x31, 0xd7, 0xa6, 0x63, 0x81, 0x65, 0x25, 0x84, 0xba, 0x45, 0x5f, 0x65,
-0x31, 0x2c, 0x71, 0x6b, 0x77, 0x69, 0xf5, 0x7a, 0xbc, 0xb0, 0x3b, 0xcd,
-0xf9, 0xa5, 0x90, 0xd1, 0xb0, 0xcd, 0xd4, 0xb0, 0xdc, 0xd7, 0xc4, 0xfa,
-0xf0, 0x78, 0x95, 0x7b, 0x27, 0xab, 0x5c, 0x5e, 0x6e, 0xd2, 0xee, 0x05,
-0xdc, 0xd8, 0xea, 0xf1, 0xf7, 0xe2, 0x1a, 0xc7, 0xee, 0x1a, 0x62, 0x2e,
-0x1f, 0xe3, 0xe8, 0xb6, 0xc4, 0x4c, 0xd3, 0x6d, 0x6e, 0xd0, 0x6b, 0xfc,
-0x4c, 0xe3, 0xd4, 0x1f, 0xc4, 0x4b, 0xf3, 0x1c, 0x2c, 0x65, 0x29, 0x67,
-0x4d, 0xbe, 0xfb, 0xad, 0x45, 0x65, 0x0c, 0xea, 0x7e, 0x1f, 0x15, 0x6b,
-0x09, 0x0b, 0x8b, 0xb7, 0x19, 0xc9, 0xa5, 0x78, 0x75, 0x6e, 0x18, 0xdf,
-0xf5, 0x79, 0x72, 0xd0, 0xa2, 0x2d, 0xb3, 0x3a, 0xbb, 0xb4, 0x41, 0x3e,
-0x53, 0xe6, 0xf4, 0xca, 0x3c, 0xa5, 0x7c, 0x86, 0xe9, 0xfd, 0x47, 0x18,
-0x2e, 0xbd, 0xce, 0xd1, 0x97, 0x26, 0x78, 0xbc, 0x7e, 0x1d, 0xff, 0xcc,
-0xa7, 0x5c, 0x71, 0x74, 0x16, 0xe3, 0x18, 0xd7, 0x1e, 0x23, 0xe8, 0xac,
-0xa3, 0x0c, 0xcd, 0x60, 0x22, 0x6f, 0x43, 0x36, 0x43, 0x3b, 0x19, 0xc6,
-0x08, 0x7a, 0xe0, 0x6c, 0xe3, 0x27, 0x8a, 0xdb, 0x4e, 0xc0, 0xd4, 0xa0,
-0xcd, 0x27, 0xaf, 0xbd, 0xcb, 0x86, 0x36, 0xc6, 0xcc, 0xfe, 0x59, 0xd2,
-0xca, 0x90, 0x93, 0x36, 0x70, 0xaf, 0x9c, 0xe4, 0xcb, 0x6f, 0x65, 0x54,
-0xd9, 0x47, 0x59, 0x70, 0xbb, 0x74, 0x1b, 0x0e, 0x89, 0xe7, 0xa3, 0xc7,
-0x12, 0x39, 0x63, 0xea, 0x68, 0x12, 0x6b, 0x53, 0x5c, 0x9e, 0xef, 0x76,
-0xf0, 0x55, 0x86, 0x0d, 0x17, 0x56, 0x9a, 0x4d, 0x94, 0x95, 0x65, 0xe6,
-0xbe, 0x67, 0x98, 0xbe, 0xfb, 0x21, 0x52, 0xd2, 0x43, 0xaf, 0x5d, 0x47,
-0x6b, 0x5c, 0xa3, 0x59, 0xbf, 0xc2, 0x62, 0xdd, 0x26, 0xa5, 0x12, 0x6a,
-0x41, 0x44, 0xdf, 0xbd, 0xcd, 0x92, 0x17, 0xa0, 0xb6, 0x03, 0x43, 0xba,
-0x66, 0x91, 0xe9, 0xdc, 0xc2, 0xce, 0xed, 0xa1, 0xfc, 0xc0, 0x2b, 0x14,
-0xff, 0xfd, 0x1e, 0x4b, 0xb3, 0xa9, 0x29, 0x87, 0x81, 0xd2, 0x04, 0x8e,
-0x66, 0x89, 0x58, 0x00, 0x7e, 0x07, 0xaf, 0xdb, 0xa4, 0xbb, 0xb5, 0x49,
-0xb9, 0xaa, 0x18, 0xb9, 0x77, 0x8e, 0xcd, 0xdb, 0x6d, 0x1e, 0x1c, 0xb5,
-0x38, 0x7d, 0xa5, 0xcf, 0xaa, 0x08, 0xeb, 0x77, 0x3f, 0x35, 0xc7, 0xda,
-0xfc, 0x02, 0xaa, 0xf6, 0x1c, 0xbb, 0x9f, 0x78, 0x9f, 0x89, 0x43, 0x47,
-0xa4, 0x6f, 0x3d, 0x06, 0xed, 0x90, 0x92, 0x79, 0x95, 0xd4, 0xe4, 0xfd,
-0x98, 0x66, 0x4a, 0x6a, 0xd7, 0xc7, 0x0b, 0x62, 0xa4, 0xe3, 0x8c, 0x4d,
-0xc4, 0xe8, 0x85, 0x98, 0xe5, 0x46, 0x44, 0x26, 0x97, 0x21, 0xe9, 0xf7,
-0xf9, 0x61, 0xc5, 0xe3, 0xd4, 0x66, 0x84, 0xd2, 0x70, 0xc9, 0xee, 0x79,
-0x98, 0x43, 0xc7, 0x5e, 0x27, 0xb6, 0x8a, 0xd2, 0x5a, 0x1f, 0xf3, 0xa9,
-0xf7, 0x88, 0xce, 0x7d, 0x85, 0x71, 0xe0, 0x79, 0x98, 0x7a, 0x90, 0x9e,
-0x1b, 0xd0, 0x13, 0x52, 0x4a, 0x66, 0x97, 0x7d, 0x33, 0x1e, 0xed, 0xae,
-0xc7, 0x87, 0x1f, 0x7d, 0xce, 0xc2, 0xd5, 0x3a, 0xe6, 0xde, 0x02, 0xcb,
-0xdb, 0x3e, 0xbe, 0xa6, 0x91, 0x95, 0x62, 0x6b, 0x2f, 0xce, 0x90, 0x3c,
-0xfd, 0xce, 0x71, 0x0e, 0xcc, 0x3e, 0x82, 0x13, 0xf4, 0x09, 0xd5, 0x00,
-0x16, 0x82, 0x98, 0xb3, 0x49, 0x24, 0xb1, 0x83, 0xc8, 0xc0, 0xd6, 0x3a,
-0x54, 0x33, 0xab, 0x14, 0x8c, 0x16, 0x4e, 0x38, 0xcc, 0xe5, 0xeb, 0x4d,
-0x5e, 0x7b, 0xfb, 0x4d, 0xaa, 0x79, 0xa1, 0x45, 0x1c, 0x9b, 0xd2, 0x94,
-0xcc, 0x0e, 0x8c, 0x52, 0x7a, 0x65, 0x17, 0xc7, 0xa9, 0x0c, 0x8e, 0xe2,
-0xf7, 0xba, 0xa8, 0xc8, 0x13, 0x87, 0x32, 0x87, 0x0b, 0x27, 0x30, 0x36,
-0x57, 0xe8, 0xea, 0x15, 0xce, 0x06, 0x65, 0x5e, 0x3d, 0x5a, 0x94, 0x53,
-0xb7, 0x59, 0x58, 0xdf, 0x25, 0xc4, 0xe4, 0xc9, 0x65, 0x3d, 0xb4, 0xb4,
-0x4e, 0x37, 0x0c, 0x29, 0x98, 0x4a, 0xe8, 0x11, 0xde, 0x85, 0x42, 0x43,
-0x1c, 0xaa, 0x38, 0x55, 0xc4, 0xb4, 0x2c, 0x22, 0x3d, 0xcd, 0xfa, 0xea,
-0x0d, 0xf4, 0x8d, 0x1f, 0xc9, 0x5f, 0xfa, 0x82, 0x6d, 0xc7, 0xe1, 0xa6,
-0x57, 0xe3, 0x56, 0x6e, 0x96, 0xbf, 0x16, 0x1f, 0xa3, 0x54, 0xaa, 0x91,
-0x16, 0x5a, 0xb2, 0xa9, 0x04, 0xaf, 0x67, 0xc9, 0xac, 0x6c, 0xfa, 0x32,
-0x9e, 0x48, 0xea, 0xa5, 0x0b, 0x89, 0x3b, 0x54, 0x47, 0xf2, 0xa1, 0xf2,
-0x2a, 0x4d, 0xeb, 0xf4, 0x17, 0xdc, 0xd4, 0x72, 0x6c, 0xb5, 0x36, 0x28,
-0xb6, 0x7e, 0x17, 0x04, 0xd3, 0xac, 0x7a, 0x42, 0xc1, 0xf4, 0x6e, 0x9e,
-0xbf, 0x6b, 0xb7, 0x3c, 0x3a, 0x21, 0x67, 0xcb, 0x41, 0x48, 0x07, 0x91,
-0xde, 0x1a, 0xe2, 0xaa, 0x9c, 0xb1, 0x59, 0xdb, 0x12, 0x25, 0xc1, 0x32,
-0x92, 0xea, 0xc9, 0xaf, 0x3b, 0x97, 0xca, 0xca, 0xfe, 0x5b, 0xfe, 0xe5,
-0x33, 0x29, 0xeb, 0x16, 0x95, 0xd2, 0x24, 0xeb, 0xda, 0x30, 0xeb, 0x95,
-0x1a, 0xd3, 0xf7, 0x0f, 0x51, 0x1c, 0xd9, 0x0b, 0x99, 0x12, 0x7a, 0x4a,
-0xd0, 0xd3, 0x25, 0x9a, 0x88, 0x45, 0xb1, 0x04, 0x33, 0x2c, 0x8a, 0x99,
-0x34, 0x6b, 0x75, 0x19, 0x91, 0x9d, 0x92, 0x29, 0x89, 0xa0, 0x2c, 0x8b,
-0x9d, 0xd8, 0x7a, 0x5e, 0x04, 0x07, 0x87, 0x66, 0x28, 0x56, 0x67, 0xb9,
-0xd6, 0xd2, 0x39, 0xd9, 0xec, 0x33, 0x30, 0xb2, 0x8b, 0xea, 0xae, 0x83,
-0x18, 0xb9, 0x31, 0x34, 0xbb, 0x42, 0x22, 0x0b, 0x21, 0x96, 0x3c, 0x61,
-0xac, 0xcb, 0x95, 0x60, 0x2a, 0xe9, 0x68, 0x79, 0x08, 0x36, 0x56, 0x65,
-0x27, 0x4a, 0xd9, 0x83, 0x00, 0xcf, 0x0b, 0xf1, 0xfc, 0x10, 0x15, 0x0a,
-0x6a, 0x75, 0x77, 0x8b, 0x86, 0xdc, 0x58, 0x57, 0x45, 0x52, 0xe9, 0x84,
-0x81, 0x7c, 0x91, 0x28, 0x55, 0x23, 0x96, 0x13, 0xd7, 0x24, 0xbe, 0xac,
-0x17, 0xfa, 0xf2, 0x78, 0x63, 0xc7, 0x82, 0x08, 0xda, 0xa6, 0xc5, 0x50,
-0x55, 0x04, 0xe5, 0x65, 0x5b, 0x06, 0xde, 0xce, 0xf0, 0x24, 0xf3, 0x4e,
-0x70, 0xb5, 0x15, 0x6a, 0x34, 0x7b, 0x11, 0x9d, 0xbe, 0x10, 0x53, 0xd0,
-0xa8, 0x86, 0x2e, 0x76, 0xb6, 0x2a, 0x9d, 0x2c, 0x48, 0x3c, 0x5b, 0xa2,
-0xc8, 0x3a, 0x37, 0xd4, 0x9d, 0xed, 0x6c, 0x4a, 0xab, 0x95, 0x6e, 0x08,
-0x66, 0x3d, 0x5a, 0xad, 0x4d, 0x18, 0xc8, 0xca, 0xfa, 0xd5, 0x85, 0x6f,
-0xf9, 0x5f, 0xde, 0x02, 0x30, 0xff, 0x03, 0x8c, 0x47, 0x35, 0xad, 0xbc,
-0xbf, 0x26, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
-0x42, 0x60, 0x82
-
-};
-
-unsigned int _Users_username_Pictures_SaltBae_png_len = 1863;
-
-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;
- int titlebarAppearsTransparent = 0;
- int hideTitle = 0;
- int useToolbar = 0;
- int hideToolbarSeparator = 0;
- int webviewIsTransparent = 1;
- int alwaysOnTop = 0;
- int hideWindowOnClose = 0;
- const char* appearance = "NSAppearanceNameDarkAqua";
- int windowIsTranslucent = 1;
- int devtoolsEnabled = 1;
- int defaultContextMenuEnabled = 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,
- startsHidden, 400, 400, 600, 600, false);
- SetBackgroundColour(result, 255, 0, 0, 255);
- void *m = NewMenu("");
- SetAbout(result, "Fake title", "I am a description", _Users_username_Pictures_SaltBae_png, _Users_username_Pictures_SaltBae_png_len);
-// AddMenuByRole(result, 1);
-
- AppendRole(result, m, 1);
- AppendRole(result, m, 2);
- void* submenu = NewMenu("test");
- void* menuITem = AppendMenuItem(result, submenu, "Woohoo", "p", 0, 0, 0, 470);
- AppendSubmenu(m, submenu);
- UpdateMenuItem(menuITem, 1);
- SetAsApplicationMenu(result, m);
-// SetPosition(result, 100, 100);
-
-
-
- Run((void*)CFBridgingRetain(result));
- return 0;
-}
diff --git a/v2/internal/frontend/desktop/darwin/menu.go b/v2/internal/frontend/desktop/darwin/menu.go
deleted file mode 100644
index 24dbe3201..000000000
--- a/v2/internal/frontend/desktop/darwin/menu.go
+++ /dev/null
@@ -1,134 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
-#import
-#import "Application.h"
-#import "WailsContext.h"
-
-#include
-*/
-import "C"
-
-import (
- "unsafe"
-
- "github.com/wailsapp/wails/v2/pkg/menu"
- "github.com/wailsapp/wails/v2/pkg/menu/keys"
-)
-
-type NSMenu struct {
- context unsafe.Pointer
- nsmenu unsafe.Pointer
-}
-
-func NewNSMenu(context unsafe.Pointer, name string) *NSMenu {
- c := NewCalloc()
- defer c.Free()
- title := c.String(name)
- nsmenu := C.NewMenu(title)
- return &NSMenu{
- context: context,
- nsmenu: nsmenu,
- }
-}
-
-func (m *NSMenu) AddSubMenu(label string) *NSMenu {
- result := NewNSMenu(m.context, label)
- C.AppendSubmenu(m.nsmenu, result.nsmenu)
- return result
-}
-
-func (m *NSMenu) AppendRole(role menu.Role) {
- C.AppendRole(m.context, m.nsmenu, C.int(role))
-}
-
-type MenuItem struct {
- id uint
- nsmenuitem unsafe.Pointer
- wailsMenuItem *menu.MenuItem
- radioGroupMembers []*MenuItem
-}
-
-func (m *NSMenu) AddMenuItem(menuItem *menu.MenuItem) *MenuItem {
- c := NewCalloc()
- defer c.Free()
- var modifier C.int
- var key *C.char
- if menuItem.Accelerator != nil {
- modifier = C.int(keys.ToMacModifier(menuItem.Accelerator))
- key = c.String(menuItem.Accelerator.Key)
- }
-
- result := &MenuItem{
- wailsMenuItem: menuItem,
- }
-
- result.id = createMenuItemID(result)
- result.nsmenuitem = C.AppendMenuItem(m.context, m.nsmenu, c.String(menuItem.Label), key, modifier, bool2Cint(menuItem.Disabled), bool2Cint(menuItem.Checked), C.int(result.id))
- return result
-}
-
-//func (w *Window) SetApplicationMenu(menu *menu.Menu) {
-//w.applicationMenu = menu
-//processMenu(w, menu)
-//}
-
-func processMenu(parent *NSMenu, wailsMenu *menu.Menu) {
- var radioGroups []*MenuItem
-
- for _, menuItem := range wailsMenu.Items {
- if menuItem.SubMenu != nil {
- if len(radioGroups) > 0 {
- processRadioGroups(radioGroups)
- radioGroups = []*MenuItem{}
- }
- submenu := parent.AddSubMenu(menuItem.Label)
- processMenu(submenu, menuItem.SubMenu)
- } else {
- lastMenuItem := processMenuItem(parent, menuItem)
- if menuItem.Type == menu.RadioType {
- radioGroups = append(radioGroups, lastMenuItem)
- } else {
- if len(radioGroups) > 0 {
- processRadioGroups(radioGroups)
- radioGroups = []*MenuItem{}
- }
- }
- }
- }
-}
-
-func processRadioGroups(groups []*MenuItem) {
- for _, item := range groups {
- item.radioGroupMembers = groups
- }
-}
-
-func processMenuItem(parent *NSMenu, menuItem *menu.MenuItem) *MenuItem {
- if menuItem.Hidden {
- return nil
- }
- if menuItem.Role != 0 {
- parent.AppendRole(menuItem.Role)
- return nil
- }
- if menuItem.Type == menu.SeparatorType {
- C.AppendSeparator(parent.nsmenu)
- return nil
- }
-
- return parent.AddMenuItem(menuItem)
-}
-
-func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
- f.mainWindow.SetApplicationMenu(menu)
-}
-
-func (f *Frontend) MenuUpdateApplicationMenu() {
- f.mainWindow.UpdateApplicationMenu()
-}
diff --git a/v2/internal/frontend/desktop/darwin/menuitem.go b/v2/internal/frontend/desktop/darwin/menuitem.go
deleted file mode 100644
index 64aab84a9..000000000
--- a/v2/internal/frontend/desktop/darwin/menuitem.go
+++ /dev/null
@@ -1,54 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
-#import
-#import "Application.h"
-#import "WailsContext.h"
-
-#include
-*/
-import "C"
-
-import (
- "log"
- "math"
- "sync"
-)
-
-var (
- menuItemToID = make(map[*MenuItem]uint)
- idToMenuItem = make(map[uint]*MenuItem)
- menuItemLock sync.Mutex
- menuItemIDCounter uint = 0
-)
-
-func createMenuItemID(item *MenuItem) uint {
- menuItemLock.Lock()
- defer menuItemLock.Unlock()
- counter := 0
- for {
- menuItemIDCounter++
- value := idToMenuItem[menuItemIDCounter]
- if value == nil {
- break
- }
- counter++
- if counter == math.MaxInt {
- log.Fatal("insane amounts of menuitems detected! Aborting before the collapse of the world!")
- }
- }
- idToMenuItem[menuItemIDCounter] = item
- menuItemToID[item] = menuItemIDCounter
- return menuItemIDCounter
-}
-
-func getMenuItemForID(id uint) *MenuItem {
- menuItemLock.Lock()
- defer menuItemLock.Unlock()
- return idToMenuItem[id]
-}
diff --git a/v2/internal/frontend/desktop/darwin/message.h b/v2/internal/frontend/desktop/darwin/message.h
deleted file mode 100644
index 86506f868..000000000
--- a/v2/internal/frontend/desktop/darwin/message.h
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// message.h
-// test
-//
-// Created by Lea Anthony on 14/10/21.
-//
-
-#ifndef export_h
-#define export_h
-
-
-#ifdef __cplusplus
-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*);
-void processSaveFileDialogResponse(const char*);
-void processCallback(int);
-
-#ifdef __cplusplus
-}
-#endif
-
-
-#endif /* export_h */
diff --git a/v2/internal/frontend/desktop/darwin/screen.go b/v2/internal/frontend/desktop/darwin/screen.go
deleted file mode 100644
index bd64a31f9..000000000
--- a/v2/internal/frontend/desktop/darwin/screen.go
+++ /dev/null
@@ -1,118 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit -framework AppKit
-#import
-#include
-#include
-
-#import "Application.h"
-#import "WailsContext.h"
-
-typedef struct Screen {
- int isCurrent;
- int isPrimary;
- int height;
- int width;
- int pHeight;
- int pWidth;
-} Screen;
-
-
-int GetNumScreens(){
- return [[NSScreen screens] count];
-}
-
-int screenUniqueID(NSScreen *screen){
- // adapted from https://stackoverflow.com/a/1237490/4188138
- NSDictionary* screenDictionary = [screen deviceDescription];
- NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"];
- CGDirectDisplayID aID = [screenID unsignedIntValue];
- return aID;
-}
-
-Screen GetNthScreen(int nth, void *inctx){
- WailsContext *ctx = (__bridge WailsContext*) inctx;
- NSArray *screens = [NSScreen screens];
- NSScreen* nthScreen = [screens objectAtIndex:nth];
- NSScreen* currentScreen = [ctx getCurrentScreen];
-
- Screen returnScreen;
- returnScreen.isCurrent = (int)(screenUniqueID(currentScreen)==screenUniqueID(nthScreen));
- // TODO properly handle screen mirroring
- // from apple documentation:
- // https://developer.apple.com/documentation/appkit/nsscreen/1388393-screens?language=objc
- // The screen at index 0 in the returned array corresponds to the primary screen of the user’s system. This is the screen that contains the menu bar and whose origin is at the point (0, 0). In the case of mirroring, the first screen is the largest drawable display; if all screens are the same size, it is the screen with the highest pixel depth. This primary screen may not be the same as the one returned by the mainScreen method, which returns the screen with the active window.
- 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"
-)
-
-func GetAllScreens(wailsContext unsafe.Pointer) ([]frontend.Screen, error) {
- err := error(nil)
- screens := []frontend.Screen{}
- numScreens := int(C.GetNumScreens())
- 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)
- }
- return screens, err
-}
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
deleted file mode 100644
index 87d4213d9..000000000
--- a/v2/internal/frontend/desktop/darwin/window.go
+++ /dev/null
@@ -1,313 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package darwin
-
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
-#import
-#import "Application.h"
-#import "WailsContext.h"
-
-#include
-*/
-import "C"
-
-import (
- "log"
- "runtime"
- "strconv"
- "strings"
- "unsafe"
-
- "github.com/wailsapp/wails/v2/pkg/menu"
-
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func init() {
- runtime.LockOSThread()
-}
-
-type Window struct {
- context unsafe.Pointer
-
- applicationMenu *menu.Menu
-}
-
-func bool2Cint(value bool) C.int {
- if value {
- return C.int(1)
- }
- return C.int(0)
-}
-
-func bool2CboolPtr(value bool) *C.bool {
- v := C.bool(value)
- return &v
-}
-
-func NewWindow(frontendOptions *options.App, debug bool, devtools bool) *Window {
- c := NewCalloc()
- defer c.Free()
-
- frameless := bool2Cint(frontendOptions.Frameless)
- resizable := bool2Cint(!frontendOptions.DisableResize)
- fullscreen := bool2Cint(frontendOptions.Fullscreen)
- 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)
-
- var fullSizeContent, hideTitleBar, zoomable, hideTitle, useToolbar, webviewIsTransparent C.int
- var titlebarAppearsTransparent, hideToolbarSeparator, windowIsTranslucent, contentProtection C.int
- var appearance, title *C.char
- var preferences C.struct_Preferences
-
- width := C.int(frontendOptions.Width)
- height := C.int(frontendOptions.Height)
- minWidth := C.int(frontendOptions.MinWidth)
- minHeight := C.int(frontendOptions.MinHeight)
- maxWidth := C.int(frontendOptions.MaxWidth)
- maxHeight := C.int(frontendOptions.MaxHeight)
- windowStartState := C.int(int(frontendOptions.WindowStartState))
-
- 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 {
- fullSizeContent = bool2Cint(mac.TitleBar.FullSizeContent)
- hideTitleBar = bool2Cint(mac.TitleBar.HideTitleBar)
- hideTitle = bool2Cint(mac.TitleBar.HideTitle)
- useToolbar = bool2Cint(mac.TitleBar.UseToolbar)
- 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,
- 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,
- )
-
- // Create menu
- result := &Window{
- context: unsafe.Pointer(context),
- }
-
- if frontendOptions.BackgroundColour != nil {
- result.SetBackgroundColour(frontendOptions.BackgroundColour.R, frontendOptions.BackgroundColour.G, frontendOptions.BackgroundColour.B, frontendOptions.BackgroundColour.A)
- }
-
- if frontendOptions.Mac != nil && frontendOptions.Mac.About != nil {
- title := c.String(frontendOptions.Mac.About.Title)
- description := c.String(frontendOptions.Mac.About.Message)
- var icon unsafe.Pointer
- var length C.int
- if frontendOptions.Mac.About.Icon != nil {
- icon = unsafe.Pointer(&frontendOptions.Mac.About.Icon[0])
- length = C.int(len(frontendOptions.Mac.About.Icon))
- }
- C.SetAbout(result.context, title, description, icon, length)
- }
-
- if frontendOptions.Menu != nil {
- result.SetApplicationMenu(frontendOptions.Menu)
- }
-
- if debug && frontendOptions.Debug.OpenInspectorOnStartup {
- showInspector(result.context)
- }
- return result
-}
-
-func (w *Window) Center() {
- C.Center(w.context)
-}
-
-func (w *Window) Run(url string) {
- _url := C.CString(url)
- C.Run(w.context, _url)
- C.free(unsafe.Pointer(_url))
-}
-
-func (w *Window) Quit() {
- C.Quit(w.context)
-}
-
-func (w *Window) SetBackgroundColour(r uint8, g uint8, b uint8, a uint8) {
- C.SetBackgroundColour(w.context, C.int(r), C.int(g), C.int(b), C.int(a))
-}
-
-func (w *Window) ExecJS(js string) {
- _js := C.CString(js)
- C.ExecJS(w.context, _js)
- C.free(unsafe.Pointer(_js))
-}
-
-func (w *Window) SetPosition(x int, y int) {
- C.SetPosition(w.context, C.int(x), C.int(y))
-}
-
-func (w *Window) SetSize(width int, height int) {
- C.SetSize(w.context, C.int(width), C.int(height))
-}
-
-func (w *Window) SetAlwaysOnTop(onTop bool) {
- C.SetAlwaysOnTop(w.context, bool2Cint(onTop))
-}
-
-func (w *Window) SetTitle(title string) {
- t := C.CString(title)
- C.SetTitle(w.context, t)
- C.free(unsafe.Pointer(t))
-}
-
-func (w *Window) Maximise() {
- C.Maximise(w.context)
-}
-
-func (w *Window) ToggleMaximise() {
- C.ToggleMaximise(w.context)
-}
-
-func (w *Window) UnMaximise() {
- C.UnMaximise(w.context)
-}
-
-func (w *Window) IsMaximised() bool {
- return (bool)(C.IsMaximised(w.context))
-}
-
-func (w *Window) Minimise() {
- C.Minimise(w.context)
-}
-
-func (w *Window) UnMinimise() {
- C.UnMinimise(w.context)
-}
-
-func (w *Window) IsMinimised() bool {
- return (bool)(C.IsMinimised(w.context))
-}
-
-func (w *Window) IsNormal() bool {
- return !w.IsMaximised() && !w.IsMinimised() && !w.IsFullScreen()
-}
-
-func (w *Window) SetMinSize(width int, height int) {
- C.SetMinSize(w.context, C.int(width), C.int(height))
-}
-
-func (w *Window) SetMaxSize(width int, height int) {
- C.SetMaxSize(w.context, C.int(width), C.int(height))
-}
-
-func (w *Window) Fullscreen() {
- C.Fullscreen(w.context)
-}
-
-func (w *Window) UnFullscreen() {
- C.UnFullscreen(w.context)
-}
-
-func (w *Window) IsFullScreen() bool {
- return (bool)(C.IsFullScreen(w.context))
-}
-
-func (w *Window) Show() {
- C.Show(w.context)
-}
-
-func (w *Window) Hide() {
- C.Hide(w.context)
-}
-
-func (w *Window) ShowApplication() {
- C.ShowApplication(w.context)
-}
-
-func (w *Window) HideApplication() {
- C.HideApplication(w.context)
-}
-
-func parseIntDuo(temp string) (int, int) {
- split := strings.Split(temp, ",")
- x, err := strconv.Atoi(split[0])
- if err != nil {
- log.Fatal(err)
- }
- y, err := strconv.Atoi(split[1])
- if err != nil {
- log.Fatal(err)
- }
- return x, y
-}
-
-func (w *Window) GetPosition() (int, int) {
- var _result *C.char = C.GetPosition(w.context)
- temp := C.GoString(_result)
- return parseIntDuo(temp)
-}
-
-func (w *Window) Size() (int, int) {
- var _result *C.char = C.GetSize(w.context)
- temp := C.GoString(_result)
- return parseIntDuo(temp)
-}
-
-func (w *Window) SetApplicationMenu(inMenu *menu.Menu) {
- w.applicationMenu = inMenu
- w.UpdateApplicationMenu()
-}
-
-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/desktop_darwin.go b/v2/internal/frontend/desktop/desktop_darwin.go
deleted file mode 100644
index c02c0cdbd..000000000
--- a/v2/internal/frontend/desktop/desktop_darwin.go
+++ /dev/null
@@ -1,20 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package desktop
-
-import (
- "context"
-
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin"
-
- "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"
-)
-
-func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
- return darwin.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
-}
diff --git a/v2/internal/frontend/desktop/desktop_linux.go b/v2/internal/frontend/desktop/desktop_linux.go
deleted file mode 100644
index e057a9e18..000000000
--- a/v2/internal/frontend/desktop/desktop_linux.go
+++ /dev/null
@@ -1,17 +0,0 @@
-//go:build linux
-// +build linux
-
-package desktop
-
-import (
- "context"
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/frontend"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/linux"
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
- return linux.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
-}
diff --git a/v2/internal/frontend/desktop/desktop_windows.go b/v2/internal/frontend/desktop/desktop_windows.go
deleted file mode 100644
index f9a680aac..000000000
--- a/v2/internal/frontend/desktop/desktop_windows.go
+++ /dev/null
@@ -1,17 +0,0 @@
-//go:build windows
-// +build windows
-
-package desktop
-
-import (
- "context"
- "github.com/wailsapp/wails/v2/internal/binding"
- "github.com/wailsapp/wails/v2/internal/frontend"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows"
- "github.com/wailsapp/wails/v2/internal/logger"
- "github.com/wailsapp/wails/v2/pkg/options"
-)
-
-func NewFrontend(ctx context.Context, appoptions *options.App, logger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
- return windows.NewFrontend(ctx, appoptions, logger, appBindings, dispatcher)
-}
diff --git a/v2/internal/frontend/desktop/linux/browser.go b/v2/internal/frontend/desktop/linux/browser.go
deleted file mode 100644
index 962e3b28a..000000000
--- a/v2/internal/frontend/desktop/linux/browser.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build linux
-// +build linux
-
-package linux
-
-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
- }
- // Specific method implementation
- if err := browser.OpenURL(url); err != nil {
- f.logger.Error("Unable to open default system browser")
- }
-}
diff --git a/v2/internal/frontend/desktop/linux/calloc.go b/v2/internal/frontend/desktop/linux/calloc.go
deleted file mode 100644
index 27e5ad18d..000000000
--- a/v2/internal/frontend/desktop/linux/calloc.go
+++ /dev/null
@@ -1,35 +0,0 @@
-//go:build linux
-// +build linux
-
-package linux
-
-/*
-#include
-*/
-import "C"
-import "unsafe"
-
-// Calloc handles alloc/dealloc of C data
-type Calloc struct {
- pool []unsafe.Pointer
-}
-
-// NewCalloc creates a new allocator
-func NewCalloc() Calloc {
- return Calloc{}
-}
-
-// String creates a new C string and retains a reference to it
-func (c Calloc) String(in string) *C.char {
- result := C.CString(in)
- c.pool = append(c.pool, unsafe.Pointer(result))
- return result
-}
-
-// Free frees all allocated C memory
-func (c Calloc) Free() {
- for _, str := range c.pool {
- C.free(str)
- }
- c.pool = []unsafe.Pointer{}
-}
diff --git a/v2/internal/frontend/desktop/linux/clipboard.go b/v2/internal/frontend/desktop/linux/clipboard.go
deleted file mode 100644
index a2a46dacc..000000000
--- a/v2/internal/frontend/desktop/linux/clipboard.go
+++ /dev/null
@@ -1,51 +0,0 @@
-//go:build linux
-// +build linux
-
-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
-
-#include "gtk/gtk.h"
-#include "webkit2/webkit2.h"
-
-static gchar* GetClipboardText() {
- GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
- return gtk_clipboard_wait_for_text(clip);
-}
-
-static void SetClipboardText(gchar* text) {
- GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
- gtk_clipboard_set_text(clip, text, -1);
-
- clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
- gtk_clipboard_set_text(clip, text, -1);
-}
-*/
-import "C"
-import "sync"
-
-func (f *Frontend) ClipboardGetText() (string, error) {
- var text string
- var wg sync.WaitGroup
- wg.Add(1)
- invokeOnMainThread(func() {
- ctxt := C.GetClipboardText()
- defer C.g_free(C.gpointer(ctxt))
- text = C.GoString(ctxt)
- wg.Done()
- })
- wg.Wait()
- return text, nil
-}
-
-func (f *Frontend) ClipboardSetText(text string) error {
- invokeOnMainThread(func() {
- ctxt := (*C.gchar)(C.CString(text))
- defer C.g_free(C.gpointer(ctxt))
- C.SetClipboardText(ctxt)
- })
- return nil
-}
diff --git a/v2/internal/frontend/desktop/linux/dialog.go b/v2/internal/frontend/desktop/linux/dialog.go
deleted file mode 100644
index c0d9158e5..000000000
--- a/v2/internal/frontend/desktop/linux/dialog.go
+++ /dev/null
@@ -1,89 +0,0 @@
-//go:build linux
-// +build linux
-
-package linux
-
-import (
- "github.com/wailsapp/wails/v2/internal/frontend"
- "unsafe"
-)
-
-/*
-#include
-#include "gtk/gtk.h"
-*/
-import "C"
-
-const (
- GTK_FILE_CHOOSER_ACTION_OPEN C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_OPEN
- GTK_FILE_CHOOSER_ACTION_SAVE C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_SAVE
- GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER C.GtkFileChooserAction = C.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
-)
-
-var openFileResults = make(chan []string)
-var messageDialogResult = make(chan string)
-
-func (f *Frontend) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (result string, err error) {
- f.mainWindow.OpenFileDialog(dialogOptions, 0, GTK_FILE_CHOOSER_ACTION_OPEN)
- results := <-openFileResults
- if len(results) == 1 {
- return results[0], nil
- }
- return "", nil
-}
-
-func (f *Frontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
- f.mainWindow.OpenFileDialog(dialogOptions, 1, GTK_FILE_CHOOSER_ACTION_OPEN)
- result := <-openFileResults
- return result, nil
-}
-
-func (f *Frontend) OpenDirectoryDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
- f.mainWindow.OpenFileDialog(dialogOptions, 0, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
- result := <-openFileResults
- if len(result) == 1 {
- return result[0], nil
- }
- return "", nil
-}
-
-func (f *Frontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (string, error) {
- options := frontend.OpenDialogOptions{
- DefaultDirectory: dialogOptions.DefaultDirectory,
- DefaultFilename: dialogOptions.DefaultFilename,
- Title: dialogOptions.Title,
- Filters: dialogOptions.Filters,
- ShowHiddenFiles: dialogOptions.ShowHiddenFiles,
- CanCreateDirectories: dialogOptions.CanCreateDirectories,
- }
- f.mainWindow.OpenFileDialog(options, 0, GTK_FILE_CHOOSER_ACTION_SAVE)
- results := <-openFileResults
- if len(results) == 1 {
- return results[0], nil
- }
- return "", nil
-}
-
-func (f *Frontend) MessageDialog(dialogOptions frontend.MessageDialogOptions) (string, error) {
- f.mainWindow.MessageDialog(dialogOptions)
- return <-messageDialogResult, nil
-}
-
-//export processOpenFileResult
-func processOpenFileResult(carray **C.char) {
- // Create a Go slice from the C array
- var result []string
- goArray := (*[1024]*C.char)(unsafe.Pointer(carray))[:1024:1024]
- for _, s := range goArray {
- if s == nil {
- break
- }
- result = append(result, C.GoString(s))
- }
- openFileResults <- result
-}
-
-//export processMessageDialogResult
-func processMessageDialogResult(result *C.char) {
- messageDialogResult <- C.GoString(result)
-}
diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go
deleted file mode 100644
index 2942a112e..000000000
--- a/v2/internal/frontend/desktop/linux/frontend.go
+++ /dev/null
@@ -1,589 +0,0 @@
-//go:build linux
-// +build linux
-
-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
-
-#include "gtk/gtk.h"
-#include "webkit2/webkit2.h"
-
-// CREDIT: https://github.com/rainycape/magick
-#include
-#include
-#include
-#include
-
-static void fix_signal(int signum)
-{
- struct sigaction st;
-
- if (sigaction(signum, NULL, &st) < 0) {
- goto fix_signal_error;
- }
- st.sa_flags |= SA_ONSTACK;
- if (sigaction(signum, &st, NULL) < 0) {
- goto fix_signal_error;
- }
- return;
-fix_signal_error:
- fprintf(stderr, "error fixing handler for signal %d, please "
- "report this issue to "
- "https://github.com/wailsapp/wails: %s\n",
- signum, strerror(errno));
-}
-
-static void install_signal_handlers()
-{
-#if defined(SIGCHLD)
- fix_signal(SIGCHLD);
-#endif
-#if defined(SIGHUP)
- fix_signal(SIGHUP);
-#endif
-#if defined(SIGINT)
- fix_signal(SIGINT);
-#endif
-#if defined(SIGQUIT)
- fix_signal(SIGQUIT);
-#endif
-#if defined(SIGABRT)
- fix_signal(SIGABRT);
-#endif
-#if defined(SIGFPE)
- fix_signal(SIGFPE);
-#endif
-#if defined(SIGTERM)
- fix_signal(SIGTERM);
-#endif
-#if defined(SIGBUS)
- fix_signal(SIGBUS);
-#endif
-#if defined(SIGSEGV)
- fix_signal(SIGSEGV);
-#endif
-#if defined(SIGXCPU)
- fix_signal(SIGXCPU);
-#endif
-#if defined(SIGXFSZ)
- fix_signal(SIGXFSZ);
-#endif
-}
-
-static 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"
- "net/url"
- "os"
- "runtime"
- "strings"
- "sync"
- "text/template"
- "unsafe"
-
- "github.com/wailsapp/wails/v2/pkg/assetserver"
- "github.com/wailsapp/wails/v2/pkg/assetserver/webview"
-
- "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
- ctx context.Context
-
- frontendOptions *options.App
- logger *logger.Logger
- debug bool
- devtoolsEnabled bool
-
- // Assets
- assets *assetserver.AssetServer
- startURL *url.URL
-
- // main window handle
- mainWindow *Window
- bindings *binding.Bindings
- dispatcher frontend.Dispatcher
-
- originValidator *originvalidator.OriginValidator
-}
-
-func (f *Frontend) RunMainLoop() {
- C.gtk_main()
-}
-
-func (f *Frontend) WindowClose() {
- f.mainWindow.Destroy()
-}
-
-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,
- logger: myLogger,
- bindings: appBindings,
- dispatcher: dispatcher,
- 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
- var err error
- if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
- bindings, err = appBindings.ToJSON()
- if err != nil {
- log.Fatal(err)
- }
- } else {
- appBindings.DB().UpdateObfuscatedCallMap()
- }
- assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
- if err != nil {
- log.Fatal(err)
- }
- result.assets = assets
-
- go result.startRequestProcessor()
- }
-
- 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.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()
-
- return result
-}
-
-func (f *Frontend) startMessageProcessor() {
- for message := range messageBuffer {
- f.processMessage(message)
- }
-}
-
-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();")
-}
-
-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
-
- go func() {
- if f.frontendOptions.OnStartup != nil {
- f.frontendOptions.OnStartup(f.ctx)
- }
- }()
-
- if f.frontendOptions.SingleInstanceLock != nil {
- SetupSingleInstance(f.frontendOptions.SingleInstanceLock.UniqueId)
- }
-
- f.mainWindow.Run(f.startURL.String())
-
- return nil
-}
-
-func (f *Frontend) WindowCenter() {
- f.mainWindow.Center()
-}
-
-func (f *Frontend) WindowSetAlwaysOnTop(b bool) {
- f.mainWindow.SetKeepAbove(b)
-}
-
-func (f *Frontend) WindowSetPosition(x, y int) {
- f.mainWindow.SetPosition(x, y)
-}
-func (f *Frontend) WindowGetPosition() (int, int) {
- return f.mainWindow.GetPosition()
-}
-
-func (f *Frontend) WindowSetSize(width, height int) {
- f.mainWindow.SetSize(width, height)
-}
-
-func (f *Frontend) WindowGetSize() (int, int) {
- return f.mainWindow.Size()
-}
-
-func (f *Frontend) WindowSetTitle(title string) {
- f.mainWindow.SetTitle(title)
-}
-
-func (f *Frontend) WindowFullscreen() {
- if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
- f.ExecJS("window.wails.flags.enableResize = false;")
- }
- f.mainWindow.Fullscreen()
-}
-
-func (f *Frontend) WindowUnfullscreen() {
- if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
- f.ExecJS("window.wails.flags.enableResize = true;")
- }
- f.mainWindow.UnFullscreen()
-}
-
-func (f *Frontend) WindowReloadApp() {
- f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
-}
-
-func (f *Frontend) WindowShow() {
- f.mainWindow.Show()
-}
-
-func (f *Frontend) WindowHide() {
- f.mainWindow.Hide()
-}
-
-func (f *Frontend) Show() {
- f.mainWindow.Show()
-}
-
-func (f *Frontend) Hide() {
- f.mainWindow.Hide()
-}
-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()
-}
-
-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)
-}
-
-func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
- if col == nil {
- return
- }
- f.mainWindow.SetBackgroundColour(col.R, col.G, col.B, col.A)
-}
-
-func (f *Frontend) ScreenGetAll() ([]Screen, error) {
- return GetAllScreens(f.mainWindow.asGTKWindow())
-}
-
-func (f *Frontend) WindowIsMaximised() bool {
- return f.mainWindow.IsMaximised()
-}
-
-func (f *Frontend) WindowIsMinimised() bool {
- return f.mainWindow.IsMinimised()
-}
-
-func (f *Frontend) WindowIsNormal() bool {
- return f.mainWindow.IsNormal()
-}
-
-func (f *Frontend) WindowIsFullscreen() bool {
- return f.mainWindow.IsFullScreen()
-}
-
-func (f *Frontend) Quit() {
- if f.frontendOptions.OnBeforeClose != nil {
- go func() {
- if !f.frontendOptions.OnBeforeClose(f.ctx) {
- f.mainWindow.Quit()
- }
- }()
- return
- }
- f.mainWindow.Quit()
-}
-
-func (f *Frontend) WindowPrint() {
- f.ExecJS("window.print();")
-}
-
-type EventNotify struct {
- Name string `json:"name"`
- Data []interface{} `json:"data"`
-}
-
-func (f *Frontend) Notify(name string, data ...interface{}) {
- notification := EventNotify{
- Name: name,
- Data: data,
- }
- payload, err := json.Marshal(notification)
- if err != nil {
- f.logger.Error(err.Error())
- return
- }
- f.mainWindow.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
-}
-
-var edgeMap = map[string]uintptr{
- "n-resize": C.GDK_WINDOW_EDGE_NORTH,
- "ne-resize": C.GDK_WINDOW_EDGE_NORTH_EAST,
- "e-resize": C.GDK_WINDOW_EDGE_EAST,
- "se-resize": C.GDK_WINDOW_EDGE_SOUTH_EAST,
- "s-resize": C.GDK_WINDOW_EDGE_SOUTH,
- "sw-resize": C.GDK_WINDOW_EDGE_SOUTH_WEST,
- "w-resize": C.GDK_WINDOW_EDGE_WEST,
- "nw-resize": C.GDK_WINDOW_EDGE_NORTH_WEST,
-}
-
-func (f *Frontend) processMessage(message string) {
- if message == "DomReady" {
- if f.frontendOptions.OnDomReady != nil {
- f.frontendOptions.OnDomReady(f.ctx)
- }
- return
- }
-
- if message == "drag" {
- if !f.mainWindow.IsFullScreen() {
- f.startDrag()
- }
- return
- }
-
- if message == "wails:showInspector" {
- f.mainWindow.ShowInspector()
- return
- }
-
- if strings.HasPrefix(message, "resize:") {
- if !f.mainWindow.IsFullScreen() {
- sl := strings.Split(message, ":")
- if len(sl) != 2 {
- f.logger.Info("Unknown message returned from dispatcher: %+v", message)
- return
- }
- edge := edgeMap[sl[1]]
- err := f.startResize(edge)
- if err != nil {
- f.logger.Error(err.Error())
- }
- }
- return
- }
-
- 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,
- )
-
- 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
- }
-
- go func() {
- 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) {
- escaped, err := json.Marshal(message)
- if err != nil {
- panic(err)
- }
- f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
-}
-
-func (f *Frontend) startDrag() {
- f.mainWindow.StartDrag()
-}
-
-func (f *Frontend) startResize(edge uintptr) error {
- f.mainWindow.StartResize(edge)
- return nil
-}
-
-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) {
- goMessage := C.GoString(message)
- 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() {
- for request := range requestBuffer {
- f.assets.ServeWebViewRequest(request)
- }
-}
-
-//export processURLRequest
-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
deleted file mode 100644
index 67a38c7a0..000000000
--- a/v2/internal/frontend/desktop/linux/gtk.go
+++ /dev/null
@@ -1,85 +0,0 @@
-//go:build linux
-// +build linux
-
-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
-
-#include "gtk/gtk.h"
-
-static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
-
-extern void blockClick(GtkWidget* menuItem, gulong handler_id);
-extern void unblockClick(GtkWidget* menuItem, gulong handler_id);
-*/
-import "C"
-import (
- "unsafe"
-
- "github.com/wailsapp/wails/v2/pkg/menu"
-)
-
-func GtkMenuItemWithLabel(label string) *C.GtkWidget {
- cLabel := C.CString(label)
- result := C.gtk_menu_item_new_with_label(cLabel)
- C.free(unsafe.Pointer(cLabel))
- return result
-}
-
-func GtkCheckMenuItemWithLabel(label string) *C.GtkWidget {
- cLabel := C.CString(label)
- result := C.gtk_check_menu_item_new_with_label(cLabel)
- C.free(unsafe.Pointer(cLabel))
- return result
-}
-
-func GtkRadioMenuItemWithLabel(label string, group *C.GSList) *C.GtkWidget {
- cLabel := C.CString(label)
- result := C.gtk_radio_menu_item_new_with_label(group, cLabel)
- C.free(unsafe.Pointer(cLabel))
- return result
-}
-
-//export handleMenuItemClick
-func handleMenuItemClick(gtkWidget unsafe.Pointer) {
- // Make sure to execute the final callback on a new goroutine otherwise if the callback e.g. tries to open a dialog, the
- // main thread will get blocked and so the message loop blocks. As a result the app will block and shows a
- // "not responding" dialog.
-
- item := gtkSignalToMenuItem[(*C.GtkWidget)(gtkWidget)]
- switch item.Type {
- case menu.CheckboxType:
- item.Checked = !item.Checked
- checked := C.int(0)
- if item.Checked {
- checked = C.int(1)
- }
- for _, gtkCheckbox := range gtkCheckboxCache[item] {
- handler := gtkSignalHandlers[gtkCheckbox]
- C.blockClick(gtkCheckbox, handler)
- C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkCheckbox)), checked)
- C.unblockClick(gtkCheckbox, handler)
- }
- go item.Click(&menu.CallbackData{MenuItem: item})
- case menu.RadioType:
- gtkRadioItems := gtkRadioMenuCache[item]
- active := C.gtk_check_menu_item_get_active(C.toGtkCheckMenuItem(gtkWidget))
- if int(active) == 1 {
- for _, gtkRadioItem := range gtkRadioItems {
- handler := gtkSignalHandlers[gtkRadioItem]
- C.blockClick(gtkRadioItem, handler)
- C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(gtkRadioItem)), 1)
- C.unblockClick(gtkRadioItem, handler)
- }
- item.Checked = true
- go item.Click(&menu.CallbackData{MenuItem: item})
- } else {
- item.Checked = false
- }
- default:
- go item.Click(&menu.CallbackData{MenuItem: item})
- }
-}
diff --git a/v2/internal/frontend/desktop/linux/invoke.go b/v2/internal/frontend/desktop/linux/invoke.go
deleted file mode 100644
index 16d5e73d2..000000000
--- a/v2/internal/frontend/desktop/linux/invoke.go
+++ /dev/null
@@ -1,78 +0,0 @@
-//go:build linux
-// +build linux
-
-package linux
-
-/*
-#cgo linux pkg-config: gtk+-3.0
-
-#include
-#include "gtk/gtk.h"
-
-extern gboolean invokeCallbacks(void *);
-
-static inline void triggerInvokesOnMainThread() {
- g_idle_add((GSourceFunc)invokeCallbacks, NULL);
-}
-*/
-import "C"
-import (
- "runtime"
- "sync"
- "unsafe"
-
- "golang.org/x/sys/unix"
-)
-
-var (
- m sync.Mutex
- mainTid int
- dispatchq []func()
-)
-
-func invokeOnMainThread(f func()) {
- if tryInvokeOnCurrentGoRoutine(f) {
- return
- }
-
- m.Lock()
- dispatchq = append(dispatchq, f)
- m.Unlock()
-
- C.triggerInvokesOnMainThread()
-}
-
-func tryInvokeOnCurrentGoRoutine(f func()) bool {
- m.Lock()
- mainThreadID := mainTid
- m.Unlock()
-
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- if mainThreadID != unix.Gettid() {
- return false
- }
- f()
- return true
-}
-
-//export invokeCallbacks
-func invokeCallbacks(_ unsafe.Pointer) C.gboolean {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- m.Lock()
- if mainTid == 0 {
- mainTid = unix.Gettid()
- }
-
- q := append([]func(){}, dispatchq...)
- dispatchq = []func(){}
- m.Unlock()
-
- for _, v := range q {
- v()
- }
- return C.G_SOURCE_REMOVE
-}
diff --git a/v2/internal/frontend/desktop/linux/keys.go b/v2/internal/frontend/desktop/linux/keys.go
deleted file mode 100644
index e5a127dbd..000000000
--- a/v2/internal/frontend/desktop/linux/keys.go
+++ /dev/null
@@ -1,110 +0,0 @@
-//go:build linux
-// +build linux
-
-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
-
-
-#include "gtk/gtk.h"
-
-*/
-import "C"
-import (
- "github.com/wailsapp/wails/v2/pkg/menu/keys"
-)
-
-var namedKeysToGTK = map[string]C.guint{
- "backspace": C.guint(0xff08),
- "tab": C.guint(0xff09),
- "return": C.guint(0xff0d),
- "enter": C.guint(0xff0d),
- "escape": C.guint(0xff1b),
- "left": C.guint(0xff51),
- "right": C.guint(0xff53),
- "up": C.guint(0xff52),
- "down": C.guint(0xff54),
- "space": C.guint(0xff80),
- "delete": C.guint(0xff9f),
- "home": C.guint(0xff95),
- "end": C.guint(0xff9c),
- "page up": C.guint(0xff9a),
- "page down": C.guint(0xff9b),
- "f1": C.guint(0xffbe),
- "f2": C.guint(0xffbf),
- "f3": C.guint(0xffc0),
- "f4": C.guint(0xffc1),
- "f5": C.guint(0xffc2),
- "f6": C.guint(0xffc3),
- "f7": C.guint(0xffc4),
- "f8": C.guint(0xffc5),
- "f9": C.guint(0xffc6),
- "f10": C.guint(0xffc7),
- "f11": C.guint(0xffc8),
- "f12": C.guint(0xffc9),
- "f13": C.guint(0xffca),
- "f14": C.guint(0xffcb),
- "f15": C.guint(0xffcc),
- "f16": C.guint(0xffcd),
- "f17": C.guint(0xffce),
- "f18": C.guint(0xffcf),
- "f19": C.guint(0xffd0),
- "f20": C.guint(0xffd1),
- "f21": C.guint(0xffd2),
- "f22": C.guint(0xffd3),
- "f23": C.guint(0xffd4),
- "f24": C.guint(0xffd5),
- "f25": C.guint(0xffd6),
- "f26": C.guint(0xffd7),
- "f27": C.guint(0xffd8),
- "f28": C.guint(0xffd9),
- "f29": C.guint(0xffda),
- "f30": C.guint(0xffdb),
- "f31": C.guint(0xffdc),
- "f32": C.guint(0xffdd),
- "f33": C.guint(0xffde),
- "f34": C.guint(0xffdf),
- "f35": C.guint(0xffe0),
- "numlock": C.guint(0xff7f),
-}
-
-func acceleratorToGTK(accelerator *keys.Accelerator) (C.guint, C.GdkModifierType) {
- key := parseKey(accelerator.Key)
- mods := parseModifiers(accelerator.Modifiers)
- return key, mods
-}
-
-func parseKey(key string) C.guint {
- var result C.guint
- result, found := namedKeysToGTK[key]
- if found {
- return result
- }
- // Check for unknown namedkeys
- // Check if we only have a single character
- if len(key) != 1 {
- return C.guint(0)
- }
- keyval := rune(key[0])
- return C.gdk_unicode_to_keyval(C.guint(keyval))
-}
-
-func parseModifiers(modifiers []keys.Modifier) C.GdkModifierType {
-
- var result C.GdkModifierType
-
- for _, modifier := range modifiers {
- switch modifier {
- case keys.ShiftKey:
- result |= C.GDK_SHIFT_MASK
- case keys.ControlKey, keys.CmdOrCtrlKey:
- result |= C.GDK_CONTROL_MASK
- case keys.OptionOrAltKey:
- result |= C.GDK_MOD1_MASK
- }
- }
- return result
-}
diff --git a/v2/internal/frontend/desktop/linux/menu.go b/v2/internal/frontend/desktop/linux/menu.go
deleted file mode 100644
index a61d190bd..000000000
--- a/v2/internal/frontend/desktop/linux/menu.go
+++ /dev/null
@@ -1,169 +0,0 @@
-//go:build linux
-// +build linux
-
-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
-
-#include "gtk/gtk.h"
-
-static GtkMenuItem *toGtkMenuItem(void *pointer) { return (GTK_MENU_ITEM(pointer)); }
-static GtkMenuShell *toGtkMenuShell(void *pointer) { return (GTK_MENU_SHELL(pointer)); }
-static GtkCheckMenuItem *toGtkCheckMenuItem(void *pointer) { return (GTK_CHECK_MENU_ITEM(pointer)); }
-static GtkRadioMenuItem *toGtkRadioMenuItem(void *pointer) { return (GTK_RADIO_MENU_ITEM(pointer)); }
-
-extern void handleMenuItemClick(void*);
-
-void blockClick(GtkWidget* menuItem, gulong handler_id) {
- g_signal_handler_block (menuItem, handler_id);
-}
-
-void unblockClick(GtkWidget* menuItem, gulong handler_id) {
- g_signal_handler_unblock (menuItem, handler_id);
-}
-
-gulong connectClick(GtkWidget* menuItem) {
- return g_signal_connect(menuItem, "activate", G_CALLBACK(handleMenuItemClick), (void*)menuItem);
-}
-
-void addAccelerator(GtkWidget* menuItem, GtkAccelGroup* group, guint key, GdkModifierType mods) {
- gtk_widget_add_accelerator(menuItem, "activate", group, key, mods, GTK_ACCEL_VISIBLE);
-}
-*/
-import "C"
-import (
- "unsafe"
-
- "github.com/wailsapp/wails/v2/pkg/menu"
-)
-
-var menuIdCounter int
-var menuItemToId map[*menu.MenuItem]int
-var menuIdToItem map[int]*menu.MenuItem
-var gtkCheckboxCache map[*menu.MenuItem][]*C.GtkWidget
-var gtkMenuCache map[*menu.MenuItem]*C.GtkWidget
-var gtkRadioMenuCache map[*menu.MenuItem][]*C.GtkWidget
-var gtkSignalHandlers map[*C.GtkWidget]C.gulong
-var gtkSignalToMenuItem map[*C.GtkWidget]*menu.MenuItem
-
-func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
- f.mainWindow.SetApplicationMenu(menu)
-}
-
-func (f *Frontend) MenuUpdateApplicationMenu() {
- f.mainWindow.SetApplicationMenu(f.mainWindow.applicationMenu)
-}
-
-func (w *Window) SetApplicationMenu(inmenu *menu.Menu) {
- if inmenu == nil {
- return
- }
-
- // Setup accelerator group
- w.accels = C.gtk_accel_group_new()
- C.gtk_window_add_accel_group(w.asGTKWindow(), w.accels)
-
- menuItemToId = make(map[*menu.MenuItem]int)
- menuIdToItem = make(map[int]*menu.MenuItem)
- gtkCheckboxCache = make(map[*menu.MenuItem][]*C.GtkWidget)
- gtkMenuCache = make(map[*menu.MenuItem]*C.GtkWidget)
- gtkRadioMenuCache = make(map[*menu.MenuItem][]*C.GtkWidget)
- gtkSignalHandlers = make(map[*C.GtkWidget]C.gulong)
- gtkSignalToMenuItem = make(map[*C.GtkWidget]*menu.MenuItem)
-
- // Increase ref count?
- w.menubar = C.gtk_menu_bar_new()
-
- processMenu(w, inmenu)
-
- C.gtk_widget_show(w.menubar)
-}
-
-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)
- }
- }
-}
-
-func processSubmenu(menuItem *menu.MenuItem, group *C.GtkAccelGroup) *C.GtkWidget {
- existingMenu := gtkMenuCache[menuItem]
- if existingMenu != nil {
- return existingMenu
- }
- gtkMenu := C.gtk_menu_new()
- submenu := GtkMenuItemWithLabel(menuItem.Label)
- for _, menuItem := range menuItem.SubMenu.Items {
- menuID := menuIdCounter
- menuIdToItem[menuID] = menuItem
- menuItemToId[menuItem] = menuID
- menuIdCounter++
- processMenuItem(gtkMenu, menuItem, group)
- }
- C.gtk_menu_item_set_submenu(C.toGtkMenuItem(unsafe.Pointer(submenu)), gtkMenu)
- gtkMenuCache[menuItem] = existingMenu
- return submenu
-}
-
-var currentRadioGroup *C.GSList
-
-func processMenuItem(parent *C.GtkWidget, menuItem *menu.MenuItem, group *C.GtkAccelGroup) {
- if menuItem.Hidden {
- return
- }
-
- if menuItem.Type != menu.RadioType {
- currentRadioGroup = nil
- }
-
- if menuItem.Type == menu.SeparatorType {
- result := C.gtk_separator_menu_item_new()
- C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
- return
- }
-
- var result *C.GtkWidget
-
- switch menuItem.Type {
- case menu.TextType:
- result = GtkMenuItemWithLabel(menuItem.Label)
- case menu.CheckboxType:
- result = GtkCheckMenuItemWithLabel(menuItem.Label)
- if menuItem.Checked {
- C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
- }
- gtkCheckboxCache[menuItem] = append(gtkCheckboxCache[menuItem], result)
-
- case menu.RadioType:
- result = GtkRadioMenuItemWithLabel(menuItem.Label, currentRadioGroup)
- currentRadioGroup = C.gtk_radio_menu_item_get_group(C.toGtkRadioMenuItem(unsafe.Pointer(result)))
- if menuItem.Checked {
- C.gtk_check_menu_item_set_active(C.toGtkCheckMenuItem(unsafe.Pointer(result)), 1)
- }
- gtkRadioMenuCache[menuItem] = append(gtkRadioMenuCache[menuItem], result)
- case menu.SubmenuType:
- result = processSubmenu(menuItem, group)
- }
- C.gtk_menu_shell_append(C.toGtkMenuShell(unsafe.Pointer(parent)), result)
- C.gtk_widget_show(result)
-
- if menuItem.Click != nil {
- handler := C.connectClick(result)
- gtkSignalHandlers[result] = handler
- gtkSignalToMenuItem[result] = menuItem
- }
-
- if menuItem.Disabled {
- C.gtk_widget_set_sensitive(result, 0)
- }
-
- if menuItem.Accelerator != nil {
- key, mods := acceleratorToGTK(menuItem.Accelerator)
- C.addAccelerator(result, group, key, mods)
- }
-}
diff --git a/v2/internal/frontend/desktop/linux/screen.go b/v2/internal/frontend/desktop/linux/screen.go
deleted file mode 100644
index 0a0507425..000000000
--- a/v2/internal/frontend/desktop/linux/screen.go
+++ /dev/null
@@ -1,91 +0,0 @@
-//go:build linux
-// +build linux
-
-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 CFLAGS: -w
-#include
-#include "webkit2/webkit2.h"
-#include "gtk/gtk.h"
-#include "gdk/gdk.h"
-
-typedef struct Screen {
- int isCurrent;
- int isPrimary;
- int height;
- int width;
- int scale;
-} Screen;
-
-int GetNMonitors(GtkWindow *window){
- GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
- GdkDisplay *display = gdk_window_get_display(gdk_window);
- return gdk_display_get_n_monitors(display);
-}
-
-Screen GetNThMonitor(int monitor_num, GtkWindow *window){
- GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
- GdkDisplay *display = gdk_window_get_display(gdk_window);
- GdkMonitor *monitor = gdk_display_get_monitor(display,monitor_num);
- GdkMonitor *currentMonitor = gdk_display_get_monitor_at_window(display,gdk_window);
- Screen screen;
- GdkRectangle geometry;
- gdk_monitor_get_geometry(monitor,&geometry);
- screen.isCurrent = currentMonitor==monitor;
- 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"
-)
-
-type Screen = frontend.Screen
-
-func GetAllScreens(window *C.GtkWindow) ([]Screen, error) {
- if window == nil {
- return nil, errors.New("window is nil, cannot perform screen operations")
- }
- var wg sync.WaitGroup
- var screens []Screen
- wg.Add(1)
- invokeOnMainThread(func() {
- 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)
- }
-
- wg.Done()
- })
- wg.Wait()
- return screens, nil
-}
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
deleted file mode 100644
index 06e0c7824..000000000
--- a/v2/internal/frontend/desktop/linux/webkit2.go
+++ /dev/null
@@ -1,32 +0,0 @@
-//go:build linux
-
-package linux
-
-/*
-#cgo !webkit2_41 pkg-config: webkit2gtk-4.0
-#cgo webkit2_41 pkg-config: webkit2gtk-4.1
-#include "webkit2/webkit2.h"
-*/
-import "C"
-import (
- "fmt"
-
- "github.com/wailsapp/wails/v2/pkg/options"
- "github.com/wailsapp/wails/v2/pkg/options/linux"
-
- "github.com/wailsapp/wails/v2/pkg/assetserver/webview"
-)
-
-func validateWebKit2Version(options *options.App) {
- if C.webkit_get_major_version() == 2 && C.webkit_get_minor_version() >= webview.Webkit2MinMinorVersion {
- return
- }
-
- msg := linux.DefaultMessages()
- if options.Linux != nil && options.Linux.Messages != nil {
- msg = options.Linux.Messages
- }
-
- v := fmt.Sprintf("2.%d.0", webview.Webkit2MinMinorVersion)
- showModalDialogAndExit("WebKit2GTK", fmt.Sprintf(msg.WebKit2GTKMinRequired, v))
-}
diff --git a/v2/internal/frontend/desktop/linux/window.c b/v2/internal/frontend/desktop/linux/window.c
deleted file mode 100644
index 5441db022..000000000
--- a/v2/internal/frontend/desktop/linux/window.c
+++ /dev/null
@@ -1,891 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "window.h"
-
-// These are the x,y,time & button of the last mouse down event
-// It's used for window dragging
-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)
-{
- g_idle_add((GSourceFunc)f, (gpointer)jscallback);
-}
-
-GtkWidget *GTKWIDGET(void *pointer)
-{
- return GTK_WIDGET(pointer);
-}
-
-GtkWindow *GTKWINDOW(void *pointer)
-{
- return GTK_WINDOW(pointer);
-}
-
-GtkContainer *GTKCONTAINER(void *pointer)
-{
- return GTK_CONTAINER(pointer);
-}
-
-GtkBox *GTKBOX(void *pointer)
-{
- return GTK_BOX(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);
-#else
- JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
- JSValueRef value = webkit_javascript_result_get_value(result);
- JSStringRef js = JSValueToStringCopy(context, value, NULL);
- size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
- char *message = g_new(char, messageSize);
- JSStringGetUTF8CString(js, message, messageSize);
- JSStringRelease(js);
-#endif
- processBindingMessage(message, uri);
- g_free(message);
- if (uri) {
- g_free(uri);
- }
-}
-
-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
- GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
- GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
- if (gdk_window == NULL)
- {
- return NULL;
- }
- GdkMonitor *monitor = gdk_display_get_monitor_at_window(display, gdk_window);
-
- return GDK_MONITOR(monitor);
-}
-
-static GdkRectangle getCurrentMonitorGeometry(GtkWindow *window)
-{
- GdkMonitor *monitor = getCurrentMonitor(window);
- GdkRectangle result;
- if (monitor == NULL)
- {
- result.x = result.y = result.height = result.width = -1;
- return result;
- }
-
- // Get the geometry of the monitor
- gdk_monitor_get_geometry(monitor, &result);
- return result;
-}
-
-static int getCurrentMonitorScaleFactor(GtkWindow *window)
-{
- GdkMonitor *monitor = getCurrentMonitor(window);
-
- return gdk_monitor_get_scale_factor(monitor);
-}
-
-// window
-
-ulong SetupInvokeSignal(void *contentManager)
-{
- return g_signal_connect((WebKitUserContentManager *)contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), NULL);
-}
-
-void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len)
-{
- GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
- if (!loader)
- {
- return;
- }
- if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL))
- {
- GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
- if (pixbuf)
- {
- gtk_window_set_icon(window, pixbuf);
- }
- }
- g_object_unref(loader);
-}
-
-void SetWindowTransparency(GtkWidget *widget)
-{
- GdkScreen *screen = gtk_widget_get_screen(widget);
- GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
-
- if (visual != NULL && gdk_screen_is_composited(screen))
- {
- gtk_widget_set_app_paintable(widget, true);
- gtk_widget_set_visual(widget, visual);
- }
-}
-
-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)
-{
- SetTitleArgs *args = (SetTitleArgs *)data;
- gtk_window_set_title(args->window, args->title);
- free((void *)args->title);
- free((void *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-void SetTitle(GtkWindow *window, char *title)
-{
- SetTitleArgs *args = malloc(sizeof(SetTitleArgs));
- args->window = window;
- args->title = title;
- ExecuteOnMainThread(setTitle, (gpointer)args);
-}
-
-static gboolean setPosition(gpointer data)
-{
- SetPositionArgs *args = (SetPositionArgs *)data;
- gtk_window_move((GtkWindow *)args->window, args->x, args->y);
- free(args);
-
- return G_SOURCE_REMOVE;
-}
-
-void SetPosition(void *window, int x, int y)
-{
- GdkRectangle monitorDimensions = getCurrentMonitorGeometry(window);
- if (isNULLRectangle(monitorDimensions))
- {
- return;
- }
- SetPositionArgs *args = malloc(sizeof(SetPositionArgs));
- args->window = window;
- args->x = monitorDimensions.x + x;
- args->y = monitorDimensions.y + y;
- ExecuteOnMainThread(setPosition, (gpointer)args);
-}
-
-void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height)
-{
- GdkGeometry size;
- size.min_width = size.min_height = size.max_width = size.max_height = 0;
-
- GdkRectangle monitorSize = getCurrentMonitorGeometry(window);
- if (isNULLRectangle(monitorSize))
- {
- 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
-static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data)
-{
- // return true to disable the context menu
- return TRUE;
-}
-
-void DisableContextMenu(void *webview)
-{
- // Disable the context menu but propagate the event
- g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL);
-}
-
-static gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void *dummy)
-{
- if (event == NULL)
- {
- xroot = yroot = 0.0f;
- dragTime = -1;
- return FALSE;
- }
- mouseButton = event->button;
- if (event->button == 3)
- {
- return FALSE;
- }
-
- if (event->type == GDK_BUTTON_PRESS && event->button == 1)
- {
- xroot = event->x_root;
- yroot = event->y_root;
- dragTime = event->time;
- }
-
- return FALSE;
-}
-
-static gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, void *dummy)
-{
- if (event == NULL || (event->type == GDK_BUTTON_RELEASE && event->button == 1))
- {
- xroot = yroot = 0.0f;
- dragTime = -1;
- }
- return FALSE;
-}
-
-void ConnectButtons(void *webview)
-{
- g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-press-event", G_CALLBACK(buttonPress), NULL);
- g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-release-event", G_CALLBACK(buttonRelease), NULL);
-}
-
-int IsFullscreen(GtkWidget *widget)
-{
- GdkWindow *gdkwindow = gtk_widget_get_window(widget);
- GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
- return state & GDK_WINDOW_STATE_FULLSCREEN;
-}
-
-int IsMaximised(GtkWidget *widget)
-{
- GdkWindow *gdkwindow = gtk_widget_get_window(widget);
- GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
- return state & GDK_WINDOW_STATE_MAXIMIZED && !(state & GDK_WINDOW_STATE_FULLSCREEN);
-}
-
-int IsMinimised(GtkWidget *widget)
-{
- GdkWindow *gdkwindow = gtk_widget_get_window(widget);
- GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow));
- return state & GDK_WINDOW_STATE_ICONIFIED;
-}
-
-gboolean Center(gpointer data)
-{
- GtkWindow *window = (GtkWindow *)data;
-
- // Get the geometry of the monitor
- GdkRectangle m = getCurrentMonitorGeometry(window);
- if (isNULLRectangle(m))
- {
- return G_SOURCE_REMOVE;
- }
-
- // Get the window width/height
- int windowWidth, windowHeight;
- gtk_window_get_size(window, &windowWidth, &windowHeight);
-
- int newX = ((m.width - windowWidth) / 2) + m.x;
- int newY = ((m.height - windowHeight) / 2) + m.y;
-
- // Place the window at the center of the monitor
- gtk_window_move(window, newX, newY);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean Show(gpointer data)
-{
- gtk_widget_show((GtkWidget *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean Hide(gpointer data)
-{
- gtk_widget_hide((GtkWidget *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean Maximise(gpointer data)
-{
- gtk_window_maximize((GtkWindow *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean UnMaximise(gpointer data)
-{
- gtk_window_unmaximize((GtkWindow *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean Minimise(gpointer data)
-{
- gtk_window_iconify((GtkWindow *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean UnMinimise(gpointer data)
-{
- gtk_window_present((GtkWindow *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean Fullscreen(gpointer data)
-{
- GtkWindow *window = (GtkWindow *)data;
-
- // Get the geometry of the monitor.
- GdkRectangle m = getCurrentMonitorGeometry(window);
- if (isNULLRectangle(m))
- {
- return G_SOURCE_REMOVE;
- }
- int scale = getCurrentMonitorScaleFactor(window);
- SetMinMaxSize(window, 0, 0, m.width * scale, m.height * scale);
-
- gtk_window_fullscreen(window);
-
- return G_SOURCE_REMOVE;
-}
-
-gboolean UnFullscreen(gpointer data)
-{
- gtk_window_unfullscreen((GtkWindow *)data);
-
- return G_SOURCE_REMOVE;
-}
-
-static void webviewLoadChanged(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer data)
-{
- if (load_event == WEBKIT_LOAD_FINISHED)
- {
- processMessage("DomReady");
- }
-}
-
-extern void processURLRequest(void *request);
-
-// This is called when the close button on the window is pressed
-gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void *data)
-{
- processMessage("Q");
- // since we handle the close in processMessage tell GTK to not invoke additional handlers - see:
- // https://docs.gtk.org/gtk3/signal.Widget.delete-event.html
- 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 *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);
- }
- else
- {
- g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(close_button_pressed), NULL);
- }
-
- WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
- webkit_settings_set_user_agent_with_application_details(settings, "wails.io", "");
-
- switch (gpuPolicy)
- {
- case 0:
- webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS);
- break;
- case 1:
- webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
- break;
- case 2:
- webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
- break;
- default:
- webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND);
- }
- return webview;
-}
-
-void DevtoolsEnabled(void *webview, int enabled, bool showInspector)
-{
- WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview));
- gboolean genabled = enabled == 1 ? true : false;
- webkit_settings_set_enable_developer_extras(settings, genabled);
-
- if (genabled && showInspector)
- {
- ShowInspector(webview);
- }
-}
-
-void LoadIndex(void *webview, char *url)
-{
- webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url);
-}
-
-static gboolean startDrag(gpointer data)
-{
- DragOptions *options = (DragOptions *)data;
-
- // Ignore non-toplevel widgets
- GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
- if (!GTK_IS_WINDOW(window))
- {
- free(data);
- return G_SOURCE_REMOVE;
- }
-
- gtk_window_begin_move_drag(options->mainwindow, mouseButton, xroot, yroot, dragTime);
- free(data);
-
- return G_SOURCE_REMOVE;
-}
-
-void StartDrag(void *webview, GtkWindow *mainwindow)
-{
- DragOptions *data = malloc(sizeof(DragOptions));
- data->webview = webview;
- data->mainwindow = mainwindow;
- ExecuteOnMainThread(startDrag, (gpointer)data);
-}
-
-static gboolean startResize(gpointer data)
-{
- ResizeOptions *options = (ResizeOptions *)data;
-
- // Ignore non-toplevel widgets
- GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview));
- if (!GTK_IS_WINDOW(window))
- {
- free(data);
- return G_SOURCE_REMOVE;
- }
-
- gtk_window_begin_resize_drag(options->mainwindow, options->edge, mouseButton, xroot, yroot, dragTime);
- free(data);
-
- return G_SOURCE_REMOVE;
-}
-
-void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge)
-{
- ResizeOptions *data = malloc(sizeof(ResizeOptions));
- data->webview = webview;
- data->mainwindow = mainwindow;
- data->edge = edge;
- ExecuteOnMainThread(startResize, (gpointer)data);
-}
-
-void ExecuteJS(void *data)
-{
- struct JSCallback *js = data;
- webkit_web_view_run_javascript(js->webview, js->script, NULL, NULL, NULL);
- free(js->script);
-}
-
-void extern processMessageDialogResult(char *);
-
-void MessageDialog(void *data)
-{
- GtkDialogFlags flags;
- GtkMessageType messageType;
- MessageDialogOptions *options = (MessageDialogOptions *)data;
- if (options->messageType == 0)
- {
- messageType = GTK_MESSAGE_INFO;
- flags = GTK_BUTTONS_OK;
- }
- else if (options->messageType == 1)
- {
- messageType = GTK_MESSAGE_ERROR;
- flags = GTK_BUTTONS_OK;
- }
- else if (options->messageType == 2)
- {
- messageType = GTK_MESSAGE_QUESTION;
- flags = GTK_BUTTONS_YES_NO;
- }
- else
- {
- messageType = GTK_MESSAGE_WARNING;
- flags = GTK_BUTTONS_OK;
- }
-
- GtkWidget *dialog;
- dialog = gtk_message_dialog_new(GTK_WINDOW(options->window),
- GTK_DIALOG_DESTROY_WITH_PARENT,
- messageType,
- flags,
- options->message, NULL);
- gtk_window_set_title(GTK_WINDOW(dialog), options->title);
- GtkResponseType result = gtk_dialog_run(GTK_DIALOG(dialog));
- if (result == GTK_RESPONSE_YES)
- {
- processMessageDialogResult("Yes");
- }
- else if (result == GTK_RESPONSE_NO)
- {
- processMessageDialogResult("No");
- }
- else if (result == GTK_RESPONSE_OK)
- {
- processMessageDialogResult("OK");
- }
- else if (result == GTK_RESPONSE_CANCEL)
- {
- processMessageDialogResult("Cancel");
- }
- else
- {
- processMessageDialogResult("");
- }
-
- gtk_widget_destroy(dialog);
- free(options->title);
- free(options->message);
-}
-
-void extern processOpenFileResult(void *);
-
-GtkFileFilter **AllocFileFilterArray(size_t ln)
-{
- return (GtkFileFilter **)malloc(ln * sizeof(GtkFileFilter *));
-}
-
-void freeFileFilterArray(GtkFileFilter **filters)
-{
- free(filters);
-}
-
-void Opendialog(void *data)
-{
- struct OpenFileDialogOptions *options = data;
- char *label = "_Open";
- if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE)
- {
- label = "_Save";
- }
- GtkWidget *dlgWidget = gtk_file_chooser_dialog_new(options->title, options->window, options->action,
- "_Cancel", GTK_RESPONSE_CANCEL,
- label, GTK_RESPONSE_ACCEPT,
- NULL);
-
- GtkFileChooser *fc = GTK_FILE_CHOOSER(dlgWidget);
- // filters
- if (options->filters != 0)
- {
- int index = 0;
- GtkFileFilter *thisFilter;
- while (options->filters[index] != NULL)
- {
- thisFilter = options->filters[index];
- gtk_file_chooser_add_filter(fc, thisFilter);
- index++;
- }
- }
-
- gtk_file_chooser_set_local_only(fc, FALSE);
-
- if (options->multipleFiles == 1)
- {
- gtk_file_chooser_set_select_multiple(fc, TRUE);
- }
- gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);
- if (options->createDirectories == 1)
- {
- gtk_file_chooser_set_create_folders(fc, TRUE);
- }
- if (options->showHiddenFiles == 1)
- {
- gtk_file_chooser_set_show_hidden(fc, TRUE);
- }
-
- if (options->defaultDirectory != NULL)
- {
- gtk_file_chooser_set_current_folder(fc, options->defaultDirectory);
- free(options->defaultDirectory);
- }
-
- if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE)
- {
- if (options->defaultFilename != NULL)
- {
- gtk_file_chooser_set_current_name(fc, options->defaultFilename);
- free(options->defaultFilename);
- }
- }
-
- gint response = gtk_dialog_run(GTK_DIALOG(dlgWidget));
-
- // Max 1024 files to select
- char **result = calloc(1024, sizeof(char *));
- int resultIndex = 0;
-
- if (response == GTK_RESPONSE_ACCEPT)
- {
- GSList *filenames = gtk_file_chooser_get_filenames(fc);
- GSList *iter = filenames;
- while (iter)
- {
- result[resultIndex++] = (char *)iter->data;
- iter = g_slist_next(iter);
- if (resultIndex == 1024)
- {
- break;
- }
- }
- processOpenFileResult(result);
- iter = filenames;
- while (iter)
- {
- g_free(iter->data);
- iter = g_slist_next(iter);
- }
- }
- else
- {
- processOpenFileResult(result);
- }
- free(result);
-
- // Release filters
- if (options->filters != NULL)
- {
- int index = 0;
- GtkFileFilter *thisFilter;
- while (options->filters[index] != 0)
- {
- thisFilter = options->filters[index];
- g_object_unref(thisFilter);
- index++;
- }
- freeFileFilterArray(options->filters);
- }
- gtk_widget_destroy(dlgWidget);
- free(options->title);
-}
-
-GtkFileFilter *newFileFilter()
-{
- GtkFileFilter *result = gtk_file_filter_new();
- 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
deleted file mode 100644
index 0bf5ac51d..000000000
--- a/v2/internal/frontend/desktop/linux/window.go
+++ /dev/null
@@ -1,479 +0,0 @@
-//go:build linux
-// +build linux
-
-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
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include "window.h"
-
-*/
-import "C"
-import (
- "log"
- "strings"
- "sync"
- "unsafe"
-
- "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 {
- if input {
- return C.gboolean(1)
- }
- return C.gboolean(0)
-}
-
-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
-}
-
-func bool2Cint(value bool) C.int {
- if value {
- return C.int(1)
- }
- return C.int(0)
-}
-
-func NewWindow(appoptions *options.App, debug bool, devtoolsEnabled bool) *Window {
- validateWebKit2Version(appoptions)
-
- result := &Window{
- appoptions: appoptions,
- debug: debug,
- devtoolsEnabled: devtoolsEnabled,
- 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)
-
- result.contentManager = unsafe.Pointer(C.webkit_user_content_manager_new())
- external := C.CString("external")
- defer C.free(unsafe.Pointer(external))
- C.webkit_user_content_manager_register_script_message_handler(result.cWebKitUserContentManager(), external)
- C.SetupInvokeSignal(result.contentManager)
-
- 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(
- result.contentManager,
- 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) {
- C.DisableContextMenu(unsafe.Pointer(webview))
- }
-
- // Set background colour
- RGBA := appoptions.BackgroundColour
- result.SetBackgroundColour(RGBA.R, RGBA.G, RGBA.B, RGBA.A)
-
- // Setup window
- result.SetKeepAbove(appoptions.AlwaysOnTop)
- result.SetResizable(!appoptions.DisableResize)
- result.SetDefaultSize(appoptions.Width, appoptions.Height)
- result.SetDecorated(!appoptions.Frameless)
- result.SetTitle(appoptions.Title)
- result.SetMinSize(appoptions.MinWidth, appoptions.MinHeight)
- result.SetMaxSize(appoptions.MaxWidth, appoptions.MaxHeight)
- if appoptions.Linux != nil {
- if appoptions.Linux.Icon != nil {
- result.SetWindowIcon(appoptions.Linux.Icon)
- }
- if appoptions.Linux.WindowIsTranslucent {
- C.SetWindowTransparency(gtkWindow)
- }
- }
-
- // Menu
- result.SetApplicationMenu(appoptions.Menu)
-
- return result
-}
-
-func (w *Window) asGTKWidget() *C.GtkWidget {
- return C.GTKWIDGET(w.gtkWindow)
-}
-
-func (w *Window) asGTKWindow() *C.GtkWindow {
- return C.GTKWINDOW(w.gtkWindow)
-}
-
-func (w *Window) asGTKContainer() *C.GtkContainer {
- return C.GTKCONTAINER(w.gtkWindow)
-}
-
-func (w *Window) cWebKitUserContentManager() *C.WebKitUserContentManager {
- return (*C.WebKitUserContentManager)(w.contentManager)
-}
-
-func (w *Window) Fullscreen() {
- C.ExecuteOnMainThread(C.Fullscreen, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) UnFullscreen() {
- if !w.IsFullScreen() {
- return
- }
- C.ExecuteOnMainThread(C.UnFullscreen, C.gpointer(w.asGTKWindow()))
- w.SetMinSize(w.minWidth, w.minHeight)
- w.SetMaxSize(w.maxWidth, w.maxHeight)
-}
-
-func (w *Window) Destroy() {
- C.gtk_widget_destroy(w.asGTKWidget())
- C.g_object_unref(C.gpointer(w.gtkWindow))
-}
-
-func (w *Window) Close() {
- C.gtk_window_close(w.asGTKWindow())
-}
-
-func (w *Window) Center() {
- C.ExecuteOnMainThread(C.Center, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) SetPosition(x int, y int) {
- invokeOnMainThread(func() {
- C.SetPosition(unsafe.Pointer(w.asGTKWindow()), C.int(x), C.int(y))
- })
-}
-
-func (w *Window) Size() (int, int) {
- var width, height C.int
- var wg sync.WaitGroup
- wg.Add(1)
- invokeOnMainThread(func() {
- C.gtk_window_get_size(w.asGTKWindow(), &width, &height)
- wg.Done()
- })
- wg.Wait()
- return int(width), int(height)
-}
-
-func (w *Window) GetPosition() (int, int) {
- var width, height C.int
- var wg sync.WaitGroup
- wg.Add(1)
- invokeOnMainThread(func() {
- C.gtk_window_get_position(w.asGTKWindow(), &width, &height)
- wg.Done()
- })
- wg.Wait()
- return int(width), int(height)
-}
-
-func (w *Window) SetMaxSize(maxWidth int, maxHeight int) {
- w.maxHeight = maxHeight
- w.maxWidth = maxWidth
- invokeOnMainThread(func() {
- C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
- })
-}
-
-func (w *Window) SetMinSize(minWidth int, minHeight int) {
- w.minHeight = minHeight
- w.minWidth = minWidth
- invokeOnMainThread(func() {
- C.SetMinMaxSize(w.asGTKWindow(), C.int(w.minWidth), C.int(w.minHeight), C.int(w.maxWidth), C.int(w.maxHeight))
- })
-}
-
-func (w *Window) Show() {
- C.ExecuteOnMainThread(C.Show, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) Hide() {
- C.ExecuteOnMainThread(C.Hide, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) Maximise() {
- C.ExecuteOnMainThread(C.Maximise, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) UnMaximise() {
- C.ExecuteOnMainThread(C.UnMaximise, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) Minimise() {
- C.ExecuteOnMainThread(C.Minimise, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) UnMinimise() {
- C.ExecuteOnMainThread(C.UnMinimise, C.gpointer(w.asGTKWindow()))
-}
-
-func (w *Window) IsFullScreen() bool {
- result := C.IsFullscreen(w.asGTKWidget())
- if result != 0 {
- return true
- }
- return false
-}
-
-func (w *Window) IsMaximised() bool {
- result := C.IsMaximised(w.asGTKWidget())
- return result > 0
-}
-
-func (w *Window) IsMinimised() bool {
- result := C.IsMinimised(w.asGTKWidget())
- return result > 0
-}
-
-func (w *Window) IsNormal() bool {
- return !w.IsMaximised() && !w.IsMinimised() && !w.IsFullScreen()
-}
-
-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),
- }
- invokeOnMainThread(func() { C.SetBackgroundColour(unsafe.Pointer(&data)) })
-
-}
-
-func (w *Window) SetWindowIcon(icon []byte) {
- if len(icon) == 0 {
- return
- }
- C.SetWindowIcon(w.asGTKWindow(), (*C.guchar)(&icon[0]), (C.gsize)(len(icon)))
-}
-
-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)
- _url := C.CString(url)
- C.LoadIndex(w.webview, _url)
- defer C.free(unsafe.Pointer(_url))
- if w.appoptions.StartHidden {
- w.Hide()
- }
- C.gtk_widget_show_all(w.asGTKWidget())
- w.Center()
- switch w.appoptions.WindowStartState {
- case options.Fullscreen:
- w.Fullscreen()
- case options.Minimised:
- w.Minimise()
- case options.Maximised:
- w.Maximise()
- }
-}
-
-func (w *Window) SetKeepAbove(top bool) {
- C.gtk_window_set_keep_above(w.asGTKWindow(), gtkBool(top))
-}
-
-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))
-}
-
-func (w *Window) SetDecorated(frameless bool) {
- C.gtk_window_set_decorated(w.asGTKWindow(), gtkBool(frameless))
-}
-
-func (w *Window) SetTitle(title string) {
- C.SetTitle(w.asGTKWindow(), C.CString(title))
-}
-
-func (w *Window) ExecJS(js string) {
- jscallback := C.JSCallback{
- webview: w.webview,
- script: C.CString(js),
- }
- invokeOnMainThread(func() { C.ExecuteJS(unsafe.Pointer(&jscallback)) })
-}
-
-func (w *Window) StartDrag() {
- C.StartDrag(w.webview, w.asGTKWindow())
-}
-
-func (w *Window) StartResize(edge uintptr) {
- C.StartResize(w.webview, w.asGTKWindow(), C.GdkWindowEdge(edge))
-}
-
-func (w *Window) Quit() {
- C.gtk_main_quit()
-}
-
-func (w *Window) OpenFileDialog(dialogOptions frontend.OpenDialogOptions, multipleFiles int, action C.GtkFileChooserAction) {
-
- data := C.OpenFileDialogOptions{
- window: w.asGTKWindow(),
- title: C.CString(dialogOptions.Title),
- multipleFiles: C.int(multipleFiles),
- action: action,
- }
-
- if len(dialogOptions.Filters) > 0 {
- // Create filter array
- mem := NewCalloc()
- arraySize := len(dialogOptions.Filters) + 1
- data.filters = C.AllocFileFilterArray((C.size_t)(arraySize))
- filters := unsafe.Slice((**C.struct__GtkFileFilter)(unsafe.Pointer(data.filters)), arraySize)
- for index, filter := range dialogOptions.Filters {
- thisFilter := C.gtk_file_filter_new()
- C.g_object_ref(C.gpointer(thisFilter))
- if filter.DisplayName != "" {
- cName := mem.String(filter.DisplayName)
- C.gtk_file_filter_set_name(thisFilter, cName)
- }
- if filter.Pattern != "" {
- for _, thisPattern := range strings.Split(filter.Pattern, ";") {
- cThisPattern := mem.String(thisPattern)
- C.gtk_file_filter_add_pattern(thisFilter, cThisPattern)
- }
- }
- // Add filter to array
- filters[index] = thisFilter
- }
- mem.Free()
- filters[arraySize-1] = nil
- }
-
- if dialogOptions.CanCreateDirectories {
- data.createDirectories = C.int(1)
- }
-
- if dialogOptions.ShowHiddenFiles {
- data.showHiddenFiles = C.int(1)
- }
-
- if dialogOptions.DefaultFilename != "" {
- data.defaultFilename = C.CString(dialogOptions.DefaultFilename)
- }
-
- if dialogOptions.DefaultDirectory != "" {
- data.defaultDirectory = C.CString(dialogOptions.DefaultDirectory)
- }
-
- invokeOnMainThread(func() { C.Opendialog(unsafe.Pointer(&data)) })
-}
-
-func (w *Window) MessageDialog(dialogOptions frontend.MessageDialogOptions) {
-
- data := C.MessageDialogOptions{
- window: w.gtkWindow,
- title: C.CString(dialogOptions.Title),
- message: C.CString(dialogOptions.Message),
- }
- switch dialogOptions.Type {
- case frontend.InfoDialog:
- data.messageType = C.int(0)
- case frontend.ErrorDialog:
- data.messageType = C.int(1)
- case frontend.QuestionDialog:
- data.messageType = C.int(2)
- case frontend.WarningDialog:
- data.messageType = C.int(3)
- }
- invokeOnMainThread(func() { C.MessageDialog(unsafe.Pointer(&data)) })
-}
-
-func (w *Window) ToggleMaximise() {
- if w.IsMaximised() {
- w.UnMaximise()
- } else {
- w.Maximise()
- }
-}
-
-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() {
- data := C.MessageDialogOptions{
- title: C.CString(title),
- message: C.CString(message),
- messageType: C.int(1),
- }
-
- C.MessageDialog(unsafe.Pointer(&data))
- }()
-
- <-messageDialogResult
- log.Fatal(message)
-}
diff --git a/v2/internal/frontend/desktop/linux/window.h b/v2/internal/frontend/desktop/linux/window.h
deleted file mode 100644
index 04410959a..000000000
--- a/v2/internal/frontend/desktop/linux/window.h
+++ /dev/null
@@ -1,128 +0,0 @@
-#ifndef window_h
-#define window_h
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-typedef struct DragOptions
-{
- void *webview;
- GtkWindow *mainwindow;
-} DragOptions;
-
-typedef struct ResizeOptions
-{
- void *webview;
- GtkWindow *mainwindow;
- GdkWindowEdge edge;
-} ResizeOptions;
-
-typedef struct JSCallback
-{
- void *webview;
- char *script;
-} JSCallback;
-
-typedef struct MessageDialogOptions
-{
- void *window;
- char *title;
- char *message;
- int messageType;
-} MessageDialogOptions;
-
-typedef struct OpenFileDialogOptions
-{
- GtkWindow *window;
- char *title;
- char *defaultFilename;
- char *defaultDirectory;
- int createDirectories;
- int multipleFiles;
- int showHiddenFiles;
- GtkFileChooserAction action;
- GtkFileFilter **filters;
-} OpenFileDialogOptions;
-
-typedef struct RGBAOptions
-{
- uint8_t r;
- uint8_t g;
- uint8_t b;
- uint8_t a;
- void *webview;
- void *webviewBox;
- gboolean windowIsTranslucent;
-} RGBAOptions;
-
-typedef struct SetTitleArgs
-{
- GtkWindow *window;
- char *title;
-} SetTitleArgs;
-
-typedef struct SetPositionArgs
-{
- int x;
- int y;
- void *window;
-} SetPositionArgs;
-
-void ExecuteOnMainThread(void *f, gpointer jscallback);
-
-GtkWidget *GTKWIDGET(void *pointer);
-GtkWindow *GTKWINDOW(void *pointer);
-GtkContainer *GTKCONTAINER(void *pointer);
-GtkBox *GTKBOX(void *pointer);
-
-// window
-ulong SetupInvokeSignal(void *contentManager);
-
-void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len);
-void SetWindowTransparency(GtkWidget *widget);
-void SetBackgroundColour(void *data);
-void SetTitle(GtkWindow *window, char *title);
-void SetPosition(void *window, int x, int y);
-void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height);
-void DisableContextMenu(void *webview);
-void ConnectButtons(void *webview);
-
-int IsFullscreen(GtkWidget *widget);
-int IsMaximised(GtkWidget *widget);
-int IsMinimised(GtkWidget *widget);
-
-gboolean Center(gpointer data);
-gboolean Show(gpointer data);
-gboolean Hide(gpointer data);
-gboolean Maximise(gpointer data);
-gboolean UnMaximise(gpointer data);
-gboolean Minimise(gpointer data);
-gboolean UnMinimise(gpointer data);
-gboolean Fullscreen(gpointer data);
-gboolean UnFullscreen(gpointer data);
-
-// WebView
-GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy, int disableWebViewDragAndDrop, int enableDragAndDrop);
-void LoadIndex(void *webview, char *url);
-void DevtoolsEnabled(void *webview, int enabled, bool showInspector);
-void ExecuteJS(void *data);
-
-// Drag
-void StartDrag(void *webview, GtkWindow *mainwindow);
-void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge);
-
-// Dialog
-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
deleted file mode 100644
index 13d037b14..000000000
--- a/v2/internal/frontend/desktop/windows/browser.go
+++ /dev/null
@@ -1,43 +0,0 @@
-//go:build windows
-// +build windows
-
-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
- }
-
- // 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)
-}
diff --git a/v2/internal/frontend/desktop/windows/clipboard.go b/v2/internal/frontend/desktop/windows/clipboard.go
deleted file mode 100644
index 975fa8e7c..000000000
--- a/v2/internal/frontend/desktop/windows/clipboard.go
+++ /dev/null
@@ -1,16 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
-)
-
-func (f *Frontend) ClipboardGetText() (string, error) {
- return win32.GetClipboardText()
-}
-
-func (f *Frontend) ClipboardSetText(text string) error {
- return win32.SetClipboardText(text)
-}
diff --git a/v2/internal/frontend/desktop/windows/dialog.go b/v2/internal/frontend/desktop/windows/dialog.go
deleted file mode 100644
index 573325886..000000000
--- a/v2/internal/frontend/desktop/windows/dialog.go
+++ /dev/null
@@ -1,210 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "path/filepath"
- "strings"
- "syscall"
-
- "github.com/wailsapp/wails/v2/internal/frontend"
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
- "github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
- "golang.org/x/sys/windows"
-)
-
-func (f *Frontend) getHandleForDialog() w32.HWND {
- if f.mainWindow.IsVisible() {
- return f.mainWindow.Handle()
- }
- return 0
-}
-
-func getDefaultFolder(folder string) (string, error) {
- if folder == "" {
- return "", nil
- }
- return filepath.Abs(folder)
-}
-
-// OpenDirectoryDialog prompts the user to select a directory
-func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
-
- defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
- if err != nil {
- return "", err
- }
-
- config := cfd.DialogConfig{
- Title: options.Title,
- Role: "PickFolder",
- Folder: defaultFolder,
- }
-
- result, err := f.showCfdDialog(
- func() (cfd.Dialog, error) {
- return cfd.NewSelectFolderDialog(config)
- }, false)
-
- if err != nil && err != cfd.ErrCancelled {
- return "", err
- }
- return result.(string), nil
-}
-
-// OpenFileDialog prompts the user to select a file
-func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
- defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
- if err != nil {
- return "", err
- }
-
- config := cfd.DialogConfig{
- Folder: defaultFolder,
- FileFilters: convertFilters(options.Filters),
- FileName: options.DefaultFilename,
- Title: options.Title,
- }
-
- result, err := f.showCfdDialog(
- func() (cfd.Dialog, error) {
- return cfd.NewOpenFileDialog(config)
- }, false)
-
- if err != nil && err != cfd.ErrCancelled {
- return "", err
- }
- return result.(string), nil
-}
-
-// OpenMultipleFilesDialog prompts the user to select a file
-func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
-
- defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
- if err != nil {
- return nil, err
- }
-
- config := cfd.DialogConfig{
- Title: options.Title,
- Role: "OpenMultipleFiles",
- FileFilters: convertFilters(options.Filters),
- FileName: options.DefaultFilename,
- Folder: defaultFolder,
- }
-
- result, err := f.showCfdDialog(
- func() (cfd.Dialog, error) {
- return cfd.NewOpenMultipleFilesDialog(config)
- }, true)
-
- if err != nil && err != cfd.ErrCancelled {
- return nil, err
- }
- return result.([]string), nil
-}
-
-// SaveFileDialog prompts the user to select a file
-func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
-
- defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
- if err != nil {
- return "", err
- }
-
- config := cfd.DialogConfig{
- Title: options.Title,
- Role: "SaveFile",
- FileFilters: convertFilters(options.Filters),
- FileName: options.DefaultFilename,
- 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 {
- return "", err
- }
- return result.(string), nil
-}
-
-func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
- return invokeSync(f.mainWindow, func() (any, error) {
- dlg, err := newDlg()
- if err != nil {
- return nil, err
- }
- defer func() {
- err := dlg.Release()
- if err != nil {
- println("ERROR: Unable to release dialog:", err.Error())
- }
- }()
-
- dlg.SetParentWindowHandle(f.getHandleForDialog())
- if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
- return multi.ShowAndGetResults()
- }
- return dlg.ShowAndGetResult()
- })
-}
-
-func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
- var flags uint32
-
- switch options.Type {
- case frontend.InfoDialog:
- flags = windows.MB_OK | windows.MB_ICONINFORMATION
- case frontend.ErrorDialog:
- flags = windows.MB_ICONERROR | windows.MB_OK
- case frontend.QuestionDialog:
- flags = windows.MB_YESNO
- if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" {
- flags |= windows.MB_DEFBUTTON2
- }
- case frontend.WarningDialog:
- flags = windows.MB_OK | windows.MB_ICONWARNING
- }
-
- return flags
-}
-
-// MessageDialog show a message dialog to the user
-func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
-
- title, err := syscall.UTF16PtrFromString(options.Title)
- if err != nil {
- return "", err
- }
- message, err := syscall.UTF16PtrFromString(options.Message)
- if err != nil {
- return "", err
- }
-
- flags := calculateMessageDialogFlags(options)
-
- button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL)
- // This maps MessageBox return values to strings
- responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
- result := "Error"
- if int(button) < len(responses) {
- result = responses[button]
- }
- return result, nil
-}
-
-func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter {
- var result []cfd.FileFilter
- for _, filter := range filters {
- result = append(result, cfd.FileFilter(filter))
- }
- return result
-}
diff --git a/v2/internal/frontend/desktop/windows/dialog_test.go b/v2/internal/frontend/desktop/windows/dialog_test.go
deleted file mode 100644
index e91058e92..000000000
--- a/v2/internal/frontend/desktop/windows/dialog_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-//go:build windows
-
-package windows
-
-import (
- "testing"
-
- "github.com/wailsapp/wails/v2/internal/frontend"
- "golang.org/x/sys/windows"
-)
-
-func Test_calculateMessageDialogFlags(t *testing.T) {
- tests := []struct {
- name string
- options frontend.MessageDialogOptions
- want uint32
- }{
- {
- name: "Test Info Dialog",
- options: frontend.MessageDialogOptions{
- Type: frontend.InfoDialog,
- },
- want: windows.MB_OK | windows.MB_ICONINFORMATION,
- },
- {
- name: "Test Error Dialog",
- options: frontend.MessageDialogOptions{
- Type: frontend.ErrorDialog,
- },
- want: windows.MB_ICONERROR | windows.MB_OK,
- },
- {
- name: "Test Question Dialog",
- options: frontend.MessageDialogOptions{
- Type: frontend.QuestionDialog,
- },
- want: windows.MB_YESNO,
- },
- {
- name: "Test Question Dialog with default cancel",
- options: frontend.MessageDialogOptions{
- Type: frontend.QuestionDialog,
- DefaultButton: "No",
- },
- want: windows.MB_YESNO | windows.MB_DEFBUTTON2,
- },
- {
- name: "Test Question Dialog with default cancel (lowercase)",
- options: frontend.MessageDialogOptions{
- Type: frontend.QuestionDialog,
- DefaultButton: "no",
- },
- want: windows.MB_YESNO | windows.MB_DEFBUTTON2,
- },
- {
- name: "Test Warning Dialog",
- options: frontend.MessageDialogOptions{
- Type: frontend.WarningDialog,
- },
- want: windows.MB_OK | windows.MB_ICONWARNING,
- },
- {
- name: "Test Error Dialog",
- options: frontend.MessageDialogOptions{
- Type: frontend.ErrorDialog,
- },
- want: windows.MB_ICONERROR | windows.MB_OK,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := calculateMessageDialogFlags(tt.options); got != tt.want {
- t.Errorf("calculateMessageDialogFlags() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go
deleted file mode 100644
index 5df13ed98..000000000
--- a/v2/internal/frontend/desktop/windows/frontend.go
+++ /dev/null
@@ -1,1004 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "log"
- "net"
- "net/url"
- "os"
- "runtime"
- "strings"
- "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/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 {
-
- // Context
- ctx context.Context
-
- frontendOptions *options.App
- logger *logger.Logger
- chromium *edge.Chromium
- debug bool
- devtoolsEnabled bool
-
- // Assets
- assets *assetserver.AssetServer
- startURL *url.URL
-
- // main window handle
- mainWindow *Window
- bindings *binding.Bindings
- dispatcher frontend.Dispatcher
-
- hasStarted bool
-
- originValidator *originvalidator.OriginValidator
-
- // Windows build number
- versionInfo *operatingsystem.WindowsVersionInfo
- resizeDebouncer func(f func())
-}
-
-func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
-
- // 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,
- bindings: appBindings,
- dispatcher: dispatcher,
- ctx: ctx,
- versionInfo: versionInfo,
- }
-
- if appoptions.Windows != nil {
- if appoptions.Windows.ResizeDebounceMS > 0 {
- result.resizeDebouncer = debounce.New(time.Duration(appoptions.Windows.ResizeDebounceMS) * time.Millisecond)
- }
- }
-
- // 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
- var err error
- if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
- bindings, err = appBindings.ToJSON()
- if err != nil {
- log.Fatal(err)
- }
- } else {
- appBindings.DB().UpdateObfuscatedCallMap()
- }
-
- assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
- if err != nil {
- log.Fatal(err)
- }
- result.assets = assets
-
- go result.startSecondInstanceProcessor()
-
- return result
-}
-
-func (f *Frontend) WindowReload() {
- f.ExecJS("runtime.WindowReload();")
-}
-
-func (f *Frontend) WindowSetSystemDefaultTheme() {
- f.mainWindow.SetTheme(windows.SystemDefault)
-}
-
-func (f *Frontend) WindowSetLightTheme() {
- f.mainWindow.SetTheme(windows.Light)
-}
-
-func (f *Frontend) WindowSetDarkTheme() {
- f.mainWindow.SetTheme(windows.Dark)
-}
-
-func (f *Frontend) Run(ctx context.Context) error {
- f.ctx = ctx
-
- 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()
-
- mainWindow.OnSize().Bind(func(arg *winc.Event) {
- if f.frontendOptions.Frameless {
- // If the window is frameless and we are minimizing, then we need to suppress the Resize on the
- // WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong
- // size during the restore animation and only fully renders when the animation is done. This highly
- // depends on the content in the WebView, see https://github.com/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() {
- f.chromium.Resize()
- })
- })
- } else {
- f.chromium.Resize()
- }
- })
-
- mainWindow.OnClose().Bind(func(arg *winc.Event) {
- if f.frontendOptions.HideWindowOnClose {
- f.WindowHide()
- } else {
- f.Quit()
- }
- })
-
- go func() {
- if f.frontendOptions.OnStartup != nil {
- f.frontendOptions.OnStartup(f.ctx)
- }
- }()
- mainWindow.UpdateTheme()
- return nil
-}
-
-func (f *Frontend) WindowClose() {
- if f.mainWindow != nil {
- f.mainWindow.Close()
- }
-}
-
-func (f *Frontend) RunMainLoop() {
- _ = winc.RunMainLoop()
-}
-
-func (f *Frontend) WindowCenter() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.Center()
-}
-
-func (f *Frontend) WindowSetAlwaysOnTop(b bool) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.SetAlwaysOnTop(b)
-}
-
-func (f *Frontend) WindowSetPosition(x, y int) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.SetPos(x, y)
-}
-func (f *Frontend) WindowGetPosition() (int, int) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- return f.mainWindow.Pos()
-}
-
-func (f *Frontend) WindowSetSize(width, height int) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.SetSize(width, height)
-}
-
-func (f *Frontend) WindowGetSize() (int, int) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- return f.mainWindow.Size()
-}
-
-func (f *Frontend) WindowSetTitle(title string) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.SetText(title)
-}
-
-func (f *Frontend) WindowFullscreen() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
- f.ExecJS("window.wails.flags.enableResize = false;")
- }
- f.mainWindow.Fullscreen()
-}
-
-func (f *Frontend) WindowReloadApp() {
- f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
-}
-
-func (f *Frontend) WindowUnfullscreen() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
- f.ExecJS("window.wails.flags.enableResize = true;")
- }
- f.mainWindow.UnFullscreen()
-}
-
-func (f *Frontend) WindowShow() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.ShowWindow()
-}
-
-func (f *Frontend) WindowHide() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.Hide()
-}
-
-func (f *Frontend) WindowMaximise() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if f.hasStarted {
- if !f.frontendOptions.DisableResize {
- f.mainWindow.Maximise()
- }
- } else {
- f.frontendOptions.WindowStartState = options.Maximised
- }
-}
-
-func (f *Frontend) WindowToggleMaximise() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if !f.hasStarted {
- return
- }
- if f.mainWindow.IsMaximised() {
- f.WindowUnmaximise()
- } else {
- f.WindowMaximise()
- }
-}
-
-func (f *Frontend) WindowUnmaximise() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if f.mainWindow.Form.IsFullScreen() {
- return
- }
- f.mainWindow.Restore()
-}
-
-func (f *Frontend) WindowMinimise() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if f.hasStarted {
- f.mainWindow.Minimise()
- } else {
- f.frontendOptions.WindowStartState = options.Minimised
- }
-}
-
-func (f *Frontend) WindowUnminimise() {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if f.mainWindow.Form.IsFullScreen() {
- return
- }
- f.mainWindow.Restore()
-}
-
-func (f *Frontend) WindowSetMinSize(width int, height int) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.SetMinSize(width, height)
-}
-func (f *Frontend) WindowSetMaxSize(width int, height int) {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- f.mainWindow.SetMaxSize(width, height)
-}
-
-func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
- if col == nil {
- return
- }
-
- f.mainWindow.Invoke(func() {
- win32.SetBackgroundColour(f.mainWindow.Handle(), col.R, col.G, col.B)
-
- controller := f.chromium.GetController()
- controller2 := controller.GetICoreWebView2Controller2()
-
- backgroundCol := edge.COREWEBVIEW2_COLOR{
- A: col.A,
- R: col.R,
- G: col.G,
- B: col.B,
- }
-
- // WebView2 only has 0 and 255 as valid values.
- if backgroundCol.A > 0 && backgroundCol.A < 255 {
- backgroundCol.A = 255
- }
-
- if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent {
- backgroundCol.A = 0
- }
-
- err := controller2.PutDefaultBackgroundColor(backgroundCol)
- if err != nil {
- log.Fatal(err)
- }
- })
-
-}
-
-func (f *Frontend) ScreenGetAll() ([]Screen, error) {
- var wg sync.WaitGroup
- wg.Add(1)
- screens := []Screen{}
- err := error(nil)
- f.mainWindow.Invoke(func() {
- screens, err = GetAllScreens(f.mainWindow.Handle())
- wg.Done()
-
- })
- wg.Wait()
- return screens, err
-}
-
-func (f *Frontend) Show() {
- f.mainWindow.Show()
-}
-
-func (f *Frontend) Hide() {
- f.mainWindow.Hide()
-}
-
-func (f *Frontend) WindowIsMaximised() bool {
- return f.mainWindow.IsMaximised()
-}
-
-func (f *Frontend) WindowIsMinimised() bool {
- return f.mainWindow.IsMinimised()
-}
-
-func (f *Frontend) WindowIsNormal() bool {
- return f.mainWindow.IsNormal()
-}
-
-func (f *Frontend) WindowIsFullscreen() bool {
- return f.mainWindow.IsFullScreen()
-}
-
-func (f *Frontend) Quit() {
- if f.frontendOptions.OnBeforeClose != nil && f.frontendOptions.OnBeforeClose(f.ctx) {
- return
- }
- // Exit must be called on the Main-Thread. It calls PostQuitMessage which sends the WM_QUIT message to the thread's
- // message queue and our message queue runs on the Main-Thread.
- f.mainWindow.Invoke(winc.Exit)
-}
-
-func (f *Frontend) WindowPrint() {
- f.ExecJS("window.print();")
-}
-
-func (f *Frontend) setupChromium() {
- chromium := f.chromium
-
- disableFeatues := []string{}
- if !f.frontendOptions.EnableFraudulentWebsiteDetection {
- disableFeatues = append(disableFeatues, "msSmartScreenProtection")
- }
-
- if opts := f.frontendOptions.Windows; opts != nil {
- chromium.DataPath = opts.WebviewUserDataPath
- chromium.BrowserPath = opts.WebviewBrowserPath
-
- if opts.WebviewGpuIsDisabled {
- chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu")
- }
- if opts.WebviewDisableRendererCodeIntegrity {
- disableFeatues = append(disableFeatues, "RendererCodeIntegrity")
- }
- }
-
- if len(disableFeatues) > 0 {
- arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ","))
- 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
- }
- chromium.ProcessFailedCallback = func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2ProcessFailedEventArgs) {
- kind, err := args.GetProcessFailedKind()
- if err != nil {
- f.logger.Error("GetProcessFailedKind: %s", err)
- return
- }
-
- f.logger.Error("WebVie2wProcess failed with kind %d", kind)
- switch kind {
- case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED:
- // => The app has to recreate a new WebView to recover from this failure.
- messages := windows.DefaultMessages()
- if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.Messages != nil {
- messages = f.frontendOptions.Windows.Messages
- }
- winc.Errorf(f.mainWindow, messages.WebView2ProcessCrash)
- os.Exit(-1)
- case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED,
- edge.COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED:
- // => A new render process is created automatically and navigated to an error page.
- // => Make sure that the error page is shown.
- if !f.hasStarted {
- // NavgiationCompleted didn't come in, make sure the chromium is shown
- chromium.Show()
- }
- if !f.mainWindow.hasBeenShown {
- // The window has never been shown, make sure to show it
- f.ShowWindow()
- }
- }
- }
-
- 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)
- if err != nil {
- log.Fatal(err)
- }
- err = settings.PutAreDevToolsEnabled(f.devtoolsEnabled)
- if err != nil {
- log.Fatal(err)
- }
-
- if opts := f.frontendOptions.Windows; opts != nil {
- if opts.ZoomFactor > 0.0 {
- chromium.PutZoomFactor(opts.ZoomFactor)
- }
- err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled)
- if err != nil {
- log.Fatal(err)
- }
- err = settings.PutIsPinchZoomEnabled(!opts.DisablePinchZoom)
- if err != nil {
- log.Fatal(err)
- }
- }
-
- err = settings.PutIsStatusBarEnabled(false)
- if err != nil {
- log.Fatal(err)
- }
- err = settings.PutAreBrowserAcceleratorKeysEnabled(false)
- if err != nil {
- log.Fatal(err)
- }
-
- if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup {
- chromium.OpenDevToolsWindow()
- }
-
- // Setup focus event handler
- onFocus := f.mainWindow.OnSetFocus()
- onFocus.Bind(f.onFocus)
-
- // Set background colour
- f.WindowSetBackgroundColour(f.frontendOptions.BackgroundColour)
-
- chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow)
- chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
- chromium.Navigate(f.startURL.String())
-}
-
-type EventNotify struct {
- Name string `json:"name"`
- Data []interface{} `json:"data"`
-}
-
-func (f *Frontend) Notify(name string, data ...interface{}) {
- notification := EventNotify{
- Name: name,
- Data: data,
- }
- payload, err := json.Marshal(notification)
- if err != nil {
- f.logger.Error(err.Error())
- return
- }
- f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
-}
-
-func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) {
- // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but
- // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request.
- if reqHeaders, err := req.GetHeaders(); err == nil {
- useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent)
- useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ")
- reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent)
- reqHeaders.Release()
- }
-
- if f.assets == nil {
- // We are using the devServer let the WebView2 handle the request with its default handler
- return
- }
-
- //Get the request
- uri, _ := req.GetUri()
- reqUri, err := url.ParseRequestURI(uri)
- if err != nil {
- f.logger.Error("Unable to parse equest uri %s: %s", uri, err)
- return
- }
-
- if reqUri.Scheme != f.startURL.Scheme {
- // Let the WebView2 handle the request with its default handler
- return
- } else if reqUri.Host != f.startURL.Host {
- // Let the WebView2 handle the request with its default handler
- 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()
- }
- })
-
- if err != nil {
- f.logger.Error("%s: NewRequest failed: %s", uri, err)
- return
- }
-
- f.assets.ServeWebViewRequest(webviewRequest)
-}
-
-var edgeMap = map[string]uintptr{
- "n-resize": w32.HTTOP,
- "ne-resize": w32.HTTOPRIGHT,
- "e-resize": w32.HTRIGHT,
- "se-resize": w32.HTBOTTOMRIGHT,
- "s-resize": w32.HTBOTTOM,
- "sw-resize": w32.HTBOTTOMLEFT,
- "w-resize": w32.HTLEFT,
- "nw-resize": w32.HTTOPLEFT,
-}
-
-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
- }
-
- if message == "drag" {
- if !f.mainWindow.IsFullScreen() {
- err := f.startDrag()
- if err != nil {
- f.logger.Error(err.Error())
- }
- }
- return
- }
-
- 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,
- )
-
- f.ExecJS(cmd)
- return
- }
-
- if strings.HasPrefix(message, "resize:") {
- if !f.mainWindow.IsFullScreen() {
- sl := strings.Split(message, ":")
- if len(sl) != 2 {
- f.logger.Info("Unknown message returned from dispatcher: %+v", message)
- return
- }
- edge := edgeMap[sl[1]]
- err := f.startResize(edge)
- if err != nil {
- f.logger.Error(err.Error())
- }
- }
- 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()
- if err != nil {
- f.logger.Error(err.Error())
- return
- }
-
- defer objs.Release()
-
- count, err := objs.GetCount()
- if err != nil {
- f.logger.Error(err.Error())
- return
- }
-
- 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) {
- escaped, err := json.Marshal(message)
- if err != nil {
- panic(err)
- }
- f.mainWindow.Invoke(func() {
- f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`)
- })
-}
-
-func (f *Frontend) startDrag() error {
- if !w32.ReleaseCapture() {
- return fmt.Errorf("unable to release mouse capture")
- }
- // Use PostMessage because we don't want to block the caller until dragging has been finished.
- w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
- return nil
-}
-
-func (f *Frontend) startResize(border uintptr) error {
- if !w32.ReleaseCapture() {
- return fmt.Errorf("unable to release mouse capture")
- }
- // Use PostMessage because we don't want to block the caller until resizing has been finished.
- w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, border, 0)
- return nil
-}
-
-func (f *Frontend) ExecJS(js string) {
- f.mainWindow.Invoke(func() {
- f.chromium.Eval(js)
- })
-}
-
-func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
- if f.frontendOptions.OnDomReady != nil {
- go f.frontendOptions.OnDomReady(f.ctx)
- }
-
- if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
- 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
- }
- f.hasStarted = true
-
- // Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
- err := f.chromium.Hide()
- if err != nil {
- log.Fatal(err)
- }
- err = f.chromium.Show()
- if err != nil {
- log.Fatal(err)
- }
-
- if f.frontendOptions.StartHidden {
- return
- }
-
- switch f.frontendOptions.WindowStartState {
- case options.Maximised:
- if !f.frontendOptions.DisableResize {
- win32.ShowWindowMaximised(f.mainWindow.Handle())
- } else {
- win32.ShowWindow(f.mainWindow.Handle())
- }
- case options.Minimised:
- win32.ShowWindowMinimised(f.mainWindow.Handle())
- case options.Fullscreen:
- f.mainWindow.Fullscreen()
- win32.ShowWindow(f.mainWindow.Handle())
- default:
- if f.frontendOptions.Fullscreen {
- f.mainWindow.Fullscreen()
- }
- win32.ShowWindow(f.mainWindow.Handle())
- }
-
- f.mainWindow.hasBeenShown = true
-
-}
-
-func (f *Frontend) ShowWindow() {
- f.mainWindow.Invoke(func() {
- if !f.mainWindow.hasBeenShown {
- f.mainWindow.hasBeenShown = true
- switch f.frontendOptions.WindowStartState {
- case options.Maximised:
- if !f.frontendOptions.DisableResize {
- win32.ShowWindowMaximised(f.mainWindow.Handle())
- } else {
- win32.ShowWindow(f.mainWindow.Handle())
- }
- case options.Minimised:
- win32.RestoreWindow(f.mainWindow.Handle())
- case options.Fullscreen:
- f.mainWindow.Fullscreen()
- win32.ShowWindow(f.mainWindow.Handle())
- default:
- if f.frontendOptions.Fullscreen {
- f.mainWindow.Fullscreen()
- }
- win32.ShowWindow(f.mainWindow.Handle())
- }
- } else {
- if win32.IsWindowMinimised(f.mainWindow.Handle()) {
- win32.RestoreWindow(f.mainWindow.Handle())
- } else {
- win32.ShowWindow(f.mainWindow.Handle())
- }
- }
- w32.SetForegroundWindow(f.mainWindow.Handle())
- w32.SetFocus(f.mainWindow.Handle())
- })
-
-}
-
-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)
- }
- }
-}
diff --git a/v2/internal/frontend/desktop/windows/keys.go b/v2/internal/frontend/desktop/windows/keys.go
deleted file mode 100644
index 2fe5f7550..000000000
--- a/v2/internal/frontend/desktop/windows/keys.go
+++ /dev/null
@@ -1,203 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
- "github.com/wailsapp/wails/v2/pkg/menu/keys"
- "strings"
-)
-
-var ModifierMap = map[keys.Modifier]winc.Modifiers{
- keys.ShiftKey: winc.ModShift,
- keys.ControlKey: winc.ModControl,
- keys.OptionOrAltKey: winc.ModAlt,
- keys.CmdOrCtrlKey: winc.ModControl,
-}
-
-func acceleratorToWincShortcut(accelerator *keys.Accelerator) winc.Shortcut {
-
- if accelerator == nil {
- return winc.NoShortcut
- }
- inKey := strings.ToUpper(accelerator.Key)
- key, exists := keyMap[inKey]
- if !exists {
- return winc.NoShortcut
- }
- var modifiers winc.Modifiers
- if _, exists := shiftMap[inKey]; exists {
- modifiers = winc.ModShift
- }
- for _, mod := range accelerator.Modifiers {
- modifiers |= ModifierMap[mod]
- }
- return winc.Shortcut{
- Modifiers: modifiers,
- Key: key,
- }
-}
-
-var shiftMap = map[string]struct{}{
- "~": {},
- ")": {},
- "!": {},
- "@": {},
- "#": {},
- "$": {},
- "%": {},
- "^": {},
- "&": {},
- "*": {},
- "(": {},
- "_": {},
- "PLUS": {},
- "<": {},
- ">": {},
- "?": {},
- ":": {},
- `"`: {},
- "{": {},
- "}": {},
- "|": {},
-}
-
-var keyMap = map[string]winc.Key{
- "0": winc.Key0,
- "1": winc.Key1,
- "2": winc.Key2,
- "3": winc.Key3,
- "4": winc.Key4,
- "5": winc.Key5,
- "6": winc.Key6,
- "7": winc.Key7,
- "8": winc.Key8,
- "9": winc.Key9,
- "A": winc.KeyA,
- "B": winc.KeyB,
- "C": winc.KeyC,
- "D": winc.KeyD,
- "E": winc.KeyE,
- "F": winc.KeyF,
- "G": winc.KeyG,
- "H": winc.KeyH,
- "I": winc.KeyI,
- "J": winc.KeyJ,
- "K": winc.KeyK,
- "L": winc.KeyL,
- "M": winc.KeyM,
- "N": winc.KeyN,
- "O": winc.KeyO,
- "P": winc.KeyP,
- "Q": winc.KeyQ,
- "R": winc.KeyR,
- "S": winc.KeyS,
- "T": winc.KeyT,
- "U": winc.KeyU,
- "V": winc.KeyV,
- "W": winc.KeyW,
- "X": winc.KeyX,
- "Y": winc.KeyY,
- "Z": winc.KeyZ,
- "F1": winc.KeyF1,
- "F2": winc.KeyF2,
- "F3": winc.KeyF3,
- "F4": winc.KeyF4,
- "F5": winc.KeyF5,
- "F6": winc.KeyF6,
- "F7": winc.KeyF7,
- "F8": winc.KeyF8,
- "F9": winc.KeyF9,
- "F10": winc.KeyF10,
- "F11": winc.KeyF11,
- "F12": winc.KeyF12,
- "F13": winc.KeyF13,
- "F14": winc.KeyF14,
- "F15": winc.KeyF15,
- "F16": winc.KeyF16,
- "F17": winc.KeyF17,
- "F18": winc.KeyF18,
- "F19": winc.KeyF19,
- "F20": winc.KeyF20,
- "F21": winc.KeyF21,
- "F22": winc.KeyF22,
- "F23": winc.KeyF23,
- "F24": winc.KeyF24,
-
- "`": winc.KeyOEM3,
- ",": winc.KeyOEMComma,
- ".": winc.KeyOEMPeriod,
- "/": winc.KeyOEM2,
- ";": winc.KeyOEM1,
- "'": winc.KeyOEM7,
- "[": winc.KeyOEM4,
- "]": winc.KeyOEM6,
- `\`: winc.KeyOEM5,
-
- "~": winc.KeyOEM3, //
- ")": winc.Key0,
- "!": winc.Key1,
- "@": winc.Key2,
- "#": winc.Key3,
- "$": winc.Key4,
- "%": winc.Key5,
- "^": winc.Key6,
- "&": winc.Key7,
- "*": winc.Key8,
- "(": winc.Key9,
- "_": winc.KeyOEMMinus,
- "PLUS": winc.KeyOEMPlus,
- "<": winc.KeyOEMComma,
- ">": winc.KeyOEMPeriod,
- "?": winc.KeyOEM2,
- ":": winc.KeyOEM1,
- `"`: winc.KeyOEM7,
- "{": winc.KeyOEM4,
- "}": winc.KeyOEM6,
- "|": winc.KeyOEM5,
-
- "SPACE": winc.KeySpace,
- "TAB": winc.KeyTab,
- "CAPSLOCK": winc.KeyCapital,
- "NUMLOCK": winc.KeyNumlock,
- "SCROLLLOCK": winc.KeyScroll,
- "BACKSPACE": winc.KeyBack,
- "DELETE": winc.KeyDelete,
- "INSERT": winc.KeyInsert,
- "RETURN": winc.KeyReturn,
- "ENTER": winc.KeyReturn,
- "UP": winc.KeyUp,
- "DOWN": winc.KeyDown,
- "LEFT": winc.KeyLeft,
- "RIGHT": winc.KeyRight,
- "HOME": winc.KeyHome,
- "END": winc.KeyEnd,
- "PAGEUP": winc.KeyPrior,
- "PAGEDOWN": winc.KeyNext,
- "ESCAPE": winc.KeyEscape,
- "ESC": winc.KeyEscape,
- "VOLUMEUP": winc.KeyVolumeUp,
- "VOLUMEDOWN": winc.KeyVolumeDown,
- "VOLUMEMUTE": winc.KeyVolumeMute,
- "MEDIANEXTTRACK": winc.KeyMediaNextTrack,
- "MEDIAPREVIOUSTRACK": winc.KeyMediaPrevTrack,
- "MEDIASTOP": winc.KeyMediaStop,
- "MEDIAPLAYPAUSE": winc.KeyMediaPlayPause,
- "PRINTSCREEN": winc.KeyPrint,
- "NUM0": winc.KeyNumpad0,
- "NUM1": winc.KeyNumpad1,
- "NUM2": winc.KeyNumpad2,
- "NUM3": winc.KeyNumpad3,
- "NUM4": winc.KeyNumpad4,
- "NUM5": winc.KeyNumpad5,
- "NUM6": winc.KeyNumpad6,
- "NUM7": winc.KeyNumpad7,
- "NUM8": winc.KeyNumpad8,
- "NUM9": winc.KeyNumpad9,
- "nummult": winc.KeyMultiply,
- "numadd": winc.KeyAdd,
- "numsub": winc.KeySubtract,
- "numdec": winc.KeyDecimal,
- "numdiv": winc.KeyDivide,
-}
diff --git a/v2/internal/frontend/desktop/windows/menu.go b/v2/internal/frontend/desktop/windows/menu.go
deleted file mode 100644
index b71128b45..000000000
--- a/v2/internal/frontend/desktop/windows/menu.go
+++ /dev/null
@@ -1,132 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
- "github.com/wailsapp/wails/v2/pkg/menu"
-)
-
-var checkboxMap = map[*menu.MenuItem][]*winc.MenuItem{}
-var radioGroupMap = map[*menu.MenuItem][]*winc.MenuItem{}
-
-func toggleCheckBox(menuItem *menu.MenuItem) {
- menuItem.Checked = !menuItem.Checked
- for _, wincMenu := range checkboxMap[menuItem] {
- wincMenu.SetChecked(menuItem.Checked)
- }
-}
-
-func addCheckBoxToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
- if checkboxMap[menuItem] == nil {
- checkboxMap[menuItem] = []*winc.MenuItem{}
- }
- checkboxMap[menuItem] = append(checkboxMap[menuItem], wincMenuItem)
-}
-
-func toggleRadioItem(menuItem *menu.MenuItem) {
- menuItem.Checked = !menuItem.Checked
- for _, wincMenu := range radioGroupMap[menuItem] {
- wincMenu.SetChecked(menuItem.Checked)
- }
-}
-
-func addRadioItemToMap(menuItem *menu.MenuItem, wincMenuItem *winc.MenuItem) {
- if radioGroupMap[menuItem] == nil {
- radioGroupMap[menuItem] = []*winc.MenuItem{}
- }
- radioGroupMap[menuItem] = append(radioGroupMap[menuItem], wincMenuItem)
-}
-
-func (w *Window) SetApplicationMenu(menu *menu.Menu) {
- w.applicationMenu = menu
- processMenu(w, menu)
-}
-
-func processMenu(window *Window, menu *menu.Menu) {
- mainMenu := window.NewMenu()
- for _, menuItem := range menu.Items {
- submenu := mainMenu.AddSubMenu(menuItem.Label)
- if menuItem.SubMenu != nil {
- for _, menuItem := range menuItem.SubMenu.Items {
- processMenuItem(submenu, menuItem)
- }
- }
- }
- mainMenu.Show()
-}
-
-func processMenuItem(parent *winc.MenuItem, menuItem *menu.MenuItem) {
- if menuItem.Hidden {
- return
- }
- switch menuItem.Type {
- case menu.SeparatorType:
- parent.AddSeparator()
- case menu.TextType:
- shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
- newItem := parent.AddItem(menuItem.Label, shortcut)
- //if menuItem.Tooltip != "" {
- // newItem.SetToolTip(menuItem.Tooltip)
- //}
- if menuItem.Click != nil {
- newItem.OnClick().Bind(func(e *winc.Event) {
- menuItem.Click(&menu.CallbackData{
- MenuItem: menuItem,
- })
- })
- }
- newItem.SetEnabled(!menuItem.Disabled)
-
- case menu.CheckboxType:
- shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
- newItem := parent.AddItem(menuItem.Label, shortcut)
- newItem.SetCheckable(true)
- newItem.SetChecked(menuItem.Checked)
- //if menuItem.Tooltip != "" {
- // newItem.SetToolTip(menuItem.Tooltip)
- //}
- if menuItem.Click != nil {
- newItem.OnClick().Bind(func(e *winc.Event) {
- toggleCheckBox(menuItem)
- menuItem.Click(&menu.CallbackData{
- MenuItem: menuItem,
- })
- })
- }
- newItem.SetEnabled(!menuItem.Disabled)
- addCheckBoxToMap(menuItem, newItem)
- case menu.RadioType:
- shortcut := acceleratorToWincShortcut(menuItem.Accelerator)
- newItem := parent.AddItemRadio(menuItem.Label, shortcut)
- newItem.SetCheckable(true)
- newItem.SetChecked(menuItem.Checked)
- //if menuItem.Tooltip != "" {
- // newItem.SetToolTip(menuItem.Tooltip)
- //}
- if menuItem.Click != nil {
- newItem.OnClick().Bind(func(e *winc.Event) {
- toggleRadioItem(menuItem)
- menuItem.Click(&menu.CallbackData{
- MenuItem: menuItem,
- })
- })
- }
- newItem.SetEnabled(!menuItem.Disabled)
- addRadioItemToMap(menuItem, newItem)
- case menu.SubmenuType:
- submenu := parent.AddSubMenu(menuItem.Label)
- for _, menuItem := range menuItem.SubMenu.Items {
- processMenuItem(submenu, menuItem)
- }
- }
-}
-
-func (f *Frontend) MenuSetApplicationMenu(menu *menu.Menu) {
- f.mainWindow.SetApplicationMenu(menu)
-}
-
-func (f *Frontend) MenuUpdateApplicationMenu() {
- processMenu(f.mainWindow, f.mainWindow.applicationMenu)
-}
diff --git a/v2/internal/frontend/desktop/windows/screen.go b/v2/internal/frontend/desktop/windows/screen.go
deleted file mode 100644
index f6e12bcce..000000000
--- a/v2/internal/frontend/desktop/windows/screen.go
+++ /dev/null
@@ -1,129 +0,0 @@
-//go:build windows
-// +build windows
-
-package windows
-
-import (
- "fmt"
- "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 {
- // Checks to make sure all the fields are the same.
- // A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
- return first.DwFlags == second.DwFlags &&
- first.RcMonitor.Top == second.RcMonitor.Top &&
- first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
- first.RcMonitor.Right == second.RcMonitor.Right &&
- first.RcMonitor.Left == second.RcMonitor.Left &&
- first.RcWork.Top == second.RcWork.Top &&
- first.RcWork.Bottom == second.RcWork.Bottom &&
- first.RcWork.Right == second.RcWork.Right &&
- first.RcWork.Left == second.RcWork.Left
-}
-
-func GetMonitorInfo(hMonitor w32.HMONITOR) (*w32.MONITORINFO, error) {
- // Adapted from winc.utils.getMonitorInfo TODO: add this to win32
- // See docs for
- //https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
-
- var info w32.MONITORINFO
- info.CbSize = uint32(unsafe.Sizeof(info))
- succeeded := w32.GetMonitorInfo(hMonitor, &info)
- if !succeeded {
- return &info, errors.New("Windows call to getMonitorInfo failed")
- }
- return &info, nil
-}
-
-func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, screenContainer *ScreenContainer) uintptr {
- // adapted from https://stackoverflow.com/a/23492886/4188138
-
- // see docs for the following pages to better understand this function
- // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
- // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
- // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
- // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
-
- ourMonitorData := Screen{}
- currentMonHndl := w32.MonitorFromWindow(screenContainer.mainWinHandle, w32.MONITOR_DEFAULTTONEAREST)
- currentMonInfo, currErr := GetMonitorInfo(currentMonHndl)
-
- if currErr != nil {
- screenContainer.errors = append(screenContainer.errors, currErr)
- screenContainer.monitors = append(screenContainer.monitors, Screen{})
- // not sure what the consequences of returning false are, so let's just return true and handle it ourselves
- return w32.TRUE
- }
-
- monInfo, err := GetMonitorInfo(hMonitor)
- if err != nil {
- screenContainer.errors = append(screenContainer.errors, err)
- screenContainer.monitors = append(screenContainer.monitors, Screen{})
- return w32.TRUE
- }
-
- width := lprcMonitor.Right - lprcMonitor.Left
- height := lprcMonitor.Bottom - lprcMonitor.Top
- ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1
- ourMonitorData.Height = int(height)
- ourMonitorData.Width = int(width)
- 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
- // If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
- screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
- // let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
- screenContainer.errors = append(screenContainer.errors, nil)
- return w32.TRUE
-}
-
-type ScreenContainer struct {
- monitors []Screen
- errors []error
- mainWinHandle w32.HWND
-}
-
-func GetAllScreens(mainWinHandle w32.HWND) ([]Screen, error) {
- // TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
- monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
- returnErr := error(nil)
- errorStrings := []string{}
-
- dc := w32.GetDC(0)
- defer w32.ReleaseDC(0, dc)
- succeeded := w32.EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
- if !succeeded {
- return monitorContainer.monitors, errors.New("Windows call to EnumDisplayMonitors failed")
- }
- for idx, err := range monitorContainer.errors {
- if err != nil {
- errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
- }
- }
-
- if len(errorStrings) > 0 {
- returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
- }
- return monitorContainer.monitors, returnErr
-}
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/theme.go b/v2/internal/frontend/desktop/windows/theme.go
deleted file mode 100644
index ac975e3d0..000000000
--- a/v2/internal/frontend/desktop/windows/theme.go
+++ /dev/null
@@ -1,67 +0,0 @@
-//go:build windows
-
-package windows
-
-import (
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
- "github.com/wailsapp/wails/v2/pkg/options/windows"
-)
-
-func (w *Window) UpdateTheme() {
-
- // Don't redraw theme if nothing has changed
- if !w.themeChanged {
- return
- }
- w.themeChanged = false
-
- if win32.IsCurrentlyHighContrastMode() {
- return
- }
-
- if !win32.SupportsThemes() {
- return
- }
-
- var isDarkMode bool
- switch w.theme {
- case windows.SystemDefault:
- isDarkMode = win32.IsCurrentlyDarkMode()
- case windows.Dark:
- isDarkMode = true
- case windows.Light:
- isDarkMode = false
- }
- win32.SetTheme(w.Handle(), isDarkMode)
-
- // Custom theme processing
- winOptions := w.frontendOptions.Windows
- var customTheme *windows.ThemeSettings
- if winOptions != nil {
- customTheme = winOptions.CustomTheme
- }
- // Custom theme
- if win32.SupportsCustomThemes() && customTheme != nil {
- if w.isActive {
- if isDarkMode {
- win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBar)
- win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleText)
- win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorder)
- } else {
- win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBar)
- win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleText)
- win32.SetBorderColour(w.Handle(), customTheme.LightModeBorder)
- }
- } else {
- if isDarkMode {
- win32.SetTitleBarColour(w.Handle(), customTheme.DarkModeTitleBarInactive)
- win32.SetTitleTextColour(w.Handle(), customTheme.DarkModeTitleTextInactive)
- win32.SetBorderColour(w.Handle(), customTheme.DarkModeBorderInactive)
- } else {
- win32.SetTitleBarColour(w.Handle(), customTheme.LightModeTitleBarInactive)
- win32.SetTitleTextColour(w.Handle(), customTheme.LightModeTitleTextInactive)
- win32.SetBorderColour(w.Handle(), customTheme.LightModeBorderInactive)
- }
- }
- }
-}
diff --git a/v2/internal/frontend/desktop/windows/win32/clipboard.go b/v2/internal/frontend/desktop/windows/win32/clipboard.go
deleted file mode 100644
index fa715f18c..000000000
--- a/v2/internal/frontend/desktop/windows/win32/clipboard.go
+++ /dev/null
@@ -1,143 +0,0 @@
-//go:build windows
-
-/*
- * Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved.
- */
-
-package win32
-
-import (
- "runtime"
- "syscall"
- "time"
- "unsafe"
-)
-
-const (
- cfUnicodetext = 13
- gmemMoveable = 0x0002
-)
-
-// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
-func waitOpenClipboard() error {
- started := time.Now()
- limit := started.Add(time.Second)
- var r uintptr
- var err error
- for time.Now().Before(limit) {
- r, _, err = procOpenClipboard.Call(0)
- if r != 0 {
- return nil
- }
- time.Sleep(time.Millisecond)
- }
- return err
-}
-
-func GetClipboardText() (string, error) {
- // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
- // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
- if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
- return "", err
- }
- err := waitOpenClipboard()
- if err != nil {
- return "", err
- }
-
- h, _, err := procGetClipboardData.Call(cfUnicodetext)
- if h == 0 {
- _, _, _ = procCloseClipboard.Call()
- return "", err
- }
-
- l, _, err := kernelGlobalLock.Call(h)
- if l == 0 {
- _, _, _ = procCloseClipboard.Call()
- return "", err
- }
-
- text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
-
- r, _, err := kernelGlobalUnlock.Call(h)
- if r == 0 {
- _, _, _ = procCloseClipboard.Call()
- return "", err
- }
-
- closed, _, err := procCloseClipboard.Call()
- if closed == 0 {
- return "", err
- }
- return text, nil
-}
-
-func SetClipboardText(text string) error {
- // LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
- // Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err := waitOpenClipboard()
- if err != nil {
- return err
- }
-
- r, _, err := procEmptyClipboard.Call(0)
- if r == 0 {
- _, _, _ = procCloseClipboard.Call()
- return err
- }
-
- data, err := syscall.UTF16FromString(text)
- if err != nil {
- return err
- }
-
- // "If the hMem parameter identifies a memory object, the object must have
- // been allocated using the function with the GMEM_MOVEABLE flag."
- h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
- if h == 0 {
- _, _, _ = procCloseClipboard.Call()
- return err
- }
- defer func() {
- if h != 0 {
- kernelGlobalFree.Call(h)
- }
- }()
-
- l, _, err := kernelGlobalLock.Call(h)
- if l == 0 {
- _, _, _ = procCloseClipboard.Call()
- return err
- }
-
- r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
- if r == 0 {
- _, _, _ = procCloseClipboard.Call()
- return err
- }
-
- r, _, err = kernelGlobalUnlock.Call(h)
- if r == 0 {
- if err.(syscall.Errno) != 0 {
- _, _, _ = procCloseClipboard.Call()
- return err
- }
- }
-
- r, _, err = procSetClipboardData.Call(cfUnicodetext, h)
- if r == 0 {
- _, _, _ = procCloseClipboard.Call()
- return err
- }
- h = 0 // suppress deferred cleanup
- closed, _, err := procCloseClipboard.Call()
- if closed == 0 {
- return err
- }
- return nil
-}
diff --git a/v2/internal/frontend/desktop/windows/win32/consts.go b/v2/internal/frontend/desktop/windows/win32/consts.go
deleted file mode 100644
index e38ea4b92..000000000
--- a/v2/internal/frontend/desktop/windows/win32/consts.go
+++ /dev/null
@@ -1,57 +0,0 @@
-//go:build windows
-
-package win32
-
-import (
- "syscall"
-
- "github.com/wailsapp/wails/v2/internal/system/operatingsystem"
-)
-
-type HRESULT int32
-type HANDLE uintptr
-type HMONITOR HANDLE
-
-var (
- moduser32 = syscall.NewLazyDLL("user32.dll")
- procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
- procGetWindowLong = moduser32.NewProc("GetWindowLongW")
- procSetClassLong = moduser32.NewProc("SetClassLongW")
- procSetClassLongPtr = moduser32.NewProc("SetClassLongPtrW")
- procShowWindow = moduser32.NewProc("ShowWindow")
- procIsWindowVisible = moduser32.NewProc("IsWindowVisible")
- procGetWindowRect = moduser32.NewProc("GetWindowRect")
- procGetMonitorInfo = moduser32.NewProc("GetMonitorInfoW")
- procMonitorFromWindow = moduser32.NewProc("MonitorFromWindow")
- procIsClipboardFormatAvailable = moduser32.NewProc("IsClipboardFormatAvailable")
- procOpenClipboard = moduser32.NewProc("OpenClipboard")
- procCloseClipboard = moduser32.NewProc("CloseClipboard")
- procEmptyClipboard = moduser32.NewProc("EmptyClipboard")
- procGetClipboardData = moduser32.NewProc("GetClipboardData")
- procSetClipboardData = moduser32.NewProc("SetClipboardData")
-)
-var (
- moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
- procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
- procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
-)
-var (
- modwingdi = syscall.NewLazyDLL("gdi32.dll")
- procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush")
-)
-var (
- kernel32 = syscall.NewLazyDLL("kernel32")
- kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc")
- kernelGlobalFree = kernel32.NewProc("GlobalFree")
- kernelGlobalLock = kernel32.NewProc("GlobalLock")
- kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock")
- kernelLstrcpy = kernel32.NewProc("lstrcpyW")
-)
-
-var windowsVersion, _ = operatingsystem.GetWindowsVersionInfo()
-
-func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
- return windowsVersion.Major >= major &&
- windowsVersion.Minor >= minor &&
- windowsVersion.Build >= buildNumber
-}
diff --git a/v2/internal/frontend/desktop/windows/win32/theme.go b/v2/internal/frontend/desktop/windows/win32/theme.go
deleted file mode 100644
index 299a4f63a..000000000
--- a/v2/internal/frontend/desktop/windows/win32/theme.go
+++ /dev/null
@@ -1,119 +0,0 @@
-//go:build windows
-
-package win32
-
-import (
- "unsafe"
-
- "golang.org/x/sys/windows/registry"
-)
-
-type DWMWINDOWATTRIBUTE int32
-
-const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19
-const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20
-const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34
-const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35
-const DwmwaTextColor DWMWINDOWATTRIBUTE = 36
-const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38
-
-const SPI_GETHIGHCONTRAST = 0x0042
-const HCF_HIGHCONTRASTON = 0x00000001
-
-// BackdropType defines the type of translucency we wish to use
-type BackdropType int32
-
-func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
- ret, _, err := procDwmSetWindowAttribute.Call(
- hwnd,
- uintptr(dwAttribute),
- uintptr(pvAttribute),
- cbAttribute)
- if ret != 0 {
- _ = err
- // println(err.Error())
- }
-}
-
-func SupportsThemes() bool {
- // We can't support Windows versions before 17763
- return IsWindowsVersionAtLeast(10, 0, 17763)
-}
-
-func SupportsCustomThemes() bool {
- return IsWindowsVersionAtLeast(10, 0, 17763)
-}
-
-func SupportsBackdropTypes() bool {
- return IsWindowsVersionAtLeast(10, 0, 22621)
-}
-
-func SupportsImmersiveDarkMode() bool {
- return IsWindowsVersionAtLeast(10, 0, 18985)
-}
-
-func SetTheme(hwnd uintptr, useDarkMode bool) {
- if SupportsThemes() {
- attr := DwmwaUseImmersiveDarkModeBefore20h1
- if SupportsImmersiveDarkMode() {
- attr = DwmwaUseImmersiveDarkMode
- }
- var winDark int32
- if useDarkMode {
- winDark = 1
- }
- dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark))
- }
-}
-
-func EnableTranslucency(hwnd uintptr, backdrop BackdropType) {
- if SupportsBackdropTypes() {
- dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop))
- } else {
- println("Warning: Translucency type unavailable on Windows < 22621")
- }
-}
-
-func SetTitleBarColour(hwnd uintptr, titleBarColour int32) {
- dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour))
-}
-
-func SetTitleTextColour(hwnd uintptr, titleTextColour int32) {
- dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour))
-}
-
-func SetBorderColour(hwnd uintptr, titleBorderColour int32) {
- dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour))
-}
-
-func IsCurrentlyDarkMode() bool {
- key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
- if err != nil {
- return false
- }
- defer key.Close()
-
- AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme")
- if err != nil {
- return false
- }
- return AppsUseLightTheme == 0
-}
-
-type highContrast struct {
- CbSize uint32
- DwFlags uint32
- LpszDefaultScheme *int16
-}
-
-func IsCurrentlyHighContrastMode() bool {
- var result highContrast
- result.CbSize = uint32(unsafe.Sizeof(result))
- res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0)
- if res == 0 {
- _ = err
- return false
- }
- r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
- return r
-}
diff --git a/v2/internal/frontend/desktop/windows/win32/window.go b/v2/internal/frontend/desktop/windows/win32/window.go
deleted file mode 100644
index 6028789de..000000000
--- a/v2/internal/frontend/desktop/windows/win32/window.go
+++ /dev/null
@@ -1,223 +0,0 @@
-//go:build windows
-
-package win32
-
-import (
- "fmt"
- "log"
- "strconv"
- "syscall"
- "unsafe"
-
- "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
-)
-
-const (
- WS_MAXIMIZE = 0x01000000
- WS_MINIMIZE = 0x20000000
-
- GWL_STYLE = -16
-
- MONITOR_DEFAULTTOPRIMARY = 0x00000001
-)
-
-const (
- SW_HIDE = 0
- SW_NORMAL = 1
- SW_SHOWNORMAL = 1
- SW_SHOWMINIMIZED = 2
- SW_MAXIMIZE = 3
- SW_SHOWMAXIMIZED = 3
- SW_SHOWNOACTIVATE = 4
- SW_SHOW = 5
- SW_MINIMIZE = 6
- SW_SHOWMINNOACTIVE = 7
- SW_SHOWNA = 8
- SW_RESTORE = 9
- SW_SHOWDEFAULT = 10
- SW_FORCEMINIMIZE = 11
-)
-
-const (
- GCLP_HBRBACKGROUND int32 = -10
-)
-
-// Power
-const (
- // WM_POWERBROADCAST - Notifies applications that a power-management event has occurred.
- WM_POWERBROADCAST = 536
-
- // PBT_APMPOWERSTATUSCHANGE - Power status has changed.
- PBT_APMPOWERSTATUSCHANGE = 10
-
- // PBT_APMRESUMEAUTOMATIC -Operation is resuming automatically from a low-power state. This message is sent every time the system resumes.
- PBT_APMRESUMEAUTOMATIC = 18
-
- // PBT_APMRESUMESUSPEND - Operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
- PBT_APMRESUMESUSPEND = 7
-
- // PBT_APMSUSPEND - System is suspending operation.
- PBT_APMSUSPEND = 4
-
- // PBT_POWERSETTINGCHANGE - A power setting change event has been received.
- PBT_POWERSETTINGCHANGE = 32787
-)
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773244.aspx
-type MARGINS struct {
- CxLeftWidth, CxRightWidth, CyTopHeight, CyBottomHeight int32
-}
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd162897.aspx
-type RECT struct {
- Left, Top, Right, Bottom int32
-}
-
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145065.aspx
-type MONITORINFO struct {
- CbSize uint32
- RcMonitor RECT
- RcWork RECT
- DwFlags uint32
-}
-
-func ExtendFrameIntoClientArea(hwnd uintptr, extend bool) {
- // -1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11)
- // Also shows the caption buttons if transparent ant translucent but they don't work.
- // 0: Adds the default frame styling but no aero shadow, does not show the caption buttons.
- // 1: Adds the default frame styling (aero shadow and e.g. rounded corners on Windows 11) but no caption buttons
- // are shown if transparent ant translucent.
- var margins MARGINS
- if extend {
- margins = MARGINS{1, 1, 1, 1} // Only extend 1 pixel to have the default frame styling but no caption buttons
- }
- if err := dwmExtendFrameIntoClientArea(hwnd, &margins); err != nil {
- log.Fatal(fmt.Errorf("DwmExtendFrameIntoClientArea failed: %s", err))
- }
-}
-
-func IsVisible(hwnd uintptr) bool {
- ret, _, _ := procIsWindowVisible.Call(hwnd)
- return ret != 0
-}
-
-func IsWindowFullScreen(hwnd uintptr) bool {
- wRect := GetWindowRect(hwnd)
- m := MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY)
- var mi MONITORINFO
- mi.CbSize = uint32(unsafe.Sizeof(mi))
- if !GetMonitorInfo(m, &mi) {
- return false
- }
- return wRect.Left == mi.RcMonitor.Left &&
- wRect.Top == mi.RcMonitor.Top &&
- wRect.Right == mi.RcMonitor.Right &&
- wRect.Bottom == mi.RcMonitor.Bottom
-}
-
-func IsWindowMaximised(hwnd uintptr) bool {
- style := uint32(getWindowLong(hwnd, GWL_STYLE))
- return style&WS_MAXIMIZE != 0
-}
-func IsWindowMinimised(hwnd uintptr) bool {
- style := uint32(getWindowLong(hwnd, GWL_STYLE))
- return style&WS_MINIMIZE != 0
-}
-
-func RestoreWindow(hwnd uintptr) {
- showWindow(hwnd, SW_RESTORE)
-}
-
-func ShowWindow(hwnd uintptr) {
- showWindow(hwnd, SW_SHOW)
-}
-
-func ShowWindowMaximised(hwnd uintptr) {
- showWindow(hwnd, SW_MAXIMIZE)
-}
-func ShowWindowMinimised(hwnd uintptr) {
- showWindow(hwnd, SW_MINIMIZE)
-}
-
-func SetBackgroundColour(hwnd uintptr, r, g, b uint8) {
- col := winc.RGB(r, g, b)
- hbrush, _, _ := procCreateSolidBrush.Call(uintptr(col))
- setClassLongPtr(hwnd, GCLP_HBRBACKGROUND, hbrush)
-}
-
-func IsWindowNormal(hwnd uintptr) bool {
- return !IsWindowMaximised(hwnd) && !IsWindowMinimised(hwnd) && !IsWindowFullScreen(hwnd)
-}
-
-func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error {
- ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
- hwnd,
- uintptr(unsafe.Pointer(margins)))
-
- if ret != 0 {
- return syscall.GetLastError()
- }
-
- return nil
-}
-
-func setClassLongPtr(hwnd uintptr, param int32, val uintptr) bool {
- proc := procSetClassLongPtr
- if strconv.IntSize == 32 {
- /*
- https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw
- Note: To write code that is compatible with both 32-bit and 64-bit Windows, use SetClassLongPtr.
- When compiling for 32-bit Windows, SetClassLongPtr is defined as a call to the SetClassLong function
-
- => We have to do this dynamically when directly calling the DLL procedures
- */
- proc = procSetClassLong
- }
-
- ret, _, _ := proc.Call(
- hwnd,
- uintptr(param),
- val,
- )
- return ret != 0
-}
-
-func getWindowLong(hwnd uintptr, index int) int32 {
- ret, _, _ := procGetWindowLong.Call(
- hwnd,
- uintptr(index))
-
- return int32(ret)
-}
-
-func showWindow(hwnd uintptr, cmdshow int) bool {
- ret, _, _ := procShowWindow.Call(
- hwnd,
- uintptr(cmdshow))
- return ret != 0
-}
-
-func GetWindowRect(hwnd uintptr) *RECT {
- var rect RECT
- procGetWindowRect.Call(
- hwnd,
- uintptr(unsafe.Pointer(&rect)))
-
- return &rect
-}
-
-func MonitorFromWindow(hwnd uintptr, dwFlags uint32) HMONITOR {
- ret, _, _ := procMonitorFromWindow.Call(
- hwnd,
- uintptr(dwFlags),
- )
- return HMONITOR(ret)
-}
-
-func GetMonitorInfo(hMonitor HMONITOR, lmpi *MONITORINFO) bool {
- ret, _, _ := procGetMonitorInfo.Call(
- uintptr(hMonitor),
- uintptr(unsafe.Pointer(lmpi)),
- )
- return ret != 0
-}
diff --git a/v2/internal/frontend/desktop/windows/winc/.gitignore b/v2/internal/frontend/desktop/windows/winc/.gitignore
deleted file mode 100644
index f1c181ec9..000000000
--- a/v2/internal/frontend/desktop/windows/winc/.gitignore
+++ /dev/null
@@ -1,12 +0,0 @@
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-
-# Test binary, build with `go test -c`
-*.test
-
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
diff --git a/v2/internal/frontend/desktop/windows/winc/AUTHORS b/v2/internal/frontend/desktop/windows/winc/AUTHORS
deleted file mode 100644
index 85674b8d9..000000000
--- a/v2/internal/frontend/desktop/windows/winc/AUTHORS
+++ /dev/null
@@ -1,12 +0,0 @@
-# This is the official list of 'Winc' authors for copyright purposes.
-
-# Names should be added to this file as
-# Name or Organization
-# The email address is not required for organizations.
-
-# Please keep the list sorted.
-
-# Contributors
-# ============
-
-Tad Vizbaras
diff --git a/v2/internal/frontend/desktop/windows/winc/LICENSE b/v2/internal/frontend/desktop/windows/winc/LICENSE
deleted file mode 100644
index a063a4370..000000000
--- a/v2/internal/frontend/desktop/windows/winc/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2019 winc Authors
-
-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/winc/README.md b/v2/internal/frontend/desktop/windows/winc/README.md
deleted file mode 100644
index 4d4d467bd..000000000
--- a/v2/internal/frontend/desktop/windows/winc/README.md
+++ /dev/null
@@ -1,181 +0,0 @@
-# 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*** **
-
-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.
-
-## Dependencies
-
-No other dependencies except Go standard library.
-
-## Building
-
-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
-
-Here app.manifest is XML file in format:
-
-```
-
-