diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 70d6d17bc..000000000 --- a/.clang-format +++ /dev/null @@ -1,3 +0,0 @@ -Language: JavaScript -BasedOnStyle: Google -ColumnLimit: 80 diff --git a/.eslintignore b/.eslintignore index b8e1c93a8..1430463fe 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,11 @@ -*_compressed*.js +# Build Artifacts /msg/* /build/* /dist/* +/typings/* +/docs/* + +# Tests other than mocha unit tests /tests/blocks/* /tests/themes/* /tests/compile/* @@ -11,10 +15,14 @@ /tests/screenshot/* /tests/test_runner.js /tests/workspace_svg/* + +# Demos, scripts, misc +/node_modules/* /generators/* /demos/* /appengine/* /externs/* /closure/* /scripts/gulpfiles/* -/typings/* +CHANGELOG.md +PULL_REQUEST_TEMPLATE.md \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 1bd12ca8b..14d27ca1a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,21 +1,6 @@ const rules = { - 'curly': ['error'], - 'eol-last': ['error'], - 'keyword-spacing': ['error'], - 'linebreak-style': ['error', 'unix'], - 'max-len': [ - 'error', - { - 'code': 100, - 'tabWidth': 4, - 'ignoreStrings': true, - 'ignoreRegExpLiterals': true, - 'ignoreUrls': true, - }, - ], - 'no-trailing-spaces': ['error', {'skipBlankLines': true}], 'no-unused-vars': [ - 'warn', + 'error', { 'args': 'after-used', // Ignore vars starting with an underscore. @@ -29,20 +14,12 @@ const rules = { // Blockly uses single quotes except for JSON blobs, which must use double // quotes. 'quotes': ['off'], - 'semi': ['error', 'always'], - // Blockly doesn't have space before function paren when defining functions. - 'space-before-function-paren': ['error', 'never'], - // Blockly doesn't have space before function paren when calling functions. - 'func-call-spacing': ['error', 'never'], - 'space-infix-ops': ['error'], // Blockly uses 'use strict' in files. 'strict': ['off'], // Closure style allows redeclarations. 'no-redeclare': ['off'], 'valid-jsdoc': ['error'], 'no-console': ['off'], - 'no-multi-spaces': ['error', {'ignoreEOLComments': true}], - 'operator-linebreak': ['error', 'after'], 'spaced-comment': [ 'error', 'always', @@ -61,27 +38,13 @@ const rules = { 'allow': ['^opt_', '^_opt_', '^testOnly_'], }, ], - // Use clang-format for indentation by running `npm run format`. - 'indent': ['off'], // Blockly uses capital letters for some non-constructor namespaces. // Keep them for legacy reasons. 'new-cap': ['off'], - // Mostly use default rules for brace style, but allow single-line blocks. - 'brace-style': ['error', '1tbs', {'allowSingleLine': true}], // Blockly uses objects as maps, but uses Object.create(null) to // instantiate them. 'guard-for-in': ['off'], 'prefer-spread': ['off'], - 'comma-dangle': [ - 'error', - { - 'arrays': 'always-multiline', - 'objects': 'always-multiline', - 'imports': 'always-multiline', - 'exports': 'always-multiline', - 'functions': 'ignore', - }, - ], }; /** @@ -92,10 +55,7 @@ const rules = { function buildTSOverride({files, tsconfig}) { return { 'files': files, - 'plugins': [ - '@typescript-eslint/eslint-plugin', - 'jsdoc', - ], + 'plugins': ['@typescript-eslint/eslint-plugin', 'jsdoc'], 'settings': { 'jsdoc': { 'mode': 'typescript', @@ -111,6 +71,7 @@ function buildTSOverride({files, tsconfig}) { 'extends': [ 'plugin:@typescript-eslint/recommended', 'plugin:jsdoc/recommended', + 'prettier', // Extend again so that these rules are applied last ], 'rules': { // TS rules @@ -124,14 +85,12 @@ function buildTSOverride({files, tsconfig}) { // Use TS-specific rule. 'no-unused-vars': ['off'], '@typescript-eslint/no-unused-vars': [ - 'warn', + 'error', { 'argsIgnorePattern': '^_', 'varsIgnorePattern': '^_', }, ], - 'func-call-spacing': ['off'], - '@typescript-eslint/func-call-spacing': ['warn'], // Temporarily disable. 23 problems. '@typescript-eslint/no-explicit-any': ['off'], // Temporarily disable. 128 problems. @@ -162,9 +121,19 @@ function buildTSOverride({files, tsconfig}) { 'publicOnly': true, }, ], - // Disable because of false alarms with Closure-supported tags. - // Re-enable after Closure is removed. - 'jsdoc/check-tag-names': ['off'], + 'jsdoc/check-tag-names': [ + 'error', + { + 'definedTags': [ + 'sealed', + 'typeParam', + 'remarks', + 'define', + 'nocollapse', + 'suppress', + ], + }, + ], // Re-enable after Closure is removed. There shouldn't even be // types in the TsDoc. // These are "types" because of Closure's @suppress {warningName} @@ -176,7 +145,9 @@ function buildTSOverride({files, tsconfig}) { 'jsdoc/check-param-names': ['off', {'checkDestructured': false}], // Allow any text in the license tag. Other checks are not relevant. 'jsdoc/check-values': ['off'], - 'jsdoc/newline-after-description': ['error'], + // Ensure there is a blank line between the body and any @tags, + // as required by the tsdoc spec (see #6353). + 'jsdoc/tag-lines': ['error', 'any', {'startLines': 1}], }, }; } @@ -193,21 +164,15 @@ const eslintJSON = { 'goog': true, 'exports': true, }, - 'extends': [ - 'eslint:recommended', - 'google', - ], + 'extends': ['eslint:recommended', 'google', 'prettier'], // TypeScript-specific config. Uses above rules plus these. 'overrides': [ buildTSOverride({ - files: ['./core/**/*.ts', './core/**/*.tsx'], + files: ['./**/*.ts', './**/*.tsx'], tsconfig: './tsconfig.json', }), buildTSOverride({ - files: [ - './tests/typescript/**/*.ts', - './tests/typescript/**/*.tsx', - ], + files: ['./tests/typescript/**/*.ts', './tests/typescript/**/*.tsx'], tsconfig: './tests/typescript/tsconfig.json', }), { diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dc0ea8c44..634b59bfa 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contributing to Blockly Want to contribute? Great! + - First, read this page (including the small print at the end). - Second, please make pull requests against develop, not master. If your patch needs to go into master immediately, include a note in your PR. @@ -8,6 +9,7 @@ Want to contribute? Great! For more information on style guide and other details, head over to the [Blockly Developers site](https://developers.google.com/blockly/guides/modify/contributing). ### Before you contribute + Before we can use your code, you must sign the [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) (CLA), which you can do online. The CLA is necessary mainly because you own the @@ -19,22 +21,26 @@ the CLA until after you've submitted your code for review and a member has approved it, but you must do it before we can put your code into our codebase. ### Larger changes + Before you start working on a larger contribution, you should get in touch with us first through the issue tracker with your idea so that we can help out and possibly guide you. Coordinating up front makes it much easier to avoid frustration later on. ### Code reviews + All submissions, including submissions by project members, require review. We use Github pull requests for this purpose. ### Browser compatibility -We care strongly about making Blockly work on all browsers. As of 2022 we -support Edge, Chrome, Safari, and Firefox. We will not accept changes that only -work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/) + +We care strongly about making Blockly work on all browsers. As of 2022 we +support Edge, Chrome, Safari, and Firefox. We will not accept changes that only +work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/) for compatibility information. ### The small print + Contributions made by corporations are covered by a different agreement than the one above, the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c0097d770..ec2f89b57 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -30,7 +30,7 @@ body: value: | 1. 2. - 3. + 3. - type: textarea id: stack-trace attributes: diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml index 93d7f5011..4ddae3b7b 100644 --- a/.github/ISSUE_TEMPLATE/documentation.yaml +++ b/.github/ISSUE_TEMPLATE/documentation.yaml @@ -1,4 +1,3 @@ - name: Documentation description: Report an issue with our documentation labels: 'issue: docs, issue: triage' diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index f64640789..7c3199829 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,4 +1,3 @@ - name: Feature request description: Suggest an idea for this project labels: 'issue: feature request, issue: triage' diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f6e75614c..42f0d297a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,23 +5,23 @@ version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - target-branch: "develop" + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests + target-branch: 'develop' schedule: - interval: "weekly" + interval: 'weekly' commit-message: - prefix: "chore(deps)" + prefix: 'chore(deps)' labels: - - "PR: chore" - - "PR: dependencies" - - package-ecosystem: "github-actions" # See documentation for possible values - directory: "/" - target-branch: "develop" + - 'PR: chore' + - 'PR: dependencies' + - package-ecosystem: 'github-actions' # See documentation for possible values + directory: '/' + target-branch: 'develop' schedule: - interval: "weekly" + interval: 'weekly' commit-message: - prefix: "chore(deps)" + prefix: 'chore(deps)' labels: - - "PR: chore" - - "PR: dependencies" + - 'PR: chore' + - 'PR: dependencies' diff --git a/.github/release.yml b/.github/release.yml index 2c5b11486..a6b8dc3ef 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -4,7 +4,7 @@ changelog: exclude: labels: - ignore-for-release - - "PR: chore" + - 'PR: chore' authors: - dependabot categories: @@ -16,17 +16,17 @@ changelog: - deprecation - title: New features ✨ labels: - - "PR: feature" + - 'PR: feature' - title: Bug fixes 🐛 labels: - - "PR: fix" + - 'PR: fix' - title: Cleanup ♻️ labels: - - "PR: docs" - - "PR: refactor" + - 'PR: docs' + - 'PR: refactor' - title: Reverted changes ⎌ labels: - - "PR: revert" + - 'PR: revert' - title: Other changes labels: - - "*" + - '*' diff --git a/.github/workflows/appengine_deploy.yml b/.github/workflows/appengine_deploy.yml index fc27a2c46..cabca25be 100644 --- a/.github/workflows/appengine_deploy.yml +++ b/.github/workflows/appengine_deploy.yml @@ -42,7 +42,7 @@ jobs: path: _deploy/ - name: Deploy to App Engine - uses: google-github-actions/deploy-appengine@v1.2.2 + uses: google-github-actions/deploy-appengine@v1.2.7 # For parameters see: # https://github.com/google-github-actions/deploy-appengine#inputs with: diff --git a/.github/workflows/assign_reviewers.yml b/.github/workflows/assign_reviewers.yml index 8facfc743..765f0e655 100644 --- a/.github/workflows/assign_reviewers.yml +++ b/.github/workflows/assign_reviewers.yml @@ -16,26 +16,26 @@ jobs: requested-reviewer: runs-on: ubuntu-latest steps: - - name: Assign requested reviewer - uses: actions/github-script@v6 - with: - script: | - try { - if (context.payload.pull_request === undefined) { - throw new Error("Can't get pull_request payload. " + - 'Check a request reviewer event was triggered.'); + - name: Assign requested reviewer + uses: actions/github-script@v6 + with: + script: | + try { + if (context.payload.pull_request === undefined) { + throw new Error("Can't get pull_request payload. " + + 'Check a request reviewer event was triggered.'); + } + const reviewers = context.payload.pull_request.requested_reviewers; + // Assignees takes in a list of logins rather than the + // reviewer object. + const reviewerNames = reviewers.map(reviewer => reviewer.login); + const {number:issue_number} = context.payload.pull_request; + github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + assignees: reviewerNames + }); + } catch (error) { + core.setFailed(error.message); } - const reviewers = context.payload.pull_request.requested_reviewers; - // Assignees takes in a list of logins rather than the - // reviewer object. - const reviewerNames = reviewers.map(reviewer => reviewer.login); - const {number:issue_number} = context.payload.pull_request; - github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue_number, - assignees: reviewerNames - }); - } catch (error) { - core.setFailed(error.message); - } diff --git a/.github/workflows/browser_test.yml b/.github/workflows/browser_test.yml new file mode 100644 index 000000000..16337cdd6 --- /dev/null +++ b/.github/workflows/browser_test.yml @@ -0,0 +1,59 @@ +# This workflow will do a clean install, start the selenium server, and run +# all of our browser based tests + +name: Run browser manually + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + timeout-minutes: 10 + runs-on: ${{ matrix.os }} + + strategy: + matrix: + # TODO (#2114): re-enable osx build. + # os: [ubuntu-latest, macos-latest] + os: [macos-latest] + node-version: [16.x, 18.x, 20.x] + # See supported Node.js release schedule at + # https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Reconfigure git to use HTTP authentication + run: > + git config --global url."https://github.com/".insteadOf + ssh://git@github.com/ + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Npm Install + run: npm install + + - name: Linux Test Setup + if: runner.os == 'Linux' + run: source ./tests/scripts/setup_linux_env.sh + + - name: MacOS Test Setup + if: runner.os == 'macOS' + run: source ./tests/scripts/setup_osx_env.sh + + - name: Run Build + run: npm run build + + - name: Run Test + run: npm run test:browser + + env: + CI: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8dd45b064..484faf969 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,70 +18,72 @@ jobs: # TODO (#2114): re-enable osx build. # os: [ubuntu-latest, macos-latest] os: [ubuntu-latest] - node-version: [14.x, 16.x, 18.x] + node-version: [16.x, 18.x, 20.x] # See supported Node.js release schedule at # https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 - with: - persist-credentials: false + - uses: actions/checkout@v3 + with: + persist-credentials: false - - name: Reconfigure git to use HTTP authentication - run: > - git config --global url."https://github.com/".insteadOf - ssh://git@github.com/ + - name: Reconfigure git to use HTTP authentication + run: > + git config --global url."https://github.com/".insteadOf + ssh://git@github.com/ - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} - - name: Npm Install - run: npm install + - name: Npm Install + run: npm install - - name: Linux Test Setup - if: runner.os == 'Linux' - run: source ./tests/scripts/setup_linux_env.sh + - name: Linux Test Setup + if: runner.os == 'Linux' + run: source ./tests/scripts/setup_linux_env.sh - - name: MacOS Test Setup - if: runner.os == 'macOS' - run: source ./tests/scripts/setup_osx_env.sh + - name: MacOS Test Setup + if: runner.os == 'macOS' + run: source ./tests/scripts/setup_osx_env.sh - - name: Run - run: npm run test + - name: Run + run: npm run test - env: - CI: true + env: + CI: true lint: timeout-minutes: 5 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Use Node.js 16.x - uses: actions/setup-node@v3 - with: - node-version: 16.x + - name: Use Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x - - name: Npm Install - run: npm install + - name: Npm Install + run: npm install - - name: Lint - run: npm run lint + - name: Lint + run: npm run lint - clang-formatter: + format: timeout-minutes: 5 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - uses: DoozyX/clang-format-lint-action@v0.15 - with: - source: 'core' - extensions: 'js,ts' - # This should be as close as possible to the version that the npm - # package supports. This can be found by running: - # npx clang-format --version. - clangFormatVersion: 15 + - name: Use Node.js 20.x + uses: actions/setup-node@v3 + with: + node-version: 20.x + + - name: Npm Install + run: npm install + + - name: Check Format + run: npm run format:check diff --git a/.github/workflows/conventional-label.yml b/.github/workflows/conventional-label.yml index a7ab2c18b..64289d987 100644 --- a/.github/workflows/conventional-label.yml +++ b/.github/workflows/conventional-label.yml @@ -10,7 +10,8 @@ jobs: steps: - uses: bcoe/conventional-release-labels@v1 with: - type_labels: '{"feat": "PR: feature", "fix": "PR: fix", "breaking": "breaking + type_labels: + '{"feat": "PR: feature", "fix": "PR: fix", "breaking": "breaking change", "chore": "PR: chore", "docs": "PR: docs", "refactor": "PR: refactor", "revert": "PR: revert", "deprecate": "deprecation"}' ignored_types: '[]' diff --git a/.github/workflows/develop_freeze.yml b/.github/workflows/develop_freeze.yml index d049b9f51..395a34434 100644 --- a/.github/workflows/develop_freeze.yml +++ b/.github/workflows/develop_freeze.yml @@ -23,4 +23,4 @@ jobs: uses: github-actions-up-and-running/pr-comment@f1f8ab2bf00dce6880a369ce08758a60c61d6c0b with: repo-token: ${{ secrets.GITHUB_TOKEN }} - message: "Thanks for the PR! The develop branch is currently frozen in preparation for the release so it may not be addressed until after release week." + message: 'Thanks for the PR! The develop branch is currently frozen in preparation for the release so it may not be addressed until after release week.' diff --git a/.github/workflows/tag_module_cleanup.yml b/.github/workflows/tag_module_cleanup.yml index d5555d962..e0186af67 100644 --- a/.github/workflows/tag_module_cleanup.yml +++ b/.github/workflows/tag_module_cleanup.yml @@ -12,7 +12,6 @@ on: jobs: tag-module-cleanup: - # Add the type: cleanup label runs-on: ubuntu-latest steps: diff --git a/.github/workflows/update_metadata.yml b/.github/workflows/update_metadata.yml deleted file mode 100644 index 33e58a285..000000000 --- a/.github/workflows/update_metadata.yml +++ /dev/null @@ -1,46 +0,0 @@ -# This workflow updates the check_metadata.sh script, which compares the current -# size of build artifacts against their size in the previous version of Blockly. - -name: Update Metadata - -on: [workflow_dispatch] - -permissions: - contents: read - -jobs: - update-metadata: - permissions: - contents: write # for peter-evans/create-pull-request to create branch - pull-requests: write # for peter-evans/create-pull-request to create a PR - runs-on: ubuntu-latest - - steps: - - name: Check Out Blockly - uses: actions/checkout@v3 - with: - ref: 'develop' - - - name: Use Node.js 16.x - uses: actions/setup-node@v3 - with: - node-version: 16.x - - - name: Build Blockly - run: npm run build:compressed - - - name: Build Blockly blocks - run: npm run build:blocks - - - name: Update Metadata - run: source ./tests/scripts/update_metadata.sh - - - name: Create Pull Request - uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 - with: - commit-message: Update build artifact sizes in check_metadata.sh - delete-branch: true - title: Update build artifact sizes in check_metadata.sh - - - name: View Pull Request - run: echo "View Pull Request - ${{ steps.cpr.outputs.pull-request-url }}" \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..64dd749f1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,31 @@ +# Build Artifacts +/msg/* +/build/* +/dist/* +/typings/* +/docs/* + +# Tests other than mocha unit tests +/tests/blocks/* +/tests/themes/* +/tests/compile/* +/tests/jsunit/* +/tests/generators/* +/tests/mocha/webdriver.js +/tests/screenshot/* +/tests/test_runner.js +/tests/workspace_svg/* + +# Demos, scripts, misc +/node_modules/* +/generators/* +/demos/* +/appengine/* +/externs/* +/closure/* +/scripts/gulpfiles/* +CHANGELOG.md +PULL_REQUEST_TEMPLATE.md + +# Don't bother formatting js blocks since we're getting rid of them +/blocks/*.js diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..bbfb46639 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,13 @@ +// This config attempts to match google-style code. + +module.exports = { + // Prefer single quotes, but minimize escaping. + singleQuote: true, + // Some properties must be quoted to preserve closure compiler behavior. + // Don't ever change whether properties are quoted. + quoteProps: 'preserve', + // Don't add spaces around braces for object literals. + bracketSpacing: false, + // Put HTML tag closing brackets on same line as last attribute. + bracketSameLine: true, +}; diff --git a/README.md b/README.md index a0ab5a8bf..45998fb37 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Blockly -Google's Blockly is a library that adds a visual code editor to web and mobile apps. The Blockly editor uses interlocking, graphical blocks to represent code concepts like variables, logical expressions, loops, and more. It allows users to apply programming principles without having to worry about syntax or the intimidation of a blinking cursor on the command line. All code is free and open source. +Google's Blockly is a library that adds a visual code editor to web and mobile apps. The Blockly editor uses interlocking, graphical blocks to represent code concepts like variables, logical expressions, loops, and more. It allows users to apply programming principles without having to worry about syntax or the intimidation of a blinking cursor on the command line. All code is free and open source. ![](https://developers.google.com/blockly/images/sample.png) @@ -8,13 +8,13 @@ Google's Blockly is a library that adds a visual code editor to web and mobile a Blockly has many resources for learning how to use the library. Start at our [Google Developers Site](https://developers.google.com/blockly) to read the documentation on how to get started, configure Blockly, and integrate it into your application. The developers site also contains links to: -* [Getting Started article](https://developers.google.com/blockly/guides/get-started/web) -* [Getting Started codelab](https://blocklycodelabs.dev/codelabs/getting-started/index.html#0) -* [More codelabs](https://blocklycodelabs.dev/) -* [Demos and plugins](https://google.github.io/blockly-samples/) +- [Getting Started article](https://developers.google.com/blockly/guides/get-started/web) +- [Getting Started codelab](https://blocklycodelabs.dev/codelabs/getting-started/index.html#0) +- [More codelabs](https://blocklycodelabs.dev/) +- [Demos and plugins](https://google.github.io/blockly-samples/) Help us focus our development efforts by telling us [what you are doing with -Blockly](https://developers.google.com/blockly/registration). The questionnaire only takes +Blockly](https://developers.google.com/blockly/registration). The questionnaire only takes a few minutes and will help us better support the Blockly community. ### Installing Blockly @@ -28,8 +28,9 @@ npm install blockly For more information on installing and using Blockly, see the [Getting Started article](https://developers.google.com/blockly/guides/get-started/web). ### Getting Help -* [Report a bug](https://developers.google.com/blockly/guides/modify/contribute/write_a_good_issue) or file a feature request on GitHub -* Ask a question, or search others' questions, on our [developer forum](https://groups.google.com/forum/#!forum/blockly). You can also drop by to say hello and show us your prototypes; collectively we have a lot of experience and can offer hints which will save you time. We actively monitor the forums and typically respond to questions within 2 working days. + +- [Report a bug](https://developers.google.com/blockly/guides/modify/contribute/write_a_good_issue) or file a feature request on GitHub +- Ask a question, or search others' questions, on our [developer forum](https://groups.google.com/forum/#!forum/blockly). You can also drop by to say hello and show us your prototypes; collectively we have a lot of experience and can offer hints which will save you time. We actively monitor the forums and typically respond to questions within 2 working days. ### blockly-samples @@ -50,6 +51,7 @@ We now have a [beta release on npm](https://www.npmjs.com/package/blockly?active ```bash npm install blockly@beta ``` + As it is a beta channel, it may be less stable, and the APIs there are subject to change. ### Branches @@ -82,5 +84,5 @@ We typically triage all bugs within 2 working days, which includes adding any ap ## Good to Know -* Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com) -* We test browsers using [BrowserStack](https://browserstack.com) +- Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com) +- We test browsers using [BrowserStack](https://browserstack.com) diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..bd053b36b --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +exclude: [] diff --git a/api-extractor.json b/api-extractor.json index 649528e7e..6c599d255 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -109,7 +109,7 @@ /** * (REQUIRED) Whether to generate an API report. */ - "enabled": false, + "enabled": false /** * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce @@ -195,7 +195,7 @@ * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/dist/.d.ts" */ - "untrimmedFilePath": "/dist/_rollup.d.ts", + "untrimmedFilePath": "/dist/_rollup.d.ts" /** * Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release. diff --git a/blocks/blocks.js b/blocks/blocks.js deleted file mode 100644 index 5ff127dd9..000000000 --- a/blocks/blocks.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview All the blocks. (Entry point for blocks_compressed.js.) - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks'); - -const colour = goog.require('Blockly.libraryBlocks.colour'); -const lists = goog.require('Blockly.libraryBlocks.lists'); -const logic = goog.require('Blockly.libraryBlocks.logic'); -const loops = goog.require('Blockly.libraryBlocks.loops'); -const math = goog.require('Blockly.libraryBlocks.math'); -const procedures = goog.require('Blockly.libraryBlocks.procedures'); -const texts = goog.require('Blockly.libraryBlocks.texts'); -const variables = goog.require('Blockly.libraryBlocks.variables'); -const variablesDynamic = goog.require('Blockly.libraryBlocks.variablesDynamic'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; - - -exports.colour = colour; -exports.lists = lists; -exports.logic = logic; -exports.loops = loops; -exports.math = math; -exports.procedures = procedures; -exports.texts = texts; -exports.variables = variables; -exports.variablesDynamic = variablesDynamic; - -/** - * A dictionary of the block definitions provided by all the - * Blockly.libraryBlocks.* modules. - * @type {!Object} - */ -const blocks = Object.assign( - {}, colour.blocks, lists.blocks, logic.blocks, loops.blocks, math.blocks, - procedures.blocks, variables.blocks, variablesDynamic.blocks); -exports.blocks = blocks; diff --git a/blocks/blocks.ts b/blocks/blocks.ts new file mode 100644 index 000000000..a147b360d --- /dev/null +++ b/blocks/blocks.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks'); + +import * as colour from './colour.js'; +import * as lists from './lists.js'; +import * as logic from './logic.js'; +import * as loops from './loops.js'; +import * as math from './math.js'; +import * as procedures from './procedures.js'; +import * as texts from './text.js'; +import * as variables from './variables.js'; +import * as variablesDynamic from './variables_dynamic.js'; +import type {BlockDefinition} from '../core/blocks.js'; + +export { + colour, + lists, + loops, + math, + procedures, + texts, + variables, + variablesDynamic, +}; + +/** + * A dictionary of the block definitions provided by all the + * Blockly.libraryBlocks.* modules. + */ +export const blocks: {[key: string]: BlockDefinition} = Object.assign( + {}, + colour.blocks, + lists.blocks, + logic.blocks, + loops.blocks, + math.blocks, + procedures.blocks, + variables.blocks, + variablesDynamic.blocks +); diff --git a/blocks/colour.ts b/blocks/colour.ts index b01c047b7..f9fb7e418 100644 --- a/blocks/colour.ts +++ b/blocks/colour.ts @@ -4,18 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Colour blocks for Blockly. - */ - import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.libraryBlocks.colour'); -import type {BlockDefinition} from '../core/blocks.js'; -import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; import '../core/field_colour.js'; - /** * A dictionary of the block definitions provided by this module. */ @@ -52,7 +49,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'colour_rgb', 'message0': - '%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3', + '%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3', 'args0': [ { 'type': 'input_value', @@ -82,8 +79,9 @@ export const blocks = createBlockDefinitionsFromJsonArray([ // Block for blending two colours together. { 'type': 'colour_blend', - 'message0': '%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} ' + - '%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3', + 'message0': + '%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} ' + + '%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3', 'args0': [ { 'type': 'input_value', diff --git a/blocks/lists.js b/blocks/lists.ts similarity index 57% rename from blocks/lists.js rename to blocks/lists.ts index 526329300..3c2b101b8 100644 --- a/blocks/lists.js +++ b/blocks/lists.ts @@ -4,38 +4,30 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview List blocks for Blockly. - * @suppress {checkTypes} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks.lists'); - -const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const xmlUtils = goog.require('Blockly.utils.xml'); -const {Align} = goog.require('Blockly.Input'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {ConnectionType} = goog.require('Blockly.ConnectionType'); -const {Msg} = goog.require('Blockly.Msg'); -const {Mutator} = goog.require('Blockly.Mutator'); -/* eslint-disable-next-line no-unused-vars */ -const {Workspace} = goog.requireType('Blockly.Workspace'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldDropdown'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.lists'); +import * as fieldRegistry from '../core/field_registry.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import {Align} from '../core/inputs/align.js'; +import type {Block} from '../core/block.js'; +import type {Connection} from '../core/connection.js'; +import type {BlockSvg} from '../core/block_svg.js'; +import type {FieldDropdown} from '../core/field_dropdown.js'; +import {Msg} from '../core/msg.js'; +import {MutatorIcon} from '../core/icons/mutator_icon.js'; +import type {Workspace} from '../core/workspace.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; +import '../core/field_dropdown.js'; +import {ValueInput} from '../core/inputs/value_input.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for creating an empty list // The 'list_create_with' block is preferred as it is more flexible. // @@ -119,98 +111,119 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'helpUrl': '%{BKY_LISTS_LENGTH_HELPURL}', }, ]); -exports.blocks = blocks; -blocks['lists_create_with'] = { +/** Type of a 'lists_create_with' block. */ +type CreateWithBlock = Block & ListCreateWithMixin; +interface ListCreateWithMixin extends ListCreateWithMixinType { + itemCount_: number; +} +type ListCreateWithMixinType = typeof LISTS_CREATE_WITH; + +const LISTS_CREATE_WITH = { /** * Block for creating a list with any number of elements of any type. - * @this {Block} */ - init: function() { + init: function (this: CreateWithBlock) { this.setHelpUrl(Msg['LISTS_CREATE_WITH_HELPURL']); this.setStyle('list_blocks'); this.itemCount_ = 3; this.updateShape_(); this.setOutput(true, 'Array'); - this.setMutator(new Mutator(['lists_create_with_item'], this)); + this.setMutator( + new MutatorIcon(['lists_create_with_item'], this as unknown as BlockSvg) + ); // BUG(#6905) this.setTooltip(Msg['LISTS_CREATE_WITH_TOOLTIP']); }, /** * Create XML to represent list inputs. * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} */ - mutationToDom: function() { + mutationToDom: function (this: CreateWithBlock): Element { const container = xmlUtils.createElement('mutation'); - container.setAttribute('items', this.itemCount_); + container.setAttribute('items', String(this.itemCount_)); return container; }, /** * Parse XML to restore the list inputs. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10); + domToMutation: function (this: CreateWithBlock, xmlElement: Element) { + const items = xmlElement.getAttribute('items'); + if (!items) throw new TypeError('element did not have items'); + this.itemCount_ = parseInt(items, 10); this.updateShape_(); }, /** * Returns the state of this block as a JSON serializable object. - * @return {{itemCount: number}} The state of this block, ie the item count. + * + * @returns The state of this block, ie the item count. */ - saveExtraState: function() { + saveExtraState: function (this: CreateWithBlock): {itemCount: number} { return { 'itemCount': this.itemCount_, }; }, /** * Applies the given state to this block. - * @param {*} state The state to apply to this block, ie the item count. + * + * @param state The state to apply to this block, ie the item count. */ - loadExtraState: function(state) { + loadExtraState: function (this: CreateWithBlock, state: AnyDuringMigration) { this.itemCount_ = state['itemCount']; this.updateShape_(); }, /** * Populate the mutator's dialog with this block's components. - * @param {!Workspace} workspace Mutator's workspace. - * @return {!Block} Root block in mutator. - * @this {Block} + * + * @param workspace Mutator's workspace. + * @returns Root block in mutator. */ - decompose: function(workspace) { - const containerBlock = workspace.newBlock('lists_create_with_container'); - containerBlock.initSvg(); - let connection = containerBlock.getInput('STACK').connection; + decompose: function ( + this: CreateWithBlock, + workspace: Workspace + ): ContainerBlock { + const containerBlock = workspace.newBlock( + 'lists_create_with_container' + ) as ContainerBlock; + (containerBlock as BlockSvg).initSvg(); + let connection = containerBlock.getInput('STACK')!.connection; for (let i = 0; i < this.itemCount_; i++) { - const itemBlock = workspace.newBlock('lists_create_with_item'); - itemBlock.initSvg(); - connection.connect(itemBlock.previousConnection); + const itemBlock = workspace.newBlock( + 'lists_create_with_item' + ) as ItemBlock; + (itemBlock as BlockSvg).initSvg(); + if (!itemBlock.previousConnection) { + throw new Error('itemBlock has no previousConnection'); + } + connection!.connect(itemBlock.previousConnection); connection = itemBlock.nextConnection; } return containerBlock; }, /** * Reconfigure this block based on the mutator dialog's components. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - compose: function(containerBlock) { - let itemBlock = containerBlock.getInputTargetBlock('STACK'); + compose: function (this: CreateWithBlock, containerBlock: Block) { + let itemBlock: ItemBlock | null = containerBlock.getInputTargetBlock( + 'STACK' + ) as ItemBlock; // Count number of inputs. - const connections = []; + const connections: Connection[] = []; while (itemBlock) { if (itemBlock.isInsertionMarker()) { - itemBlock = itemBlock.getNextBlock(); + itemBlock = itemBlock.getNextBlock() as ItemBlock | null; continue; } - connections.push(itemBlock.valueConnection_); - itemBlock = itemBlock.getNextBlock(); + connections.push(itemBlock.valueConnection_ as Connection); + itemBlock = itemBlock.getNextBlock() as ItemBlock | null; } // Disconnect any children that don't belong. for (let i = 0; i < this.itemCount_; i++) { - const connection = this.getInput('ADD' + i).connection.targetConnection; + const connection = this.getInput('ADD' + i)!.connection!.targetConnection; if (connection && connections.indexOf(connection) === -1) { connection.disconnect(); } @@ -219,39 +232,41 @@ blocks['lists_create_with'] = { this.updateShape_(); // Reconnect any child blocks. for (let i = 0; i < this.itemCount_; i++) { - Mutator.reconnect(connections[i], this, 'ADD' + i); + connections[i]?.reconnect(this, 'ADD' + i); } }, /** * Store pointers to any connected child blocks. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - saveConnections: function(containerBlock) { - let itemBlock = containerBlock.getInputTargetBlock('STACK'); + saveConnections: function (this: CreateWithBlock, containerBlock: Block) { + let itemBlock: ItemBlock | null = containerBlock.getInputTargetBlock( + 'STACK' + ) as ItemBlock; let i = 0; while (itemBlock) { if (itemBlock.isInsertionMarker()) { - itemBlock = itemBlock.getNextBlock(); + itemBlock = itemBlock.getNextBlock() as ItemBlock | null; continue; } const input = this.getInput('ADD' + i); - itemBlock.valueConnection_ = input && input.connection.targetConnection; - itemBlock = itemBlock.getNextBlock(); + itemBlock.valueConnection_ = input?.connection! + .targetConnection as Connection; + itemBlock = itemBlock.getNextBlock() as ItemBlock | null; i++; } }, /** * Modify this block to have the correct number of inputs. - * @private - * @this {Block} */ - updateShape_: function() { + updateShape_: function (this: CreateWithBlock) { if (this.itemCount_ && this.getInput('EMPTY')) { this.removeInput('EMPTY'); } else if (!this.itemCount_ && !this.getInput('EMPTY')) { this.appendDummyInput('EMPTY').appendField( - Msg['LISTS_CREATE_EMPTY_TITLE']); + Msg['LISTS_CREATE_EMPTY_TITLE'] + ); } // Add new inputs. for (let i = 0; i < this.itemCount_; i++) { @@ -268,28 +283,41 @@ blocks['lists_create_with'] = { } }, }; +blocks['lists_create_with'] = LISTS_CREATE_WITH; -blocks['lists_create_with_container'] = { +/** Type for a 'lists_create_with_container' block. */ +type ContainerBlock = Block & ContainerMutator; +interface ContainerMutator extends ContainerMutatorType {} +type ContainerMutatorType = typeof LISTS_CREATE_WITH_CONTAINER; + +const LISTS_CREATE_WITH_CONTAINER = { /** * Mutator block for list container. - * @this {Block} */ - init: function() { + init: function (this: ContainerBlock) { this.setStyle('list_blocks'); this.appendDummyInput().appendField( - Msg['LISTS_CREATE_WITH_CONTAINER_TITLE_ADD']); + Msg['LISTS_CREATE_WITH_CONTAINER_TITLE_ADD'] + ); this.appendStatementInput('STACK'); this.setTooltip(Msg['LISTS_CREATE_WITH_CONTAINER_TOOLTIP']); this.contextMenu = false; }, }; +blocks['lists_create_with_container'] = LISTS_CREATE_WITH_CONTAINER; -blocks['lists_create_with_item'] = { +/** Type for a 'lists_create_with_item' block. */ +type ItemBlock = Block & ItemMutator; +interface ItemMutator extends ItemMutatorType { + valueConnection_?: Connection; +} +type ItemMutatorType = typeof LISTS_CREATE_WITH_ITEM; + +const LISTS_CREATE_WITH_ITEM = { /** * Mutator block for adding items. - * @this {Block} */ - init: function() { + init: function (this: ItemBlock) { this.setStyle('list_blocks'); this.appendDummyInput().appendField(Msg['LISTS_CREATE_WITH_ITEM_TITLE']); this.setPreviousStatement(true); @@ -298,13 +326,18 @@ blocks['lists_create_with_item'] = { this.contextMenu = false; }, }; +blocks['lists_create_with_item'] = LISTS_CREATE_WITH_ITEM; -blocks['lists_indexOf'] = { +/** Type for a 'lists_indexOf' block. */ +type IndexOfBlock = Block & IndexOfMutator; +interface IndexOfMutator extends IndexOfMutatorType {} +type IndexOfMutatorType = typeof LISTS_INDEXOF; + +const LISTS_INDEXOF = { /** * Block for finding an item in the list. - * @this {Block} */ - init: function() { + init: function (this: IndexOfBlock) { const OPERATORS = [ [Msg['LISTS_INDEX_OF_FIRST'], 'FIRST'], [Msg['LISTS_INDEX_OF_LAST'], 'LAST'], @@ -312,29 +345,38 @@ blocks['lists_indexOf'] = { this.setHelpUrl(Msg['LISTS_INDEX_OF_HELPURL']); this.setStyle('list_blocks'); this.setOutput(true, 'Number'); - this.appendValueInput('VALUE').setCheck('Array').appendField( - Msg['LISTS_INDEX_OF_INPUT_IN_LIST']); + this.appendValueInput('VALUE') + .setCheck('Array') + .appendField(Msg['LISTS_INDEX_OF_INPUT_IN_LIST']); const operatorsDropdown = fieldRegistry.fromJson({ type: 'field_dropdown', options: OPERATORS, }); + if (!operatorsDropdown) throw new Error('field_dropdown not found'); this.appendValueInput('FIND').appendField(operatorsDropdown, 'END'); this.setInputsInline(true); - // Assign 'this' to a variable for use in the tooltip closure below. - const thisBlock = this; - this.setTooltip(function() { + this.setTooltip(() => { return Msg['LISTS_INDEX_OF_TOOLTIP'].replace( - '%1', thisBlock.workspace.options.oneBasedIndex ? '0' : '-1'); + '%1', + this.workspace.options.oneBasedIndex ? '0' : '-1' + ); }); }, }; +blocks['lists_indexOf'] = LISTS_INDEXOF; -blocks['lists_getIndex'] = { +/** Type for a 'lists_getIndex' block. */ +type GetIndexBlock = Block & GetIndexMutator; +interface GetIndexMutator extends GetIndexMutatorType { + WHERE_OPTIONS: Array<[string, string]>; +} +type GetIndexMutatorType = typeof LISTS_GETINDEX; + +const LISTS_GETINDEX = { /** * Block for getting element at index. - * @this {Block} */ - init: function() { + init: function (this: GetIndexBlock) { const MODE = [ [Msg['LISTS_GET_INDEX_GET'], 'GET'], [Msg['LISTS_GET_INDEX_GET_REMOVE'], 'GET_REMOVE'], @@ -352,21 +394,21 @@ blocks['lists_getIndex'] = { const modeMenu = fieldRegistry.fromJson({ type: 'field_dropdown', options: MODE, - }); + }) as FieldDropdown; modeMenu.setValidator( - /** - * @param {*} value The input value. - * @this {FieldDropdown} - */ - function(value) { - const isStatement = (value === 'REMOVE'); - this.getSourceBlock().updateStatement_(isStatement); - }); - this.appendValueInput('VALUE').setCheck('Array').appendField( - Msg['LISTS_GET_INDEX_INPUT_IN_LIST']); + /** @param value The input value. */ + function (this: FieldDropdown, value: string) { + const isStatement = value === 'REMOVE'; + (this.getSourceBlock() as GetIndexBlock).updateStatement_(isStatement); + return undefined; + } + ); + this.appendValueInput('VALUE') + .setCheck('Array') + .appendField(Msg['LISTS_GET_INDEX_INPUT_IN_LIST']); this.appendDummyInput() - .appendField(modeMenu, 'MODE') - .appendField('', 'SPACE'); + .appendField(modeMenu, 'MODE') + .appendField('', 'SPACE'); this.appendDummyInput('AT'); if (Msg['LISTS_GET_INDEX_TAIL']) { this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_INDEX_TAIL']); @@ -374,11 +416,9 @@ blocks['lists_getIndex'] = { this.setInputsInline(true); this.setOutput(true); this.updateAt_(true); - // Assign 'this' to a variable for use in the tooltip closure below. - const thisBlock = this; - this.setTooltip(function() { - const mode = thisBlock.getFieldValue('MODE'); - const where = thisBlock.getFieldValue('WHERE'); + this.setTooltip(() => { + const mode = this.getFieldValue('MODE'); + const where = this.getFieldValue('WHERE'); let tooltip = ''; switch (mode + ' ' + where) { case 'GET FROM_START': @@ -422,12 +462,13 @@ blocks['lists_getIndex'] = { break; } if (where === 'FROM_START' || where === 'FROM_END') { - const msg = (where === 'FROM_START') ? - Msg['LISTS_INDEX_FROM_START_TOOLTIP'] : - Msg['LISTS_INDEX_FROM_END_TOOLTIP']; - tooltip += ' ' + - msg.replace( - '%1', thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0'); + const msg = + where === 'FROM_START' + ? Msg['LISTS_INDEX_FROM_START_TOOLTIP'] + : Msg['LISTS_INDEX_FROM_END_TOOLTIP']; + tooltip += + ' ' + + msg.replace('%1', this.workspace.options.oneBasedIndex ? '#1' : '#0'); } return tooltip; }); @@ -435,41 +476,42 @@ blocks['lists_getIndex'] = { /** * Create XML to represent whether the block is a statement or a value. * Also represent whether there is an 'AT' input. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: GetIndexBlock): Element { const container = xmlUtils.createElement('mutation'); const isStatement = !this.outputConnection; - container.setAttribute('statement', isStatement); - const isAt = this.getInput('AT').type === ConnectionType.INPUT_VALUE; - container.setAttribute('at', isAt); + container.setAttribute('statement', String(isStatement)); + const isAt = this.getInput('AT') instanceof ValueInput; + container.setAttribute('at', String(isAt)); return container; }, /** * Parse XML to restore the 'AT' input. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { + domToMutation: function (this: GetIndexBlock, xmlElement: Element) { // Note: Until January 2013 this block did not have mutations, // so 'statement' defaults to false and 'at' defaults to true. - const isStatement = (xmlElement.getAttribute('statement') === 'true'); + const isStatement = xmlElement.getAttribute('statement') === 'true'; this.updateStatement_(isStatement); - const isAt = (xmlElement.getAttribute('at') !== 'false'); + const isAt = xmlElement.getAttribute('at') !== 'false'; this.updateAt_(isAt); }, - /** * Returns the state of this block as a JSON serializable object. * Returns null for efficiency if no state is needed (not a statement) - * @return {?{isStatement: boolean}} The state of this block, ie whether it's - * a statement. + * + * @returns The state of this block, ie whether it's a statement. */ - saveExtraState: function() { + saveExtraState: function (this: GetIndexBlock): { + isStatement: boolean; + } | null { if (!this.outputConnection) { return { - 'isStatement': true, + isStatement: true, }; } return null; @@ -477,10 +519,11 @@ blocks['lists_getIndex'] = { /** * Applies the given state to this block. - * @param {*} state The state to apply to this block, ie whether it's a + * + * @param state The state to apply to this block, ie whether it's a * statement. */ - loadExtraState: function(state) { + loadExtraState: function (this: GetIndexBlock, state: AnyDuringMigration) { if (state['isStatement']) { this.updateStatement_(true); } else if (typeof state === 'string') { @@ -491,15 +534,15 @@ blocks['lists_getIndex'] = { /** * Switch between a value block and a statement block. - * @param {boolean} newStatement True if the block should be a statement. + * + * @param newStatement True if the block should be a statement. * False if the block should be a value. - * @private - * @this {Block} */ - updateStatement_: function(newStatement) { + updateStatement_: function (this: GetIndexBlock, newStatement: boolean) { const oldStatement = !this.outputConnection; if (newStatement !== oldStatement) { - this.unplug(true, true); + // TODO(#6920): The .unplug only has one parameter. + (this.unplug as (arg0?: boolean, arg1?: boolean) => void)(true, true); if (newStatement) { this.setOutput(false); this.setPreviousStatement(true); @@ -513,11 +556,10 @@ blocks['lists_getIndex'] = { }, /** * Create or delete an input for the numeric index. - * @param {boolean} isAt True if the input should exist. - * @private - * @this {Block} + * + * @param isAt True if the input should exist. */ - updateAt_: function(isAt) { + updateAt_: function (this: GetIndexBlock, isAt: boolean) { // Destroy old 'AT' and 'ORDINAL' inputs. this.removeInput('AT'); this.removeInput('ORDINAL', true); @@ -526,7 +568,8 @@ blocks['lists_getIndex'] = { this.appendValueInput('AT').setCheck('Number'); if (Msg['ORDINAL_NUMBER_SUFFIX']) { this.appendDummyInput('ORDINAL').appendField( - Msg['ORDINAL_NUMBER_SUFFIX']); + Msg['ORDINAL_NUMBER_SUFFIX'] + ); } } else { this.appendDummyInput('AT'); @@ -534,41 +577,47 @@ blocks['lists_getIndex'] = { const menu = fieldRegistry.fromJson({ type: 'field_dropdown', options: this.WHERE_OPTIONS, - }); + }) as FieldDropdown; menu.setValidator( - /** - * @param {*} value The input value. - * @this {FieldDropdown} - * @return {null|undefined} Null if the field has been replaced; - * otherwise undefined. - */ - function(value) { - const newAt = (value === 'FROM_START') || (value === 'FROM_END'); - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock(); - block.updateAt_(newAt); - // This menu has been destroyed and replaced. Update the - // replacement. - block.setFieldValue(value, 'WHERE'); - return null; - } - return undefined; - }); - this.getInput('AT').appendField(menu, 'WHERE'); + /** + * @param value The input value. + * @returns Null if the field has been replaced; otherwise undefined. + */ + function (this: FieldDropdown, value: string) { + const newAt = value === 'FROM_START' || value === 'FROM_END'; + // The 'isAt' variable is available due to this function being a + // closure. + if (newAt !== isAt) { + const block = this.getSourceBlock() as GetIndexBlock; + block.updateAt_(newAt); + // This menu has been destroyed and replaced. Update the + // replacement. + block.setFieldValue(value, 'WHERE'); + return null; + } + return undefined; + } + ); + this.getInput('AT')!.appendField(menu, 'WHERE'); if (Msg['LISTS_GET_INDEX_TAIL']) { this.moveInputBefore('TAIL', null); } }, }; +blocks['lists_getIndex'] = LISTS_GETINDEX; -blocks['lists_setIndex'] = { +/** Type for a 'lists_setIndex' block. */ +type SetIndexBlock = Block & SetIndexMutator; +interface SetIndexMutator extends SetIndexMutatorType { + WHERE_OPTIONS: Array<[string, string]>; +} +type SetIndexMutatorType = typeof LISTS_SETINDEX; + +const LISTS_SETINDEX = { /** * Block for setting the element at index. - * @this {Block} */ - init: function() { + init: function (this: SetIndexBlock) { const MODE = [ [Msg['LISTS_SET_INDEX_SET'], 'SET'], [Msg['LISTS_SET_INDEX_INSERT'], 'INSERT'], @@ -582,15 +631,16 @@ blocks['lists_setIndex'] = { ]; this.setHelpUrl(Msg['LISTS_SET_INDEX_HELPURL']); this.setStyle('list_blocks'); - this.appendValueInput('LIST').setCheck('Array').appendField( - Msg['LISTS_SET_INDEX_INPUT_IN_LIST']); + this.appendValueInput('LIST') + .setCheck('Array') + .appendField(Msg['LISTS_SET_INDEX_INPUT_IN_LIST']); const operationDropdown = fieldRegistry.fromJson({ type: 'field_dropdown', options: MODE, - }); + }) as FieldDropdown; this.appendDummyInput() - .appendField(operationDropdown, 'MODE') - .appendField('', 'SPACE'); + .appendField(operationDropdown, 'MODE') + .appendField('', 'SPACE'); this.appendDummyInput('AT'); this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']); this.setInputsInline(true); @@ -598,11 +648,9 @@ blocks['lists_setIndex'] = { this.setNextStatement(true); this.setTooltip(Msg['LISTS_SET_INDEX_TOOLTIP']); this.updateAt_(true); - // Assign 'this' to a variable for use in the tooltip closure below. - const thisBlock = this; - this.setTooltip(function() { - const mode = thisBlock.getFieldValue('MODE'); - const where = thisBlock.getFieldValue('WHERE'); + this.setTooltip(() => { + const mode = this.getFieldValue('MODE'); + const where = this.getFieldValue('WHERE'); let tooltip = ''; switch (mode + ' ' + where) { case 'SET FROM_START': @@ -633,33 +681,36 @@ blocks['lists_setIndex'] = { break; } if (where === 'FROM_START' || where === 'FROM_END') { - tooltip += ' ' + - Msg['LISTS_INDEX_FROM_START_TOOLTIP'].replace( - '%1', thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0'); + tooltip += + ' ' + + Msg['LISTS_INDEX_FROM_START_TOOLTIP'].replace( + '%1', + this.workspace.options.oneBasedIndex ? '#1' : '#0' + ); } return tooltip; }); }, /** * Create XML to represent whether there is an 'AT' input. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: SetIndexBlock): Element { const container = xmlUtils.createElement('mutation'); - const isAt = this.getInput('AT').type === ConnectionType.INPUT_VALUE; - container.setAttribute('at', isAt); + const isAt = this.getInput('AT') instanceof ValueInput; + container.setAttribute('at', String(isAt)); return container; }, /** * Parse XML to restore the 'AT' input. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { + domToMutation: function (this: SetIndexBlock, xmlElement: Element) { // Note: Until January 2013 this block did not have mutations, // so 'at' defaults to true. - const isAt = (xmlElement.getAttribute('at') !== 'false'); + const isAt = xmlElement.getAttribute('at') !== 'false'; this.updateAt_(isAt); }, @@ -668,9 +719,10 @@ blocks['lists_setIndex'] = { * This block does not need to serialize any specific state as it is already * encoded in the dropdown values, but must have an implementation to avoid * the backward compatible XML mutations being serialized. - * @return {null} The state of this block. + * + * @returns The state of this block. */ - saveExtraState: function() { + saveExtraState: function (this: SetIndexBlock): null { return null; }, @@ -679,15 +731,14 @@ blocks['lists_setIndex'] = { * No extra state is needed or expected as it is already encoded in the * dropdown values. */ - loadExtraState: function() {}, + loadExtraState: function (this: SetIndexBlock) {}, /** * Create or delete an input for the numeric index. - * @param {boolean} isAt True if the input should exist. - * @private - * @this {Block} + * + * @param isAt True if the input should exist. */ - updateAt_: function(isAt) { + updateAt_: function (this: SetIndexBlock, isAt: boolean) { // Destroy old 'AT' and 'ORDINAL' input. this.removeInput('AT'); this.removeInput('ORDINAL', true); @@ -696,7 +747,8 @@ blocks['lists_setIndex'] = { this.appendValueInput('AT').setCheck('Number'); if (Msg['ORDINAL_NUMBER_SUFFIX']) { this.appendDummyInput('ORDINAL').appendField( - Msg['ORDINAL_NUMBER_SUFFIX']); + Msg['ORDINAL_NUMBER_SUFFIX'] + ); } } else { this.appendDummyInput('AT'); @@ -704,43 +756,50 @@ blocks['lists_setIndex'] = { const menu = fieldRegistry.fromJson({ type: 'field_dropdown', options: this.WHERE_OPTIONS, - }); + }) as FieldDropdown; menu.setValidator( - /** - * @param {*} value The input value. - * @this {FieldDropdown} - * @return {null|undefined} Null if the field has been replaced; - * otherwise undefined. - */ - function(value) { - const newAt = (value === 'FROM_START') || (value === 'FROM_END'); - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock(); - block.updateAt_(newAt); - // This menu has been destroyed and replaced. Update the - // replacement. - block.setFieldValue(value, 'WHERE'); - return null; - } - return undefined; - }); + /** + * @param value The input value. + * @returns Null if the field has been replaced; otherwise undefined. + */ + function (this: FieldDropdown, value: string) { + const newAt = value === 'FROM_START' || value === 'FROM_END'; + // The 'isAt' variable is available due to this function being a + // closure. + if (newAt !== isAt) { + const block = this.getSourceBlock() as SetIndexBlock; + block.updateAt_(newAt); + // This menu has been destroyed and replaced. Update the + // replacement. + block.setFieldValue(value, 'WHERE'); + return null; + } + return undefined; + } + ); this.moveInputBefore('AT', 'TO'); if (this.getInput('ORDINAL')) { this.moveInputBefore('ORDINAL', 'TO'); } - this.getInput('AT').appendField(menu, 'WHERE'); + this.getInput('AT')!.appendField(menu, 'WHERE'); }, }; +blocks['lists_setIndex'] = LISTS_SETINDEX; -blocks['lists_getSublist'] = { +/** Type for a 'lists_getSublist' block. */ +type GetSublistBlock = Block & GetSublistMutator; +interface GetSublistMutator extends GetSublistMutatorType { + WHERE_OPTIONS_1: Array<[string, string]>; + WHERE_OPTIONS_2: Array<[string, string]>; +} +type GetSublistMutatorType = typeof LISTS_GETSUBLIST; + +const LISTS_GETSUBLIST = { /** * Block for getting sublist. - * @this {Block} */ - init: function() { + init: function (this: GetSublistBlock) { this['WHERE_OPTIONS_1'] = [ [Msg['LISTS_GET_SUBLIST_START_FROM_START'], 'FROM_START'], [Msg['LISTS_GET_SUBLIST_START_FROM_END'], 'FROM_END'], @@ -753,8 +812,9 @@ blocks['lists_getSublist'] = { ]; this.setHelpUrl(Msg['LISTS_GET_SUBLIST_HELPURL']); this.setStyle('list_blocks'); - this.appendValueInput('LIST').setCheck('Array').appendField( - Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']); + this.appendValueInput('LIST') + .setCheck('Array') + .appendField(Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']); this.appendDummyInput('AT1'); this.appendDummyInput('AT2'); if (Msg['LISTS_GET_SUBLIST_TAIL']) { @@ -768,25 +828,25 @@ blocks['lists_getSublist'] = { }, /** * Create XML to represent whether there are 'AT' inputs. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: GetSublistBlock): Element { const container = xmlUtils.createElement('mutation'); - const isAt1 = this.getInput('AT1').type === ConnectionType.INPUT_VALUE; - container.setAttribute('at1', isAt1); - const isAt2 = this.getInput('AT2').type === ConnectionType.INPUT_VALUE; - container.setAttribute('at2', isAt2); + const isAt1 = this.getInput('AT1') instanceof ValueInput; + container.setAttribute('at1', String(isAt1)); + const isAt2 = this.getInput('AT2') instanceof ValueInput; + container.setAttribute('at2', String(isAt2)); return container; }, /** * Parse XML to restore the 'AT' inputs. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - const isAt1 = (xmlElement.getAttribute('at1') === 'true'); - const isAt2 = (xmlElement.getAttribute('at2') === 'true'); + domToMutation: function (this: GetSublistBlock, xmlElement: Element) { + const isAt1 = xmlElement.getAttribute('at1') === 'true'; + const isAt2 = xmlElement.getAttribute('at2') === 'true'; this.updateAt_(1, isAt1); this.updateAt_(2, isAt2); }, @@ -796,9 +856,10 @@ blocks['lists_getSublist'] = { * This block does not need to serialize any specific state as it is already * encoded in the dropdown values, but must have an implementation to avoid * the backward compatible XML mutations being serialized. - * @return {null} The state of this block. + * + * @returns The state of this block. */ - saveExtraState: function() { + saveExtraState: function (this: GetSublistBlock): null { return null; }, @@ -807,17 +868,16 @@ blocks['lists_getSublist'] = { * No extra state is needed or expected as it is already encoded in the * dropdown values. */ - loadExtraState: function() {}, + loadExtraState: function (this: GetSublistBlock) {}, /** * Create or delete an input for a numeric index. * This block has two such inputs, independent of each other. - * @param {number} n Specify first or second input (1 or 2). - * @param {boolean} isAt True if the input should exist. - * @private - * @this {Block} + * + * @param n Specify first or second input (1 or 2). + * @param isAt True if the input should exist. */ - updateAt_: function(n, isAt) { + updateAt_: function (this: GetSublistBlock, n: 1 | 2, isAt: boolean) { // Create or delete an input for the numeric index. // Destroy old 'AT' and 'ORDINAL' inputs. this.removeInput('AT' + n); @@ -826,37 +886,38 @@ blocks['lists_getSublist'] = { if (isAt) { this.appendValueInput('AT' + n).setCheck('Number'); if (Msg['ORDINAL_NUMBER_SUFFIX']) { - this.appendDummyInput('ORDINAL' + n) - .appendField(Msg['ORDINAL_NUMBER_SUFFIX']); + this.appendDummyInput('ORDINAL' + n).appendField( + Msg['ORDINAL_NUMBER_SUFFIX'] + ); } } else { this.appendDummyInput('AT' + n); } const menu = fieldRegistry.fromJson({ type: 'field_dropdown', - options: this['WHERE_OPTIONS_' + n], - }); + options: + this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'], + }) as FieldDropdown; menu.setValidator( - /** - * @param {*} value The input value. - * @this {FieldDropdown} - * @return {null|undefined} Null if the field has been replaced; - * otherwise undefined. - */ - function(value) { - const newAt = (value === 'FROM_START') || (value === 'FROM_END'); - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock(); - block.updateAt_(n, newAt); - // This menu has been destroyed and replaced. - // Update the replacement. - block.setFieldValue(value, 'WHERE' + n); - return null; - } - }); - this.getInput('AT' + n).appendField(menu, 'WHERE' + n); + /** + * @param value The input value. + * @returns Null if the field has been replaced; otherwise undefined. + */ + function (this: FieldDropdown, value: string) { + const newAt = value === 'FROM_START' || value === 'FROM_END'; + // The 'isAt' variable is available due to this function being a + // closure. + if (newAt !== isAt) { + const block = this.getSourceBlock() as GetSublistBlock; + block.updateAt_(n, newAt); + // This menu has been destroyed and replaced. + // Update the replacement. + block.setFieldValue(value, 'WHERE' + n); + return null; + } + } + ); + this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n); if (n === 1) { this.moveInputBefore('AT1', 'AT2'); if (this.getInput('ORDINAL1')) { @@ -868,13 +929,15 @@ blocks['lists_getSublist'] = { } }, }; +blocks['lists_getSublist'] = LISTS_GETSUBLIST; + +type SortBlock = Block | (typeof blocks)['lists_sort']; blocks['lists_sort'] = { /** * Block for sorting a list. - * @this {Block} */ - init: function() { + init: function (this: SortBlock) { this.jsonInit({ 'message0': '%{BKY_LISTS_SORT_TITLE}', 'args0': [ @@ -909,14 +972,13 @@ blocks['lists_sort'] = { }, }; +type SplitBlock = Block | (typeof blocks)['lists_split']; + blocks['lists_split'] = { /** * Block for splitting text into a list, or joining a list into text. - * @this {Block} */ - init: function() { - // Assign 'this' to a variable for use in the closures below. - const thisBlock = this; + init: function (this: SplitBlock) { const dropdown = fieldRegistry.fromJson({ type: 'field_dropdown', options: [ @@ -924,19 +986,22 @@ blocks['lists_split'] = { [Msg['LISTS_SPLIT_TEXT_FROM_LIST'], 'JOIN'], ], }); - dropdown.setValidator(function(newMode) { - thisBlock.updateType_(newMode); + if (!dropdown) throw new Error('field_dropdown not found'); + dropdown.setValidator((newMode) => { + this.updateType_(newMode); }); this.setHelpUrl(Msg['LISTS_SPLIT_HELPURL']); this.setStyle('list_blocks'); - this.appendValueInput('INPUT').setCheck('String').appendField( - dropdown, 'MODE'); - this.appendValueInput('DELIM').setCheck('String').appendField( - Msg['LISTS_SPLIT_WITH_DELIMITER']); + this.appendValueInput('INPUT') + .setCheck('String') + .appendField(dropdown, 'MODE'); + this.appendValueInput('DELIM') + .setCheck('String') + .appendField(Msg['LISTS_SPLIT_WITH_DELIMITER']); this.setInputsInline(true); this.setOutput(true, 'Array'); - this.setTooltip(function() { - const mode = thisBlock.getFieldValue('MODE'); + this.setTooltip(() => { + const mode = this.getFieldValue('MODE'); if (mode === 'SPLIT') { return Msg['LISTS_SPLIT_TOOLTIP_SPLIT']; } else if (mode === 'JOIN') { @@ -947,49 +1012,49 @@ blocks['lists_split'] = { }, /** * Modify this block to have the correct input and output types. - * @param {string} newMode Either 'SPLIT' or 'JOIN'. - * @private - * @this {Block} + * + * @param newMode Either 'SPLIT' or 'JOIN'. */ - updateType_: function(newMode) { + updateType_: function (this: SplitBlock, newMode: string) { const mode = this.getFieldValue('MODE'); if (mode !== newMode) { - const inputConnection = this.getInput('INPUT').connection; - inputConnection.setShadowDom(null); - const inputBlock = inputConnection.targetBlock(); + const inputConnection = this.getInput('INPUT')!.connection; + inputConnection!.setShadowDom(null); + const inputBlock = inputConnection!.targetBlock(); + // TODO(#6920): This is probably not needed; see details in bug. if (inputBlock) { - inputConnection.disconnect(); + inputConnection!.disconnect(); if (inputBlock.isShadow()) { - inputBlock.dispose(); + inputBlock.dispose(false); } else { this.bumpNeighbours(); } } } if (newMode === 'SPLIT') { - this.outputConnection.setCheck('Array'); - this.getInput('INPUT').setCheck('String'); + this.outputConnection!.setCheck('Array'); + this.getInput('INPUT')!.setCheck('String'); } else { - this.outputConnection.setCheck('String'); - this.getInput('INPUT').setCheck('Array'); + this.outputConnection!.setCheck('String'); + this.getInput('INPUT')!.setCheck('Array'); } }, /** * Create XML to represent the input and output types. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: SplitBlock): Element { const container = xmlUtils.createElement('mutation'); container.setAttribute('mode', this.getFieldValue('MODE')); return container; }, /** * Parse XML to restore the input and output types. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { + domToMutation: function (this: SplitBlock, xmlElement: Element) { this.updateType_(xmlElement.getAttribute('mode')); }, @@ -998,9 +1063,10 @@ blocks['lists_split'] = { * This block does not need to serialize any specific state as it is already * encoded in the dropdown values, but must have an implementation to avoid * the backward compatible XML mutations being serialized. - * @return {null} The state of this block. + * + * @returns The state of this block. */ - saveExtraState: function() { + saveExtraState: function (this: SplitBlock): null { return null; }, @@ -1009,7 +1075,7 @@ blocks['lists_split'] = { * No extra state is needed or expected as it is already encoded in the * dropdown values. */ - loadExtraState: function() {}, + loadExtraState: function (this: SplitBlock) {}, }; // Register provided blocks. diff --git a/blocks/logic.js b/blocks/logic.ts similarity index 61% rename from blocks/logic.js rename to blocks/logic.ts index ee1228d98..6996d1721 100644 --- a/blocks/logic.js +++ b/blocks/logic.ts @@ -4,43 +4,30 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Logic blocks for Blockly. - * @suppress {checkTypes} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks.logic'); - -/* eslint-disable-next-line no-unused-vars */ -const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); -const Events = goog.require('Blockly.Events'); -const Extensions = goog.require('Blockly.Extensions'); -const xmlUtils = goog.require('Blockly.utils.xml'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {Msg} = goog.require('Blockly.Msg'); -const {Mutator} = goog.require('Blockly.Mutator'); -/* eslint-disable-next-line no-unused-vars */ -const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection'); -/* eslint-disable-next-line no-unused-vars */ -const {Workspace} = goog.requireType('Blockly.Workspace'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldDropdown'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.logic'); +import * as Events from '../core/events/events.js'; +import * as Extensions from '../core/extensions.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; +import type {Block} from '../core/block.js'; +import type {BlockSvg} from '../core/block_svg.js'; +import type {Connection} from '../core/connection.js'; +import {Msg} from '../core/msg.js'; +import type {Workspace} from '../core/workspace.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; +import '../core/field_dropdown.js'; +import '../core/field_label.js'; +import '../core/icons/mutator_icon.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for boolean data type: true and false. { 'type': 'logic_boolean', @@ -266,13 +253,12 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'tooltip': '%{BKY_CONTROLS_IF_ELSE_TOOLTIP}', }, ]); -exports.blocks = blocks; /** * Tooltip text, keyed by block OP value. Used by logic_compare and * logic_operation blocks. + * * @see {Extensions#buildTooltipForDropdown} - * @readonly */ const TOOLTIPS_BY_OP = { // logic_compare @@ -289,14 +275,33 @@ const TOOLTIPS_BY_OP = { }; Extensions.register( - 'logic_op_tooltip', - Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP)); + 'logic_op_tooltip', + Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP) +); + +/** Type of a block that has CONTROLS_IF_MUTATOR_MIXIN */ +type IfBlock = Block & IfMixin; +interface IfMixin extends IfMixinType {} +type IfMixinType = typeof CONTROLS_IF_MUTATOR_MIXIN; + +// Types for quarks defined in JSON. +/** Type of a controls_if_if (if mutator container) block. */ +interface ContainerBlock extends Block {} + +/** Type of a controls_if_elseif or controls_if_else block. */ +interface ClauseBlock extends Block { + valueConnection_?: Connection | null; + statementConnection_?: Connection | null; +} + +/** Extra state for serialising controls_if blocks. */ +type IfExtraState = { + elseIfCount?: number; + hasElse?: boolean; +}; /** * Mutator methods added to controls_if blocks. - * @mixin - * @augments Block - * @readonly */ const CONTROLS_IF_MUTATOR_MIXIN = { elseifCount_: 0, @@ -305,39 +310,39 @@ const CONTROLS_IF_MUTATOR_MIXIN = { /** * Create XML to represent the number of else-if and else inputs. * Backwards compatible serialization implementation. - * @return {Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: IfBlock): Element | null { if (!this.elseifCount_ && !this.elseCount_) { return null; } const container = xmlUtils.createElement('mutation'); if (this.elseifCount_) { - container.setAttribute('elseif', this.elseifCount_); + container.setAttribute('elseif', String(this.elseifCount_)); } if (this.elseCount_) { - container.setAttribute('else', 1); + container.setAttribute('else', '1'); } return container; }, /** * Parse XML to restore the else-if and else inputs. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0; - this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0; + domToMutation: function (this: IfBlock, xmlElement: Element) { + this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif')!, 10) || 0; + this.elseCount_ = parseInt(xmlElement.getAttribute('else')!, 10) || 0; this.rebuildShape_(); }, /** * Returns the state of this block as a JSON serializable object. - * @return {?{elseIfCount: (number|undefined), haseElse: (boolean|undefined)}} - * The state of this block, ie the else if count and else state. + * + * @returns The state of this block, ie the else if count and else state. */ - saveExtraState: function() { + saveExtraState: function (this: IfBlock): IfExtraState | null { if (!this.elseifCount_ && !this.elseCount_) { return null; } @@ -352,86 +357,102 @@ const CONTROLS_IF_MUTATOR_MIXIN = { }, /** * Applies the given state to this block. - * @param {*} state The state to apply to this block, ie the else if count and + * + * @param state The state to apply to this block, ie the else if count + and * else state. */ - loadExtraState: function(state) { + loadExtraState: function (this: IfBlock, state: IfExtraState) { this.elseifCount_ = state['elseIfCount'] || 0; this.elseCount_ = state['hasElse'] ? 1 : 0; this.updateShape_(); }, /** * Populate the mutator's dialog with this block's components. - * @param {!Workspace} workspace Mutator's workspace. - * @return {!Block} Root block in mutator. - * @this {Block} + * + * @param workspace MutatorIcon's workspace. + * @returns Root block in mutator. */ - decompose: function(workspace) { + decompose: function (this: IfBlock, workspace: Workspace): ContainerBlock { const containerBlock = workspace.newBlock('controls_if_if'); - containerBlock.initSvg(); - let connection = containerBlock.nextConnection; + (containerBlock as BlockSvg).initSvg(); + let connection = containerBlock.nextConnection!; for (let i = 1; i <= this.elseifCount_; i++) { const elseifBlock = workspace.newBlock('controls_if_elseif'); - elseifBlock.initSvg(); - connection.connect(elseifBlock.previousConnection); - connection = elseifBlock.nextConnection; + (elseifBlock as BlockSvg).initSvg(); + connection.connect(elseifBlock.previousConnection!); + connection = elseifBlock.nextConnection!; } if (this.elseCount_) { const elseBlock = workspace.newBlock('controls_if_else'); - elseBlock.initSvg(); - connection.connect(elseBlock.previousConnection); + (elseBlock as BlockSvg).initSvg(); + connection.connect(elseBlock.previousConnection!); } return containerBlock; }, /** * Reconfigure this block based on the mutator dialog's components. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - compose: function(containerBlock) { - let clauseBlock = containerBlock.nextConnection.targetBlock(); + compose: function (this: IfBlock, containerBlock: ContainerBlock) { + let clauseBlock = + containerBlock.nextConnection!.targetBlock() as ClauseBlock | null; // Count number of inputs. this.elseifCount_ = 0; this.elseCount_ = 0; - const valueConnections = [null]; - const statementConnections = [null]; - let elseStatementConnection = null; + // Connections arrays are passed to .reconnectChildBlocks_() which + // takes 1-based arrays, so are initialised with a dummy value at + // index 0 for convenience. + const valueConnections: Array = [null]; + const statementConnections: Array = [null]; + let elseStatementConnection: Connection | null = null; while (clauseBlock) { if (clauseBlock.isInsertionMarker()) { - clauseBlock = clauseBlock.getNextBlock(); + clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null; continue; } switch (clauseBlock.type) { case 'controls_if_elseif': this.elseifCount_++; - valueConnections.push(clauseBlock.valueConnection_); - statementConnections.push(clauseBlock.statementConnection_); + // TODO(#6920): null valid, undefined not. + valueConnections.push( + clauseBlock.valueConnection_ as Connection | null + ); + statementConnections.push( + clauseBlock.statementConnection_ as Connection | null + ); break; case 'controls_if_else': this.elseCount_++; - elseStatementConnection = clauseBlock.statementConnection_; + elseStatementConnection = + clauseBlock.statementConnection_ as Connection | null; break; default: throw TypeError('Unknown block type: ' + clauseBlock.type); } - clauseBlock = clauseBlock.getNextBlock(); + clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null; } this.updateShape_(); // Reconnect any child blocks. this.reconnectChildBlocks_( - valueConnections, statementConnections, elseStatementConnection); + valueConnections, + statementConnections, + elseStatementConnection + ); }, /** * Store pointers to any connected child blocks. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - saveConnections: function(containerBlock) { - let clauseBlock = containerBlock.nextConnection.targetBlock(); + saveConnections: function (this: IfBlock, containerBlock: ContainerBlock) { + let clauseBlock = + containerBlock!.nextConnection!.targetBlock() as ClauseBlock | null; let i = 1; while (clauseBlock) { if (clauseBlock.isInsertionMarker()) { - clauseBlock = clauseBlock.getNextBlock(); + clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null; continue; } switch (clauseBlock.type) { @@ -439,53 +460,55 @@ const CONTROLS_IF_MUTATOR_MIXIN = { const inputIf = this.getInput('IF' + i); const inputDo = this.getInput('DO' + i); clauseBlock.valueConnection_ = - inputIf && inputIf.connection.targetConnection; + inputIf && inputIf.connection!.targetConnection; clauseBlock.statementConnection_ = - inputDo && inputDo.connection.targetConnection; + inputDo && inputDo.connection!.targetConnection; i++; break; } case 'controls_if_else': { const inputDo = this.getInput('ELSE'); clauseBlock.statementConnection_ = - inputDo && inputDo.connection.targetConnection; + inputDo && inputDo.connection!.targetConnection; break; } default: throw TypeError('Unknown block type: ' + clauseBlock.type); } - clauseBlock = clauseBlock.getNextBlock(); + clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null; } }, /** * Reconstructs the block with all child blocks attached. - * @this {Block} */ - rebuildShape_: function() { - const valueConnections = [null]; - const statementConnections = [null]; - let elseStatementConnection = null; + rebuildShape_: function (this: IfBlock) { + const valueConnections: Array = [null]; + const statementConnections: Array = [null]; + let elseStatementConnection: Connection | null = null; if (this.getInput('ELSE')) { elseStatementConnection = - this.getInput('ELSE').connection.targetConnection; + this.getInput('ELSE')!.connection!.targetConnection; } for (let i = 1; this.getInput('IF' + i); i++) { const inputIf = this.getInput('IF' + i); const inputDo = this.getInput('DO' + i); - valueConnections.push(inputIf.connection.targetConnection); - statementConnections.push(inputDo.connection.targetConnection); + valueConnections.push(inputIf!.connection!.targetConnection); + statementConnections.push(inputDo!.connection!.targetConnection); } this.updateShape_(); this.reconnectChildBlocks_( - valueConnections, statementConnections, elseStatementConnection); + valueConnections, + statementConnections, + elseStatementConnection + ); }, /** * Modify this block to have the correct number of inputs. - * @this {Block} - * @private + * + * @internal */ - updateShape_: function() { + updateShape_: function (this: IfBlock) { // Delete everything. if (this.getInput('ELSE')) { this.removeInput('ELSE'); @@ -496,86 +519,107 @@ const CONTROLS_IF_MUTATOR_MIXIN = { } // Rebuild block. for (let i = 1; i <= this.elseifCount_; i++) { - this.appendValueInput('IF' + i).setCheck('Boolean').appendField( - Msg['CONTROLS_IF_MSG_ELSEIF']); + this.appendValueInput('IF' + i) + .setCheck('Boolean') + .appendField(Msg['CONTROLS_IF_MSG_ELSEIF']); this.appendStatementInput('DO' + i).appendField( - Msg['CONTROLS_IF_MSG_THEN']); + Msg['CONTROLS_IF_MSG_THEN'] + ); } if (this.elseCount_) { this.appendStatementInput('ELSE').appendField( - Msg['CONTROLS_IF_MSG_ELSE']); + Msg['CONTROLS_IF_MSG_ELSE'] + ); } }, /** * Reconnects child blocks. - * @param {!Array} valueConnections List of - * value connections for 'if' input. - * @param {!Array} statementConnections List of - * statement connections for 'do' input. - * @param {?RenderedConnection} elseStatementConnection Statement - * connection for else input. - * @this {Block} + * + * @param valueConnections 1-based array of value connections for + * 'if' input. Value at index [0] ignored. + * @param statementConnections 1-based array of statement + * connections for 'do' input. Value at index [0] ignored. + * @param elseStatementConnection Statement connection for else input. */ - reconnectChildBlocks_: function( - valueConnections, statementConnections, elseStatementConnection) { + reconnectChildBlocks_: function ( + this: IfBlock, + valueConnections: Array, + statementConnections: Array, + elseStatementConnection: Connection | null + ) { for (let i = 1; i <= this.elseifCount_; i++) { - Mutator.reconnect(valueConnections[i], this, 'IF' + i); - Mutator.reconnect(statementConnections[i], this, 'DO' + i); + valueConnections[i]?.reconnect(this, 'IF' + i); + statementConnections[i]?.reconnect(this, 'DO' + i); } - Mutator.reconnect(elseStatementConnection, this, 'ELSE'); + elseStatementConnection?.reconnect(this, 'ELSE'); }, }; Extensions.registerMutator( - 'controls_if_mutator', CONTROLS_IF_MUTATOR_MIXIN, null, - ['controls_if_elseif', 'controls_if_else']); + 'controls_if_mutator', + CONTROLS_IF_MUTATOR_MIXIN, + null as unknown as undefined, // TODO(#6920) + ['controls_if_elseif', 'controls_if_else'] +); + /** - * "controls_if" extension function. Adds mutator, shape updating methods, and - * dynamic tooltip to "controls_if" blocks. - * @this {Block} + * "controls_if" extension function. Adds mutator, shape updating methods, + * and dynamic tooltip to "controls_if" blocks. */ -const CONTROLS_IF_TOOLTIP_EXTENSION = function() { - this.setTooltip(function() { - if (!this.elseifCount_ && !this.elseCount_) { - return Msg['CONTROLS_IF_TOOLTIP_1']; - } else if (!this.elseifCount_ && this.elseCount_) { - return Msg['CONTROLS_IF_TOOLTIP_2']; - } else if (this.elseifCount_ && !this.elseCount_) { - return Msg['CONTROLS_IF_TOOLTIP_3']; - } else if (this.elseifCount_ && this.elseCount_) { - return Msg['CONTROLS_IF_TOOLTIP_4']; - } - return ''; - }.bind(this)); +const CONTROLS_IF_TOOLTIP_EXTENSION = function (this: IfBlock) { + this.setTooltip( + function (this: IfBlock) { + if (!this.elseifCount_ && !this.elseCount_) { + return Msg['CONTROLS_IF_TOOLTIP_1']; + } else if (!this.elseifCount_ && this.elseCount_) { + return Msg['CONTROLS_IF_TOOLTIP_2']; + } else if (this.elseifCount_ && !this.elseCount_) { + return Msg['CONTROLS_IF_TOOLTIP_3']; + } else if (this.elseifCount_ && this.elseCount_) { + return Msg['CONTROLS_IF_TOOLTIP_4']; + } + return ''; + }.bind(this) + ); }; Extensions.register('controls_if_tooltip', CONTROLS_IF_TOOLTIP_EXTENSION); +/** Type of a block that has LOGIC_COMPARE_ONCHANGE_MIXIN */ +type CompareBlock = Block & CompareMixin; +interface CompareMixin extends CompareMixinType { + prevBlocks_?: Array; +} +type CompareMixinType = typeof LOGIC_COMPARE_ONCHANGE_MIXIN; + /** - * Adds dynamic type validation for the left and right sides of a logic_compare - * block. - * @mixin - * @augments Block - * @readonly + * Adds dynamic type validation for the left and right sides of a + * logic_compare block. */ const LOGIC_COMPARE_ONCHANGE_MIXIN = { /** * Called whenever anything on the workspace changes. * Prevent mismatched types from being compared. - * @param {!AbstractEvent} e Change event. - * @this {Block} + * + * @param e Change event. */ - onchange: function(e) { + onchange: function (this: CompareBlock, e: AbstractEvent) { if (!this.prevBlocks_) { this.prevBlocks_ = [null, null]; } const blockA = this.getInputTargetBlock('A'); const blockB = this.getInputTargetBlock('B'); - // Disconnect blocks that existed prior to this change if they don't match. - if (blockA && blockB && - !this.workspace.connectionChecker.doTypeChecks( - blockA.outputConnection, blockB.outputConnection)) { + // Disconnect blocks that existed prior to this change if they don't + // match. + if ( + blockA && + blockB && + !this.workspace.connectionChecker.doTypeChecks( + blockA.outputConnection!, + blockB.outputConnection! + ) + ) { // Mismatch between two inputs. Revert the block connections, // bumping away the newly connected block(s). Events.setGroup(e.group); @@ -584,7 +628,7 @@ const LOGIC_COMPARE_ONCHANGE_MIXIN = { blockA.unplug(); if (prevA && !prevA.isDisposed() && !prevA.isShadow()) { // The shadow block is automatically replaced during unplug(). - this.getInput('A').connection.connect(prevA.outputConnection); + this.getInput('A')!.connection!.connect(prevA.outputConnection!); } } const prevB = this.prevBlocks_[1]; @@ -592,7 +636,7 @@ const LOGIC_COMPARE_ONCHANGE_MIXIN = { blockB.unplug(); if (prevB && !prevB.isDisposed() && !prevB.isShadow()) { // The shadow block is automatically replaced during unplug(). - this.getInput('B').connection.connect(prevB.outputConnection); + this.getInput('B')!.connection!.connect(prevB.outputConnection!); } } this.bumpNeighbours(); @@ -606,43 +650,47 @@ const LOGIC_COMPARE_ONCHANGE_MIXIN = { /** * "logic_compare" extension function. Adds type left and right side type * checking to "logic_compare" blocks. - * @this {Block} - * @readonly */ -const LOGIC_COMPARE_EXTENSION = function() { +const LOGIC_COMPARE_EXTENSION = function (this: CompareBlock) { // Add onchange handler to ensure types are compatible. this.mixin(LOGIC_COMPARE_ONCHANGE_MIXIN); }; Extensions.register('logic_compare', LOGIC_COMPARE_EXTENSION); +/** Type of a block that has LOGIC_TERNARY_ONCHANGE_MIXIN */ +type TernaryBlock = Block & TernaryMixin; +interface TernaryMixin extends TernaryMixinType {} +type TernaryMixinType = typeof LOGIC_TERNARY_ONCHANGE_MIXIN; + /** * Adds type coordination between inputs and output. - * @mixin - * @augments Block - * @readonly */ const LOGIC_TERNARY_ONCHANGE_MIXIN = { - prevParentConnection_: null, + prevParentConnection_: null as Connection | null, /** * Called whenever anything on the workspace changes. * Prevent mismatched types. - * @param {!AbstractEvent} e Change event. - * @this {Block} */ - onchange: function(e) { + onchange: function (this: TernaryBlock, e: AbstractEvent) { const blockA = this.getInputTargetBlock('THEN'); const blockB = this.getInputTargetBlock('ELSE'); - const parentConnection = this.outputConnection.targetConnection; - // Disconnect blocks that existed prior to this change if they don't match. + const parentConnection = this.outputConnection!.targetConnection; + // Disconnect blocks that existed prior to this change if they don't + // match. if ((blockA || blockB) && parentConnection) { for (let i = 0; i < 2; i++) { - const block = (i === 1) ? blockA : blockB; - if (block && - !block.workspace.connectionChecker.doTypeChecks( - block.outputConnection, parentConnection)) { - // Ensure that any disconnections are grouped with the causing event. + const block = i === 1 ? blockA : blockB; + if ( + block && + !block.workspace.connectionChecker.doTypeChecks( + block.outputConnection!, + parentConnection + ) + ) { + // Ensure that any disconnections are grouped with the causing + // event. Events.setGroup(e.group); if (parentConnection === this.prevParentConnection_) { this.unplug(); diff --git a/blocks/loops.js b/blocks/loops.ts similarity index 59% rename from blocks/loops.js rename to blocks/loops.ts index 220c17d67..fa9d77656 100644 --- a/blocks/loops.js +++ b/blocks/loops.ts @@ -4,60 +4,55 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Loop blocks for Blockly. - * @suppress {checkTypes} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks.loops'); - -/* eslint-disable-next-line no-unused-vars */ -const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Events = goog.require('Blockly.Events'); -const Extensions = goog.require('Blockly.Extensions'); -const Variables = goog.require('Blockly.Variables'); -const xmlUtils = goog.require('Blockly.utils.xml'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {Msg} = goog.require('Blockly.Msg'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldDropdown'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldNumber'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Warning'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.loops'); +import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; +import type {Block} from '../core/block.js'; +import * as ContextMenu from '../core/contextmenu.js'; +import type { + ContextMenuOption, + LegacyContextMenuOption, +} from '../core/contextmenu_registry.js'; +import * as Events from '../core/events/events.js'; +import * as Extensions from '../core/extensions.js'; +import * as Variables from '../core/variables.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import {Msg} from '../core/msg.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; +import '../core/field_dropdown.js'; +import '../core/field_label.js'; +import '../core/field_number.js'; +import '../core/field_variable.js'; +import '../core/icons/warning_icon.js'; +import {FieldVariable} from '../core/field_variable.js'; +import {WorkspaceSvg} from '../core/workspace_svg.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for repeat n times (external number). { 'type': 'controls_repeat_ext', 'message0': '%{BKY_CONTROLS_REPEAT_TITLE}', - 'args0': [{ - 'type': 'input_value', - 'name': 'TIMES', - 'check': 'Number', - }], + 'args0': [ + { + 'type': 'input_value', + 'name': 'TIMES', + 'check': 'Number', + }, + ], 'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1', - 'args1': [{ - 'type': 'input_statement', - 'name': 'DO', - }], + 'args1': [ + { + 'type': 'input_statement', + 'name': 'DO', + }, + ], 'previousStatement': null, 'nextStatement': null, 'style': 'loop_blocks', @@ -69,18 +64,22 @@ const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'controls_repeat', 'message0': '%{BKY_CONTROLS_REPEAT_TITLE}', - 'args0': [{ - 'type': 'field_number', - 'name': 'TIMES', - 'value': 10, - 'min': 0, - 'precision': 1, - }], + 'args0': [ + { + 'type': 'field_number', + 'name': 'TIMES', + 'value': 10, + 'min': 0, + 'precision': 1, + }, + ], 'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1', - 'args1': [{ - 'type': 'input_statement', - 'name': 'DO', - }], + 'args1': [ + { + 'type': 'input_statement', + 'name': 'DO', + }, + ], 'previousStatement': null, 'nextStatement': null, 'style': 'loop_blocks', @@ -107,10 +106,12 @@ const blocks = createBlockDefinitionsFromJsonArray([ }, ], 'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1', - 'args1': [{ - 'type': 'input_statement', - 'name': 'DO', - }], + 'args1': [ + { + 'type': 'input_statement', + 'name': 'DO', + }, + ], 'previousStatement': null, 'nextStatement': null, 'style': 'loop_blocks', @@ -147,19 +148,18 @@ const blocks = createBlockDefinitionsFromJsonArray([ }, ], 'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1', - 'args1': [{ - 'type': 'input_statement', - 'name': 'DO', - }], + 'args1': [ + { + 'type': 'input_statement', + 'name': 'DO', + }, + ], 'inputsInline': true, 'previousStatement': null, 'nextStatement': null, 'style': 'loop_blocks', 'helpUrl': '%{BKY_CONTROLS_FOR_HELPURL}', - 'extensions': [ - 'contextMenu_newGetVariableBlock', - 'controls_for_tooltip', - ], + 'extensions': ['contextMenu_newGetVariableBlock', 'controls_for_tooltip'], }, // Block for 'for each' loop. { @@ -178,10 +178,12 @@ const blocks = createBlockDefinitionsFromJsonArray([ }, ], 'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1', - 'args1': [{ - 'type': 'input_statement', - 'name': 'DO', - }], + 'args1': [ + { + 'type': 'input_statement', + 'name': 'DO', + }, + ], 'previousStatement': null, 'nextStatement': null, 'style': 'loop_blocks', @@ -195,30 +197,28 @@ const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'controls_flow_statements', 'message0': '%1', - 'args0': [{ - 'type': 'field_dropdown', - 'name': 'FLOW', - 'options': [ - ['%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}', 'BREAK'], - ['%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}', 'CONTINUE'], - ], - }], + 'args0': [ + { + 'type': 'field_dropdown', + 'name': 'FLOW', + 'options': [ + ['%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}', 'BREAK'], + ['%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}', 'CONTINUE'], + ], + }, + ], 'previousStatement': null, 'style': 'loop_blocks', 'helpUrl': '%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}', 'suppressPrefixSuffix': true, - 'extensions': [ - 'controls_flow_tooltip', - 'controls_flow_in_loop_check', - ], + 'extensions': ['controls_flow_tooltip', 'controls_flow_in_loop_check'], }, ]); -exports.blocks = blocks; /** * Tooltips for the 'controls_whileUntil' block, keyed by MODE value. + * * @see {Extensions#buildTooltipForDropdown} - * @readonly */ const WHILE_UNTIL_TOOLTIPS = { 'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}', @@ -226,13 +226,14 @@ const WHILE_UNTIL_TOOLTIPS = { }; Extensions.register( - 'controls_whileUntil_tooltip', - Extensions.buildTooltipForDropdown('MODE', WHILE_UNTIL_TOOLTIPS)); + 'controls_whileUntil_tooltip', + Extensions.buildTooltipForDropdown('MODE', WHILE_UNTIL_TOOLTIPS) +); /** * Tooltips for the 'controls_flow_statements' block, keyed by FLOW value. + * * @see {Extensions#buildTooltipForDropdown} - * @readonly */ const BREAK_CONTINUE_TOOLTIPS = { 'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}', @@ -240,55 +241,66 @@ const BREAK_CONTINUE_TOOLTIPS = { }; Extensions.register( - 'controls_flow_tooltip', - Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS)); + 'controls_flow_tooltip', + Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS) +); + +/** Type of a block that has CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN */ +type CustomContextMenuBlock = Block & CustomContextMenuMixin; +interface CustomContextMenuMixin extends CustomContextMenuMixinType {} +type CustomContextMenuMixinType = + typeof CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN; /** * Mixin to add a context menu item to create a 'variables_get' block. * Used by blocks 'controls_for' and 'controls_forEach'. - * @mixin - * @augments Block - * @package - * @readonly */ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = { /** * Add context menu option to create getter block for the loop's variable. * (customContextMenu support limited to web BlockSvg.) - * @param {!Array} options List of menu options to add to. - * @this {Block} + * + * @param options List of menu options to add to. */ - customContextMenu: function(options) { + customContextMenu: function ( + this: CustomContextMenuBlock, + options: Array + ) { if (this.isInFlyout) { return; } - const variable = this.getField('VAR').getVariable(); + const varField = this.getField('VAR') as FieldVariable; + const variable = varField.getVariable()!; const varName = variable.name; if (!this.isCollapsed() && varName !== null) { - const option = {enabled: true}; - option.text = Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName); const xmlField = Variables.generateVariableFieldDom(variable); const xmlBlock = xmlUtils.createElement('block'); xmlBlock.setAttribute('type', 'variables_get'); xmlBlock.appendChild(xmlField); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + + options.push({ + enabled: true, + text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName), + callback: ContextMenu.callbackFactory(this, xmlBlock), + }); } }, }; Extensions.registerMixin( - 'contextMenu_newGetVariableBlock', - CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN); + 'contextMenu_newGetVariableBlock', + CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN +); Extensions.register( - 'controls_for_tooltip', - Extensions.buildTooltipWithFieldText('%{BKY_CONTROLS_FOR_TOOLTIP}', 'VAR')); + 'controls_for_tooltip', + Extensions.buildTooltipWithFieldText('%{BKY_CONTROLS_FOR_TOOLTIP}', 'VAR') +); Extensions.register( - 'controls_forEach_tooltip', - Extensions.buildTooltipWithFieldText( - '%{BKY_CONTROLS_FOREACH_TOOLTIP}', 'VAR')); + 'controls_forEach_tooltip', + Extensions.buildTooltipWithFieldText('%{BKY_CONTROLS_FOREACH_TOOLTIP}', 'VAR') +); /** * List of block types that are loops and thus do not need warnings. @@ -304,34 +316,33 @@ Extensions.register( * * // Else if using blockly_compressed + blockss_compressed.js in browser: * Blockly.libraryBlocks.loopTypes.add('custom_loop'); - * - * @type {!Set} */ -const loopTypes = new Set([ +export const loopTypes: Set = new Set([ 'controls_repeat', 'controls_repeat_ext', 'controls_forEach', 'controls_for', 'controls_whileUntil', ]); -exports.loopTypes = loopTypes; + +/** Type of a block that has CONTROL_FLOW_IN_LOOP_CHECK_MIXIN */ +type ControlFlowInLoopBlock = Block & ControlFlowInLoopMixin; +interface ControlFlowInLoopMixin extends ControlFlowInLoopMixinType {} +type ControlFlowInLoopMixinType = typeof CONTROL_FLOW_IN_LOOP_CHECK_MIXIN; /** * This mixin adds a check to make sure the 'controls_flow_statements' block * is contained in a loop. Otherwise a warning is added to the block. - * @mixin - * @augments Block - * @public - * @readonly */ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { /** * Is this block enclosed (at any level) by a loop? - * @return {Block} The nearest surrounding loop, or null if none. - * @this {Block} + * + * @returns The nearest surrounding loop, or null if none. */ - getSurroundLoop: function() { - let block = this; + getSurroundLoop: function (this: ControlFlowInLoopBlock): Block | null { + // eslint-disable-next-line @typescript-eslint/no-this-alias + let block: Block | null = this; do { if (loopTypes.has(block.type)) { return block; @@ -344,20 +355,19 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. - * @param {!AbstractEvent} e Move event. - * @this {Block} */ - onchange: function(e) { + onchange: function (this: ControlFlowInLoopBlock, e: AbstractEvent) { + const ws = this.workspace as WorkspaceSvg; // Don't change state if: // * It's at the start of a drag. // * It's not a move event. - if (!this.workspace.isDragging || this.workspace.isDragging() || - e.type !== Events.BLOCK_MOVE) { + if (!ws.isDragging || ws.isDragging() || e.type !== Events.BLOCK_MOVE) { return; } - const enabled = this.getSurroundLoop(this); + const enabled = !!this.getSurroundLoop(); this.setWarningText( - enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']); + enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING'] + ); if (!this.isInFlyout) { const group = Events.getGroup(); // Makes it so the move and the disable event get undone together. @@ -369,7 +379,9 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { }; Extensions.registerMixin( - 'controls_flow_in_loop_check', CONTROL_FLOW_IN_LOOP_CHECK_MIXIN); + 'controls_flow_in_loop_check', + CONTROL_FLOW_IN_LOOP_CHECK_MIXIN +); // Register provided blocks. defineBlocks(blocks); diff --git a/blocks/math.ts b/blocks/math.ts index 1fdaf3807..c67979215 100644 --- a/blocks/math.ts +++ b/blocks/math.ts @@ -4,26 +4,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Math blocks for Blockly. - */ - import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.libraryBlocks.math'); import * as Extensions from '../core/extensions.js'; -import type {Field} from '../core/field.js'; import type {FieldDropdown} from '../core/field_dropdown.js'; import * as xmlUtils from '../core/utils/xml.js'; import type {Block} from '../core/block.js'; -import type {BlockDefinition} from '../core/blocks.js'; -import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; import '../core/field_dropdown.js'; import '../core/field_label.js'; import '../core/field_number.js'; import '../core/field_variable.js'; - /** * A dictionary of the block definitions provided by this module. */ @@ -32,11 +28,13 @@ export const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'math_number', 'message0': '%1', - 'args0': [{ - 'type': 'field_number', - 'name': 'NUM', - 'value': 0, - }], + 'args0': [ + { + 'type': 'field_number', + 'name': 'NUM', + 'value': 0, + }, + ], 'output': 'Number', 'helpUrl': '%{BKY_MATH_NUMBER_HELPURL}', 'style': 'math_blocks', @@ -428,12 +426,13 @@ const TOOLTIPS_BY_OP = { }; Extensions.register( - 'math_op_tooltip', - Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP)); + 'math_op_tooltip', + Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP) +); /** Type of a block that has IS_DIVISBLEBY_MUTATOR_MIXIN */ -type DivisiblebyBlock = Block&DivisiblebyMixin; -interface DivisiblebyMixin extends DivisiblebyMixinType {}; +type DivisiblebyBlock = Block & DivisiblebyMixin; +interface DivisiblebyMixin extends DivisiblebyMixinType {} type DivisiblebyMixinType = typeof IS_DIVISIBLEBY_MUTATOR_MIXIN; /** @@ -451,9 +450,9 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = { * * @returns XML storage element. */ - mutationToDom: function(this: DivisiblebyBlock): Element { + mutationToDom: function (this: DivisiblebyBlock): Element { const container = xmlUtils.createElement('mutation'); - const divisorInput = (this.getFieldValue('PROPERTY') === 'DIVISIBLE_BY'); + const divisorInput = this.getFieldValue('PROPERTY') === 'DIVISIBLE_BY'; container.setAttribute('divisor_input', String(divisorInput)); return container; }, @@ -463,8 +462,8 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = { * * @param xmlElement XML storage element. */ - domToMutation: function(this: DivisiblebyBlock, xmlElement: Element) { - const divisorInput = (xmlElement.getAttribute('divisor_input') === 'true'); + domToMutation: function (this: DivisiblebyBlock, xmlElement: Element) { + const divisorInput = xmlElement.getAttribute('divisor_input') === 'true'; this.updateShape_(divisorInput); }, @@ -478,7 +477,7 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = { * * @param divisorInput True if this block has a divisor input. */ - updateShape_: function(this: DivisiblebyBlock, divisorInput: boolean) { + updateShape_: function (this: DivisiblebyBlock, divisorInput: boolean) { // Add or remove a Value Input. const inputExists = this.getInput('DIVISOR'); if (divisorInput) { @@ -496,28 +495,32 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = { * can update the block shape (add/remove divisor input) based on whether * property is "divisible by". */ -const IS_DIVISIBLE_MUTATOR_EXTENSION = function(this: DivisiblebyBlock) { +const IS_DIVISIBLE_MUTATOR_EXTENSION = function (this: DivisiblebyBlock) { this.getField('PROPERTY')!.setValidator( - /** @param option The selected dropdown option. */ - function(this: FieldDropdown, option: string) { - const divisorInput = (option === 'DIVISIBLE_BY'); - (this.getSourceBlock() as DivisiblebyBlock).updateShape_(divisorInput); - return undefined; // FieldValidators can't be void. Use option as-is. - }); + /** @param option The selected dropdown option. */ + function (this: FieldDropdown, option: string) { + const divisorInput = option === 'DIVISIBLE_BY'; + (this.getSourceBlock() as DivisiblebyBlock).updateShape_(divisorInput); + return undefined; // FieldValidators can't be void. Use option as-is. + } + ); }; Extensions.registerMutator( - 'math_is_divisibleby_mutator', IS_DIVISIBLEBY_MUTATOR_MIXIN, - IS_DIVISIBLE_MUTATOR_EXTENSION); + 'math_is_divisibleby_mutator', + IS_DIVISIBLEBY_MUTATOR_MIXIN, + IS_DIVISIBLE_MUTATOR_EXTENSION +); // Update the tooltip of 'math_change' block to reference the variable. Extensions.register( - 'math_change_tooltip', - Extensions.buildTooltipWithFieldText('%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR')); + 'math_change_tooltip', + Extensions.buildTooltipWithFieldText('%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR') +); /** Type of a block that has LIST_MODES_MUTATOR_MIXIN */ -type ListModesBlock = Block&ListModesMixin; -interface ListModesMixin extends ListModesMixinType {}; +type ListModesBlock = Block & ListModesMixin; +interface ListModesMixin extends ListModesMixinType {} type ListModesMixinType = typeof LIST_MODES_MUTATOR_MIXIN; /** @@ -530,7 +533,7 @@ const LIST_MODES_MUTATOR_MIXIN = { * * @param newOp Either 'MODE' or some op than returns a number. */ - updateType_: function(this: ListModesBlock, newOp: string) { + updateType_: function (this: ListModesBlock, newOp: string) { if (newOp === 'MODE') { this.outputConnection!.setCheck('Array'); } else { @@ -543,7 +546,7 @@ const LIST_MODES_MUTATOR_MIXIN = { * * @returns XML storage element. */ - mutationToDom: function(this: ListModesBlock): Element { + mutationToDom: function (this: ListModesBlock): Element { const container = xmlUtils.createElement('mutation'); container.setAttribute('op', this.getFieldValue('OP')); return container; @@ -554,7 +557,7 @@ const LIST_MODES_MUTATOR_MIXIN = { * * @param xmlElement XML storage element. */ - domToMutation: function(this: ListModesBlock, xmlElement: Element) { + domToMutation: function (this: ListModesBlock, xmlElement: Element) { const op = xmlElement.getAttribute('op'); if (op === null) throw new TypeError('xmlElement had no op attribute'); this.updateType_(op); @@ -570,17 +573,20 @@ const LIST_MODES_MUTATOR_MIXIN = { * Extension to 'math_on_list' blocks that allows support of * modes operation (outputs a list of numbers). */ -const LIST_MODES_MUTATOR_EXTENSION = function(this: ListModesBlock) { +const LIST_MODES_MUTATOR_EXTENSION = function (this: ListModesBlock) { this.getField('OP')!.setValidator( - function(this: ListModesBlock, newOp: string) { - this.updateType_(newOp); - return undefined; - }.bind(this)); + function (this: ListModesBlock, newOp: string) { + this.updateType_(newOp); + return undefined; + }.bind(this) + ); }; Extensions.registerMutator( - 'math_modes_of_list_mutator', LIST_MODES_MUTATOR_MIXIN, - LIST_MODES_MUTATOR_EXTENSION); + 'math_modes_of_list_mutator', + LIST_MODES_MUTATOR_MIXIN, + LIST_MODES_MUTATOR_EXTENSION +); // Register provided blocks. defineBlocks(blocks); diff --git a/blocks/procedures.js b/blocks/procedures.ts similarity index 64% rename from blocks/procedures.js rename to blocks/procedures.ts index 130b4cf16..58095c81d 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.ts @@ -4,51 +4,62 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Procedure blocks for Blockly. - * @suppress {checkTypes|visibility} - */ -'use strict'; +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.procedures'); -goog.module('Blockly.libraryBlocks.procedures'); +import * as ContextMenu from '../core/contextmenu.js'; +import * as Events from '../core/events/events.js'; +import * as Procedures from '../core/procedures.js'; +import * as Variables from '../core/variables.js'; +import * as Xml from '../core/xml.js'; +import * as fieldRegistry from '../core/field_registry.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; +import {Align} from '../core/inputs/input.js'; +import type {Block} from '../core/block.js'; +import type {BlockSvg} from '../core/block_svg.js'; +import type {BlockCreate} from '../core/events/events_block_create.js'; +import type {BlockChange} from '../core/events/events_block_change.js'; +import type {BlockDefinition} from '../core/blocks.js'; +import type {Connection} from '../core/connection.js'; +import type { + ContextMenuOption, + LegacyContextMenuOption, +} from '../core/contextmenu_registry.js'; +import {FieldCheckbox} from '../core/field_checkbox.js'; +import {FieldLabel} from '../core/field_label.js'; +import {FieldTextInput} from '../core/field_textinput.js'; +import {Msg} from '../core/msg.js'; +import {MutatorIcon as Mutator} from '../core/icons/mutator_icon.js'; +import {Names} from '../core/names.js'; +import type {VariableModel} from '../core/variable_model.js'; +import type {Workspace} from '../core/workspace.js'; +import type {WorkspaceSvg} from '../core/workspace_svg.js'; +import {config} from '../core/config.js'; +import {defineBlocks} from '../core/common.js'; +import '../core/icons/comment_icon.js'; +import '../core/icons/warning_icon.js'; -/* eslint-disable-next-line no-unused-vars */ -const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Events = goog.require('Blockly.Events'); -const Procedures = goog.require('Blockly.Procedures'); -const Variables = goog.require('Blockly.Variables'); -const Xml = goog.require('Blockly.Xml'); -const fieldRegistry = goog.require('Blockly.fieldRegistry'); -const xmlUtils = goog.require('Blockly.utils.xml'); -const {Align} = goog.require('Blockly.Input'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {config} = goog.require('Blockly.config'); -const {Msg} = goog.require('Blockly.Msg'); -const {Mutator} = goog.require('Blockly.Mutator'); -const {Names} = goog.require('Blockly.Names'); -/* eslint-disable-next-line no-unused-vars */ -const {VariableModel} = goog.requireType('Blockly.VariableModel'); -/* eslint-disable-next-line no-unused-vars */ -const {Workspace} = goog.requireType('Blockly.Workspace'); -const {defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Comment'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Warning'); +/** A dictionary of the block definitions provided by this module. */ +export const blocks: {[key: string]: BlockDefinition} = {}; +/** Type of a block using the PROCEDURE_DEF_COMMON mixin. */ +type ProcedureBlock = Block & ProcedureMixin; +interface ProcedureMixin extends ProcedureMixinType { + arguments_: string[]; + argumentVarModels_: VariableModel[]; + callType_: string; + paramIds_: string[]; + hasStatements_: boolean; + statementConnection_: Connection | null; +} +type ProcedureMixinType = typeof PROCEDURE_DEF_COMMON; -/** - * A dictionary of the block definitions provided by this module. - * @type {!Object} - */ -const blocks = {}; -exports.blocks = blocks; +/** Extra state for serialising procedure blocks. */ +type ProcedureExtraState = { + params?: Array<{name: string; id: string}>; + hasStatements: boolean; +}; /** * Common properties for the procedure_defnoreturn and @@ -57,16 +68,17 @@ exports.blocks = blocks; const PROCEDURE_DEF_COMMON = { /** * Add or remove the statement block from this function definition. - * @param {boolean} hasStatements True if a statement block is needed. - * @this {Block} + * + * @param hasStatements True if a statement block is needed. */ - setStatements_: function(hasStatements) { + setStatements_: function (this: ProcedureBlock, hasStatements: boolean) { if (this.hasStatements_ === hasStatements) { return; } if (hasStatements) { this.appendStatementInput('STACK').appendField( - Msg['PROCEDURES_DEFNORETURN_DO']); + Msg['PROCEDURES_DEFNORETURN_DO'] + ); if (this.getInput('RETURN')) { this.moveInputBefore('STACK', 'RETURN'); } @@ -77,15 +89,15 @@ const PROCEDURE_DEF_COMMON = { }, /** * Update the display of parameters for this procedure definition block. - * @private - * @this {Block} + * + * @internal */ - updateParams_: function() { + updateParams_: function (this: ProcedureBlock) { // Merge the arguments into a human-readable list. let paramString = ''; if (this.arguments_.length) { paramString = - Msg['PROCEDURES_BEFORE_PARAMS'] + ' ' + this.arguments_.join(', '); + Msg['PROCEDURES_BEFORE_PARAMS'] + ' ' + this.arguments_.join(', '); } // The params field is deterministic based on the mutation, // no need to fire a change event. @@ -99,12 +111,15 @@ const PROCEDURE_DEF_COMMON = { /** * Create XML to represent the argument inputs. * Backwards compatible serialization implementation. - * @param {boolean=} opt_paramIds If true include the IDs of the parameter + * + * @param opt_paramIds If true include the IDs of the parameter * quarks. Used by Procedures.mutateCallers for reconnection. - * @return {!Element} XML storage element. - * @this {Block} + * @returns XML storage element. */ - mutationToDom: function(opt_paramIds) { + mutationToDom: function ( + this: ProcedureBlock, + opt_paramIds: boolean + ): Element { const container = xmlUtils.createElement('mutation'); if (opt_paramIds) { container.setAttribute('name', this.getFieldValue('NAME')); @@ -129,25 +144,32 @@ const PROCEDURE_DEF_COMMON = { /** * Parse XML to restore the argument inputs. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { + domToMutation: function (this: ProcedureBlock, xmlElement: Element) { this.arguments_ = []; this.argumentVarModels_ = []; for (let i = 0, childNode; (childNode = xmlElement.childNodes[i]); i++) { if (childNode.nodeName.toLowerCase() === 'arg') { - const varName = childNode.getAttribute('name'); + const childElement = childNode as Element; + const varName = childElement.getAttribute('name')!; const varId = - childNode.getAttribute('varid') || childNode.getAttribute('varId'); + childElement.getAttribute('varid') || + childElement.getAttribute('varId'); this.arguments_.push(varName); const variable = Variables.getOrCreateVariablePackage( - this.workspace, varId, varName, ''); + this.workspace, + varId, + varName, + '' + ); if (variable !== null) { this.argumentVarModels_.push(variable); } else { console.log( - `Failed to create a variable named "${varName}", ignoring.`); + `Failed to create a variable named "${varName}", ignoring.` + ); } } } @@ -159,11 +181,10 @@ const PROCEDURE_DEF_COMMON = { }, /** * Returns the state of this block as a JSON serializable object. - * @return {?{params: (!Array<{name: string, id: string}>|undefined), - * hasStatements: (boolean|undefined)}} The state of this block, eg the - * parameters and statements. + * + * @returns The state of this block, eg the parameters and statements. */ - saveExtraState: function() { + saveExtraState: function (this: ProcedureBlock): ProcedureExtraState | null { if (!this.argumentVarModels_.length && this.hasStatements_) { return null; } @@ -186,17 +207,22 @@ const PROCEDURE_DEF_COMMON = { }, /** * Applies the given state to this block. - * @param {*} state The state to apply to this block, eg the parameters and - * statements. + * + * @param state The state to apply to this block, eg the parameters + * and statements. */ - loadExtraState: function(state) { + loadExtraState: function (this: ProcedureBlock, state: ProcedureExtraState) { this.arguments_ = []; this.argumentVarModels_ = []; if (state['params']) { for (let i = 0; i < state['params'].length; i++) { const param = state['params'][i]; const variable = Variables.getOrCreateVariablePackage( - this.workspace, param['id'], param['name'], ''); + this.workspace, + param['id'], + param['name'], + '' + ); this.arguments_.push(variable.name); this.argumentVarModels_.push(variable); } @@ -207,11 +233,14 @@ const PROCEDURE_DEF_COMMON = { }, /** * Populate the mutator's dialog with this block's components. - * @param {!Workspace} workspace Mutator's workspace. - * @return {!Block} Root block in mutator. - * @this {Block} + * + * @param workspace Mutator's workspace. + * @returns Root block in mutator. */ - decompose: function(workspace) { + decompose: function ( + this: ProcedureBlock, + workspace: Workspace + ): ContainerBlock { /* * Creates the following XML: * @@ -246,7 +275,10 @@ const PROCEDURE_DEF_COMMON = { node = nextNode; } - const containerBlock = Xml.domToBlock(containerBlockNode, workspace); + const containerBlock = Xml.domToBlock( + containerBlockNode, + workspace + ) as ContainerBlock; if (this.type === 'procedures_defreturn') { containerBlock.setFieldValue(this.hasStatements_, 'STATEMENTS'); @@ -260,10 +292,10 @@ const PROCEDURE_DEF_COMMON = { }, /** * Reconfigure this block based on the mutator dialog's components. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - compose: function(containerBlock) { + compose: function (this: ProcedureBlock, containerBlock: ContainerBlock) { // Parameter list. this.arguments_ = []; this.paramIds_ = []; @@ -272,12 +304,12 @@ const PROCEDURE_DEF_COMMON = { while (paramBlock && !paramBlock.isInsertionMarker()) { const varName = paramBlock.getFieldValue('NAME'); this.arguments_.push(varName); - const variable = this.workspace.getVariable(varName, ''); + const variable = this.workspace.getVariable(varName, '')!; this.argumentVarModels_.push(variable); this.paramIds_.push(paramBlock.id); paramBlock = - paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); + paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); } this.updateParams_(); Procedures.mutateCallers(this); @@ -290,14 +322,14 @@ const PROCEDURE_DEF_COMMON = { if (hasStatements) { this.setStatements_(true); // Restore the stack, if one was saved. - Mutator.reconnect(this.statementConnection_, this, 'STACK'); + this.statementConnection_?.reconnect(this, 'STACK'); this.statementConnection_ = null; } else { // Save the stack, then disconnect it. - const stackConnection = this.getInput('STACK').connection; - this.statementConnection_ = stackConnection.targetConnection; + const stackConnection = this.getInput('STACK')!.connection; + this.statementConnection_ = stackConnection!.targetConnection; if (this.statementConnection_) { - const stackBlock = stackConnection.targetBlock(); + const stackBlock = stackConnection!.targetBlock()!; stackBlock.unplug(); stackBlock.bumpNeighbours(); } @@ -308,38 +340,41 @@ const PROCEDURE_DEF_COMMON = { }, /** * Return all variables referenced by this block. - * @return {!Array} List of variable names. - * @this {Block} + * + * @returns List of variable names. */ - getVars: function() { + getVars: function (this: ProcedureBlock): string[] { return this.arguments_; }, /** * Return all variables referenced by this block. - * @return {!Array} List of variable models. - * @this {Block} + * + * @returns List of variable models. */ - getVarModels: function() { + getVarModels: function (this: ProcedureBlock): VariableModel[] { return this.argumentVarModels_; }, /** * Notification that a variable is renaming. * If the ID matches one of this block's variables, rename it. - * @param {string} oldId ID of variable to rename. - * @param {string} newId ID of new variable. May be the same as oldId, but - * with an updated name. Guaranteed to be the same type as the old - * variable. - * @override - * @this {Block} + * + * @param oldId ID of variable to rename. + * @param newId ID of new variable. May be the same as oldId, but + * with an updated name. Guaranteed to be the same type as the + * old variable. */ - renameVarById: function(oldId, newId) { - const oldVariable = this.workspace.getVariableById(oldId); + renameVarById: function ( + this: ProcedureBlock & BlockSvg, + oldId: string, + newId: string + ) { + const oldVariable = this.workspace.getVariableById(oldId)!; if (oldVariable.type !== '') { // Procedure arguments always have the empty type. return; } const oldName = oldVariable.name; - const newVar = this.workspace.getVariableById(newId); + const newVar = this.workspace.getVariableById(newId)!; let change = false; for (let i = 0; i < this.argumentVarModels_.length; i++) { @@ -357,12 +392,13 @@ const PROCEDURE_DEF_COMMON = { /** * Notification that a variable is renaming but keeping the same ID. If the * variable is in use on this block, rerender to show the new name. - * @param {!VariableModel} variable The variable being renamed. - * @package - * @override - * @this {Block} + * + * @param variable The variable being renamed. */ - updateVarName: function(variable) { + updateVarName: function ( + this: ProcedureBlock & BlockSvg, + variable: VariableModel + ) { const newName = variable.name; let change = false; let oldName; @@ -374,25 +410,32 @@ const PROCEDURE_DEF_COMMON = { } } if (change) { - this.displayRenamedVar_(oldName, newName); + this.displayRenamedVar_(oldName as string, newName); Procedures.mutateCallers(this); } }, /** * Update the display to reflect a newly renamed argument. - * @param {string} oldName The old display name of the argument. - * @param {string} newName The new display name of the argument. - * @private - * @this {Block} + * + * @internal + * @param oldName The old display name of the argument. + * @param newName The new display name of the argument. */ - displayRenamedVar_: function(oldName, newName) { + displayRenamedVar_: function ( + this: ProcedureBlock & BlockSvg, + oldName: string, + newName: string + ) { this.updateParams_(); // Update the mutator's variables if the mutator is open. - if (this.mutator && this.mutator.isVisible()) { - const blocks = this.mutator.workspace_.getAllBlocks(false); + const mutator = this.getIcon(Mutator.TYPE); + if (mutator && mutator.bubbleIsVisible()) { + const blocks = mutator.getWorkspace()!.getAllBlocks(false); for (let i = 0, block; (block = blocks[i]); i++) { - if (block.type === 'procedures_mutatorarg' && - Names.equals(oldName, block.getFieldValue('NAME'))) { + if ( + block.type === 'procedures_mutatorarg' && + Names.equals(oldName, block.getFieldValue('NAME')) + ) { block.setFieldValue(newName, 'NAME'); } } @@ -400,17 +443,18 @@ const PROCEDURE_DEF_COMMON = { }, /** * Add custom menu options to this block's context menu. - * @param {!Array} options List of menu options to add to. - * @this {Block} + * + * @param options List of menu options to add to. */ - customContextMenu: function(options) { + customContextMenu: function ( + this: ProcedureBlock, + options: Array + ) { if (this.isInFlyout) { return; } // Add option to create caller. - const option = {enabled: true}; const name = this.getFieldValue('NAME'); - option.text = Msg['PROCEDURES_CREATE_DO'].replace('%1', name); const xmlMutation = xmlUtils.createElement('mutation'); xmlMutation.setAttribute('name', name); for (let i = 0; i < this.arguments_.length; i++) { @@ -419,25 +463,27 @@ const PROCEDURE_DEF_COMMON = { xmlMutation.appendChild(xmlArg); } const xmlBlock = xmlUtils.createElement('block'); - xmlBlock.setAttribute('type', this.callType_); + xmlBlock.setAttribute('type', (this as AnyDuringMigration).callType_); xmlBlock.appendChild(xmlMutation); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + options.push({ + enabled: true, + text: Msg['PROCEDURES_CREATE_DO'].replace('%1', name), + callback: ContextMenu.callbackFactory(this, xmlBlock), + }); // Add options to create getters for each parameter. if (!this.isCollapsed()) { for (let i = 0; i < this.argumentVarModels_.length; i++) { - const argOption = {enabled: true}; const argVar = this.argumentVarModels_[i]; - argOption.text = - Msg['VARIABLES_SET_CREATE_GET'].replace('%1', argVar.name); - const argXmlField = Variables.generateVariableFieldDom(argVar); const argXmlBlock = xmlUtils.createElement('block'); argXmlBlock.setAttribute('type', 'variables_get'); argXmlBlock.appendChild(argXmlField); - argOption.callback = ContextMenu.callbackFactory(this, argXmlBlock); - options.push(argOption); + options.push({ + enabled: true, + text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', argVar.name), + callback: ContextMenu.callbackFactory(this, argXmlBlock), + }); } } }, @@ -447,25 +493,26 @@ blocks['procedures_defnoreturn'] = { ...PROCEDURE_DEF_COMMON, /** * Block for defining a procedure with no return value. - * @this {Block} */ - init: function() { + init: function (this: ProcedureBlock & BlockSvg) { const initName = Procedures.findLegalName('', this); const nameField = fieldRegistry.fromJson({ type: 'field_input', text: initName, - }); - nameField.setValidator(Procedures.rename); + }) as FieldTextInput; + nameField!.setValidator(Procedures.rename); nameField.setSpellcheck(false); this.appendDummyInput() - .appendField(Msg['PROCEDURES_DEFNORETURN_TITLE']) - .appendField(nameField, 'NAME') - .appendField('', 'PARAMS'); + .appendField(Msg['PROCEDURES_DEFNORETURN_TITLE']) + .appendField(nameField, 'NAME') + .appendField('', 'PARAMS'); this.setMutator(new Mutator(['procedures_mutatorarg'], this)); - if ((this.workspace.options.comments || - (this.workspace.options.parentWorkspace && + if ( + (this.workspace.options.comments || + (this.workspace.options.parentWorkspace && this.workspace.options.parentWorkspace.options.comments)) && - Msg['PROCEDURES_DEFNORETURN_COMMENT']) { + Msg['PROCEDURES_DEFNORETURN_COMMENT'] + ) { this.setCommentText(Msg['PROCEDURES_DEFNORETURN_COMMENT']); } this.setStyle('procedure_blocks'); @@ -478,13 +525,13 @@ blocks['procedures_defnoreturn'] = { }, /** * Return the signature of this procedure definition. - * @return {!Array} Tuple containing three elements: + * + * @returns Tuple containing three elements: * - the name of the defined procedure, * - a list of all its arguments, * - that it DOES NOT have a return value. - * @this {Block} */ - getProcedureDef: function() { + getProcedureDef: function (this: ProcedureBlock): [string, string[], false] { return [this.getFieldValue('NAME'), this.arguments_, false]; }, callType_: 'procedures_callnoreturn', @@ -494,28 +541,29 @@ blocks['procedures_defreturn'] = { ...PROCEDURE_DEF_COMMON, /** * Block for defining a procedure with a return value. - * @this {Block} */ - init: function() { + init: function (this: ProcedureBlock & BlockSvg) { const initName = Procedures.findLegalName('', this); const nameField = fieldRegistry.fromJson({ type: 'field_input', text: initName, - }); + }) as FieldTextInput; nameField.setValidator(Procedures.rename); nameField.setSpellcheck(false); this.appendDummyInput() - .appendField(Msg['PROCEDURES_DEFRETURN_TITLE']) - .appendField(nameField, 'NAME') - .appendField('', 'PARAMS'); + .appendField(Msg['PROCEDURES_DEFRETURN_TITLE']) + .appendField(nameField, 'NAME') + .appendField('', 'PARAMS'); this.appendValueInput('RETURN') - .setAlign(Align.RIGHT) - .appendField(Msg['PROCEDURES_DEFRETURN_RETURN']); + .setAlign(Align.RIGHT) + .appendField(Msg['PROCEDURES_DEFRETURN_RETURN']); this.setMutator(new Mutator(['procedures_mutatorarg'], this)); - if ((this.workspace.options.comments || - (this.workspace.options.parentWorkspace && + if ( + (this.workspace.options.comments || + (this.workspace.options.parentWorkspace && this.workspace.options.parentWorkspace.options.comments)) && - Msg['PROCEDURES_DEFRETURN_COMMENT']) { + Msg['PROCEDURES_DEFRETURN_COMMENT'] + ) { this.setCommentText(Msg['PROCEDURES_DEFRETURN_COMMENT']); } this.setStyle('procedure_blocks'); @@ -528,67 +576,81 @@ blocks['procedures_defreturn'] = { }, /** * Return the signature of this procedure definition. - * @return {!Array} Tuple containing three elements: + * + * @returns Tuple containing three elements: * - the name of the defined procedure, * - a list of all its arguments, * - that it DOES have a return value. - * @this {Block} */ - getProcedureDef: function() { + getProcedureDef: function (this: ProcedureBlock): [string, string[], true] { return [this.getFieldValue('NAME'), this.arguments_, true]; }, callType_: 'procedures_callreturn', }; -blocks['procedures_mutatorcontainer'] = { +/** Type of a procedures_mutatorcontainer block. */ +type ContainerBlock = Block & ContainerMixin; +interface ContainerMixin extends ContainerMixinType {} +type ContainerMixinType = typeof PROCEDURES_MUTATORCONTAINER; + +const PROCEDURES_MUTATORCONTAINER = { /** * Mutator block for procedure container. - * @this {Block} */ - init: function() { + init: function (this: ContainerBlock) { this.appendDummyInput().appendField( - Msg['PROCEDURES_MUTATORCONTAINER_TITLE']); + Msg['PROCEDURES_MUTATORCONTAINER_TITLE'] + ); this.appendStatementInput('STACK'); this.appendDummyInput('STATEMENT_INPUT') - .appendField(Msg['PROCEDURES_ALLOW_STATEMENTS']) - .appendField( - fieldRegistry.fromJson({ - type: 'field_checkbox', - checked: true, - }), - 'STATEMENTS'); + .appendField(Msg['PROCEDURES_ALLOW_STATEMENTS']) + .appendField( + fieldRegistry.fromJson({ + type: 'field_checkbox', + checked: true, + }) as FieldCheckbox, + 'STATEMENTS' + ); this.setStyle('procedure_blocks'); this.setTooltip(Msg['PROCEDURES_MUTATORCONTAINER_TOOLTIP']); this.contextMenu = false; }, }; +blocks['procedures_mutatorcontainer'] = PROCEDURES_MUTATORCONTAINER; -blocks['procedures_mutatorarg'] = { +/** Type of a procedures_mutatorarg block. */ +type ArgumentBlock = Block & ArgumentMixin; +interface ArgumentMixin extends ArgumentMixinType {} +type ArgumentMixinType = typeof PROCEDURES_MUTATORARGUMENT; + +// TODO(#6920): This is kludgy. +type FieldTextInputForArgument = FieldTextInput & { + oldShowEditorFn_(_e?: Event, quietInput?: boolean): void; + createdVariables_: VariableModel[]; +}; + +const PROCEDURES_MUTATORARGUMENT = { /** * Mutator block for procedure argument. - * @this {Block} */ - init: function() { + init: function (this: ArgumentBlock) { const field = fieldRegistry.fromJson({ type: 'field_input', text: Procedures.DEFAULT_ARG, - }); + }) as FieldTextInputForArgument; field.setValidator(this.validator_); // Hack: override showEditor to do just a little bit more work. // We don't have a good place to hook into the start of a text edit. - field.oldShowEditorFn_ = field.showEditor_; - /** - * @this {FieldTextInput} - */ - const newShowEditorFn = function() { + field.oldShowEditorFn_ = (field as AnyDuringMigration).showEditor_; + const newShowEditorFn = function (this: typeof field) { this.createdVariables_ = []; this.oldShowEditorFn_(); }; - field.showEditor_ = newShowEditorFn; + (field as AnyDuringMigration).showEditor_ = newShowEditorFn; this.appendDummyInput() - .appendField(Msg['PROCEDURES_MUTATORARG_TITLE']) - .appendField(field, 'NAME'); + .appendField(Msg['PROCEDURES_MUTATORARG_TITLE']) + .appendField(field, 'NAME'); this.setPreviousStatement(true); this.setNextStatement(true); this.setStyle('procedure_blocks'); @@ -609,14 +671,17 @@ blocks['procedures_mutatorarg'] = { * necessary. * Merge runs of whitespace. Strip leading and trailing whitespace. * Beyond this, all names are legal. - * @param {string} varName User-supplied name. - * @return {?string} Valid name, or null if a name was not specified. - * @private - * @this {FieldTextInput} + * + * @internal + * @param varName User-supplied name. + * @returns Valid name, or null if a name was not specified. */ - validator_: function(varName) { - const sourceBlock = this.getSourceBlock(); - const outerWs = Mutator.findParentWs(sourceBlock.workspace); + validator_: function ( + this: FieldTextInputForArgument, + varName: string + ): string | null { + const sourceBlock = this.getSourceBlock()!; + const outerWs = sourceBlock!.workspace.getRootWorkspace()!; varName = varName.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); if (!varName) { return null; @@ -624,11 +689,12 @@ blocks['procedures_mutatorarg'] = { // Prevents duplicate parameter names in functions const workspace = - sourceBlock.workspace.targetWorkspace || sourceBlock.workspace; + (sourceBlock.workspace as WorkspaceSvg).targetWorkspace || + sourceBlock.workspace; const blocks = workspace.getAllBlocks(false); const caselessName = varName.toLowerCase(); for (let i = 0; i < blocks.length; i++) { - if (blocks[i].id === this.getSourceBlock().id) { + if (blocks[i].id === this.getSourceBlock()!.id) { continue; } // Other blocks values may not be set yet when this is loaded. @@ -662,12 +728,15 @@ blocks['procedures_mutatorarg'] = { * Called when focusing away from the text field. * Deletes all variables that were created as the user typed their intended * variable name. - * @param {string} newText The new variable name. - * @private - * @this {FieldTextInput} + * + * @internal + * @param newText The new variable name. */ - deleteIntermediateVars_: function(newText) { - const outerWs = Mutator.findParentWs(this.getSourceBlock().workspace); + deleteIntermediateVars_: function ( + this: FieldTextInputForArgument, + newText: string + ) { + const outerWs = this.getSourceBlock()!.workspace.getRootWorkspace(); if (!outerWs) { return; } @@ -679,6 +748,25 @@ blocks['procedures_mutatorarg'] = { } }, }; +blocks['procedures_mutatorarg'] = PROCEDURES_MUTATORARGUMENT; + +/** Type of a block using the PROCEDURE_CALL_COMMON mixin. */ +type CallBlock = Block & CallMixin; +interface CallMixin extends CallMixinType { + argumentVarModels_: VariableModel[]; + arguments_: string[]; + defType_: string; + quarkIds_: string[] | null; + quarkConnections_: {[id: string]: Connection}; + previousEnabledState_: boolean; +} +type CallMixinType = typeof PROCEDURE_CALL_COMMON; + +/** Extra state for serialising call blocks. */ +type CallExtraState = { + name: string; + params?: string[]; +}; /** * Common properties for the procedure_callnoreturn and @@ -687,39 +775,47 @@ blocks['procedures_mutatorarg'] = { const PROCEDURE_CALL_COMMON = { /** * Returns the name of the procedure this block calls. - * @return {string} Procedure name. - * @this {Block} + * + * @returns Procedure name. */ - getProcedureCall: function() { + getProcedureCall: function (this: CallBlock): string { // The NAME field is guaranteed to exist, null will never be returned. - return /** @type {string} */ (this.getFieldValue('NAME')); + return this.getFieldValue('NAME'); }, /** * Notification that a procedure is renaming. * If the name matches this block's procedure, rename it. - * @param {string} oldName Previous name of procedure. - * @param {string} newName Renamed procedure. - * @this {Block} + * + * @param oldName Previous name of procedure. + * @param newName Renamed procedure. */ - renameProcedure: function(oldName, newName) { + renameProcedure: function ( + this: CallBlock, + oldName: string, + newName: string + ) { if (Names.equals(oldName, this.getProcedureCall())) { this.setFieldValue(newName, 'NAME'); - const baseMsg = this.outputConnection ? - Msg['PROCEDURES_CALLRETURN_TOOLTIP'] : - Msg['PROCEDURES_CALLNORETURN_TOOLTIP']; + const baseMsg = this.outputConnection + ? Msg['PROCEDURES_CALLRETURN_TOOLTIP'] + : Msg['PROCEDURES_CALLNORETURN_TOOLTIP']; this.setTooltip(baseMsg.replace('%1', newName)); } }, /** * Notification that the procedure's parameters have changed. - * @param {!Array} paramNames New param names, e.g. ['x', 'y', 'z']. - * @param {!Array} paramIds IDs of params (consistent for each - * parameter through the life of a mutator, regardless of param renaming), + * + * @internal + * @param paramNames New param names, e.g. ['x', 'y', 'z']. + * @param paramIds IDs of params (consistent for each parameter + * through the life of a mutator, regardless of param renaming), * e.g. ['piua', 'f8b_', 'oi.o']. - * @private - * @this {Block} */ - setProcedureParameters_: function(paramNames, paramIds) { + setProcedureParameters_: function ( + this: CallBlock, + paramNames: string[], + paramIds: string[] + ) { // Data structures: // this.arguments = ['x', 'y'] // Existing param names. @@ -729,10 +825,12 @@ const PROCEDURE_CALL_COMMON = { // Existing param IDs. // Note that quarkConnections_ may include IDs that no longer exist, but // which might reappear if a param is reattached in the mutator. - const defBlock = - Procedures.getDefinition(this.getProcedureCall(), this.workspace); - const mutatorOpen = - defBlock && defBlock.mutator && defBlock.mutator.isVisible(); + const defBlock = Procedures.getDefinition( + this.getProcedureCall(), + this.workspace + ); + const mutatorIcon = defBlock && defBlock.getIcon(Mutator.TYPE); + const mutatorOpen = mutatorIcon && mutatorIcon.bubbleIsVisible(); if (!mutatorOpen) { this.quarkConnections_ = {}; this.quarkIds_ = null; @@ -756,17 +854,17 @@ const PROCEDURE_CALL_COMMON = { this.quarkConnections_ = {}; this.quarkIds_ = []; } - // Switch off rendering while the block is rebuilt. - const savedRendered = this.rendered; - this.rendered = false; // Update the quarkConnections_ with existing connections. for (let i = 0; i < this.arguments_.length; i++) { const input = this.getInput('ARG' + i); if (input) { - const connection = input.connection.targetConnection; + const connection = input.connection!.targetConnection!; this.quarkConnections_[this.quarkIds_[i]] = connection; - if (mutatorOpen && connection && - paramIds.indexOf(this.quarkIds_[i]) === -1) { + if ( + mutatorOpen && + connection && + paramIds.indexOf(this.quarkIds_[i]) === -1 + ) { // This connection should no longer be attached to this block. connection.disconnect(); connection.getSourceBlock().bumpNeighbours(); @@ -774,12 +872,16 @@ const PROCEDURE_CALL_COMMON = { } } // Rebuild the block's arguments. - this.arguments_ = [].concat(paramNames); + this.arguments_ = ([] as string[]).concat(paramNames); // And rebuild the argument model list. this.argumentVarModels_ = []; for (let i = 0; i < this.arguments_.length; i++) { const variable = Variables.getOrCreateVariablePackage( - this.workspace, null, this.arguments_[i], ''); + this.workspace, + null, + this.arguments_[i], + '' + ); this.argumentVarModels_.push(variable); } @@ -788,28 +890,24 @@ const PROCEDURE_CALL_COMMON = { // Reconnect any child blocks. if (this.quarkIds_) { for (let i = 0; i < this.arguments_.length; i++) { - const quarkId = this.quarkIds_[i]; + const quarkId: string = this.quarkIds_[i]; // TODO(#6920) if (quarkId in this.quarkConnections_) { - const connection = this.quarkConnections_[quarkId]; - if (!Mutator.reconnect(connection, this, 'ARG' + i)) { + // TODO(#6920): investigate claimed circular initialisers. + const connection: Connection = this.quarkConnections_[quarkId]; + if (!connection?.reconnect(this, 'ARG' + i)) { // Block no longer exists or has been attached elsewhere. delete this.quarkConnections_[quarkId]; } } } } - // Restore rendering and show the changes. - this.rendered = savedRendered; - if (this.rendered) { - this.render(); - } }, /** * Modify this block to have the correct number of arguments. - * @private - * @this {Block} + * + * @internal */ - updateShape_: function() { + updateShape_: function (this: CallBlock) { for (let i = 0; i < this.arguments_.length; i++) { const argField = this.getField('ARGNAME' + i); if (argField) { @@ -827,10 +925,10 @@ const PROCEDURE_CALL_COMMON = { const newField = fieldRegistry.fromJson({ type: 'field_label', text: this.arguments_[i], - }); + }) as FieldLabel; const input = this.appendValueInput('ARG' + i) - .setAlign(Align.RIGHT) - .appendField(newField, 'ARGNAME' + i); + .setAlign(Align.RIGHT) + .appendField(newField, 'ARGNAME' + i); input.init(); } } @@ -856,10 +954,10 @@ const PROCEDURE_CALL_COMMON = { /** * Create XML to represent the (non-editable) name and arguments. * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: CallBlock): Element { const container = xmlUtils.createElement('mutation'); container.setAttribute('name', this.getProcedureCall()); for (let i = 0; i < this.arguments_.length; i++) { @@ -872,28 +970,28 @@ const PROCEDURE_CALL_COMMON = { /** * Parse XML to restore the (non-editable) name and parameters. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - const name = xmlElement.getAttribute('name'); + domToMutation: function (this: CallBlock, xmlElement: Element) { + const name = xmlElement.getAttribute('name')!; this.renameProcedure(this.getProcedureCall(), name); - const args = []; + const args: string[] = []; const paramIds = []; for (let i = 0, childNode; (childNode = xmlElement.childNodes[i]); i++) { if (childNode.nodeName.toLowerCase() === 'arg') { - args.push(childNode.getAttribute('name')); - paramIds.push(childNode.getAttribute('paramId')); + args.push((childNode as Element).getAttribute('name')!); + paramIds.push((childNode as Element).getAttribute('paramId')!); } } this.setProcedureParameters_(args, paramIds); }, /** * Returns the state of this block as a JSON serializable object. - * @return {{name: string, params:(!Array|undefined)}} The state of - * this block, ie the params and procedure name. + * + * @returns The state of this block, ie the params and procedure name. */ - saveExtraState: function() { + saveExtraState: function (this: CallBlock): CallExtraState { const state = Object.create(null); state['name'] = this.getProcedureCall(); if (this.arguments_.length) { @@ -903,42 +1001,43 @@ const PROCEDURE_CALL_COMMON = { }, /** * Applies the given state to this block. - * @param {*} state The state to apply to this block, ie the params and + * + * @param state The state to apply to this block, ie the params and * procedure name. */ - loadExtraState: function(state) { + loadExtraState: function (this: CallBlock, state: CallExtraState) { this.renameProcedure(this.getProcedureCall(), state['name']); const params = state['params']; if (params) { - const ids = []; + const ids: string[] = []; ids.length = params.length; - ids.fill(null); + ids.fill(null as unknown as string); // TODO(#6920) this.setProcedureParameters_(params, ids); } }, /** * Return all variables referenced by this block. - * @return {!Array} List of variable names. - * @this {Block} + * + * @returns List of variable names. */ - getVars: function() { + getVars: function (this: CallBlock): string[] { return this.arguments_; }, /** * Return all variables referenced by this block. - * @return {!Array} List of variable models. - * @this {Block} + * + * @returns List of variable models. */ - getVarModels: function() { + getVarModels: function (this: CallBlock): VariableModel[] { return this.argumentVarModels_; }, /** * Procedure calls cannot exist without the corresponding procedure * definition. Enforce this link whenever an event is fired. - * @param {!AbstractEvent} event Change event. - * @this {Block} + * + * @param event Change event. */ - onchange: function(event) { + onchange: function (this: CallBlock, event: AbstractEvent) { if (!this.workspace || this.workspace.isFlyout) { // Block is deleted or is in a flyout. return; @@ -947,16 +1046,20 @@ const PROCEDURE_CALL_COMMON = { // Events not generated by user. Skip handling. return; } - if (event.type === Events.BLOCK_CREATE && - event.ids.indexOf(this.id) !== -1) { + if ( + event.type === Events.BLOCK_CREATE && + (event as BlockCreate).ids!.indexOf(this.id) !== -1 + ) { // Look for the case where a procedure call was created (usually through // paste) and there is no matching definition. In this case, create // an empty definition block with the correct signature. const name = this.getProcedureCall(); let def = Procedures.getDefinition(name, this.workspace); - if (def && - (def.type !== this.defType_ || - JSON.stringify(def.getVars()) !== JSON.stringify(this.arguments_))) { + if ( + def && + (def.type !== this.defType_ || + JSON.stringify(def.getVars()) !== JSON.stringify(this.arguments_)) + ) { // The signatures don't match. def = null; } @@ -979,8 +1082,8 @@ const PROCEDURE_CALL_COMMON = { const xy = this.getRelativeToSurfaceXY(); const x = xy.x + config.snapRadius * (this.RTL ? -1 : 1); const y = xy.y + config.snapRadius * 2; - block.setAttribute('x', x); - block.setAttribute('y', y); + block.setAttribute('x', `${x}`); + block.setAttribute('y', `${y}`); const mutation = this.mutationToDom(); block.appendChild(mutation); const field = xmlUtils.createElement('field'); @@ -1007,10 +1110,14 @@ const PROCEDURE_CALL_COMMON = { this.dispose(true); Events.setGroup(false); } - } else if (event.type === Events.CHANGE && event.element === 'disabled') { + } else if ( + event.type === Events.BLOCK_CHANGE && + (event as BlockChange).element === 'disabled' + ) { + const blockChangeEvent = event as BlockChange; const name = this.getProcedureCall(); const def = Procedures.getDefinition(name, this.workspace); - if (def && def.id === event.blockId) { + if (def && def.id === blockChangeEvent.blockId) { // in most cases the old group should be '' const oldGroup = Events.getGroup(); if (oldGroup) { @@ -1019,10 +1126,11 @@ const PROCEDURE_CALL_COMMON = { // investigate. If the use ends up being valid we may need to reorder // events in the undo stack. console.log( - 'Saw an existing group while responding to a definition change'); + 'Saw an existing group while responding to a definition change' + ); } Events.setGroup(event.group); - if (event.newValue) { + if (blockChangeEvent.newValue) { this.previousEnabledState_ = this.isEnabled(); this.setEnabled(false); } else { @@ -1034,28 +1142,32 @@ const PROCEDURE_CALL_COMMON = { }, /** * Add menu option to find the definition block for this call. - * @param {!Array} options List of menu options to add to. - * @this {Block} + * + * @param options List of menu options to add to. */ - customContextMenu: function(options) { - if (!this.workspace.isMovable()) { + customContextMenu: function ( + this: CallBlock, + options: Array + ) { + if (!(this.workspace as WorkspaceSvg).isMovable()) { // If we center on the block and the workspace isn't movable we could // loose blocks at the edges of the workspace. return; } - const option = {enabled: true}; - option.text = Msg['PROCEDURES_HIGHLIGHT_DEF']; const name = this.getProcedureCall(); const workspace = this.workspace; - option.callback = function() { - const def = Procedures.getDefinition(name, workspace); - if (def) { - workspace.centerOnBlock(def.id); - def.select(); - } - }; - options.push(option); + options.push({ + enabled: true, + text: Msg['PROCEDURES_HIGHLIGHT_DEF'], + callback: function () { + const def = Procedures.getDefinition(name, workspace); + if (def) { + (workspace as WorkspaceSvg).centerOnBlock(def.id); + (def as BlockSvg).select(); + } + }, + }); }, }; @@ -1063,9 +1175,8 @@ blocks['procedures_callnoreturn'] = { ...PROCEDURE_CALL_COMMON, /** * Block for calling a procedure with no return value. - * @this {Block} */ - init: function() { + init: function (this: CallBlock) { this.appendDummyInput('TOPROW').appendField('', 'NAME'); this.setPreviousStatement(true); this.setNextStatement(true); @@ -1086,9 +1197,8 @@ blocks['procedures_callreturn'] = { ...PROCEDURE_CALL_COMMON, /** * Block for calling a procedure with a return value. - * @this {Block} */ - init: function() { + init: function (this: CallBlock) { this.appendDummyInput('TOPROW').appendField('', 'NAME'); this.setOutput(true); this.setStyle('procedure_blocks'); @@ -1104,17 +1214,24 @@ blocks['procedures_callreturn'] = { defType_: 'procedures_defreturn', }; -blocks['procedures_ifreturn'] = { +/** Type of a procedures_ifreturn block. */ +type IfReturnBlock = Block & IfReturnMixin; +interface IfReturnMixin extends IfReturnMixinType { + hasReturnValue_: boolean; +} +type IfReturnMixinType = typeof PROCEDURES_IFRETURN; + +const PROCEDURES_IFRETURN = { /** * Block for conditionally returning a value from a procedure. - * @this {Block} */ - init: function() { + init: function (this: IfReturnBlock) { this.appendValueInput('CONDITION') - .setCheck('Boolean') - .appendField(Msg['CONTROLS_IF_MSG_IF']); + .setCheck('Boolean') + .appendField(Msg['CONTROLS_IF_MSG_IF']); this.appendValueInput('VALUE').appendField( - Msg['PROCEDURES_DEFRETURN_RETURN']); + Msg['PROCEDURES_DEFRETURN_RETURN'] + ); this.setInputsInline(true); this.setPreviousStatement(true); this.setNextStatement(true); @@ -1125,26 +1242,27 @@ blocks['procedures_ifreturn'] = { }, /** * Create XML to represent whether this block has a return value. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: IfReturnBlock): Element { const container = xmlUtils.createElement('mutation'); - container.setAttribute('value', Number(this.hasReturnValue_)); + container.setAttribute('value', String(Number(this.hasReturnValue_))); return container; }, /** * Parse XML to restore whether this block has a return value. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { + domToMutation: function (this: IfReturnBlock, xmlElement: Element) { const value = xmlElement.getAttribute('value'); - this.hasReturnValue_ = (value === '1'); + this.hasReturnValue_ = value === '1'; if (!this.hasReturnValue_) { this.removeInput('VALUE'); this.appendDummyInput('VALUE').appendField( - Msg['PROCEDURES_DEFRETURN_RETURN']); + Msg['PROCEDURES_DEFRETURN_RETURN'] + ); } }, @@ -1156,36 +1274,43 @@ blocks['procedures_ifreturn'] = { /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. - * @param {!AbstractEvent} e Move event. - * @this {Block} + * + * @param e Move event. */ - onchange: function(e) { - if (this.workspace.isDragging && this.workspace.isDragging() || - e.type !== Events.BLOCK_MOVE) { - return; // Don't change state at the start of a drag. + onchange: function (this: IfReturnBlock, e: AbstractEvent) { + if ( + ((this.workspace as WorkspaceSvg).isDragging && + (this.workspace as WorkspaceSvg).isDragging()) || + e.type !== Events.BLOCK_MOVE + ) { + return; // Don't change state at the start of a drag. } let legal = false; // Is the block nested in a procedure? - let block = this; + let block = this; // eslint-disable-line @typescript-eslint/no-this-alias do { if (this.FUNCTION_TYPES.indexOf(block.type) !== -1) { legal = true; break; } - block = block.getSurroundParent(); + block = block.getSurroundParent()!; } while (block); if (legal) { // If needed, toggle whether this block has a return value. if (block.type === 'procedures_defnoreturn' && this.hasReturnValue_) { this.removeInput('VALUE'); this.appendDummyInput('VALUE').appendField( - Msg['PROCEDURES_DEFRETURN_RETURN']); + Msg['PROCEDURES_DEFRETURN_RETURN'] + ); this.hasReturnValue_ = false; } else if ( - block.type === 'procedures_defreturn' && !this.hasReturnValue_) { + block.type === 'procedures_defreturn' && + !this.hasReturnValue_ + ) { this.removeInput('VALUE'); this.appendValueInput('VALUE').appendField( - Msg['PROCEDURES_DEFRETURN_RETURN']); + Msg['PROCEDURES_DEFRETURN_RETURN'] + ); this.hasReturnValue_ = true; } this.setWarningText(null); @@ -1207,6 +1332,7 @@ blocks['procedures_ifreturn'] = { */ FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn'], }; +blocks['procedures_ifreturn'] = PROCEDURES_IFRETURN; // Register provided blocks. defineBlocks(blocks); diff --git a/blocks/text.js b/blocks/text.ts similarity index 58% rename from blocks/text.js rename to blocks/text.ts index 66bd06752..0ce745d14 100644 --- a/blocks/text.js +++ b/blocks/text.ts @@ -4,59 +4,50 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Text blocks for Blockly. - * @suppress {checkTypes} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks.texts'); - -const Extensions = goog.require('Blockly.Extensions'); -const {Msg} = goog.require('Blockly.Msg'); -const fieldRegistry = goog.require('Blockly.fieldRegistry'); -/* eslint-disable-next-line no-unused-vars */ -const xmlUtils = goog.require('Blockly.utils.xml'); -const {Align} = goog.require('Blockly.Input'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {ConnectionType} = goog.require('Blockly.ConnectionType'); -const {Mutator} = goog.require('Blockly.Mutator'); -/* eslint-disable-next-line no-unused-vars */ -const {Workspace} = goog.requireType('Blockly.Workspace'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldMultilineInput'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.texts'); +import * as Extensions from '../core/extensions.js'; +import * as fieldRegistry from '../core/field_registry.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import {Align} from '../core/inputs/align.js'; +import type {Block} from '../core/block.js'; +import type {BlockSvg} from '../core/block_svg.js'; +import {Connection} from '../core/connection.js'; +import {FieldImage} from '../core/field_image.js'; +import {FieldDropdown} from '../core/field_dropdown.js'; +import {FieldTextInput} from '../core/field_textinput.js'; +import {Msg} from '../core/msg.js'; +import {MutatorIcon} from '../core/icons/mutator_icon.js'; +import type {Workspace} from '../core/workspace.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; +import '../core/field_multilineinput.js'; +import '../core/field_variable.js'; +import {ValueInput} from '../core/inputs/value_input.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for text value { 'type': 'text', 'message0': '%1', - 'args0': [{ - 'type': 'field_input', - 'name': 'TEXT', - 'text': '', - }], + 'args0': [ + { + 'type': 'field_input', + 'name': 'TEXT', + 'text': '', + }, + ], 'output': 'String', 'style': 'text_blocks', 'helpUrl': '%{BKY_TEXT_TEXT_HELPURL}', 'tooltip': '%{BKY_TEXT_TEXT_TOOLTIP}', - 'extensions': [ - 'text_quotes', - 'parent_tooltip_when_inline', - ], + 'extensions': ['text_quotes', 'parent_tooltip_when_inline'], }, { 'type': 'text_multiline', @@ -65,15 +56,15 @@ const blocks = createBlockDefinitionsFromJsonArray([ { 'type': 'field_image', 'src': - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpP' + - 'U2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0' + - 'U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjm' + - 'RIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z88' + - '73zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5luj' + - 'nYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiD' + - 'MYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYX' + - 'fwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4z' + - 'wA5X2Z9AYnQrEAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpP' + + 'U2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0' + + 'U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjm' + + 'RIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z88' + + '73zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5luj' + + 'nYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiD' + + 'MYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYX' + + 'fwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4z' + + 'wA5X2Z9AYnQrEAAAAASUVORK5CYII=', 'width': 12, 'height': 17, 'alt': '\u00B6', @@ -88,9 +79,7 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'style': 'text_blocks', 'helpUrl': '%{BKY_TEXT_TEXT_HELPURL}', 'tooltip': '%{BKY_TEXT_TEXT_TOOLTIP}', - 'extensions': [ - 'parent_tooltip_when_inline', - ], + 'extensions': ['parent_tooltip_when_inline'], }, { 'type': 'text_join', @@ -100,7 +89,6 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'helpUrl': '%{BKY_TEXT_JOIN_HELPURL}', 'tooltip': '%{BKY_TEXT_JOIN_TOOLTIP}', 'mutator': 'text_join_mutator', - }, { 'type': 'text_create_join_container', @@ -144,9 +132,7 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'previousStatement': null, 'nextStatement': null, 'style': 'text_blocks', - 'extensions': [ - 'text_append_tooltip', - ], + 'extensions': ['text_append_tooltip'], }, { 'type': 'text_length', @@ -191,14 +177,8 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'type': 'field_dropdown', 'name': 'END', 'options': [ - [ - '%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}', - 'FIRST', - ], - [ - '%{BKY_TEXT_INDEXOF_OPERATOR_LAST}', - 'LAST', - ], + ['%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}', 'FIRST'], + ['%{BKY_TEXT_INDEXOF_OPERATOR_LAST}', 'LAST'], ], }, { @@ -211,13 +191,11 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'style': 'text_blocks', 'helpUrl': '%{BKY_TEXT_INDEXOF_HELPURL}', 'inputsInline': true, - 'extensions': [ - 'text_indexOf_tooltip', - ], + 'extensions': ['text_indexOf_tooltip'], }, { 'type': 'text_charAt', - 'message0': '%{BKY_TEXT_CHARAT_TITLE}', // "in text %1 %2" + 'message0': '%{BKY_TEXT_CHARAT_TITLE}', // "in text %1 %2" 'args0': [ { 'type': 'input_value', @@ -243,14 +221,20 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'mutator': 'text_charAt_mutator', }, ]); -exports.blocks = blocks; -blocks['text_getSubstring'] = { +/** Type of a 'text_get_substring' block. */ +type GetSubstringBlock = Block & GetSubstringMixin; +interface GetSubstringMixin extends GetSubstringType { + WHERE_OPTIONS_1: Array<[string, string]>; + WHERE_OPTIONS_2: Array<[string, string]>; +} +type GetSubstringType = typeof GET_SUBSTRING_BLOCK; + +const GET_SUBSTRING_BLOCK = { /** * Block for getting substring. - * @this {Block} */ - init: function() { + init: function (this: GetSubstringBlock) { this['WHERE_OPTIONS_1'] = [ [Msg['TEXT_GET_SUBSTRING_START_FROM_START'], 'FROM_START'], [Msg['TEXT_GET_SUBSTRING_START_FROM_END'], 'FROM_END'], @@ -263,8 +247,9 @@ blocks['text_getSubstring'] = { ]; this.setHelpUrl(Msg['TEXT_GET_SUBSTRING_HELPURL']); this.setStyle('text_blocks'); - this.appendValueInput('STRING').setCheck('String').appendField( - Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']); + this.appendValueInput('STRING') + .setCheck('String') + .appendField(Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']); this.appendDummyInput('AT1'); this.appendDummyInput('AT2'); if (Msg['TEXT_GET_SUBSTRING_TAIL']) { @@ -279,26 +264,26 @@ blocks['text_getSubstring'] = { /** * Create XML to represent whether there are 'AT' inputs. * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: GetSubstringBlock): Element { const container = xmlUtils.createElement('mutation'); - const isAt1 = this.getInput('AT1').type === ConnectionType.INPUT_VALUE; - container.setAttribute('at1', isAt1); - const isAt2 = this.getInput('AT2').type === ConnectionType.INPUT_VALUE; - container.setAttribute('at2', isAt2); + const isAt1 = this.getInput('AT1') instanceof ValueInput; + container.setAttribute('at1', `${isAt1}`); + const isAt2 = this.getInput('AT2') instanceof ValueInput; + container.setAttribute('at2', `${isAt2}`); return container; }, /** * Parse XML to restore the 'AT' inputs. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - const isAt1 = (xmlElement.getAttribute('at1') === 'true'); - const isAt2 = (xmlElement.getAttribute('at2') === 'true'); + domToMutation: function (this: GetSubstringBlock, xmlElement: Element) { + const isAt1 = xmlElement.getAttribute('at1') === 'true'; + const isAt2 = xmlElement.getAttribute('at2') === 'true'; this.updateAt_(1, isAt1); this.updateAt_(2, isAt2); }, @@ -311,12 +296,12 @@ blocks['text_getSubstring'] = { /** * Create or delete an input for a numeric index. * This block has two such inputs, independent of each other. - * @param {number} n Specify first or second input (1 or 2). - * @param {boolean} isAt True if the input should exist. - * @private - * @this {Block} + * + * @internal + * @param n Which input to modify (either 1 or 2). + * @param isAt True if the input includes a value connection, false otherwise. */ - updateAt_: function(n, isAt) { + updateAt_: function (this: GetSubstringBlock, n: 1 | 2, isAt: boolean) { // Create or delete an input for the numeric index. // Destroy old 'AT' and 'ORDINAL' inputs. this.removeInput('AT' + n); @@ -325,8 +310,9 @@ blocks['text_getSubstring'] = { if (isAt) { this.appendValueInput('AT' + n).setCheck('Number'); if (Msg['ORDINAL_NUMBER_SUFFIX']) { - this.appendDummyInput('ORDINAL' + n) - .appendField(Msg['ORDINAL_NUMBER_SUFFIX']); + this.appendDummyInput('ORDINAL' + n).appendField( + Msg['ORDINAL_NUMBER_SUFFIX'] + ); } } else { this.appendDummyInput('AT' + n); @@ -338,31 +324,31 @@ blocks['text_getSubstring'] = { } const menu = fieldRegistry.fromJson({ type: 'field_dropdown', - options: this['WHERE_OPTIONS_' + n], - }); + options: + this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'], + }) as FieldDropdown; menu.setValidator( - /** - * @param {*} value The input value. - * @this {FieldDropdown} - * @return {null|undefined} Null if the field has been replaced; - * otherwise undefined. - */ - function(value) { - const newAt = (value === 'FROM_START') || (value === 'FROM_END'); - // The 'isAt' variable is available due to this function being a - // closure. - if (newAt !== isAt) { - const block = this.getSourceBlock(); - block.updateAt_(n, newAt); - // This menu has been destroyed and replaced. - // Update the replacement. - block.setFieldValue(value, 'WHERE' + n); - return null; - } - return undefined; - }); + /** + * @param value The input value. + * @returns Null if the field has been replaced; otherwise undefined. + */ + function (this: FieldDropdown, value: any): null | undefined { + const newAt = value === 'FROM_START' || value === 'FROM_END'; + // The 'isAt' variable is available due to this function being a + // closure. + if (newAt !== isAt) { + const block = this.getSourceBlock() as GetSubstringBlock; + block.updateAt_(n, newAt); + // This menu has been destroyed and replaced. + // Update the replacement. + block.setFieldValue(value, 'WHERE' + n); + return null; + } + return undefined; + } + ); - this.getInput('AT' + n).appendField(menu, 'WHERE' + n); + this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n); if (n === 1) { this.moveInputBefore('AT1', 'AT2'); if (this.getInput('ORDINAL1')) { @@ -372,12 +358,13 @@ blocks['text_getSubstring'] = { }, }; +blocks['text_getSubstring'] = GET_SUBSTRING_BLOCK; + blocks['text_changeCase'] = { /** * Block for changing capitalization. - * @this {Block} */ - init: function() { + init: function (this: Block) { const OPERATORS = [ [Msg['TEXT_CHANGECASE_OPERATOR_UPPERCASE'], 'UPPERCASE'], [Msg['TEXT_CHANGECASE_OPERATOR_LOWERCASE'], 'LOWERCASE'], @@ -385,12 +372,15 @@ blocks['text_changeCase'] = { ]; this.setHelpUrl(Msg['TEXT_CHANGECASE_HELPURL']); this.setStyle('text_blocks'); - this.appendValueInput('TEXT').setCheck('String').appendField( + this.appendValueInput('TEXT') + .setCheck('String') + .appendField( fieldRegistry.fromJson({ type: 'field_dropdown', options: OPERATORS, - }), - 'CASE'); + }) as FieldDropdown, + 'CASE' + ); this.setOutput(true, 'String'); this.setTooltip(Msg['TEXT_CHANGECASE_TOOLTIP']); }, @@ -399,9 +389,8 @@ blocks['text_changeCase'] = { blocks['text_trim'] = { /** * Block for trimming spaces. - * @this {Block} */ - init: function() { + init: function (this: Block) { const OPERATORS = [ [Msg['TEXT_TRIM_OPERATOR_BOTH'], 'BOTH'], [Msg['TEXT_TRIM_OPERATOR_LEFT'], 'LEFT'], @@ -409,12 +398,15 @@ blocks['text_trim'] = { ]; this.setHelpUrl(Msg['TEXT_TRIM_HELPURL']); this.setStyle('text_blocks'); - this.appendValueInput('TEXT').setCheck('String').appendField( + this.appendValueInput('TEXT') + .setCheck('String') + .appendField( fieldRegistry.fromJson({ type: 'field_dropdown', options: OPERATORS, - }), - 'MODE'); + }) as FieldDropdown, + 'MODE' + ); this.setOutput(true, 'String'); this.setTooltip(Msg['TEXT_TRIM_TOOLTIP']); }, @@ -423,9 +415,8 @@ blocks['text_trim'] = { blocks['text_print'] = { /** * Block for print statement. - * @this {Block} */ - init: function() { + init: function (this: Block) { this.jsonInit({ 'message0': Msg['TEXT_PRINT_TITLE'], 'args0': [ @@ -443,28 +434,31 @@ blocks['text_print'] = { }, }; +type PromptCommonBlock = Block & PromptCommonMixin; +interface PromptCommonMixin extends PromptCommonType {} +type PromptCommonType = typeof PROMPT_COMMON; /** * Common properties for the text_prompt_ext and text_prompt blocks * definitions. */ -const TEXT_PROMPT_COMMON = { +const PROMPT_COMMON = { /** * Modify this block to have the correct output type. - * @param {string} newOp Either 'TEXT' or 'NUMBER'. - * @private - * @this {Block} + * + * @internal + * @param newOp The new output type. Should be either 'TEXT' or 'NUMBER'. */ - updateType_: function(newOp) { - this.outputConnection.setCheck(newOp === 'NUMBER' ? 'Number' : 'String'); + updateType_: function (this: PromptCommonBlock, newOp: string) { + this.outputConnection!.setCheck(newOp === 'NUMBER' ? 'Number' : 'String'); }, /** * Create XML to represent the output type. * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: PromptCommonBlock): Element { const container = xmlUtils.createElement('mutation'); container.setAttribute('type', this.getFieldValue('TYPE')); return container; @@ -472,42 +466,40 @@ const TEXT_PROMPT_COMMON = { /** * Parse XML to restore the output type. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - this.updateType_(xmlElement.getAttribute('type')); + domToMutation: function (this: PromptCommonBlock, xmlElement: Element) { + this.updateType_(xmlElement.getAttribute('type')!); }, }; blocks['text_prompt_ext'] = { - ...TEXT_PROMPT_COMMON, + ...PROMPT_COMMON, /** * Block for prompt function (external message). - * @this {Block} */ - init: function() { + init: function (this: PromptCommonBlock) { const TYPES = [ [Msg['TEXT_PROMPT_TYPE_TEXT'], 'TEXT'], [Msg['TEXT_PROMPT_TYPE_NUMBER'], 'NUMBER'], ]; this.setHelpUrl(Msg['TEXT_PROMPT_HELPURL']); this.setStyle('text_blocks'); - // Assign 'this' to a variable for use in the closures below. - const thisBlock = this; const dropdown = fieldRegistry.fromJson({ type: 'field_dropdown', options: TYPES, - }); - dropdown.setValidator(function(newOp) { - thisBlock.updateType_(newOp); + }) as FieldDropdown; + dropdown.setValidator((newOp: string) => { + this.updateType_(newOp); + return undefined; // FieldValidators can't be void. Use option as-is. }); this.appendValueInput('TEXT').appendField(dropdown, 'TYPE'); this.setOutput(true, 'String'); - this.setTooltip(function() { - return (thisBlock.getFieldValue('TYPE') === 'TEXT') ? - Msg['TEXT_PROMPT_TOOLTIP_TEXT'] : - Msg['TEXT_PROMPT_TOOLTIP_NUMBER']; + this.setTooltip(() => { + return this.getFieldValue('TYPE') === 'TEXT' + ? Msg['TEXT_PROMPT_TOOLTIP_TEXT'] + : Msg['TEXT_PROMPT_TOOLTIP_NUMBER']; }); }, @@ -517,56 +509,58 @@ blocks['text_prompt_ext'] = { // XML hooks are kept for backwards compatibility. }; -blocks['text_prompt'] = { - ...TEXT_PROMPT_COMMON, +type PromptBlock = Block & PromptCommonMixin & QuoteImageMixin; + +const TEXT_PROMPT_BLOCK = { + ...PROMPT_COMMON, /** * Block for prompt function (internal message). * The 'text_prompt_ext' block is preferred as it is more flexible. - * @this {Block} */ - init: function() { + init: function (this: PromptBlock) { this.mixin(QUOTE_IMAGE_MIXIN); const TYPES = [ [Msg['TEXT_PROMPT_TYPE_TEXT'], 'TEXT'], [Msg['TEXT_PROMPT_TYPE_NUMBER'], 'NUMBER'], ]; - // Assign 'this' to a variable for use in the closures below. - const thisBlock = this; this.setHelpUrl(Msg['TEXT_PROMPT_HELPURL']); this.setStyle('text_blocks'); const dropdown = fieldRegistry.fromJson({ type: 'field_dropdown', options: TYPES, - }); - dropdown.setValidator(function(newOp) { - thisBlock.updateType_(newOp); + }) as FieldDropdown; + dropdown.setValidator((newOp: string) => { + this.updateType_(newOp); + return undefined; // FieldValidators can't be void. Use option as-is. }); this.appendDummyInput() - .appendField(dropdown, 'TYPE') - .appendField(this.newQuote_(true)) - .appendField( - fieldRegistry.fromJson({ - type: 'field_input', - text: '', - }), - 'TEXT') - .appendField(this.newQuote_(false)); + .appendField(dropdown, 'TYPE') + .appendField(this.newQuote_(true)) + .appendField( + fieldRegistry.fromJson({ + type: 'field_input', + text: '', + }) as FieldTextInput, + 'TEXT' + ) + .appendField(this.newQuote_(false)); this.setOutput(true, 'String'); - this.setTooltip(function() { - return (thisBlock.getFieldValue('TYPE') === 'TEXT') ? - Msg['TEXT_PROMPT_TOOLTIP_TEXT'] : - Msg['TEXT_PROMPT_TOOLTIP_NUMBER']; + this.setTooltip(() => { + return this.getFieldValue('TYPE') === 'TEXT' + ? Msg['TEXT_PROMPT_TOOLTIP_TEXT'] + : Msg['TEXT_PROMPT_TOOLTIP_NUMBER']; }); }, }; +blocks['text_prompt'] = TEXT_PROMPT_BLOCK; + blocks['text_count'] = { /** * Block for counting how many times one string appears within another string. - * @this {Block} */ - init: function() { + init: function (this: Block) { this.jsonInit({ 'message0': Msg['TEXT_COUNT_MESSAGE0'], 'args0': [ @@ -593,9 +587,8 @@ blocks['text_count'] = { blocks['text_replace'] = { /** * Block for replacing one string with another in the text. - * @this {Block} */ - init: function() { + init: function (this: Block) { this.jsonInit({ 'message0': Msg['TEXT_REPLACE_MESSAGE0'], 'args0': [ @@ -627,9 +620,8 @@ blocks['text_replace'] = { blocks['text_reverse'] = { /** * Block for reversing a string. - * @this {Block} */ - init: function() { + init: function (this: Block) { this.jsonInit({ 'message0': Msg['TEXT_REVERSE_MESSAGE0'], 'args0': [ @@ -648,51 +640,47 @@ blocks['text_reverse'] = { }, }; -/** - * @mixin - * @package - * @readonly - */ +/** Type of a block that has QUOTE_IMAGE_MIXIN */ +type QuoteImageBlock = Block & QuoteImageMixin; +interface QuoteImageMixin extends QuoteImageMixinType {} +type QuoteImageMixinType = typeof QUOTE_IMAGE_MIXIN; + const QUOTE_IMAGE_MIXIN = { /** * Image data URI of an LTR opening double quote (same as RTL closing double * quote). - * @readonly */ QUOTE_IMAGE_LEFT_DATAURI: - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' + - 'n0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY' + - '1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1' + - 'HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMf' + - 'z9AylsaRRgGzvZAAAAAElFTkSuQmCC', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' + + 'n0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY' + + '1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1' + + 'HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMf' + + 'z9AylsaRRgGzvZAAAAAElFTkSuQmCC', /** * Image data URI of an LTR closing double quote (same as RTL opening double * quote). - * @readonly */ QUOTE_IMAGE_RIGHT_DATAURI: - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' + - 'qUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhg' + - 'gONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvB' + - 'O3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5Aos' + - 'lLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' + + 'qUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhg' + + 'gONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvB' + + 'O3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5Aos' + + 'lLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==', /** * Pixel width of QUOTE_IMAGE_LEFT_DATAURI and QUOTE_IMAGE_RIGHT_DATAURI. - * @readonly */ QUOTE_IMAGE_WIDTH: 12, /** * Pixel height of QUOTE_IMAGE_LEFT_DATAURI and QUOTE_IMAGE_RIGHT_DATAURI. - * @readonly */ QUOTE_IMAGE_HEIGHT: 12, /** * Inserts appropriate quote images before and after the named field. - * @param {string} fieldName The name of the field to wrap with quotes. - * @this {Block} + * + * @param fieldName The name of the field to wrap with quotes. */ - quoteField_: function(fieldName) { + quoteField_: function (this: QuoteImageBlock, fieldName: string) { for (let i = 0, input; (input = this.inputList[i]); i++) { for (let j = 0, field; (field = input.fieldRow[j]); j++) { if (fieldName === field.name) { @@ -703,97 +691,113 @@ const QUOTE_IMAGE_MIXIN = { } } console.warn( - 'field named "' + fieldName + '" not found in ' + this.toDevString()); + 'field named "' + fieldName + '" not found in ' + this.toDevString() + ); }, /** * A helper function that generates a FieldImage of an opening or * closing double quote. The selected quote will be adapted for RTL blocks. - * @param {boolean} open If the image should be open quote (“ in LTR). + * + * @param open If the image should be open quote (“ in LTR). * Otherwise, a closing quote is used (” in LTR). - * @return {!FieldImage} The new field. - * @this {Block} + * @returns The new field. */ - newQuote_: function(open) { + newQuote_: function (this: QuoteImageBlock, open: boolean): FieldImage { const isLeft = this.RTL ? !open : open; - const dataUri = - isLeft ? this.QUOTE_IMAGE_LEFT_DATAURI : this.QUOTE_IMAGE_RIGHT_DATAURI; + const dataUri = isLeft + ? this.QUOTE_IMAGE_LEFT_DATAURI + : this.QUOTE_IMAGE_RIGHT_DATAURI; return fieldRegistry.fromJson({ type: 'field_image', src: dataUri, width: this.QUOTE_IMAGE_WIDTH, height: this.QUOTE_IMAGE_HEIGHT, alt: isLeft ? '\u201C' : '\u201D', - }); + }) as FieldImage; }, }; /** * Wraps TEXT field with images of double quote characters. - * @this {Block} */ -const TEXT_QUOTES_EXTENSION = function() { +const QUOTES_EXTENSION = function (this: QuoteImageBlock) { this.mixin(QUOTE_IMAGE_MIXIN); this.quoteField_('TEXT'); }; +/** Type of a block that has TEXT_JOIN_MUTATOR_MIXIN */ +type JoinMutatorBlock = BlockSvg & JoinMutatorMixin & QuoteImageMixin; +interface JoinMutatorMixin extends JoinMutatorMixinType {} +type JoinMutatorMixinType = typeof JOIN_MUTATOR_MIXIN; + +/** Type of a item block in the text_join_mutator bubble. */ +type JoinItemBlock = BlockSvg & JoinItemMixin; +interface JoinItemMixin { + valueConnection_: Connection | null; +} + /** * Mixin for mutator functions in the 'text_join_mutator' extension. - * @mixin - * @augments Block - * @package */ -const TEXT_JOIN_MUTATOR_MIXIN = { +const JOIN_MUTATOR_MIXIN = { + itemCount_: 0, /** * Create XML to represent number of text inputs. * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: JoinMutatorBlock): Element { const container = xmlUtils.createElement('mutation'); - container.setAttribute('items', this.itemCount_); + container.setAttribute('items', `${this.itemCount_}`); return container; }, /** * Parse XML to restore the text inputs. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { - this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10); + domToMutation: function (this: JoinMutatorBlock, xmlElement: Element) { + this.itemCount_ = parseInt(xmlElement.getAttribute('items')!, 10); this.updateShape_(); }, /** * Returns the state of this block as a JSON serializable object. - * @return {{itemCount: number}} The state of this block, ie the item count. + * + * @returns The state of this block, ie the item count. */ - saveExtraState: function() { + saveExtraState: function (this: JoinMutatorBlock): {itemCount: number} { return { 'itemCount': this.itemCount_, }; }, /** * Applies the given state to this block. - * @param {*} state The state to apply to this block, ie the item count. + * + * @param state The state to apply to this block, ie the item count. */ - loadExtraState: function(state) { + loadExtraState: function (this: JoinMutatorBlock, state: {[x: string]: any}) { this.itemCount_ = state['itemCount']; this.updateShape_(); }, /** * Populate the mutator's dialog with this block's components. - * @param {!Workspace} workspace Mutator's workspace. - * @return {!Block} Root block in mutator. - * @this {Block} + * + * @param workspace Mutator's workspace. + * @returns Root block in mutator. */ - decompose: function(workspace) { - const containerBlock = workspace.newBlock('text_create_join_container'); + decompose: function (this: JoinMutatorBlock, workspace: Workspace): Block { + const containerBlock = workspace.newBlock( + 'text_create_join_container' + ) as BlockSvg; containerBlock.initSvg(); - let connection = containerBlock.getInput('STACK').connection; + let connection = containerBlock.getInput('STACK')!.connection!; for (let i = 0; i < this.itemCount_; i++) { - const itemBlock = workspace.newBlock('text_create_join_item'); + const itemBlock = workspace.newBlock( + 'text_create_join_item' + ) as JoinItemBlock; itemBlock.initSvg(); connection.connect(itemBlock.previousConnection); connection = itemBlock.nextConnection; @@ -802,24 +806,26 @@ const TEXT_JOIN_MUTATOR_MIXIN = { }, /** * Reconfigure this block based on the mutator dialog's components. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - compose: function(containerBlock) { - let itemBlock = containerBlock.getInputTargetBlock('STACK'); + compose: function (this: JoinMutatorBlock, containerBlock: Block) { + let itemBlock = containerBlock.getInputTargetBlock( + 'STACK' + ) as JoinItemBlock; // Count number of inputs. const connections = []; while (itemBlock) { if (itemBlock.isInsertionMarker()) { - itemBlock = itemBlock.getNextBlock(); + itemBlock = itemBlock.getNextBlock() as JoinItemBlock; continue; } connections.push(itemBlock.valueConnection_); - itemBlock = itemBlock.getNextBlock(); + itemBlock = itemBlock.getNextBlock() as JoinItemBlock; } // Disconnect any children that don't belong. for (let i = 0; i < this.itemCount_; i++) { - const connection = this.getInput('ADD' + i).connection.targetConnection; + const connection = this.getInput('ADD' + i)!.connection!.targetConnection; if (connection && connections.indexOf(connection) === -1) { connection.disconnect(); } @@ -828,15 +834,15 @@ const TEXT_JOIN_MUTATOR_MIXIN = { this.updateShape_(); // Reconnect any child blocks. for (let i = 0; i < this.itemCount_; i++) { - Mutator.reconnect(connections[i], this, 'ADD' + i); + connections[i]?.reconnect(this, 'ADD' + i); } }, /** * Store pointers to any connected child blocks. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} + * + * @param containerBlock Root block in mutator. */ - saveConnections: function(containerBlock) { + saveConnections: function (this: JoinMutatorBlock, containerBlock: Block) { let itemBlock = containerBlock.getInputTargetBlock('STACK'); let i = 0; while (itemBlock) { @@ -845,23 +851,23 @@ const TEXT_JOIN_MUTATOR_MIXIN = { continue; } const input = this.getInput('ADD' + i); - itemBlock.valueConnection_ = input && input.connection.targetConnection; + (itemBlock as JoinItemBlock).valueConnection_ = + input && input.connection!.targetConnection; itemBlock = itemBlock.getNextBlock(); i++; } }, /** * Modify this block to have the correct number of inputs. - * @private - * @this {Block} + * */ - updateShape_: function() { + updateShape_: function (this: JoinMutatorBlock) { if (this.itemCount_ && this.getInput('EMPTY')) { this.removeInput('EMPTY'); } else if (!this.itemCount_ && !this.getInput('EMPTY')) { this.appendDummyInput('EMPTY') - .appendField(this.newQuote_(true)) - .appendField(this.newQuote_(false)); + .appendField(this.newQuote_(true)) + .appendField(this.newQuote_(false)); } // Add new inputs. for (let i = 0; i < this.itemCount_; i++) { @@ -881,64 +887,66 @@ const TEXT_JOIN_MUTATOR_MIXIN = { /** * Performs final setup of a text_join block. - * @this {Block} */ -const TEXT_JOIN_EXTENSION = function() { +const JOIN_EXTENSION = function (this: JoinMutatorBlock) { // Add the quote mixin for the itemCount_ = 0 case. this.mixin(QUOTE_IMAGE_MIXIN); // Initialize the mutator values. this.itemCount_ = 2; this.updateShape_(); // Configure the mutator UI. - this.setMutator(new Mutator(['text_create_join_item'], this)); + this.setMutator(new MutatorIcon(['text_create_join_item'], this)); }; // Update the tooltip of 'text_append' block to reference the variable. Extensions.register( - 'text_append_tooltip', - Extensions.buildTooltipWithFieldText('%{BKY_TEXT_APPEND_TOOLTIP}', 'VAR')); + 'text_append_tooltip', + Extensions.buildTooltipWithFieldText('%{BKY_TEXT_APPEND_TOOLTIP}', 'VAR') +); /** * Update the tooltip of 'text_append' block to reference the variable. - * @this {Block} */ -const TEXT_INDEXOF_TOOLTIP_EXTENSION = function() { - // Assign 'this' to a variable for use in the tooltip closure below. - const thisBlock = this; - this.setTooltip(function() { +const INDEXOF_TOOLTIP_EXTENSION = function (this: Block) { + this.setTooltip(() => { return Msg['TEXT_INDEXOF_TOOLTIP'].replace( - '%1', thisBlock.workspace.options.oneBasedIndex ? '0' : '-1'); + '%1', + this.workspace.options.oneBasedIndex ? '0' : '-1' + ); }); }; +/** Type of a block that has TEXT_CHARAT_MUTATOR_MIXIN */ +type CharAtBlock = Block & CharAtMixin; +interface CharAtMixin extends CharAtMixinType {} +type CharAtMixinType = typeof CHARAT_MUTATOR_MIXIN; + /** * Mixin for mutator functions in the 'text_charAt_mutator' extension. - * @mixin - * @augments Block - * @package */ -const TEXT_CHARAT_MUTATOR_MIXIN = { +const CHARAT_MUTATOR_MIXIN = { + isAt_: false, /** * Create XML to represent whether there is an 'AT' input. * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} + * + * @returns XML storage element. */ - mutationToDom: function() { + mutationToDom: function (this: CharAtBlock): Element { const container = xmlUtils.createElement('mutation'); - container.setAttribute('at', !!this.isAt_); + container.setAttribute('at', `${this.isAt_}`); return container; }, /** * Parse XML to restore the 'AT' input. * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} + * + * @param xmlElement XML storage element. */ - domToMutation: function(xmlElement) { + domToMutation: function (this: CharAtBlock, xmlElement: Element) { // Note: Until January 2013 this block did not have mutations, // so 'at' defaults to true. - const isAt = (xmlElement.getAttribute('at') !== 'false'); + const isAt = xmlElement.getAttribute('at') !== 'false'; this.updateAt_(isAt); }, @@ -949,11 +957,11 @@ const TEXT_CHARAT_MUTATOR_MIXIN = { /** * Create or delete an input for the numeric index. - * @param {boolean} isAt True if the input should exist. - * @private - * @this {Block} + * + * @internal + * @param isAt True if the input should exist. */ - updateAt_: function(isAt) { + updateAt_: function (this: CharAtBlock, isAt: boolean) { // Destroy old 'AT' and 'ORDINAL' inputs. this.removeInput('AT', true); this.removeInput('ORDINAL', true); @@ -962,7 +970,8 @@ const TEXT_CHARAT_MUTATOR_MIXIN = { this.appendValueInput('AT').setCheck('Number'); if (Msg['ORDINAL_NUMBER_SUFFIX']) { this.appendDummyInput('ORDINAL').appendField( - Msg['ORDINAL_NUMBER_SUFFIX']); + Msg['ORDINAL_NUMBER_SUFFIX'] + ); } } if (Msg['TEXT_CHARAT_TAIL']) { @@ -976,51 +985,51 @@ const TEXT_CHARAT_MUTATOR_MIXIN = { /** * Does the initial mutator update of text_charAt and adds the tooltip - * @this {Block} */ -const TEXT_CHARAT_EXTENSION = function() { - const dropdown = this.getField('WHERE'); - dropdown.setValidator( - /** - * @param {*} value The input value. - * @this {FieldDropdown} - */ - function(value) { - const newAt = (value === 'FROM_START') || (value === 'FROM_END'); - if (newAt !== this.isAt_) { - const block = this.getSourceBlock(); - block.updateAt_(newAt); - } - }); +const CHARAT_EXTENSION = function (this: CharAtBlock) { + const dropdown = this.getField('WHERE') as FieldDropdown; + dropdown.setValidator(function (this: FieldDropdown, value: any) { + const newAt = value === 'FROM_START' || value === 'FROM_END'; + const block = this.getSourceBlock() as CharAtBlock; + if (newAt !== block.isAt_) { + block.updateAt_(newAt); + } + return undefined; // FieldValidators can't be void. Use option as-is. + }); this.updateAt_(true); - // Assign 'this' to a variable for use in the tooltip closure below. - const thisBlock = this; - this.setTooltip(function() { - const where = thisBlock.getFieldValue('WHERE'); + this.setTooltip(() => { + const where = this.getFieldValue('WHERE'); let tooltip = Msg['TEXT_CHARAT_TOOLTIP']; if (where === 'FROM_START' || where === 'FROM_END') { - const msg = (where === 'FROM_START') ? - Msg['LISTS_INDEX_FROM_START_TOOLTIP'] : - Msg['LISTS_INDEX_FROM_END_TOOLTIP']; + const msg = + where === 'FROM_START' + ? Msg['LISTS_INDEX_FROM_START_TOOLTIP'] + : Msg['LISTS_INDEX_FROM_END_TOOLTIP']; if (msg) { - tooltip += ' ' + - msg.replace( - '%1', thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0'); + tooltip += + ' ' + + msg.replace('%1', this.workspace.options.oneBasedIndex ? '#1' : '#0'); } } return tooltip; }); }; -Extensions.register('text_indexOf_tooltip', TEXT_INDEXOF_TOOLTIP_EXTENSION); +Extensions.register('text_indexOf_tooltip', INDEXOF_TOOLTIP_EXTENSION); -Extensions.register('text_quotes', TEXT_QUOTES_EXTENSION); +Extensions.register('text_quotes', QUOTES_EXTENSION); Extensions.registerMutator( - 'text_join_mutator', TEXT_JOIN_MUTATOR_MIXIN, TEXT_JOIN_EXTENSION); + 'text_join_mutator', + JOIN_MUTATOR_MIXIN, + JOIN_EXTENSION +); Extensions.registerMutator( - 'text_charAt_mutator', TEXT_CHARAT_MUTATOR_MIXIN, TEXT_CHARAT_EXTENSION); + 'text_charAt_mutator', + CHARAT_MUTATOR_MIXIN, + CHARAT_EXTENSION +); // Register provided blocks. defineBlocks(blocks); diff --git a/blocks/variables.js b/blocks/variables.ts similarity index 56% rename from blocks/variables.js rename to blocks/variables.ts index a3bbd1a8d..19705f4ae 100644 --- a/blocks/variables.js +++ b/blocks/variables.ts @@ -4,37 +4,31 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Variable blocks for Blockly. - * @suppress {checkTypes} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks.variables'); - -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Extensions = goog.require('Blockly.Extensions'); -const Variables = goog.require('Blockly.Variables'); -const xmlUtils = goog.require('Blockly.utils.xml'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {Msg} = goog.require('Blockly.Msg'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.variables'); +import * as ContextMenu from '../core/contextmenu.js'; +import * as Extensions from '../core/extensions.js'; +import * as Variables from '../core/variables.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import type {Block} from '../core/block.js'; +import type { + ContextMenuOption, + LegacyContextMenuOption, +} from '../core/contextmenu_registry.js'; +import {FieldVariable} from '../core/field_variable.js'; +import {Msg} from '../core/msg.js'; +import type {WorkspaceSvg} from '../core/workspace_svg.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; +import '../core/field_label.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get', @@ -75,25 +69,28 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'extensions': ['contextMenu_variableSetterGetter'], }, ]); -exports.blocks = blocks; +/** Type of a block that has CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN */ +type VariableBlock = Block & VariableMixin; +interface VariableMixin extends VariableMixinType {} +type VariableMixinType = + typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN; /** * Mixin to add context menu items to create getter/setter blocks for this * setter/getter. * Used by blocks 'variables_set' and 'variables_get'. - * @mixin - * @augments Block - * @package - * @readonly */ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Add menu option to create getter/setter block for this setter/getter. - * @param {!Array} options List of menu options to add to. - * @this {Block} + * + * @param options List of menu options to add to. */ - customContextMenu: function(options) { + customContextMenu: function ( + this: VariableBlock, + options: Array + ) { if (!this.isInFlyout) { let oppositeType; let contextMenuMsg; @@ -106,27 +103,31 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { contextMenuMsg = Msg['VARIABLES_SET_CREATE_GET']; } - const option = {enabled: this.workspace.remainingCapacity() > 0}; - const name = this.getField('VAR').getText(); - option.text = contextMenuMsg.replace('%1', name); + const name = this.getField('VAR')!.getText(); const xmlField = xmlUtils.createElement('field'); xmlField.setAttribute('name', 'VAR'); xmlField.appendChild(xmlUtils.createTextNode(name)); const xmlBlock = xmlUtils.createElement('block'); xmlBlock.setAttribute('type', oppositeType); xmlBlock.appendChild(xmlField); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + + options.push({ + enabled: this.workspace.remainingCapacity() > 0, + text: contextMenuMsg.replace('%1', name), + callback: ContextMenu.callbackFactory(this, xmlBlock), + }); // Getter blocks have the option to rename or delete that variable. } else { - if (this.type === 'variables_get' || - this.type === 'variables_get_reporter') { + if ( + this.type === 'variables_get' || + this.type === 'variables_get_reporter' + ) { const renameOption = { text: Msg['RENAME_VARIABLE'], enabled: true, callback: renameOptionCallbackFactory(this), }; - const name = this.getField('VAR').getText(); + const name = this.getField('VAR')!.getText(); const deleteOption = { text: Msg['DELETE_VARIABLE'].replace('%1', name), enabled: true, @@ -142,13 +143,17 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Factory for callbacks for rename variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to rename. - * @return {!function()} A function that renames the variable. + * + * @param block The block with the variable to rename. + * @returns A function that renames the variable. */ -const renameOptionCallbackFactory = function(block) { - return function() { +const renameOptionCallbackFactory = function ( + block: VariableBlock +): () => void { + return function () { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; Variables.renameVariable(workspace, variable); }; }; @@ -156,21 +161,26 @@ const renameOptionCallbackFactory = function(block) { /** * Factory for callbacks for delete variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to delete. - * @return {!function()} A function that deletes the variable. + * + * @param block The block with the variable to delete. + * @returns A function that deletes the variable. */ -const deleteOptionCallbackFactory = function(block) { - return function() { +const deleteOptionCallbackFactory = function ( + block: VariableBlock +): () => void { + return function () { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; workspace.deleteVariableById(variable.getId()); - workspace.refreshToolboxSelection(); + (workspace as WorkspaceSvg).refreshToolboxSelection(); }; }; Extensions.registerMixin( - 'contextMenu_variableSetterGetter', - CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + 'contextMenu_variableSetterGetter', + CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN +); // Register provided blocks. defineBlocks(blocks); diff --git a/blocks/variables_dynamic.js b/blocks/variables_dynamic.ts similarity index 54% rename from blocks/variables_dynamic.js rename to blocks/variables_dynamic.ts index 4ee7dcadc..70fd4bb2c 100644 --- a/blocks/variables_dynamic.js +++ b/blocks/variables_dynamic.ts @@ -4,48 +4,43 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @fileoverview Variable blocks for Blockly. - * @suppress {checkTypes} - */ -'use strict'; - -goog.module('Blockly.libraryBlocks.variablesDynamic'); - -/* eslint-disable-next-line no-unused-vars */ -const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Extensions = goog.require('Blockly.Extensions'); -const Variables = goog.require('Blockly.Variables'); -const xml = goog.require('Blockly.utils.xml'); -/* eslint-disable-next-line no-unused-vars */ -const {Block} = goog.requireType('Blockly.Block'); -// const {BlockDefinition} = goog.requireType('Blockly.blocks'); -// TODO (6248): Properly import the BlockDefinition type. -/* eslint-disable-next-line no-unused-vars */ -const BlockDefinition = Object; -const {Msg} = goog.require('Blockly.Msg'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.variablesDynamic'); +import * as ContextMenu from '../core/contextmenu.js'; +import * as Extensions from '../core/extensions.js'; +import * as Variables from '../core/variables.js'; +import * as xml from '../core/utils/xml.js'; +import {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; +import type {Block} from '../core/block.js'; +import type { + ContextMenuOption, + LegacyContextMenuOption, +} from '../core/contextmenu_registry.js'; +import {FieldVariable} from '../core/field_variable.js'; +import {Msg} from '../core/msg.js'; +import type {WorkspaceSvg} from '../core/workspace_svg.js'; +import { + createBlockDefinitionsFromJsonArray, + defineBlocks, +} from '../core/common.js'; +import '../core/field_label.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get_dynamic', 'message0': '%1', - 'args0': [{ - 'type': 'field_variable', - 'name': 'VAR', - 'variable': '%{BKY_VARIABLES_DEFAULT_NAME}', - }], + 'args0': [ + { + 'type': 'field_variable', + 'name': 'VAR', + 'variable': '%{BKY_VARIABLES_DEFAULT_NAME}', + }, + ], 'output': null, 'style': 'variable_dynamic_blocks', 'helpUrl': '%{BKY_VARIABLES_GET_HELPURL}', @@ -75,30 +70,35 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'extensions': ['contextMenu_variableDynamicSetterGetter'], }, ]); -exports.blocks = blocks; + +/** Type of a block that has CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN */ +type VariableBlock = Block & VariableMixin; +interface VariableMixin extends VariableMixinType {} +type VariableMixinType = + typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN; /** * Mixin to add context menu items to create getter/setter blocks for this * setter/getter. * Used by blocks 'variables_set_dynamic' and 'variables_get_dynamic'. - * @mixin - * @augments Block - * @readonly */ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Add menu option to create getter/setter block for this setter/getter. - * @param {!Array} options List of menu options to add to. - * @this {Block} + * + * @param options List of menu options to add to. */ - customContextMenu: function(options) { + customContextMenu: function ( + this: VariableBlock, + options: Array + ) { // Getter blocks have the option to create a setter block, and vice versa. if (!this.isInFlyout) { let oppositeType; let contextMenuMsg; const id = this.getFieldValue('VAR'); const variableModel = this.workspace.getVariableById(id); - const varType = variableModel.type; + const varType = variableModel!.type; if (this.type === 'variables_get_dynamic') { oppositeType = 'variables_set_dynamic'; contextMenuMsg = Msg['VARIABLES_GET_CREATE_SET']; @@ -107,9 +107,7 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { contextMenuMsg = Msg['VARIABLES_SET_CREATE_GET']; } - const option = {enabled: this.workspace.remainingCapacity() > 0}; - const name = this.getField('VAR').getText(); - option.text = contextMenuMsg.replace('%1', name); + const name = this.getField('VAR')!.getText(); const xmlField = xml.createElement('field'); xmlField.setAttribute('name', 'VAR'); xmlField.setAttribute('variabletype', varType); @@ -117,17 +115,23 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { const xmlBlock = xml.createElement('block'); xmlBlock.setAttribute('type', oppositeType); xmlBlock.appendChild(xmlField); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + + options.push({ + enabled: this.workspace.remainingCapacity() > 0, + text: contextMenuMsg.replace('%1', name), + callback: ContextMenu.callbackFactory(this, xmlBlock), + }); } else { - if (this.type === 'variables_get_dynamic' || - this.type === 'variables_get_reporter_dynamic') { + if ( + this.type === 'variables_get_dynamic' || + this.type === 'variables_get_reporter_dynamic' + ) { const renameOption = { text: Msg['RENAME_VARIABLE'], enabled: true, callback: renameOptionCallbackFactory(this), }; - const name = this.getField('VAR').getText(); + const name = this.getField('VAR')!.getText(); const deleteOption = { text: Msg['DELETE_VARIABLE'].replace('%1', name), enabled: true, @@ -141,16 +145,16 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Called whenever anything on the workspace changes. * Set the connection type for this block. - * @param {AbstractEvent} _e Change event. - * @this {Block} + * + * @param _e Change event. */ - onchange: function(_e) { + onchange: function (this: VariableBlock, _e: AbstractEvent) { const id = this.getFieldValue('VAR'); - const variableModel = Variables.getVariable(this.workspace, id); + const variableModel = Variables.getVariable(this.workspace, id)!; if (this.type === 'variables_get_dynamic') { - this.outputConnection.setCheck(variableModel.type); + this.outputConnection!.setCheck(variableModel.type); } else { - this.getInput('VALUE').connection.setCheck(variableModel.type); + this.getInput('VALUE')!.connection!.setCheck(variableModel.type); } }, }; @@ -158,13 +162,15 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Factory for callbacks for rename variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to rename. - * @return {!function()} A function that renames the variable. + * + * @param block The block with the variable to rename. + * @returns A function that renames the variable. */ -const renameOptionCallbackFactory = function(block) { - return function() { +const renameOptionCallbackFactory = function (block: VariableBlock) { + return function () { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; Variables.renameVariable(workspace, variable); }; }; @@ -172,21 +178,24 @@ const renameOptionCallbackFactory = function(block) { /** * Factory for callbacks for delete variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to delete. - * @return {!function()} A function that deletes the variable. + * + * @param block The block with the variable to delete. + * @returns A function that deletes the variable. */ -const deleteOptionCallbackFactory = function(block) { - return function() { +const deleteOptionCallbackFactory = function (block: VariableBlock) { + return function () { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; workspace.deleteVariableById(variable.getId()); - workspace.refreshToolboxSelection(); + (workspace as WorkspaceSvg).refreshToolboxSelection(); }; }; Extensions.registerMixin( - 'contextMenu_variableDynamicSetterGetter', - CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); + 'contextMenu_variableDynamicSetterGetter', + CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN +); // Register provided blocks. defineBlocks(blocks); diff --git a/core/block.ts b/core/block.ts index 33dc56af3..a4627e3ad 100644 --- a/core/block.ts +++ b/core/block.ts @@ -20,31 +20,37 @@ import './events/events_block_create.js'; import './events/events_block_delete.js'; import {Blocks} from './blocks.js'; -import type {Comment} from './comment.js'; import * as common from './common.js'; import {Connection} from './connection.js'; import {ConnectionType} from './connection_type.js'; import * as constants from './constants.js'; +import {DuplicateIconType} from './icons/exceptions.js'; import type {Abstract} from './events/events_abstract.js'; import type {BlockMove} from './events/events_block_move.js'; import * as eventUtils from './events/utils.js'; import * as Extensions from './extensions.js'; import type {Field} from './field.js'; import * as fieldRegistry from './field_registry.js'; -import {Align, Input} from './input.js'; -import {inputTypes} from './input_types.js'; +import {Input} from './inputs/input.js'; +import {Align} from './inputs/align.js'; import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import type {IDeletable} from './interfaces/i_deletable.js'; -import type {Mutator} from './mutator.js'; +import type {IIcon} from './interfaces/i_icon.js'; +import {CommentIcon} from './icons/comment_icon.js'; +import type {MutatorIcon} from './icons/mutator_icon.js'; import * as Tooltip from './tooltip.js'; import * as arrayUtils from './utils/array.js'; import {Coordinate} from './utils/coordinate.js'; import * as idGenerator from './utils/idgenerator.js'; import * as parsing from './utils/parsing.js'; +import * as registry from './registry.js'; import {Size} from './utils/size.js'; import type {VariableModel} from './variable_model.js'; import type {Workspace} from './workspace.js'; - +import {DummyInput} from './inputs/dummy_input.js'; +import {ValueInput} from './inputs/value_input.js'; +import {StatementInput} from './inputs/statement_input.js'; +import {IconType} from './icons/icon_types.js'; /** * Class for one block. @@ -56,7 +62,7 @@ export class Block implements IASTNodeLocation, IDeletable { * changes. This is usually only called from the constructor, the block type * initializer function, or an extension initializer function. */ - onchange?: ((p1: Abstract) => void)|null; + onchange?: ((p1: Abstract) => void) | null; /** The language-neutral ID given to the collapsed input. */ static readonly COLLAPSED_INPUT_NAME: string = constants.COLLAPSED_INPUT_NAME; @@ -68,7 +74,7 @@ export class Block implements IASTNodeLocation, IDeletable { * Optional text data that round-trips between blocks and XML. * Has no effect. May be used by 3rd parties for meta information. */ - data: string|null = null; + data: string | null = null; /** * Has this block been disposed of? @@ -81,7 +87,7 @@ export class Block implements IASTNodeLocation, IDeletable { * Colour of the block as HSV hue value (0-360) * This may be null if the block colour was not set via a hue number. */ - private hue_: number|null = null; + private hue_: number | null = null; /** Colour of the block in '#RRGGBB' format. */ protected colour_ = '#000000'; @@ -90,10 +96,10 @@ export class Block implements IASTNodeLocation, IDeletable { protected styleName_ = ''; /** An optional method called during initialization. */ - init?: (() => void); + init?: () => void; /** An optional method called during disposal. */ - destroy?: (() => void); + destroy?: () => void; /** * An optional serialization method for defining how to serialize the @@ -127,7 +133,7 @@ export class Block implements IASTNodeLocation, IDeletable { * An optional property for suppressing adding STATEMENT_PREFIX and * STATEMENT_SUFFIX to generated code. */ - suppressPrefixSuffix: boolean|null = false; + suppressPrefixSuffix: boolean | null = false; /** * An optional property for declaring developer variables. Return a list of @@ -149,16 +155,17 @@ export class Block implements IASTNodeLocation, IDeletable { */ decompose?: (p1: Workspace) => Block; id: string; - outputConnection: Connection|null = null; - nextConnection: Connection|null = null; - previousConnection: Connection|null = null; + outputConnection: Connection | null = null; + nextConnection: Connection | null = null; + previousConnection: Connection | null = null; inputList: Input[] = []; inputsInline?: boolean; + icons: IIcon[] = []; private disabled = false; tooltip: Tooltip.TipInfo = ''; contextMenu = true; - protected parentBlock_: this|null = null; + protected parentBlock_: this | null = null; protected childBlocks_: this[] = []; @@ -171,21 +178,13 @@ export class Block implements IASTNodeLocation, IDeletable { private isShadow_ = false; protected collapsed_ = false; - protected outputShape_: number|null = null; + protected outputShape_: number | null = null; /** * Is the current block currently in the process of being disposed? */ private disposing = false; - /** - * A string representing the comment attached to this block. - * - * @deprecated August 2019. Use getCommentText instead. - */ - comment: string|Comment|null = null; - /** @internal */ - commentModel: CommentModel; private readonly xy_: Coordinate; isInFlyout: boolean; isInMutator: boolean; @@ -197,15 +196,15 @@ export class Block implements IASTNodeLocation, IDeletable { /** Name of the type of hat. */ hat?: string; - rendered: boolean|null = null; + rendered: boolean | null = null; /** * String for block help, or function that returns a URL. Null for no help. */ - helpUrl: string|Function|null = null; + helpUrl: string | Function | null = null; /** A bound callback function to use when the parent workspace changes. */ - private onchangeWrapper_: ((p1: Abstract) => void)|null = null; + private onchangeWrapper_: ((p1: Abstract) => void) | null = null; /** * A count of statement inputs on the block. @@ -230,13 +229,10 @@ export class Block implements IASTNodeLocation, IDeletable { constructor(workspace: Workspace, prototypeName: string, opt_id?: string) { this.workspace = workspace; - this.id = opt_id && !workspace.getBlockById(opt_id) ? opt_id : - idGenerator.genUid(); + this.id = + opt_id && !workspace.getBlockById(opt_id) ? opt_id : idGenerator.genUid(); workspace.setBlockById(this.id, this); - /** A model of the comment attached to this block. */ - this.commentModel = {text: null, pinned: false, size: new Size(160, 80)}; - /** * The block's position in workspace units. (0, 0) is at the workspace's * origin; scale does not change this value. @@ -307,7 +303,6 @@ export class Block implements IASTNodeLocation, IDeletable { * @param healStack If true, then try to heal any gap by connecting the next * statement with the previous statement. Otherwise, dispose of all * children of this block. - * @suppress {checkTypes} */ dispose(healStack: boolean) { if (this.isDeadOrDying()) return; @@ -339,23 +334,17 @@ export class Block implements IASTNodeLocation, IDeletable { this.workspace.removeChangeListener(this.onchangeWrapper_); } - eventUtils.disable(); - try { - this.workspace.removeTypedBlock(this); - this.workspace.removeBlockById(this.id); - this.disposing = true; + this.workspace.removeTypedBlock(this); + this.workspace.removeBlockById(this.id); + this.disposing = true; - this.childBlocks_.forEach((c) => c.disposeInternal()); - this.inputList.forEach((i) => i.dispose()); - this.inputList.length = 0; - this.getConnections_(true).forEach((c) => c.dispose()); - } finally { - eventUtils.enable(); - if (typeof this.destroy === 'function') { - this.destroy(); - } - this.disposed = true; - } + if (typeof this.destroy === 'function') this.destroy(); + + this.childBlocks_.forEach((c) => c.disposeInternal()); + this.inputList.forEach((i) => i.dispose()); + this.inputList.length = 0; + this.getConnections_(true).forEach((c) => c.dispose()); + this.disposed = true; } /** @@ -377,8 +366,8 @@ export class Block implements IASTNodeLocation, IDeletable { * change). */ initModel() { - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { if (field.initModel) { field.initModel(); } @@ -423,8 +412,11 @@ export class Block implements IASTNodeLocation, IDeletable { } const thisConnection = this.getOnlyValueConnection_(); - if (!thisConnection || !thisConnection.isConnected() || - thisConnection.targetBlock()!.isShadow()) { + if ( + !thisConnection || + !thisConnection.isConnected() || + thisConnection.targetBlock()!.isShadow() + ) { // Too many or too few possible connections on this block, or there's // nothing on the other side of this connection. return; @@ -434,8 +426,13 @@ export class Block implements IASTNodeLocation, IDeletable { // Disconnect the child block. childConnection?.disconnect(); // Connect child to the parent if possible, otherwise bump away. - if (this.workspace.connectionChecker.canConnect( - childConnection, parentConnection, false)) { + if ( + this.workspace.connectionChecker.canConnect( + childConnection, + parentConnection, + false + ) + ) { parentConnection.connect(childConnection!); } else { childConnection?.onFailedConnect(parentConnection); @@ -451,15 +448,17 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns The connection on the value input, or null. */ - private getOnlyValueConnection_(): Connection|null { + private getOnlyValueConnection_(): Connection | null { let connection = null; for (let i = 0; i < this.inputList.length; i++) { const thisConnection = this.inputList[i].connection; - if (thisConnection && - thisConnection.type === ConnectionType.INPUT_VALUE && - thisConnection.targetConnection) { + if ( + thisConnection && + thisConnection.type === ConnectionType.INPUT_VALUE && + thisConnection.targetConnection + ) { if (connection) { - return null; // More than one value input found. + return null; // More than one value input found. } connection = thisConnection; } @@ -487,9 +486,14 @@ export class Block implements IASTNodeLocation, IDeletable { // Disconnect the next statement. const nextTarget = this.nextConnection?.targetConnection ?? null; nextTarget?.disconnect(); - if (previousTarget && - this.workspace.connectionChecker.canConnect( - previousTarget, nextTarget, false)) { + if ( + previousTarget && + this.workspace.connectionChecker.canConnect( + previousTarget, + nextTarget, + false + ) + ) { // Attach the next statement to the previous statement. previousTarget.connect(nextTarget!); } @@ -514,7 +518,7 @@ export class Block implements IASTNodeLocation, IDeletable { if (this.nextConnection) { myConnections.push(this.nextConnection); } - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.connection) { myConnections.push(input.connection); } @@ -532,11 +536,11 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The last next connection on the stack, or null. * @internal */ - lastConnectionInStack(ignoreShadows: boolean): Connection|null { + lastConnectionInStack(ignoreShadows: boolean): Connection | null { let nextConnection = this.nextConnection; while (nextConnection) { const nextBlock = nextConnection.targetBlock(); - if (!nextBlock || ignoreShadows && nextBlock.isShadow()) { + if (!nextBlock || (ignoreShadows && nextBlock.isShadow())) { return nextConnection; } nextConnection = nextBlock.nextConnection; @@ -559,7 +563,7 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns The block (if any) that holds the current block. */ - getParent(): this|null { + getParent(): this | null { return this.parentBlock_; } @@ -569,8 +573,8 @@ export class Block implements IASTNodeLocation, IDeletable { * @param block A block connected to an input on this block. * @returns The input (if any) that connects to the specified block. */ - getInputWithBlock(block: Block): Input|null { - for (let i = 0, input; input = this.inputList[i]; i++) { + getInputWithBlock(block: Block): Input | null { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.connection && input.connection.targetBlock() === block) { return input; } @@ -586,9 +590,9 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns The block (if any) that surrounds the current block. */ - getSurroundParent(): this|null { + getSurroundParent(): this | null { /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - let block: this|null = this; + let block: this | null = this; let prevBlock; do { prevBlock = block; @@ -607,7 +611,7 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns The next statement block or null. */ - getNextBlock(): Block|null { + getNextBlock(): Block | null { return this.nextConnection && this.nextConnection.targetBlock(); } @@ -616,7 +620,7 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns The previous statement block or null. */ - getPreviousBlock(): Block|null { + getPreviousBlock(): Block | null { return this.previousConnection && this.previousConnection.targetBlock(); } @@ -627,10 +631,12 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The first statement connection or null. * @internal */ - getFirstStatementConnection(): Connection|null { - for (let i = 0, input; input = this.inputList[i]; i++) { - if (input.connection && - input.connection.type === ConnectionType.NEXT_STATEMENT) { + getFirstStatementConnection(): Connection | null { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if ( + input.connection && + input.connection.type === ConnectionType.NEXT_STATEMENT + ) { return input.connection; } } @@ -646,7 +652,7 @@ export class Block implements IASTNodeLocation, IDeletable { getRootBlock(): this { let rootBlock: this; /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - let block: this|null = this; + let block: this | null = this; do { rootBlock = block; block = rootBlock.parentBlock_; @@ -670,8 +676,11 @@ export class Block implements IASTNodeLocation, IDeletable { previous = block.getPreviousBlock(); // AnyDuringMigration because: Type 'Block' is not assignable to type // 'this'. - } while (previous && previous.getNextBlock() === block && - (block = previous as AnyDuringMigration)); + } while ( + previous && + previous.getNextBlock() === block && + (block = previous as AnyDuringMigration) + ); return block; } @@ -689,7 +698,7 @@ export class Block implements IASTNodeLocation, IDeletable { return this.childBlocks_; } const blocks = []; - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.connection) { const child = input.connection.targetBlock(); if (child) { @@ -710,7 +719,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @param newParent New parent block. * @internal */ - setParent(newParent: this|null) { + setParent(newParent: this | null) { if (newParent === this.parentBlock_) { return; } @@ -718,8 +727,8 @@ export class Block implements IASTNodeLocation, IDeletable { // Check that block is connected to new parent if new parent is not null and // that block is not connected to superior one if new parent is null. const targetBlock = - this.previousConnection && this.previousConnection.targetBlock() || - this.outputConnection && this.outputConnection.targetBlock(); + (this.previousConnection && this.previousConnection.targetBlock()) || + (this.outputConnection && this.outputConnection.targetBlock()); const isConnected = !!targetBlock; if (isConnected && newParent && targetBlock !== newParent) { @@ -728,8 +737,9 @@ export class Block implements IASTNodeLocation, IDeletable { throw Error('Block not connected to new parent.'); } else if (isConnected && !newParent) { throw Error( - 'Cannot set parent to null while block is still connected to' + - ' superior block.'); + 'Cannot set parent to null while block is still connected to' + + ' superior block.' + ); } // This block hasn't actually moved on-screen, so there's no need to @@ -766,10 +776,10 @@ export class Block implements IASTNodeLocation, IDeletable { getDescendants(ordered: boolean): this[] { const blocks = [this]; const childBlocks = this.getChildren(ordered); - for (let child, i = 0; child = childBlocks[i]; i++) { + for (let child, i = 0; (child = childBlocks[i]); i++) { // AnyDuringMigration because: Argument of type 'Block[]' is not // assignable to parameter of type 'this[]'. - blocks.push(...child.getDescendants(ordered) as AnyDuringMigration); + blocks.push(...(child.getDescendants(ordered) as AnyDuringMigration)); } return blocks; } @@ -780,8 +790,12 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns True if deletable. */ isDeletable(): boolean { - return this.deletable_ && !this.isShadow_ && !this.isDeadOrDying() && - !this.workspace.options.readOnly; + return ( + this.deletable_ && + !this.isShadow_ && + !this.isDeadOrDying() && + !this.workspace.options.readOnly + ); } /** @@ -809,8 +823,12 @@ export class Block implements IASTNodeLocation, IDeletable { * @internal */ isMovable(): boolean { - return this.movable_ && !this.isShadow_ && !this.isDeadOrDying() && - !this.workspace.options.readOnly; + return ( + this.movable_ && + !this.isShadow_ && + !this.isDeadOrDying() && + !this.workspace.options.readOnly + ); } /** @@ -845,7 +863,8 @@ export class Block implements IASTNodeLocation, IDeletable { return true; } return this.workspace.isCapacityAvailable( - common.getBlockTypeCounts(this, true)); + common.getBlockTypeCounts(this, true) + ); } /** @@ -894,8 +913,11 @@ export class Block implements IASTNodeLocation, IDeletable { * @internal */ isEditable(): boolean { - return this.editable_ && !this.isDeadOrDying() && - !this.workspace.options.readOnly; + return ( + this.editable_ && + !this.isDeadOrDying() && + !this.workspace.options.readOnly + ); } /** @@ -914,8 +936,8 @@ export class Block implements IASTNodeLocation, IDeletable { */ setEditable(editable: boolean) { this.editable_ = editable; - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { field.updateEditable(); } } @@ -940,7 +962,10 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The matching connection on this block, or null. * @internal */ - getMatchingConnection(otherBlock: Block, conn: Connection): Connection|null { + getMatchingConnection( + otherBlock: Block, + conn: Connection + ): Connection | null { const connections = this.getConnections_(true); const otherConnections = otherBlock.getConnections_(true); if (connections.length !== otherConnections.length) { @@ -960,7 +985,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @param url URL string for block help, or function that returns a URL. Null * for no help. */ - setHelpUrl(url: string|Function) { + setHelpUrl(url: string | Function) { this.helpUrl = url; } @@ -1007,7 +1032,7 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns Hue value (0-360). */ - getHue(): number|null { + getHue(): number | null { return this.hue_; } @@ -1017,7 +1042,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @param colour HSV hue value (0 to 360), #RRGGBB string, or a message * reference string pointing to one of those two values. */ - setColour(colour: number|string) { + setColour(colour: number | string) { const parsed = parsing.parseBlockColour(colour); this.hue_ = parsed.hue; this.colour_ = parsed.hex; @@ -1059,16 +1084,17 @@ export class Block implements IASTNodeLocation, IDeletable { * @param name The name of the field. * @returns Named field, or null if field does not exist. */ - getField(name: string): Field|null { + getField(name: string): Field | null { if (typeof name !== 'string') { throw TypeError( - 'Block.prototype.getField expects a string ' + + 'Block.prototype.getField expects a string ' + 'with the field name but received ' + (name === undefined ? 'nothing' : name + ' of type ' + typeof name) + - ' instead'); + ' instead' + ); } - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { if (field.name === name) { return field; } @@ -1084,8 +1110,8 @@ export class Block implements IASTNodeLocation, IDeletable { */ getVars(): string[] { const vars: string[] = []; - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { if (field.referencesVariables()) { // NOTE: This only applies to `FieldVariable`, a `Field` vars.push(field.getValue() as string); @@ -1103,11 +1129,12 @@ export class Block implements IASTNodeLocation, IDeletable { */ getVarModels(): VariableModel[] { const vars = []; - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { if (field.referencesVariables()) { - const model = - this.workspace.getVariableById(field.getValue() as string); + const model = this.workspace.getVariableById( + field.getValue() as string + ); // Check if the variable actually exists (and isn't just a potential // variable). if (model) { @@ -1127,10 +1154,12 @@ export class Block implements IASTNodeLocation, IDeletable { * @internal */ updateVarName(variable: VariableModel) { - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { - if (field.referencesVariables() && - variable.getId() === field.getValue()) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { + if ( + field.referencesVariables() && + variable.getId() === field.getValue() + ) { field.refreshVariableName(); } } @@ -1146,8 +1175,8 @@ export class Block implements IASTNodeLocation, IDeletable { * updated name. */ renameVarById(oldId: string, newId: string) { - for (let i = 0, input; input = this.inputList[i]; i++) { - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { if (field.referencesVariables() && oldId === field.getValue()) { field.setValue(newId); } @@ -1190,22 +1219,27 @@ export class Block implements IASTNodeLocation, IDeletable { * @param opt_check Statement type or list of statement types. Null/undefined * if any type could be connected. */ - setPreviousStatement(newBoolean: boolean, opt_check?: string|string[]|null) { + setPreviousStatement( + newBoolean: boolean, + opt_check?: string | string[] | null + ) { if (newBoolean) { if (opt_check === undefined) { opt_check = null; } if (!this.previousConnection) { - this.previousConnection = - this.makeConnection_(ConnectionType.PREVIOUS_STATEMENT); + this.previousConnection = this.makeConnection_( + ConnectionType.PREVIOUS_STATEMENT + ); } this.previousConnection.setCheck(opt_check); } else { if (this.previousConnection) { if (this.previousConnection.isConnected()) { throw Error( - 'Must disconnect previous statement before removing ' + - 'connection.'); + 'Must disconnect previous statement before removing ' + + 'connection.' + ); } this.previousConnection.dispose(); this.previousConnection = null; @@ -1220,22 +1254,23 @@ export class Block implements IASTNodeLocation, IDeletable { * @param opt_check Statement type or list of statement types. Null/undefined * if any type could be connected. */ - setNextStatement(newBoolean: boolean, opt_check?: string|string[]|null) { + setNextStatement(newBoolean: boolean, opt_check?: string | string[] | null) { if (newBoolean) { if (opt_check === undefined) { opt_check = null; } if (!this.nextConnection) { - this.nextConnection = - this.makeConnection_(ConnectionType.NEXT_STATEMENT); + this.nextConnection = this.makeConnection_( + ConnectionType.NEXT_STATEMENT + ); } this.nextConnection.setCheck(opt_check); } else { if (this.nextConnection) { if (this.nextConnection.isConnected()) { throw Error( - 'Must disconnect next statement before removing ' + - 'connection.'); + 'Must disconnect next statement before removing ' + 'connection.' + ); } this.nextConnection.dispose(); this.nextConnection = null; @@ -1250,21 +1285,23 @@ export class Block implements IASTNodeLocation, IDeletable { * @param opt_check Returned type or list of returned types. Null or * undefined if any type could be returned (e.g. variable get). */ - setOutput(newBoolean: boolean, opt_check?: string|string[]|null) { + setOutput(newBoolean: boolean, opt_check?: string | string[] | null) { if (newBoolean) { if (opt_check === undefined) { opt_check = null; } if (!this.outputConnection) { - this.outputConnection = - this.makeConnection_(ConnectionType.OUTPUT_VALUE); + this.outputConnection = this.makeConnection_( + ConnectionType.OUTPUT_VALUE + ); } this.outputConnection.setCheck(opt_check); } else { if (this.outputConnection) { if (this.outputConnection.isConnected()) { throw Error( - 'Must disconnect output value before removing connection.'); + 'Must disconnect output value before removing connection.' + ); } this.outputConnection.dispose(); this.outputConnection = null; @@ -1279,8 +1316,15 @@ export class Block implements IASTNodeLocation, IDeletable { */ setInputsInline(newBoolean: boolean) { if (this.inputsInline !== newBoolean) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'inline', null, this.inputsInline, newBoolean)); + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, + 'inline', + null, + this.inputsInline, + newBoolean + ) + ); this.inputsInline = newBoolean; } } @@ -1297,15 +1341,19 @@ export class Block implements IASTNodeLocation, IDeletable { } // Not defined explicitly. Figure out what would look best. for (let i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type === inputTypes.DUMMY && - this.inputList[i].type === inputTypes.DUMMY) { + if ( + this.inputList[i - 1] instanceof DummyInput && + this.inputList[i] instanceof DummyInput + ) { // Two dummy inputs in a row. Don't inline them. return false; } } for (let i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type === inputTypes.VALUE && - this.inputList[i].type === inputTypes.DUMMY) { + if ( + this.inputList[i - 1] instanceof ValueInput && + this.inputList[i] instanceof DummyInput + ) { // Dummy input after a value input. Inline them. return true; } @@ -1318,7 +1366,7 @@ export class Block implements IASTNodeLocation, IDeletable { * * @param outputShape Value representing an output shape. */ - setOutputShape(outputShape: number|null) { + setOutputShape(outputShape: number | null) { this.outputShape_ = outputShape; } @@ -1327,7 +1375,7 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns Value representing output shape if one exists. */ - getOutputShape(): number|null { + getOutputShape(): number | null { return this.outputShape_; } @@ -1349,8 +1397,15 @@ export class Block implements IASTNodeLocation, IDeletable { if (this.isEnabled() !== enabled) { const oldValue = this.disabled; this.disabled = !enabled; - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'disabled', null, oldValue, !enabled)); + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, + 'disabled', + null, + oldValue, + !enabled + ) + ); } } @@ -1388,8 +1443,15 @@ export class Block implements IASTNodeLocation, IDeletable { */ setCollapsed(collapsed: boolean) { if (this.collapsed_ !== collapsed) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'collapsed', null, this.collapsed_, collapsed)); + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, + 'collapsed', + null, + this.collapsed_, + collapsed + ) + ); this.collapsed_ = collapsed; } } @@ -1418,7 +1480,7 @@ export class Block implements IASTNodeLocation, IDeletable { // Join the text array, removing the spaces around added parentheses. let prev = ''; let text: string = tokens.reduce((acc, curr) => { - const val = acc + ((prev === '(' || curr === ')') ? '' : ' ') + curr; + const val = acc + (prev === '(' || curr === ')' ? '' : ' ') + curr; prev = curr[curr.length - 1]; return val; }, ''); @@ -1455,8 +1517,10 @@ export class Block implements IASTNodeLocation, IDeletable { if (!checks && connection.targetConnection) { checks = connection.targetConnection.getCheck(); } - return !!checks && - (checks.indexOf('Boolean') !== -1 || checks.indexOf('Number') !== -1); + return ( + !!checks && + (checks.indexOf('Boolean') !== -1 || checks.indexOf('Number') !== -1) + ); } for (const input of this.inputList) { @@ -1482,36 +1546,66 @@ export class Block implements IASTNodeLocation, IDeletable { } /** - * Shortcut for appending a value input row. + * Appends a value input row. * * @param name Language-neutral identifier which may used to find this input * again. Should be unique to this block. * @returns The input object created. */ appendValueInput(name: string): Input { - return this.appendInput_(inputTypes.VALUE, name); + return this.appendInput(new ValueInput(name, this)); } /** - * Shortcut for appending a statement input row. + * Appends a statement input row. * * @param name Language-neutral identifier which may used to find this input * again. Should be unique to this block. * @returns The input object created. */ appendStatementInput(name: string): Input { - return this.appendInput_(inputTypes.STATEMENT, name); + this.statementInputCount++; + return this.appendInput(new StatementInput(name, this)); } /** - * Shortcut for appending a dummy input row. + * Appends a dummy input row. * - * @param opt_name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. + * @param name Optional language-neutral identifier which may used to find + * this input again. Should be unique to this block. * @returns The input object created. */ - appendDummyInput(opt_name?: string): Input { - return this.appendInput_(inputTypes.DUMMY, opt_name || ''); + appendDummyInput(name = ''): Input { + return this.appendInput(new DummyInput(name, this)); + } + + /** + * Appends the given input row. + * + * Allows for custom inputs to be appended to the block. + */ + appendInput(input: Input): Input { + this.inputList.push(input); + return input; + } + + /** + * Appends an input with the given input type and name to the block after + * constructing it from the registry. + * + * @param type The name the input is registered under in the registry. + * @param name The name the input will have within the block. + * @returns The constucted input, or null if there was no constructor + * associated with the type. + */ + private appendInputFromRegistry(type: string, name: string): Input | null { + const inputConstructor = registry.getClass( + registry.Type.INPUT, + type, + false + ); + if (!inputConstructor) return null; + return this.appendInput(new inputConstructor(name, this)); } /** @@ -1526,8 +1620,8 @@ export class Block implements IASTNodeLocation, IDeletable { // Validate inputs. if (json['output'] && json['previousStatement']) { throw Error( - warningPrefix + - 'Must not have both an output and a previousStatement.'); + warningPrefix + 'Must not have both an output and a previousStatement.' + ); } // Set basic properties of block. @@ -1551,8 +1645,11 @@ export class Block implements IASTNodeLocation, IDeletable { let i = 0; while (json['message' + i] !== undefined) { this.interpolate_( - json['message' + i], json['args' + i] || [], - json['lastDummyAlign' + i], warningPrefix); + json['message' + i], + json['args' + i] || [], + json['lastDummyAlign' + i], + warningPrefix + ); i++; } @@ -1590,11 +1687,13 @@ export class Block implements IASTNodeLocation, IDeletable { } if (typeof json['extensions'] === 'string') { console.warn( - warningPrefix + - 'JSON attribute \'extensions\' should be an array of' + - ' strings. Found raw string in JSON for \'' + json['type'] + - '\' block.'); - json['extensions'] = [json['extensions']]; // Correct and continue. + warningPrefix + + "JSON attribute 'extensions' should be an array of" + + " strings. Found raw string in JSON for '" + + json['type'] + + "' block." + ); + json['extensions'] = [json['extensions']]; // Correct and continue. } // Add the mutator to the block. @@ -1657,8 +1756,10 @@ export class Block implements IASTNodeLocation, IDeletable { * @param opt_disableCheck Option flag to disable overwrite checks. */ mixin(mixinObj: AnyDuringMigration, opt_disableCheck?: boolean) { - if (opt_disableCheck !== undefined && - typeof opt_disableCheck !== 'boolean') { + if ( + opt_disableCheck !== undefined && + typeof opt_disableCheck !== 'boolean' + ) { throw Error('opt_disableCheck must be a boolean if provided'); } if (!opt_disableCheck) { @@ -1670,8 +1771,8 @@ export class Block implements IASTNodeLocation, IDeletable { } if (overwrites.length) { throw Error( - 'Mixin will overwrite block members: ' + - JSON.stringify(overwrites)); + 'Mixin will overwrite block members: ' + JSON.stringify(overwrites) + ); } } Object.assign(this, mixinObj); @@ -1688,20 +1789,23 @@ export class Block implements IASTNodeLocation, IDeletable { * @param warningPrefix Warning prefix string identifying block. */ private interpolate_( - message: string, args: AnyDuringMigration[], - lastDummyAlign: string|undefined, warningPrefix: string) { + message: string, + args: AnyDuringMigration[], + lastDummyAlign: string | undefined, + warningPrefix: string + ) { const tokens = parsing.tokenizeInterpolation(message); this.validateTokens_(tokens, args.length); const elements = this.interpolateArguments_(tokens, args, lastDummyAlign); // An array of [field, fieldName] tuples. const fieldStack = []; - for (let i = 0, element; element = elements[i]; i++) { + for (let i = 0, element; (element = elements[i]); i++) { if (this.isInputKeyword_(element['type'])) { const input = this.inputFromJson_(element, warningPrefix); // Should never be null, but just in case. if (input) { - for (let j = 0, tuple; tuple = fieldStack[j]; j++) { + for (let j = 0, tuple; (tuple = fieldStack[j]); j++) { input.appendField(tuple[0], tuple[1]); } fieldStack.length = 0; @@ -1725,7 +1829,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @param tokens An array of tokens to validate * @param argsCount The number of args that need to be referred to. */ - private validateTokens_(tokens: Array, argsCount: number) { + private validateTokens_(tokens: Array, argsCount: number) { const visitedArgsHash = []; let visitedArgsCount = 0; for (let i = 0; i < tokens.length; i++) { @@ -1735,21 +1839,36 @@ export class Block implements IASTNodeLocation, IDeletable { } if (token < 1 || token > argsCount) { throw Error( - 'Block "' + this.type + '": ' + - 'Message index %' + token + ' out of range.'); + 'Block "' + + this.type + + '": ' + + 'Message index %' + + token + + ' out of range.' + ); } if (visitedArgsHash[token]) { throw Error( - 'Block "' + this.type + '": ' + - 'Message index %' + token + ' duplicated.'); + 'Block "' + + this.type + + '": ' + + 'Message index %' + + token + + ' duplicated.' + ); } visitedArgsHash[token] = true; visitedArgsCount++; } if (visitedArgsCount !== argsCount) { throw Error( - 'Block "' + this.type + '": ' + - 'Message does not reference all ' + argsCount + ' arg(s).'); + 'Block "' + + this.type + + '": ' + + 'Message does not reference all ' + + argsCount + + ' arg(s).' + ); } } @@ -1765,8 +1884,10 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The JSON definitions of field and inputs to add to the block. */ private interpolateArguments_( - tokens: Array, args: Array, - lastDummyAlign: string|undefined): AnyDuringMigration[] { + tokens: Array, + args: Array, + lastDummyAlign: string | undefined + ): AnyDuringMigration[] { const elements = []; for (let i = 0; i < tokens.length; i++) { let element = tokens[i]; @@ -1786,9 +1907,12 @@ export class Block implements IASTNodeLocation, IDeletable { } const length = elements.length; - if (length && - !this.isInputKeyword_( - (elements as AnyDuringMigration)[length - 1]['type'])) { + if ( + length && + !this.isInputKeyword_( + (elements as AnyDuringMigration)[length - 1]['type'] + ) + ) { const dummyInput = {'type': 'input_dummy'}; if (lastDummyAlign) { (dummyInput as AnyDuringMigration)['align'] = lastDummyAlign; @@ -1807,8 +1931,11 @@ export class Block implements IASTNodeLocation, IDeletable { * @param element The element to try to turn into a field. * @returns The field defined by the JSON, or null if one couldn't be created. */ - private fieldFromJson_(element: {alt?: string, type: string, text?: string}): - Field|null { + private fieldFromJson_(element: { + alt?: string; + type: string; + text?: string; + }): Field | null { const field = fieldRegistry.fromJson(element); if (!field && element['alt']) { if (typeof element['alt'] === 'string') { @@ -1830,8 +1957,10 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The input that has been created, or null if one could not be * created for some reason (should never happen). */ - private inputFromJson_(element: AnyDuringMigration, warningPrefix: string): - Input|null { + private inputFromJson_( + element: AnyDuringMigration, + warningPrefix: string + ): Input | null { const alignmentLookup = { 'LEFT': Align.LEFT, 'RIGHT': Align.RIGHT, @@ -1850,6 +1979,10 @@ export class Block implements IASTNodeLocation, IDeletable { case 'input_dummy': input = this.appendDummyInput(element['name']); break; + default: { + input = this.appendInputFromRegistry(element['type'], element['name']); + break; + } } // Should never be hit because of interpolate_'s checks, but just in case. if (!input) { @@ -1860,9 +1993,9 @@ export class Block implements IASTNodeLocation, IDeletable { input.setCheck(element['check']); } if (element['align']) { - const alignment = - (alignmentLookup as - AnyDuringMigration)[element['align'].toUpperCase()]; + const alignment = (alignmentLookup as AnyDuringMigration)[ + element['align'].toUpperCase() + ]; if (alignment === undefined) { console.warn(warningPrefix + 'Illegal align value: ', element['align']); } else { @@ -1880,8 +2013,12 @@ export class Block implements IASTNodeLocation, IDeletable { * otherwise. */ private isInputKeyword_(str: string): boolean { - return str === 'input_value' || str === 'input_statement' || - str === 'input_dummy'; + return ( + str === 'input_value' || + str === 'input_statement' || + str === 'input_dummy' || + registry.hasItem(registry.Type.INPUT, str) + ); } /** @@ -1891,7 +2028,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @param str String to turn into the JSON definition of a label field. * @returns The JSON definition or null. */ - private stringToFieldJson_(str: string): {text: string, type: string}|null { + private stringToFieldJson_(str: string): {text: string; type: string} | null { str = str.trim(); if (str) { return { @@ -1902,28 +2039,6 @@ export class Block implements IASTNodeLocation, IDeletable { return null; } - /** - * Add a value input, statement input or local variable to this block. - * - * @param type One of Blockly.inputTypes. - * @param name Language-neutral identifier which may used to find this input - * again. Should be unique to this block. - * @returns The input object created. - */ - protected appendInput_(type: number, name: string): Input { - let connection = null; - if (type === inputTypes.VALUE || type === inputTypes.STATEMENT) { - connection = this.makeConnection_(type); - } - if (type === inputTypes.STATEMENT) { - this.statementInputCount++; - } - const input = new Input(type, name, this, connection); - // Append input to list. - this.inputList.push(input); - return input; - } - /** * Move a named input to a different location on this block. * @@ -1931,14 +2046,14 @@ export class Block implements IASTNodeLocation, IDeletable { * @param refName Name of input that should be after the moved input, or null * to be the input at the end. */ - moveInputBefore(name: string, refName: string|null) { + moveInputBefore(name: string, refName: string | null) { if (name === refName) { return; } // Find both inputs. let inputIndex = -1; let refIndex = refName ? -1 : this.inputList.length; - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.name === name) { inputIndex = i; if (refIndex !== -1) { @@ -1969,7 +2084,7 @@ export class Block implements IASTNodeLocation, IDeletable { moveNumberedInputBefore(inputIndex: number, refIndex: number) { // Validate arguments. if (inputIndex === refIndex) { - throw Error('Can\'t move input to itself.'); + throw Error("Can't move input to itself."); } if (inputIndex >= this.inputList.length) { throw RangeError('Input index ' + inputIndex + ' out of bounds.'); @@ -1997,11 +2112,9 @@ export class Block implements IASTNodeLocation, IDeletable { * @throws {Error} if the input is not present and opt_quiet is not true. */ removeInput(name: string, opt_quiet?: boolean): boolean { - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.name === name) { - if (input.type === inputTypes.STATEMENT) { - this.statementInputCount--; - } + if (input instanceof StatementInput) this.statementInputCount--; input.dispose(); this.inputList.splice(i, 1); return true; @@ -2019,8 +2132,8 @@ export class Block implements IASTNodeLocation, IDeletable { * @param name The name of the input. * @returns The input object, or null if input does not exist. */ - getInput(name: string): Input|null { - for (let i = 0, input; input = this.inputList[i]; i++) { + getInput(name: string): Input | null { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.name === name) { return input; } @@ -2036,7 +2149,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The attached value block, or null if the input is either * disconnected or if the input does not exist. */ - getInputTargetBlock(name: string): Block|null { + getInputTargetBlock(name: string): Block | null { const input = this.getInput(name); return input && input.connection && input.connection.targetBlock(); } @@ -2046,8 +2159,9 @@ export class Block implements IASTNodeLocation, IDeletable { * * @returns Block's comment. */ - getCommentText(): string|null { - return this.commentModel.text; + getCommentText(): string | null { + const comment = this.getIcon(CommentIcon.TYPE) as CommentIcon | null; + return comment?.getText() ?? null; } /** @@ -2055,14 +2169,29 @@ export class Block implements IASTNodeLocation, IDeletable { * * @param text The text, or null to delete. */ - setCommentText(text: string|null) { - if (this.commentModel.text === text) { - return; + setCommentText(text: string | null) { + const comment = this.getIcon(CommentIcon.TYPE) as CommentIcon | null; + const oldText = comment?.getText() ?? null; + if (oldText === text) return; + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this, + 'comment', + null, + oldText, + text + ) + ); + + if (text !== null) { + let comment = this.getIcon(CommentIcon.TYPE) as CommentIcon | undefined; + if (!comment) { + comment = this.addIcon(new CommentIcon(this)); + } + comment.setText(text); + } else { + this.removeIcon(CommentIcon.TYPE); } - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this, 'comment', null, this.commentModel.text, text)); - this.commentModel.text = text; - this.comment = text; // For backwards compatibility. } /** @@ -2072,16 +2201,67 @@ export class Block implements IASTNodeLocation, IDeletable { * @param _opt_id An optional ID for the warning text to be able to maintain * multiple warnings. */ - setWarningText(_text: string|null, _opt_id?: string) {} - // NOP. + setWarningText(_text: string | null, _opt_id?: string) { + // NOOP. + } /** * Give this block a mutator dialog. * * @param _mutator A mutator dialog instance or null to remove. */ - setMutator(_mutator: Mutator) {} - // NOP. + setMutator(_mutator: MutatorIcon) { + // NOOP. + } + + /** Adds the given icon to the block. */ + addIcon(icon: T): T { + if (this.hasIcon(icon.getType())) throw new DuplicateIconType(icon); + this.icons.push(icon); + this.icons.sort((a, b) => a.getWeight() - b.getWeight()); + return icon; + } + + /** + * Removes the icon whose getType matches the given type iconType from the + * block. + * + * @param type The type of the icon to remove from the block. + * @returns True if an icon with the given type was found, false otherwise. + */ + removeIcon(type: IconType): boolean { + if (!this.hasIcon(type)) return false; + this.getIcon(type)?.dispose(); + this.icons = this.icons.filter((icon) => !icon.getType().equals(type)); + return true; + } + + /** + * @returns True if an icon with the given type exists on the block, + * false otherwise. + */ + hasIcon(type: IconType): boolean { + return this.icons.some((icon) => icon.getType().equals(type)); + } + + /** + * @param type The type of the icon to retrieve. Prefer passing an `IconType` + * for proper type checking when using typescript. + * @returns The icon with the given type if it exists on the block, undefined + * otherwise. + */ + getIcon(type: IconType | string): T | undefined { + if (type instanceof IconType) { + return this.icons.find((icon) => icon.getType().equals(type)) as T; + } else { + return this.icons.find((icon) => icon.getType().toString() === type) as T; + } + } + + /** @returns An array of the icons attached to this block. */ + getIcons(): IIcon[] { + return [...this.icons]; + } /** * Return the coordinates of the top-left corner of this block relative to the @@ -2098,13 +2278,16 @@ export class Block implements IASTNodeLocation, IDeletable { * * @param dx Horizontal offset, in workspace units. * @param dy Vertical offset, in workspace units. + * @param reason Why is this move happening? 'drag', 'bump', 'snap', ... */ - moveBy(dx: number, dy: number) { + moveBy(dx: number, dy: number, reason?: string[]) { if (this.parentBlock_) { - throw Error('Block has parent.'); + throw Error('Block has parent'); } - const event = - new (eventUtils.get(eventUtils.BLOCK_MOVE))(this) as BlockMove; + const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( + this + ) as BlockMove; + reason && event.setReason(reason); this.xy_.translate(dx, dy); event.recordNew(); eventUtils.fire(event); @@ -2115,8 +2298,9 @@ export class Block implements IASTNodeLocation, IDeletable { * * @param type The type of the connection to create. * @returns A new connection of the specified type. + * @internal */ - protected makeConnection_(type: number): Connection { + makeConnection_(type: ConnectionType): Connection { return new Connection(this, type); } @@ -2138,7 +2322,7 @@ export class Block implements IASTNodeLocation, IDeletable { } // Recursively check each input block of the current block. - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (!input.connection) { continue; } @@ -2178,7 +2362,7 @@ export class Block implements IASTNodeLocation, IDeletable { export namespace Block { export interface CommentModel { - text: string|null; + text: string | null; pinned: boolean; size: Size; } diff --git a/core/block_animations.ts b/core/block_animations.ts index 8f72bfa65..9c9a52aa7 100644 --- a/core/block_animations.ts +++ b/core/block_animations.ts @@ -11,7 +11,6 @@ import type {BlockSvg} from './block_svg.js'; import * as dom from './utils/dom.js'; import {Svg} from './utils/svg.js'; - /** A bounding box for a cloned block. */ interface CloneRect { x: number; @@ -21,11 +20,10 @@ interface CloneRect { } /** PID of disconnect UI animation. There can only be one at a time. */ -let disconnectPid: ReturnType|null = null; +let disconnectPid: ReturnType | null = null; /** The wobbling block. There can only be one at a time. */ -let wobblingBlock: BlockSvg|null = null; - +let wobblingBlock: BlockSvg | null = null; /** * Play some UI effects (sound, animation) when disposing of a block. @@ -46,8 +44,12 @@ export function disposeUiEffect(block: BlockSvg) { const clone: SVGGElement = svgGroup.cloneNode(true) as SVGGElement; clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')'); workspace.getParentSvg().appendChild(clone); - const cloneRect = - {'x': xy.x, 'y': xy.y, 'width': block.width, 'height': block.height}; + const cloneRect = { + 'x': xy.x, + 'y': xy.y, + 'width': block.width, + 'height': block.height, + }; disposeUiStep(clone, cloneRect, workspace.RTL, new Date(), workspace.scale); } /** @@ -62,21 +64,25 @@ export function disposeUiEffect(block: BlockSvg) { * @param workspaceScale Scale of workspace. */ function disposeUiStep( - clone: Element, rect: CloneRect, rtl: boolean, start: Date, - workspaceScale: number) { + clone: Element, + rect: CloneRect, + rtl: boolean, + start: Date, + workspaceScale: number +) { const ms = new Date().getTime() - start.getTime(); const percent = ms / 150; if (percent > 1) { dom.removeNode(clone); } else { const x = - rect.x + (rtl ? -1 : 1) * rect.width * workspaceScale / 2 * percent; + rect.x + (((rtl ? -1 : 1) * rect.width * workspaceScale) / 2) * percent; const y = rect.y + rect.height * workspaceScale * percent; const scale = (1 - percent) * workspaceScale; clone.setAttribute( - 'transform', - 'translate(' + x + ',' + y + ')' + - ' scale(' + scale + ')'); + 'transform', + 'translate(' + x + ',' + y + ')' + ' scale(' + scale + ')' + ); setTimeout(disposeUiStep, 10, clone, rect, rtl, start, workspaceScale); } } @@ -92,7 +98,7 @@ export function connectionUiEffect(block: BlockSvg) { const scale = workspace.scale; workspace.getAudioManager().play('click'); if (scale < 1) { - return; // Too small to care about visual effects. + return; // Too small to care about visual effects. } // Determine the absolute coordinates of the inferior block. const xy = workspace.getSvgXY(block.getSvgRoot()); @@ -105,15 +111,17 @@ export function connectionUiEffect(block: BlockSvg) { xy.y += 3 * scale; } const ripple = dom.createSvgElement( - Svg.CIRCLE, { - 'cx': xy.x, - 'cy': xy.y, - 'r': 0, - 'fill': 'none', - 'stroke': '#888', - 'stroke-width': 10, - }, - workspace.getParentSvg()); + Svg.CIRCLE, + { + 'cx': xy.x, + 'cy': xy.y, + 'r': 0, + 'fill': 'none', + 'stroke': '#888', + 'stroke-width': 10, + }, + workspace.getParentSvg() + ); // Start the animation. connectionUiStep(ripple, new Date(), scale); } @@ -147,13 +155,13 @@ export function disconnectUiEffect(block: BlockSvg) { disconnectUiStop(); block.workspace.getAudioManager().play('disconnect'); if (block.workspace.scale < 1) { - return; // Too small to care about visual effects. + return; // Too small to care about visual effects. } // Horizontal distance for bottom of block to wiggle. const DISPLACEMENT = 10; // Scale magnitude of skew to height of block. const height = block.getHeightWidth().height; - let magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180; + let magnitude = (Math.atan(DISPLACEMENT / height) / Math.PI) * 180; if (!block.RTL) { magnitude *= -1; } @@ -170,8 +178,8 @@ export function disconnectUiEffect(block: BlockSvg) { * @param start Date of animation's start. */ function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) { - const DURATION = 200; // Milliseconds. - const WIGGLES = 3; // Half oscillations. + const DURATION = 200; // Milliseconds. + const WIGGLES = 3; // Half oscillations. const ms = new Date().getTime() - start.getTime(); const percent = ms / DURATION; @@ -179,13 +187,15 @@ function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) { let skew = ''; if (percent <= 1) { const val = Math.round( - Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude); + Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude + ); skew = `skewX(${val})`; disconnectPid = setTimeout(disconnectUiStep, 10, block, magnitude, start); } - block.getSvgRoot().setAttribute( - 'transform', `${block.getTranslation()} ${skew}`); + block + .getSvgRoot() + .setAttribute('transform', `${block.getTranslation()} ${skew}`); } /** @@ -199,7 +209,8 @@ export function disconnectUiStop() { clearTimeout(disconnectPid); disconnectPid = null; } - wobblingBlock.getSvgRoot().setAttribute( - 'transform', wobblingBlock.getTranslation()); + wobblingBlock + .getSvgRoot() + .setAttribute('transform', wobblingBlock.getTranslation()); wobblingBlock = null; } diff --git a/core/block_drag_surface.ts b/core/block_drag_surface.ts index 909aa76bf..c249458cb 100644 --- a/core/block_drag_surface.ts +++ b/core/block_drag_surface.ts @@ -23,7 +23,6 @@ import * as dom from './utils/dom.js'; import {Svg} from './utils/svg.js'; import * as svgMath from './utils/svg_math.js'; - /** * Class for a drag surface for the currently dragged block. This is a separate * SVG that contains only the currently moving block, or nothing. @@ -65,14 +64,16 @@ export class BlockDragSurfaceSvg { /** @param container Containing element. */ constructor(private readonly container: Element) { this.svg = dom.createSvgElement( - Svg.SVG, { - 'xmlns': dom.SVG_NS, - 'xmlns:html': dom.HTML_NS, - 'xmlns:xlink': dom.XLINK_NS, - 'version': '1.1', - 'class': 'blocklyBlockDragSurface', - }, - this.container); + Svg.SVG, + { + 'xmlns': dom.SVG_NS, + 'xmlns:html': dom.HTML_NS, + 'xmlns:xlink': dom.XLINK_NS, + 'version': '1.1', + 'class': 'blocklyBlockDragSurface', + }, + this.container + ); this.dragGroup = dom.createSvgElement(Svg.G, {}, this.svg); } @@ -120,8 +121,9 @@ export class BlockDragSurfaceSvg { this.childSurfaceXY.x = roundX; this.childSurfaceXY.y = roundY; this.dragGroup.setAttribute( - 'transform', - 'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')'); + 'transform', + 'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')' + ); } /** @@ -200,7 +202,7 @@ export class BlockDragSurfaceSvg { * * @returns Drag surface block DOM element, or null if no blocks exist. */ - getCurrentBlock(): Element|null { + getCurrentBlock(): Element | null { return this.dragGroup.firstChild as Element; } diff --git a/core/block_dragger.ts b/core/block_dragger.ts index 0a2cb0f33..1e6d34a7d 100644 --- a/core/block_dragger.ts +++ b/core/block_dragger.ts @@ -21,7 +21,7 @@ import * as bumpObjects from './bump_objects.js'; import * as common from './common.js'; import type {BlockMove} from './events/events_block_move.js'; import * as eventUtils from './events/utils.js'; -import type {Icon} from './icon.js'; +import type {Icon} from './icons/icon.js'; import {InsertionMarkerManager} from './insertion_marker_manager.js'; import type {IBlockDragger} from './interfaces/i_block_dragger.js'; import type {IDragTarget} from './interfaces/i_drag_target.js'; @@ -29,7 +29,7 @@ import * as registry from './registry.js'; import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - +import {hasBubble} from './interfaces/i_has_bubble.js'; /** * Class for a block dragger. It moves blocks around the workspace when they @@ -44,7 +44,7 @@ export class BlockDragger implements IBlockDragger { protected workspace_: WorkspaceSvg; /** Which drag area the mouse pointer is over, if any. */ - private dragTarget_: IDragTarget|null = null; + private dragTarget_: IDragTarget | null = null; /** Whether the block would be deleted if dropped immediately. */ protected wouldDeleteBlock_ = false; @@ -59,8 +59,9 @@ export class BlockDragger implements IBlockDragger { this.draggingBlock_ = block; /** Object that keeps track of connections on dragged blocks. */ - this.draggedConnectionManager_ = - new InsertionMarkerManager(this.draggingBlock_); + this.draggedConnectionManager_ = new InsertionMarkerManager( + this.draggingBlock_ + ); this.workspace_ = workspace; @@ -75,7 +76,7 @@ export class BlockDragger implements IBlockDragger { * on this block and its descendants. Moving an icon moves the bubble that * extends from it if that bubble is open. */ - this.dragIconData_ = initIconData(block); + this.dragIconData_ = initIconData(block, this.startXY_); } /** @@ -92,7 +93,7 @@ export class BlockDragger implements IBlockDragger { } /** - * Start dragging a block. This includes moving it to the drag surface. + * Start dragging a block. * * @param currentDragDeltaXY How far the pointer has moved from the position * at mouse down, in pixel units. @@ -122,10 +123,6 @@ export class BlockDragger implements IBlockDragger { this.disconnectBlock_(healStack, currentDragDeltaXY); } this.draggingBlock_.setDragging(true); - // For future consideration: we may be able to put moveToDragSurface inside - // the block dragger, which would also let the block not track the block - // drag surface. - this.draggingBlock_.moveToDragSurface(); } /** @@ -136,9 +133,11 @@ export class BlockDragger implements IBlockDragger { */ protected shouldDisconnect_(healStack: boolean): boolean { return !!( - this.draggingBlock_.getParent() || - healStack && this.draggingBlock_.nextConnection && - this.draggingBlock_.nextConnection.targetBlock()); + this.draggingBlock_.getParent() || + (healStack && + this.draggingBlock_.nextConnection && + this.draggingBlock_.nextConnection.targetBlock()) + ); } /** @@ -149,7 +148,9 @@ export class BlockDragger implements IBlockDragger { * at mouse down, in pixel units. */ protected disconnectBlock_( - healStack: boolean, currentDragDeltaXY: Coordinate) { + healStack: boolean, + currentDragDeltaXY: Coordinate + ) { this.draggingBlock_.unplug(healStack); const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); const newLoc = Coordinate.sum(this.startXY_, delta); @@ -162,7 +163,10 @@ export class BlockDragger implements IBlockDragger { /** Fire a UI event at the start of a block drag. */ protected fireDragStartEvent_() { const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( - this.draggingBlock_, true, this.draggingBlock_.getDescendants(false)); + this.draggingBlock_, + true, + this.draggingBlock_.getDescendants(false) + ); eventUtils.fire(event); } @@ -217,18 +221,14 @@ export class BlockDragger implements IBlockDragger { blockAnimation.disconnectUiStop(); - const preventMove = !!this.dragTarget_ && - this.dragTarget_.shouldPreventMove(this.draggingBlock_); - let newLoc: Coordinate; - let delta: Coordinate|null = null; - if (preventMove) { - newLoc = this.startXY_; - } else { + const preventMove = + !!this.dragTarget_ && + this.dragTarget_.shouldPreventMove(this.draggingBlock_); + let delta: Coordinate | null = null; + if (!preventMove) { const newValues = this.getNewLocationAfterDrag_(currentDragDeltaXY); delta = newValues.delta; - newLoc = newValues.newLocation; } - this.draggingBlock_.moveOffDragSurface(newLoc); if (this.dragTarget_) { this.dragTarget_.onDrop(this.draggingBlock_); @@ -238,15 +238,17 @@ export class BlockDragger implements IBlockDragger { if (!deleted) { // These are expensive and don't need to be done if we're deleting. this.draggingBlock_.setDragging(false); - if (delta) { // !preventMove + if (delta) { + // !preventMove this.updateBlockAfterMove_(); } else { // Blocks dragged directly from a flyout may need to be bumped into // bounds. bumpObjects.bumpIntoBounds( - this.draggingBlock_.workspace, - this.workspace_.getMetricsManager().getScrollMetrics(true), - this.draggingBlock_); + this.draggingBlock_.workspace, + this.workspace_.getMetricsManager().getScrollMetrics(true), + this.draggingBlock_ + ); } } this.workspace_.setResizesEnabled(true); @@ -262,8 +264,10 @@ export class BlockDragger implements IBlockDragger { * @returns New location after drag. delta is in workspace units. newLocation * is the new coordinate where the block should end up. */ - protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate): - {delta: Coordinate, newLocation: Coordinate} { + protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate): { + delta: Coordinate; + newLocation: Coordinate; + } { const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); const newLocation = Coordinate.sum(this.startXY_, delta); return { @@ -307,7 +311,10 @@ export class BlockDragger implements IBlockDragger { /** Fire a UI event at the end of a block drag. */ protected fireDragEndEvent_() { const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( - this.draggingBlock_, false, this.draggingBlock_.getDescendants(false)); + this.draggingBlock_, + false, + this.draggingBlock_.getDescendants(false) + ); eventUtils.fire(event); } @@ -322,21 +329,25 @@ export class BlockDragger implements IBlockDragger { const toolbox = this.workspace_.getToolbox(); if (toolbox) { - const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : - 'blocklyToolboxGrab'; + const style = this.draggingBlock_.isDeletable() + ? 'blocklyToolboxDelete' + : 'blocklyToolboxGrab'; // AnyDuringMigration because: Property 'removeStyle' does not exist on // type 'IToolbox'. - if (isEnd && - typeof (toolbox as AnyDuringMigration).removeStyle === 'function') { + if ( + isEnd && + typeof (toolbox as AnyDuringMigration).removeStyle === 'function' + ) { // AnyDuringMigration because: Property 'removeStyle' does not exist on // type 'IToolbox'. (toolbox as AnyDuringMigration).removeStyle(style); // AnyDuringMigration because: Property 'addStyle' does not exist on // type 'IToolbox'. } else if ( - !isEnd && - typeof (toolbox as AnyDuringMigration).addStyle === 'function') { + !isEnd && + typeof (toolbox as AnyDuringMigration).addStyle === 'function' + ) { // AnyDuringMigration because: Property 'addStyle' does not exist on // type 'IToolbox'. (toolbox as AnyDuringMigration).addStyle(style); @@ -348,7 +359,9 @@ export class BlockDragger implements IBlockDragger { protected fireMoveEvent_() { if (this.draggingBlock_.isDeadOrDying()) return; const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( - this.draggingBlock_) as BlockMove; + this.draggingBlock_ + ) as BlockMove; + event.setReason(['drag']); event.oldCoordinate = this.startXY_; event.recordNew(); eventUtils.fire(event); @@ -373,8 +386,9 @@ export class BlockDragger implements IBlockDragger { */ protected pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate { const result = new Coordinate( - pixelCoord.x / this.workspace_.scale, - pixelCoord.y / this.workspace_.scale); + pixelCoord.x / this.workspace_.scale, + pixelCoord.y / this.workspace_.scale + ); if (this.workspace_.isMutator) { // If we're in a mutator, its scale is always 1, purely because of some // oddities in our rendering optimizations. The actual scale is the same @@ -393,9 +407,8 @@ export class BlockDragger implements IBlockDragger { */ protected dragIcons_(dxy: Coordinate) { // Moving icons moves their associated bubbles. - for (let i = 0; i < this.dragIconData_.length; i++) { - const data = this.dragIconData_[i]; - data.icon.setIconLocation(Coordinate.sum(data.location, dxy)); + for (const data of this.dragIconData_) { + data.icon.onLocationChange(Coordinate.sum(data.location, dxy)); } } @@ -407,8 +420,10 @@ export class BlockDragger implements IBlockDragger { */ getInsertionMarkers(): BlockSvg[] { // No insertion markers with the old style of dragged connection managers. - if (this.draggedConnectionManager_ && - this.draggedConnectionManager_.getInsertionMarkers) { + if ( + this.draggedConnectionManager_ && + this.draggedConnectionManager_.getInsertionMarkers + ) { return this.draggedConnectionManager_.getInsertionMarkers(); } return []; @@ -427,24 +442,28 @@ export interface IconPositionData { * extends from it if that bubble is open. * * @param block The root block that is being dragged. + * @param blockOrigin The top left of the given block in workspace coordinates. * @returns The list of all icons and their locations. */ -function initIconData(block: BlockSvg): IconPositionData[] { +function initIconData( + block: BlockSvg, + blockOrigin: Coordinate +): IconPositionData[] { // Build a list of icons that need to be moved and where they started. const dragIconData = []; - const descendants = (block.getDescendants(false)); - for (let i = 0, descendant; descendant = descendants[i]; i++) { - const icons = descendant.getIcons(); - for (let j = 0; j < icons.length; j++) { - const data = { - // Coordinate with x and y properties (workspace - // coordinates). - location: icons[j].getIconLocation(), // Blockly.Icon - icon: icons[j], - }; - dragIconData.push(data); - } + for (const icon of block.getIcons()) { + // Only bother to track icons whose bubble is visible. + if (hasBubble(icon) && !icon.bubbleIsVisible()) continue; + + dragIconData.push({location: blockOrigin, icon: icon}); + icon.onLocationChange(blockOrigin); + } + + for (const child of block.getChildren(false)) { + dragIconData.push( + ...initIconData(child, Coordinate.sum(blockOrigin, child.relativeCoords)) + ); } // AnyDuringMigration because: Type '{ location: Coordinate | null; icon: // Icon; }[]' is not assignable to type 'IconPositionData[]'. diff --git a/core/block_svg.ts b/core/block_svg.ts index b463c64c6..ed942b1aa 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -18,32 +18,35 @@ import './events/events_selected.js'; import {Block} from './block.js'; import * as blockAnimations from './block_animations.js'; import * as browserEvents from './browser_events.js'; -import {Comment} from './comment.js'; +import {CommentIcon} from './icons/comment_icon.js'; import * as common from './common.js'; import {config} from './config.js'; import type {Connection} from './connection.js'; import {ConnectionType} from './connection_type.js'; import * as constants from './constants.js'; import * as ContextMenu from './contextmenu.js'; -import {ContextMenuOption, ContextMenuRegistry, LegacyContextMenuOption} from './contextmenu_registry.js'; +import { + ContextMenuOption, + ContextMenuRegistry, + LegacyContextMenuOption, +} from './contextmenu_registry.js'; import type {BlockMove} from './events/events_block_move.js'; import * as eventUtils from './events/utils.js'; import type {Field} from './field.js'; import {FieldLabel} from './field_label.js'; -import type {Icon} from './icon.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import type {CopyData, ICopyable} from './interfaces/i_copyable.js'; import type {IDraggable} from './interfaces/i_draggable.js'; +import {IIcon} from './interfaces/i_icon.js'; import * as internalConstants from './internal_constants.js'; import {ASTNode} from './keyboard_nav/ast_node.js'; import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js'; import {MarkerManager} from './marker_manager.js'; import {Msg} from './msg.js'; -import type {Mutator} from './mutator.js'; +import {MutatorIcon} from './icons/mutator_icon.js'; import {RenderedConnection} from './rendered_connection.js'; -import type {Debug as BlockRenderingDebug} from './renderers/common/debugger.js'; import type {IPathObject} from './renderers/common/i_path_object.js'; import * as blocks from './serialization/blocks.js'; import type {BlockStyle} from './theme.js'; @@ -53,19 +56,21 @@ import * as dom from './utils/dom.js'; import {Rect} from './utils/rect.js'; import {Svg} from './utils/svg.js'; import * as svgMath from './utils/svg_math.js'; -import {Warning} from './warning.js'; +import {WarningIcon} from './icons/warning_icon.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; import {queueRender} from './render_management.js'; - +import * as deprecation from './utils/deprecation.js'; +import {IconType} from './icons/icon_types.js'; /** * Class for a block's SVG representation. * Not normally called directly, workspace.newBlock() is preferred. */ -export class BlockSvg extends Block implements IASTNodeLocationSvg, - IBoundedElement, ICopyable, - IDraggable { +export class BlockSvg + extends Block + implements IASTNodeLocationSvg, IBoundedElement, ICopyable, IDraggable +{ /** * Constant for identifying rows that are to be rendered inline. * Don't collide with Blockly.inputTypes. @@ -81,15 +86,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, override decompose?: (p1: Workspace) => BlockSvg; // override compose?: ((p1: BlockSvg) => void)|null; saveConnections?: (p1: BlockSvg) => void; - customContextMenu?: - (p1: Array) => void; - - /** - * An property used internally to reference the block's rendering debugger. - * - * @internal - */ - renderingDebugger: BlockRenderingDebug|null = null; + customContextMenu?: ( + p1: Array + ) => void; /** * Height of this block, not including any statement blocks above or below. @@ -110,13 +109,14 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, private warningTextDb = new Map>(); /** Block's mutator icon (if any). */ - mutator: Mutator|null = null; + mutator: MutatorIcon | null = null; - /** Block's comment icon (if any). */ - private commentIcon_: Comment|null = null; - - /** Block's warning icon (if any). */ - warning: Warning|null = null; + /** + * Block's warning icon (if any). + * + * @deprecated Use `setWarningText` to modify warnings on this block. + */ + warning: WarningIcon | null = null; private svgGroup_: SVGGElement; style: BlockStyle; @@ -141,7 +141,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, override nextConnection!: RenderedConnection; // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. override previousConnection!: RenderedConnection; - private readonly useDragSurface_: boolean; private translation = ''; @@ -160,7 +159,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ relativeCoords = new Coordinate(0, 0); - /** * @param workspace The block's workspace. * @param prototypeName Name of the language object containing type-specific @@ -177,14 +175,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, this.style = workspace.getRenderer().getConstants().getBlockStyle(null); /** The renderer's path object. */ - this.pathObject = - workspace.getRenderer().makePathObject(this.svgGroup_, this.style); - - /** - * Whether to move the block to the drag surface when it is dragged. - * True if it should move, false if it should be translated directly. - */ - this.useDragSurface_ = !!workspace.getBlockDragSurface(); + this.pathObject = workspace + .getRenderer() + .makePathObject(this.svgGroup_, this.style); const svgPath = this.pathObject.svgPath; (svgPath as any).tooltip = this; @@ -204,19 +197,23 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, if (!this.workspace.rendered) { throw TypeError('Workspace is headless.'); } - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { input.init(); } - const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].createIcon(); + for (const icon of this.getIcons()) { + icon.initView(this.createIconPointerDownListener(icon)); + icon.updateEditable(); } this.applyColour(); this.pathObject.updateMovable(this.isMovable()); const svg = this.getSvgRoot(); if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) { browserEvents.conditionalBind( - svg, 'pointerdown', this, this.onMouseDown_); + svg, + 'pointerdown', + this, + this.onMouseDown_ + ); } this.eventsInit_ = true; @@ -230,7 +227,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @returns #RRGGBB string. */ - getColourSecondary(): string|undefined { + getColourSecondary(): string | undefined { return this.style.colourSecondary; } @@ -239,7 +236,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @returns #RRGGBB string. */ - getColourTertiary(): string|undefined { + getColourTertiary(): string | undefined { return this.style.colourTertiary; } @@ -268,7 +265,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, } } const event = new (eventUtils.get(eventUtils.SELECTED))( - oldId, this.id, this.workspace.id); + oldId, + this.id, + this.workspace.id + ); eventUtils.fire(event); common.setSelected(this); this.addSelect(); @@ -283,39 +283,23 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, return; } const event = new (eventUtils.get(eventUtils.SELECTED))( - this.id, null, this.workspace.id); + this.id, + null, + this.workspace.id + ); event.workspaceId = this.workspace.id; eventUtils.fire(event); common.setSelected(null); this.removeSelect(); } - /** - * Returns a list of mutator, comment, and warning icons. - * - * @returns List of icons. - */ - getIcons(): Icon[] { - const icons = []; - if (this.mutator) { - icons.push(this.mutator); - } - if (this.commentIcon_) { - icons.push(this.commentIcon_); - } - if (this.warning) { - icons.push(this.warning); - } - return icons; - } - /** * Sets the parent of this block to be a new block or null. * * @param newParent New parent block. * @internal */ - override setParent(newParent: this|null) { + override setParent(newParent: this | null) { const oldParent = this.parentBlock_; if (newParent === oldParent) { return; @@ -359,10 +343,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, let x = 0; let y = 0; - const dragSurfaceGroup = this.useDragSurface_ ? - this.workspace.getBlockDragSurface()!.getGroup() : - null; - let element: SVGElement = this.getSvgRoot(); if (element) { do { @@ -370,19 +350,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, const xy = svgMath.getRelativeXY(element); x += xy.x; y += xy.y; - // If this element is the current element on the drag surface, include - // the translation of the drag surface itself. - if (this.useDragSurface_ && - this.workspace.getBlockDragSurface()!.getCurrentBlock() === - element) { - const surfaceTranslation = - this.workspace.getBlockDragSurface()!.getSurfaceTranslation(); - x += surfaceTranslation.x; - y += surfaceTranslation.y; - } element = element.parentNode as SVGElement; - } while (element && element !== this.workspace.getCanvas() && - element !== dragSurfaceGroup); + } while (element && element !== this.workspace.getCanvas()); } return new Coordinate(x, y); } @@ -392,15 +361,17 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @param dx Horizontal offset in workspace units. * @param dy Vertical offset in workspace units. + * @param reason Why is this move happening? 'drag', 'bump', 'snap', ... */ - override moveBy(dx: number, dy: number) { + override moveBy(dx: number, dy: number, reason?: string[]) { if (this.parentBlock_) { - throw Error('Block has parent.'); + throw Error('Block has parent'); } const eventsEnabled = eventUtils.isEnabled(); - let event: BlockMove|null = null; + let event: BlockMove | null = null; if (eventsEnabled) { - event = new (eventUtils.get(eventUtils.BLOCK_MOVE))!(this) as BlockMove; + event = new (eventUtils.get(eventUtils.BLOCK_MOVE)!)(this) as BlockMove; + reason && event.setReason(reason); } const xy = this.getRelativeToSurfaceXY(); this.translate(xy.x + dx, xy.y + dy); @@ -434,76 +405,27 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, return this.translation; } - /** - * Move this block to its workspace's drag surface, accounting for - * positioning. Generally should be called at the same time as - * setDragging_(true). Does nothing if useDragSurface_ is false. - * - * @internal - */ - moveToDragSurface() { - if (!this.useDragSurface_) { - return; - } - // The translation for drag surface blocks, - // is equal to the current relative-to-surface position, - // to keep the position in sync as it move on/off the surface. - // This is in workspace coordinates. - const xy = this.getRelativeToSurfaceXY(); - this.clearTransformAttributes_(); - this.workspace.getBlockDragSurface()!.translateSurface(xy.x, xy.y); - // Execute the move on the top-level SVG component - const svg = this.getSvgRoot(); - if (svg) { - this.workspace.getBlockDragSurface()!.setBlocksAndShow(svg); - } - } - /** * Move a block to a position. * * @param xy The position to move to in workspace units. + * @param reason Why is this move happening? 'drag', 'bump', 'snap', ... */ - moveTo(xy: Coordinate) { + moveTo(xy: Coordinate, reason?: string[]) { const curXY = this.getRelativeToSurfaceXY(); - this.moveBy(xy.x - curXY.x, xy.y - curXY.y); + this.moveBy(xy.x - curXY.x, xy.y - curXY.y, reason); } /** - * Move this block back to the workspace block canvas. - * Generally should be called at the same time as setDragging_(false). - * Does nothing if useDragSurface_ is false. - * - * @param newXY The position the block should take on on the workspace canvas, - * in workspace coordinates. - * @internal - */ - moveOffDragSurface(newXY: Coordinate) { - if (!this.useDragSurface_) { - return; - } - // Translate to current position, turning off 3d. - this.translate(newXY.x, newXY.y); - this.workspace.getBlockDragSurface()!.clearAndHide( - this.workspace.getCanvas()); - } - - /** - * Move this block during a drag, taking into account whether we are using a - * drag surface to translate blocks. + * Move this block during a drag. * This block must be a top-level block. * * @param newLoc The location to translate to, in workspace coordinates. * @internal */ moveDuringDrag(newLoc: Coordinate) { - if (this.useDragSurface_) { - this.workspace.getBlockDragSurface()!.translateSurface( - newLoc.x, newLoc.y); - } else { - this.translate(newLoc.x, newLoc.y); - this.getSvgRoot().setAttribute('transform', this.getTranslation()); - } + this.translate(newLoc.x, newLoc.y); + this.getSvgRoot().setAttribute('transform', this.getTranslation()); } /** @@ -517,31 +439,33 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, /** Snap this block to the nearest grid point. */ snapToGrid() { if (this.isDeadOrDying()) { - return; // Deleted block. + return; // Deleted block. } if (this.workspace.isDragging()) { - return; // Don't bump blocks during a drag.; + return; // Don't bump blocks during a drag.; } if (this.getParent()) { - return; // Only snap top-level blocks. + return; // Only snap top-level blocks. } if (this.isInFlyout) { - return; // Don't move blocks around in a flyout. + return; // Don't move blocks around in a flyout. } const grid = this.workspace.getGrid(); if (!grid || !grid.shouldSnap()) { - return; // Config says no snapping. + return; // Config says no snapping. } const spacing = grid.getSpacing(); const half = spacing / 2; const xy = this.getRelativeToSurfaceXY(); - const dx = - Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x); - const dy = - Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y); + const dx = Math.round( + Math.round((xy.x - half) / spacing) * spacing + half - xy.x + ); + const dy = Math.round( + Math.round((xy.y - half) / spacing) * spacing + half - xy.y + ); if (dx || dy) { - this.moveBy(dx, dy); + this.moveBy(dx, dy, ['snap']); } } @@ -573,7 +497,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ markDirty() { this.pathObject.constants = this.workspace.getRenderer().getConstants(); - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { input.markDirty(); } } @@ -600,31 +524,31 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, const collapsedInputName = constants.COLLAPSED_INPUT_NAME; const collapsedFieldName = constants.COLLAPSED_FIELD_NAME; - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.name !== collapsedInputName) { input.setVisible(!collapsed); } } + for (const icon of this.getIcons()) { + icon.updateCollapsed(); + } + if (!collapsed) { this.updateDisabled(); this.removeInput(collapsedInputName); return; } - const icons = this.getIcons(); - for (let i = 0, icon; icon = icons[i]; i++) { - icon.setVisible(false); - } - const text = this.toString(internalConstants.COLLAPSE_CHARS); const field = this.getField(collapsedFieldName); if (field) { field.setValue(text); return; } - const input = this.getInput(collapsedInputName) || - this.appendDummyInput(collapsedInputName); + const input = + this.getInput(collapsedInputName) || + this.appendDummyInput(collapsedInputName); input.appendField(new FieldLabel(text), collapsedFieldName); } @@ -676,7 +600,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ showHelp() { const url = - typeof this.helpUrl === 'function' ? this.helpUrl() : this.helpUrl; + typeof this.helpUrl === 'function' ? this.helpUrl() : this.helpUrl; if (url) { window.open(url); } @@ -687,13 +611,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @returns Context menu options or null if no menu. */ - protected generateContextMenu(): - Array|null { + protected generateContextMenu(): Array< + ContextMenuOption | LegacyContextMenuOption + > | null { if (this.workspace.options.readOnly || !this.contextMenu) { return null; } const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions( - ContextMenuRegistry.ScopeType.BLOCK, {block: this}); + ContextMenuRegistry.ScopeType.BLOCK, + {block: this} + ); // Allow the block to add or modify menuOptions. if (this.customContextMenu) { @@ -737,8 +664,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, myConnections[i].moveBy(dx, dy); } const icons = this.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].computeIconLocation(); + const pos = this.getRelativeToSurfaceXY(); + for (const icon of icons) { + icon.onLocationChange(pos); } // Recurse through all blocks attached under this one. @@ -812,12 +740,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ override setInsertionMarker(insertionMarker: boolean) { if (this.isInsertionMarker_ === insertionMarker) { - return; // No change. + return; // No change. } this.isInsertionMarker_ = insertionMarker; if (this.isInsertionMarker_) { this.setColour( - this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR); + this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR + ); this.pathObject.updateInsertionMarker(true); } } @@ -838,7 +767,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * statement with the previous statement. Otherwise, dispose of all * children of this block. * @param animate If true, show a disposal animation and sound. - * @suppress {checkTypes} */ override dispose(healStack?: boolean, animate?: boolean) { if (this.isDeadOrDying()) return; @@ -894,8 +822,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, // (https://github.com/google/blockly/issues/4832) this.dispose(false, true); } else { - this.dispose(/* heal */ - true, true); + this.dispose(/* heal */ true, true); } eventUtils.setGroup(false); } @@ -906,14 +833,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @returns Copy metadata, or null if the block is an insertion marker. * @internal */ - toCopyData(): CopyData|null { + toCopyData(): CopyData | null { if (this.isInsertionMarker_) { return null; } return { - saveInfo: - blocks.save(this, {addCoordinates: true, addNextBlocks: false}) as - blocks.State, + saveInfo: blocks.save(this, { + addCoordinates: true, + addNextBlocks: false, + }) as blocks.State, source: this.workspace, typeCounts: common.getBlockTypeCounts(this, true), }; @@ -932,8 +860,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, icons[i].applyColour(); } - for (let x = 0, input; input = this.inputList[x]; x++) { - for (let y = 0, field; field = input.fieldRow[y]; y++) { + for (let x = 0, input; (input = this.inputList[x]); x++) { + for (let y = 0, field; (field = input.fieldRow[y]); y++) { field.applyColour(); } } @@ -947,7 +875,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ updateDisabled() { const disabled = !this.isEnabled() || this.getInheritedDisabled(); - if (this.visuallyDisabled === disabled) return; + + if (this.visuallyDisabled === disabled) { + this.getNextBlock()?.updateDisabled(); + return; + } + this.applyColour(); this.visuallyDisabled = disabled; for (const child of this.getChildren(false)) { @@ -960,42 +893,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * comment. * * @returns The comment icon attached to this block, or null. + * @deprecated Use getIcon. To be remove in v11. */ - getCommentIcon(): Comment|null { - return this.commentIcon_; - } - - /** - * Set this block's comment text. - * - * @param text The text, or null to delete. - */ - override setCommentText(text: string|null) { - if (this.commentModel.text === text) { - return; - } - super.setCommentText(text); - - const shouldHaveComment = text !== null; - if (!!this.commentIcon_ === shouldHaveComment) { - // If the comment's state of existence is correct, but the text is new - // that means we're just updating a comment. - this.commentIcon_!.updateText(); - return; - } - if (shouldHaveComment) { - this.commentIcon_ = new Comment(this); - this.comment = this.commentIcon_; // For backwards compatibility. - } else { - this.commentIcon_!.dispose(); - this.commentIcon_ = null; - this.comment = null; // For backwards compatibility. - } - if (this.rendered) { - this.render(); - // Adding or removing a comment icon will cause the block to change shape. - this.bumpNeighbours(); - } + getCommentIcon(): CommentIcon | null { + deprecation.warn('getCommentIcon', 'v10', 'v11', 'getIcon'); + return (this.getIcon(CommentIcon.TYPE) ?? null) as CommentIcon | null; } /** @@ -1005,7 +907,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @param opt_id An optional ID for the warning text to be able to maintain * multiple warnings. */ - override setWarningText(text: string|null, opt_id?: string) { + override setWarningText(text: string | null, opt_id?: string) { const id = opt_id || ''; if (!id) { // Kill all previous pending processes, this edit supersedes them all. @@ -1021,19 +923,22 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, if (this.workspace.isDragging()) { // Don't change the warning text during a drag. // Wait until the drag finishes. - this.warningTextDb.set(id, setTimeout(() => { - if (!this.isDeadOrDying()) { - this.warningTextDb.delete(id); - this.setWarningText(text, id); - } - }, 100)); + this.warningTextDb.set( + id, + setTimeout(() => { + if (!this.isDeadOrDying()) { + this.warningTextDb.delete(id); + this.setWarningText(text, id); + } + }, 100) + ); return; } if (this.isInFlyout) { text = null; } - let changedState = false; + const icon = this.getIcon(WarningIcon.TYPE) as WarningIcon | undefined; if (typeof text === 'string') { // Bubble up to add a warning on top-most collapsed block. let parent = this.getSurroundParent(); @@ -1046,34 +951,24 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, } if (collapsedParent) { collapsedParent.setWarningText( - Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID); + Msg['COLLAPSED_WARNINGS_WARNING'], + BlockSvg.COLLAPSED_WARNING_ID + ); } - if (!this.warning) { - this.warning = new Warning(this); - changedState = true; + if (icon) { + (icon as WarningIcon).addMessage(text, id); + } else { + this.addIcon(new WarningIcon(this).addMessage(text, id)); } - this.warning!.setText((text), id); - } else { + } else if (icon) { // Dispose all warnings if no ID is given. - if (this.warning && !id) { - this.warning.dispose(); - changedState = true; - } else if (this.warning) { - const oldText = this.warning.getText(); - this.warning.setText('', id); - const newText = this.warning.getText(); - if (!newText) { - this.warning.dispose(); - } - changedState = oldText !== newText; + if (!id) { + this.removeIcon(WarningIcon.TYPE); + } else { + if (!icon.getText()) this.removeIcon(WarningIcon.TYPE); } } - if (changedState && this.rendered) { - this.render(); - // Adding or removing a warning icon will cause the block to change shape. - this.bumpNeighbours(); - } } /** @@ -1081,20 +976,55 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @param mutator A mutator dialog instance or null to remove. */ - override setMutator(mutator: Mutator|null) { - if (this.mutator && this.mutator !== mutator) { - this.mutator.dispose(); - } - if (mutator) { - mutator.setBlock(this); - this.mutator = mutator; - mutator.createIcon(); - } + override setMutator(mutator: MutatorIcon | null) { + this.removeIcon(MutatorIcon.TYPE); + if (mutator) this.addIcon(mutator); + } + + override addIcon(icon: T): T { + super.addIcon(icon); + + if (icon instanceof WarningIcon) this.warning = icon; + if (icon instanceof MutatorIcon) this.mutator = icon; + if (this.rendered) { + icon.initView(this.createIconPointerDownListener(icon)); + icon.applyColour(); + icon.updateEditable(); + // TODO: Change this based on #7068. this.render(); - // Adding or removing a mutator icon will cause the block to change shape. this.bumpNeighbours(); } + + return icon; + } + + /** + * Creates a pointer down event listener for the icon to append to its + * root svg. + */ + private createIconPointerDownListener(icon: IIcon) { + return (e: PointerEvent) => { + if (this.isDeadOrDying()) return; + const gesture = this.workspace.getGesture(e); + if (gesture) { + gesture.setStartIcon(icon); + } + }; + } + + override removeIcon(type: IconType): boolean { + const removed = super.removeIcon(type); + + if (type.equals(WarningIcon.TYPE)) this.warning = null; + if (type.equals(MutatorIcon.TYPE)) this.mutator = null; + + if (this.rendered) { + // TODO: Change this based on #7068. + this.render(); + this.bumpNeighbours(); + } + return removed; } /** @@ -1172,11 +1102,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @param colour HSV hue value, or #RRGGBB string. */ - override setColour(colour: number|string) { + override setColour(colour: number | string) { super.setColour(colour); - const styleObj = - this.workspace.getRenderer().getConstants().getBlockStyleForColour( - this.colour_); + const styleObj = this.workspace + .getRenderer() + .getConstants() + .getBlockStyleForColour(this.colour_); this.pathObject.setStyle(styleObj.style); this.style = styleObj.style; @@ -1192,9 +1123,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @throws {Error} if the block style does not exist. */ override setStyle(blockStyleName: string) { - const blockStyle = - this.workspace.getRenderer().getConstants().getBlockStyle( - blockStyleName); + const blockStyle = this.workspace + .getRenderer() + .getConstants() + .getBlockStyle(blockStyleName); this.styleName_ = blockStyleName; if (blockStyle) { @@ -1220,7 +1152,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ bringToFront() { /* eslint-disable-next-line @typescript-eslint/no-this-alias */ - let block: this|null = this; + let block: this | null = this; do { const root = block.getSvgRoot(); const parent = root.parentNode; @@ -1241,7 +1173,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * if any type could be connected. */ override setPreviousStatement( - newBoolean: boolean, opt_check?: string|string[]|null) { + newBoolean: boolean, + opt_check?: string | string[] | null + ) { super.setPreviousStatement(newBoolean, opt_check); if (this.rendered) { @@ -1258,7 +1192,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * if any type could be connected. */ override setNextStatement( - newBoolean: boolean, opt_check?: string|string[]|null) { + newBoolean: boolean, + opt_check?: string | string[] | null + ) { super.setNextStatement(newBoolean, opt_check); if (this.rendered) { @@ -1274,7 +1210,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @param opt_check Returned type or list of returned types. Null or * undefined if any type could be returned (e.g. variable get). */ - override setOutput(newBoolean: boolean, opt_check?: string|string[]|null) { + override setOutput( + newBoolean: boolean, + opt_check?: string | string[] | null + ) { super.setOutput(newBoolean, opt_check); if (this.rendered) { @@ -1334,16 +1273,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, } } - /** - * Add a value input, statement input or local variable to this block. - * - * @param type One of Blockly.inputTypes. - * @param name Language-neutral identifier which may used to find this input - * again. Should be unique to this block. - * @returns The input object created. - */ - protected override appendInput_(type: number, name: string): Input { - const input = super.appendInput_(type, name); + /** @override */ + override appendInput(input: Input): Input { + super.appendInput(input); if (this.rendered) { this.queueRender(); @@ -1365,14 +1297,14 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ setConnectionTracking(track: boolean) { if (this.previousConnection) { - (this.previousConnection).setTracking(track); + this.previousConnection.setTracking(track); } if (this.outputConnection) { - (this.outputConnection).setTracking(track); + this.outputConnection.setTracking(track); } if (this.nextConnection) { - (this.nextConnection).setTracking(track); - const child = (this.nextConnection).targetBlock(); + this.nextConnection.setTracking(track); + const child = this.nextConnection.targetBlock(); if (child) { child.setConnectionTracking(track); } @@ -1421,7 +1353,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, myConnections.push(this.nextConnection); } if (all || !this.collapsed_) { - for (let i = 0, input; input = this.inputList[i]; i++) { + for (let i = 0, input; (input = this.inputList[i]); i++) { if (input.connection) { myConnections.push(input.connection as RenderedConnection); } @@ -1441,8 +1373,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @returns The last next connection on the stack, or null. * @internal */ - override lastConnectionInStack(ignoreShadows: boolean): RenderedConnection - |null { + override lastConnectionInStack( + ignoreShadows: boolean + ): RenderedConnection | null { return super.lastConnectionInStack(ignoreShadows) as RenderedConnection; } @@ -1456,8 +1389,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @returns The matching connection on this block, or null. * @internal */ - override getMatchingConnection(otherBlock: Block, conn: Connection): - RenderedConnection|null { + override getMatchingConnection( + otherBlock: Block, + conn: Connection + ): RenderedConnection | null { return super.getMatchingConnection(otherBlock, conn) as RenderedConnection; } @@ -1466,8 +1401,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @param type The type of the connection to create. * @returns A new connection of the specified type. + * @internal */ - protected override makeConnection_(type: number): RenderedConnection { + override makeConnection_(type: ConnectionType): RenderedConnection { return new RenderedConnection(this, type); } @@ -1476,7 +1412,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @returns The next statement block or null. */ - override getNextBlock(): BlockSvg|null { + override getNextBlock(): BlockSvg | null { return super.getNextBlock() as BlockSvg; } @@ -1485,7 +1421,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @returns The previous statement block or null. */ - override getPreviousBlock(): BlockSvg|null { + override getPreviousBlock(): BlockSvg | null { return super.getPreviousBlock() as BlockSvg; } @@ -1513,8 +1449,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ private bumpNeighboursInternal() { const root = this.getRootBlock(); - if (this.isDeadOrDying() || this.workspace.isDragging() || - root.isInFlyout) { + if ( + this.isDeadOrDying() || + this.workspace.isDragging() || + root.isInFlyout + ) { return; } @@ -1574,12 +1513,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @internal */ positionNearConnection( - sourceConnection: RenderedConnection, - targetConnection: RenderedConnection) { + sourceConnection: RenderedConnection, + targetConnection: RenderedConnection + ) { // We only need to position the new block if it's before the existing one, // otherwise its position is set by the previous block. - if (sourceConnection.type === ConnectionType.NEXT_STATEMENT || - sourceConnection.type === ConnectionType.INPUT_VALUE) { + if ( + sourceConnection.type === ConnectionType.NEXT_STATEMENT || + sourceConnection.type === ConnectionType.INPUT_VALUE + ) { const dx = targetConnection.x - sourceConnection.x; const dy = targetConnection.y - sourceConnection.y; @@ -1591,7 +1533,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @returns The first statement connection or null. * @internal */ - override getFirstStatementConnection(): RenderedConnection|null { + override getFirstStatementConnection(): RenderedConnection | null { return super.getFirstStatementConnection() as RenderedConnection | null; } @@ -1611,10 +1553,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, /** * Triggers a rerender after a delay to allow for batching. * + * @returns A promise that resolves after the currently queued renders have + * been completed. Used for triggering other behavior that relies on + * updated size/position location for the block. * @internal */ - queueRender() { - queueRender(this); + queueRender(): Promise { + return queueRender(this); } /** @@ -1626,18 +1571,23 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, */ render(opt_bubble?: boolean) { if (this.renderIsInProgress_) { - return; // Don't allow recursive renders. + return; // Don't allow recursive renders. } this.renderIsInProgress_ = true; try { this.rendered = true; dom.startTextWidthCache(); + if (!this.isEnabled()) { + // Apply disabled styles if needed. + this.updateDisabled(); + } + if (this.isCollapsed()) { this.updateCollapsed_(); } this.workspace.getRenderer().render(this); - this.updateConnectionLocations(); + this.updateConnectionAndIconLocations(); if (opt_bubble !== false) { const parentBlock = this.getParent(); @@ -1716,7 +1666,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * * @internal */ - updateConnectionLocations() { + private updateConnectionAndIconLocations() { const blockTL = this.getRelativeToSurfaceXY(); // Don't tighten previous or output connections because they are inferior // connections. @@ -1743,6 +1693,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, this.nextConnection.tighten(); } } + + for (const icon of this.getIcons()) { + icon.onLocationChange(blockTL); + } } /** @@ -1774,15 +1728,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, * @returns Object with height and width properties in workspace units. * @internal */ - getHeightWidth(): {height: number, width: number} { + getHeightWidth(): {height: number; width: number} { let height = this.height; let width = this.width; // Recursively add size of subsequent blocks. const nextBlock = this.getNextBlock(); if (nextBlock) { const nextHeightWidth = nextBlock.getHeightWidth(); - const tabHeight = - this.workspace.getRenderer().getConstants().NOTCH_HEIGHT; + const tabHeight = this.workspace + .getRenderer() + .getConstants().NOTCH_HEIGHT; height += nextHeightWidth.height - tabHeight; width = Math.max(width, nextHeightWidth.width); } diff --git a/core/blockly.ts b/core/blockly.ts index b833924f6..ed74064bb 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -12,25 +12,22 @@ import './events/events_block_create.js'; // Unused import preserved for side-effects. Remove if unneeded. import './events/workspace_events.js'; // Unused import preserved for side-effects. Remove if unneeded. -import './events/events_ui.js'; -// Unused import preserved for side-effects. Remove if unneeded. import './events/events_ui_base.js'; // Unused import preserved for side-effects. Remove if unneeded. import './events/events_var_create.js'; import {Block} from './block.js'; import * as blockAnimations from './block_animations.js'; -import {BlockDragSurfaceSvg} from './block_drag_surface.js'; import {BlockDragger} from './block_dragger.js'; import {BlockSvg} from './block_svg.js'; import {BlocklyOptions} from './blockly_options.js'; import {Blocks} from './blocks.js'; import * as browserEvents from './browser_events.js'; -import {Bubble} from './bubble.js'; +import {Bubble} from './bubbles/bubble.js'; +import * as bubbles from './bubbles.js'; import {BubbleDragger} from './bubble_dragger.js'; import * as bumpObjects from './bump_objects.js'; import * as clipboard from './clipboard.js'; -import {Comment} from './comment.js'; import * as common from './common.js'; import {ComponentManager} from './component_manager.js'; import {config} from './config.js'; @@ -48,19 +45,75 @@ import {DragTarget} from './drag_target.js'; import * as dropDownDiv from './dropdowndiv.js'; import * as Events from './events/events.js'; import * as Extensions from './extensions.js'; -import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js'; -import {FieldAngle, FieldAngleConfig, FieldAngleFromJsonConfig, FieldAngleValidator} from './field_angle.js'; -import {FieldCheckbox, FieldCheckboxConfig, FieldCheckboxFromJsonConfig, FieldCheckboxValidator} from './field_checkbox.js'; -import {FieldColour, FieldColourConfig, FieldColourFromJsonConfig, FieldColourValidator} from './field_colour.js'; -import {FieldDropdown, FieldDropdownConfig, FieldDropdownFromJsonConfig, FieldDropdownValidator, MenuGenerator, MenuGeneratorFunction, MenuOption} from './field_dropdown.js'; -import {FieldImage, FieldImageConfig, FieldImageFromJsonConfig} from './field_image.js'; -import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js'; +import { + Field, + FieldConfig, + FieldValidator, + UnattachedFieldError, +} from './field.js'; +import { + FieldAngle, + FieldAngleConfig, + FieldAngleFromJsonConfig, + FieldAngleValidator, +} from './field_angle.js'; +import { + FieldCheckbox, + FieldCheckboxConfig, + FieldCheckboxFromJsonConfig, + FieldCheckboxValidator, +} from './field_checkbox.js'; +import { + FieldColour, + FieldColourConfig, + FieldColourFromJsonConfig, + FieldColourValidator, +} from './field_colour.js'; +import { + FieldDropdown, + FieldDropdownConfig, + FieldDropdownFromJsonConfig, + FieldDropdownValidator, + MenuGenerator, + MenuGeneratorFunction, + MenuOption, +} from './field_dropdown.js'; +import { + FieldImage, + FieldImageConfig, + FieldImageFromJsonConfig, +} from './field_image.js'; +import { + FieldLabel, + FieldLabelConfig, + FieldLabelFromJsonConfig, +} from './field_label.js'; import {FieldLabelSerializable} from './field_label_serializable.js'; -import {FieldMultilineInput, FieldMultilineInputConfig, FieldMultilineInputFromJsonConfig, FieldMultilineInputValidator} from './field_multilineinput.js'; -import {FieldNumber, FieldNumberConfig, FieldNumberFromJsonConfig, FieldNumberValidator} from './field_number.js'; +import { + FieldMultilineInput, + FieldMultilineInputConfig, + FieldMultilineInputFromJsonConfig, + FieldMultilineInputValidator, +} from './field_multilineinput.js'; +import { + FieldNumber, + FieldNumberConfig, + FieldNumberFromJsonConfig, + FieldNumberValidator, +} from './field_number.js'; import * as fieldRegistry from './field_registry.js'; -import {FieldTextInput, FieldTextInputConfig, FieldTextInputFromJsonConfig, FieldTextInputValidator} from './field_textinput.js'; -import {FieldVariable, FieldVariableConfig, FieldVariableFromJsonConfig, FieldVariableValidator} from './field_variable.js'; +import { + FieldTextInput, + FieldTextInputConfig, + FieldTextInputFromJsonConfig, + FieldTextInputValidator, +} from './field_textinput.js'; +import { + FieldVariable, + FieldVariableConfig, + FieldVariableFromJsonConfig, + FieldVariableValidator, +} from './field_variable.js'; import {Flyout} from './flyout_base.js'; import {FlyoutButton} from './flyout_button.js'; import {HorizontalFlyout} from './flyout_horizontal.js'; @@ -69,10 +122,12 @@ import {VerticalFlyout} from './flyout_vertical.js'; import {CodeGenerator} from './generator.js'; import {Gesture} from './gesture.js'; import {Grid} from './grid.js'; -import {Icon} from './icon.js'; +import * as icons from './icons.js'; import {inject} from './inject.js'; -import {Align, Input} from './input.js'; -import {inputTypes} from './input_types.js'; +import {Align} from './inputs/align.js'; +import {Input} from './inputs/input.js'; +import {inputTypes} from './inputs/input_types.js'; +import * as inputs from './inputs.js'; import {InsertionMarkerManager} from './insertion_marker_manager.js'; import {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; @@ -91,6 +146,8 @@ import {IDeleteArea} from './interfaces/i_delete_area.js'; import {IDragTarget} from './interfaces/i_drag_target.js'; import {IDraggable} from './interfaces/i_draggable.js'; import {IFlyout} from './interfaces/i_flyout.js'; +import {IHasBubble, hasBubble} from './interfaces/i_has_bubble.js'; +import {IIcon, isIcon} from './interfaces/i_icon.js'; import {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js'; import {IMetricsManager} from './interfaces/i_metrics_manager.js'; import {IMovable} from './interfaces/i_movable.js'; @@ -99,10 +156,14 @@ import {IPositionable} from './interfaces/i_positionable.js'; import {IRegistrable} from './interfaces/i_registrable.js'; import {ISelectable} from './interfaces/i_selectable.js'; import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.js'; +import {ISerializable, isSerializable} from './interfaces/i_serializable.js'; import {IStyleable} from './interfaces/i_styleable.js'; import {IToolbox} from './interfaces/i_toolbox.js'; import {IToolboxItem} from './interfaces/i_toolbox_item.js'; -import {IVariableBackedParameterModel, isVariableBackedParameterModel} from './interfaces/i_variable_backed_parameter_model.js'; +import { + IVariableBackedParameterModel, + isVariableBackedParameterModel, +} from './interfaces/i_variable_backed_parameter_model.js'; import * as internalConstants from './internal_constants.js'; import {ASTNode} from './keyboard_nav/ast_node.js'; import {BasicCursor} from './keyboard_nav/basic_cursor.js'; @@ -114,13 +175,14 @@ import {Menu} from './menu.js'; import {MenuItem} from './menuitem.js'; import {MetricsManager} from './metrics_manager.js'; import {Msg, setLocale} from './msg.js'; -import {Mutator} from './mutator.js'; +import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js'; import {Names} from './names.js'; import {Options} from './options.js'; import * as uiPosition from './positionable_helpers.js'; import * as Procedures from './procedures.js'; import * as registry from './registry.js'; import {RenderedConnection} from './rendered_connection.js'; +import * as renderManagement from './render_management.js'; import * as blockRendering from './renderers/common/block_rendering.js'; import * as constants from './constants.js'; import * as geras from './renderers/geras/geras.js'; @@ -144,26 +206,21 @@ import * as Tooltip from './tooltip.js'; import * as Touch from './touch.js'; import {Trashcan} from './trashcan.js'; import * as utils from './utils.js'; -import * as colour from './utils/colour.js'; -import * as deprecation from './utils/deprecation.js'; import * as toolbox from './utils/toolbox.js'; import {VariableMap} from './variable_map.js'; import {VariableModel} from './variable_model.js'; import * as Variables from './variables.js'; import * as VariablesDynamic from './variables_dynamic.js'; -import {Warning} from './warning.js'; import * as WidgetDiv from './widgetdiv.js'; import {Workspace} from './workspace.js'; import {WorkspaceAudio} from './workspace_audio.js'; import {WorkspaceComment} from './workspace_comment.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; -import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js'; import {WorkspaceDragger} from './workspace_dragger.js'; -import {resizeSvgContents as realResizeSvgContents, WorkspaceSvg} from './workspace_svg.js'; +import {WorkspaceSvg} from './workspace_svg.js'; import * as Xml from './xml.js'; import {ZoomControls} from './zoom_controls.js'; - /** * Blockly core version. * This constant is overridden by the build script (npm run build) to the value @@ -190,16 +247,19 @@ export const VERSION = 'uncompiled'; /** * @see Blockly.Input.Align.LEFT + * @deprecated Use `Blockly.inputs.Align.LEFT`. To be removed in v11. */ export const ALIGN_LEFT = Align.LEFT; /** * @see Blockly.Input.Align.CENTRE + * @deprecated Use `Blockly.inputs.Align.CENTER`. To be removed in v11. */ export const ALIGN_CENTRE = Align.CENTRE; /** * @see Blockly.Input.Align.RIGHT + * @deprecated Use `Blockly.inputs.Align.RIGHT`. To be removed in v11. */ export const ALIGN_RIGHT = Align.RIGHT; /* @@ -228,6 +288,7 @@ export const PREVIOUS_STATEMENT = ConnectionType.PREVIOUS_STATEMENT; /** * @see inputTypes.DUMMY_INPUT + * @deprecated Use `Blockly.inputs.inputTypes.DUMMY`. To be removed in v11. */ export const DUMMY_INPUT = inputTypes.DUMMY; @@ -313,162 +374,6 @@ export const defineBlocksWithJsonArray = common.defineBlocksWithJsonArray; */ export const setParentContainer = common.setParentContainer; -/** - * Size the workspace when the contents change. This also updates - * scrollbars accordingly. - * - * @param workspace The workspace to resize. - * @deprecated Use **workspace.resizeContents** instead. - * @see Blockly.WorkspaceSvg.resizeContents - */ -function resizeSvgContentsLocal(workspace: WorkspaceSvg) { - deprecation.warn( - 'Blockly.resizeSvgContents', 'December 2021', 'December 2022', - 'Blockly.WorkspaceSvg.resizeSvgContents'); - realResizeSvgContents(workspace); -} -export const resizeSvgContents = resizeSvgContentsLocal; - -/** - * Copy a block or workspace comment onto the local clipboard. - * - * @param toCopy Block or Workspace Comment to be copied. - * @deprecated Use **Blockly.clipboard.copy** instead. - * @see Blockly.clipboard.copy - */ -export function copy(toCopy: ICopyable) { - deprecation.warn( - 'Blockly.copy', 'December 2021', 'December 2022', - 'Blockly.clipboard.copy'); - clipboard.copy(toCopy); -} - -/** - * Paste a block or workspace comment on to the main workspace. - * - * @returns True if the paste was successful, false otherwise. - * @deprecated Use **Blockly.clipboard.paste** instead. - * @see Blockly.clipboard.paste - */ -export function paste(): boolean { - deprecation.warn( - 'Blockly.paste', 'December 2021', 'December 2022', - 'Blockly.clipboard.paste'); - return !!clipboard.paste(); -} - -/** - * Duplicate this block and its children, or a workspace comment. - * - * @param toDuplicate Block or Workspace Comment to be copied. - * @deprecated Use **Blockly.clipboard.duplicate** instead. - * @see Blockly.clipboard.duplicate - */ -export function duplicate(toDuplicate: ICopyable) { - deprecation.warn( - 'Blockly.duplicate', 'December 2021', 'December 2022', - 'Blockly.clipboard.duplicate'); - clipboard.duplicate(toDuplicate); -} - -/** - * Is the given string a number (includes negative and decimals). - * - * @param str Input string. - * @returns True if number, false otherwise. - * @deprecated Use **Blockly.utils.string.isNumber** instead. - * @see Blockly.utils.string.isNumber - */ -export function isNumber(str: string): boolean { - deprecation.warn( - 'Blockly.isNumber', 'December 2021', 'December 2022', - 'Blockly.utils.string.isNumber'); - return utils.string.isNumber(str); -} - -/** - * Convert a hue (HSV model) into an RGB hex triplet. - * - * @param hue Hue on a colour wheel (0-360). - * @returns RGB code, e.g. '#5ba65b'. - * @deprecated Use **Blockly.utils.colour.hueToHex** instead. - * @see Blockly.utils.colour.hueToHex - */ -export function hueToHex(hue: number): string { - deprecation.warn( - 'Blockly.hueToHex', 'December 2021', 'December 2022', - 'Blockly.utils.colour.hueToHex'); - return colour.hueToHex(hue); -} - -/** - * Bind an event handler that should be called regardless of whether it is part - * of the active touch stream. - * Use this for events that are not part of a multi-part gesture (e.g. - * mouseover for tooltips). - * - * @param node Node upon which to listen. - * @param name Event name to listen to (e.g. 'mousedown'). - * @param thisObject The value of 'this' in the function. - * @param func Function to call when event is triggered. - * @returns Opaque data that can be passed to unbindEvent_. - * @deprecated Use **Blockly.browserEvents.bind** instead. - * @see Blockly.browserEvents.bind - */ -export function bindEvent_( - node: EventTarget, name: string, thisObject: Object|null, - func: Function): browserEvents.Data { - deprecation.warn( - 'Blockly.bindEvent_', 'December 2021', 'December 2022', - 'Blockly.browserEvents.bind'); - return browserEvents.bind(node, name, thisObject, func); -} - -/** - * Unbind one or more events event from a function call. - * - * @param bindData Opaque data from bindEvent_. - * This list is emptied during the course of calling this function. - * @returns The function call. - * @deprecated Use **Blockly.browserEvents.unbind** instead. - * @see browserEvents.unbind - */ -export function unbindEvent_(bindData: browserEvents.Data): Function { - deprecation.warn( - 'Blockly.unbindEvent_', 'December 2021', 'December 2022', - 'Blockly.browserEvents.unbind'); - return browserEvents.unbind(bindData); -} - -/** - * Bind an event handler that can be ignored if it is not part of the active - * touch stream. - * Use this for events that either start or continue a multi-part gesture (e.g. - * mousedown or mousemove, which may be part of a drag or click). - * - * @param node Node upon which to listen. - * @param name Event name to listen to (e.g. 'mousedown'). - * @param thisObject The value of 'this' in the function. - * @param func Function to call when event is triggered. - * @param opt_noCaptureIdentifier True if triggering on this event should not - * block execution of other event handlers on this touch or other - * simultaneous touches. False by default. - * @param _opt_noPreventDefault No-op, deprecated and will be removed in v10. - * @returns Opaque data that can be passed to unbindEvent_. - * @deprecated Use **Blockly.browserEvents.conditionalBind** instead. - * @see browserEvents.conditionalBind - */ -export function bindEventWithChecks_( - node: EventTarget, name: string, thisObject: Object|null, func: Function, - opt_noCaptureIdentifier?: boolean, - _opt_noPreventDefault?: boolean): browserEvents.Data { - deprecation.warn( - 'Blockly.bindEventWithChecks_', 'December 2021', 'December 2022', - 'Blockly.browserEvents.conditionalBind'); - return browserEvents.conditionalBind( - node, name, thisObject, func, opt_noCaptureIdentifier); -} - // Aliases to allow external code to access these values for legacy reasons. export const COLLAPSE_CHARS = internalConstants.COLLAPSE_CHARS; export const DRAG_STACK = internalConstants.DRAG_STACK; @@ -491,7 +396,7 @@ export const VARIABLE_CATEGORY_NAME: string = Variables.CATEGORY_NAME; * variable blocks. */ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string = - VariablesDynamic.CATEGORY_NAME; + VariablesDynamic.CATEGORY_NAME; /** * String for use in the "custom" attribute of a category in toolbox XML. * This string indicates that the category should be dynamically populated with @@ -499,58 +404,64 @@ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string = */ export const PROCEDURE_CATEGORY_NAME: string = Procedures.CATEGORY_NAME; - // Context for why we need to monkey-patch in these functions (internal): // https://docs.google.com/document/d/1MbO0LEA-pAyx1ErGLJnyUqTLrcYTo-5zga9qplnxeXo/edit?usp=sharing&resourcekey=0-5h_32-i-dHwHjf_9KYEVKg // clang-format off -Workspace.prototype.newBlock = - function(prototypeName: string, opt_id?: string): Block { - return new Block(this, prototypeName, opt_id); - }; +Workspace.prototype.newBlock = function ( + prototypeName: string, + opt_id?: string +): Block { + return new Block(this, prototypeName, opt_id); +}; -WorkspaceSvg.prototype.newBlock = - function(prototypeName: string, opt_id?: string): BlockSvg { - return new BlockSvg(this, prototypeName, opt_id); - }; +WorkspaceSvg.prototype.newBlock = function ( + prototypeName: string, + opt_id?: string +): BlockSvg { + return new BlockSvg(this, prototypeName, opt_id); +}; -WorkspaceSvg.newTrashcan = function(workspace: WorkspaceSvg): Trashcan { +WorkspaceSvg.newTrashcan = function (workspace: WorkspaceSvg): Trashcan { return new Trashcan(workspace); }; -WorkspaceCommentSvg.prototype.showContextMenu = - function(this: WorkspaceCommentSvg, e: Event) { - if (this.workspace.options.readOnly) { - return; - } - const menuOptions = []; +WorkspaceCommentSvg.prototype.showContextMenu = function ( + this: WorkspaceCommentSvg, + e: Event +) { + if (this.workspace.options.readOnly) { + return; + } + const menuOptions = []; - if (this.isDeletable() && this.isMovable()) { - menuOptions.push(ContextMenu.commentDuplicateOption(this)); - menuOptions.push(ContextMenu.commentDeleteOption(this)); - } + if (this.isDeletable() && this.isMovable()) { + menuOptions.push(ContextMenu.commentDuplicateOption(this)); + menuOptions.push(ContextMenu.commentDeleteOption(this)); + } - ContextMenu.show(e, menuOptions, this.RTL); - }; + ContextMenu.show(e, menuOptions, this.RTL); +}; -Mutator.prototype.newWorkspaceSvg = - function(options: Options): WorkspaceSvg { - return new WorkspaceSvg(options); - }; +MiniWorkspaceBubble.prototype.newWorkspaceSvg = function ( + options: Options +): WorkspaceSvg { + return new WorkspaceSvg(options); +}; -Names.prototype.populateProcedures = - function(this: Names, workspace: Workspace) { - const procedures = Procedures.allProcedures(workspace); - // Flatten the return vs no-return procedure lists. - const flattenedProcedures: AnyDuringMigration[][] = - procedures[0].concat(procedures[1]); - for (let i = 0; i < flattenedProcedures.length; i++) { - this.getName(flattenedProcedures[i][0], Names.NameType.PROCEDURE); - } - }; +Names.prototype.populateProcedures = function ( + this: Names, + workspace: Workspace +) { + const procedures = Procedures.allProcedures(workspace); + // Flatten the return vs no-return procedure lists. + const flattenedProcedures = procedures[0].concat(procedures[1]); + for (let i = 0; i < flattenedProcedures.length; i++) { + this.getName(flattenedProcedures[i][0], Names.NameType.PROCEDURE); + } +}; // clang-format on - // Re-export submodules that no longer declareLegacyNamespace. export {browserEvents}; export {ContextMenu}; @@ -588,13 +499,13 @@ export {BasicCursor}; export {Block}; export {BlocklyOptions}; export {BlockDragger}; -export {BlockDragSurfaceSvg}; export {BlockSvg}; export {Blocks}; +export {bubbles}; +/** @deprecated Use Blockly.bubbles.Bubble instead. To be removed in v11. */ export {Bubble}; export {BubbleDragger}; export {CollapsibleToolboxCategory}; -export {Comment}; export {ComponentManager}; export {Connection}; export {ConnectionType}; @@ -664,9 +575,8 @@ export {Flyout}; export {FlyoutButton}; export {FlyoutMetricsManager}; export {CodeGenerator}; -export {CodeGenerator as Generator}; // Deprecated name, October 2022. +export {CodeGenerator as Generator}; // Deprecated name, October 2022. export {Gesture}; -export {Gesture as TouchGesture}; // Remove in v10. export {Grid}; export {HorizontalFlyout}; export {IASTNodeLocation}; @@ -680,23 +590,27 @@ export {ICollapsibleToolboxItem}; export {IComponent}; export {IConnectionChecker}; export {IContextMenu}; -export {Icon}; +export {icons}; export {ICopyable}; export {IDeletable}; export {IDeleteArea}; export {IDragTarget}; export {IDraggable}; export {IFlyout}; +export {IHasBubble, hasBubble}; +export {IIcon, isIcon}; export {IKeyboardAccessible}; export {IMetricsManager}; export {IMovable}; export {Input}; +export {inputs}; export {InsertionMarkerManager}; export {IObservable, isObservable}; export {IPositionable}; export {IRegistrable}; export {ISelectable}; export {ISelectableToolboxItem}; +export {ISerializable, isSerializable}; export {IStyleable}; export {IToolbox}; export {IToolboxItem}; @@ -706,11 +620,11 @@ export {MarkerManager}; export {Menu}; export {MenuItem}; export {MetricsManager}; -export {Mutator}; export {Msg, setLocale}; export {Names}; export {Options}; export {RenderedConnection}; +export {renderManagement}; export {Scrollbar}; export {ScrollbarPair}; export {ShortcutRegistry}; @@ -725,12 +639,10 @@ export {Trashcan}; export {VariableMap}; export {VariableModel}; export {VerticalFlyout}; -export {Warning}; export {Workspace}; export {WorkspaceAudio}; export {WorkspaceComment}; export {WorkspaceCommentSvg}; -export {WorkspaceDragSurfaceSvg}; export {WorkspaceDragger}; export {WorkspaceSvg}; export {ZoomControls}; @@ -738,5 +650,6 @@ export {config}; /** @deprecated Use Blockly.ConnectionType instead. */ export const connectionTypes = ConnectionType; export {inject}; +/** @deprecated Use Blockly.inputs.inputTypes instead. To be removed in v11. */ export {inputTypes}; export {serialization}; diff --git a/core/blockly_options.ts b/core/blockly_options.ts index deabe1f96..2c8ccea55 100644 --- a/core/blockly_options.ts +++ b/core/blockly_options.ts @@ -11,7 +11,6 @@ import type {Theme, ITheme} from './theme.js'; import type {WorkspaceSvg} from './workspace_svg.js'; import type {ToolboxDefinition} from './utils/toolbox.js'; - /** * Blockly options. */ @@ -32,14 +31,14 @@ export interface BlocklyOptions { renderer?: string; rendererOverrides?: {[rendererConstant: string]: any}; rtl?: boolean; - scrollbars?: ScrollbarOptions|boolean; + scrollbars?: ScrollbarOptions | boolean; sounds?: boolean; - theme?: Theme|string|ITheme; - toolbox?: string|ToolboxDefinition|Element; + theme?: Theme | string | ITheme; + toolbox?: string | ToolboxDefinition | Element; toolboxPosition?: string; trashcan?: boolean; maxTrashcanContents?: number; - plugins?: {[key: string]: (new(...p1: any[]) => any)|string}; + plugins?: {[key: string]: (new (...p1: any[]) => any) | string}; zoom?: ZoomOptions; parentWorkspace?: WorkspaceSvg; } @@ -53,7 +52,7 @@ export interface GridOptions { export interface MoveOptions { drag?: boolean; - scrollbars?: boolean|ScrollbarOptions; + scrollbars?: boolean | ScrollbarOptions; wheel?: boolean; } diff --git a/core/blocks.ts b/core/blocks.ts index 0b381fa89..f0c1f183d 100644 --- a/core/blocks.ts +++ b/core/blocks.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.blocks'); - /** * A block definition. For now this very loose, but it can potentially * be refined e.g. by replacing this typedef with a class definition. diff --git a/core/browser_events.ts b/core/browser_events.ts index 6723aa7e5..d5fdff0a1 100644 --- a/core/browser_events.ts +++ b/core/browser_events.ts @@ -8,10 +8,8 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.browserEvents'); import * as Touch from './touch.js'; -import * as deprecation from './utils/deprecation.js'; import * as userAgent from './utils/useragent.js'; - /** * Blockly opaque event data used to unbind events when using * `bind` and `conditionalBind`. @@ -45,17 +43,15 @@ const PAGE_MODE_MULTIPLIER = 125; * @param opt_noCaptureIdentifier True if triggering on this event should not * block execution of other event handlers on this touch or other * simultaneous touches. False by default. - * @param opt_noPreventDefault No-op, deprecated and will be removed in v10. * @returns Opaque data that can be passed to unbindEvent_. */ export function conditionalBind( - node: EventTarget, name: string, thisObject: Object|null, func: Function, - opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean): Data { - if (opt_noPreventDefault !== undefined) { - deprecation.warn( - 'The opt_noPreventDefault argument of conditionalBind', 'version 9', - 'version 10'); - } + node: EventTarget, + name: string, + thisObject: Object | null, + func: Function, + opt_noCaptureIdentifier?: boolean +): Data { /** * * @param e @@ -99,8 +95,11 @@ export function conditionalBind( * @returns Opaque data that can be passed to unbindEvent_. */ export function bind( - node: EventTarget, name: string, thisObject: Object|null, - func: Function): Data { + node: EventTarget, + name: string, + thisObject: Object | null, + func: Function +): Data { /** * * @param e @@ -154,17 +153,24 @@ export function unbind(bindData: Data): (e: Event) => void { */ export function isTargetInput(e: Event): boolean { if (e.target instanceof HTMLElement) { - if (e.target.isContentEditable || - e.target.getAttribute('data-is-text-input') === 'true') { + if ( + e.target.isContentEditable || + e.target.getAttribute('data-is-text-input') === 'true' + ) { return true; } if (e.target instanceof HTMLInputElement) { const target = e.target; - return target.type === 'text' || target.type === 'number' || - target.type === 'email' || target.type === 'password' || - target.type === 'search' || target.type === 'tel' || - target.type === 'url'; + return ( + target.type === 'text' || + target.type === 'number' || + target.type === 'email' || + target.type === 'password' || + target.type === 'search' || + target.type === 'tel' || + target.type === 'url' + ); } if (e.target instanceof HTMLTextAreaElement) { @@ -200,7 +206,10 @@ export function isRightButton(e: MouseEvent): boolean { * @returns Object with .x and .y properties. */ export function mouseToSvg( - e: MouseEvent, svg: SVGSVGElement, matrix: SVGMatrix|null): SVGPoint { + e: MouseEvent, + svg: SVGSVGElement, + matrix: SVGMatrix | null +): SVGPoint { const svgPoint = svg.createSVGPoint(); svgPoint.x = e.clientX; svgPoint.y = e.clientY; @@ -217,17 +226,17 @@ export function mouseToSvg( * @param e Mouse event. * @returns Scroll delta object with .x and .y properties. */ -export function getScrollDeltaPixels(e: WheelEvent): {x: number, y: number} { +export function getScrollDeltaPixels(e: WheelEvent): {x: number; y: number} { switch (e.deltaMode) { - case 0x00: // Pixel mode. + case 0x00: // Pixel mode. default: return {x: e.deltaX, y: e.deltaY}; - case 0x01: // Line mode. + case 0x01: // Line mode. return { x: e.deltaX * LINE_MODE_MULTIPLIER, y: e.deltaY * LINE_MODE_MULTIPLIER, }; - case 0x02: // Page mode. + case 0x02: // Page mode. return { x: e.deltaX * PAGE_MODE_MULTIPLIER, y: e.deltaY * PAGE_MODE_MULTIPLIER, diff --git a/core/bubble.ts b/core/bubble.ts deleted file mode 100644 index c8fa2b0f3..000000000 --- a/core/bubble.ts +++ /dev/null @@ -1,908 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing a UI bubble. - * - * @class - */ -import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Bubble'); - -import type {BlockDragSurfaceSvg} from './block_drag_surface.js'; -import type {BlockSvg} from './block_svg.js'; -import * as browserEvents from './browser_events.js'; -import type {IBubble} from './interfaces/i_bubble.js'; -import type {ContainerRegion} from './metrics_manager.js'; -import {Scrollbar} from './scrollbar.js'; -import * as Touch from './touch.js'; -import {Coordinate} from './utils/coordinate.js'; -import * as dom from './utils/dom.js'; -import * as math from './utils/math.js'; -import {Size} from './utils/size.js'; -import {Svg} from './utils/svg.js'; -import * as userAgent from './utils/useragent.js'; -import type {WorkspaceSvg} from './workspace_svg.js'; - - -/** - * Class for UI bubble. - */ -export class Bubble implements IBubble { - /** Width of the border around the bubble. */ - static BORDER_WIDTH = 6; - - /** - * Determines the thickness of the base of the arrow in relation to the size - * of the bubble. Higher numbers result in thinner arrows. - */ - static ARROW_THICKNESS = 5; - - /** The number of degrees that the arrow bends counter-clockwise. */ - static ARROW_ANGLE = 20; - - /** - * The sharpness of the arrow's bend. Higher numbers result in smoother - * arrows. - */ - static ARROW_BEND = 4; - - /** Distance between arrow point and anchor point. */ - static ANCHOR_RADIUS = 8; - - /** Mouse up event data. */ - private static onMouseUpWrapper: browserEvents.Data|null = null; - - /** Mouse move event data. */ - private static onMouseMoveWrapper: browserEvents.Data|null = null; - - workspace_: WorkspaceSvg; - content_: SVGElement; - shape_: SVGElement; - - /** Flag to stop incremental rendering during construction. */ - private readonly rendered: boolean; - - /** The SVG group containing all parts of the bubble. */ - private bubbleGroup: SVGGElement|null = null; - - /** - * The SVG path for the arrow from the bubble to the icon on the block. - */ - private bubbleArrow: SVGPathElement|null = null; - - /** The SVG rect for the main body of the bubble. */ - private bubbleBack: SVGRectElement|null = null; - - /** The SVG group for the resize hash marks on some bubbles. */ - private resizeGroup: SVGGElement|null = null; - - /** Absolute coordinate of anchor point, in workspace coordinates. */ - private anchorXY!: Coordinate; - - /** - * Relative X coordinate of bubble with respect to the anchor's centre, - * in workspace units. - * In RTL mode the initial value is negated. - */ - private relativeLeft = 0; - - /** - * Relative Y coordinate of bubble with respect to the anchor's centre, in - * workspace units. - */ - private relativeTop = 0; - - /** Width of bubble, in workspace units. */ - private width = 0; - - /** Height of bubble, in workspace units. */ - private height = 0; - - /** Automatically position and reposition the bubble. */ - private autoLayout = true; - - /** Method to call on resize of bubble. */ - private resizeCallback: (() => void)|null = null; - - /** Method to call on move of bubble. */ - private moveCallback: (() => void)|null = null; - - /** Mouse down on bubbleBack event data. */ - private onMouseDownBubbleWrapper: browserEvents.Data|null = null; - - /** Mouse down on resizeGroup event data. */ - private onMouseDownResizeWrapper: browserEvents.Data|null = null; - - /** - * Describes whether this bubble has been disposed of (nodes and event - * listeners removed from the page) or not. - * - * @internal - */ - disposed = false; - private arrowRadians: number; - - /** - * @param workspace The workspace on which to draw the bubble. - * @param content SVG content for the bubble. - * @param shape SVG element to avoid eclipsing. - * @param anchorXY Absolute position of bubble's anchor point. - * @param bubbleWidth Width of bubble, or null if not resizable. - * @param bubbleHeight Height of bubble, or null if not resizable. - */ - constructor( - workspace: WorkspaceSvg, content: SVGElement, shape: SVGElement, - anchorXY: Coordinate, bubbleWidth: number|null, - bubbleHeight: number|null) { - this.rendered = false; - this.workspace_ = workspace; - this.content_ = content; - this.shape_ = shape; - - let angle = Bubble.ARROW_ANGLE; - if (this.workspace_.RTL) { - angle = -angle; - } - this.arrowRadians = math.toRadians(angle); - - const canvas = workspace.getBubbleCanvas(); - canvas.appendChild( - this.createDom(content, !!(bubbleWidth && bubbleHeight))); - - this.setAnchorLocation(anchorXY); - if (!bubbleWidth || !bubbleHeight) { - const bBox = (this.content_ as SVGGraphicsElement).getBBox(); - bubbleWidth = bBox.width + 2 * Bubble.BORDER_WIDTH; - bubbleHeight = bBox.height + 2 * Bubble.BORDER_WIDTH; - } - this.setBubbleSize(bubbleWidth, bubbleHeight); - - // Render the bubble. - this.positionBubble(); - this.renderArrow(); - this.rendered = true; - } - - /** - * Create the bubble's DOM. - * - * @param content SVG content for the bubble. - * @param hasResize Add diagonal resize gripper if true. - * @returns The bubble's SVG group. - */ - private createDom(content: Element, hasResize: boolean): SVGElement { - /* Create the bubble. Here's the markup that will be generated: - - - - - - - - - - - [...content goes here...] - - */ - this.bubbleGroup = dom.createSvgElement(Svg.G, {}); - let filter: {filter?: string} = { - 'filter': 'url(#' + - this.workspace_.getRenderer().getConstants().embossFilterId + ')', - }; - if (userAgent.JavaFx) { - // Multiple reports that JavaFX can't handle filters. - // https://github.com/google/blockly/issues/99 - filter = {}; - } - const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup); - this.bubbleArrow = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss); - this.bubbleBack = dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyDraggable', - 'x': 0, - 'y': 0, - 'rx': Bubble.BORDER_WIDTH, - 'ry': Bubble.BORDER_WIDTH, - }, - bubbleEmboss); - if (hasResize) { - this.resizeGroup = dom.createSvgElement( - Svg.G, { - 'class': this.workspace_.RTL ? 'blocklyResizeSW' : - 'blocklyResizeSE', - }, - this.bubbleGroup); - const size = 2 * Bubble.BORDER_WIDTH; - dom.createSvgElement( - Svg.POLYGON, {'points': `0,${size} ${size},${size} ${size},0`}, - this.resizeGroup); - dom.createSvgElement( - Svg.LINE, { - 'class': 'blocklyResizeLine', - 'x1': size / 3, - 'y1': size - 1, - 'x2': size - 1, - 'y2': size / 3, - }, - this.resizeGroup); - dom.createSvgElement( - Svg.LINE, { - 'class': 'blocklyResizeLine', - 'x1': size * 2 / 3, - 'y1': size - 1, - 'x2': size - 1, - 'y2': size * 2 / 3, - }, - this.resizeGroup); - } else { - this.resizeGroup = null; - } - - if (!this.workspace_.options.readOnly) { - this.onMouseDownBubbleWrapper = browserEvents.conditionalBind( - this.bubbleBack, 'pointerdown', this, this.bubbleMouseDown); - if (this.resizeGroup) { - this.onMouseDownResizeWrapper = browserEvents.conditionalBind( - this.resizeGroup, 'pointerdown', this, this.resizeMouseDown); - } - } - this.bubbleGroup.appendChild(content); - return this.bubbleGroup; - } - - /** - * Return the root node of the bubble's SVG group. - * - * @returns The root SVG node of the bubble's group. - */ - getSvgRoot(): SVGElement { - return this.bubbleGroup as SVGElement; - } - - /** - * Expose the block's ID on the bubble's top-level SVG group. - * - * @param id ID of block. - */ - setSvgId(id: string) { - this.bubbleGroup?.setAttribute('data-block-id', id); - } - - /** - * Handle a pointerdown on bubble's border. - * - * @param e Pointer down event. - */ - private bubbleMouseDown(e: PointerEvent) { - const gesture = this.workspace_.getGesture(e); - if (gesture) { - gesture.handleBubbleStart(e, this); - } - } - - /** - * Show the context menu for this bubble. - * - * @param _e Mouse event. - * @internal - */ - showContextMenu(_e: Event) {} - // NOP on bubbles, but used by the bubble dragger to pass events to - // workspace comments. - - /** - * Get whether this bubble is deletable or not. - * - * @returns True if deletable. - * @internal - */ - isDeletable(): boolean { - return false; - } - - /** - * Update the style of this bubble when it is dragged over a delete area. - * - * @param _enable True if the bubble is about to be deleted, false otherwise. - */ - setDeleteStyle(_enable: boolean) {} - // NOP if bubble is not deletable. - - /** - * Handle a pointerdown on bubble's resize corner. - * - * @param e Pointer down event. - */ - private resizeMouseDown(e: PointerEvent) { - this.promote(); - Bubble.unbindDragEvents(); - if (browserEvents.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } - // Left-click (or middle click) - this.workspace_.startDrag( - e, - new Coordinate( - this.workspace_.RTL ? -this.width : this.width, this.height)); - - Bubble.onMouseUpWrapper = browserEvents.conditionalBind( - document, 'pointerup', this, Bubble.bubbleMouseUp); - Bubble.onMouseMoveWrapper = browserEvents.conditionalBind( - document, 'pointermove', this, this.resizeMouseMove); - this.workspace_.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - } - - /** - * Resize this bubble to follow the pointer. - * - * @param e Pointer move event. - */ - private resizeMouseMove(e: PointerEvent) { - this.autoLayout = false; - const newXY = this.workspace_.moveDrag(e); - this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y); - if (this.workspace_.RTL) { - // RTL requires the bubble to move its left edge. - this.positionBubble(); - } - } - - /** - * Register a function as a callback event for when the bubble is resized. - * - * @param callback The function to call on resize. - */ - registerResizeEvent(callback: () => void) { - this.resizeCallback = callback; - } - - /** - * Register a function as a callback event for when the bubble is moved. - * - * @param callback The function to call on move. - */ - registerMoveEvent(callback: () => void) { - this.moveCallback = callback; - } - - /** - * Move this bubble to the top of the stack. - * - * @returns Whether or not the bubble has been moved. - * @internal - */ - promote(): boolean { - const svgGroup = this.bubbleGroup?.parentNode; - if (svgGroup?.lastChild !== this.bubbleGroup && this.bubbleGroup) { - svgGroup?.appendChild(this.bubbleGroup); - return true; - } - return false; - } - - /** - * Notification that the anchor has moved. - * Update the arrow and bubble accordingly. - * - * @param xy Absolute location. - */ - setAnchorLocation(xy: Coordinate) { - this.anchorXY = xy; - if (this.rendered) { - this.positionBubble(); - } - } - - /** Position the bubble so that it does not fall off-screen. */ - private layoutBubble() { - // Get the metrics in workspace units. - const viewMetrics = - this.workspace_.getMetricsManager().getViewMetrics(true); - - const optimalLeft = this.getOptimalRelativeLeft(viewMetrics); - const optimalTop = this.getOptimalRelativeTop(viewMetrics); - const bbox = (this.shape_ as SVGGraphicsElement).getBBox(); - - const topPosition = { - x: optimalLeft, - y: -this.height - - this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as - number, - }; - const startPosition = {x: -this.width - 30, y: optimalTop}; - const endPosition = {x: bbox.width, y: optimalTop}; - const bottomPosition = {x: optimalLeft, y: bbox.height}; - - const closerPosition = - bbox.width < bbox.height ? endPosition : bottomPosition; - const fartherPosition = - bbox.width < bbox.height ? bottomPosition : endPosition; - - const topPositionOverlap = this.getOverlap(topPosition, viewMetrics); - const startPositionOverlap = this.getOverlap(startPosition, viewMetrics); - const closerPositionOverlap = this.getOverlap(closerPosition, viewMetrics); - const fartherPositionOverlap = - this.getOverlap(fartherPosition, viewMetrics); - - // Set the position to whichever position shows the most of the bubble, - // with tiebreaks going in the order: top > start > close > far. - const mostOverlap = Math.max( - topPositionOverlap, startPositionOverlap, closerPositionOverlap, - fartherPositionOverlap); - if (topPositionOverlap === mostOverlap) { - this.relativeLeft = topPosition.x; - this.relativeTop = topPosition.y; - return; - } - if (startPositionOverlap === mostOverlap) { - this.relativeLeft = startPosition.x; - this.relativeTop = startPosition.y; - return; - } - if (closerPositionOverlap === mostOverlap) { - this.relativeLeft = closerPosition.x; - this.relativeTop = closerPosition.y; - return; - } - // TODO: I believe relativeLeft_ should actually be called relativeStart_ - // and then the math should be fixed to reflect this. (hopefully it'll - // make it look simpler) - this.relativeLeft = fartherPosition.x; - this.relativeTop = fartherPosition.y; - } - - /** - * Calculate the what percentage of the bubble overlaps with the visible - * workspace (what percentage of the bubble is visible). - * - * @param relativeMin The position of the top-left corner of the bubble - * relative to the anchor point. - * @param viewMetrics The view metrics of the workspace the bubble will appear - * in. - * @returns The percentage of the bubble that is visible. - */ - private getOverlap( - relativeMin: {x: number, y: number}, - viewMetrics: ContainerRegion): number { - // The position of the top-left corner of the bubble in workspace units. - const bubbleMin = { - x: this.workspace_.RTL ? this.anchorXY.x - relativeMin.x - this.width : - relativeMin.x + this.anchorXY.x, - y: relativeMin.y + this.anchorXY.y, - }; - // The position of the bottom-right corner of the bubble in workspace units. - const bubbleMax = { - x: bubbleMin.x + this.width, - y: bubbleMin.y + this.height, - }; - - // We could adjust these values to account for the scrollbars, but the - // bubbles should have been adjusted to not collide with them anyway, so - // giving the workspace a slightly larger "bounding box" shouldn't affect - // the calculation. - - // The position of the top-left corner of the workspace. - const workspaceMin = {x: viewMetrics.left, y: viewMetrics.top}; - // The position of the bottom-right corner of the workspace. - const workspaceMax = { - x: viewMetrics.left + viewMetrics.width, - y: viewMetrics.top + viewMetrics.height, - }; - - const overlapWidth = Math.min(bubbleMax.x, workspaceMax.x) - - Math.max(bubbleMin.x, workspaceMin.x); - const overlapHeight = Math.min(bubbleMax.y, workspaceMax.y) - - Math.max(bubbleMin.y, workspaceMin.y); - return Math.max( - 0, - Math.min(1, overlapWidth * overlapHeight / (this.width * this.height))); - } - - /** - * Calculate what the optimal horizontal position of the top-left corner of - * the bubble is (relative to the anchor point) so that the most area of the - * bubble is shown. - * - * @param viewMetrics The view metrics of the workspace the bubble will appear - * in. - * @returns The optimal horizontal position of the top-left corner of the - * bubble. - */ - private getOptimalRelativeLeft(viewMetrics: ContainerRegion): number { - let relativeLeft = -this.width / 4; - - // No amount of sliding left or right will give us a better overlap. - if (this.width > viewMetrics.width) { - return relativeLeft; - } - - if (this.workspace_.RTL) { - // Bubble coordinates are flipped in RTL. - const bubbleRight = this.anchorXY.x - relativeLeft; - const bubbleLeft = bubbleRight - this.width; - - const workspaceRight = viewMetrics.left + viewMetrics.width; - const workspaceLeft = viewMetrics.left + - // Thickness in workspace units. - Scrollbar.scrollbarThickness / this.workspace_.scale; - - if (bubbleLeft < workspaceLeft) { - // Slide the bubble right until it is onscreen. - relativeLeft = -(workspaceLeft - this.anchorXY.x + this.width); - } else if (bubbleRight > workspaceRight) { - // Slide the bubble left until it is onscreen. - relativeLeft = -(workspaceRight - this.anchorXY.x); - } - } else { - const bubbleLeft = relativeLeft + this.anchorXY.x; - const bubbleRight = bubbleLeft + this.width; - - const workspaceLeft = viewMetrics.left; - const workspaceRight = viewMetrics.left + viewMetrics.width - - // Thickness in workspace units. - Scrollbar.scrollbarThickness / this.workspace_.scale; - - if (bubbleLeft < workspaceLeft) { - // Slide the bubble right until it is onscreen. - relativeLeft = workspaceLeft - this.anchorXY.x; - } else if (bubbleRight > workspaceRight) { - // Slide the bubble left until it is onscreen. - relativeLeft = workspaceRight - this.anchorXY.x - this.width; - } - } - - return relativeLeft; - } - - /** - * Calculate what the optimal vertical position of the top-left corner of - * the bubble is (relative to the anchor point) so that the most area of the - * bubble is shown. - * - * @param viewMetrics The view metrics of the workspace the bubble will appear - * in. - * @returns The optimal vertical position of the top-left corner of the - * bubble. - */ - private getOptimalRelativeTop(viewMetrics: ContainerRegion): number { - let relativeTop = -this.height / 4; - - // No amount of sliding up or down will give us a better overlap. - if (this.height > viewMetrics.height) { - return relativeTop; - } - - const bubbleTop = this.anchorXY.y + relativeTop; - const bubbleBottom = bubbleTop + this.height; - const workspaceTop = viewMetrics.top; - const workspaceBottom = viewMetrics.top + - viewMetrics.height - // Thickness in workspace units. - Scrollbar.scrollbarThickness / this.workspace_.scale; - - const anchorY = this.anchorXY.y; - if (bubbleTop < workspaceTop) { - // Slide the bubble down until it is onscreen. - relativeTop = workspaceTop - anchorY; - } else if (bubbleBottom > workspaceBottom) { - // Slide the bubble up until it is onscreen. - relativeTop = workspaceBottom - anchorY - this.height; - } - - return relativeTop; - } - - /** Move the bubble to a location relative to the anchor's centre. */ - private positionBubble() { - let left = this.anchorXY.x; - if (this.workspace_.RTL) { - left -= this.relativeLeft + this.width; - } else { - left += this.relativeLeft; - } - const top = this.relativeTop + this.anchorXY.y; - this.moveTo(left, top); - } - - /** - * Move the bubble group to the specified location in workspace coordinates. - * - * @param x The x position to move to. - * @param y The y position to move to. - * @internal - */ - moveTo(x: number, y: number) { - this.bubbleGroup?.setAttribute( - 'transform', 'translate(' + x + ',' + y + ')'); - } - - /** - * Triggers a move callback if one exists at the end of a drag. - * - * @param adding True if adding, false if removing. - * @internal - */ - setDragging(adding: boolean) { - if (!adding && this.moveCallback) { - this.moveCallback(); - } - } - - /** - * Get the dimensions of this bubble. - * - * @returns The height and width of the bubble. - */ - getBubbleSize(): Size { - return new Size(this.width, this.height); - } - - /** - * Size this bubble. - * - * @param width Width of the bubble. - * @param height Height of the bubble. - */ - setBubbleSize(width: number, height: number) { - const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; - // Minimum size of a bubble. - width = Math.max(width, doubleBorderWidth + 45); - height = Math.max(height, doubleBorderWidth + 20); - this.width = width; - this.height = height; - this.bubbleBack?.setAttribute('width', `${width}`); - this.bubbleBack?.setAttribute('height', `${height}`); - if (this.resizeGroup) { - if (this.workspace_.RTL) { - // Mirror the resize group. - const resizeSize = 2 * Bubble.BORDER_WIDTH; - this.resizeGroup.setAttribute( - 'transform', - 'translate(' + resizeSize + ',' + (height - doubleBorderWidth) + - ') scale(-1 1)'); - } else { - this.resizeGroup.setAttribute( - 'transform', - 'translate(' + (width - doubleBorderWidth) + ',' + - (height - doubleBorderWidth) + ')'); - } - } - if (this.autoLayout) { - this.layoutBubble(); - } - this.positionBubble(); - this.renderArrow(); - - // Allow the contents to resize. - if (this.resizeCallback) { - this.resizeCallback(); - } - } - - /** Draw the arrow between the bubble and the origin. */ - private renderArrow() { - const steps = []; - // Find the relative coordinates of the center of the bubble. - const relBubbleX = this.width / 2; - const relBubbleY = this.height / 2; - // Find the relative coordinates of the center of the anchor. - let relAnchorX = -this.relativeLeft; - let relAnchorY = -this.relativeTop; - if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) { - // Null case. Bubble is directly on top of the anchor. - // Short circuit this rather than wade through divide by zeros. - steps.push('M ' + relBubbleX + ',' + relBubbleY); - } else { - // Compute the angle of the arrow's line. - const rise = relAnchorY - relBubbleY; - let run = relAnchorX - relBubbleX; - if (this.workspace_.RTL) { - run *= -1; - } - const hypotenuse = Math.sqrt(rise * rise + run * run); - let angle = Math.acos(run / hypotenuse); - if (rise < 0) { - angle = 2 * Math.PI - angle; - } - // Compute a line perpendicular to the arrow. - let rightAngle = angle + Math.PI / 2; - if (rightAngle > Math.PI * 2) { - rightAngle -= Math.PI * 2; - } - const rightRise = Math.sin(rightAngle); - const rightRun = Math.cos(rightAngle); - - // Calculate the thickness of the base of the arrow. - const bubbleSize = this.getBubbleSize(); - let thickness = - (bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS; - thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4; - - // Back the tip of the arrow off of the anchor. - const backoffRatio = 1 - Bubble.ANCHOR_RADIUS / hypotenuse; - relAnchorX = relBubbleX + backoffRatio * run; - relAnchorY = relBubbleY + backoffRatio * rise; - - // Coordinates for the base of the arrow. - const baseX1 = relBubbleX + thickness * rightRun; - const baseY1 = relBubbleY + thickness * rightRise; - const baseX2 = relBubbleX - thickness * rightRun; - const baseY2 = relBubbleY - thickness * rightRise; - - // Distortion to curve the arrow. - let swirlAngle = angle + this.arrowRadians; - if (swirlAngle > Math.PI * 2) { - swirlAngle -= Math.PI * 2; - } - const swirlRise = Math.sin(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; - const swirlRun = Math.cos(swirlAngle) * hypotenuse / Bubble.ARROW_BEND; - - steps.push('M' + baseX1 + ',' + baseY1); - steps.push( - 'C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + ' ' + - relAnchorX + ',' + relAnchorY + ' ' + relAnchorX + ',' + relAnchorY); - steps.push( - 'C' + relAnchorX + ',' + relAnchorY + ' ' + (baseX2 + swirlRun) + - ',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2); - } - steps.push('z'); - this.bubbleArrow?.setAttribute('d', steps.join(' ')); - } - - /** - * Change the colour of a bubble. - * - * @param hexColour Hex code of colour. - */ - setColour(hexColour: string) { - this.bubbleBack?.setAttribute('fill', hexColour); - this.bubbleArrow?.setAttribute('fill', hexColour); - } - - /** Dispose of this bubble. */ - dispose() { - if (this.onMouseDownBubbleWrapper) { - browserEvents.unbind(this.onMouseDownBubbleWrapper); - } - if (this.onMouseDownResizeWrapper) { - browserEvents.unbind(this.onMouseDownResizeWrapper); - } - Bubble.unbindDragEvents(); - dom.removeNode(this.bubbleGroup); - this.disposed = true; - } - - /** - * Move this bubble during a drag, taking into account whether or not there is - * a drag surface. - * - * @param dragSurface The surface that carries rendered items during a drag, - * or null if no drag surface is in use. - * @param newLoc The location to translate to, in workspace coordinates. - * @internal - */ - moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate) { - if (dragSurface) { - dragSurface.translateSurface(newLoc.x, newLoc.y); - } else { - this.moveTo(newLoc.x, newLoc.y); - } - if (this.workspace_.RTL) { - this.relativeLeft = this.anchorXY.x - newLoc.x - this.width; - } else { - this.relativeLeft = newLoc.x - this.anchorXY.x; - } - this.relativeTop = newLoc.y - this.anchorXY.y; - this.renderArrow(); - } - - /** - * Return the coordinates of the top-left corner of this bubble's body - * relative to the drawing surface's origin (0,0), in workspace units. - * - * @returns Object with .x and .y properties. - */ - getRelativeToSurfaceXY(): Coordinate { - return new Coordinate( - this.workspace_.RTL ? - -this.relativeLeft + this.anchorXY.x - this.width : - this.anchorXY.x + this.relativeLeft, - this.anchorXY.y + this.relativeTop); - } - - /** - * Set whether auto-layout of this bubble is enabled. The first time a bubble - * is shown it positions itself to not cover any blocks. Once a user has - * dragged it to reposition, it renders where the user put it. - * - * @param enable True if auto-layout should be enabled, false otherwise. - * @internal - */ - setAutoLayout(enable: boolean) { - this.autoLayout = enable; - } - - /** Stop binding to the global mouseup and mousemove events. */ - private static unbindDragEvents() { - if (Bubble.onMouseUpWrapper) { - browserEvents.unbind(Bubble.onMouseUpWrapper); - Bubble.onMouseUpWrapper = null; - } - if (Bubble.onMouseMoveWrapper) { - browserEvents.unbind(Bubble.onMouseMoveWrapper); - Bubble.onMouseMoveWrapper = null; - } - } - - /** - * Handle a pointerup event while dragging a bubble's border or resize handle. - * - * @param _e Pointer up event. - */ - private static bubbleMouseUp(_e: PointerEvent) { - Touch.clearTouchIdentifier(); - Bubble.unbindDragEvents(); - } - - /** - * Create the text for a non editable bubble. - * - * @param text The text to display. - * @returns The top-level node of the text. - * @internal - */ - static textToDom(text: string): SVGTextElement { - const paragraph = dom.createSvgElement(Svg.TEXT, { - 'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents', - 'y': Bubble.BORDER_WIDTH, - }); - const lines = text.split('\n'); - for (let i = 0; i < lines.length; i++) { - const tspanElement = dom.createSvgElement( - Svg.TSPAN, {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, paragraph); - const textNode = document.createTextNode(lines[i]); - tspanElement.appendChild(textNode); - } - return paragraph; - } - - /** - * Creates a bubble that can not be edited. - * - * @param paragraphElement The text element for the non editable bubble. - * @param block The block that the bubble is attached to. - * @param iconXY The coordinate of the icon. - * @returns The non editable bubble. - * @internal - */ - static createNonEditableBubble( - paragraphElement: SVGTextElement, block: BlockSvg, - iconXY: Coordinate): Bubble { - const bubble = new Bubble( - block.workspace!, paragraphElement, block.pathObject.svgPath, (iconXY), - null, null); - // Expose this bubble's block's ID on its top-level SVG group. - bubble.setSvgId(block.id); - if (block.RTL) { - // Right-align the paragraph. - // This cannot be done until the bubble is rendered on screen. - const maxWidth = paragraphElement.getBBox().width; - for (let i = 0, textElement; - textElement = paragraphElement.childNodes[i] as SVGTSpanElement; - i++) { - textElement.setAttribute('text-anchor', 'end'); - textElement.setAttribute('x', String(maxWidth + Bubble.BORDER_WIDTH)); - } - } - return bubble; - } -} diff --git a/core/bubble_dragger.ts b/core/bubble_dragger.ts index f960e399f..31abf6c7b 100644 --- a/core/bubble_dragger.ts +++ b/core/bubble_dragger.ts @@ -12,7 +12,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.BubbleDragger'); -import type {BlockDragSurfaceSvg} from './block_drag_surface.js'; import {ComponentManager} from './component_manager.js'; import type {CommentMove} from './events/events_comment_move.js'; import * as eventUtils from './events/utils.js'; @@ -23,7 +22,6 @@ import {Coordinate} from './utils/coordinate.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for a bubble dragger. It moves things on the bubble canvas around the * workspace when they are being dragged by a mouse or touch. These can be @@ -31,12 +29,11 @@ import type {WorkspaceSvg} from './workspace_svg.js'; */ export class BubbleDragger { /** Which drag target the mouse pointer is over, if any. */ - private dragTarget_: IDragTarget|null = null; + private dragTarget_: IDragTarget | null = null; /** Whether the bubble would be deleted if dropped immediately. */ private wouldDeleteBubble_ = false; private readonly startXY_: Coordinate; - private dragSurface_: BlockDragSurfaceSvg|null; /** * @param bubble The item on the bubble canvas to drag. @@ -48,16 +45,10 @@ export class BubbleDragger { * beginning of the drag, in workspace coordinates. */ this.startXY_ = this.bubble.getRelativeToSurfaceXY(); - - /** - * The drag surface to move bubbles to during a drag, or null if none should - * be used. Block dragging and bubble dragging use the same surface. - */ - this.dragSurface_ = workspace.getBlockDragSurface(); } /** - * Start dragging a bubble. This includes moving it to the drag surface. + * Start dragging a bubble. * * @internal */ @@ -67,12 +58,8 @@ export class BubbleDragger { } this.workspace.setResizesEnabled(false); - this.bubble.setAutoLayout(false); - if (this.dragSurface_) { - this.bubble.moveTo(0, 0); - this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y); - // Execute the move on the top-level SVG component. - this.dragSurface_.setBlocksAndShow(this.bubble.getSvgRoot()); + if ((this.bubble as AnyDuringMigration).setAutoLayout) { + (this.bubble as AnyDuringMigration).setAutoLayout(false); } this.bubble.setDragging && this.bubble.setDragging(true); @@ -90,7 +77,7 @@ export class BubbleDragger { dragBubble(e: PointerEvent, currentDragDeltaXY: Coordinate) { const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); const newLoc = Coordinate.sum(this.startXY_, delta); - this.bubble.moveDuringDrag(this.dragSurface_, newLoc); + this.bubble.moveDuringDrag(newLoc); const oldDragTarget = this.dragTarget_; this.dragTarget_ = this.workspace.getDragTarget(e); @@ -116,11 +103,13 @@ export class BubbleDragger { * @param dragTarget The drag target that the bubblee is currently over. * @returns Whether dropping the bubble immediately would delete the block. */ - private shouldDelete_(dragTarget: IDragTarget|null): boolean { + private shouldDelete_(dragTarget: IDragTarget | null): boolean { if (dragTarget) { const componentManager = this.workspace.getComponentManager(); const isDeleteArea = componentManager.hasCapability( - dragTarget.id, ComponentManager.Capability.DELETE_AREA); + dragTarget.id, + ComponentManager.Capability.DELETE_AREA + ); if (isDeleteArea) { return (dragTarget as IDeleteArea).wouldDelete(this.bubble, false); } @@ -149,7 +138,7 @@ export class BubbleDragger { this.dragBubble(e, currentDragDeltaXY); const preventMove = - this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble); + this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble); let newLoc; if (preventMove) { newLoc = this.startXY_; @@ -170,9 +159,6 @@ export class BubbleDragger { this.bubble.dispose(); } else { // Put everything back onto the bubble canvas. - if (this.dragSurface_) { - this.dragSurface_.clearAndHide(this.workspace.getBubbleCanvas()); - } if (this.bubble.setDragging) { this.bubble.setDragging(false); } @@ -187,7 +173,8 @@ export class BubbleDragger { private fireMoveEvent_() { if (this.bubble instanceof WorkspaceCommentSvg) { const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( - this.bubble) as CommentMove; + this.bubble + ) as CommentMove; event.setOldCoordinate(this.startXY_); event.recordNew(); eventUtils.fire(event); @@ -207,8 +194,9 @@ export class BubbleDragger { */ private pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate { const result = new Coordinate( - pixelCoord.x / this.workspace.scale, - pixelCoord.y / this.workspace.scale); + pixelCoord.x / this.workspace.scale, + pixelCoord.y / this.workspace.scale + ); if (this.workspace.isMutator) { // If we're in a mutator, its scale is always 1, purely because of some // oddities in our rendering optimizations. The actual scale is the same diff --git a/core/bubbles.ts b/core/bubbles.ts new file mode 100644 index 000000000..0c61741f9 --- /dev/null +++ b/core/bubbles.ts @@ -0,0 +1,12 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Bubble} from './bubbles/bubble.js'; +import {TextBubble} from './bubbles/text_bubble.js'; +import {TextInputBubble} from './bubbles/textinput_bubble.js'; +import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js'; + +export {Bubble, TextBubble, TextInputBubble, MiniWorkspaceBubble}; diff --git a/core/bubbles/bubble.ts b/core/bubbles/bubble.ts new file mode 100644 index 000000000..7eaee873e --- /dev/null +++ b/core/bubbles/bubble.ts @@ -0,0 +1,607 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as browserEvents from '../browser_events.js'; +import {IBubble} from '../interfaces/i_bubble.js'; +import {ContainerRegion} from '../metrics_manager.js'; +import {Scrollbar} from '../scrollbar.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import * as math from '../utils/math.js'; +import {Rect} from '../utils/rect.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; + +/** + * The abstract pop-up bubble class. This creates a UI that looks like a speech + * bubble, where it has a "tail" that points to the block, and a "head" that + * displays arbitrary svg elements. + */ +export abstract class Bubble implements IBubble { + /** The width of the border around the bubble. */ + static readonly BORDER_WIDTH = 6; + + /** Double the width of the border around the bubble. */ + static readonly DOUBLE_BORDER = this.BORDER_WIDTH * 2; + + /** The minimum size the bubble can have. */ + static readonly MIN_SIZE = this.DOUBLE_BORDER; + + /** + * The thickness of the base of the tail in relation to the size of the + * bubble. Higher numbers result in thinner tails. + */ + static readonly TAIL_THICKNESS = 1; + + /** The number of degrees that the tail bends counter-clockwise. */ + static readonly TAIL_ANGLE = 20; + + /** + * The sharpness of the tail's bend. Higher numbers result in smoother + * tails. + */ + static readonly TAIL_BEND = 4; + + /** Distance between arrow point and anchor point. */ + static readonly ANCHOR_RADIUS = 8; + + /** The SVG group containing all parts of the bubble. */ + protected svgRoot: SVGGElement; + + /** The SVG path for the arrow from the anchor to the bubble. */ + private tail: SVGPathElement; + + /** The SVG background rect for the main body of the bubble. */ + private background: SVGRectElement; + + /** The SVG group containing the contents of the bubble. */ + protected contentContainer: SVGGElement; + + /** + * The size of the bubble (including background and contents but not tail). + */ + private size = new Size(0, 0); + + /** The colour of the background of the bubble. */ + private colour = '#ffffff'; + + /** True if the bubble has been disposed, false otherwise. */ + public disposed = false; + + /** The position of the top of the bubble relative to its anchor. */ + private relativeTop = 0; + + /** The position of the left of the bubble realtive to its anchor. */ + private relativeLeft = 0; + + /** + * @param workspace The workspace this bubble belongs to. + * @param anchor The anchor location of the thing this bubble is attached to. + * The tail of the bubble will point to this location. + * @param ownerRect An optional rect we don't want the bubble to overlap with + * when automatically positioning. + */ + constructor( + protected readonly workspace: WorkspaceSvg, + protected anchor: Coordinate, + protected ownerRect?: Rect + ) { + this.svgRoot = dom.createSvgElement(Svg.G, {}, workspace.getBubbleCanvas()); + const embossGroup = dom.createSvgElement( + Svg.G, + { + 'filter': `url(#${ + this.workspace.getRenderer().getConstants().embossFilterId + })`, + }, + this.svgRoot + ); + this.tail = dom.createSvgElement(Svg.PATH, {}, embossGroup); + this.background = dom.createSvgElement( + Svg.RECT, + { + 'class': 'blocklyDraggable', + 'x': 0, + 'y': 0, + 'rx': Bubble.BORDER_WIDTH, + 'ry': Bubble.BORDER_WIDTH, + }, + embossGroup + ); + this.contentContainer = dom.createSvgElement(Svg.G, {}, this.svgRoot); + + browserEvents.conditionalBind( + this.background, + 'pointerdown', + this, + this.onMouseDown + ); + } + + /** Dispose of this bubble. */ + dispose() { + dom.removeNode(this.svgRoot); + this.disposed = true; + } + + /** + * Set the location the tail of this bubble points to. + * + * @param anchor The location the tail of this bubble points to. + * @param relayout If true, reposition the bubble from scratch so that it is + * optimally visible. If false, reposition it so it maintains the same + * position relative to the anchor. + */ + setAnchorLocation(anchor: Coordinate, relayout = false) { + this.anchor = anchor; + if (relayout) { + this.positionByRect(this.ownerRect); + } else { + this.positionRelativeToAnchor(); + } + this.renderTail(); + } + + /** Sets the position of this bubble relative to its anchor. */ + setPositionRelativeToAnchor(left: number, top: number) { + this.relativeLeft = left; + this.relativeTop = top; + this.positionRelativeToAnchor(); + this.renderTail(); + } + + /** @returns the size of this bubble. */ + protected getSize() { + return this.size; + } + + /** + * Sets the size of this bubble, including the border. + * + * @param size Sets the size of this bubble, including the border. + * @param relayout If true, reposition the bubble from scratch so that it is + * optimally visible. If false, reposition it so it maintains the same + * position relative to the anchor. + */ + protected setSize(size: Size, relayout = false) { + size.width = Math.max(size.width, Bubble.MIN_SIZE); + size.height = Math.max(size.height, Bubble.MIN_SIZE); + this.size = size; + + this.background.setAttribute('width', `${size.width}`); + this.background.setAttribute('height', `${size.height}`); + + if (relayout) { + this.positionByRect(this.ownerRect); + } else { + this.positionRelativeToAnchor(); + } + this.renderTail(); + } + + /** Returns the colour of the background and tail of this bubble. */ + protected getColour(): string { + return this.colour; + } + + /** Sets the colour of the background and tail of this bubble. */ + public setColour(colour: string) { + this.colour = colour; + this.tail.setAttribute('fill', colour); + this.background.setAttribute('fill', colour); + } + + /** Passes the pointer event off to the gesture system. */ + private onMouseDown(e: PointerEvent) { + this.workspace.getGesture(e)?.handleBubbleStart(e, this); + } + + /** Positions the bubble relative to its anchor. Does not render its tail. */ + protected positionRelativeToAnchor() { + let left = this.anchor.x; + if (this.workspace.RTL) { + left -= this.relativeLeft + this.size.width; + } else { + left += this.relativeLeft; + } + const top = this.relativeTop + this.anchor.y; + this.moveTo(left, top); + } + + /** + * Moves the bubble to the given coordinates. + * + * @internal + */ + moveTo(x: number, y: number) { + this.svgRoot.setAttribute('transform', `translate(${x}, ${y})`); + } + + /** + * Positions the bubble "optimally" so that the most of it is visible and + * it does not overlap the rect (if provided). + */ + protected positionByRect(rect = new Rect(0, 0, 0, 0)) { + const viewMetrics = this.workspace.getMetricsManager().getViewMetrics(true); + + const optimalLeft = this.getOptimalRelativeLeft(viewMetrics); + const optimalTop = this.getOptimalRelativeTop(viewMetrics); + + const topPosition = { + x: optimalLeft, + y: (-this.size.height - + this.workspace.getRenderer().getConstants().MIN_BLOCK_HEIGHT) as number, + }; + const startPosition = {x: -this.size.width - 30, y: optimalTop}; + const endPosition = {x: rect.getWidth(), y: optimalTop}; + const bottomPosition = {x: optimalLeft, y: rect.getHeight()}; + + const closerPosition = + rect.getWidth() < rect.getHeight() ? endPosition : bottomPosition; + const fartherPosition = + rect.getWidth() < rect.getHeight() ? bottomPosition : endPosition; + + const topPositionOverlap = this.getOverlap(topPosition, viewMetrics); + const startPositionOverlap = this.getOverlap(startPosition, viewMetrics); + const closerPositionOverlap = this.getOverlap(closerPosition, viewMetrics); + const fartherPositionOverlap = this.getOverlap( + fartherPosition, + viewMetrics + ); + + // Set the position to whichever position shows the most of the bubble, + // with tiebreaks going in the order: top > start > close > far. + const mostOverlap = Math.max( + topPositionOverlap, + startPositionOverlap, + closerPositionOverlap, + fartherPositionOverlap + ); + if (topPositionOverlap === mostOverlap) { + this.relativeLeft = topPosition.x; + this.relativeTop = topPosition.y; + this.positionRelativeToAnchor(); + return; + } + if (startPositionOverlap === mostOverlap) { + this.relativeLeft = startPosition.x; + this.relativeTop = startPosition.y; + this.positionRelativeToAnchor(); + return; + } + if (closerPositionOverlap === mostOverlap) { + this.relativeLeft = closerPosition.x; + this.relativeTop = closerPosition.y; + this.positionRelativeToAnchor(); + return; + } + // TODO: I believe relativeLeft_ should actually be called relativeStart_ + // and then the math should be fixed to reflect this. (hopefully it'll + // make it look simpler) + this.relativeLeft = fartherPosition.x; + this.relativeTop = fartherPosition.y; + this.positionRelativeToAnchor(); + } + + /** + * Calculate the what percentage of the bubble overlaps with the visible + * workspace (what percentage of the bubble is visible). + * + * @param relativeMin The position of the top-left corner of the bubble + * relative to the anchor point. + * @param viewMetrics The view metrics of the workspace the bubble will appear + * in. + * @returns The percentage of the bubble that is visible. + */ + private getOverlap( + relativeMin: {x: number; y: number}, + viewMetrics: ContainerRegion + ): number { + // The position of the top-left corner of the bubble in workspace units. + const bubbleMin = { + x: this.workspace.RTL + ? this.anchor.x - relativeMin.x - this.size.width + : relativeMin.x + this.anchor.x, + y: relativeMin.y + this.anchor.y, + }; + // The position of the bottom-right corner of the bubble in workspace units. + const bubbleMax = { + x: bubbleMin.x + this.size.width, + y: bubbleMin.y + this.size.height, + }; + + // We could adjust these values to account for the scrollbars, but the + // bubbles should have been adjusted to not collide with them anyway, so + // giving the workspace a slightly larger "bounding box" shouldn't affect + // the calculation. + + // The position of the top-left corner of the workspace. + const workspaceMin = {x: viewMetrics.left, y: viewMetrics.top}; + // The position of the bottom-right corner of the workspace. + const workspaceMax = { + x: viewMetrics.left + viewMetrics.width, + y: viewMetrics.top + viewMetrics.height, + }; + + const overlapWidth = + Math.min(bubbleMax.x, workspaceMax.x) - + Math.max(bubbleMin.x, workspaceMin.x); + const overlapHeight = + Math.min(bubbleMax.y, workspaceMax.y) - + Math.max(bubbleMin.y, workspaceMin.y); + return Math.max( + 0, + Math.min( + 1, + (overlapWidth * overlapHeight) / (this.size.width * this.size.height) + ) + ); + } + + /** + * Calculate what the optimal horizontal position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * + * @param viewMetrics The view metrics of the workspace the bubble will appear + * in. + * @returns The optimal horizontal position of the top-left corner of the + * bubble. + */ + private getOptimalRelativeLeft(viewMetrics: ContainerRegion): number { + // By default, show the bubble just a bit to the left of the anchor. + let relativeLeft = -this.size.width / 4; + + // No amount of sliding left or right will give us better overlap. + if (this.size.width > viewMetrics.width) return relativeLeft; + + const workspaceRect = this.getWorkspaceViewRect(viewMetrics); + + if (this.workspace.RTL) { + // Bubble coordinates are flipped in RTL. + const bubbleRight = this.anchor.x - relativeLeft; + const bubbleLeft = bubbleRight - this.size.width; + + if (bubbleLeft < workspaceRect.left) { + // Slide the bubble right until it is onscreen. + relativeLeft = -(workspaceRect.left - this.anchor.x + this.size.width); + } else if (bubbleRight > workspaceRect.right) { + // Slide the bubble left until it is onscreen. + relativeLeft = -(workspaceRect.right - this.anchor.x); + } + } else { + const bubbleLeft = relativeLeft + this.anchor.x; + const bubbleRight = bubbleLeft + this.size.width; + + if (bubbleLeft < workspaceRect.left) { + // Slide the bubble right until it is onscreen. + relativeLeft = workspaceRect.left - this.anchor.x; + } else if (bubbleRight > workspaceRect.right) { + // Slide the bubble left until it is onscreen. + relativeLeft = workspaceRect.right - this.anchor.x - this.size.width; + } + } + + return relativeLeft; + } + + /** + * Calculate what the optimal vertical position of the top-left corner of + * the bubble is (relative to the anchor point) so that the most area of the + * bubble is shown. + * + * @param viewMetrics The view metrics of the workspace the bubble will appear + * in. + * @returns The optimal vertical position of the top-left corner of the + * bubble. + */ + private getOptimalRelativeTop(viewMetrics: ContainerRegion): number { + // By default, show the bubble just a bit higher than the anchor. + let relativeTop = -this.size.height / 4; + + // No amount of sliding up or down will give us better overlap. + if (this.size.height > viewMetrics.height) return relativeTop; + + const top = this.anchor.y + relativeTop; + const bottom = top + this.size.height; + const workspaceRect = this.getWorkspaceViewRect(viewMetrics); + + if (top < workspaceRect.top) { + // Slide the bubble down until it is onscreen. + relativeTop = workspaceRect.top - this.anchor.y; + } else if (bottom > workspaceRect.bottom) { + // Slide the bubble up until it is onscreen. + relativeTop = workspaceRect.bottom - this.anchor.y - this.size.height; + } + + return relativeTop; + } + + /** + * @returns a rect defining the bounds of the workspace's view in workspace + * coordinates. + */ + private getWorkspaceViewRect(viewMetrics: ContainerRegion): Rect { + const top = viewMetrics.top; + let bottom = viewMetrics.top + viewMetrics.height; + let left = viewMetrics.left; + let right = viewMetrics.left + viewMetrics.width; + + bottom -= this.getScrollbarThickness(); + if (this.workspace.RTL) { + left -= this.getScrollbarThickness(); + } else { + right -= this.getScrollbarThickness(); + } + + return new Rect(top, bottom, left, right); + } + + /** @returns the scrollbar thickness in workspace units. */ + private getScrollbarThickness() { + return Scrollbar.scrollbarThickness / this.workspace.scale; + } + + /** Draws the tail of the bubble. */ + private renderTail() { + const steps = []; + // Find the relative coordinates of the center of the bubble. + const relBubbleX = this.size.width / 2; + const relBubbleY = this.size.height / 2; + // Find the relative coordinates of the center of the anchor. + let relAnchorX = -this.relativeLeft; + let relAnchorY = -this.relativeTop; + if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) { + // Null case. Bubble is directly on top of the anchor. + // Short circuit this rather than wade through divide by zeros. + steps.push('M ' + relBubbleX + ',' + relBubbleY); + } else { + // Compute the angle of the tail's line. + const rise = relAnchorY - relBubbleY; + let run = relAnchorX - relBubbleX; + if (this.workspace.RTL) { + run *= -1; + } + const hypotenuse = Math.sqrt(rise * rise + run * run); + let angle = Math.acos(run / hypotenuse); + if (rise < 0) { + angle = 2 * Math.PI - angle; + } + // Compute a line perpendicular to the tail. + let rightAngle = angle + Math.PI / 2; + if (rightAngle > Math.PI * 2) { + rightAngle -= Math.PI * 2; + } + const rightRise = Math.sin(rightAngle); + const rightRun = Math.cos(rightAngle); + + // Calculate the thickness of the base of the tail. + let thickness = + (this.size.width + this.size.height) / Bubble.TAIL_THICKNESS; + thickness = Math.min(thickness, this.size.width, this.size.height) / 4; + + // Back the tip of the tail off of the anchor. + const backoffRatio = 1 - Bubble.ANCHOR_RADIUS / hypotenuse; + relAnchorX = relBubbleX + backoffRatio * run; + relAnchorY = relBubbleY + backoffRatio * rise; + + // Coordinates for the base of the tail. + const baseX1 = relBubbleX + thickness * rightRun; + const baseY1 = relBubbleY + thickness * rightRise; + const baseX2 = relBubbleX - thickness * rightRun; + const baseY2 = relBubbleY - thickness * rightRise; + + // Distortion to curve the tail. + const radians = math.toRadians( + this.workspace.RTL ? -Bubble.TAIL_ANGLE : Bubble.TAIL_ANGLE + ); + let swirlAngle = angle + radians; + if (swirlAngle > Math.PI * 2) { + swirlAngle -= Math.PI * 2; + } + const swirlRise = (Math.sin(swirlAngle) * hypotenuse) / Bubble.TAIL_BEND; + const swirlRun = (Math.cos(swirlAngle) * hypotenuse) / Bubble.TAIL_BEND; + + steps.push('M' + baseX1 + ',' + baseY1); + steps.push( + 'C' + + (baseX1 + swirlRun) + + ',' + + (baseY1 + swirlRise) + + ' ' + + relAnchorX + + ',' + + relAnchorY + + ' ' + + relAnchorX + + ',' + + relAnchorY + ); + steps.push( + 'C' + + relAnchorX + + ',' + + relAnchorY + + ' ' + + (baseX2 + swirlRun) + + ',' + + (baseY2 + swirlRise) + + ' ' + + baseX2 + + ',' + + baseY2 + ); + } + steps.push('z'); + this.tail?.setAttribute('d', steps.join(' ')); + } + /** + * Move this bubble to the front of the visible workspace. + * + * @returns Whether or not the bubble has been moved. + * @internal + */ + bringToFront(): boolean { + const svgGroup = this.svgRoot?.parentNode; + if (this.svgRoot && svgGroup?.lastChild !== this.svgRoot) { + svgGroup?.appendChild(this.svgRoot); + return true; + } + return false; + } + + /** @internal */ + getRelativeToSurfaceXY(): Coordinate { + return new Coordinate( + this.workspace.RTL + ? -this.relativeLeft + this.anchor.x - this.size.width + : this.anchor.x + this.relativeLeft, + this.anchor.y + this.relativeTop + ); + } + + /** @internal */ + getSvgRoot(): SVGElement { + return this.svgRoot; + } + + /** + * Move this bubble during a drag. + * + * @param newLoc The location to translate to, in workspace coordinates. + * @internal + */ + moveDuringDrag(newLoc: Coordinate) { + this.moveTo(newLoc.x, newLoc.y); + if (this.workspace.RTL) { + this.relativeLeft = this.anchor.x - newLoc.x - this.size.width; + } else { + this.relativeLeft = newLoc.x - this.anchor.x; + } + this.relativeTop = newLoc.y - this.anchor.y; + this.renderTail(); + } + + setDragging(_start: boolean) { + // NOOP in base class. + } + + /** @internal */ + setDeleteStyle(_enable: boolean) { + // NOOP in base class. + } + + /** @internal */ + isDeletable(): boolean { + return false; + } + + /** @internal */ + showContextMenu(_e: Event) { + // NOOP in base class. + } +} diff --git a/core/bubbles/mini_workspace_bubble.ts b/core/bubbles/mini_workspace_bubble.ts new file mode 100644 index 000000000..7d3877b9a --- /dev/null +++ b/core/bubbles/mini_workspace_bubble.ts @@ -0,0 +1,282 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Abstract as AbstractEvent} from '../events/events_abstract.js'; +import type {BlocklyOptions} from '../blockly_options.js'; +import {Bubble} from './bubble.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import {Options} from '../options.js'; +import {Svg} from '../utils/svg.js'; +import type {Rect} from '../utils/rect.js'; +import {Size} from '../utils/size.js'; +import type {WorkspaceSvg} from '../workspace_svg.js'; + +/** + * A bubble that contains a mini-workspace which can hold arbitrary blocks. + * Used by the mutator icon. + */ +export class MiniWorkspaceBubble extends Bubble { + /** + * The minimum amount of change to the mini workspace view to trigger + * resizing the bubble. + */ + private static readonly MINIMUM_VIEW_CHANGE = 10; + + /** + * An arbitrary margin of whitespace to put around the blocks in the + * workspace. + */ + private static readonly MARGIN = Bubble.DOUBLE_BORDER * 3; + + /** The root svg element containing the workspace. */ + private svgDialog: SVGElement; + + /** The workspace that gets shown within this bubble. */ + private miniWorkspace: WorkspaceSvg; + + /** + * Should this bubble automatically reposition itself when it resizes? + * Becomes false after this bubble is first dragged. + */ + private autoLayout = true; + + /** @internal */ + constructor( + workspaceOptions: BlocklyOptions, + protected readonly workspace: WorkspaceSvg, + protected anchor: Coordinate, + protected ownerRect?: Rect + ) { + super(workspace, anchor, ownerRect); + const options = new Options(workspaceOptions); + this.validateWorkspaceOptions(options); + + this.svgDialog = dom.createSvgElement( + Svg.SVG, + { + 'x': Bubble.BORDER_WIDTH, + 'y': Bubble.BORDER_WIDTH, + }, + this.contentContainer + ); + workspaceOptions.parentWorkspace = this.workspace; + this.miniWorkspace = this.newWorkspaceSvg(new Options(workspaceOptions)); + const background = this.miniWorkspace.createDom('blocklyMutatorBackground'); + this.svgDialog.appendChild(background); + if (options.languageTree) { + background.insertBefore( + this.miniWorkspace.addFlyout(Svg.G), + this.miniWorkspace.getCanvas() + ); + const flyout = this.miniWorkspace.getFlyout(); + flyout?.init(this.miniWorkspace); + flyout?.show(options.languageTree); + } + + this.miniWorkspace.addChangeListener(this.onWorkspaceChange.bind(this)); + this.miniWorkspace + .getFlyout() + ?.getWorkspace() + ?.addChangeListener(this.onWorkspaceChange.bind(this)); + this.updateBubbleSize(); + } + + dispose() { + this.miniWorkspace.dispose(); + super.dispose(); + } + + /** @internal */ + getWorkspace(): WorkspaceSvg { + return this.miniWorkspace; + } + + /** Adds a change listener to the mini workspace. */ + addWorkspaceChangeListener(listener: (e: AbstractEvent) => void) { + this.miniWorkspace.addChangeListener(listener); + } + + /** + * Validates the workspace options to make sure folks aren't trying to + * enable things the miniworkspace doesn't support. + */ + private validateWorkspaceOptions(options: Options) { + if (options.hasCategories) { + throw new Error( + 'The miniworkspace bubble does not support toolboxes with categories' + ); + } + if (options.hasTrashcan) { + throw new Error('The miniworkspace bubble does not support trashcans'); + } + if ( + options.zoomOptions.controls || + options.zoomOptions.wheel || + options.zoomOptions.pinch + ) { + throw new Error('The miniworkspace bubble does not support zooming'); + } + if ( + options.moveOptions.scrollbars || + options.moveOptions.wheel || + options.moveOptions.drag + ) { + throw new Error( + 'The miniworkspace bubble does not scrolling/moving the workspace' + ); + } + if (options.horizontalLayout) { + throw new Error( + 'The miniworkspace bubble does not support horizontal layouts' + ); + } + } + + private onWorkspaceChange() { + this.bumpBlocksIntoBounds(); + this.updateBubbleSize(); + } + + /** + * Bumps blocks that are above the top or outside the start-side of the + * workspace back within the workspace. + * + * Blocks that are below the bottom or outside the end-side of the workspace + * are dealt with by resizing the workspace to show them. + */ + private bumpBlocksIntoBounds() { + if (this.miniWorkspace.isDragging()) return; + + const MARGIN = 20; + + for (const block of this.miniWorkspace.getTopBlocks(false)) { + const blockXY = block.getRelativeToSurfaceXY(); + + // Bump any block that's above the top back inside. + if (blockXY.y < MARGIN) { + block.moveBy(0, MARGIN - blockXY.y); + } + // Bump any block overlapping the flyout back inside. + if (block.RTL) { + let right = -MARGIN; + const flyout = this.miniWorkspace.getFlyout(); + if (flyout) { + right -= flyout.getWidth(); + } + if (blockXY.x > right) { + block.moveBy(right - blockXY.x, 0); + } + } else if (blockXY.x < MARGIN) { + block.moveBy(MARGIN - blockXY.x, 0); + } + } + } + + /** + * Updates the size of this bubble to account for the size of the + * mini workspace. + */ + private updateBubbleSize() { + if (this.miniWorkspace.isDragging()) return; + + const currSize = this.getSize(); + const newSize = this.calculateWorkspaceSize(); + if ( + Math.abs(currSize.width - newSize.width) < + MiniWorkspaceBubble.MINIMUM_VIEW_CHANGE && + Math.abs(currSize.height - newSize.height) < + MiniWorkspaceBubble.MINIMUM_VIEW_CHANGE + ) { + // Only resize if the size has noticeably changed. + return; + } + this.svgDialog.setAttribute('width', `${newSize.width}px`); + this.svgDialog.setAttribute('height', `${newSize.height}px`); + this.miniWorkspace.setCachedParentSvgSize(newSize.width, newSize.height); + if (this.miniWorkspace.RTL) { + // Scroll the workspace to always left-align. + this.miniWorkspace + .getCanvas() + .setAttribute('transform', `translate(${newSize.width}, 0)`); + } + this.setSize( + new Size( + newSize.width + Bubble.DOUBLE_BORDER, + newSize.height + Bubble.DOUBLE_BORDER + ), + this.autoLayout + ); + this.miniWorkspace.resize(); + this.miniWorkspace.recordDragTargets(); + } + + /** + * Calculates the size of the mini workspace for use in resizing the bubble. + */ + private calculateWorkspaceSize(): Size { + // TODO (#7104): Clean this up to be more readable and unified for RTL + // vs LTR. + const canvas = this.miniWorkspace.getCanvas(); + const workspaceSize = canvas.getBBox(); + let width = workspaceSize.width + workspaceSize.x; + let height = workspaceSize.height + MiniWorkspaceBubble.MARGIN; + const flyout = this.miniWorkspace.getFlyout(); + if (flyout) { + const flyoutScrollMetrics = flyout + .getWorkspace() + .getMetricsManager() + .getScrollMetrics(); + height = Math.max(height, flyoutScrollMetrics.height + 20); + width += flyout.getWidth(); + } + if (this.miniWorkspace.RTL) { + width = -workspaceSize.x; + } + width += MiniWorkspaceBubble.MARGIN; + return new Size(width, height); + } + + /** Reapplies styles to all of the blocks in the mini workspace. */ + updateBlockStyles() { + for (const block of this.miniWorkspace.getAllBlocks(false)) { + block.setStyle(block.getStyleName()); + } + + const flyoutWs = this.miniWorkspace.getFlyout()?.getWorkspace(); + if (flyoutWs) { + for (const block of flyoutWs.getAllBlocks(false)) { + block.setStyle(block.getStyleName()); + } + } + } + + /** + * Move this bubble during a drag. + * + * @param newLoc The location to translate to, in workspace coordinates. + * @internal + */ + moveDuringDrag(newLoc: Coordinate): void { + super.moveDuringDrag(newLoc); + this.autoLayout = false; + } + + /** @internal */ + moveTo(x: number, y: number): void { + super.moveTo(x, y); + this.miniWorkspace.recordDragTargets(); + } + + /** @internal */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + newWorkspaceSvg(options: Options): WorkspaceSvg { + throw new Error( + 'The implementation of newWorkspaceSvg should be ' + + 'monkey-patched in by blockly.ts' + ); + } +} diff --git a/core/bubbles/text_bubble.ts b/core/bubbles/text_bubble.ts new file mode 100644 index 000000000..ecbe5a200 --- /dev/null +++ b/core/bubbles/text_bubble.ts @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Bubble} from './bubble.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import {Rect} from '../utils/rect.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; + +/** + * A bubble that displays non-editable text. Used by the warning icon. + */ +export class TextBubble extends Bubble { + private paragraph: SVGTextElement; + + constructor( + private text: string, + protected readonly workspace: WorkspaceSvg, + protected anchor: Coordinate, + protected ownerRect?: Rect + ) { + super(workspace, anchor, ownerRect); + this.paragraph = this.stringToSvg(text, this.contentContainer); + this.updateBubbleSize(); + } + + /** @returns the current text of this text bubble. */ + getText(): string { + return this.text; + } + + /** Sets the current text of this text bubble, and updates the display. */ + setText(text: string) { + this.text = text; + dom.removeNode(this.paragraph); + this.paragraph = this.stringToSvg(text, this.contentContainer); + this.updateBubbleSize(); + } + + /** + * Converts the given string into an svg containing that string, + * broken up by newlines. + */ + private stringToSvg(text: string, container: SVGGElement) { + const paragraph = this.createParagraph(container); + const spans = this.createSpans(paragraph, text); + if (this.workspace.RTL) + this.rightAlignSpans(paragraph.getBBox().width, spans); + return paragraph; + } + + /** Creates the paragraph container for this bubble's view's spans. */ + private createParagraph(container: SVGGElement): SVGTextElement { + return dom.createSvgElement( + Svg.TEXT, + { + 'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents', + 'y': Bubble.BORDER_WIDTH, + }, + container + ); + } + + /** Creates the spans visualizing the text of this bubble. */ + private createSpans(parent: SVGTextElement, text: string): SVGTSpanElement[] { + return text.split('\n').map((line) => { + const tspan = dom.createSvgElement( + Svg.TSPAN, + {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, + parent + ); + const textNode = document.createTextNode(line); + tspan.appendChild(textNode); + return tspan; + }); + } + + /** Right aligns the given spans. */ + private rightAlignSpans(maxWidth: number, spans: SVGTSpanElement[]) { + for (const span of spans) { + span.setAttribute('text-anchor', 'end'); + span.setAttribute('x', `${maxWidth + Bubble.BORDER_WIDTH}`); + } + } + + /** Updates the size of this bubble to account for the size of the text. */ + private updateBubbleSize() { + const bbox = this.paragraph.getBBox(); + this.setSize( + new Size( + bbox.width + Bubble.BORDER_WIDTH * 2, + bbox.height + Bubble.BORDER_WIDTH * 2 + ), + true + ); + } +} diff --git a/core/bubbles/textinput_bubble.ts b/core/bubbles/textinput_bubble.ts new file mode 100644 index 000000000..5f86e235b --- /dev/null +++ b/core/bubbles/textinput_bubble.ts @@ -0,0 +1,343 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Bubble} from './bubble.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as Css from '../css.js'; +import * as dom from '../utils/dom.js'; +import {Rect} from '../utils/rect.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import * as touch from '../touch.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; +import {browserEvents} from '../utils.js'; + +/** + * A bubble that displays editable text. It can also be resized by the user. + * Used by the comment icon. + */ +export class TextInputBubble extends Bubble { + /** The root of the elements specific to the text element. */ + private inputRoot: SVGForeignObjectElement; + + /** The text input area element. */ + private textArea: HTMLTextAreaElement; + + /** The group containing the lines indicating the bubble is resizable. */ + private resizeGroup: SVGGElement; + + /** + * Event data associated with the listener for pointer up events on the + * resize group. + */ + private resizePointerUpListener: browserEvents.Data | null = null; + + /** + * Event data associated with the listener for pointer move events on the + * resize group. + */ + private resizePointerMoveListener: browserEvents.Data | null = null; + + /** Functions listening for changes to the text of this bubble. */ + private textChangeListeners: (() => void)[] = []; + + /** Functions listening for changes to the size of this bubble. */ + private sizeChangeListeners: (() => void)[] = []; + + /** The text of this bubble. */ + private text = ''; + + /** The default size of this bubble, including borders. */ + private readonly DEFAULT_SIZE = new Size( + 160 + Bubble.DOUBLE_BORDER, + 80 + Bubble.DOUBLE_BORDER + ); + + /** The minimum size of this bubble, including borders. */ + private readonly MIN_SIZE = new Size( + 45 + Bubble.DOUBLE_BORDER, + 20 + Bubble.DOUBLE_BORDER + ); + + /** + * @param workspace The workspace this bubble belongs to. + * @param anchor The anchor location of the thing this bubble is attached to. + * The tail of the bubble will point to this location. + * @param ownerRect An optional rect we don't want the bubble to overlap with + * when automatically positioning. + */ + constructor( + protected readonly workspace: WorkspaceSvg, + protected anchor: Coordinate, + protected ownerRect?: Rect + ) { + super(workspace, anchor, ownerRect); + ({inputRoot: this.inputRoot, textArea: this.textArea} = this.createEditor( + this.contentContainer + )); + this.resizeGroup = this.createResizeHandle(this.svgRoot); + this.setSize(this.DEFAULT_SIZE, true); + } + + /** @returns the text of this bubble. */ + getText(): string { + return this.text; + } + + /** Sets the text of this bubble. Calls change listeners. */ + setText(text: string) { + this.text = text; + this.textArea.value = text; + this.onTextChange(); + } + + /** Adds a change listener to be notified when this bubble's text changes. */ + addTextChangeListener(listener: () => void) { + this.textChangeListeners.push(listener); + } + + /** Adds a change listener to be notified when this bubble's size changes. */ + addSizeChangeListener(listener: () => void) { + this.sizeChangeListeners.push(listener); + } + + /** Creates the editor UI for this bubble. */ + private createEditor(container: SVGGElement): { + inputRoot: SVGForeignObjectElement; + textArea: HTMLTextAreaElement; + } { + const inputRoot = dom.createSvgElement( + Svg.FOREIGNOBJECT, + { + 'x': Bubble.BORDER_WIDTH, + 'y': Bubble.BORDER_WIDTH, + }, + container + ); + + const body = document.createElementNS(dom.HTML_NS, 'body'); + body.setAttribute('xmlns', dom.HTML_NS); + body.className = 'blocklyMinimalBody'; + + const textArea = document.createElementNS( + dom.HTML_NS, + 'textarea' + ) as HTMLTextAreaElement; + textArea.className = 'blocklyCommentTextarea'; + textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR'); + + body.appendChild(textArea); + inputRoot.appendChild(body); + + this.bindTextAreaEvents(textArea); + setTimeout(() => { + textArea.focus(); + }, 0); + + return {inputRoot, textArea}; + } + + /** Binds events to the text area element. */ + private bindTextAreaEvents(textArea: HTMLTextAreaElement) { + // Don't zoom with mousewheel. + browserEvents.conditionalBind(textArea, 'wheel', this, (e: Event) => { + e.stopPropagation(); + }); + + browserEvents.conditionalBind( + textArea, + 'focus', + this, + this.onStartEdit, + true + ); + browserEvents.conditionalBind(textArea, 'change', this, this.onTextChange); + } + + /** Creates the resize handler elements and binds events to them. */ + private createResizeHandle(container: SVGGElement): SVGGElement { + const resizeGroup = dom.createSvgElement( + Svg.G, + { + 'class': this.workspace.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE', + }, + container + ); + const size = 2 * Bubble.BORDER_WIDTH; + dom.createSvgElement( + Svg.POLYGON, + {'points': `0,${size} ${size},${size} ${size},0`}, + resizeGroup + ); + dom.createSvgElement( + Svg.LINE, + { + 'class': 'blocklyResizeLine', + 'x1': size / 3, + 'y1': size - 1, + 'x2': size - 1, + 'y2': size / 3, + }, + resizeGroup + ); + dom.createSvgElement( + Svg.LINE, + { + 'class': 'blocklyResizeLine', + 'x1': (size * 2) / 3, + 'y1': size - 1, + 'x2': size - 1, + 'y2': (size * 2) / 3, + }, + resizeGroup + ); + + browserEvents.conditionalBind( + resizeGroup, + 'pointerdown', + this, + this.onResizePointerDown + ); + + return resizeGroup; + } + + /** + * Sets the size of this bubble, including the border. + * + * @param size Sets the size of this bubble, including the border. + * @param relayout If true, reposition the bubble from scratch so that it is + * optimally visible. If false, reposition it so it maintains the same + * position relative to the anchor. + */ + setSize(size: Size, relayout = false) { + size.width = Math.max(size.width, this.MIN_SIZE.width); + size.height = Math.max(size.height, this.MIN_SIZE.height); + + const widthMinusBorder = size.width - Bubble.DOUBLE_BORDER; + const heightMinusBorder = size.height - Bubble.DOUBLE_BORDER; + this.inputRoot.setAttribute('width', `${widthMinusBorder}`); + this.inputRoot.setAttribute('height', `${heightMinusBorder}`); + this.textArea.style.width = `${widthMinusBorder - 4}px`; + this.textArea.style.height = `${heightMinusBorder - 4}px`; + + if (this.workspace.RTL) { + this.resizeGroup.setAttribute( + 'transform', + `translate(${Bubble.DOUBLE_BORDER}, ${heightMinusBorder}) scale(-1 1)` + ); + } else { + this.resizeGroup.setAttribute( + 'transform', + `translate(${widthMinusBorder}, ${heightMinusBorder})` + ); + } + + super.setSize(size, relayout); + this.onSizeChange(); + } + + /** @returns the size of this bubble. */ + getSize(): Size { + // Overriden to be public. + return super.getSize(); + } + + /** Handles mouse down events on the resize target. */ + private onResizePointerDown(e: PointerEvent) { + this.bringToFront(); + if (browserEvents.isRightButton(e)) { + e.stopPropagation(); + return; + } + + this.workspace.startDrag( + e, + new Coordinate( + this.workspace.RTL ? -this.getSize().width : this.getSize().width, + this.getSize().height + ) + ); + + this.resizePointerUpListener = browserEvents.conditionalBind( + document, + 'pointerup', + this, + this.onResizePointerUp + ); + this.resizePointerMoveListener = browserEvents.conditionalBind( + document, + 'pointermove', + this, + this.onResizePointerMove + ); + this.workspace.hideChaff(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); + } + + /** Handles pointer up events on the resize target. */ + private onResizePointerUp(_e: PointerEvent) { + touch.clearTouchIdentifier(); + if (this.resizePointerUpListener) { + browserEvents.unbind(this.resizePointerUpListener); + this.resizePointerUpListener = null; + } + if (this.resizePointerMoveListener) { + browserEvents.unbind(this.resizePointerMoveListener); + this.resizePointerMoveListener = null; + } + } + + /** Handles pointer move events on the resize target. */ + private onResizePointerMove(e: PointerEvent) { + const delta = this.workspace.moveDrag(e); + this.setSize( + new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y), + false + ); + this.onSizeChange(); + } + + /** + * Handles starting an edit of the text area. Brings the bubble to the front. + */ + private onStartEdit() { + if (this.bringToFront()) { + // Since the act of moving this node within the DOM causes a loss of + // focus, we need to reapply the focus. + this.textArea.focus(); + } + } + + /** Handles a text change event for the text area. Calls event listeners. */ + private onTextChange() { + this.text = this.textArea.value; + for (const listener of this.textChangeListeners) { + listener(); + } + } + + /** Handles a size change event for the text area. Calls event listeners. */ + private onSizeChange() { + for (const listener of this.sizeChangeListeners) { + listener(); + } + } +} + +Css.register(` +.blocklyCommentTextarea { + background-color: #fef49c; + border: 0; + display: block; + margin: 0; + outline: 0; + padding: 3px; + resize: none; + text-overflow: hidden; +} +`); diff --git a/core/bump_objects.ts b/core/bump_objects.ts index 7396b7a0e..7f4816791 100644 --- a/core/bump_objects.ts +++ b/core/bump_objects.ts @@ -21,38 +21,44 @@ import * as mathUtils from './utils/math.js'; import type {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Bumps the given object that has passed out of bounds. * * @param workspace The workspace containing the object. - * @param scrollMetrics Scroll metrics - * in workspace coordinates. + * @param bounds The region to bump an object into. For example, pass + * ScrollMetrics to bump a block into the scrollable region of the + * workspace, or pass ViewMetrics to bump a block into the visible region of + * the workspace. This should be specified in workspace coordinates. * @param object The object to bump. - * @returns True if block was bumped. + * @returns True if object was bumped. */ function bumpObjectIntoBounds( - workspace: WorkspaceSvg, scrollMetrics: ContainerRegion, - object: IBoundedElement): boolean { + workspace: WorkspaceSvg, + bounds: ContainerRegion, + object: IBoundedElement +): boolean { // Compute new top/left position for object. const objectMetrics = object.getBoundingRectangle(); const height = objectMetrics.bottom - objectMetrics.top; const width = objectMetrics.right - objectMetrics.left; - const topClamp = scrollMetrics.top; - const scrollMetricsBottom = scrollMetrics.top + scrollMetrics.height; - const bottomClamp = scrollMetricsBottom - height; + const topClamp = bounds.top; + const boundsBottom = bounds.top + bounds.height; + const bottomClamp = boundsBottom - height; // If the object is taller than the workspace we want to // top-align the block - const newYPosition = - mathUtils.clamp(topClamp, objectMetrics.top, bottomClamp); + const newYPosition = mathUtils.clamp( + topClamp, + objectMetrics.top, + bottomClamp + ); const deltaY = newYPosition - objectMetrics.top; // Note: Even in RTL mode the "anchor" of the object is the // top-left corner of the object. - let leftClamp = scrollMetrics.left; - const scrollMetricsRight = scrollMetrics.left + scrollMetrics.width; - let rightClamp = scrollMetricsRight - width; + let leftClamp = bounds.left; + const boundsRight = bounds.left + bounds.width; + let rightClamp = boundsRight - width; if (workspace.RTL) { // If the object is wider than the workspace and we're in RTL // mode we want to right-align the block, which means setting @@ -64,12 +70,15 @@ function bumpObjectIntoBounds( // the right clamp to match. rightClamp = Math.max(leftClamp, rightClamp); } - const newXPosition = - mathUtils.clamp(leftClamp, objectMetrics.left, rightClamp); + const newXPosition = mathUtils.clamp( + leftClamp, + objectMetrics.left, + rightClamp + ); const deltaX = newXPosition - objectMetrics.left; if (deltaX || deltaY) { - object.moveBy(deltaX, deltaY); + object.moveBy(deltaX, deltaY, ['inbounds']); return true; } return false; @@ -82,8 +91,9 @@ export const bumpIntoBounds = bumpObjectIntoBounds; * @param workspace The workspace to handle. * @returns The event handler. */ -export function bumpIntoBoundsHandler(workspace: WorkspaceSvg): - (p1: Abstract) => void { +export function bumpIntoBoundsHandler( + workspace: WorkspaceSvg +): (p1: Abstract) => void { return (e) => { const metricsManager = workspace.getMetricsManager(); if (!metricsManager.hasFixedEdges() || workspace.isDragging()) { @@ -94,8 +104,10 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg): const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true); // Triggered by move/create event - const object = - extractObjectFromEvent(workspace, e as eventUtils.BumpEvent); + const object = extractObjectFromEvent( + workspace, + e as eventUtils.BumpEvent + ); if (!object) { return; } @@ -104,18 +116,25 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg): eventUtils.setGroup(e.group); const wasBumped = bumpObjectIntoBounds( - workspace, scrollMetricsInWsCoords, (object as IBoundedElement)); + workspace, + scrollMetricsInWsCoords, + object as IBoundedElement + ); if (wasBumped && !e.group) { console.warn( - 'Moved object in bounds but there was no' + - ' event group. This may break undo.'); + 'Moved object in bounds but there was no' + + ' event group. This may break undo.' + ); } eventUtils.setGroup(existingGroup); } else if (e.type === eventUtils.VIEWPORT_CHANGE) { - const viewportEvent = (e as ViewportChange); - if (viewportEvent.scale && viewportEvent.oldScale && - viewportEvent.scale > viewportEvent.oldScale) { + const viewportEvent = e as ViewportChange; + if ( + viewportEvent.scale && + viewportEvent.oldScale && + viewportEvent.scale > viewportEvent.oldScale + ) { bumpTopObjectsIntoBounds(workspace); } } @@ -132,8 +151,9 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg): * object. */ function extractObjectFromEvent( - workspace: WorkspaceSvg, e: eventUtils.BumpEvent): BlockSvg|null| - WorkspaceCommentSvg { + workspace: WorkspaceSvg, + e: eventUtils.BumpEvent +): BlockSvg | null | WorkspaceCommentSvg { let object = null; switch (e.type) { case eventUtils.BLOCK_CREATE: @@ -145,10 +165,9 @@ function extractObjectFromEvent( break; case eventUtils.COMMENT_CREATE: case eventUtils.COMMENT_MOVE: - object = - workspace.getCommentById((e as CommentCreate | CommentMove).commentId! - ) as WorkspaceCommentSvg | - null; + object = workspace.getCommentById( + (e as CommentCreate | CommentMove).commentId! + ) as WorkspaceCommentSvg | null; break; } return object; @@ -167,7 +186,7 @@ export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg) { const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true); const topBlocks = workspace.getTopBoundedElements(); - for (let i = 0, block; block = topBlocks[i]; i++) { + for (let i = 0, block; (block = topBlocks[i]); i++) { bumpObjectIntoBounds(workspace, scrollMetricsInWsCoords, block); } } diff --git a/core/clipboard.ts b/core/clipboard.ts index 6b402a5e9..662a1c973 100644 --- a/core/clipboard.ts +++ b/core/clipboard.ts @@ -9,9 +9,8 @@ goog.declareModuleId('Blockly.clipboard'); import type {CopyData, ICopyable} from './interfaces/i_copyable.js'; - /** Metadata about the object that is currently on the clipboard. */ -let copyData: CopyData|null = null; +let copyData: CopyData | null = null; /** * Copy a block or workspace comment onto the local clipboard. @@ -36,7 +35,7 @@ function copyInternal(toCopy: ICopyable) { * @returns The pasted thing if the paste was successful, null otherwise. * @internal */ -export function paste(): ICopyable|null { +export function paste(): ICopyable | null { if (!copyData) { return null; } @@ -46,8 +45,10 @@ export function paste(): ICopyable|null { if (workspace.isFlyout) { workspace = workspace.targetWorkspace!; } - if (copyData.typeCounts && - workspace.isCapacityAvailable(copyData.typeCounts)) { + if ( + copyData.typeCounts && + workspace.isCapacityAvailable(copyData.typeCounts) + ) { return workspace.paste(copyData.saveInfo); } return null; @@ -61,18 +62,18 @@ export function paste(): ICopyable|null { * duplication failed. * @internal */ -export function duplicate(toDuplicate: ICopyable): ICopyable|null { +export function duplicate(toDuplicate: ICopyable): ICopyable | null { return TEST_ONLY.duplicateInternal(toDuplicate); } /** * Private version of duplicate for stubbing in tests. */ -function duplicateInternal(toDuplicate: ICopyable): ICopyable|null { +function duplicateInternal(toDuplicate: ICopyable): ICopyable | null { const oldCopyData = copyData; copy(toDuplicate); const pastedThing = - toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null; + toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null; copyData = oldCopyData; return pastedThing; } diff --git a/core/comment.ts b/core/comment.ts deleted file mode 100644 index b60d7547e..000000000 --- a/core/comment.ts +++ /dev/null @@ -1,369 +0,0 @@ -/** - * @license - * Copyright 2011 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing a code comment. - * - * @class - */ -import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Comment'); - -// Unused import preserved for side-effects. Remove if unneeded. -import './events/events_block_change.js'; -// Unused import preserved for side-effects. Remove if unneeded. -import './events/events_bubble_open.js'; - -import type {CommentModel} from './block.js'; -import type {BlockSvg} from './block_svg.js'; -import * as browserEvents from './browser_events.js'; -import {Bubble} from './bubble.js'; -import * as Css from './css.js'; -import * as eventUtils from './events/utils.js'; -import {Icon} from './icon.js'; -import type {Coordinate} from './utils/coordinate.js'; -import * as dom from './utils/dom.js'; -import type {Size} from './utils/size.js'; -import {Svg} from './utils/svg.js'; - - -/** - * Class for a comment. - */ -export class Comment extends Icon { - private readonly model: CommentModel; - - /** - * The model's text value at the start of an edit. - * Used to tell if an event should be fired at the end of an edit. - */ - private cachedText: string|null = ''; - - /** - * Array holding info needed to unbind events. - * Used for disposing. - * Ex: [[node, name, func], [node, name, func]]. - */ - private boundEvents: browserEvents.Data[] = []; - - /** - * The SVG element that contains the text edit area, or null if not created. - */ - private foreignObject: SVGForeignObjectElement|null = null; - - /** The editable text area, or null if not created. */ - private textarea_: HTMLTextAreaElement|null = null; - - /** The top-level node of the comment text, or null if not created. */ - private paragraphElement_: SVGTextElement|null = null; - - /** @param block The block associated with this comment. */ - constructor(block: BlockSvg) { - super(block); - - /** The model for this comment. */ - this.model = block.commentModel; - // If someone creates the comment directly instead of calling - // block.setCommentText we want to make sure the text is non-null; - this.model.text = this.model.text ?? ''; - - this.createIcon(); - } - - /** - * Draw the comment icon. - * - * @param group The icon group. - */ - protected override drawIcon_(group: Element) { - // Circle. - dom.createSvgElement( - Svg.CIRCLE, - {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, group); - // Can't use a real '?' text character since different browsers and - // operating systems render it differently. Body of question mark. - dom.createSvgElement( - Svg.PATH, { - 'class': 'blocklyIconSymbol', - 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' + - '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' + - '-1.201,0.998 -1.201,1.528 -1.204,2.19z', - }, - group); - // Dot of question mark. - dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyIconSymbol', - 'x': '6.8', - 'y': '10.78', - 'height': '2', - 'width': '2', - }, - group); - } - - /** - * Create the editor for the comment's bubble. - * - * @returns The top-level node of the editor. - */ - private createEditor(): SVGElement { - /* Create the editor. Here's the markup that will be generated in - * editable mode: - - - - - * For non-editable mode see Warning.textToDom_. - */ - - this.foreignObject = dom.createSvgElement( - Svg.FOREIGNOBJECT, - {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH}); - - const body = document.createElementNS(dom.HTML_NS, 'body'); - body.setAttribute('xmlns', dom.HTML_NS); - body.className = 'blocklyMinimalBody'; - - this.textarea_ = document.createElementNS(dom.HTML_NS, 'textarea') as - HTMLTextAreaElement; - const textarea = this.textarea_; - textarea.className = 'blocklyCommentTextarea'; - textarea.setAttribute('dir', this.getBlock().RTL ? 'RTL' : 'LTR'); - textarea.value = this.model.text ?? ''; - this.resizeTextarea(); - - body.appendChild(textarea); - this.foreignObject!.appendChild(body); - - this.boundEvents.push(browserEvents.conditionalBind( - textarea, 'focus', this, this.startEdit, true)); - // Don't zoom with mousewheel. - this.boundEvents.push(browserEvents.conditionalBind( - textarea, 'wheel', this, function(e: Event) { - e.stopPropagation(); - })); - this.boundEvents.push(browserEvents.conditionalBind( - textarea, 'change', this, - /** - * @param _e Unused event parameter. - */ - function(this: Comment, _e: Event) { - if (this.cachedText !== this.model.text) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this.getBlock(), 'comment', null, this.cachedText, - this.model.text)); - } - })); - this.boundEvents.push(browserEvents.conditionalBind( - textarea, 'input', this, - /** - * @param _e Unused event parameter. - */ - function(this: Comment, _e: Event) { - this.model.text = textarea.value; - })); - - setTimeout(textarea.focus.bind(textarea), 0); - - return this.foreignObject; - } - - /** Add or remove editability of the comment. */ - override updateEditable() { - super.updateEditable(); - if (this.isVisible()) { - // Recreate the bubble with the correct UI. - this.disposeBubble(); - this.createBubble(); - } - } - - /** - * Callback function triggered when the bubble has resized. - * Resize the text area accordingly. - */ - private onBubbleResize() { - if (!this.isVisible() || !this.bubble_) { - return; - } - - this.model.size = this.bubble_.getBubbleSize(); - this.resizeTextarea(); - } - - /** - * Resizes the text area to match the size defined on the model (which is - * the size of the bubble). - */ - private resizeTextarea() { - if (!this.textarea_ || !this.foreignObject) { - return; - } - const size = this.model.size; - const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; - const widthMinusBorder = size.width - doubleBorderWidth; - const heightMinusBorder = size.height - doubleBorderWidth; - this.foreignObject.setAttribute('width', `${widthMinusBorder}`); - this.foreignObject.setAttribute('height', `${heightMinusBorder}`); - this.textarea_.style.width = widthMinusBorder - 4 + 'px'; - this.textarea_.style.height = heightMinusBorder - 4 + 'px'; - } - - /** - * Show or hide the comment bubble. - * - * @param visible True if the bubble should be visible. - */ - override setVisible(visible: boolean) { - if (visible === this.isVisible()) { - return; - } - eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( - this.getBlock(), visible, 'comment')); - this.model.pinned = visible; - if (visible) { - this.createBubble(); - } else { - this.disposeBubble(); - } - } - - /** Show the bubble. Handles deciding if it should be editable or not. */ - private createBubble() { - if (!this.getBlock().isEditable()) { - this.createNonEditableBubble(); - } else { - this.createEditableBubble(); - } - } - - /** Show an editable bubble. */ - private createEditableBubble() { - const block = this.getBlock(); - this.bubble_ = new Bubble( - block.workspace, this.createEditor(), block.pathObject.svgPath, - (this.iconXY_ as Coordinate), this.model.size.width, - this.model.size.height); - // Expose this comment's block's ID on its top-level SVG group. - this.bubble_.setSvgId(block.id); - this.bubble_.registerResizeEvent(this.onBubbleResize.bind(this)); - this.applyColour(); - } - - /** - * Show a non-editable bubble. - */ - private createNonEditableBubble() { - // TODO (#2917): It would be great if the comment could support line breaks. - this.paragraphElement_ = Bubble.textToDom(this.model.text ?? ''); - this.bubble_ = Bubble.createNonEditableBubble( - this.paragraphElement_, this.getBlock(), this.iconXY_ as Coordinate); - this.applyColour(); - } - - /** - * Dispose of the bubble. - */ - private disposeBubble() { - for (const event of this.boundEvents) { - browserEvents.unbind(event); - } - this.boundEvents.length = 0; - if (this.bubble_) { - this.bubble_.dispose(); - this.bubble_ = null; - } - this.textarea_ = null; - this.foreignObject = null; - this.paragraphElement_ = null; - } - - /** - * Callback fired when an edit starts. - * - * Bring the comment to the top of the stack when clicked on. Also cache the - * current text so it can be used to fire a change event. - * - * @param _e Mouse up event. - */ - private startEdit(_e: PointerEvent) { - if (this.bubble_?.promote()) { - // Since the act of moving this node within the DOM causes a loss of - // focus, we need to reapply the focus. - this.textarea_!.focus(); - } - - this.cachedText = this.model.text; - } - - /** - * Get the dimensions of this comment's bubble. - * - * @returns Object with width and height properties. - */ - getBubbleSize(): Size { - return this.model.size; - } - - /** - * Size this comment's bubble. - * - * @param width Width of the bubble. - * @param height Height of the bubble. - */ - setBubbleSize(width: number, height: number) { - if (this.bubble_) { - this.bubble_.setBubbleSize(width, height); - } else { - this.model.size.width = width; - this.model.size.height = height; - } - } - - /** - * Update the comment's view to match the model. - * - * @internal - */ - updateText() { - if (this.textarea_) { - this.textarea_.value = this.model.text ?? ''; - } else if (this.paragraphElement_) { - // Non-Editable mode. - // TODO (#2917): If 2917 gets added this will probably need to be updated. - this.paragraphElement_.firstChild!.textContent = this.model.text; - } - } - - /** - * Dispose of this comment. - * - * If you want to receive a comment "delete" event (newValue: null), then this - * should not be called directly. Instead call block.setCommentText(null); - */ - override dispose() { - this.getBlock().comment = null; - super.dispose(); - } -} - -/** CSS for block comment. See css.js for use. */ -Css.register(` -.blocklyCommentTextarea { - background-color: #fef49c; - border: 0; - display: block; - margin: 0; - outline: 0; - padding: 3px; - resize: none; - text-overflow: hidden; -} -`); diff --git a/core/common.ts b/core/common.ts index b50eb7b68..ec10d222d 100644 --- a/core/common.ts +++ b/core/common.ts @@ -15,18 +15,16 @@ import type {ICopyable} from './interfaces/i_copyable.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** Database of all workspaces. */ const WorkspaceDB_ = Object.create(null); - /** * Find the workspace with the specified ID. * * @param id ID of workspace to find. * @returns The sought after workspace or null if not found. */ -export function getWorkspaceById(id: string): Workspace|null { +export function getWorkspaceById(id: string): Workspace | null { return WorkspaceDB_[id] || null; } @@ -90,12 +88,12 @@ export function setMainWorkspace(workspace: Workspace) { /** * Currently selected copyable object. */ -let selected: ICopyable|null = null; +let selected: ICopyable | null = null; /** * Returns the currently selected copyable object. */ -export function getSelected(): ICopyable|null { +export function getSelected(): ICopyable | null { return selected; } @@ -107,14 +105,14 @@ export function getSelected(): ICopyable|null { * @param newSelection The newly selected block. * @internal */ -export function setSelected(newSelection: ICopyable|null) { +export function setSelected(newSelection: ICopyable | null) { selected = newSelection; } /** * Container element in which to render the WidgetDiv, DropDownDiv and Tooltip. */ -let parentContainer: Element|null; +let parentContainer: Element | null; /** * Get the container element in which to render the WidgetDiv, DropDownDiv and @@ -122,7 +120,7 @@ let parentContainer: Element|null; * * @returns The parent container. */ -export function getParentContainer(): Element|null { +export function getParentContainer(): Element | null { return parentContainer; } @@ -189,7 +187,9 @@ export const draggingConnections: Connection[] = []; * @returns Map of types to type counts for descendants of the bock. */ export function getBlockTypeCounts( - block: Block, opt_stripFollowing?: boolean): {[key: string]: number} { + block: Block, + opt_stripFollowing?: boolean +): {[key: string]: number} { const typeCountsMap = Object.create(null); const descendants = block.getDescendants(true); if (opt_stripFollowing) { @@ -199,7 +199,7 @@ export function getBlockTypeCounts( descendants.splice(index, descendants.length - index); } } - for (let i = 0, checkBlock; checkBlock = descendants[i]; i++) { + for (let i = 0, checkBlock; (checkBlock = descendants[i]); i++) { if (typeCountsMap[checkBlock.type]) { typeCountsMap[checkBlock.type]++; } else { @@ -218,7 +218,7 @@ export function getBlockTypeCounts( * of jsonDef. */ function jsonInitFactory(jsonDef: AnyDuringMigration): () => void { - return function(this: Block) { + return function (this: Block) { this.jsonInit(jsonDef); }; } @@ -249,7 +249,8 @@ function defineBlocksWithJsonArrayInternal(jsonArray: AnyDuringMigration[]) { * definitions created. */ export function createBlockDefinitionsFromJsonArray( - jsonArray: AnyDuringMigration[]): {[key: string]: BlockDefinition} { + jsonArray: AnyDuringMigration[] +): {[key: string]: BlockDefinition} { const blocks: {[key: string]: BlockDefinition} = {}; for (let i = 0; i < jsonArray.length; i++) { const elem = jsonArray[i]; @@ -260,8 +261,9 @@ export function createBlockDefinitionsFromJsonArray( const type = elem['type']; if (!type) { console.warn( - `Block definition #${i} in JSON array is missing a type attribute. ` + - 'Skipping.'); + `Block definition #${i} in JSON array is missing a type attribute. ` + + 'Skipping.' + ); continue; } blocks[type] = {init: jsonInitFactory(elem)}; diff --git a/core/component_manager.ts b/core/component_manager.ts index 6b014baa4..4eed1d2a4 100644 --- a/core/component_manager.ts +++ b/core/component_manager.ts @@ -19,7 +19,6 @@ import type {IDragTarget} from './interfaces/i_drag_target.js'; import type {IPositionable} from './interfaces/i_positionable.js'; import * as arrayUtils from './utils/array.js'; - class Capability<_T> { static POSITIONABLE = new Capability('positionable'); static DRAG_TARGET = new Capability('drag_target'); @@ -67,8 +66,12 @@ export class ComponentManager { const id = componentInfo.component.id; if (!opt_allowOverrides && this.componentData.has(id)) { throw Error( - 'Plugin "' + id + '" with capabilities "' + - this.componentData.get(id)?.capabilities + '" already added.'); + 'Plugin "' + + id + + '" with capabilities "' + + this.componentData.get(id)?.capabilities + + '" already added.' + ); } this.componentData.set(id, componentInfo); const stringCapabilities = []; @@ -107,15 +110,20 @@ export class ComponentManager { * @param id The ID of the component to add the capability to. * @param capability The capability to add. */ - addCapability(id: string, capability: string|Capability) { + addCapability(id: string, capability: string | Capability) { if (!this.getComponent(id)) { throw Error( - 'Cannot add capability, "' + capability + '". Plugin "' + id + - '" has not been added to the ComponentManager'); + 'Cannot add capability, "' + + capability + + '". Plugin "' + + id + + '" has not been added to the ComponentManager' + ); } if (this.hasCapability(id, capability)) { console.warn( - 'Plugin "' + id + 'already has capability "' + capability + '"'); + 'Plugin "' + id + 'already has capability "' + capability + '"' + ); return; } capability = `${capability}`.toLowerCase(); @@ -129,16 +137,24 @@ export class ComponentManager { * @param id The ID of the component to remove the capability from. * @param capability The capability to remove. */ - removeCapability(id: string, capability: string|Capability) { + removeCapability(id: string, capability: string | Capability) { if (!this.getComponent(id)) { throw Error( - 'Cannot remove capability, "' + capability + '". Plugin "' + id + - '" has not been added to the ComponentManager'); + 'Cannot remove capability, "' + + capability + + '". Plugin "' + + id + + '" has not been added to the ComponentManager' + ); } if (!this.hasCapability(id, capability)) { console.warn( - 'Plugin "' + id + 'doesn\'t have capability "' + capability + - '" to remove'); + 'Plugin "' + + id + + 'doesn\'t have capability "' + + capability + + '" to remove' + ); return; } capability = `${capability}`.toLowerCase(); @@ -153,10 +169,12 @@ export class ComponentManager { * @param capability The capability to check for. * @returns Whether the component has the capability. */ - hasCapability(id: string, capability: string|Capability): boolean { + hasCapability(id: string, capability: string | Capability): boolean { capability = `${capability}`.toLowerCase(); - return this.componentData.has(id) && - this.componentData.get(id)!.capabilities.indexOf(capability) !== -1; + return ( + this.componentData.has(id) && + this.componentData.get(id)!.capabilities.indexOf(capability) !== -1 + ); } /** @@ -165,7 +183,7 @@ export class ComponentManager { * @param id The ID of the component to get. * @returns The component with the given name or undefined if not found. */ - getComponent(id: string): IComponent|undefined { + getComponent(id: string): IComponent | undefined { return this.componentData.get(id)?.component; } @@ -177,7 +195,9 @@ export class ComponentManager { * @returns The components that match the specified capability. */ getComponents( - capability: string|Capability, sorted: boolean): T[] { + capability: string | Capability, + sorted: boolean + ): T[] { capability = `${capability}`.toLowerCase(); const componentIds = this.capabilityToComponentIds.get(capability); if (!componentIds) { @@ -189,10 +209,10 @@ export class ComponentManager { componentIds.forEach((id) => { componentDataList.push(this.componentData.get(id)!); }); - componentDataList.sort(function(a, b) { + componentDataList.sort(function (a, b) { return a.weight - b.weight; }); - componentDataList.forEach(function(componentDatum) { + componentDataList.forEach(function (componentDatum) { components.push(componentDatum.component as T); }); } else { @@ -208,7 +228,7 @@ export namespace ComponentManager { /** An object storing component information. */ export interface ComponentDatum { component: IComponent; - capabilities: Array>; + capabilities: Array>; weight: number; } } diff --git a/core/config.ts b/core/config.ts index 549a6aaf7..b1b05063b 100644 --- a/core/config.ts +++ b/core/config.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.config'); - /** * All the values that we expect developers to be able to change * before injecting Blockly. diff --git a/core/connection.ts b/core/connection.ts index ee7ab8b99..c7c9a3da6 100644 --- a/core/connection.ts +++ b/core/connection.ts @@ -16,13 +16,12 @@ import type {Block} from './block.js'; import {ConnectionType} from './connection_type.js'; import type {BlockMove} from './events/events_block_move.js'; import * as eventUtils from './events/utils.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; import type {IConnectionChecker} from './interfaces/i_connection_checker.js'; import * as blocks from './serialization/blocks.js'; import * as Xml from './xml.js'; - /** * Class for a connection between blocks. */ @@ -41,7 +40,7 @@ export class Connection implements IASTNodeLocationWithBlock { protected sourceBlock_: Block; /** Connection this connection connects to. Null if not connected. */ - targetConnection: Connection|null = null; + targetConnection: Connection | null = null; /** * Has this connection been disposed of? @@ -51,10 +50,10 @@ export class Connection implements IASTNodeLocationWithBlock { disposed = false; /** List of compatible value types. Null if all types are compatible. */ - private check_: string[]|null = null; + private check: string[] | null = null; /** DOM representation of a shadow block, or null if none. */ - private shadowDom_: Element|null = null; + private shadowDom: Element | null = null; /** * Horizontal location of this connection. @@ -70,7 +69,7 @@ export class Connection implements IASTNodeLocationWithBlock { */ y = 0; - private shadowState_: blocks.State|null = null; + private shadowState: blocks.State | null = null; /** * @param source The block establishing this connection. @@ -99,7 +98,7 @@ export class Connection implements IASTNodeLocationWithBlock { // Make sure the parentConnection is available. let orphan; if (this.isConnected()) { - const shadowState = this.stashShadowState_(); + const shadowState = this.stashShadowState(); const target = this.targetBlock(); if (target!.isShadow()) { target!.dispose(false); @@ -107,14 +106,16 @@ export class Connection implements IASTNodeLocationWithBlock { this.disconnectInternal(); orphan = target; } - this.applyShadowState_(shadowState); + this.applyShadowState(shadowState); } // Connect the new connection to the parent. let event; if (eventUtils.isEnabled()) { - event = - new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock) as BlockMove; + event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( + childBlock + ) as BlockMove; + event.setReason(['connect']); } connectReciprocally(this, childConnection); childBlock.setParent(parentBlock); @@ -125,11 +126,15 @@ export class Connection implements IASTNodeLocationWithBlock { // Deal with the orphan if it exists. if (orphan) { - const orphanConnection = this.type === INPUT ? orphan.outputConnection : - orphan.previousConnection; + const orphanConnection = + this.type === INPUT + ? orphan.outputConnection + : orphan.previousConnection; if (!orphanConnection) return; const connection = Connection.getConnectionForOrphanedConnection( - childBlock, orphanConnection); + childBlock, + orphanConnection + ); if (connection) { orphanConnection.connect(connection); } else { @@ -147,7 +152,7 @@ export class Connection implements IASTNodeLocationWithBlock { // isConnected returns true for shadows and non-shadows. if (this.isConnected()) { // Destroy the attached shadow block & its children (if it exists). - this.setShadowStateInternal_(); + this.setShadowStateInternal(); const targetBlock = this.targetBlock(); if (targetBlock && !targetBlock.isDeadOrDying()) { @@ -175,8 +180,10 @@ export class Connection implements IASTNodeLocationWithBlock { * @returns True if connection faces down or right. */ isSuperior(): boolean { - return this.type === ConnectionType.INPUT_VALUE || - this.type === ConnectionType.NEXT_STATEMENT; + return ( + this.type === ConnectionType.INPUT_VALUE || + this.type === ConnectionType.NEXT_STATEMENT + ); } /** @@ -258,7 +265,7 @@ export class Connection implements IASTNodeLocationWithBlock { */ protected disconnectInternal(setParent = true) { const {parentConnection, childConnection} = - this.getParentAndChildConnections(); + this.getParentAndChildConnections(); if (!parentConnection || !childConnection) { throw Error('Source connection not connected.'); } @@ -271,7 +278,9 @@ export class Connection implements IASTNodeLocationWithBlock { let event; if (eventUtils.isEnabled()) { event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( - childConnection.getSourceBlock()) as BlockMove; + childConnection.getSourceBlock() + ) as BlockMove; + event.setReason(['disconnect']); } const otherConnection = this.targetConnection; if (otherConnection) { @@ -299,8 +308,10 @@ export class Connection implements IASTNodeLocationWithBlock { * @returns The parent connection and child connection, given this connection * and the connection it is connected to. */ - protected getParentAndChildConnections(): - {parentConnection?: Connection, childConnection?: Connection} { + protected getParentAndChildConnections(): { + parentConnection?: Connection; + childConnection?: Connection; + } { if (!this.targetConnection) return {}; if (this.isSuperior()) { return { @@ -319,7 +330,37 @@ export class Connection implements IASTNodeLocationWithBlock { */ protected respawnShadow_() { // Have to keep respawnShadow_ for backwards compatibility. - this.createShadowBlock_(true); + this.createShadowBlock(true); + } + + /** + * Reconnects this connection to the input with the given name on the given + * block. If there is already a connection connected to that input, that + * connection is disconnected. + * + * @param block The block to connect this connection to. + * @param inputName The name of the input to connect this connection to. + * @returns True if this connection was able to connect, false otherwise. + */ + reconnect(block: Block, inputName: string): boolean { + // No need to reconnect if this connection's block is deleted. + if (this.getSourceBlock().isDeadOrDying()) return false; + + const connectionParent = block.getInput(inputName)?.connection; + const currentParent = this.targetBlock(); + if ( + (!currentParent || currentParent === block) && + connectionParent && + connectionParent.targetConnection !== this + ) { + if (connectionParent.isConnected()) { + // There's already something connected here. Get rid of it. + connectionParent.disconnect(); + } + connectionParent.connect(this); + return true; + } + return false; } /** @@ -327,7 +368,7 @@ export class Connection implements IASTNodeLocationWithBlock { * * @returns The connected block or null if none is connected. */ - targetBlock(): Block|null { + targetBlock(): Block | null { if (this.isConnected()) { return this.targetConnection?.getSourceBlock() ?? null; } @@ -339,10 +380,15 @@ export class Connection implements IASTNodeLocationWithBlock { */ protected onCheckChanged_() { // The new value type may not be compatible with the existing connection. - if (this.isConnected() && - (!this.targetConnection || - !this.getConnectionChecker().canConnect( - this, this.targetConnection, false))) { + if ( + this.isConnected() && + (!this.targetConnection || + !this.getConnectionChecker().canConnect( + this, + this.targetConnection, + false + )) + ) { const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; child!.unplug(); } @@ -355,15 +401,15 @@ export class Connection implements IASTNodeLocationWithBlock { * types are compatible. * @returns The connection being modified (to allow chaining). */ - setCheck(check: string|string[]|null): Connection { + setCheck(check: string | string[] | null): Connection { if (check) { if (!Array.isArray(check)) { check = [check]; } - this.check_ = check; + this.check = check; this.onCheckChanged_(); } else { - this.check_ = null; + this.check = null; } return this; } @@ -374,8 +420,8 @@ export class Connection implements IASTNodeLocationWithBlock { * @returns List of compatible value types. * Null if all types are compatible. */ - getCheck(): string[]|null { - return this.check_; + getCheck(): string[] | null { + return this.check; } /** @@ -383,8 +429,8 @@ export class Connection implements IASTNodeLocationWithBlock { * * @param shadowDom DOM representation of a block or null. */ - setShadowDom(shadowDom: Element|null) { - this.setShadowStateInternal_({shadowDom}); + setShadowDom(shadowDom: Element | null) { + this.setShadowStateInternal({shadowDom}); } /** @@ -396,10 +442,10 @@ export class Connection implements IASTNodeLocationWithBlock { * just returned. * @returns Shadow DOM representation of a block or null. */ - getShadowDom(returnCurrent?: boolean): Element|null { - return returnCurrent && this.targetBlock()!.isShadow() ? - Xml.blockToDom((this.targetBlock() as Block)) as Element : - this.shadowDom_; + getShadowDom(returnCurrent?: boolean): Element | null { + return returnCurrent && this.targetBlock()!.isShadow() + ? (Xml.blockToDom(this.targetBlock() as Block) as Element) + : this.shadowDom; } /** @@ -407,8 +453,8 @@ export class Connection implements IASTNodeLocationWithBlock { * * @param shadowState An state represetation of the block or null. */ - setShadowState(shadowState: blocks.State|null) { - this.setShadowStateInternal_({shadowState}); + setShadowState(shadowState: blocks.State | null) { + this.setShadowStateInternal({shadowState}); } /** @@ -421,11 +467,11 @@ export class Connection implements IASTNodeLocationWithBlock { * returned. * @returns Serialized object representation of the block, or null. */ - getShadowState(returnCurrent?: boolean): blocks.State|null { + getShadowState(returnCurrent?: boolean): blocks.State | null { if (returnCurrent && this.targetBlock() && this.targetBlock()!.isShadow()) { return blocks.save(this.targetBlock() as Block); } - return this.shadowState_; + return this.shadowState; } /** @@ -452,7 +498,7 @@ export class Connection implements IASTNodeLocationWithBlock { * exists. * @internal */ - getParentInput(): Input|null { + getParentInput(): Input | null { let parentInput = null; const inputs = this.sourceBlock_.inputList; for (let i = 0; i < inputs.length; i++) { @@ -484,7 +530,7 @@ export class Connection implements IASTNodeLocationWithBlock { msg = 'Next Connection of '; } else { let parentInput = null; - for (let i = 0, input; input = block.inputList[i]; i++) { + for (let i = 0, input; (input = block.inputList[i]); i++) { if (input.connection === this) { parentInput = input; break; @@ -506,13 +552,15 @@ export class Connection implements IASTNodeLocationWithBlock { * * @returns The state of both the shadowDom_ and shadowState_ properties. */ - private stashShadowState_(): - {shadowDom: Element|null, shadowState: blocks.State|null} { + private stashShadowState(): { + shadowDom: Element | null; + shadowState: blocks.State | null; + } { const shadowDom = this.getShadowDom(true); const shadowState = this.getShadowState(true); // Set to null so it doesn't respawn. - this.shadowDom_ = null; - this.shadowState_ = null; + this.shadowDom = null; + this.shadowState = null; return {shadowDom, shadowState}; } @@ -522,12 +570,15 @@ export class Connection implements IASTNodeLocationWithBlock { * @param param0 The state to reapply to the shadowDom_ and shadowState_ * properties. */ - private applyShadowState_({shadowDom, shadowState}: { - shadowDom: Element|null, - shadowState: blocks.State|null + private applyShadowState({ + shadowDom, + shadowState, + }: { + shadowDom: Element | null; + shadowState: blocks.State | null; }) { - this.shadowDom_ = shadowDom; - this.shadowState_ = shadowState; + this.shadowDom = shadowDom; + this.shadowState = shadowState; } /** @@ -535,31 +586,34 @@ export class Connection implements IASTNodeLocationWithBlock { * * @param param0 The state to set the shadow of this connection to. */ - private setShadowStateInternal_({shadowDom = null, shadowState = null}: { - shadowDom?: Element|null, - shadowState?: blocks.State|null + private setShadowStateInternal({ + shadowDom = null, + shadowState = null, + }: { + shadowDom?: Element | null; + shadowState?: blocks.State | null; } = {}) { // One or both of these should always be null. // If neither is null, the shadowState will get priority. - this.shadowDom_ = shadowDom; - this.shadowState_ = shadowState; + this.shadowDom = shadowDom; + this.shadowState = shadowState; const target = this.targetBlock(); if (!target) { this.respawnShadow_(); if (this.targetBlock() && this.targetBlock()!.isShadow()) { - this.serializeShadow_(this.targetBlock()); + this.serializeShadow(this.targetBlock()); } } else if (target.isShadow()) { target.dispose(false); if (this.getSourceBlock().isDeadOrDying()) return; this.respawnShadow_(); if (this.targetBlock() && this.targetBlock()!.isShadow()) { - this.serializeShadow_(this.targetBlock()); + this.serializeShadow(this.targetBlock()); } } else { - const shadow = this.createShadowBlock_(false); - this.serializeShadow_(shadow); + const shadow = this.createShadowBlock(false); + this.serializeShadow(shadow); if (shadow) { shadow.dispose(false); } @@ -575,11 +629,11 @@ export class Connection implements IASTNodeLocationWithBlock { * @returns The shadow block that was created, or null if both the * shadowState_ and shadowDom_ are null. */ - private createShadowBlock_(attemptToConnect: boolean): Block|null { + private createShadowBlock(attemptToConnect: boolean): Block | null { const parentBlock = this.getSourceBlock(); const shadowState = this.getShadowState(); const shadowDom = this.getShadowDom(); - if (parentBlock.isDeadOrDying() || !shadowState && !shadowDom) { + if (parentBlock.isDeadOrDying() || (!shadowState && !shadowDom)) { return null; } @@ -612,7 +666,8 @@ export class Connection implements IASTNodeLocationWithBlock { } } else { throw new Error( - 'Cannot connect a shadow block to a previous/output connection'); + 'Cannot connect a shadow block to a previous/output connection' + ); } } return blockShadow; @@ -626,12 +681,12 @@ export class Connection implements IASTNodeLocationWithBlock { * * @param shadow The shadow to serialize, or null. */ - private serializeShadow_(shadow: Block|null) { + private serializeShadow(shadow: Block | null) { if (!shadow) { return; } - this.shadowDom_ = Xml.blockToDom(shadow) as Element; - this.shadowState_ = blocks.save(shadow); + this.shadowDom = Xml.blockToDom(shadow) as Element; + this.shadowState = blocks.save(shadow); } /** @@ -644,10 +699,14 @@ export class Connection implements IASTNodeLocationWithBlock { * @returns The suitable connection point on the chain of blocks, or null. */ static getConnectionForOrphanedConnection( - startBlock: Block, orphanConnection: Connection): Connection|null { + startBlock: Block, + orphanConnection: Connection + ): Connection | null { if (orphanConnection.type === ConnectionType.OUTPUT_VALUE) { return getConnectionForOrphanedOutput( - startBlock, orphanConnection.getSourceBlock()); + startBlock, + orphanConnection.getSourceBlock() + ); } // Otherwise we're dealing with a stack. const connection = startBlock.lastConnectionInStack(true); @@ -682,17 +741,19 @@ function connectReciprocally(first: Connection, second: Connection) { * @param orphanBlock The inferior block. * @returns The suitable connection point on 'block', or null. */ -function getSingleConnection(block: Block, orphanBlock: Block): Connection| - null { +function getSingleConnection( + block: Block, + orphanBlock: Block +): Connection | null { let foundConnection = null; const output = orphanBlock.outputConnection; const typeChecker = output?.getConnectionChecker(); - for (let i = 0, input; input = block.inputList[i]; i++) { + for (let i = 0, input; (input = block.inputList[i]); i++) { const connection = input.connection; if (connection && typeChecker?.canConnect(output, connection, false)) { if (foundConnection) { - return null; // More than one connection. + return null; // More than one connection. } foundConnection = connection; } @@ -712,10 +773,12 @@ function getSingleConnection(block: Block, orphanBlock: Block): Connection| * @returns The suitable connection point on the chain of blocks, or null. */ function getConnectionForOrphanedOutput( - startBlock: Block, orphanBlock: Block): Connection|null { - let newBlock: Block|null = startBlock; + startBlock: Block, + orphanBlock: Block +): Connection | null { + let newBlock: Block | null = startBlock; let connection; - while (connection = getSingleConnection(newBlock, orphanBlock)) { + while ((connection = getSingleConnection(newBlock, orphanBlock))) { newBlock = connection.targetBlock(); if (!newBlock || newBlock.isShadow()) { return connection; diff --git a/core/connection_checker.ts b/core/connection_checker.ts index 662037a93..25cf87130 100644 --- a/core/connection_checker.ts +++ b/core/connection_checker.ts @@ -21,7 +21,6 @@ import * as internalConstants from './internal_constants.js'; import * as registry from './registry.js'; import type {RenderedConnection} from './rendered_connection.js'; - /** * Class for connection type checking logic. */ @@ -38,10 +37,15 @@ export class ConnectionChecker implements IConnectionChecker { * @returns Whether the connection is legal. */ canConnect( - a: Connection|null, b: Connection|null, isDragging: boolean, - opt_distance?: number): boolean { - return this.canConnectWithReason(a, b, isDragging, opt_distance) === - Connection.CAN_CONNECT; + a: Connection | null, + b: Connection | null, + isDragging: boolean, + opt_distance?: number + ): boolean { + return ( + this.canConnectWithReason(a, b, isDragging, opt_distance) === + Connection.CAN_CONNECT + ); } /** @@ -57,8 +61,11 @@ export class ConnectionChecker implements IConnectionChecker { * otherwise. */ canConnectWithReason( - a: Connection|null, b: Connection|null, isDragging: boolean, - opt_distance?: number): number { + a: Connection | null, + b: Connection | null, + isDragging: boolean, + opt_distance?: number + ): number { const safety = this.doSafetyChecks(a, b); if (safety !== Connection.CAN_CONNECT) { return safety; @@ -71,10 +78,14 @@ export class ConnectionChecker implements IConnectionChecker { return Connection.REASON_CHECKS_FAILED; } - if (isDragging && - !this.doDragChecks( - a as RenderedConnection, b as RenderedConnection, - opt_distance || 0)) { + if ( + isDragging && + !this.doDragChecks( + a as RenderedConnection, + b as RenderedConnection, + opt_distance || 0 + ) + ) { return Connection.REASON_DRAG_CHECKS_FAILED; } @@ -89,8 +100,11 @@ export class ConnectionChecker implements IConnectionChecker { * @param b The second of the two connections being checked. * @returns A developer-readable error string. */ - getErrorMessage(errorCode: number, a: Connection|null, b: Connection|null): - string { + getErrorMessage( + errorCode: number, + a: Connection | null, + b: Connection | null + ): string { switch (errorCode) { case Connection.REASON_SELF_CONNECTION: return 'Attempted to connect a block to itself.'; @@ -105,8 +119,12 @@ export class ConnectionChecker implements IConnectionChecker { const connOne = a!; const connTwo = b!; let msg = 'Connection checks failed. '; - msg += connOne + ' expected ' + connOne.getCheck() + ', found ' + - connTwo.getCheck(); + msg += + connOne + + ' expected ' + + connOne.getCheck() + + ', found ' + + connTwo.getCheck(); return msg; } case Connection.REASON_SHADOW_PARENT: @@ -128,7 +146,7 @@ export class ConnectionChecker implements IConnectionChecker { * @param b The second of the connections to check. * @returns An enum with the reason this connection is safe or unsafe. */ - doSafetyChecks(a: Connection|null, b: Connection|null): number { + doSafetyChecks(a: Connection | null, b: Connection | null): number { if (!a || !b) { return Connection.REASON_TARGET_NULL; } @@ -150,22 +168,25 @@ export class ConnectionChecker implements IConnectionChecker { if (superiorBlock === inferiorBlock) { return Connection.REASON_SELF_CONNECTION; } else if ( - inferiorConnection.type !== - internalConstants.OPPOSITE_TYPE[superiorConnection.type]) { + inferiorConnection.type !== + internalConstants.OPPOSITE_TYPE[superiorConnection.type] + ) { return Connection.REASON_WRONG_TYPE; } else if (superiorBlock.workspace !== inferiorBlock.workspace) { return Connection.REASON_DIFFERENT_WORKSPACES; } else if (superiorBlock.isShadow() && !inferiorBlock.isShadow()) { return Connection.REASON_SHADOW_PARENT; } else if ( - inferiorConnection.type === ConnectionType.OUTPUT_VALUE && - inferiorBlock.previousConnection && - inferiorBlock.previousConnection.isConnected()) { + inferiorConnection.type === ConnectionType.OUTPUT_VALUE && + inferiorBlock.previousConnection && + inferiorBlock.previousConnection.isConnected() + ) { return Connection.REASON_PREVIOUS_AND_OUTPUT; } else if ( - inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT && - inferiorBlock.outputConnection && - inferiorBlock.outputConnection.isConnected()) { + inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT && + inferiorBlock.outputConnection && + inferiorBlock.outputConnection.isConnected() + ) { return Connection.REASON_PREVIOUS_AND_OUTPUT; } return Connection.CAN_CONNECT; @@ -206,8 +227,11 @@ export class ConnectionChecker implements IConnectionChecker { * @param distance The maximum allowable distance between connections. * @returns True if the connection is allowed during a drag. */ - doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number): - boolean { + doDragChecks( + a: RenderedConnection, + b: RenderedConnection, + distance: number + ): boolean { if (a.distanceFrom(b) > distance) { return false; } @@ -223,8 +247,10 @@ export class ConnectionChecker implements IConnectionChecker { case ConnectionType.OUTPUT_VALUE: { // Don't offer to connect an already connected left (male) value plug to // an available right (female) value plug. - if (b.isConnected() && !b.targetBlock()!.isInsertionMarker() || - a.isConnected()) { + if ( + (b.isConnected() && !b.targetBlock()!.isInsertionMarker()) || + a.isConnected() + ) { return false; } break; @@ -233,8 +259,11 @@ export class ConnectionChecker implements IConnectionChecker { // Offering to connect the left (male) of a value block to an already // connected value pair is ok, we'll splice it in. // However, don't offer to splice into an immovable block. - if (b.isConnected() && !b.targetBlock()!.isMovable() && - !b.targetBlock()!.isShadow()) { + if ( + b.isConnected() && + !b.targetBlock()!.isMovable() && + !b.targetBlock()!.isShadow() + ) { return false; } break; @@ -244,15 +273,22 @@ export class ConnectionChecker implements IConnectionChecker { // the stack. But covering up a shadow block or stack of shadow blocks // is fine. Similarly, replacing a terminal statement with another // terminal statement is allowed. - if (b.isConnected() && !a.getSourceBlock().nextConnection && - !b.targetBlock()!.isShadow() && b.targetBlock()!.nextConnection) { + if ( + b.isConnected() && + !a.getSourceBlock().nextConnection && + !b.targetBlock()!.isShadow() && + b.targetBlock()!.nextConnection + ) { return false; } // Don't offer to splice into a stack where the connected block is // immovable, unless the block is a shadow block. - if (b.targetBlock() && !b.targetBlock()!.isMovable() && - !b.targetBlock()!.isShadow()) { + if ( + b.targetBlock() && + !b.targetBlock()!.isMovable() && + !b.targetBlock()!.isShadow() + ) { return false; } break; @@ -307,4 +343,7 @@ export class ConnectionChecker implements IConnectionChecker { } registry.register( - registry.Type.CONNECTION_CHECKER, registry.DEFAULT, ConnectionChecker); + registry.Type.CONNECTION_CHECKER, + registry.DEFAULT, + ConnectionChecker +); diff --git a/core/connection_db.ts b/core/connection_db.ts index 9320d9f28..1cf8900b7 100644 --- a/core/connection_db.ts +++ b/core/connection_db.ts @@ -19,7 +19,6 @@ import type {IConnectionChecker} from './interfaces/i_connection_checker.js'; import type {RenderedConnection} from './rendered_connection.js'; import type {Coordinate} from './utils/coordinate.js'; - /** * Database of connections. * Connections are stored in order of their vertical component. This way @@ -27,7 +26,7 @@ import type {Coordinate} from './utils/coordinate.js'; */ export class ConnectionDB { /** Array of connections sorted by y position in workspace units. */ - private readonly connections_: RenderedConnection[] = []; + private readonly connections: RenderedConnection[] = []; /** * @param connectionChecker The workspace's connection type checker, used to @@ -43,8 +42,8 @@ export class ConnectionDB { * @internal */ addConnection(connection: RenderedConnection, yPos: number) { - const index = this.calculateIndexForYPos_(yPos); - this.connections_.splice(index, 0, connection); + const index = this.calculateIndexForYPos(yPos); + this.connections.splice(index, 0, connection); } /** @@ -58,14 +57,16 @@ export class ConnectionDB { * @returns The index of the connection, or -1 if the connection was not * found. */ - private findIndexOfConnection_(conn: RenderedConnection, yPos: number): - number { - if (!this.connections_.length) { + private findIndexOfConnection( + conn: RenderedConnection, + yPos: number + ): number { + if (!this.connections.length) { return -1; } - const bestGuess = this.calculateIndexForYPos_(yPos); - if (bestGuess >= this.connections_.length) { + const bestGuess = this.calculateIndexForYPos(yPos); + if (bestGuess >= this.connections.length) { // Not in list return -1; } @@ -73,17 +74,19 @@ export class ConnectionDB { yPos = conn.y; // Walk forward and back on the y axis looking for the connection. let pointer = bestGuess; - while (pointer >= 0 && this.connections_[pointer].y === yPos) { - if (this.connections_[pointer] === conn) { + while (pointer >= 0 && this.connections[pointer].y === yPos) { + if (this.connections[pointer] === conn) { return pointer; } pointer--; } pointer = bestGuess; - while (pointer < this.connections_.length && - this.connections_[pointer].y === yPos) { - if (this.connections_[pointer] === conn) { + while ( + pointer < this.connections.length && + this.connections[pointer].y === yPos + ) { + if (this.connections[pointer] === conn) { return pointer; } pointer++; @@ -97,17 +100,17 @@ export class ConnectionDB { * @param yPos The y position used to decide where to insert the connection. * @returns The candidate index. */ - private calculateIndexForYPos_(yPos: number): number { - if (!this.connections_.length) { + private calculateIndexForYPos(yPos: number): number { + if (!this.connections.length) { return 0; } let pointerMin = 0; - let pointerMax = this.connections_.length; + let pointerMax = this.connections.length; while (pointerMin < pointerMax) { const pointerMid = Math.floor((pointerMin + pointerMax) / 2); - if (this.connections_[pointerMid].y < yPos) { + if (this.connections[pointerMid].y < yPos) { pointerMin = pointerMid + 1; - } else if (this.connections_[pointerMid].y > yPos) { + } else if (this.connections[pointerMid].y > yPos) { pointerMax = pointerMid; } else { pointerMin = pointerMid; @@ -125,11 +128,11 @@ export class ConnectionDB { * @throws {Error} If the connection cannot be found in the database. */ removeConnection(connection: RenderedConnection, yPos: number) { - const index = this.findIndexOfConnection_(connection, yPos); + const index = this.findIndexOfConnection(connection, yPos); if (index === -1) { throw Error('Unable to find connection in connectionDB.'); } - this.connections_.splice(index, 1); + this.connections.splice(index, 1); } /** @@ -140,9 +143,11 @@ export class ConnectionDB { * @param maxRadius The maximum radius to another connection. * @returns List of connections. */ - getNeighbours(connection: RenderedConnection, maxRadius: number): - RenderedConnection[] { - const db = this.connections_; + getNeighbours( + connection: RenderedConnection, + maxRadius: number + ): RenderedConnection[] { + const db = this.connections; const currentX = connection.x; const currentY = connection.y; @@ -169,7 +174,7 @@ export class ConnectionDB { * @returns True if the current connection's vertical distance from the * other connection is less than the allowed radius. */ - function checkConnection_(yIndex: number): boolean { + function checkConnection(yIndex: number): boolean { const dx = currentX - db[yIndex].x; const dy = currentY - db[yIndex].y; const r = Math.sqrt(dx * dx + dy * dy); @@ -183,12 +188,12 @@ export class ConnectionDB { pointerMin = pointerMid; pointerMax = pointerMid; if (db.length) { - while (pointerMin >= 0 && checkConnection_(pointerMin)) { + while (pointerMin >= 0 && checkConnection(pointerMin)) { pointerMin--; } do { pointerMax++; - } while (pointerMax < db.length && checkConnection_(pointerMax)); + } while (pointerMax < db.length && checkConnection(pointerMax)); } return neighbours; @@ -203,9 +208,8 @@ export class ConnectionDB { * @param maxRadius The maximum radius to another connection. * @returns True if connection is in range. */ - private isInYRange_(index: number, baseY: number, maxRadius: number): - boolean { - return Math.abs(this.connections_[index].y - baseY) <= maxRadius; + private isInYRange(index: number, baseY: number, maxRadius: number): boolean { + return Math.abs(this.connections[index].y - baseY) <= maxRadius; } /** @@ -219,9 +223,11 @@ export class ConnectionDB { * connection or null, and 'radius' which is the distance. */ searchForClosest( - conn: RenderedConnection, maxRadius: number, - dxy: Coordinate): {connection: RenderedConnection|null, radius: number} { - if (!this.connections_.length) { + conn: RenderedConnection, + maxRadius: number, + dxy: Coordinate + ): {connection: RenderedConnection | null; radius: number} { + if (!this.connections.length) { // Don't bother. return {connection: null, radius: maxRadius}; } @@ -236,7 +242,7 @@ export class ConnectionDB { // calculateIndexForYPos_ finds an index for insertion, which is always // after any block with the same y index. We want to search both forward // and back, so search on both sides of the index. - const closestIndex = this.calculateIndexForYPos_(conn.y); + const closestIndex = this.calculateIndexForYPos(conn.y); let bestConnection = null; let bestRadius = maxRadius; @@ -244,8 +250,8 @@ export class ConnectionDB { // Walk forward and back on the y axis looking for the closest x,y point. let pointerMin = closestIndex - 1; - while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y, maxRadius)) { - temp = this.connections_[pointerMin]; + while (pointerMin >= 0 && this.isInYRange(pointerMin, conn.y, maxRadius)) { + temp = this.connections[pointerMin]; if (this.connectionChecker.canConnect(conn, temp, true, bestRadius)) { bestConnection = temp; bestRadius = temp.distanceFrom(conn); @@ -254,9 +260,11 @@ export class ConnectionDB { } let pointerMax = closestIndex; - while (pointerMax < this.connections_.length && - this.isInYRange_(pointerMax, conn.y, maxRadius)) { - temp = this.connections_[pointerMax]; + while ( + pointerMax < this.connections.length && + this.isInYRange(pointerMax, conn.y, maxRadius) + ) { + temp = this.connections[pointerMax]; if (this.connectionChecker.canConnect(conn, temp, true, bestRadius)) { bestConnection = temp; bestRadius = temp.distanceFrom(conn); diff --git a/core/connection_type.ts b/core/connection_type.ts index 5fe77b12b..2e6a13e9d 100644 --- a/core/connection_type.ts +++ b/core/connection_type.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.ConnectionType'); - /** * Enum for the type of a connection or input. */ @@ -19,5 +18,5 @@ export enum ConnectionType { // A down-facing block stack. E.g. 'if-do' or 'else'. NEXT_STATEMENT, // An up-facing block stack. E.g. 'break out of loop'. - PREVIOUS_STATEMENT + PREVIOUS_STATEMENT, } diff --git a/core/constants.ts b/core/constants.ts index ace571ff1..8d222640b 100644 --- a/core/constants.ts +++ b/core/constants.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.constants'); - /** * The language-neutral ID given to the collapsed input. */ diff --git a/core/contextmenu.ts b/core/contextmenu.ts index f9015a57e..dc8cf1512 100644 --- a/core/contextmenu.ts +++ b/core/contextmenu.ts @@ -13,7 +13,10 @@ import * as browserEvents from './browser_events.js'; import * as clipboard from './clipboard.js'; import {config} from './config.js'; import * as dom from './utils/dom.js'; -import type {ContextMenuOption, LegacyContextMenuOption} from './contextmenu_registry.js'; +import type { + ContextMenuOption, + LegacyContextMenuOption, +} from './contextmenu_registry.js'; import * as eventUtils from './events/utils.js'; import {Menu} from './menu.js'; import {MenuItem} from './menuitem.js'; @@ -27,11 +30,10 @@ import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; import * as Xml from './xml.js'; - /** * Which block is the context menu attached to? */ -let currentBlock: Block|null = null; +let currentBlock: Block | null = null; const dummyOwner = {}; @@ -40,7 +42,7 @@ const dummyOwner = {}; * * @returns The block the context menu is attached to. */ -export function getCurrentBlock(): Block|null { +export function getCurrentBlock(): Block | null { return currentBlock; } @@ -49,14 +51,14 @@ export function getCurrentBlock(): Block|null { * * @param block The block the context menu is attached to. */ -export function setCurrentBlock(block: Block|null) { +export function setCurrentBlock(block: Block | null) { currentBlock = block; } /** * Menu object. */ -let menu_: Menu|null = null; +let menu_: Menu | null = null; /** * Construct the menu based on the list of options and show the menu. @@ -66,8 +68,10 @@ let menu_: Menu|null = null; * @param rtl True if RTL, false if LTR. */ export function show( - e: Event, options: (ContextMenuOption|LegacyContextMenuOption)[], - rtl: boolean) { + e: Event, + options: (ContextMenuOption | LegacyContextMenuOption)[], + rtl: boolean +) { WidgetDiv.show(dummyOwner, rtl, dispose); if (!options.length) { hide(); @@ -79,10 +83,10 @@ export function show( position_(menu, e, rtl); // 1ms delay is required for focusing on context menus because some other // mouse event is still waiting in the queue and clears focus. - setTimeout(function() { + setTimeout(function () { menu.focus(); }, 1); - currentBlock = null; // May be set by Blockly.Block. + currentBlock = null; // May be set by Blockly.Block. } /** @@ -93,8 +97,9 @@ export function show( * @returns The menu that will be shown on right click. */ function populate_( - options: (ContextMenuOption|LegacyContextMenuOption)[], - rtl: boolean): Menu { + options: (ContextMenuOption | LegacyContextMenuOption)[], + rtl: boolean +): Menu { /* Here's what one option object looks like: {text: 'Make It So', enabled: true, @@ -110,7 +115,7 @@ function populate_( menu.addChild(menuItem); menuItem.setEnabled(option.enabled); if (option.enabled) { - const actionHandler = function() { + const actionHandler = function () { hide(); requestAnimationFrame(() => { setTimeout(() => { @@ -143,10 +148,11 @@ function position_(menu: Menu, e: Event, rtl: boolean) { // This one is just a point, but we'll pretend that it's a rect so we can use // some helper functions. const anchorBBox = new Rect( - mouseEvent.clientY + viewportBBox.top, - mouseEvent.clientY + viewportBBox.top, - mouseEvent.clientX + viewportBBox.left, - mouseEvent.clientX + viewportBBox.left); + mouseEvent.clientY + viewportBBox.top, + mouseEvent.clientY + viewportBBox.top, + mouseEvent.clientX + viewportBBox.left, + mouseEvent.clientX + viewportBBox.left + ); createWidget_(menu); const menuSize = menu.getSize(); @@ -179,7 +185,11 @@ function createWidget_(menu: Menu) { dom.addClass(menuDom, 'blocklyContextMenu'); // Prevent system context menu when right-clicking a Blockly context menu. browserEvents.conditionalBind( - (menuDom as EventTarget), 'contextmenu', null, haltPropagation); + menuDom as EventTarget, + 'contextmenu', + null, + haltPropagation + ); // Focus only after the initial render to avoid issue #1329. menu.focus(); } @@ -220,7 +230,7 @@ export function dispose() { * @param xml XML representation of new block. * @returns Function that creates a block. */ -export function callbackFactory(block: Block, xml: Element): Function { +export function callbackFactory(block: Block, xml: Element): () => void { return () => { eventUtils.disable(); let newBlock; @@ -256,12 +266,13 @@ export function callbackFactory(block: Block, xml: Element): Function { * containing text, enabled, and a callback. * @internal */ -export function commentDeleteOption(comment: WorkspaceCommentSvg): - LegacyContextMenuOption { +export function commentDeleteOption( + comment: WorkspaceCommentSvg +): LegacyContextMenuOption { const deleteOption = { text: Msg['REMOVE_COMMENT'], enabled: true, - callback: function() { + callback: function () { eventUtils.setGroup(true); comment.dispose(); eventUtils.setGroup(false); @@ -279,12 +290,13 @@ export function commentDeleteOption(comment: WorkspaceCommentSvg): * containing text, enabled, and a callback. * @internal */ -export function commentDuplicateOption(comment: WorkspaceCommentSvg): - LegacyContextMenuOption { +export function commentDuplicateOption( + comment: WorkspaceCommentSvg +): LegacyContextMenuOption { const duplicateOption = { text: Msg['DUPLICATE_COMMENT'], enabled: true, - callback: function() { + callback: function () { clipboard.duplicate(comment); }, }; @@ -298,20 +310,24 @@ export function commentDuplicateOption(comment: WorkspaceCommentSvg): * originated. * @param e The right-click mouse event. * @returns A menu option, containing text, enabled, and a callback. - * @suppress {strictModuleDepCheck,checkTypes} Suppress checks while workspace * comments are not bundled in. * @internal */ export function workspaceCommentOption( - ws: WorkspaceSvg, e: Event): ContextMenuOption { + ws: WorkspaceSvg, + e: Event +): ContextMenuOption { /** * Helper function to create and position a comment correctly based on the * location of the mouse event. */ function addWsComment() { const comment = new WorkspaceCommentSvg( - ws, Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'], - WorkspaceCommentSvg.DEFAULT_SIZE, WorkspaceCommentSvg.DEFAULT_SIZE); + ws, + Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'], + WorkspaceCommentSvg.DEFAULT_SIZE, + WorkspaceCommentSvg.DEFAULT_SIZE + ); const injectionDiv = ws.getInjectionDiv(); // Bounding rect coordinates are in client coordinates, meaning that they @@ -322,8 +338,9 @@ export function workspaceCommentOption( // The client coordinates offset by the injection div's upper left corner. const mouseEvent = e as MouseEvent; const clientOffsetPixels = new Coordinate( - mouseEvent.clientX - boundingRect.left, - mouseEvent.clientY - boundingRect.top); + mouseEvent.clientX - boundingRect.left, + mouseEvent.clientY - boundingRect.top + ); // The offset in pixels between the main workspace's origin and the upper // left corner of the injection div. @@ -331,8 +348,10 @@ export function workspaceCommentOption( // The position of the new comment in pixels relative to the origin of the // main workspace. - const finalOffset = - Coordinate.difference(clientOffsetPixels, mainOffsetPixels); + const finalOffset = Coordinate.difference( + clientOffsetPixels, + mainOffsetPixels + ); // The position of the new comment in main workspace coordinates. finalOffset.scale(1 / ws.scale); @@ -350,7 +369,7 @@ export function workspaceCommentOption( enabled: true, } as ContextMenuOption; wsCommentOption.text = Msg['ADD_COMMENT']; - wsCommentOption.callback = function() { + wsCommentOption.callback = function () { addWsComment(); }; return wsCommentOption; diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 67d035307..ab0b104fc 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -9,15 +9,19 @@ goog.declareModuleId('Blockly.ContextMenuItems'); import type {BlockSvg} from './block_svg.js'; import * as clipboard from './clipboard.js'; -import {ContextMenuRegistry, RegistryItem, Scope} from './contextmenu_registry.js'; +import { + ContextMenuRegistry, + RegistryItem, + Scope, +} from './contextmenu_registry.js'; import * as dialog from './dialog.js'; import * as Events from './events/events.js'; import * as eventUtils from './events/utils.js'; -import {inputTypes} from './input_types.js'; +import {CommentIcon} from './icons/comment_icon.js'; import {Msg} from './msg.js'; +import {StatementInput} from './renderers/zelos/zelos.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Option to undo previous action. */ @@ -111,7 +115,7 @@ function toggleOption_(shouldCollapse: boolean, topBlocks: BlockSvg[]) { } Events.setGroup(true); for (let i = 0; i < topBlocks.length; i++) { - let block: BlockSvg|null = topBlocks[i]; + let block: BlockSvg | null = topBlocks[i]; while (block) { timeoutCounter++; setTimeout(timeoutFn.bind(null, block), ms); @@ -133,7 +137,7 @@ export function registerCollapse() { if (scope.workspace!.options.collapse) { const topBlocks = scope.workspace!.getTopBlocks(false); for (let i = 0; i < topBlocks.length; i++) { - let block: BlockSvg|null = topBlocks[i]; + let block: BlockSvg | null = topBlocks[i]; while (block) { if (!block.isCollapsed()) { return 'enabled'; @@ -167,7 +171,7 @@ export function registerExpand() { if (scope.workspace!.options.collapse) { const topBlocks = scope.workspace!.getTopBlocks(false); for (let i = 0; i < topBlocks.length; i++) { - let block: BlockSvg|null = topBlocks[i]; + let block: BlockSvg | null = topBlocks[i]; while (block) { if (block.isCollapsed()) { return 'enabled'; @@ -280,13 +284,16 @@ export function registerDeleteAll() { deleteNext_(deletableBlocks); } else { dialog.confirm( - Msg['DELETE_ALL_BLOCKS'].replace( - '%1', String(deletableBlocks.length)), - function(ok) { - if (ok) { - deleteNext_(deletableBlocks); - } - }); + Msg['DELETE_ALL_BLOCKS'].replace( + '%1', + String(deletableBlocks.length) + ), + function (ok) { + if (ok) { + deleteNext_(deletableBlocks); + } + } + ); } }, scopeType: ContextMenuRegistry.ScopeType.WORKSPACE, @@ -341,7 +348,7 @@ export function registerDuplicate() { export function registerComment() { const commentOption: RegistryItem = { displayText(scope: Scope) { - if (scope.block!.getCommentIcon()) { + if (scope.block!.hasIcon(CommentIcon.TYPE)) { // If there's already a comment, option is to remove. return Msg['REMOVE_COMMENT']; } @@ -350,15 +357,19 @@ export function registerComment() { }, preconditionFn(scope: Scope) { const block = scope.block; - if (!block!.isInFlyout && block!.workspace.options.comments && - !block!.isCollapsed() && block!.isEditable()) { + if ( + !block!.isInFlyout && + block!.workspace.options.comments && + !block!.isCollapsed() && + block!.isEditable() + ) { return 'enabled'; } return 'hidden'; }, callback(scope: Scope) { const block = scope.block; - if (block!.getCommentIcon()) { + if (block!.hasIcon(CommentIcon.TYPE)) { block!.setCommentText(null); } else { block!.setCommentText(''); @@ -377,8 +388,9 @@ export function registerComment() { export function registerInline() { const inlineOption: RegistryItem = { displayText(scope: Scope) { - return scope.block!.getInputsInline() ? Msg['EXTERNAL_INPUTS'] : - Msg['INLINE_INPUTS']; + return scope.block!.getInputsInline() + ? Msg['EXTERNAL_INPUTS'] + : Msg['INLINE_INPUTS']; }, preconditionFn(scope: Scope) { const block = scope.block; @@ -386,8 +398,10 @@ export function registerInline() { for (let i = 1; i < block!.inputList.length; i++) { // Only display this option if there are two value or dummy inputs // next to each other. - if (block!.inputList[i - 1].type !== inputTypes.STATEMENT && - block!.inputList[i].type !== inputTypes.STATEMENT) { + if ( + !(block!.inputList[i - 1] instanceof StatementInput) && + !(block!.inputList[i] instanceof StatementInput) + ) { return 'enabled'; } } @@ -410,13 +424,17 @@ export function registerInline() { export function registerCollapseExpandBlock() { const collapseExpandOption: RegistryItem = { displayText(scope: Scope) { - return scope.block!.isCollapsed() ? Msg['EXPAND_BLOCK'] : - Msg['COLLAPSE_BLOCK']; + return scope.block!.isCollapsed() + ? Msg['EXPAND_BLOCK'] + : Msg['COLLAPSE_BLOCK']; }, preconditionFn(scope: Scope) { const block = scope.block; - if (!block!.isInFlyout && block!.isMovable() && - block!.workspace.options.collapse) { + if ( + !block!.isInFlyout && + block!.isMovable() && + block!.workspace.options.collapse + ) { return 'enabled'; } return 'hidden'; @@ -437,13 +455,17 @@ export function registerCollapseExpandBlock() { export function registerDisable() { const disableOption: RegistryItem = { displayText(scope: Scope) { - return scope.block!.isEnabled() ? Msg['DISABLE_BLOCK'] : - Msg['ENABLE_BLOCK']; + return scope.block!.isEnabled() + ? Msg['DISABLE_BLOCK'] + : Msg['ENABLE_BLOCK']; }, preconditionFn(scope: Scope) { const block = scope.block; - if (!block!.isInFlyout && block!.workspace.options.disable && - block!.isEditable()) { + if ( + !block!.isInFlyout && + block!.workspace.options.disable && + block!.isEditable() + ) { if (block!.getInheritedDisabled()) { return 'disabled'; } @@ -481,9 +503,9 @@ export function registerDelete() { // Blocks in the current stack would survive this block's deletion. descendantCount -= nextBlock.getDescendants(false).length; } - return descendantCount === 1 ? - Msg['DELETE_BLOCK'] : - Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`); + return descendantCount === 1 + ? Msg['DELETE_BLOCK'] + : Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`); }, preconditionFn(scope: Scope) { if (!scope.block!.isInFlyout && scope.block!.isDeletable()) { @@ -513,8 +535,10 @@ export function registerHelp() { }, preconditionFn(scope: Scope) { const block = scope.block; - const url = typeof block!.helpUrl === 'function' ? block!.helpUrl() : - block!.helpUrl; + const url = + typeof block!.helpUrl === 'function' + ? block!.helpUrl() + : block!.helpUrl; if (url) { return 'enabled'; } diff --git a/core/contextmenu_registry.ts b/core/contextmenu_registry.ts index 6f3eb5e1c..cd7534a63 100644 --- a/core/contextmenu_registry.ts +++ b/core/contextmenu_registry.ts @@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.ContextMenuRegistry'); import type {BlockSvg} from './block_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for the registry of context menu items. This is intended to be a * singleton. You should not create a new instance, and only access this class @@ -66,7 +65,7 @@ export class ContextMenuRegistry { * @param id The ID of the RegistryItem to get. * @returns RegistryItem or null if not found */ - getItem(id: string): RegistryItem|null { + getItem(id: string): RegistryItem | null { return this.registry_.get(id) ?? null; } @@ -81,16 +80,19 @@ export class ContextMenuRegistry { * block being clicked on) * @returns the list of ContextMenuOptions */ - getContextMenuOptions(scopeType: ScopeType, scope: Scope): - ContextMenuOption[] { + getContextMenuOptions( + scopeType: ScopeType, + scope: Scope + ): ContextMenuOption[] { const menuOptions: ContextMenuOption[] = []; for (const item of this.registry_.values()) { if (scopeType === item.scopeType) { const precondition = item.preconditionFn(scope); if (precondition !== 'hidden') { - const displayText = typeof item.displayText === 'function' ? - item.displayText(scope) : - item.displayText; + const displayText = + typeof item.displayText === 'function' + ? item.displayText(scope) + : item.displayText; const menuOption: ContextMenuOption = { text: displayText, enabled: precondition === 'enabled', @@ -102,7 +104,7 @@ export class ContextMenuRegistry { } } } - menuOptions.sort(function(a, b) { + menuOptions.sort(function (a, b) { return a.weight - b.weight; }); return menuOptions; @@ -135,7 +137,7 @@ export namespace ContextMenuRegistry { export interface RegistryItem { callback: (p1: Scope) => void; scopeType: ScopeType; - displayText: ((p1: Scope) => string)|string; + displayText: ((p1: Scope) => string) | string; preconditionFn: (p1: Scope) => string; weight: number; id: string; @@ -175,4 +177,4 @@ export type Scope = ContextMenuRegistry.Scope; export type RegistryItem = ContextMenuRegistry.RegistryItem; export type ContextMenuOption = ContextMenuRegistry.ContextMenuOption; export type LegacyContextMenuOption = - ContextMenuRegistry.LegacyContextMenuOption; + ContextMenuRegistry.LegacyContextMenuOption; diff --git a/core/css.ts b/core/css.ts index f473aacf7..4214a72bc 100644 --- a/core/css.ts +++ b/core/css.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.Css'); - /** Has CSS already been injected? */ let injected = false; @@ -89,31 +88,6 @@ let content = ` -webkit-user-select: none; } -.blocklyWsDragSurface { - display: none; - position: absolute; - top: 0; - left: 0; -} - -/* Added as a separate rule with multiple classes to make it more specific - than a bootstrap rule that selects svg:root. See issue #1275 for context. -*/ -.blocklyWsDragSurface.blocklyOverflowVisible { - overflow: visible; -} - -.blocklyBlockDragSurface { - display: none; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: visible !important; - z-index: 50; /* Display below toolbox, but above everything else. */ -} - .blocklyBlockCanvas.blocklyCanvasTransitioning, .blocklyBubbleCanvas.blocklyCanvasTransitioning { transition: transform .5s; @@ -242,14 +216,6 @@ let content = ` cursor: -webkit-grabbing; } -/* Change the cursor on the whole drag surface in case the mouse gets - ahead of block during a drag. This way the cursor is still a closed hand. - */ -.blocklyBlockDragSurface .blocklyDraggable { - cursor: grabbing; - cursor: -webkit-grabbing; -} - .blocklyDragging.blocklyDraggingDelete { cursor: url("<<>>/handdelete.cur"), auto; } @@ -302,8 +268,7 @@ let content = ` Don't allow users to select text. It gets annoying when trying to drag a block and selected text moves instead. */ -.blocklySvg text, -.blocklyBlockDragSurface text { +.blocklySvg text { user-select: none; -ms-user-select: none; -webkit-user-select: none; diff --git a/core/delete_area.ts b/core/delete_area.ts index 576d2475a..90fcb62bc 100644 --- a/core/delete_area.ts +++ b/core/delete_area.ts @@ -18,7 +18,6 @@ import {DragTarget} from './drag_target.js'; import type {IDeleteArea} from './interfaces/i_delete_area.js'; import type {IDraggable} from './interfaces/i_draggable.js'; - /** * Abstract class for a component that can delete a block or bubble that is * dropped on top of it. @@ -59,7 +58,7 @@ export class DeleteArea extends DragTarget implements IDeleteArea { */ wouldDelete(element: IDraggable, couldConnect: boolean): boolean { if (element instanceof BlockSvg) { - const block = (element); + const block = element; const couldDeleteBlock = !block.getParent() && block.isDeletable(); this.updateWouldDelete_(couldDeleteBlock && !couldConnect); } else { diff --git a/core/dialog.ts b/core/dialog.ts index 4c5d17852..046b40c13 100644 --- a/core/dialog.ts +++ b/core/dialog.ts @@ -7,22 +7,28 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.dialog'); - -let alertImplementation = function(message: string, opt_callback?: () => void) { +let alertImplementation = function ( + message: string, + opt_callback?: () => void +) { window.alert(message); if (opt_callback) { opt_callback(); } }; -let confirmImplementation = function( - message: string, callback: (result: boolean) => void) { +let confirmImplementation = function ( + message: string, + callback: (result: boolean) => void +) { callback(window.confirm(message)); }; -let promptImplementation = function( - message: string, defaultValue: string, - callback: (result: string|null) => void) { +let promptImplementation = function ( + message: string, + defaultValue: string, + callback: (result: string | null) => void +) { callback(window.prompt(message, defaultValue)); }; @@ -65,7 +71,6 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) { confirmImplementation(message, callback); } - /** * Sets the function to be run when Blockly.dialog.confirm() is called. * @@ -73,7 +78,8 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) { * @see Blockly.dialog.confirm */ export function setConfirm( - confirmFunction: (p1: string, p2: (p1: boolean) => void) => void) { + confirmFunction: (p1: string, p2: (p1: boolean) => void) => void +) { confirmImplementation = confirmFunction; } @@ -88,8 +94,10 @@ export function setConfirm( * @param callback The callback for handling user response. */ export function prompt( - message: string, defaultValue: string, - callback: (p1: string|null) => void) { + message: string, + defaultValue: string, + callback: (p1: string | null) => void +) { promptImplementation(message, defaultValue, callback); } @@ -100,8 +108,12 @@ export function prompt( * @see Blockly.dialog.prompt */ export function setPrompt( - promptFunction: (p1: string, p2: string, p3: (p1: string|null) => void) => - void) { + promptFunction: ( + p1: string, + p2: string, + p3: (p1: string | null) => void + ) => void +) { promptImplementation = promptFunction; } diff --git a/core/drag_target.ts b/core/drag_target.ts index 5ef9d82cc..b6c2faa9f 100644 --- a/core/drag_target.ts +++ b/core/drag_target.ts @@ -17,7 +17,6 @@ import type {IDragTarget} from './interfaces/i_drag_target.js'; import type {IDraggable} from './interfaces/i_draggable.js'; import type {Rect} from './utils/rect.js'; - /** * Abstract class for a component with custom behaviour when a block or bubble * is dragged over or dropped on top of it. @@ -76,7 +75,7 @@ export class DragTarget implements IDragTarget { * @returns The component's bounding box. Null if drag target area should be * ignored. */ - getClientRect(): Rect|null { + getClientRect(): Rect | null { return null; } diff --git a/core/dropdowndiv.ts b/core/dropdowndiv.ts index a1a254d03..f4b69b00a 100644 --- a/core/dropdowndiv.ts +++ b/core/dropdowndiv.ts @@ -23,7 +23,6 @@ import type {Size} from './utils/size.js'; import * as style from './utils/style.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Arrow size in px. Should match the value in CSS * (need to position pre-render). @@ -52,10 +51,10 @@ export const ANIMATION_TIME = 0.25; * Timer for animation out, to be cleared if we need to immediately hide * without disrupting new shows. */ -let animateOutTimer: ReturnType|null = null; +let animateOutTimer: ReturnType | null = null; /** Callback for when the drop-down is hidden. */ -let onHide: Function|null = null; +let onHide: Function | null = null; /** A class name representing the current owner's workspace renderer. */ let renderedClassName = ''; @@ -76,13 +75,13 @@ let arrow: HTMLDivElement; * Drop-downs will appear within the bounds of this element if possible. * Set in setBoundsElement. */ -let boundsElement: Element|null = null; +let boundsElement: Element | null = null; /** The object currently using the drop-down. */ -let owner: Field|null = null; +let owner: Field | null = null; /** Whether the dropdown was positioned to a field or the source block. */ -let positionToField: boolean|null = null; +let positionToField: boolean | null = null; /** * Dropdown bounds info object used to encapsulate sizing information about a @@ -103,9 +102,9 @@ export interface PositionMetrics { initialY: number; finalX: number; finalY: number; - arrowX: number|null; - arrowY: number|null; - arrowAtTop: boolean|null; + arrowX: number | null; + arrowY: number | null; + arrowAtTop: boolean | null; arrowVisible: boolean; } @@ -116,7 +115,7 @@ export interface PositionMetrics { */ export function createDom() { if (div) { - return; // Already created. + return; // Already created. } div = document.createElement('div'); div.className = 'blocklyDropDownDiv'; @@ -133,15 +132,15 @@ export function createDom() { div.style.opacity = '0'; // Transition animation for transform: translate() and opacity. - div.style.transition = 'transform ' + ANIMATION_TIME + 's, ' + - 'opacity ' + ANIMATION_TIME + 's'; + div.style.transition = + 'transform ' + ANIMATION_TIME + 's, ' + 'opacity ' + ANIMATION_TIME + 's'; // Handle focusin/out events to add a visual indicator when // a child is focused or blurred. - div.addEventListener('focusin', function() { + div.addEventListener('focusin', function () { dom.addClass(div, 'blocklyFocused'); }); - div.addEventListener('focusout', function() { + div.addEventListener('focusout', function () { dom.removeClass(div, 'blocklyFocused'); }); } @@ -152,14 +151,14 @@ export function createDom() { * * @param boundsElem Element to bind drop-down to. */ -export function setBoundsElement(boundsElem: Element|null) { +export function setBoundsElement(boundsElem: Element | null) { boundsElement = boundsElem; } /** * @returns The field that currently owns this, or null. */ -export function getOwner(): Field|null { +export function getOwner(): Field | null { return owner; } @@ -202,11 +201,17 @@ export function setColour(backgroundColour: string, borderColour: string) { * @returns True if the menu rendered below block; false if above. */ export function showPositionedByBlock( - field: Field, block: BlockSvg, opt_onHide?: Function, - opt_secondaryYOffset?: number): boolean { + field: Field, + block: BlockSvg, + opt_onHide?: Function, + opt_secondaryYOffset?: number +): boolean { return showPositionedByRect( - getScaledBboxOfBlock(block), field as Field, opt_onHide, - opt_secondaryYOffset); + getScaledBboxOfBlock(block), + field as Field, + opt_onHide, + opt_secondaryYOffset + ); } /** @@ -221,12 +226,17 @@ export function showPositionedByBlock( * @returns True if the menu rendered below block; false if above. */ export function showPositionedByField( - field: Field, opt_onHide?: Function, - opt_secondaryYOffset?: number): boolean { + field: Field, + opt_onHide?: Function, + opt_secondaryYOffset?: number +): boolean { positionToField = true; return showPositionedByRect( - getScaledBboxOfField(field as Field), field as Field, opt_onHide, - opt_secondaryYOffset); + getScaledBboxOfField(field as Field), + field as Field, + opt_onHide, + opt_secondaryYOffset + ); } /** * Get the scaled bounding box of a block. @@ -267,8 +277,11 @@ function getScaledBboxOfField(field: Field): Rect { * @returns True if the menu rendered below block; false if above. */ function showPositionedByRect( - bBox: Rect, field: Field, opt_onHide?: Function, - opt_secondaryYOffset?: number): boolean { + bBox: Rect, + field: Field, + opt_onHide?: Function, + opt_secondaryYOffset?: number +): boolean { // If we can fit it, render below the block. const primaryX = bBox.left + (bBox.right - bBox.left) / 2; const primaryY = bBox.bottom; @@ -286,8 +299,14 @@ function showPositionedByRect( } setBoundsElement(workspace.getParentSvg().parentNode as Element | null); return show( - field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY, - opt_onHide); + field, + sourceBlock.RTL, + primaryX, + primaryY, + secondaryX, + secondaryY, + opt_onHide + ); } /** @@ -310,8 +329,14 @@ function showPositionedByRect( * @internal */ export function show( - newOwner: Field, rtl: boolean, primaryX: number, primaryY: number, - secondaryX: number, secondaryY: number, opt_onHide?: Function): boolean { + newOwner: Field, + rtl: boolean, + primaryX: number, + primaryY: number, + secondaryX: number, + secondaryY: number, + opt_onHide?: Function +): boolean { owner = newOwner as Field; onHide = opt_onHide || null; // Set direction. @@ -345,7 +370,7 @@ const internal = { * @returns An object containing size information about the bounding element * (bounding box and width/height). */ - getBoundsInfo: function(): BoundsInfo { + getBoundsInfo: function (): BoundsInfo { const boundPosition = style.getPageOffset(boundsElement as Element); const boundSize = style.getSize(boundsElement as Element); @@ -370,9 +395,12 @@ const internal = { * @returns Various final metrics, including rendered positions for drop-down * and arrow. */ - getPositionMetrics: function( - primaryX: number, primaryY: number, secondaryX: number, - secondaryY: number): PositionMetrics { + getPositionMetrics: function ( + primaryX: number, + primaryY: number, + secondaryX: number, + secondaryY: number + ): PositionMetrics { const boundsInfo = internal.getBoundsInfo(); const divSize = style.getSize(div as Element); @@ -383,7 +411,11 @@ const internal = { // Can we fit in-bounds above the target? if (secondaryY - divSize.height > boundsInfo.top) { return getPositionAboveMetrics( - secondaryX, secondaryY, boundsInfo, divSize); + secondaryX, + secondaryY, + boundsInfo, + divSize + ); } // Can we fit outside the workspace bounds (but inside the window) // below? @@ -394,7 +426,11 @@ const internal = { // above? if (secondaryY - divSize.height > document.documentElement.clientTop) { return getPositionAboveMetrics( - secondaryX, secondaryY, boundsInfo, divSize); + secondaryX, + secondaryY, + boundsInfo, + divSize + ); } // Last resort, render at top of page. @@ -415,10 +451,17 @@ const internal = { * and arrow. */ function getPositionBelowMetrics( - primaryX: number, primaryY: number, boundsInfo: BoundsInfo, - divSize: Size): PositionMetrics { - const xCoords = - getPositionX(primaryX, boundsInfo.left, boundsInfo.right, divSize.width); + primaryX: number, + primaryY: number, + boundsInfo: BoundsInfo, + divSize: Size +): PositionMetrics { + const xCoords = getPositionX( + primaryX, + boundsInfo.left, + boundsInfo.right, + divSize.width + ); const arrowY = -(ARROW_SIZE / 2 + BORDER_SIZE); const finalY = primaryY + PADDING_Y; @@ -426,7 +469,7 @@ function getPositionBelowMetrics( return { initialX: xCoords.divX, initialY: primaryY, - finalX: xCoords.divX, // X position remains constant during animation. + finalX: xCoords.divX, // X position remains constant during animation. finalY, arrowX: xCoords.arrowX, arrowY, @@ -448,19 +491,26 @@ function getPositionBelowMetrics( * and arrow. */ function getPositionAboveMetrics( - secondaryX: number, secondaryY: number, boundsInfo: BoundsInfo, - divSize: Size): PositionMetrics { + secondaryX: number, + secondaryY: number, + boundsInfo: BoundsInfo, + divSize: Size +): PositionMetrics { const xCoords = getPositionX( - secondaryX, boundsInfo.left, boundsInfo.right, divSize.width); + secondaryX, + boundsInfo.left, + boundsInfo.right, + divSize.width + ); const arrowY = divSize.height - BORDER_SIZE * 2 - ARROW_SIZE / 2; const finalY = secondaryY - divSize.height - PADDING_Y; - const initialY = secondaryY - divSize.height; // No padding on Y. + const initialY = secondaryY - divSize.height; // No padding on Y. return { initialX: xCoords.divX, initialY, - finalX: xCoords.divX, // X position remains constant during animation. + finalX: xCoords.divX, // X position remains constant during animation. finalY, arrowX: xCoords.arrowX, arrowY, @@ -481,16 +531,23 @@ function getPositionAboveMetrics( * and arrow. */ function getPositionTopOfPageMetrics( - sourceX: number, boundsInfo: BoundsInfo, divSize: Size): PositionMetrics { - const xCoords = - getPositionX(sourceX, boundsInfo.left, boundsInfo.right, divSize.width); + sourceX: number, + boundsInfo: BoundsInfo, + divSize: Size +): PositionMetrics { + const xCoords = getPositionX( + sourceX, + boundsInfo.left, + boundsInfo.right, + divSize.width + ); // No need to provide arrow-specific information because it won't be visible. return { initialX: xCoords.divX, initialY: 0, - finalX: xCoords.divX, // X position remains constant during animation. - finalY: 0, // Y position remains constant during animation. + finalX: xCoords.divX, // X position remains constant during animation. + finalY: 0, // Y position remains constant during animation. arrowAtTop: null, arrowX: null, arrowY: null, @@ -511,8 +568,11 @@ function getPositionTopOfPageMetrics( * @internal */ export function getPositionX( - sourceX: number, boundsLeft: number, boundsRight: number, - divWidth: number): {divX: number, arrowX: number} { + sourceX: number, + boundsLeft: number, + boundsRight: number, + divWidth: number +): {divX: number; arrowX: number} { let divX = sourceX; // Offset the topLeft coord so that the dropdowndiv is centered. divX -= divWidth / 2; @@ -527,7 +587,10 @@ export function getPositionX( const horizPadding = ARROW_HORIZONTAL_PADDING; // Clamp the arrow position so that it stays attached to the dropdowndiv. relativeArrowX = math.clamp( - horizPadding, relativeArrowX, divWidth - horizPadding - ARROW_SIZE); + horizPadding, + relativeArrowX, + divWidth - horizPadding - ARROW_SIZE + ); return {arrowX: relativeArrowX, divX}; } @@ -550,7 +613,9 @@ export function isVisible(): boolean { * @returns True if hidden. */ export function hideIfOwner( - divOwner: Field, opt_withoutAnimation?: boolean): boolean { + divOwner: Field, + opt_withoutAnimation?: boolean +): boolean { if (owner === divOwner) { if (opt_withoutAnimation) { hideWithoutAnimation(); @@ -569,7 +634,7 @@ export function hide() { div.style.transform = 'translate(0, 0)'; div.style.opacity = '0'; // Finish animation - reset all values to default. - animateOutTimer = setTimeout(function() { + animateOutTimer = setTimeout(function () { hideWithoutAnimation(); }, ANIMATION_TIME * 1000); if (onHide) { @@ -625,20 +690,33 @@ export function hideWithoutAnimation() { * @returns True if the menu rendered at the primary origin point. */ function positionInternal( - primaryX: number, primaryY: number, secondaryX: number, - secondaryY: number): boolean { - const metrics = - internal.getPositionMetrics(primaryX, primaryY, secondaryX, secondaryY); + primaryX: number, + primaryY: number, + secondaryX: number, + secondaryY: number +): boolean { + const metrics = internal.getPositionMetrics( + primaryX, + primaryY, + secondaryX, + secondaryY + ); // Update arrow CSS. if (metrics.arrowVisible) { arrow.style.display = ''; - arrow.style.transform = 'translate(' + metrics.arrowX + 'px,' + - metrics.arrowY + 'px) rotate(45deg)'; + arrow.style.transform = + 'translate(' + + metrics.arrowX + + 'px,' + + metrics.arrowY + + 'px) rotate(45deg)'; arrow.setAttribute( - 'class', - metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' : - 'blocklyDropDownArrow blocklyArrowBottom'); + 'class', + metrics.arrowAtTop + ? 'blocklyDropDownArrow blocklyArrowTop' + : 'blocklyDropDownArrow blocklyArrowBottom' + ); } else { arrow.style.display = 'none'; } @@ -679,8 +757,9 @@ export function repositionForWindowResize() { // it. if (owner) { const block = owner.getSourceBlock() as BlockSvg; - const bBox = positionToField ? getScaledBboxOfField(owner) : - getScaledBboxOfBlock(block); + const bBox = positionToField + ? getScaledBboxOfField(owner) + : getScaledBboxOfBlock(block); // If we can fit it, render below the block. const primaryX = bBox.left + (bBox.right - bBox.left) / 2; const primaryY = bBox.bottom; diff --git a/core/events/events.ts b/core/events/events.ts index edea5a6ff..0bd4857ab 100644 --- a/core/events/events.ts +++ b/core/events/events.ts @@ -7,13 +7,16 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events'); - import {Abstract, AbstractEventJson} from './events_abstract.js'; import {BlockBase, BlockBaseJson} from './events_block_base.js'; import {BlockChange, BlockChangeJson} from './events_block_change.js'; import {BlockCreate, BlockCreateJson} from './events_block_create.js'; import {BlockDelete, BlockDeleteJson} from './events_block_delete.js'; import {BlockDrag, BlockDragJson} from './events_block_drag.js'; +import { + BlockFieldIntermediateChange, + BlockFieldIntermediateChangeJson, +} from './events_block_field_intermediate_change.js'; import {BlockMove, BlockMoveJson} from './events_block_move.js'; import {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js'; import {Click, ClickJson, ClickTarget} from './events_click.js'; @@ -25,9 +28,11 @@ import {CommentMove, CommentMoveJson} from './events_comment_move.js'; import {MarkerMove, MarkerMoveJson} from './events_marker_move.js'; import {Selected, SelectedJson} from './events_selected.js'; import {ThemeChange, ThemeChangeJson} from './events_theme_change.js'; -import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js'; +import { + ToolboxItemSelect, + ToolboxItemSelectJson, +} from './events_toolbox_item_select.js'; import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js'; -import {Ui} from './events_ui.js'; import {UiBase} from './events_ui_base.js'; import {VarBase, VarBaseJson} from './events_var_base.js'; import {VarCreate, VarCreateJson} from './events_var_create.js'; @@ -35,8 +40,7 @@ import {VarDelete, VarDeleteJson} from './events_var_delete.js'; import {VarRename, VarRenameJson} from './events_var_rename.js'; import {ViewportChange, ViewportChangeJson} from './events_viewport.js'; import * as eventUtils from './utils.js'; -import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js'; - +import {FinishedLoading} from './workspace_events.js'; // Events. export {Abstract}; @@ -54,6 +58,8 @@ export {BlockDelete}; export {BlockDeleteJson}; export {BlockDrag}; export {BlockDragJson}; +export {BlockFieldIntermediateChange}; +export {BlockFieldIntermediateChangeJson}; export {BlockMove}; export {BlockMoveJson}; export {Click}; @@ -69,7 +75,6 @@ export {CommentDelete}; export {CommentMove}; export {CommentMoveJson}; export {FinishedLoading}; -export {FinishedLoadingJson}; export {MarkerMove}; export {MarkerMoveJson}; export {Selected}; @@ -80,7 +85,6 @@ export {ToolboxItemSelect}; export {ToolboxItemSelectJson}; export {TrashcanOpen}; export {TrashcanOpenJson}; -export {Ui}; export {UiBase}; export {VarBase}; export {VarBaseJson}; @@ -99,6 +103,8 @@ export const BLOCK_CREATE = eventUtils.BLOCK_CREATE; export const BLOCK_DELETE = eventUtils.BLOCK_DELETE; export const BLOCK_DRAG = eventUtils.BLOCK_DRAG; export const BLOCK_MOVE = eventUtils.BLOCK_MOVE; +export const BLOCK_FIELD_INTERMEDIATE_CHANGE = + eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE; export const BUBBLE_OPEN = eventUtils.BUBBLE_OPEN; export type BumpEvent = eventUtils.BumpEvent; export const BUMP_EVENTS = eventUtils.BUMP_EVENTS; diff --git a/core/events/events_abstract.ts b/core/events/events_abstract.ts index a20d8c6b0..4e6b6de12 100644 --- a/core/events/events_abstract.ts +++ b/core/events/events_abstract.ts @@ -13,13 +13,11 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.Abstract'); -import * as deprecation from '../utils/deprecation.js'; import * as common from '../common.js'; import type {Workspace} from '../workspace.js'; import * as eventUtils from './utils.js'; - /** * Abstract class for an event. */ @@ -67,19 +65,6 @@ export abstract class Abstract { }; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - fromJson(json: AbstractEventJson) { - deprecation.warn( - 'Blockly.Events.Abstract.prototype.fromJson', 'version 9', 'version 10', - 'Blockly.Events.fromJson'); - this.isBlank = false; - this.group = json['group'] || ''; - } - /** * Deserializes the JSON event. * @@ -89,8 +74,11 @@ export abstract class Abstract { * supertypes of parameters to static methods in superclasses. * @internal */ - static fromJson(json: AbstractEventJson, workspace: Workspace, event: any): - Abstract { + static fromJson( + json: AbstractEventJson, + workspace: Workspace, + event: any + ): Abstract { event.isBlank = false; event.group = json['group'] || ''; event.workspaceId = workspace.id; @@ -130,8 +118,9 @@ export abstract class Abstract { } if (!workspace) { throw Error( - 'Workspace is null. Event must have been generated from real' + - ' Blockly events.'); + 'Workspace is null. Event must have been generated from real' + + ' Blockly events.' + ); } return workspace; } diff --git a/core/events/events_block_base.ts b/core/events/events_block_base.ts index b279af43b..10fb84278 100644 --- a/core/events/events_block_base.ts +++ b/core/events/events_block_base.ts @@ -13,11 +13,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.BlockBase'); import type {Block} from '../block.js'; -import * as deprecation from '../utils/deprecation.js'; import type {Workspace} from '../workspace.js'; -import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; - +import { + Abstract as AbstractEvent, + AbstractEventJson, +} from './events_abstract.js'; /** * Abstract class for any event related to blocks. @@ -51,26 +52,14 @@ export class BlockBase extends AbstractEvent { const json = super.toJson() as BlockBaseJson; if (!this.blockId) { throw new Error( - 'The block ID is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } json['blockId'] = this.blockId; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BlockBaseJson) { - deprecation.warn( - 'Blockly.Events.BlockBase.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.blockId = json['blockId']; - } - /** * Deserializes the JSON event. * @@ -80,10 +69,16 @@ export class BlockBase extends AbstractEvent { * static methods in superclasses. * @internal */ - static fromJson(json: BlockBaseJson, workspace: Workspace, event?: any): - BlockBase { - const newEvent = - super.fromJson(json, workspace, event ?? new BlockBase()) as BlockBase; + static fromJson( + json: BlockBaseJson, + workspace: Workspace, + event?: any + ): BlockBase { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockBase() + ) as BlockBase; newEvent.blockId = json['blockId']; return newEvent; } diff --git a/core/events/events_block_change.ts b/core/events/events_block_change.ts index 7218ea5d0..ff638266f 100644 --- a/core/events/events_block_change.ts +++ b/core/events/events_block_change.ts @@ -14,7 +14,8 @@ goog.declareModuleId('Blockly.Events.BlockChange'); import type {Block} from '../block.js'; import type {BlockSvg} from '../block_svg.js'; -import * as deprecation from '../utils/deprecation.js'; +import {IconType} from '../icons/icon_types.js'; +import {hasBubble} from '../interfaces/i_has_bubble.js'; import * as registry from '../registry.js'; import * as utilsXml from '../utils/xml.js'; import {Workspace} from '../workspace.js'; @@ -23,7 +24,6 @@ import * as Xml from '../xml.js'; import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; - /** * Notifies listeners when some element of a block has changed (e.g. * field values, comments, etc). @@ -53,12 +53,16 @@ export class BlockChange extends BlockBase { * @param opt_newValue New value of element. */ constructor( - opt_block?: Block, opt_element?: string, opt_name?: string|null, - opt_oldValue?: unknown, opt_newValue?: unknown) { + opt_block?: Block, + opt_element?: string, + opt_name?: string | null, + opt_oldValue?: unknown, + opt_newValue?: unknown + ) { super(opt_block); if (!opt_block) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.element = opt_element; this.name = opt_name || undefined; @@ -75,8 +79,9 @@ export class BlockChange extends BlockBase { const json = super.toJson() as BlockChangeJson; if (!this.element) { throw new Error( - 'The changed element is undefined. Either pass an ' + - 'element to the constructor, or call fromJson'); + 'The changed element is undefined. Either pass an ' + + 'element to the constructor, or call fromJson' + ); } json['element'] = this.element; json['name'] = this.name; @@ -85,22 +90,6 @@ export class BlockChange extends BlockBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BlockChangeJson) { - deprecation.warn( - 'Blockly.Events.BlockChange.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.element = json['element']; - this.name = json['name']; - this.oldValue = json['oldValue']; - this.newValue = json['newValue']; - } - /** * Deserializes the JSON event. * @@ -110,11 +99,16 @@ export class BlockChange extends BlockBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: BlockChangeJson, workspace: Workspace, event?: any): - BlockChange { - const newEvent = - super.fromJson(json, workspace, event ?? new BlockChange()) as - BlockChange; + static fromJson( + json: BlockChangeJson, + workspace: Workspace, + event?: any + ): BlockChange { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockChange() + ) as BlockChange; newEvent.element = json['element']; newEvent.name = json['name']; newEvent.oldValue = json['oldValue']; @@ -140,20 +134,22 @@ export class BlockChange extends BlockBase { const workspace = this.getEventWorkspace_(); if (!this.blockId) { throw new Error( - 'The block ID is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } const block = workspace.getBlockById(this.blockId); if (!block) { throw new Error( - 'The associated block is undefined. Either pass a ' + - 'block to the constructor, or call fromJson'); + 'The associated block is undefined. Either pass a ' + + 'block to the constructor, or call fromJson' + ); } // Assume the block is rendered so that then we can check. - const blockSvg = block as BlockSvg; - if (blockSvg.mutator) { + const icon = block.getIcon(IconType.MUTATOR); + if (icon && hasBubble(icon) && icon.bubbleIsVisible()) { // Close the mutator (if open) since we don't want to update it. - blockSvg.mutator.setVisible(false); + icon.setBubbleVisible(false); } const value = forward ? this.newValue : this.oldValue; switch (this.element) { @@ -162,12 +158,12 @@ export class BlockChange extends BlockBase { if (field) { field.setValue(value); } else { - console.warn('Can\'t set non-existent field: ' + this.name); + console.warn("Can't set non-existent field: " + this.name); } break; } case 'comment': - block.setCommentText(value as string || null); + block.setCommentText((value as string) || null); break; case 'collapsed': block.setCollapsed(!!value); @@ -181,13 +177,15 @@ export class BlockChange extends BlockBase { case 'mutation': { const oldState = BlockChange.getExtraBlockState_(block as BlockSvg); if (block.loadExtraState) { - block.loadExtraState(JSON.parse(value as string || '{}')); + block.loadExtraState(JSON.parse((value as string) || '{}')); } else if (block.domToMutation) { block.domToMutation( - utilsXml.textToDom(value as string || '')); + utilsXml.textToDom((value as string) || '') + ); } eventUtils.fire( - new BlockChange(block, 'mutation', null, oldState, value)); + new BlockChange(block, 'mutation', null, oldState, value) + ); break; } default: diff --git a/core/events/events_block_create.ts b/core/events/events_block_create.ts index 7e09ed581..285a5b9e8 100644 --- a/core/events/events_block_create.ts +++ b/core/events/events_block_create.ts @@ -13,7 +13,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.BlockCreate'); import type {Block} from '../block.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import * as blocks from '../serialization/blocks.js'; import * as utilsXml from '../utils/xml.js'; @@ -23,7 +22,6 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; import {Workspace} from '../workspace.js'; - /** * Notifies listeners when a block (or connected stack of blocks) is * created. @@ -32,7 +30,7 @@ export class BlockCreate extends BlockBase { override type = eventUtils.BLOCK_CREATE; /** The XML representation of the created block(s). */ - xml?: Element|DocumentFragment; + xml?: Element | DocumentFragment; /** The JSON respresentation of the created block(s). */ json?: blocks.State; @@ -45,7 +43,7 @@ export class BlockCreate extends BlockBase { super(opt_block); if (!opt_block) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } if (opt_block.isShadow()) { @@ -68,18 +66,21 @@ export class BlockCreate extends BlockBase { const json = super.toJson() as BlockCreateJson; if (!this.xml) { throw new Error( - 'The block XML is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block XML is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } if (!this.ids) { throw new Error( - 'The block IDs are undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } if (!this.json) { throw new Error( - 'The block JSON is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block JSON is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } json['xml'] = Xml.domToText(this.xml); json['ids'] = this.ids; @@ -90,24 +91,6 @@ export class BlockCreate extends BlockBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BlockCreateJson) { - deprecation.warn( - 'Blockly.Events.BlockCreate.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.xml = utilsXml.textToDom(json['xml']); - this.ids = json['ids']; - this.json = json['json'] as blocks.State; - if (json['recordUndo'] !== undefined) { - this.recordUndo = json['recordUndo']; - } - } - /** * Deserializes the JSON event. * @@ -117,11 +100,16 @@ export class BlockCreate extends BlockBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: BlockCreateJson, workspace: Workspace, event?: any): - BlockCreate { - const newEvent = - super.fromJson(json, workspace, event ?? new BlockCreate()) as - BlockCreate; + static fromJson( + json: BlockCreateJson, + workspace: Workspace, + event?: any + ): BlockCreate { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockCreate() + ) as BlockCreate; newEvent.xml = utilsXml.textToDom(json['xml']); newEvent.ids = json['ids']; newEvent.json = json['json'] as blocks.State; @@ -140,13 +128,15 @@ export class BlockCreate extends BlockBase { const workspace = this.getEventWorkspace_(); if (!this.json) { throw new Error( - 'The block JSON is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block JSON is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } if (!this.ids) { throw new Error( - 'The block IDs are undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } if (forward) { blocks.append(this.json, workspace); @@ -158,7 +148,7 @@ export class BlockCreate extends BlockBase { block.dispose(false); } else if (id === this.blockId) { // Only complain about root-level block. - console.warn('Can\'t uncreate non-existent block: ' + id); + console.warn("Can't uncreate non-existent block: " + id); } } } diff --git a/core/events/events_block_delete.ts b/core/events/events_block_delete.ts index 2331041fa..737939a65 100644 --- a/core/events/events_block_delete.ts +++ b/core/events/events_block_delete.ts @@ -13,7 +13,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.BlockDelete'); import type {Block} from '../block.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import * as blocks from '../serialization/blocks.js'; import * as utilsXml from '../utils/xml.js'; @@ -23,14 +22,13 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; import {Workspace} from '../workspace.js'; - /** * Notifies listeners when a block (or connected stack of blocks) is * deleted. */ export class BlockDelete extends BlockBase { /** The XML representation of the deleted block(s). */ - oldXml?: Element|DocumentFragment; + oldXml?: Element | DocumentFragment; /** The JSON respresentation of the deleted block(s). */ oldJson?: blocks.State; @@ -48,7 +46,7 @@ export class BlockDelete extends BlockBase { super(opt_block); if (!opt_block) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } if (opt_block.getParent()) { @@ -62,8 +60,9 @@ export class BlockDelete extends BlockBase { this.oldXml = Xml.blockToDomWithXY(opt_block); this.ids = eventUtils.getDescendantIds(opt_block); this.wasShadow = opt_block.isShadow(); - this.oldJson = - blocks.save(opt_block, {addCoordinates: true}) as blocks.State; + this.oldJson = blocks.save(opt_block, { + addCoordinates: true, + }) as blocks.State; } /** @@ -75,23 +74,27 @@ export class BlockDelete extends BlockBase { const json = super.toJson() as BlockDeleteJson; if (!this.oldXml) { throw new Error( - 'The old block XML is undefined. Either pass a block ' + - 'to the constructor, or call fromJson'); + 'The old block XML is undefined. Either pass a block ' + + 'to the constructor, or call fromJson' + ); } if (!this.ids) { throw new Error( - 'The block IDs are undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } if (this.wasShadow === undefined) { throw new Error( - 'Whether the block was a shadow is undefined. Either ' + - 'pass a block to the constructor, or call fromJson'); + 'Whether the block was a shadow is undefined. Either ' + + 'pass a block to the constructor, or call fromJson' + ); } if (!this.oldJson) { throw new Error( - 'The old block JSON is undefined. Either pass a block ' + - 'to the constructor, or call fromJson'); + 'The old block JSON is undefined. Either pass a block ' + + 'to the constructor, or call fromJson' + ); } json['oldXml'] = Xml.domToText(this.oldXml); json['ids'] = this.ids; @@ -103,26 +106,6 @@ export class BlockDelete extends BlockBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BlockDeleteJson) { - deprecation.warn( - 'Blockly.Events.BlockDelete.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.oldXml = utilsXml.textToDom(json['oldXml']); - this.ids = json['ids']; - this.wasShadow = - json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow'; - this.oldJson = json['oldJson']; - if (json['recordUndo'] !== undefined) { - this.recordUndo = json['recordUndo']; - } - } - /** * Deserializes the JSON event. * @@ -132,15 +115,20 @@ export class BlockDelete extends BlockBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: BlockDeleteJson, workspace: Workspace, event?: any): - BlockDelete { - const newEvent = - super.fromJson(json, workspace, event ?? new BlockDelete()) as - BlockDelete; + static fromJson( + json: BlockDeleteJson, + workspace: Workspace, + event?: any + ): BlockDelete { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockDelete() + ) as BlockDelete; newEvent.oldXml = utilsXml.textToDom(json['oldXml']); newEvent.ids = json['ids']; newEvent.wasShadow = - json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow'; + json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow'; newEvent.oldJson = json['oldJson']; if (json['recordUndo'] !== undefined) { newEvent.recordUndo = json['recordUndo']; @@ -157,13 +145,15 @@ export class BlockDelete extends BlockBase { const workspace = this.getEventWorkspace_(); if (!this.ids) { throw new Error( - 'The block IDs are undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block IDs are undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } if (!this.oldJson) { throw new Error( - 'The old block JSON is undefined. Either pass a block ' + - 'to the constructor, or call fromJson'); + 'The old block JSON is undefined. Either pass a block ' + + 'to the constructor, or call fromJson' + ); } if (forward) { for (let i = 0; i < this.ids.length; i++) { @@ -173,7 +163,7 @@ export class BlockDelete extends BlockBase { block.dispose(false); } else if (id === this.blockId) { // Only complain about root-level block. - console.warn('Can\'t delete non-existent block: ' + id); + console.warn("Can't delete non-existent block: " + id); } } } else { diff --git a/core/events/events_block_drag.ts b/core/events/events_block_drag.ts index 4163da907..501c36076 100644 --- a/core/events/events_block_drag.ts +++ b/core/events/events_block_drag.ts @@ -13,14 +13,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.BlockDrag'); import type {Block} from '../block.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import {Workspace} from '../workspace.js'; - /** * Notifies listeners when a block is being manually dragged/dropped. */ @@ -66,13 +64,15 @@ export class BlockDrag extends UiBase { const json = super.toJson() as BlockDragJson; if (this.isStart === undefined) { throw new Error( - 'Whether this event is the start of a drag is undefined. ' + - 'Either pass the value to the constructor, or call fromJson'); + 'Whether this event is the start of a drag is undefined. ' + + 'Either pass the value to the constructor, or call fromJson' + ); } if (this.blockId === undefined) { throw new Error( - 'The block ID is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } json['isStart'] = this.isStart; json['blockId'] = this.blockId; @@ -82,21 +82,6 @@ export class BlockDrag extends UiBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BlockDragJson) { - deprecation.warn( - 'Blockly.Events.BlockDrag.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.isStart = json['isStart']; - this.blockId = json['blockId']; - this.blocks = json['blocks']; - } - /** * Deserializes the JSON event. * @@ -106,10 +91,16 @@ export class BlockDrag extends UiBase { * static methods in superclasses.. * @internal */ - static fromJson(json: BlockDragJson, workspace: Workspace, event?: any): - BlockDrag { - const newEvent = - super.fromJson(json, workspace, event ?? new BlockDrag()) as BlockDrag; + static fromJson( + json: BlockDragJson, + workspace: Workspace, + event?: any + ): BlockDrag { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockDrag() + ) as BlockDrag; newEvent.isStart = json['isStart']; newEvent.blockId = json['blockId']; newEvent.blocks = json['blocks']; diff --git a/core/events/events_block_field_intermediate_change.ts b/core/events/events_block_field_intermediate_change.ts new file mode 100644 index 000000000..76e3c1068 --- /dev/null +++ b/core/events/events_block_field_intermediate_change.ts @@ -0,0 +1,138 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Class for an event representing an intermediate change to a block's field's + * value. + * + * @class + */ +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Events.BlockFieldIntermediateChange'); + +import type {Block} from '../block.js'; +import * as registry from '../registry.js'; +import {Workspace} from '../workspace.js'; + +import {BlockBase, BlockBaseJson} from './events_block_base.js'; +import * as eventUtils from './utils.js'; + +/** + * Notifies listeners when the value of a block's field has changed but the + * change is not yet complete, and is expected to be followed by a block change + * event. + */ +export class BlockFieldIntermediateChange extends BlockBase { + override type = eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE; + + // Intermediate events do not undo or redo. They may be fired frequently while + // the field editor widget is open. A separate BLOCK_CHANGE event is fired + // when the editor is closed, which combines all of the field value changes + // into a single change that is recorded in the undo history instead. The + // intermediate changes are important for reacting to immediate changes, but + // some event handlers would prefer to handle the less frequent final events, + // like when triggering workspace serialization. Technically, this method of + // grouping changes can result in undo perfoming actions out of order if some + // other event occurs between opening and closing the field editor, but such + // events are unlikely to cause a broken state. + override recordUndo = false; + + /** The name of the field that changed. */ + name?: string; + + /** The original value of the element. */ + oldValue: unknown; + + /** The new value of the element. */ + newValue: unknown; + + /** + * @param opt_block The changed block. Undefined for a blank event. + * @param opt_name Name of the field affected. + * @param opt_oldValue Previous value of element. + * @param opt_newValue New value of element. + */ + constructor( + opt_block?: Block, + opt_name?: string, + opt_oldValue?: unknown, + opt_newValue?: unknown + ) { + super(opt_block); + if (!opt_block) { + return; // Blank event to be populated by fromJson. + } + + this.name = opt_name; + this.oldValue = opt_oldValue; + this.newValue = opt_newValue; + } + + /** + * Encode the event as JSON. + * + * @returns JSON representation. + */ + override toJson(): BlockFieldIntermediateChangeJson { + const json = super.toJson() as BlockFieldIntermediateChangeJson; + if (!this.name) { + throw new Error( + 'The changed field name is undefined. Either pass a ' + + 'name to the constructor, or call fromJson.' + ); + } + json['name'] = this.name; + json['oldValue'] = this.oldValue; + json['newValue'] = this.newValue; + return json; + } + + /** + * Deserializes the JSON event. + * + * @param event The event to append new properties to. Should be a subclass + * of BlockFieldIntermediateChange, but we can't specify that due to the + * fact that parameters to static methods in subclasses must be supertypes + * of parameters to static methods in superclasses. + * @internal + */ + static fromJson( + json: BlockFieldIntermediateChangeJson, + workspace: Workspace, + event?: any + ): BlockFieldIntermediateChange { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockFieldIntermediateChange() + ) as BlockFieldIntermediateChange; + newEvent.name = json['name']; + newEvent.oldValue = json['oldValue']; + newEvent.newValue = json['newValue']; + return newEvent; + } + + /** + * Does this event record any change of state? + * + * @returns False if something changed. + */ + override isNull(): boolean { + return this.oldValue === this.newValue; + } +} + +export interface BlockFieldIntermediateChangeJson extends BlockBaseJson { + name: string; + newValue: unknown; + oldValue: unknown; +} + +registry.register( + registry.Type.EVENT, + eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE, + BlockFieldIntermediateChange +); diff --git a/core/events/events_block_move.ts b/core/events/events_block_move.ts index e886a06ac..de32299a9 100644 --- a/core/events/events_block_move.ts +++ b/core/events/events_block_move.ts @@ -14,7 +14,6 @@ goog.declareModuleId('Blockly.Events.BlockMove'); import type {Block} from '../block.js'; import {ConnectionType} from '../connection_type.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {Coordinate} from '../utils/coordinate.js'; @@ -22,7 +21,6 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - interface BlockLocation { parentId?: string; inputName?: string; @@ -61,11 +59,25 @@ export class BlockMove extends BlockBase { newInputName?: string; /** - * The new X and Y workspace coordinates of the block if it is a top level + * The new X and Y workspace coordinates of the block if it is a top-level * block. Undefined if it is not a top level block. */ newCoordinate?: Coordinate; + /** + * An explanation of what this move is for. Known values include: + * 'drag' -- A drag operation completed. + * 'bump' -- Block got bumped away from an invalid connection. + * 'snap' -- Block got shifted to line up with the grid. + * 'inbounds' -- Block got pushed back into a non-scrolling workspace. + * 'connect' -- Block got connected to another block. + * 'disconnect' -- Block got disconnected from another block. + * 'create' -- Block created via XML. + * 'cleanup' -- Workspace aligned top-level blocks. + * Event merging may create multiple reasons: ['drag', 'bump', 'snap']. + */ + reason?: string[]; + /** @param opt_block The moved block. Undefined for a blank event. */ constructor(opt_block?: Block) { super(opt_block); @@ -95,14 +107,19 @@ export class BlockMove extends BlockBase { json['oldParentId'] = this.oldParentId; json['oldInputName'] = this.oldInputName; if (this.oldCoordinate) { - json['oldCoordinate'] = `${Math.round(this.oldCoordinate.x)}, ` + - `${Math.round(this.oldCoordinate.y)}`; + json['oldCoordinate'] = + `${Math.round(this.oldCoordinate.x)}, ` + + `${Math.round(this.oldCoordinate.y)}`; } json['newParentId'] = this.newParentId; json['newInputName'] = this.newInputName; if (this.newCoordinate) { - json['newCoordinate'] = `${Math.round(this.newCoordinate.x)}, ` + - `${Math.round(this.newCoordinate.y)}`; + json['newCoordinate'] = + `${Math.round(this.newCoordinate.x)}, ` + + `${Math.round(this.newCoordinate.y)}`; + } + if (this.reason) { + json['reason'] = this.reason; } if (!this.recordUndo) { json['recordUndo'] = this.recordUndo; @@ -110,33 +127,6 @@ export class BlockMove extends BlockBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BlockMoveJson) { - deprecation.warn( - 'Blockly.Events.BlockMove.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.oldParentId = json['oldParentId']; - this.oldInputName = json['oldInputName']; - if (json['oldCoordinate']) { - const xy = json['oldCoordinate'].split(','); - this.oldCoordinate = new Coordinate(Number(xy[0]), Number(xy[1])); - } - this.newParentId = json['newParentId']; - this.newInputName = json['newInputName']; - if (json['newCoordinate']) { - const xy = json['newCoordinate'].split(','); - this.newCoordinate = new Coordinate(Number(xy[0]), Number(xy[1])); - } - if (json['recordUndo'] !== undefined) { - this.recordUndo = json['recordUndo']; - } - } - /** * Deserializes the JSON event. * @@ -146,10 +136,16 @@ export class BlockMove extends BlockBase { * static methods in superclasses. * @internal */ - static fromJson(json: BlockMoveJson, workspace: Workspace, event?: any): - BlockMove { - const newEvent = - super.fromJson(json, workspace, event ?? new BlockMove()) as BlockMove; + static fromJson( + json: BlockMoveJson, + workspace: Workspace, + event?: any + ): BlockMove { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BlockMove() + ) as BlockMove; newEvent.oldParentId = json['oldParentId']; newEvent.oldInputName = json['oldInputName']; if (json['oldCoordinate']) { @@ -162,6 +158,9 @@ export class BlockMove extends BlockBase { const xy = json['newCoordinate'].split(','); newEvent.newCoordinate = new Coordinate(Number(xy[0]), Number(xy[1])); } + if (json['reason'] !== undefined) { + newEvent.reason = json['reason']; + } if (json['recordUndo'] !== undefined) { newEvent.recordUndo = json['recordUndo']; } @@ -176,6 +175,15 @@ export class BlockMove extends BlockBase { this.newCoordinate = location.coordinate; } + /** + * Set the reason for a move event. + * + * @param reason Why is this move happening? 'drag', 'bump', 'snap', ... + */ + setReason(reason: string[]) { + this.reason = reason; + } + /** * Returns the parentId and input if the block is connected, * or the XY location if disconnected. @@ -186,14 +194,15 @@ export class BlockMove extends BlockBase { const workspace = this.getEventWorkspace_(); if (!this.blockId) { throw new Error( - 'The block ID is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } const block = workspace.getBlockById(this.blockId); if (!block) { throw new Error( - 'The block associated with the block move event ' + - 'could not be found'); + 'The block associated with the block move event ' + 'could not be found' + ); } const location = {} as BlockLocation; const parent = block.getParent(); @@ -215,9 +224,11 @@ export class BlockMove extends BlockBase { * @returns False if something changed. */ override isNull(): boolean { - return this.oldParentId === this.newParentId && - this.oldInputName === this.newInputName && - Coordinate.equals(this.oldCoordinate, this.newCoordinate); + return ( + this.oldParentId === this.newParentId && + this.oldInputName === this.newInputName && + Coordinate.equals(this.oldCoordinate, this.newCoordinate) + ); } /** @@ -229,22 +240,23 @@ export class BlockMove extends BlockBase { const workspace = this.getEventWorkspace_(); if (!this.blockId) { throw new Error( - 'The block ID is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The block ID is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } const block = workspace.getBlockById(this.blockId); if (!block) { - console.warn('Can\'t move non-existent block: ' + this.blockId); + console.warn("Can't move non-existent block: " + this.blockId); return; } const parentId = forward ? this.newParentId : this.oldParentId; const inputName = forward ? this.newInputName : this.oldInputName; const coordinate = forward ? this.newCoordinate : this.oldCoordinate; - let parentBlock: Block|null; + let parentBlock: Block | null; if (parentId) { parentBlock = workspace.getBlockById(parentId); if (!parentBlock) { - console.warn('Can\'t connect to non-existent block: ' + parentId); + console.warn("Can't connect to non-existent block: " + parentId); return; } } @@ -253,11 +265,13 @@ export class BlockMove extends BlockBase { } if (coordinate) { const xy = block.getRelativeToSurfaceXY(); - block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y); + block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y, this.reason); } else { let blockConnection = block.outputConnection; - if (!blockConnection || - block.previousConnection && block.previousConnection.isConnected()) { + if ( + !blockConnection || + (block.previousConnection && block.previousConnection.isConnected()) + ) { blockConnection = block.previousConnection; } let parentConnection; @@ -273,7 +287,7 @@ export class BlockMove extends BlockBase { if (parentConnection && blockConnection) { blockConnection.connect(parentConnection); } else { - console.warn('Can\'t connect to non-existent input: ' + inputName); + console.warn("Can't connect to non-existent input: " + inputName); } } } @@ -286,6 +300,7 @@ export interface BlockMoveJson extends BlockBaseJson { newParentId?: string; newInputName?: string; newCoordinate?: string; + reason?: string[]; recordUndo?: boolean; } diff --git a/core/events/events_bubble_open.ts b/core/events/events_bubble_open.ts index 8697d22c0..32c76b490 100644 --- a/core/events/events_bubble_open.ts +++ b/core/events/events_bubble_open.ts @@ -14,13 +14,11 @@ goog.declareModuleId('Blockly.Events.BubbleOpen'); import type {AbstractEventJson} from './events_abstract.js'; import type {BlockSvg} from '../block_svg.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Class for a bubble open event. */ @@ -44,7 +42,10 @@ export class BubbleOpen extends UiBase { * 'warning'. Undefined for a blank event. */ constructor( - opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: BubbleType) { + opt_block?: BlockSvg, + opt_isOpen?: boolean, + opt_bubbleType?: BubbleType + ) { const workspaceId = opt_block ? opt_block.workspace.id : undefined; super(workspaceId); if (!opt_block) return; @@ -63,13 +64,15 @@ export class BubbleOpen extends UiBase { const json = super.toJson() as BubbleOpenJson; if (this.isOpen === undefined) { throw new Error( - 'Whether this event is for opening the bubble is undefined. ' + - 'Either pass the value to the constructor, or call fromJson'); + 'Whether this event is for opening the bubble is undefined. ' + + 'Either pass the value to the constructor, or call fromJson' + ); } if (!this.bubbleType) { throw new Error( - 'The type of bubble is undefined. Either pass the ' + - 'value to the constructor, or call fromJson'); + 'The type of bubble is undefined. Either pass the ' + + 'value to the constructor, or call fromJson' + ); } json['isOpen'] = this.isOpen; json['bubbleType'] = this.bubbleType; @@ -77,21 +80,6 @@ export class BubbleOpen extends UiBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: BubbleOpenJson) { - deprecation.warn( - 'Blockly.Events.BubbleOpen.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.isOpen = json['isOpen']; - this.bubbleType = json['bubbleType']; - this.blockId = json['blockId']; - } - /** * Deserializes the JSON event. * @@ -101,11 +89,16 @@ export class BubbleOpen extends UiBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: BubbleOpenJson, workspace: Workspace, event?: any): - BubbleOpen { - const newEvent = - super.fromJson(json, workspace, event ?? new BubbleOpen()) as - BubbleOpen; + static fromJson( + json: BubbleOpenJson, + workspace: Workspace, + event?: any + ): BubbleOpen { + const newEvent = super.fromJson( + json, + workspace, + event ?? new BubbleOpen() + ) as BubbleOpen; newEvent.isOpen = json['isOpen']; newEvent.bubbleType = json['bubbleType']; newEvent.blockId = json['blockId']; diff --git a/core/events/events_click.ts b/core/events/events_click.ts index 66915762a..a33ea8897 100644 --- a/core/events/events_click.ts +++ b/core/events/events_click.ts @@ -13,7 +13,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.Click'); import type {Block} from '../block.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; @@ -21,7 +20,6 @@ import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import {Workspace} from '../workspace.js'; - /** * Notifies listeners that ome blockly element was clicked. */ @@ -46,8 +44,10 @@ export class Click extends UiBase { * Undefined for a blank event. */ constructor( - opt_block?: Block|null, opt_workspaceId?: string|null, - opt_targetType?: ClickTarget) { + opt_block?: Block | null, + opt_workspaceId?: string | null, + opt_targetType?: ClickTarget + ) { let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId; if (workspaceId === null) { workspaceId = undefined; @@ -67,28 +67,15 @@ export class Click extends UiBase { const json = super.toJson() as ClickJson; if (!this.targetType) { throw new Error( - 'The click target type is undefined. Either pass a block to ' + - 'the constructor, or call fromJson'); + 'The click target type is undefined. Either pass a block to ' + + 'the constructor, or call fromJson' + ); } json['targetType'] = this.targetType; json['blockId'] = this.blockId; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: ClickJson) { - deprecation.warn( - 'Blockly.Events.Click.prototype.fromJson', 'version 9', 'version 10', - 'Blockly.Events.fromJson'); - super.fromJson(json); - this.targetType = json['targetType']; - this.blockId = json['blockId']; - } - /** * Deserializes the JSON event. * @@ -99,8 +86,11 @@ export class Click extends UiBase { * @internal */ static fromJson(json: ClickJson, workspace: Workspace, event?: any): Click { - const newEvent = - super.fromJson(json, workspace, event ?? new Click()) as Click; + const newEvent = super.fromJson( + json, + workspace, + event ?? new Click() + ) as Click; newEvent.targetType = json['targetType']; newEvent.blockId = json['blockId']; return newEvent; diff --git a/core/events/events_comment_base.ts b/core/events/events_comment_base.ts index bee960886..0dc4ee62f 100644 --- a/core/events/events_comment_base.ts +++ b/core/events/events_comment_base.ts @@ -12,18 +12,19 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.CommentBase'); -import * as deprecation from '../utils/deprecation.js'; import * as utilsXml from '../utils/xml.js'; import type {WorkspaceComment} from '../workspace_comment.js'; import * as Xml from '../xml.js'; -import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; +import { + Abstract as AbstractEvent, + AbstractEventJson, +} from './events_abstract.js'; import type {CommentCreate} from './events_comment_create.js'; import type {CommentDelete} from './events_comment_delete.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Abstract class for a comment event. */ @@ -59,26 +60,14 @@ export class CommentBase extends AbstractEvent { const json = super.toJson() as CommentBaseJson; if (!this.commentId) { throw new Error( - 'The comment ID is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } json['commentId'] = this.commentId; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: CommentBaseJson) { - deprecation.warn( - 'Blockly.Events.CommentBase.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.commentId = json['commentId']; - } - /** * Deserializes the JSON event. * @@ -88,11 +77,16 @@ export class CommentBase extends AbstractEvent { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: CommentBaseJson, workspace: Workspace, event?: any): - CommentBase { - const newEvent = - super.fromJson(json, workspace, event ?? new CommentBase()) as - CommentBase; + static fromJson( + json: CommentBaseJson, + workspace: Workspace, + event?: any + ): CommentBase { + const newEvent = super.fromJson( + json, + workspace, + event ?? new CommentBase() + ) as CommentBase; newEvent.commentId = json['commentId']; return newEvent; } @@ -104,7 +98,9 @@ export class CommentBase extends AbstractEvent { * @param create if True then Create, if False then Delete */ static CommentCreateDeleteHelper( - event: CommentCreate|CommentDelete, create: boolean) { + event: CommentCreate | CommentDelete, + create: boolean + ) { const workspace = event.getEventWorkspace_(); if (create) { const xmlElement = utilsXml.createElement('xml'); @@ -116,16 +112,16 @@ export class CommentBase extends AbstractEvent { } else { if (!event.commentId) { throw new Error( - 'The comment ID is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } const comment = workspace.getCommentById(event.commentId); if (comment) { comment.dispose(); } else { // Only complain about root-level block. - console.warn( - 'Can\'t uncreate non-existent comment: ' + event.commentId); + console.warn("Can't uncreate non-existent comment: " + event.commentId); } } } diff --git a/core/events/events_comment_change.ts b/core/events/events_comment_change.ts index d154f6425..62064f41a 100644 --- a/core/events/events_comment_change.ts +++ b/core/events/events_comment_change.ts @@ -12,7 +12,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.CommentChange'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import type {WorkspaceComment} from '../workspace_comment.js'; @@ -20,7 +19,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that the contents of a workspace comment has changed. */ @@ -41,18 +39,20 @@ export class CommentChange extends CommentBase { * @param opt_newContents New contents of the comment. */ constructor( - opt_comment?: WorkspaceComment, opt_oldContents?: string, - opt_newContents?: string) { + opt_comment?: WorkspaceComment, + opt_oldContents?: string, + opt_newContents?: string + ) { super(opt_comment); if (!opt_comment) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.oldContents_ = - typeof opt_oldContents === 'undefined' ? '' : opt_oldContents; + typeof opt_oldContents === 'undefined' ? '' : opt_oldContents; this.newContents_ = - typeof opt_newContents === 'undefined' ? '' : opt_newContents; + typeof opt_newContents === 'undefined' ? '' : opt_newContents; } /** @@ -64,33 +64,21 @@ export class CommentChange extends CommentBase { const json = super.toJson() as CommentChangeJson; if (!this.oldContents_) { throw new Error( - 'The old contents is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The old contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } if (!this.newContents_) { throw new Error( - 'The new contents is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The new contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } json['oldContents'] = this.oldContents_; json['newContents'] = this.newContents_; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: CommentChangeJson) { - deprecation.warn( - 'Blockly.Events.CommentChange.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.oldContents_ = json['oldContents']; - this.newContents_ = json['newContents']; - } - /** * Deserializes the JSON event. * @@ -100,11 +88,16 @@ export class CommentChange extends CommentBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: CommentChangeJson, workspace: Workspace, event?: any): - CommentChange { - const newEvent = - super.fromJson(json, workspace, event ?? new CommentChange()) as - CommentChange; + static fromJson( + json: CommentChangeJson, + workspace: Workspace, + event?: any + ): CommentChange { + const newEvent = super.fromJson( + json, + workspace, + event ?? new CommentChange() + ) as CommentChange; newEvent.oldContents_ = json['oldContents']; newEvent.newContents_ = json['newContents']; return newEvent; @@ -128,24 +121,27 @@ export class CommentChange extends CommentBase { const workspace = this.getEventWorkspace_(); if (!this.commentId) { throw new Error( - 'The comment ID is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } const comment = workspace.getCommentById(this.commentId); if (!comment) { - console.warn('Can\'t change non-existent comment: ' + this.commentId); + console.warn("Can't change non-existent comment: " + this.commentId); return; } const contents = forward ? this.newContents_ : this.oldContents_; if (!contents) { if (forward) { throw new Error( - 'The new contents is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The new contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } throw new Error( - 'The old contents is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The old contents is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } comment.setContent(contents); } @@ -157,4 +153,7 @@ export interface CommentChangeJson extends CommentBaseJson { } registry.register( - registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange); + registry.Type.EVENT, + eventUtils.COMMENT_CHANGE, + CommentChange +); diff --git a/core/events/events_comment_create.ts b/core/events/events_comment_create.ts index 261a6c00f..6bd317b3d 100644 --- a/core/events/events_comment_create.ts +++ b/core/events/events_comment_create.ts @@ -12,7 +12,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.CommentCreate'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import type {WorkspaceComment} from '../workspace_comment.js'; import * as utilsXml from '../utils/xml.js'; @@ -22,7 +21,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a workspace comment was created. */ @@ -30,7 +28,7 @@ export class CommentCreate extends CommentBase { override type = eventUtils.COMMENT_CREATE; /** The XML representation of the created workspace comment. */ - xml?: Element|DocumentFragment; + xml?: Element | DocumentFragment; /** * @param opt_comment The created comment. @@ -56,26 +54,14 @@ export class CommentCreate extends CommentBase { const json = super.toJson() as CommentCreateJson; if (!this.xml) { throw new Error( - 'The comment XML is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The comment XML is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } json['xml'] = Xml.domToText(this.xml); return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: CommentCreateJson) { - deprecation.warn( - 'Blockly.Events.CommentCreate.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.xml = utilsXml.textToDom(json['xml']); - } - /** * Deserializes the JSON event. * @@ -85,11 +71,16 @@ export class CommentCreate extends CommentBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: CommentCreateJson, workspace: Workspace, event?: any): - CommentCreate { - const newEvent = - super.fromJson(json, workspace, event ?? new CommentCreate()) as - CommentCreate; + static fromJson( + json: CommentCreateJson, + workspace: Workspace, + event?: any + ): CommentCreate { + const newEvent = super.fromJson( + json, + workspace, + event ?? new CommentCreate() + ) as CommentCreate; newEvent.xml = utilsXml.textToDom(json['xml']); return newEvent; } @@ -109,4 +100,7 @@ export interface CommentCreateJson extends CommentBaseJson { } registry.register( - registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate); + registry.Type.EVENT, + eventUtils.COMMENT_CREATE, + CommentCreate +); diff --git a/core/events/events_comment_delete.ts b/core/events/events_comment_delete.ts index 736ef6223..8b418735d 100644 --- a/core/events/events_comment_delete.ts +++ b/core/events/events_comment_delete.ts @@ -21,7 +21,6 @@ import * as utilsXml from '../utils/xml.js'; import * as Xml from '../xml.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a workspace comment has been deleted. */ @@ -39,7 +38,7 @@ export class CommentDelete extends CommentBase { super(opt_comment); if (!opt_comment) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.xml = opt_comment.toXmlWithXY(); @@ -63,8 +62,9 @@ export class CommentDelete extends CommentBase { const json = super.toJson() as CommentDeleteJson; if (!this.xml) { throw new Error( - 'The comment XML is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The comment XML is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } json['xml'] = Xml.domToText(this.xml); return json; @@ -79,11 +79,16 @@ export class CommentDelete extends CommentBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: CommentDeleteJson, workspace: Workspace, event?: any): - CommentDelete { - const newEvent = - super.fromJson(json, workspace, event ?? new CommentDelete()) as - CommentDelete; + static fromJson( + json: CommentDeleteJson, + workspace: Workspace, + event?: any + ): CommentDelete { + const newEvent = super.fromJson( + json, + workspace, + event ?? new CommentDelete() + ) as CommentDelete; newEvent.xml = utilsXml.textToDom(json['xml']); return newEvent; } @@ -94,4 +99,7 @@ export interface CommentDeleteJson extends CommentBaseJson { } registry.register( - registry.Type.EVENT, eventUtils.COMMENT_DELETE, CommentDelete); + registry.Type.EVENT, + eventUtils.COMMENT_DELETE, + CommentDelete +); diff --git a/core/events/events_comment_move.ts b/core/events/events_comment_move.ts index 31a893c0a..a5039a24f 100644 --- a/core/events/events_comment_move.ts +++ b/core/events/events_comment_move.ts @@ -12,7 +12,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.CommentMove'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {Coordinate} from '../utils/coordinate.js'; import type {WorkspaceComment} from '../workspace_comment.js'; @@ -21,7 +20,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a workspace comment has moved. */ @@ -46,7 +44,7 @@ export class CommentMove extends CommentBase { super(opt_comment); if (!opt_comment) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.comment_ = opt_comment; @@ -60,13 +58,15 @@ export class CommentMove extends CommentBase { recordNew() { if (this.newCoordinate_) { throw Error( - 'Tried to record the new position of a comment on the ' + - 'same event twice.'); + 'Tried to record the new position of a comment on the ' + + 'same event twice.' + ); } if (!this.comment_) { throw new Error( - 'The comment is undefined. Pass a comment to ' + - 'the constructor if you want to use the record functionality'); + 'The comment is undefined. Pass a comment to ' + + 'the constructor if you want to use the record functionality' + ); } this.newCoordinate_ = this.comment_.getRelativeToSurfaceXY(); } @@ -91,37 +91,26 @@ export class CommentMove extends CommentBase { const json = super.toJson() as CommentMoveJson; if (!this.oldCoordinate_) { throw new Error( - 'The old comment position is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The old comment position is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } if (!this.newCoordinate_) { throw new Error( - 'The new comment position is undefined. Either call recordNew, or ' + - 'call fromJson'); + 'The new comment position is undefined. Either call recordNew, or ' + + 'call fromJson' + ); } - json['oldCoordinate'] = `${Math.round(this.oldCoordinate_.x)}, ` + - `${Math.round(this.oldCoordinate_.y)}`; - json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' + - Math.round(this.newCoordinate_.y); + json['oldCoordinate'] = + `${Math.round(this.oldCoordinate_.x)}, ` + + `${Math.round(this.oldCoordinate_.y)}`; + json['newCoordinate'] = + Math.round(this.newCoordinate_.x) + + ',' + + Math.round(this.newCoordinate_.y); return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: CommentMoveJson) { - deprecation.warn( - 'Blockly.Events.CommentMove.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - let xy = json['oldCoordinate'].split(','); - this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); - xy = json['newCoordinate'].split(','); - this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); - } - /** * Deserializes the JSON event. * @@ -131,11 +120,16 @@ export class CommentMove extends CommentBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: CommentMoveJson, workspace: Workspace, event?: any): - CommentMove { - const newEvent = - super.fromJson(json, workspace, event ?? new CommentMove()) as - CommentMove; + static fromJson( + json: CommentMoveJson, + workspace: Workspace, + event?: any + ): CommentMove { + const newEvent = super.fromJson( + json, + workspace, + event ?? new CommentMove() + ) as CommentMove; let xy = json['oldCoordinate'].split(','); newEvent.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1])); xy = json['newCoordinate'].split(','); @@ -161,21 +155,23 @@ export class CommentMove extends CommentBase { const workspace = this.getEventWorkspace_(); if (!this.commentId) { throw new Error( - 'The comment ID is undefined. Either pass a comment to ' + - 'the constructor, or call fromJson'); + 'The comment ID is undefined. Either pass a comment to ' + + 'the constructor, or call fromJson' + ); } const comment = workspace.getCommentById(this.commentId); if (!comment) { - console.warn('Can\'t move non-existent comment: ' + this.commentId); + console.warn("Can't move non-existent comment: " + this.commentId); return; } const target = forward ? this.newCoordinate_ : this.oldCoordinate_; if (!target) { throw new Error( - 'Either oldCoordinate_ or newCoordinate_ is undefined. ' + + 'Either oldCoordinate_ or newCoordinate_ is undefined. ' + 'Either pass a comment to the constructor and call recordNew, ' + - 'or call fromJson'); + 'or call fromJson' + ); } // TODO: Check if the comment is being dragged, and give up if so. const current = comment.getRelativeToSurfaceXY(); diff --git a/core/events/events_marker_move.ts b/core/events/events_marker_move.ts index dcb4fe784..074532510 100644 --- a/core/events/events_marker_move.ts +++ b/core/events/events_marker_move.ts @@ -14,7 +14,6 @@ goog.declareModuleId('Blockly.Events.MarkerMove'); import type {Block} from '../block.js'; import {ASTNode} from '../keyboard_nav/ast_node.js'; -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import type {Workspace} from '../workspace.js'; import {AbstractEventJson} from './events_abstract.js'; @@ -22,7 +21,6 @@ import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; - /** * Notifies listeners that a marker (used for keyboard navigation) has * moved. @@ -57,8 +55,11 @@ export class MarkerMove extends UiBase { * Undefined for a blank event. */ constructor( - opt_block?: Block|null, isCursor?: boolean, opt_oldNode?: ASTNode|null, - opt_newNode?: ASTNode) { + opt_block?: Block | null, + isCursor?: boolean, + opt_oldNode?: ASTNode | null, + opt_newNode?: ASTNode + ) { let workspaceId = opt_block ? opt_block.workspace.id : undefined; if (opt_newNode && opt_newNode.getType() === ASTNode.types.WORKSPACE) { workspaceId = (opt_newNode.getLocation() as Workspace).id; @@ -80,13 +81,15 @@ export class MarkerMove extends UiBase { const json = super.toJson() as MarkerMoveJson; if (this.isCursor === undefined) { throw new Error( - 'Whether this is a cursor event or not is undefined. Either pass ' + - 'a value to the constructor, or call fromJson'); + 'Whether this is a cursor event or not is undefined. Either pass ' + + 'a value to the constructor, or call fromJson' + ); } if (!this.newNode) { throw new Error( - 'The new node is undefined. Either pass a node to ' + - 'the constructor, or call fromJson'); + 'The new node is undefined. Either pass a node to ' + + 'the constructor, or call fromJson' + ); } json['isCursor'] = this.isCursor; json['blockId'] = this.blockId; @@ -95,22 +98,6 @@ export class MarkerMove extends UiBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: MarkerMoveJson) { - deprecation.warn( - 'Blockly.Events.MarkerMove.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.isCursor = json['isCursor']; - this.blockId = json['blockId']; - this.oldNode = json['oldNode']; - this.newNode = json['newNode']; - } - /** * Deserializes the JSON event. * @@ -120,11 +107,16 @@ export class MarkerMove extends UiBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: MarkerMoveJson, workspace: Workspace, event?: any): - MarkerMove { - const newEvent = - super.fromJson(json, workspace, event ?? new MarkerMove()) as - MarkerMove; + static fromJson( + json: MarkerMoveJson, + workspace: Workspace, + event?: any + ): MarkerMove { + const newEvent = super.fromJson( + json, + workspace, + event ?? new MarkerMove() + ) as MarkerMove; newEvent.isCursor = json['isCursor']; newEvent.blockId = json['blockId']; newEvent.oldNode = json['oldNode']; diff --git a/core/events/events_selected.ts b/core/events/events_selected.ts index fc1d6ce3f..833420a5a 100644 --- a/core/events/events_selected.ts +++ b/core/events/events_selected.ts @@ -12,7 +12,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.Selected'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; @@ -20,7 +19,6 @@ import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Class for a selected event. * Notifies listeners that a new element has been selected. @@ -46,8 +44,10 @@ export class Selected extends UiBase { * Null if no element previously selected. Undefined for a blank event. */ constructor( - opt_oldElementId?: string|null, opt_newElementId?: string|null, - opt_workspaceId?: string) { + opt_oldElementId?: string | null, + opt_newElementId?: string | null, + opt_workspaceId?: string + ) { super(opt_workspaceId); this.oldElementId = opt_oldElementId ?? undefined; @@ -66,20 +66,6 @@ export class Selected extends UiBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: SelectedJson) { - deprecation.warn( - 'Blockly.Events.Selected.prototype.fromJson', 'version 9', 'version 10', - 'Blockly.Events.fromJson'); - super.fromJson(json); - this.oldElementId = json['oldElementId']; - this.newElementId = json['newElementId']; - } - /** * Deserializes the JSON event. * @@ -89,10 +75,16 @@ export class Selected extends UiBase { * static methods in superclasses. * @internal */ - static fromJson(json: SelectedJson, workspace: Workspace, event?: any): - Selected { - const newEvent = - super.fromJson(json, workspace, event ?? new Selected()) as Selected; + static fromJson( + json: SelectedJson, + workspace: Workspace, + event?: any + ): Selected { + const newEvent = super.fromJson( + json, + workspace, + event ?? new Selected() + ) as Selected; newEvent.oldElementId = json['oldElementId']; newEvent.newElementId = json['newElementId']; return newEvent; diff --git a/core/events/events_theme_change.ts b/core/events/events_theme_change.ts index d7ebef5bf..4deb1d70a 100644 --- a/core/events/events_theme_change.ts +++ b/core/events/events_theme_change.ts @@ -12,14 +12,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.ThemeChange'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that the workspace theme has changed. */ @@ -48,26 +46,14 @@ export class ThemeChange extends UiBase { const json = super.toJson() as ThemeChangeJson; if (!this.themeName) { throw new Error( - 'The theme name is undefined. Either pass a theme name to ' + - 'the constructor, or call fromJson'); + 'The theme name is undefined. Either pass a theme name to ' + + 'the constructor, or call fromJson' + ); } json['themeName'] = this.themeName; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: ThemeChangeJson) { - deprecation.warn( - 'Blockly.Events.ThemeChange.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.themeName = json['themeName']; - } - /** * Deserializes the JSON event. * @@ -77,11 +63,16 @@ export class ThemeChange extends UiBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: ThemeChangeJson, workspace: Workspace, event?: any): - ThemeChange { - const newEvent = - super.fromJson(json, workspace, event ?? new ThemeChange()) as - ThemeChange; + static fromJson( + json: ThemeChangeJson, + workspace: Workspace, + event?: any + ): ThemeChange { + const newEvent = super.fromJson( + json, + workspace, + event ?? new ThemeChange() + ) as ThemeChange; newEvent.themeName = json['themeName']; return newEvent; } diff --git a/core/events/events_toolbox_item_select.ts b/core/events/events_toolbox_item_select.ts index 4eca80b4d..f89485da6 100644 --- a/core/events/events_toolbox_item_select.ts +++ b/core/events/events_toolbox_item_select.ts @@ -12,14 +12,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.ToolboxItemSelect'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a toolbox item has been selected. */ @@ -41,8 +39,10 @@ export class ToolboxItemSelect extends UiBase { * Undefined for a blank event. */ constructor( - opt_oldItem?: string|null, opt_newItem?: string|null, - opt_workspaceId?: string) { + opt_oldItem?: string | null, + opt_newItem?: string | null, + opt_workspaceId?: string + ) { super(opt_workspaceId); this.oldItem = opt_oldItem ?? undefined; this.newItem = opt_newItem ?? undefined; @@ -60,20 +60,6 @@ export class ToolboxItemSelect extends UiBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: ToolboxItemSelectJson) { - deprecation.warn( - 'Blockly.Events.ToolboxItemSelect.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.oldItem = json['oldItem']; - this.newItem = json['newItem']; - } - /** * Deserializes the JSON event. * @@ -84,11 +70,15 @@ export class ToolboxItemSelect extends UiBase { * @internal */ static fromJson( - json: ToolboxItemSelectJson, workspace: Workspace, - event?: any): ToolboxItemSelect { - const newEvent = - super.fromJson(json, workspace, event ?? new ToolboxItemSelect()) as - ToolboxItemSelect; + json: ToolboxItemSelectJson, + workspace: Workspace, + event?: any + ): ToolboxItemSelect { + const newEvent = super.fromJson( + json, + workspace, + event ?? new ToolboxItemSelect() + ) as ToolboxItemSelect; newEvent.oldItem = json['oldItem']; newEvent.newItem = json['newItem']; return newEvent; @@ -101,4 +91,7 @@ export interface ToolboxItemSelectJson extends AbstractEventJson { } registry.register( - registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect); + registry.Type.EVENT, + eventUtils.TOOLBOX_ITEM_SELECT, + ToolboxItemSelect +); diff --git a/core/events/events_trashcan_open.ts b/core/events/events_trashcan_open.ts index fd03b2da6..71b12dbe2 100644 --- a/core/events/events_trashcan_open.ts +++ b/core/events/events_trashcan_open.ts @@ -12,7 +12,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.TrashcanOpen'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; @@ -20,7 +19,6 @@ import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners when the trashcan is opening or closing. */ @@ -52,26 +50,14 @@ export class TrashcanOpen extends UiBase { const json = super.toJson() as TrashcanOpenJson; if (this.isOpen === undefined) { throw new Error( - 'Whether this is already open or not is undefined. Either pass ' + - 'a value to the constructor, or call fromJson'); + 'Whether this is already open or not is undefined. Either pass ' + + 'a value to the constructor, or call fromJson' + ); } json['isOpen'] = this.isOpen; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: TrashcanOpenJson) { - deprecation.warn( - 'Blockly.Events.TrashcanOpen.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.isOpen = json['isOpen']; - } - /** * Deserializes the JSON event. * @@ -81,11 +67,16 @@ export class TrashcanOpen extends UiBase { * parameters to static methods in superclasses. * @internal */ - static fromJson(json: TrashcanOpenJson, workspace: Workspace, event?: any): - TrashcanOpen { - const newEvent = - super.fromJson(json, workspace, event ?? new TrashcanOpen()) as - TrashcanOpen; + static fromJson( + json: TrashcanOpenJson, + workspace: Workspace, + event?: any + ): TrashcanOpen { + const newEvent = super.fromJson( + json, + workspace, + event ?? new TrashcanOpen() + ) as TrashcanOpen; newEvent.isOpen = json['isOpen']; return newEvent; } diff --git a/core/events/events_ui.ts b/core/events/events_ui.ts deleted file mode 100644 index f743f14d9..000000000 --- a/core/events/events_ui.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2018 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * (Deprecated) Events fired as a result of UI actions in - * Blockly's editor. - * - * @class - */ -import * as goog from '../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Events.Ui'); - -import type {Block} from '../block.js'; -import * as registry from '../registry.js'; -import {UiBase} from './events_ui_base.js'; -import * as eventUtils from './utils.js'; - - -/** - * Class for a UI event. - * - * @deprecated December 2020. Instead use a more specific UI event. - */ -export class Ui extends UiBase { - blockId: AnyDuringMigration; - element: AnyDuringMigration; - oldValue: AnyDuringMigration; - newValue: AnyDuringMigration; - override type = eventUtils.UI; - - /** - * @param opt_block The affected block. Null for UI events that do not have - * an associated block. Undefined for a blank event. - * @param opt_element One of 'selected', 'comment', 'mutatorOpen', etc. - * @param opt_oldValue Previous value of element. - * @param opt_newValue New value of element. - */ - constructor( - opt_block?: Block|null, opt_element?: string, - opt_oldValue?: AnyDuringMigration, opt_newValue?: AnyDuringMigration) { - const workspaceId = opt_block ? opt_block.workspace.id : undefined; - super(workspaceId); - - this.blockId = opt_block ? opt_block.id : null; - this.element = typeof opt_element === 'undefined' ? '' : opt_element; - this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue; - this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue; - } - - /** - * Encode the event as JSON. - * - * @returns JSON representation. - */ - override toJson(): AnyDuringMigration { - const json = super.toJson() as AnyDuringMigration; - json['element'] = this.element; - if (this.newValue !== undefined) { - json['newValue'] = this.newValue; - } - if (this.blockId) { - json['blockId'] = this.blockId; - } - return json; - } - - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: AnyDuringMigration) { - super.fromJson(json); - this.element = json['element']; - this.newValue = json['newValue']; - this.blockId = json['blockId']; - } -} - -registry.register(registry.Type.EVENT, eventUtils.UI, Ui); diff --git a/core/events/events_ui_base.ts b/core/events/events_ui_base.ts index be5a60d59..60c7b5e45 100644 --- a/core/events/events_ui_base.ts +++ b/core/events/events_ui_base.ts @@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.Events.UiBase'); import {Abstract as AbstractEvent} from './events_abstract.js'; - /** * Base class for a UI event. * UI events are events that don't need to be sent over the wire for multi-user diff --git a/core/events/events_var_base.ts b/core/events/events_var_base.ts index f25628888..1792c411a 100644 --- a/core/events/events_var_base.ts +++ b/core/events/events_var_base.ts @@ -12,13 +12,14 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.VarBase'); -import * as deprecation from '../utils/deprecation.js'; import type {VariableModel} from '../variable_model.js'; -import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; +import { + Abstract as AbstractEvent, + AbstractEventJson, +} from './events_abstract.js'; import type {Workspace} from '../workspace.js'; - /** * Abstract class for a variable event. */ @@ -49,26 +50,14 @@ export class VarBase extends AbstractEvent { const json = super.toJson() as VarBaseJson; if (!this.varId) { throw new Error( - 'The var ID is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } json['varId'] = this.varId; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: VarBaseJson) { - deprecation.warn( - 'Blockly.Events.VarBase.prototype.fromJson', 'version 9', 'version 10', - 'Blockly.Events.fromJson'); - super.fromJson(json); - this.varId = json['varId']; - } - /** * Deserializes the JSON event. * @@ -78,10 +67,16 @@ export class VarBase extends AbstractEvent { * static methods in superclasses. * @internal */ - static fromJson(json: VarBaseJson, workspace: Workspace, event?: any): - VarBase { - const newEvent = - super.fromJson(json, workspace, event ?? new VarBase()) as VarBase; + static fromJson( + json: VarBaseJson, + workspace: Workspace, + event?: any + ): VarBase { + const newEvent = super.fromJson( + json, + workspace, + event ?? new VarBase() + ) as VarBase; newEvent.varId = json['varId']; return newEvent; } diff --git a/core/events/events_var_create.ts b/core/events/events_var_create.ts index eb8ccfe9f..83f541f9a 100644 --- a/core/events/events_var_create.ts +++ b/core/events/events_var_create.ts @@ -12,7 +12,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.VarCreate'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import type {VariableModel} from '../variable_model.js'; @@ -20,7 +19,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a variable model has been created. */ @@ -40,7 +38,7 @@ export class VarCreate extends VarBase { super(opt_variable); if (!opt_variable) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.varType = opt_variable.type; this.varName = opt_variable.name; @@ -55,33 +53,21 @@ export class VarCreate extends VarBase { const json = super.toJson() as VarCreateJson; if (this.varType === undefined) { throw new Error( - 'The var type is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var type is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.varName) { throw new Error( - 'The var name is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } json['varType'] = this.varType; json['varName'] = this.varName; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: VarCreateJson) { - deprecation.warn( - 'Blockly.Events.VarCreate.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.varType = json['varType']; - this.varName = json['varName']; - } - /** * Deserializes the JSON event. * @@ -91,10 +77,16 @@ export class VarCreate extends VarBase { * static methods in superclasses. * @internal */ - static fromJson(json: VarCreateJson, workspace: Workspace, event?: any): - VarCreate { - const newEvent = - super.fromJson(json, workspace, event ?? new VarCreate()) as VarCreate; + static fromJson( + json: VarCreateJson, + workspace: Workspace, + event?: any + ): VarCreate { + const newEvent = super.fromJson( + json, + workspace, + event ?? new VarCreate() + ) as VarCreate; newEvent.varType = json['varType']; newEvent.varName = json['varName']; return newEvent; @@ -109,13 +101,15 @@ export class VarCreate extends VarBase { const workspace = this.getEventWorkspace_(); if (!this.varId) { throw new Error( - 'The var ID is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.varName) { throw new Error( - 'The var name is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (forward) { workspace.createVariable(this.varName, this.varType, this.varId); diff --git a/core/events/events_var_delete.ts b/core/events/events_var_delete.ts index be0fec4fe..e12dcf592 100644 --- a/core/events/events_var_delete.ts +++ b/core/events/events_var_delete.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.VarDelete'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import type {VariableModel} from '../variable_model.js'; @@ -15,7 +14,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a variable model has been deleted. * @@ -35,7 +33,7 @@ export class VarDelete extends VarBase { super(opt_variable); if (!opt_variable) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.varType = opt_variable.type; this.varName = opt_variable.name; @@ -50,33 +48,21 @@ export class VarDelete extends VarBase { const json = super.toJson() as VarDeleteJson; if (this.varType === undefined) { throw new Error( - 'The var type is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var type is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.varName) { throw new Error( - 'The var name is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } json['varType'] = this.varType; json['varName'] = this.varName; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: VarDeleteJson) { - deprecation.warn( - 'Blockly.Events.VarDelete.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.varType = json['varType']; - this.varName = json['varName']; - } - /** * Deserializes the JSON event. * @@ -86,10 +72,16 @@ export class VarDelete extends VarBase { * static methods in superclasses. * @internal */ - static fromJson(json: VarDeleteJson, workspace: Workspace, event?: any): - VarDelete { - const newEvent = - super.fromJson(json, workspace, event ?? new VarDelete()) as VarDelete; + static fromJson( + json: VarDeleteJson, + workspace: Workspace, + event?: any + ): VarDelete { + const newEvent = super.fromJson( + json, + workspace, + event ?? new VarDelete() + ) as VarDelete; newEvent.varType = json['varType']; newEvent.varName = json['varName']; return newEvent; @@ -104,13 +96,15 @@ export class VarDelete extends VarBase { const workspace = this.getEventWorkspace_(); if (!this.varId) { throw new Error( - 'The var ID is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.varName) { throw new Error( - 'The var name is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (forward) { workspace.deleteVariableById(this.varId); diff --git a/core/events/events_var_rename.ts b/core/events/events_var_rename.ts index bed95cf62..c80b4e2ef 100644 --- a/core/events/events_var_rename.ts +++ b/core/events/events_var_rename.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.VarRename'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import type {VariableModel} from '../variable_model.js'; @@ -15,7 +14,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that a variable model was renamed. * @@ -38,7 +36,7 @@ export class VarRename extends VarBase { super(opt_variable); if (!opt_variable) { - return; // Blank event to be populated by fromJson. + return; // Blank event to be populated by fromJson. } this.oldName = opt_variable.name; this.newName = typeof newName === 'undefined' ? '' : newName; @@ -53,33 +51,21 @@ export class VarRename extends VarBase { const json = super.toJson() as VarRenameJson; if (!this.oldName) { throw new Error( - 'The old var name is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The old var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.newName) { throw new Error( - 'The new var name is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The new var name is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } json['oldName'] = this.oldName; json['newName'] = this.newName; return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: VarRenameJson) { - deprecation.warn( - 'Blockly.Events.VarRename.prototype.fromJson', 'version 9', - 'version 10', 'Blockly.Events.fromJson'); - super.fromJson(json); - this.oldName = json['oldName']; - this.newName = json['newName']; - } - /** * Deserializes the JSON event. * @@ -89,10 +75,16 @@ export class VarRename extends VarBase { * static methods in superclasses. * @internal */ - static fromJson(json: VarRenameJson, workspace: Workspace, event?: any): - VarRename { - const newEvent = - super.fromJson(json, workspace, event ?? new VarRename()) as VarRename; + static fromJson( + json: VarRenameJson, + workspace: Workspace, + event?: any + ): VarRename { + const newEvent = super.fromJson( + json, + workspace, + event ?? new VarRename() + ) as VarRename; newEvent.oldName = json['oldName']; newEvent.newName = json['newName']; return newEvent; @@ -107,18 +99,21 @@ export class VarRename extends VarBase { const workspace = this.getEventWorkspace_(); if (!this.varId) { throw new Error( - 'The var ID is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The var ID is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.oldName) { throw new Error( - 'The old var name is undefined. Either pass a variable to ' + - 'the constructor, or call fromJson'); + 'The old var name is undefined. Either pass a variable to ' + + 'the constructor, or call fromJson' + ); } if (!this.newName) { throw new Error( - 'The new var name is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The new var name is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } if (forward) { workspace.renameVariableById(this.varId, this.newName); diff --git a/core/events/events_viewport.ts b/core/events/events_viewport.ts index f0287a399..31e35e18c 100644 --- a/core/events/events_viewport.ts +++ b/core/events/events_viewport.ts @@ -12,14 +12,12 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Events.ViewportChange'); -import * as deprecation from '../utils/deprecation.js'; import * as registry from '../registry.js'; import {AbstractEventJson} from './events_abstract.js'; import {UiBase} from './events_ui_base.js'; import * as eventUtils from './utils.js'; import type {Workspace} from '../workspace.js'; - /** * Notifies listeners that the workspace surface's position or scale has * changed. @@ -59,8 +57,12 @@ export class ViewportChange extends UiBase { * event. */ constructor( - opt_top?: number, opt_left?: number, opt_scale?: number, - opt_workspaceId?: string, opt_oldScale?: number) { + opt_top?: number, + opt_left?: number, + opt_scale?: number, + opt_workspaceId?: string, + opt_oldScale?: number + ) { super(opt_workspaceId); this.viewTop = opt_top; @@ -78,23 +80,27 @@ export class ViewportChange extends UiBase { const json = super.toJson() as ViewportChangeJson; if (this.viewTop === undefined) { throw new Error( - 'The view top is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The view top is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } if (this.viewLeft === undefined) { throw new Error( - 'The view left is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The view left is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } if (this.scale === undefined) { throw new Error( - 'The scale is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The scale is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } if (this.oldScale === undefined) { throw new Error( - 'The old scale is undefined. Either pass a value to ' + - 'the constructor, or call fromJson'); + 'The old scale is undefined. Either pass a value to ' + + 'the constructor, or call fromJson' + ); } json['viewTop'] = this.viewTop; json['viewLeft'] = this.viewLeft; @@ -103,22 +109,6 @@ export class ViewportChange extends UiBase { return json; } - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: ViewportChangeJson) { - deprecation.warn( - 'Blockly.Events.Viewport.prototype.fromJson', 'version 9', 'version 10', - 'Blockly.Events.fromJson'); - super.fromJson(json); - this.viewTop = json['viewTop']; - this.viewLeft = json['viewLeft']; - this.scale = json['scale']; - this.oldScale = json['oldScale']; - } - /** * Deserializes the JSON event. * @@ -128,11 +118,16 @@ export class ViewportChange extends UiBase { * static methods in superclasses. * @internal */ - static fromJson(json: ViewportChangeJson, workspace: Workspace, event?: any): - ViewportChange { - const newEvent = - super.fromJson(json, workspace, event ?? new ViewportChange()) as - ViewportChange; + static fromJson( + json: ViewportChangeJson, + workspace: Workspace, + event?: any + ): ViewportChange { + const newEvent = super.fromJson( + json, + workspace, + event ?? new ViewportChange() + ) as ViewportChange; newEvent.viewTop = json['viewTop']; newEvent.viewLeft = json['viewLeft']; newEvent.scale = json['scale']; @@ -149,4 +144,7 @@ export interface ViewportChangeJson extends AbstractEventJson { } registry.register( - registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange); + registry.Type.EVENT, + eventUtils.VIEWPORT_CHANGE, + ViewportChange +); diff --git a/core/events/utils.ts b/core/events/utils.ts index e8505f38d..0636a39fe 100644 --- a/core/events/utils.ts +++ b/core/events/utils.ts @@ -22,7 +22,6 @@ import type {CommentCreate} from './events_comment_create.js'; import type {CommentMove} from './events_comment_move.js'; import type {ViewportChange} from './events_viewport.js'; - /** Group ID for new events. Grouped events are indivisible. */ let group = ''; @@ -80,6 +79,13 @@ export const CHANGE = 'change'; */ export const BLOCK_CHANGE = CHANGE; +/** + * Name of event representing an in-progress change to a field of a block, which + * is expected to be followed by a block change event. + */ +export const BLOCK_FIELD_INTERMEDIATE_CHANGE = + 'block_field_intermediate_change'; + /** * Name of event that moves a block. Will be deprecated for BLOCK_MOVE. */ @@ -187,7 +193,7 @@ export const FINISHED_LOADING = 'finished_loading'; * Not to be confused with bumping so that disconnected connections do not * appear connected. */ -export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove; +export type BumpEvent = BlockCreate | BlockMove | CommentCreate | CommentMove; /** * List of events that cause objects to be bumped back into the visible @@ -196,8 +202,12 @@ export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove; * Not to be confused with bumping so that disconnected connections do not * appear connected. */ -export const BUMP_EVENTS: string[] = - [BLOCK_CREATE, BLOCK_MOVE, COMMENT_CREATE, COMMENT_MOVE]; +export const BUMP_EVENTS: string[] = [ + BLOCK_CREATE, + BLOCK_MOVE, + COMMENT_CREATE, + COMMENT_MOVE, +]; /** List of events queued for firing. */ const FIRE_QUEUE: Abstract[] = []; @@ -235,12 +245,11 @@ function fireInternal(event: Abstract) { FIRE_QUEUE.push(event); } - /** Fire all queued events. */ function fireNow() { const queue = filter(FIRE_QUEUE, true); FIRE_QUEUE.length = 0; - for (let i = 0, event; event = queue[i]; i++) { + for (let i = 0, event; (event = queue[i]); i++) { if (!event.workspaceId) { continue; } @@ -249,6 +258,46 @@ function fireNow() { eventWorkspace.fireChangeListener(event); } } + + // Post-filter the undo stack to squash and remove any events that result in + // a null event + + // 1. Determine which workspaces will need to have their undo stacks validated + const workspaceIds = new Set(queue.map((e) => e.workspaceId)); + for (const workspaceId of workspaceIds) { + // Only process valid workspaces + if (!workspaceId) { + continue; + } + const eventWorkspace = common.getWorkspaceById(workspaceId); + if (!eventWorkspace) { + continue; + } + + // Find the last contiguous group of events on the stack + const undoStack = eventWorkspace.getUndoStack(); + let i; + let group: string | undefined = undefined; + for (i = undoStack.length; i > 0; i--) { + const event = undoStack[i - 1]; + if (event.group === '') { + break; + } else if (group === undefined) { + group = event.group; + } else if (event.group !== group) { + break; + } + } + if (!group || i == undoStack.length - 1) { + // Need a group of two or more events on the stack. Nothing to do here. + continue; + } + + // Extract the event group, filter, and add back to the undo stack + let events = undoStack.splice(i, undoStack.length - i); + events = filter(events, true); + undoStack.push(...events); + } } /** @@ -268,7 +317,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] { const mergedQueue = []; const hash = Object.create(null); // Merge duplicates. - for (let i = 0, event; event = queue[i]; i++) { + for (let i = 0, event; (event = queue[i]); i++) { if (!event.isNull()) { // Treat all UI events as the same type in hash table. const eventType = event.isUiEvent ? UI : event.type; @@ -290,11 +339,23 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] { lastEvent.newParentId = moveEvent.newParentId; lastEvent.newInputName = moveEvent.newInputName; lastEvent.newCoordinate = moveEvent.newCoordinate; + if (moveEvent.reason) { + if (lastEvent.reason) { + // Concatenate reasons without duplicates. + const reasonSet = new Set( + moveEvent.reason.concat(lastEvent.reason) + ); + lastEvent.reason = Array.from(reasonSet); + } else { + lastEvent.reason = moveEvent.reason; + } + } lastEntry.index = i; } else if ( - event.type === CHANGE && - (event as BlockChange).element === lastEvent.element && - (event as BlockChange).name === lastEvent.name) { + event.type === CHANGE && + (event as BlockChange).element === lastEvent.element && + (event as BlockChange).name === lastEvent.name + ) { const changeEvent = event as BlockChange; // Merge change events. lastEvent.newValue = changeEvent.newValue; @@ -316,7 +377,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] { } } // Filter out any events that have become null due to merging. - queue = mergedQueue.filter(function(e) { + queue = mergedQueue.filter(function (e) { return !e.isNull(); }); if (!forward) { @@ -325,11 +386,13 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] { } // Move mutation events to the top of the queue. // Intentionally skip first event. - for (let i = 1, event; event = queue[i]; i++) { + for (let i = 1, event; (event = queue[i]); i++) { // AnyDuringMigration because: Property 'element' does not exist on type // 'Abstract'. - if (event.type === CHANGE && - (event as AnyDuringMigration).element === 'mutation') { + if ( + event.type === CHANGE && + (event as AnyDuringMigration).element === 'mutation' + ) { queue.unshift(queue.splice(i, 1)[0]); } } @@ -341,7 +404,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] { * in the undo stack. Called by Workspace.clearUndo. */ export function clearPendingUndo() { - for (let i = 0, event; event = FIRE_QUEUE[i]; i++) { + for (let i = 0, event; (event = FIRE_QUEUE[i]); i++) { event.recordUndo = false; } } @@ -385,14 +448,14 @@ export function getGroup(): string { * @param state True to start new group, false to end group. * String to set group explicitly. */ -export function setGroup(state: boolean|string) { +export function setGroup(state: boolean | string) { TEST_ONLY.setGroupInternal(state); } /** * Private version of setGroup for stubbing in tests. */ -function setGroupInternal(state: boolean|string) { +function setGroupInternal(state: boolean | string) { if (typeof state === 'boolean') { group = state ? idGenerator.genUid() : ''; } else { @@ -410,7 +473,7 @@ function setGroupInternal(state: boolean|string) { export function getDescendantIds(block: Block): string[] { const ids = []; const descendants = block.getDescendants(false); - for (let i = 0, descendant; descendant = descendants[i]; i++) { + for (let i = 0, descendant; (descendant = descendants[i]); i++) { ids[i] = descendant.id; } return ids; @@ -425,33 +488,13 @@ export function getDescendantIds(block: Block): string[] { * @throws {Error} if an event type is not found in the registry. */ export function fromJson( - json: AnyDuringMigration, workspace: Workspace): Abstract { + json: AnyDuringMigration, + workspace: Workspace +): Abstract { const eventClass = get(json['type']); if (!eventClass) throw Error('Unknown event type.'); - if (eventClassHasStaticFromJson(eventClass)) { - return (eventClass as any).fromJson(json, workspace); - } - - // Fallback to the old deserialization method. - const event = new eventClass(); - event.fromJson(json); - event.workspaceId = workspace.id; - return event; -} - -/** - * Returns true if the given event constructor has /its own/ static fromJson - * method. - * - * Returns false if no static fromJson method exists on the contructor, or if - * the static fromJson method is inheritted. - */ -function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract): - boolean { - const untypedEventClass = eventClass as any; - return Object.getOwnPropertyDescriptors(untypedEventClass).fromJson && - typeof untypedEventClass.fromJson === 'function'; + return (eventClass as any).fromJson(json, workspace); } /** @@ -460,8 +503,9 @@ function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract): * @param eventType The type of the event to get. * @returns The event class with the given type. */ -export function get(eventType: string): - (new (...p1: AnyDuringMigration[]) => Abstract) { +export function get( + eventType: string +): new (...p1: AnyDuringMigration[]) => Abstract { const event = registry.getClass(registry.Type.EVENT, eventType); if (!event) { throw new Error(`Event type ${eventType} not found in registry.`); @@ -483,8 +527,9 @@ export function disableOrphans(event: Abstract) { if (!blockEvent.workspaceId) { return; } - const eventWorkspace = - common.getWorkspaceById(blockEvent.workspaceId) as WorkspaceSvg; + const eventWorkspace = common.getWorkspaceById( + blockEvent.workspaceId + ) as WorkspaceSvg; if (!blockEvent.blockId) { throw new Error('Encountered a blockEvent without a proper blockId'); } @@ -497,12 +542,13 @@ export function disableOrphans(event: Abstract) { const parent = block.getParent(); if (parent && parent.isEnabled()) { const children = block.getDescendants(false); - for (let i = 0, child; child = children[i]; i++) { + for (let i = 0, child; (child = children[i]); i++) { child.setEnabled(true); } } else if ( - (block.outputConnection || block.previousConnection) && - !eventWorkspace.isDragging()) { + (block.outputConnection || block.previousConnection) && + !eventWorkspace.isDragging() + ) { do { block.setEnabled(false); block = block.getNextBlock(); diff --git a/core/events/workspace_events.ts b/core/events/workspace_events.ts index 30fab99b7..550b6ebda 100644 --- a/core/events/workspace_events.ts +++ b/core/events/workspace_events.ts @@ -14,10 +14,9 @@ goog.declareModuleId('Blockly.Events.FinishedLoading'); import * as registry from '../registry.js'; import type {Workspace} from '../workspace.js'; -import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js'; +import {Abstract as AbstractEvent} from './events_abstract.js'; import * as eventUtils from './utils.js'; - /** * Notifies listeners when the workspace has finished deserializing from * JSON/XML. @@ -39,37 +38,10 @@ export class FinishedLoading extends AbstractEvent { this.workspaceId = opt_workspace.id; } - - /** - * Encode the event as JSON. - * - * @returns JSON representation. - */ - override toJson(): FinishedLoadingJson { - const json = super.toJson() as FinishedLoadingJson; - if (!this.workspaceId) { - throw new Error( - 'The workspace ID is undefined. Either pass a workspace to ' + - 'the constructor, or call fromJson'); - } - json['workspaceId'] = this.workspaceId; - return json; - } - - /** - * Decode the JSON event. - * - * @param json JSON representation. - */ - override fromJson(json: FinishedLoadingJson) { - super.fromJson(json); - this.workspaceId = json['workspaceId']; - } -} - -export interface FinishedLoadingJson extends AbstractEventJson { - workspaceId: string; } registry.register( - registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading); + registry.Type.EVENT, + eventUtils.FINISHED_LOADING, + FinishedLoading +); diff --git a/core/extensions.ts b/core/extensions.ts index 20e1ed47e..1b133f7ef 100644 --- a/core/extensions.ts +++ b/core/extensions.ts @@ -10,10 +10,9 @@ goog.declareModuleId('Blockly.Extensions'); import type {Block} from './block.js'; import type {BlockSvg} from './block_svg.js'; import {FieldDropdown} from './field_dropdown.js'; -import {Mutator} from './mutator.js'; +import {MutatorIcon} from './icons/mutator_icon.js'; import * as parsing from './utils/parsing.js'; - /** The set of all registered extensions, keyed by extension name/id. */ const allExtensions = Object.create(null); export const TEST_ONLY = {allExtensions}; @@ -54,7 +53,7 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) { if (!mixinObj || typeof mixinObj !== 'object') { throw Error('Error: Mixin "' + name + '" must be a object'); } - register(name, function(this: Block) { + register(name, function (this: Block) { this.mixin(mixinObj); }); } @@ -73,8 +72,11 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) { * @throws {Error} if the mutation is invalid or can't be applied to the block. */ export function registerMutator( - name: string, mixinObj: AnyDuringMigration, - opt_helperFn?: () => AnyDuringMigration, opt_blockList?: string[]) { + name: string, + mixinObj: AnyDuringMigration, + opt_helperFn?: () => AnyDuringMigration, + opt_blockList?: string[] +) { const errorPrefix = 'Error when registering mutator "' + name + '": '; checkHasMutatorProperties(errorPrefix, mixinObj); @@ -85,9 +87,9 @@ export function registerMutator( } // Sanity checks passed. - register(name, function(this: Block) { + register(name, function (this: Block) { if (hasMutatorDialog) { - this.setMutator(new Mutator(opt_blockList || [], this as BlockSvg)); + this.setMutator(new MutatorIcon(opt_blockList || [], this as BlockSvg)); } // Mixin the object. this.mixin(mixinObj); @@ -108,7 +110,8 @@ export function unregister(name: string) { delete allExtensions[name]; } else { console.warn( - 'No extension mapping for name "' + name + '" found to unregister'); + 'No extension mapping for name "' + name + '" found to unregister' + ); } } @@ -151,11 +154,15 @@ export function apply(name: string, block: Block, isMutator: boolean) { const errorPrefix = 'Error after applying mutator "' + name + '": '; checkHasMutatorProperties(errorPrefix, block); } else { - if (!mutatorPropertiesMatch( - mutatorProperties as AnyDuringMigration[], block)) { + if ( + !mutatorPropertiesMatch(mutatorProperties as AnyDuringMigration[], block) + ) { throw Error( - 'Error when applying extension "' + name + '": ' + - 'mutation properties changed when applying a non-mutator extension.'); + 'Error when applying extension "' + + name + + '": ' + + 'mutation properties changed when applying a non-mutator extension.' + ); } } } @@ -173,9 +180,12 @@ function checkNoMutatorProperties(mutationName: string, block: Block) { const properties = getMutatorProperties(block); if (properties.length) { throw Error( - 'Error: tried to apply mutation "' + mutationName + + 'Error: tried to apply mutation "' + + mutationName + '" to a block that already has mutator functions.' + - ' Block id: ' + block.id); + ' Block id: ' + + block.id + ); } } @@ -191,10 +201,14 @@ function checkNoMutatorProperties(mutationName: string, block: Block) { * actually a function. */ function checkXmlHooks( - object: AnyDuringMigration, errorPrefix: string): boolean { + object: AnyDuringMigration, + errorPrefix: string +): boolean { return checkHasFunctionPair( - object.mutationToDom, object.domToMutation, - errorPrefix + ' mutationToDom/domToMutation'); + object.mutationToDom, + object.domToMutation, + errorPrefix + ' mutationToDom/domToMutation' + ); } /** * Checks if the given object has both the 'saveExtraState' and 'loadExtraState' @@ -208,10 +222,14 @@ function checkXmlHooks( * actually a function. */ function checkJsonHooks( - object: AnyDuringMigration, errorPrefix: string): boolean { + object: AnyDuringMigration, + errorPrefix: string +): boolean { return checkHasFunctionPair( - object.saveExtraState, object.loadExtraState, - errorPrefix + ' saveExtraState/loadExtraState'); + object.saveExtraState, + object.loadExtraState, + errorPrefix + ' saveExtraState/loadExtraState' + ); } /** @@ -225,9 +243,14 @@ function checkJsonHooks( * actually a function. */ function checkMutatorDialog( - object: AnyDuringMigration, errorPrefix: string): boolean { + object: AnyDuringMigration, + errorPrefix: string +): boolean { return checkHasFunctionPair( - object.compose, object.decompose, errorPrefix + ' compose/decompose'); + object.compose, + object.decompose, + errorPrefix + ' compose/decompose' + ); } /** @@ -243,8 +266,10 @@ function checkMutatorDialog( * actually a function. */ function checkHasFunctionPair( - func1: AnyDuringMigration, func2: AnyDuringMigration, - errorPrefix: string): boolean { + func1: AnyDuringMigration, + func2: AnyDuringMigration, + errorPrefix: string +): boolean { if (func1 && func2) { if (typeof func1 !== 'function' || typeof func2 !== 'function') { throw Error(errorPrefix + ' must be a function'); @@ -263,13 +288,16 @@ function checkHasFunctionPair( * @param object The object to inspect. */ function checkHasMutatorProperties( - errorPrefix: string, object: AnyDuringMigration) { + errorPrefix: string, + object: AnyDuringMigration +) { const hasXmlHooks = checkXmlHooks(object, errorPrefix); const hasJsonHooks = checkJsonHooks(object, errorPrefix); if (!hasXmlHooks && !hasJsonHooks) { throw Error( - errorPrefix + - 'Mutations must contain either XML hooks, or JSON hooks, or both'); + errorPrefix + + 'Mutations must contain either XML hooks, or JSON hooks, or both' + ); } // A block with a mutator isn't required to have a mutation dialog, but // it should still have both or neither of compose and decompose. @@ -318,7 +346,9 @@ function getMutatorProperties(block: Block): AnyDuringMigration[] { * @returns True if the property lists match. */ function mutatorPropertiesMatch( - oldProperties: AnyDuringMigration[], block: Block): boolean { + oldProperties: AnyDuringMigration[], + block: Block +): boolean { const newProperties = getMutatorProperties(block); if (newProperties.length !== oldProperties.length) { return false; @@ -343,10 +373,10 @@ export function runAfterPageLoad(fn: () => void) { throw Error('runAfterPageLoad() requires browser document.'); } if (document.readyState === 'complete') { - fn(); // Page has already loaded. Call immediately. + fn(); // Page has already loaded. Call immediately. } else { // Poll readyState. - const readyStateCheckInterval = setInterval(function() { + const readyStateCheckInterval = setInterval(function () { if (document.readyState === 'complete') { clearInterval(readyStateCheckInterval); fn(); @@ -375,7 +405,9 @@ export function runAfterPageLoad(fn: () => void) { * @returns The extension function. */ export function buildTooltipForDropdown( - dropdownName: string, lookupTable: {[key: string]: string}): Function { + dropdownName: string, + lookupTable: {[key: string]: string} +): Function { // List of block types already validated, to minimize duplicate warnings. const blockTypesChecked: AnyDuringMigration[] = []; @@ -383,8 +415,9 @@ export function buildTooltipForDropdown( // Wait for load, in case Blockly.Msg is not yet populated. // runAfterPageLoad() does not run in a Node.js environment due to lack // of document object, in which case skip the validation. - if (typeof document === 'object') { // Relies on document.readyState - runAfterPageLoad(function() { + if (typeof document === 'object') { + // Relies on document.readyState + runAfterPageLoad(function () { for (const key in lookupTable) { // Will print warnings if reference is missing. parsing.checkMessageReferences(lookupTable[key]); @@ -399,24 +432,29 @@ export function buildTooltipForDropdown( blockTypesChecked.push(this.type); } - this.setTooltip(function(this: Block) { - const value = String(this.getFieldValue(dropdownName)); - let tooltip = lookupTable[value]; - if (tooltip === null) { - if (blockTypesChecked.indexOf(this.type) === -1) { - // Warn for missing values on generated tooltips. - let warning = 'No tooltip mapping for value ' + value + ' of field ' + + this.setTooltip( + function (this: Block) { + const value = String(this.getFieldValue(dropdownName)); + let tooltip = lookupTable[value]; + if (tooltip === null) { + if (blockTypesChecked.indexOf(this.type) === -1) { + // Warn for missing values on generated tooltips. + let warning = + 'No tooltip mapping for value ' + + value + + ' of field ' + dropdownName; - if (this.type !== null) { - warning += ' of block type ' + this.type; + if (this.type !== null) { + warning += ' of block type ' + this.type; + } + console.warn(warning + '.'); } - console.warn(warning + '.'); + } else { + tooltip = parsing.replaceMessageReferences(tooltip); } - } else { - tooltip = parsing.replaceMessageReferences(tooltip); - } - return tooltip; - }.bind(this)); + return tooltip; + }.bind(this) + ); } return extensionFn; } @@ -430,17 +468,25 @@ export function buildTooltipForDropdown( * @param lookupTable The string lookup table */ function checkDropdownOptionsInTable( - block: Block, dropdownName: string, lookupTable: {[key: string]: string}) { + block: Block, + dropdownName: string, + lookupTable: {[key: string]: string} +) { // Validate all dropdown options have values. const dropdown = block.getField(dropdownName); if (dropdown instanceof FieldDropdown && !dropdown.isOptionListDynamic()) { const options = dropdown.getOptions(); for (let i = 0; i < options.length; i++) { - const optionKey = options[i][1]; // label, then value + const optionKey = options[i][1]; // label, then value if (lookupTable[optionKey] === null) { console.warn( - 'No tooltip mapping for value ' + optionKey + ' of field ' + - dropdownName + ' of block type ' + block.type); + 'No tooltip mapping for value ' + + optionKey + + ' of field ' + + dropdownName + + ' of block type ' + + block.type + ); } } } @@ -457,13 +503,16 @@ function checkDropdownOptionsInTable( * @returns The extension function. */ export function buildTooltipWithFieldText( - msgTemplate: string, fieldName: string): Function { + msgTemplate: string, + fieldName: string +): Function { // Check the tooltip string messages for invalid references. // Wait for load, in case Blockly.Msg is not yet populated. // runAfterPageLoad() does not run in a Node.js environment due to lack // of document object, in which case skip the validation. - if (typeof document === 'object') { // Relies on document.readyState - runAfterPageLoad(function() { + if (typeof document === 'object') { + // Relies on document.readyState + runAfterPageLoad(function () { // Will print warnings if reference is missing. parsing.checkMessageReferences(msgTemplate); }); @@ -471,11 +520,14 @@ export function buildTooltipWithFieldText( /** The actual extension. */ function extensionFn(this: Block) { - this.setTooltip(function(this: Block) { - const field = this.getField(fieldName); - return parsing.replaceMessageReferences(msgTemplate) + this.setTooltip( + function (this: Block) { + const field = this.getField(fieldName); + return parsing + .replaceMessageReferences(msgTemplate) .replace('%1', field ? field.getText() : ''); - }.bind(this)); + }.bind(this) + ); } return extensionFn; } @@ -488,10 +540,14 @@ export function buildTooltipWithFieldText( */ function extensionParentTooltip(this: Block) { const tooltipWhenNotConnected = this.tooltip; - this.setTooltip(function(this: Block) { - const parent = this.getParent(); - return parent && parent.getInputsInline() && parent.tooltip || - tooltipWhenNotConnected; - }.bind(this)); + this.setTooltip( + function (this: Block) { + const parent = this.getParent(); + return ( + (parent && parent.getInputsInline() && parent.tooltip) || + tooltipWhenNotConnected + ); + }.bind(this) + ); } register('parent_tooltip_when_inline', extensionParentTooltip); diff --git a/core/field.ts b/core/field.ts index eda991fe4..866d31bf7 100644 --- a/core/field.ts +++ b/core/field.ts @@ -22,7 +22,7 @@ import type {BlockSvg} from './block_svg.js'; import * as browserEvents from './browser_events.js'; import * as dropDownDiv from './dropdowndiv.js'; import * as eventUtils from './events/utils.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; import type {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js'; @@ -42,6 +42,7 @@ import * as userAgent from './utils/useragent.js'; import * as utilsXml from './utils/xml.js'; import * as WidgetDiv from './widgetdiv.js'; import type {WorkspaceSvg} from './workspace_svg.js'; +import {ISerializable} from './interfaces/i_serializable.js'; /** * A function that is called to validate changes to the field's value before @@ -58,17 +59,21 @@ import type {WorkspaceSvg} from './workspace_svg.js'; * * - `undefined` to set `newValue` as is. */ -export type FieldValidator = (newValue: T) => T|null|undefined; +export type FieldValidator = (newValue: T) => T | null | undefined; /** * Abstract class for an editable field. * * @typeParam T - The value stored on the field. */ -export abstract class Field implements IASTNodeLocationSvg, - IASTNodeLocationWithBlock, - IKeyboardAccessible, - IRegistrable { +export abstract class Field + implements + IASTNodeLocationSvg, + IASTNodeLocationWithBlock, + IKeyboardAccessible, + IRegistrable, + ISerializable +{ /** * To overwrite the default value which is set in **Field**, directly update * the prototype. @@ -78,7 +83,7 @@ export abstract class Field implements IASTNodeLocationSvg, * FieldImage.prototype.DEFAULT_VALUE = null; * ``` */ - DEFAULT_VALUE: T|null = null; + DEFAULT_VALUE: T | null = null; /** Non-breaking space. */ static readonly NBSP = '\u00A0'; @@ -95,47 +100,47 @@ export abstract class Field implements IASTNodeLocationSvg, * Static labels are usually unnamed. */ name?: string = undefined; - protected value_: T|null; + protected value_: T | null; /** Validation function called when user edits an editable field. */ - protected validator_: FieldValidator|null = null; + protected validator_: FieldValidator | null = null; /** * Used to cache the field's tooltip value if setTooltip is called when the * field is not yet initialized. Is *not* guaranteed to be accurate. */ - private tooltip_: Tooltip.TipInfo|null = null; + private tooltip_: Tooltip.TipInfo | null = null; protected size_: Size; /** * Holds the cursors svg element when the cursor is attached to the field. * This is null if there is no cursor on the field. */ - private cursorSvg_: SVGElement|null = null; + private cursorSvg_: SVGElement | null = null; /** * Holds the markers svg element when the marker is attached to the field. * This is null if there is no marker on the field. */ - private markerSvg_: SVGElement|null = null; + private markerSvg_: SVGElement | null = null; /** The rendered field's SVG group element. */ - protected fieldGroup_: SVGGElement|null = null; + protected fieldGroup_: SVGGElement | null = null; /** The rendered field's SVG border element. */ - protected borderRect_: SVGRectElement|null = null; + protected borderRect_: SVGRectElement | null = null; /** The rendered field's SVG text element. */ - protected textElement_: SVGTextElement|null = null; + protected textElement_: SVGTextElement | null = null; /** The rendered field's text content element. */ - protected textContent_: Text|null = null; + protected textContent_: Text | null = null; /** Mouse down event listener data. */ - private mouseDownWrapper_: browserEvents.Data|null = null; + private mouseDownWrapper_: browserEvents.Data | null = null; /** Constants associated with the source block's renderer. */ - protected constants_: ConstantProvider|null = null; + protected constants_: ConstantProvider | null = null; /** * Has this field been disposed of? @@ -148,7 +153,7 @@ export abstract class Field implements IASTNodeLocationSvg, maxDisplayLength = 50; /** Block this field is attached to. Starts as null, then set in init. */ - protected sourceBlock_: Block|null = null; + protected sourceBlock_: Block | null = null; /** Does this block need to be re-rendered? */ protected isDirty_ = true; @@ -162,21 +167,21 @@ export abstract class Field implements IASTNodeLocationSvg, protected enabled_ = true; /** The element the click handler is bound to. */ - protected clickTarget_: Element|null = null; + protected clickTarget_: Element | null = null; /** * The prefix field. * * @internal */ - prefixField: string|null = null; + prefixField: string | null = null; /** * The suffix field. * * @internal */ - suffixField: string|null = null; + suffixField: string | null = null; /** * Editable fields usually show some sort of UI indicating they are @@ -207,15 +212,18 @@ export abstract class Field implements IASTNodeLocationSvg, * this parameter supports. */ constructor( - value: T|typeof Field.SKIP_SETUP, validator?: FieldValidator|null, - config?: FieldConfig) { + value: T | typeof Field.SKIP_SETUP, + validator?: FieldValidator | null, + config?: FieldConfig + ) { /** * A generic value possessed by the field. * Should generally be non-null, only null when the field is created. */ - this.value_ = 'DEFAULT_VALUE' in new.target.prototype ? - new.target.prototype.DEFAULT_VALUE : - this.DEFAULT_VALUE; + this.value_ = + 'DEFAULT_VALUE' in new.target.prototype + ? new.target.prototype.DEFAULT_VALUE + : this.DEFAULT_VALUE; /** The size of the area rendered by the field. */ this.size_ = new Size(0, 0); @@ -262,13 +270,16 @@ export abstract class Field implements IASTNodeLocationSvg, * * @returns The renderer constant provider. */ - getConstants(): ConstantProvider|null { - if (!this.constants_ && this.sourceBlock_ && - !this.sourceBlock_.isDeadOrDying() && - this.sourceBlock_.workspace.rendered) { + getConstants(): ConstantProvider | null { + if ( + !this.constants_ && + this.sourceBlock_ && + !this.sourceBlock_.isDeadOrDying() && + this.sourceBlock_.workspace.rendered + ) { this.constants_ = (this.sourceBlock_.workspace as WorkspaceSvg) - .getRenderer() - .getConstants(); + .getRenderer() + .getConstants(); } return this.constants_; } @@ -279,7 +290,7 @@ export abstract class Field implements IASTNodeLocationSvg, * @returns The block containing this field. * @throws An error if the source block is not defined. */ - getSourceBlock(): Block|null { + getSourceBlock(): Block | null { return this.sourceBlock_; } @@ -333,16 +344,18 @@ export abstract class Field implements IASTNodeLocationSvg, */ protected createBorderRect_() { this.borderRect_ = dom.createSvgElement( - Svg.RECT, { - 'rx': this.getConstants()!.FIELD_BORDER_RECT_RADIUS, - 'ry': this.getConstants()!.FIELD_BORDER_RECT_RADIUS, - 'x': 0, - 'y': 0, - 'height': this.size_.height, - 'width': this.size_.width, - 'class': 'blocklyFieldRect', - }, - this.fieldGroup_); + Svg.RECT, + { + 'rx': this.getConstants()!.FIELD_BORDER_RECT_RADIUS, + 'ry': this.getConstants()!.FIELD_BORDER_RECT_RADIUS, + 'x': 0, + 'y': 0, + 'height': this.size_.height, + 'width': this.size_.width, + 'class': 'blocklyFieldRect', + }, + this.fieldGroup_ + ); } /** @@ -352,10 +365,12 @@ export abstract class Field implements IASTNodeLocationSvg, */ protected createTextElement_() { this.textElement_ = dom.createSvgElement( - Svg.TEXT, { - 'class': 'blocklyText', - }, - this.fieldGroup_); + Svg.TEXT, + { + 'class': 'blocklyText', + }, + this.fieldGroup_ + ); if (this.getConstants()!.FIELD_TEXT_BASELINE_CENTER) { this.textElement_.setAttribute('dominant-baseline', 'central'); } @@ -372,7 +387,11 @@ export abstract class Field implements IASTNodeLocationSvg, if (!clickTarget) throw new Error('A click target has not been set.'); Tooltip.bindMouseEvents(clickTarget); this.mouseDownWrapper_ = browserEvents.conditionalBind( - clickTarget, 'pointerdown', this, this.onMouseDown_); + clickTarget, + 'pointerdown', + this, + this.onMouseDown_ + ); } /** @@ -442,14 +461,18 @@ export abstract class Field implements IASTNodeLocationSvg, * Used to see if `this` has overridden any relevant hooks. * @returns The stringified version of the XML state, or null. */ - protected saveLegacyState(callingClass: FieldProto): string|null { - if (callingClass.prototype.saveState === this.saveState && - callingClass.prototype.toXml !== this.toXml) { + protected saveLegacyState(callingClass: FieldProto): string | null { + if ( + callingClass.prototype.saveState === this.saveState && + callingClass.prototype.toXml !== this.toXml + ) { const elem = utilsXml.createElement('field'); elem.setAttribute('name', this.name || ''); const text = utilsXml.domToText(this.toXml(elem)); return text.replace( - ' xmlns="https://developers.google.com/blockly/xml"', ''); + ' xmlns="https://developers.google.com/blockly/xml"', + '' + ); } // Either they called this on purpose from their saveState, or they have // no implementations of either hook. Just do our thing. @@ -465,10 +488,14 @@ export abstract class Field implements IASTNodeLocationSvg, * @param state The state to apply to the field. * @returns Whether the state was applied or not. */ - loadLegacyState(callingClass: FieldProto, state: AnyDuringMigration): - boolean { - if (callingClass.prototype.loadState === this.loadState && - callingClass.prototype.fromXml !== this.fromXml) { + loadLegacyState( + callingClass: FieldProto, + state: AnyDuringMigration + ): boolean { + if ( + callingClass.prototype.loadState === this.loadState && + callingClass.prototype.fromXml !== this.fromXml + ) { this.fromXml(utilsXml.textToDom(state as string)); return true; } @@ -538,9 +565,12 @@ export abstract class Field implements IASTNodeLocationSvg, * @returns Whether this field is clickable. */ isClickable(): boolean { - return this.enabled_ && !!this.sourceBlock_ && - this.sourceBlock_.isEditable() && - this.showEditor_ !== Field.prototype.showEditor_; + return ( + this.enabled_ && + !!this.sourceBlock_ && + this.sourceBlock_.isEditable() && + this.showEditor_ !== Field.prototype.showEditor_ + ); } /** @@ -552,8 +582,12 @@ export abstract class Field implements IASTNodeLocationSvg, * editable block. */ isCurrentlyEditable(): boolean { - return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ && - this.sourceBlock_.isEditable(); + return ( + this.enabled_ && + this.EDITABLE && + !!this.sourceBlock_ && + this.sourceBlock_.isEditable() + ); } /** @@ -569,9 +603,10 @@ export abstract class Field implements IASTNodeLocationSvg, isSerializable = true; } else if (this.EDITABLE) { console.warn( - 'Detected an editable field that was not serializable.' + + 'Detected an editable field that was not serializable.' + ' Please define SERIALIZABLE property as true on all editable custom' + - ' fields. Proceeding with serialization.'); + ' fields. Proceeding with serialization.' + ); isSerializable = true; } } @@ -629,7 +664,7 @@ export abstract class Field implements IASTNodeLocationSvg, * * @returns Validation function, or null. */ - getValidator(): FieldValidator|null { + getValidator(): FieldValidator | null { return this.validator_; } @@ -639,7 +674,7 @@ export abstract class Field implements IASTNodeLocationSvg, * * @returns The group element. */ - getSvgRoot(): SVGGElement|null { + getSvgRoot(): SVGGElement | null { return this.fieldGroup_; } @@ -683,13 +718,18 @@ export abstract class Field implements IASTNodeLocationSvg, } /** - * Updates the field to match the colour/style of the block. Should only be - * called by BlockSvg.applyColour(). + * Updates the field to match the colour/style of the block. * - * @internal + * Non-abstract sub-classes may wish to implement this if the colour of the + * field depends on the colour of the block. It will automatically be called + * at relevant times, such as when the parent block or renderer changes. + * + * See {@link + * https://developers.google.com/blockly/guides/create-custom-blocks/fields/customizing-fields/creating#matching_block_colours + * | the field documentation} for more information, or FieldDropdown for an + * example. */ applyColour() {} - // Non-abstract sub-classes may wish to implement this. See FieldDropdown. /** * Used by getSize() to move/resize any DOM elements, and get the new size. @@ -729,6 +769,28 @@ export abstract class Field implements IASTNodeLocationSvg, protected showEditor_(_e?: Event): void {} // NOP + /** + * A developer hook to reposition the WidgetDiv during a window resize. You + * need to define this hook if your field has a WidgetDiv that needs to + * reposition itself when the window is resized. For example, text input + * fields define this hook so that the input WidgetDiv can reposition itself + * on a window resize event. This is especially important when modal inputs + * have been disabled, as Android devices will fire a window resize event when + * the soft keyboard opens. + * + * If you want the WidgetDiv to hide itself instead of repositioning, return + * false. This is the default behavior. + * + * DropdownDivs already handle their own positioning logic, so you do not need + * to override this function if your field only has a DropdownDiv. + * + * @returns True if the field should be repositioned, + * false if the WidgetDiv should hide itself instead. + */ + repositionForWindowResize(): boolean { + return false; + } + /** * Updates the size of the field based on the text. * @@ -736,17 +798,23 @@ export abstract class Field implements IASTNodeLocationSvg, */ protected updateSize_(margin?: number) { const constants = this.getConstants(); - const xOffset = margin !== undefined ? margin : - this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : - 0; + const xOffset = + margin !== undefined + ? margin + : this.borderRect_ + ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING + : 0; let totalWidth = xOffset * 2; let totalHeight = constants!.FIELD_TEXT_HEIGHT; let contentWidth = 0; if (this.textElement_) { contentWidth = dom.getFastTextWidth( - this.textElement_, constants!.FIELD_TEXT_FONTSIZE, - constants!.FIELD_TEXT_FONTWEIGHT, constants!.FIELD_TEXT_FONTFAMILY); + this.textElement_, + constants!.FIELD_TEXT_FONTSIZE, + constants!.FIELD_TEXT_FONTWEIGHT, + constants!.FIELD_TEXT_FONTFAMILY + ); totalWidth += contentWidth; } if (this.borderRect_) { @@ -775,18 +843,23 @@ export abstract class Field implements IASTNodeLocationSvg, const halfHeight = this.size_.height / 2; this.textElement_.setAttribute( - 'x', - String( - this.getSourceBlock()?.RTL ? - this.size_.width - contentWidth - xOffset : - xOffset)); + 'x', + String( + this.getSourceBlock()?.RTL + ? this.size_.width - contentWidth - xOffset + : xOffset + ) + ); this.textElement_.setAttribute( - 'y', - String( - constants!.FIELD_TEXT_BASELINE_CENTER ? - halfHeight : - halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 + - constants!.FIELD_TEXT_BASELINE)); + 'y', + String( + constants!.FIELD_TEXT_BASELINE_CENTER + ? halfHeight + : halfHeight - + constants!.FIELD_TEXT_HEIGHT / 2 + + constants!.FIELD_TEXT_BASELINE + ) + ); } /** Position a field's border rect after a size change. */ @@ -797,9 +870,13 @@ export abstract class Field implements IASTNodeLocationSvg, this.borderRect_.setAttribute('width', String(this.size_.width)); this.borderRect_.setAttribute('height', String(this.size_.height)); this.borderRect_.setAttribute( - 'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS)); + 'rx', + String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS) + ); this.borderRect_.setAttribute( - 'ry', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS)); + 'ry', + String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS) + ); } /** @@ -824,8 +901,9 @@ export abstract class Field implements IASTNodeLocationSvg, // Don't issue a warning if the field is actually zero width. if (this.size_.width !== 0) { console.warn( - 'Deprecated use of setting size_.width to 0 to rerender a' + - ' field. Set field.isDirty_ to true instead.'); + 'Deprecated use of setting size_.width to 0 to rerender a' + + ' field. Set field.isDirty_ to true instead.' + ); } } return this.size_; @@ -925,7 +1003,7 @@ export abstract class Field implements IASTNodeLocationSvg, * * @returns Current text or null. */ - protected getText_(): string|null { + protected getText_(): string | null { return null; } @@ -964,9 +1042,12 @@ export abstract class Field implements IASTNodeLocationSvg, * than this method. * * @param newValue New value. + * @param fireChangeEvent Whether to fire a change event. Defaults to true. + * Should usually be true unless the change will be reported some other + * way, e.g. an intermediate field change event. * @sealed */ - setValue(newValue: AnyDuringMigration) { + setValue(newValue: AnyDuringMigration, fireChangeEvent = true) { const doLogging = false; if (newValue === null) { doLogging && console.log('null, return'); @@ -1002,9 +1083,16 @@ export abstract class Field implements IASTNodeLocationSvg, } this.doValueUpdate_(localValue); - if (source && eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - source, 'field', this.name || null, oldValue, localValue)); + if (fireChangeEvent && source && eventUtils.isEnabled()) { + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + source, + 'field', + this.name || null, + oldValue, + localValue + ) + ); } if (this.isDirty_) { this.forceRerender(); @@ -1020,7 +1108,9 @@ export abstract class Field implements IASTNodeLocationSvg, * @returns New value, or an Error object. */ private processValidation_( - newValue: AnyDuringMigration, validatedValue: T|null|undefined): T|Error { + newValue: AnyDuringMigration, + validatedValue: T | null | undefined + ): T | Error { if (validatedValue === null) { this.doValueInvalid_(newValue); if (this.isDirty_) { @@ -1028,7 +1118,7 @@ export abstract class Field implements IASTNodeLocationSvg, } return Error(); } - return validatedValue === undefined ? newValue as T : validatedValue; + return validatedValue === undefined ? (newValue as T) : validatedValue; } /** @@ -1036,7 +1126,7 @@ export abstract class Field implements IASTNodeLocationSvg, * * @returns Current value. */ - getValue(): T|null { + getValue(): T | null { return this.value_; } @@ -1060,10 +1150,11 @@ export abstract class Field implements IASTNodeLocationSvg, * * - `undefined` to set `newValue` as is. */ - protected doClassValidation_(newValue: T): T|null|undefined; - protected doClassValidation_(newValue?: AnyDuringMigration): T|null; - protected doClassValidation_(newValue?: T|AnyDuringMigration): T|null - |undefined { + protected doClassValidation_(newValue: T): T | null | undefined; + protected doClassValidation_(newValue?: AnyDuringMigration): T | null; + protected doClassValidation_( + newValue?: T | AnyDuringMigration + ): T | null | undefined { if (newValue === null || newValue === undefined) { return null; } @@ -1115,8 +1206,9 @@ export abstract class Field implements IASTNodeLocationSvg, * display the tooltip of the parent block. To not display a tooltip pass * the empty string. */ - setTooltip(newTip: Tooltip.TipInfo|null) { - if (!newTip && newTip !== '') { // If null or undefined. + setTooltip(newTip: Tooltip.TipInfo | null) { + if (!newTip && newTip !== '') { + // If null or undefined. newTip = this.sourceBlock_; } const clickTarget = this.getClickTarget_(); @@ -1149,7 +1241,7 @@ export abstract class Field implements IASTNodeLocationSvg, * * @returns Element to bind click handler to. */ - protected getClickTarget_(): Element|null { + protected getClickTarget_(): Element | null { return this.clickTarget_ || this.getSvgRoot(); } @@ -1323,7 +1415,8 @@ export class UnattachedFieldError extends Error { /** @internal */ constructor() { super( - 'The field has not yet been attached to its input. ' + - 'Call appendField to attach it.'); + 'The field has not yet been attached to its input. ' + + 'Call appendField to attach it.' + ); } } diff --git a/core/field_angle.ts b/core/field_angle.ts index e5fbbf35a..1f06bfc44 100644 --- a/core/field_angle.ts +++ b/core/field_angle.ts @@ -16,9 +16,14 @@ import {BlockSvg} from './block_svg.js'; import * as browserEvents from './browser_events.js'; import * as Css from './css.js'; import * as dropDownDiv from './dropdowndiv.js'; +import * as eventUtils from './events/utils.js'; import {Field, UnattachedFieldError} from './field.js'; import * as fieldRegistry from './field_registry.js'; -import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js'; +import { + FieldInput, + FieldInputConfig, + FieldInputValidator, +} from './field_input.js'; import * as dom from './utils/dom.js'; import * as math from './utils/math.js'; import {Svg} from './utils/svg.js'; @@ -92,13 +97,13 @@ export class FieldAngle extends FieldInput { private boundEvents: browserEvents.Data[] = []; /** Dynamic red line pointing at the value's angle. */ - private line: SVGLineElement|null = null; + private line: SVGLineElement | null = null; /** Dynamic pink area extending from 0 to the value's angle. */ - private gauge: SVGPathElement|null = null; + private gauge: SVGPathElement | null = null; /** The degree symbol for this field. */ - protected symbol_: SVGTSpanElement|null = null; + protected symbol_: SVGTSpanElement | null = null; /** * @param value The initial value of the field. Should cast to a number. @@ -114,8 +119,10 @@ export class FieldAngle extends FieldInput { * for a list of properties this parameter supports. */ constructor( - value?: string|number|typeof Field.SKIP_SETUP, - validator?: FieldAngleValidator, config?: FieldAngleConfig) { + value?: string | number | typeof Field.SKIP_SETUP, + validator?: FieldAngleValidator, + config?: FieldAngleConfig + ) { super(Field.SKIP_SETUP); if (value === Field.SKIP_SETUP) return; @@ -192,8 +199,9 @@ export class FieldAngle extends FieldInput { if (this.sourceBlock_ instanceof BlockSvg) { dropDownDiv.setColour( - this.sourceBlock_.style.colourPrimary, - this.sourceBlock_.style.colourTertiary); + this.sourceBlock_.style.colourPrimary, + this.sourceBlock_.style.colourTertiary + ); } dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this)); @@ -217,50 +225,80 @@ export class FieldAngle extends FieldInput { 'style': 'touch-action: none', }); const circle = dom.createSvgElement( - Svg.CIRCLE, { - 'cx': FieldAngle.HALF, - 'cy': FieldAngle.HALF, - 'r': FieldAngle.RADIUS, - 'class': 'blocklyAngleCircle', - }, - svg); - this.gauge = - dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg); + Svg.CIRCLE, + { + 'cx': FieldAngle.HALF, + 'cy': FieldAngle.HALF, + 'r': FieldAngle.RADIUS, + 'class': 'blocklyAngleCircle', + }, + svg + ); + this.gauge = dom.createSvgElement( + Svg.PATH, + {'class': 'blocklyAngleGauge'}, + svg + ); this.line = dom.createSvgElement( - Svg.LINE, { - 'x1': FieldAngle.HALF, - 'y1': FieldAngle.HALF, - 'class': 'blocklyAngleLine', - }, - svg); + Svg.LINE, + { + 'x1': FieldAngle.HALF, + 'y1': FieldAngle.HALF, + 'class': 'blocklyAngleLine', + }, + svg + ); // Draw markers around the edge. for (let angle = 0; angle < 360; angle += 15) { dom.createSvgElement( - Svg.LINE, { - 'x1': FieldAngle.HALF + FieldAngle.RADIUS, - 'y1': FieldAngle.HALF, - 'x2': FieldAngle.HALF + FieldAngle.RADIUS - - (angle % 45 === 0 ? 10 : 5), - 'y2': FieldAngle.HALF, - 'class': 'blocklyAngleMarks', - 'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' + - FieldAngle.HALF + ')', - }, - svg); + Svg.LINE, + { + 'x1': FieldAngle.HALF + FieldAngle.RADIUS, + 'y1': FieldAngle.HALF, + 'x2': + FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5), + 'y2': FieldAngle.HALF, + 'class': 'blocklyAngleMarks', + 'transform': + 'rotate(' + + angle + + ',' + + FieldAngle.HALF + + ',' + + FieldAngle.HALF + + ')', + }, + svg + ); } // The angle picker is different from other fields in that it updates on // mousemove even if it's not in the middle of a drag. In future we may // change this behaviour. this.boundEvents.push( - browserEvents.conditionalBind(svg, 'click', this, this.hide)); + browserEvents.conditionalBind(svg, 'click', this, this.hide) + ); // On touch devices, the picker's value is only updated with a drag. Add // a click handler on the drag surface to update the value if the surface // is clicked. - this.boundEvents.push(browserEvents.conditionalBind( - circle, 'pointerdown', this, this.onMouseMove_, true)); - this.boundEvents.push(browserEvents.conditionalBind( - circle, 'pointermove', this, this.onMouseMove_, true)); + this.boundEvents.push( + browserEvents.conditionalBind( + circle, + 'pointerdown', + this, + this.onMouseMove_, + true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + circle, + 'pointermove', + this, + this.onMouseMove_, + true + ) + ); return svg; } @@ -326,7 +364,26 @@ export class FieldAngle extends FieldInput { } angle = this.wrapValue(angle); if (angle !== this.value_) { - this.setEditorValue_(angle); + // Intermediate value changes from user input are not confirmed until the + // user closes the editor, and may be numerous. Inhibit reporting these as + // normal block change events, and instead report them as special + // intermediate changes that do not get recorded in undo history. + const oldValue = this.value_; + this.setEditorValue_(angle, false); + if ( + this.sourceBlock_ && + eventUtils.isEnabled() && + this.value_ !== oldValue + ) { + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE))( + this.sourceBlock_, + this.name || null, + oldValue, + this.value_ + ) + ); + } } } @@ -353,14 +410,31 @@ export class FieldAngle extends FieldInput { x2 += Math.cos(angleRadians) * FieldAngle.RADIUS; y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS; // Don't ask how the flag calculations work. They just do. - let largeFlag = - Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2); + let largeFlag = Math.abs( + Math.floor((angleRadians - angle1) / Math.PI) % 2 + ); if (clockwiseFlag) { largeFlag = 1 - largeFlag; } path.push( - ' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS, - ' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z'); + ' l ', + x1, + ',', + y1, + ' A ', + FieldAngle.RADIUS, + ',', + FieldAngle.RADIUS, + ' 0 ', + largeFlag, + ' ', + clockwiseFlag, + ' ', + x2, + ',', + y2, + ' z' + ); } this.gauge.setAttribute('d', path.join('')); this.line.setAttribute('x2', `${x2}`); @@ -412,7 +486,7 @@ export class FieldAngle extends FieldInput { * @param newValue The input value. * @returns A valid angle, or null if invalid. */ - protected override doClassValidation_(newValue?: any): number|null { + protected override doClassValidation_(newValue?: any): number | null { const value = Number(newValue); if (isNaN(value) || !isFinite(value)) { return null; @@ -456,7 +530,6 @@ fieldRegistry.register('field_angle', FieldAngle); FieldAngle.prototype.DEFAULT_VALUE = 0; - /** * CSS for angle field. */ diff --git a/core/field_checkbox.ts b/core/field_checkbox.ts index 3f26d6700..bd8533c2d 100644 --- a/core/field_checkbox.ts +++ b/core/field_checkbox.ts @@ -19,8 +19,8 @@ import * as dom from './utils/dom.js'; import {Field, FieldConfig, FieldValidator} from './field.js'; import * as fieldRegistry from './field_registry.js'; -type BoolString = 'TRUE'|'FALSE'; -type CheckboxBool = BoolString|boolean; +type BoolString = 'TRUE' | 'FALSE'; +type CheckboxBool = BoolString | boolean; /** * Class for a checkbox field. @@ -28,7 +28,7 @@ type CheckboxBool = BoolString|boolean; export class FieldCheckbox extends Field { /** Default character for the checkmark. */ static readonly CHECK_CHAR = '✓'; - private checkChar_: string; + private checkChar: string; /** * Serializable fields are saved by the serializer, non-serializable fields @@ -45,7 +45,7 @@ export class FieldCheckbox extends Field { * NOTE: The default value is set in `Field`, so maintain that value instead * of overwriting it here or in the constructor. */ - override value_: boolean|null = this.value_; + override value_: boolean | null = this.value_; /** * @param value The initial value of the field. Should either be 'TRUE', @@ -62,15 +62,17 @@ export class FieldCheckbox extends Field { * for a list of properties this parameter supports. */ constructor( - value?: CheckboxBool|typeof Field.SKIP_SETUP, - validator?: FieldCheckboxValidator, config?: FieldCheckboxConfig) { + value?: CheckboxBool | typeof Field.SKIP_SETUP, + validator?: FieldCheckboxValidator, + config?: FieldCheckboxConfig + ) { super(Field.SKIP_SETUP); /** * Character for the check mark. Used to apply a different check mark * character to individual fields. */ - this.checkChar_ = FieldCheckbox.CHECK_CHAR; + this.checkChar = FieldCheckbox.CHECK_CHAR; if (value === Field.SKIP_SETUP) return; if (config) { @@ -89,7 +91,7 @@ export class FieldCheckbox extends Field { */ protected override configure_(config: FieldCheckboxConfig) { super.configure_(config); - if (config.checkCharacter) this.checkChar_ = config.checkCharacter; + if (config.checkCharacter) this.checkChar = config.checkCharacter; } /** @@ -127,7 +129,7 @@ export class FieldCheckbox extends Field { } override getDisplayText_() { - return this.checkChar_; + return this.checkChar; } /** @@ -136,8 +138,8 @@ export class FieldCheckbox extends Field { * @param character The character to use for the check mark, or null to use * the default. */ - setCheckCharacter(character: string|null) { - this.checkChar_ = character || FieldCheckbox.CHECK_CHAR; + setCheckCharacter(character: string | null) { + this.checkChar = character || FieldCheckbox.CHECK_CHAR; this.forceRerender(); } @@ -152,8 +154,9 @@ export class FieldCheckbox extends Field { * @param newValue The input value. * @returns A valid value ('TRUE' or 'FALSE), or null if invalid. */ - protected override doClassValidation_(newValue?: AnyDuringMigration): - BoolString|null { + protected override doClassValidation_( + newValue?: AnyDuringMigration + ): BoolString | null { if (newValue === true || newValue === 'TRUE') { return 'TRUE'; } @@ -191,7 +194,7 @@ export class FieldCheckbox extends Field { * * @returns The boolean value of this field. */ - getValueBoolean(): boolean|null { + getValueBoolean(): boolean | null { return this.value_; } @@ -213,7 +216,7 @@ export class FieldCheckbox extends Field { * @param value The value to convert. * @returns The converted value. */ - private convertValueToBool_(value: CheckboxBool|null): boolean { + private convertValueToBool_(value: CheckboxBool | null): boolean { if (typeof value === 'string') return value === 'TRUE'; return !!value; } diff --git a/core/field_colour.ts b/core/field_colour.ts index 87c34b99d..5ff12c595 100644 --- a/core/field_colour.ts +++ b/core/field_colour.ts @@ -36,6 +36,7 @@ export class FieldColour extends Field { * Copied from goog.ui.ColorPicker.SIMPLE_GRID_COLORS * All colour pickers use this unless overridden with setColours. */ + // prettier-ignore static COLOURS: string[] = [ // grays '#ffffff', '#cccccc', '#c0c0c0', '#999999', @@ -74,10 +75,10 @@ export class FieldColour extends Field { static COLUMNS = 7; /** The field's colour picker element. */ - private picker: HTMLElement|null = null; + private picker: HTMLElement | null = null; /** Index of the currently highlighted element. */ - private highlightedIndex: number|null = null; + private highlightedIndex: number | null = null; /** * Array holding info needed to unbind events. @@ -103,13 +104,13 @@ export class FieldColour extends Field { protected override isDirty_ = false; /** Array of colours used by this field. If null, use the global list. */ - private colours: string[]|null = null; + private colours: string[] | null = null; /** * Array of colour tooltips used by this field. If null, use the global * list. */ - private titles: string[]|null = null; + private titles: string[] | null = null; /** * Number of colour columns used by this field. If 0, use the global @@ -132,8 +133,10 @@ export class FieldColour extends Field { * for a list of properties this parameter supports. */ constructor( - value?: string|typeof Field.SKIP_SETUP, validator?: FieldColourValidator, - config?: FieldColourConfig) { + value?: string | typeof Field.SKIP_SETUP, + validator?: FieldColourValidator, + config?: FieldColourConfig + ) { super(Field.SKIP_SETUP); if (value === Field.SKIP_SETUP) return; @@ -165,8 +168,9 @@ export class FieldColour extends Field { */ override initView() { this.size_ = new Size( - this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH, - this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT); + this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH, + this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT + ); if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) { this.createBorderRect_(); this.getBorderRect().style['fillOpacity'] = '1'; @@ -177,8 +181,6 @@ export class FieldColour extends Field { /** * Updates text field to match the colour/style of the block. - * - * @internal */ override applyColour() { if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) { @@ -187,7 +189,9 @@ export class FieldColour extends Field { } } else if (this.sourceBlock_ instanceof BlockSvg) { this.sourceBlock_.pathObject.svgPath.setAttribute( - 'fill', this.getValue() as string); + 'fill', + this.getValue() as string + ); this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff'); } } @@ -198,7 +202,7 @@ export class FieldColour extends Field { * @param newValue The input value. * @returns A valid colour, or null if invalid. */ - protected override doClassValidation_(newValue?: any): string|null { + protected override doClassValidation_(newValue?: any): string | null { if (typeof newValue !== 'string') { return null; } @@ -216,8 +220,10 @@ export class FieldColour extends Field { if (this.borderRect_) { this.borderRect_.style.fill = newValue; } else if ( - this.sourceBlock_ && this.sourceBlock_.rendered && - this.sourceBlock_ instanceof BlockSvg) { + this.sourceBlock_ && + this.sourceBlock_.rendered && + this.sourceBlock_ instanceof BlockSvg + ) { this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue); this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff'); } @@ -299,7 +305,7 @@ export class FieldColour extends Field { */ private onKeyDown(e: KeyboardEvent) { let handled = true; - let highlighted: HTMLElement|null; + let highlighted: HTMLElement | null; switch (e.key) { case 'ArrowUp': this.moveHighlightBy(0, -1); @@ -425,7 +431,7 @@ export class FieldColour extends Field { * * @returns Highlighted item (null if none). */ - private getHighlighted(): HTMLElement|null { + private getHighlighted(): HTMLElement | null { if (!this.highlightedIndex) { return null; } @@ -478,7 +484,10 @@ export class FieldColour extends Field { aria.setRole(table, aria.Role.GRID); aria.setState(table, aria.State.EXPANDED, true); aria.setState( - table, aria.State.ROWCOUNT, Math.floor(colours.length / columns)); + table, + aria.State.ROWCOUNT, + Math.floor(colours.length / columns) + ); aria.setState(table, aria.State.COLCOUNT, columns); let row: Element; for (let i = 0; i < colours.length; i++) { @@ -487,7 +496,7 @@ export class FieldColour extends Field { aria.setRole(row, aria.Role.ROW); table.appendChild(row); } - const cell = (document.createElement('td')); + const cell = document.createElement('td'); row!.appendChild(cell); // This becomes the value, if clicked. cell.setAttribute('data-colour', colours[i]); @@ -505,16 +514,51 @@ export class FieldColour extends Field { } // Configure event handler on the table to listen for any event in a cell. - this.boundEvents.push(browserEvents.conditionalBind( - table, 'pointerdown', this, this.onClick, true)); - this.boundEvents.push(browserEvents.conditionalBind( - table, 'pointermove', this, this.onMouseMove, true)); - this.boundEvents.push(browserEvents.conditionalBind( - table, 'pointerenter', this, this.onMouseEnter, true)); - this.boundEvents.push(browserEvents.conditionalBind( - table, 'pointerleave', this, this.onMouseLeave, true)); - this.boundEvents.push(browserEvents.conditionalBind( - table, 'keydown', this, this.onKeyDown, false)); + this.boundEvents.push( + browserEvents.conditionalBind( + table, + 'pointerdown', + this, + this.onClick, + true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + table, + 'pointermove', + this, + this.onMouseMove, + true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + table, + 'pointerenter', + this, + this.onMouseEnter, + true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + table, + 'pointerleave', + this, + this.onMouseLeave, + true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + table, + 'keydown', + this, + this.onKeyDown, + false + ) + ); this.picker = table; } @@ -549,7 +593,6 @@ FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0]; fieldRegistry.register('field_colour', FieldColour); - /** * CSS for colour picker. */ diff --git a/core/field_dropdown.ts b/core/field_dropdown.ts index 9b1fdf615..251ebd81a 100644 --- a/core/field_dropdown.ts +++ b/core/field_dropdown.ts @@ -16,7 +16,12 @@ goog.declareModuleId('Blockly.FieldDropdown'); import type {BlockSvg} from './block_svg.js'; import * as dropDownDiv from './dropdowndiv.js'; -import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js'; +import { + Field, + FieldConfig, + FieldValidator, + UnattachedFieldError, +} from './field.js'; import * as fieldRegistry from './field_registry.js'; import {Menu} from './menu.js'; import {MenuItem} from './menuitem.js'; @@ -44,21 +49,21 @@ export class FieldDropdown extends Field { static ARROW_CHAR = '▾'; /** A reference to the currently selected menu item. */ - private selectedMenuItem_: MenuItem|null = null; + private selectedMenuItem: MenuItem | null = null; /** The dropdown menu. */ - protected menu_: Menu|null = null; + protected menu_: Menu | null = null; /** * SVG image element if currently selected option is an image, or null. */ - private imageElement_: SVGImageElement|null = null; + private imageElement: SVGImageElement | null = null; /** Tspan based arrow element. */ - private arrow_: SVGTSpanElement|null = null; + private arrow: SVGTSpanElement | null = null; /** SVG based arrow element. */ - private svgArrow_: SVGElement|null = null; + private svgArrow: SVGElement | null = null; /** * Serializable fields are saved by the serializer, non-serializable fields @@ -72,24 +77,24 @@ export class FieldDropdown extends Field { protected menuGenerator_?: MenuGenerator; /** A cache of the most recently generated options. */ - private generatedOptions_: MenuOption[]|null = null; + private generatedOptions: MenuOption[] | null = null; /** * The prefix field label, of common words set after options are trimmed. * * @internal */ - override prefixField: string|null = null; + override prefixField: string | null = null; /** * The suffix field label, of common words set after options are trimmed. * * @internal */ - override suffixField: string|null = null; + override suffixField: string | null = null; // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. - private selectedOption_!: MenuOption; - override clickTarget_: SVGElement|null = null; + private selectedOption!: MenuOption; + override clickTarget_: SVGElement | null = null; /** * @param menuGenerator A non-empty array of options for a dropdown list, or a @@ -108,15 +113,15 @@ export class FieldDropdown extends Field { * @throws {TypeError} If `menuGenerator` options are incorrectly structured. */ constructor( - menuGenerator: MenuGenerator, - validator?: FieldDropdownValidator, - config?: FieldDropdownConfig, + menuGenerator: MenuGenerator, + validator?: FieldDropdownValidator, + config?: FieldDropdownConfig ); constructor(menuGenerator: typeof Field.SKIP_SETUP); constructor( - menuGenerator: MenuGenerator|typeof Field.SKIP_SETUP, - validator?: FieldDropdownValidator, - config?: FieldDropdownConfig, + menuGenerator: MenuGenerator | typeof Field.SKIP_SETUP, + validator?: FieldDropdownValidator, + config?: FieldDropdownConfig ) { super(Field.SKIP_SETUP); @@ -137,12 +142,12 @@ export class FieldDropdown extends Field { * The currently selected option. The field is initialized with the * first option selected. */ - this.selectedOption_ = this.getOptions(false)[0]; + this.selectedOption = this.getOptions(false)[0]; if (config) { this.configure_(config); } - this.setValue(this.selectedOption_[1]); + this.setValue(this.selectedOption[1]); if (validator) { this.setValidator(validator); } @@ -191,7 +196,7 @@ export class FieldDropdown extends Field { } this.createTextElement_(); - this.imageElement_ = dom.createSvgElement(Svg.IMAGE, {}, this.fieldGroup_); + this.imageElement = dom.createSvgElement(Svg.IMAGE, {}, this.fieldGroup_); if (this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW) { this.createSVGArrow_(); @@ -210,35 +215,45 @@ export class FieldDropdown extends Field { * @returns True if the dropdown field should add a border rect. */ protected shouldAddBorderRect_(): boolean { - return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || - this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW && - !this.getSourceBlock()?.isShadow(); + return ( + !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || + (this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW && + !this.getSourceBlock()?.isShadow()) + ); } /** Create a tspan based arrow. */ protected createTextArrow_() { - this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_); - this.arrow_!.appendChild(document.createTextNode( - this.getSourceBlock()?.RTL ? FieldDropdown.ARROW_CHAR + ' ' : - ' ' + FieldDropdown.ARROW_CHAR)); + this.arrow = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_); + this.arrow!.appendChild( + document.createTextNode( + this.getSourceBlock()?.RTL + ? FieldDropdown.ARROW_CHAR + ' ' + : ' ' + FieldDropdown.ARROW_CHAR + ) + ); if (this.getSourceBlock()?.RTL) { - this.getTextElement().insertBefore(this.arrow_, this.textContent_); + this.getTextElement().insertBefore(this.arrow, this.textContent_); } else { - this.getTextElement().appendChild(this.arrow_); + this.getTextElement().appendChild(this.arrow); } } /** Create an SVG based arrow. */ protected createSVGArrow_() { - this.svgArrow_ = dom.createSvgElement( - Svg.IMAGE, { - 'height': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', - 'width': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', - }, - this.fieldGroup_); - this.svgArrow_!.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI); + this.svgArrow = dom.createSvgElement( + Svg.IMAGE, + { + 'height': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', + 'width': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px', + }, + this.fieldGroup_ + ); + this.svgArrow!.setAttributeNS( + dom.XLINK_NS, + 'xlink:href', + this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI + ); } /** @@ -252,7 +267,7 @@ export class FieldDropdown extends Field { if (!block) { throw new UnattachedFieldError(); } - this.dropdownCreate_(); + this.dropdownCreate(); if (e && typeof e.clientX === 'number') { this.menu_!.openingCoords = new Coordinate(e.clientX, e.clientY); } else { @@ -266,11 +281,12 @@ export class FieldDropdown extends Field { dom.addClass(menuElement, 'blocklyDropdownMenu'); if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) { - const primaryColour = - block.isShadow() ? block.getParent()!.getColour() : block.getColour(); - const borderColour = block.isShadow() ? - (block.getParent() as BlockSvg).style.colourTertiary : - (this.sourceBlock_ as BlockSvg).style.colourTertiary; + const primaryColour = block.isShadow() + ? block.getParent()!.getColour() + : block.getColour(); + const borderColour = block.isShadow() + ? (block.getParent() as BlockSvg).style.colourTertiary + : (this.sourceBlock_ as BlockSvg).style.colourTertiary; dropDownDiv.setColour(primaryColour, borderColour); } @@ -281,18 +297,20 @@ export class FieldDropdown extends Field { // view. See issue #1329. this.menu_!.focus(); - if (this.selectedMenuItem_) { - this.menu_!.setHighlighted(this.selectedMenuItem_); + if (this.selectedMenuItem) { + this.menu_!.setHighlighted(this.selectedMenuItem); style.scrollIntoContainerView( - this.selectedMenuItem_.getElement()!, dropDownDiv.getContentDiv(), - true); + this.selectedMenuItem.getElement()!, + dropDownDiv.getContentDiv(), + true + ); } this.applyColour(); } /** Create the dropdown editor. */ - private dropdownCreate_() { + private dropdownCreate() { const block = this.getSourceBlock(); if (!block) { throw new UnattachedFieldError(); @@ -302,7 +320,7 @@ export class FieldDropdown extends Field { this.menu_ = menu; const options = this.getOptions(false); - this.selectedMenuItem_ = null; + this.selectedMenuItem = null; for (let i = 0; i < options.length; i++) { const [label, value] = options[i]; const content = (() => { @@ -322,9 +340,9 @@ export class FieldDropdown extends Field { menu.addChild(menuItem); menuItem.setChecked(value === this.value_); if (value === this.value_) { - this.selectedMenuItem_ = menuItem; + this.selectedMenuItem = menuItem; } - menuItem.onAction(this.handleMenuActionEvent_, this); + menuItem.onAction(this.handleMenuActionEvent, this); } } @@ -336,7 +354,7 @@ export class FieldDropdown extends Field { this.menu_.dispose(); } this.menu_ = null; - this.selectedMenuItem_ = null; + this.selectedMenuItem = null; this.applyColour(); } @@ -345,7 +363,7 @@ export class FieldDropdown extends Field { * * @param menuItem The MenuItem selected within menu. */ - private handleMenuActionEvent_(menuItem: MenuItem) { + private handleMenuActionEvent(menuItem: MenuItem) { dropDownDiv.hideIfOwner(this, true); this.onItemSelected_(this.menu_ as Menu, menuItem); } @@ -384,11 +402,11 @@ export class FieldDropdown extends Field { throw TypeError('A menu generator was never defined.'); } if (Array.isArray(this.menuGenerator_)) return this.menuGenerator_; - if (useCache && this.generatedOptions_) return this.generatedOptions_; + if (useCache && this.generatedOptions) return this.generatedOptions; - this.generatedOptions_ = this.menuGenerator_(); - validateOptions(this.generatedOptions_); - return this.generatedOptions_; + this.generatedOptions = this.menuGenerator_(); + validateOptions(this.generatedOptions); + return this.generatedOptions; } /** @@ -397,16 +415,21 @@ export class FieldDropdown extends Field { * @param newValue The input value. * @returns A valid language-neutral option, or null if invalid. */ - protected override doClassValidation_(newValue?: string): string|null { + protected override doClassValidation_(newValue?: string): string | null { const options = this.getOptions(true); const isValueValid = options.some((option) => option[1] === newValue); if (!isValueValid) { if (this.sourceBlock_) { console.warn( - 'Cannot set the dropdown\'s value to an unavailable option.' + - ' Block type: ' + this.sourceBlock_.type + - ', Field name: ' + this.name + ', Value: ' + newValue); + "Cannot set the dropdown's value to an unavailable option." + + ' Block type: ' + + this.sourceBlock_.type + + ', Field name: ' + + this.name + + ', Value: ' + + newValue + ); } return null; } @@ -422,17 +445,15 @@ export class FieldDropdown extends Field { protected override doValueUpdate_(newValue: string) { super.doValueUpdate_(newValue); const options = this.getOptions(true); - for (let i = 0, option; option = options[i]; i++) { + for (let i = 0, option; (option = options[i]); i++) { if (option[1] === this.value_) { - this.selectedOption_ = option; + this.selectedOption = option; } } } /** * Updates the dropdown arrow to match the colour/style of the block. - * - * @internal */ override applyColour() { const style = (this.sourceBlock_ as BlockSvg).style; @@ -445,11 +466,11 @@ export class FieldDropdown extends Field { } } // Update arrow's colour. - if (this.sourceBlock_ && this.arrow_) { + if (this.sourceBlock_ && this.arrow) { if (this.sourceBlock_.isShadow()) { - this.arrow_.style.fill = style.colourSecondary; + this.arrow.style.fill = style.colourSecondary; } else { - this.arrow_.style.fill = style.colourPrimary; + this.arrow.style.fill = style.colourPrimary; } } } @@ -458,14 +479,14 @@ export class FieldDropdown extends Field { protected override render_() { // Hide both elements. this.getTextContent().nodeValue = ''; - this.imageElement_!.style.display = 'none'; + this.imageElement!.style.display = 'none'; // Show correct element. - const option = this.selectedOption_ && this.selectedOption_[0]; + const option = this.selectedOption && this.selectedOption[0]; if (option && typeof option === 'object') { - this.renderSelectedImage_(option); + this.renderSelectedImage(option); } else { - this.renderSelectedText_(); + this.renderSelectedText(); } this.positionBorderRect_(); @@ -476,16 +497,19 @@ export class FieldDropdown extends Field { * * @param imageJson Selected option that must be an image. */ - private renderSelectedImage_(imageJson: ImageProperties) { + private renderSelectedImage(imageJson: ImageProperties) { const block = this.getSourceBlock(); if (!block) { throw new UnattachedFieldError(); } - this.imageElement_!.style.display = ''; - this.imageElement_!.setAttributeNS( - dom.XLINK_NS, 'xlink:href', imageJson.src); - this.imageElement_!.setAttribute('height', String(imageJson.height)); - this.imageElement_!.setAttribute('width', String(imageJson.width)); + this.imageElement!.style.display = ''; + this.imageElement!.setAttributeNS( + dom.XLINK_NS, + 'xlink:href', + imageJson.src + ); + this.imageElement!.setAttribute('height', String(imageJson.height)); + this.imageElement!.setAttribute('width', String(imageJson.width)); const imageHeight = Number(imageJson.height); const imageWidth = Number(imageJson.width); @@ -493,21 +517,25 @@ export class FieldDropdown extends Field { // Height and width include the border rect. const hasBorder = !!this.borderRect_; const height = Math.max( - hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, - imageHeight + IMAGE_Y_PADDING); - const xPadding = - hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0; + hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, + imageHeight + IMAGE_Y_PADDING + ); + const xPadding = hasBorder + ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING + : 0; let arrowWidth = 0; - if (this.svgArrow_) { - arrowWidth = this.positionSVGArrow_( - imageWidth + xPadding, - height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2); + if (this.svgArrow) { + arrowWidth = this.positionSVGArrow( + imageWidth + xPadding, + height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2 + ); } else { arrowWidth = dom.getFastTextWidth( - this.arrow_ as SVGTSpanElement, - this.getConstants()!.FIELD_TEXT_FONTSIZE, - this.getConstants()!.FIELD_TEXT_FONTWEIGHT, - this.getConstants()!.FIELD_TEXT_FONTFAMILY); + this.arrow as SVGTSpanElement, + this.getConstants()!.FIELD_TEXT_FONTSIZE, + this.getConstants()!.FIELD_TEXT_FONTWEIGHT, + this.getConstants()!.FIELD_TEXT_FONTFAMILY + ); } this.size_.width = imageWidth + arrowWidth + xPadding * 2; this.size_.height = height; @@ -515,19 +543,19 @@ export class FieldDropdown extends Field { let arrowX = 0; if (block.RTL) { const imageX = xPadding + arrowWidth; - this.imageElement_!.setAttribute('x', `${imageX}`); + this.imageElement!.setAttribute('x', `${imageX}`); } else { arrowX = imageWidth + arrowWidth; this.getTextElement().setAttribute('text-anchor', 'end'); - this.imageElement_!.setAttribute('x', `${xPadding}`); + this.imageElement!.setAttribute('x', `${xPadding}`); } - this.imageElement_!.setAttribute('y', String(height / 2 - imageHeight / 2)); + this.imageElement!.setAttribute('y', String(height / 2 - imageHeight / 2)); this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth); } /** Renders the selected option, which must be text. */ - private renderSelectedText_() { + private renderSelectedText() { // Retrieves the selected option to display through getText_. this.getTextContent().nodeValue = this.getDisplayText_(); const textElement = this.getTextElement(); @@ -537,19 +565,24 @@ export class FieldDropdown extends Field { // Height and width include the border rect. const hasBorder = !!this.borderRect_; const height = Math.max( - hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, - this.getConstants()!.FIELD_TEXT_HEIGHT); + hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, + this.getConstants()!.FIELD_TEXT_HEIGHT + ); const textWidth = dom.getFastTextWidth( - this.getTextElement(), this.getConstants()!.FIELD_TEXT_FONTSIZE, - this.getConstants()!.FIELD_TEXT_FONTWEIGHT, - this.getConstants()!.FIELD_TEXT_FONTFAMILY); - const xPadding = - hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0; + this.getTextElement(), + this.getConstants()!.FIELD_TEXT_FONTSIZE, + this.getConstants()!.FIELD_TEXT_FONTWEIGHT, + this.getConstants()!.FIELD_TEXT_FONTFAMILY + ); + const xPadding = hasBorder + ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING + : 0; let arrowWidth = 0; - if (this.svgArrow_) { - arrowWidth = this.positionSVGArrow_( - textWidth + xPadding, - height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2); + if (this.svgArrow) { + arrowWidth = this.positionSVGArrow( + textWidth + xPadding, + height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2 + ); } this.size_.width = textWidth + arrowWidth + xPadding * 2; this.size_.height = height; @@ -564,8 +597,8 @@ export class FieldDropdown extends Field { * @param y Y position the arrow is being rendered at, in px. * @returns Amount of space the arrow is taking up, in px. */ - private positionSVGArrow_(x: number, y: number): number { - if (!this.svgArrow_) { + private positionSVGArrow(x: number, y: number): number { + if (!this.svgArrow) { return 0; } const block = this.getSourceBlock(); @@ -573,13 +606,16 @@ export class FieldDropdown extends Field { throw new UnattachedFieldError(); } const hasBorder = !!this.borderRect_; - const xPadding = - hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0; + const xPadding = hasBorder + ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING + : 0; const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING; const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE; const arrowX = block.RTL ? xPadding : x + textPadding; - this.svgArrow_.setAttribute( - 'transform', 'translate(' + arrowX + ',' + y + ')'); + this.svgArrow.setAttribute( + 'transform', + 'translate(' + arrowX + ',' + y + ')' + ); return svgArrowSize + textPadding; } @@ -590,11 +626,11 @@ export class FieldDropdown extends Field { * * @returns Selected option text. */ - protected override getText_(): string|null { - if (!this.selectedOption_) { + protected override getText_(): string | null { + if (!this.selectedOption) { return null; } - const option = this.selectedOption_[0]; + const option = this.selectedOption[0]; if (typeof option === 'object') { return option['alt']; } @@ -612,9 +648,10 @@ export class FieldDropdown extends Field { static fromJson(options: FieldDropdownFromJsonConfig): FieldDropdown { if (!options.options) { throw new Error( - 'options are required for the dropdown field. The ' + + 'options are required for the dropdown field. The ' + 'options property must be assigned an array of ' + - '[humanReadableValue, languageNeutralValue] tuples.'); + '[humanReadableValue, languageNeutralValue] tuples.' + ); } // `this` might be a subclass of FieldDropdown if that class doesn't // override the static fromJson method. @@ -649,7 +686,7 @@ export type MenuGeneratorFunction = (this: FieldDropdown) => MenuOption[]; * Either an array of menu options or a function that generates an array of * menu options for FieldDropdown or its descendants. */ -export type MenuGenerator = MenuOption[]|MenuGeneratorFunction; +export type MenuGenerator = MenuOption[] | MenuGeneratorFunction; /** * Config options for the dropdown field. @@ -693,8 +730,11 @@ const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2; * Factor out common words in statically defined options. * Create prefix and/or suffix labels. */ -function trimOptions(options: MenuOption[]): - {options: MenuOption[]; prefix?: string; suffix?: string;} { +function trimOptions(options: MenuOption[]): { + options: MenuOption[]; + prefix?: string; + suffix?: string; +} { let hasImages = false; const trimmedOptions = options.map(([label, value]): MenuOption => { if (typeof label === 'string') { @@ -704,9 +744,10 @@ function trimOptions(options: MenuOption[]): hasImages = true; // Copy the image properties so they're not influenced by the original. // NOTE: No need to deep copy since image properties are only 1 level deep. - const imageLabel = label.alt !== null ? - {...label, alt: parsing.replaceMessageReferences(label.alt)} : - {...label}; + const imageLabel = + label.alt !== null + ? {...label, alt: parsing.replaceMessageReferences(label.alt)} + : {...label}; return [imageLabel, value]; }); @@ -719,16 +760,20 @@ function trimOptions(options: MenuOption[]): const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest); const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest); - if ((!prefixLength && !suffixLength) || - (shortest <= prefixLength + suffixLength)) { + if ( + (!prefixLength && !suffixLength) || + shortest <= prefixLength + suffixLength + ) { // One or more strings will entirely vanish if we proceed. Abort. return {options: stringOptions}; } - const prefix = - prefixLength ? stringLabels[0].substring(0, prefixLength - 1) : undefined; - const suffix = - suffixLength ? stringLabels[0].substr(1 - suffixLength) : undefined; + const prefix = prefixLength + ? stringLabels[0].substring(0, prefixLength - 1) + : undefined; + const suffix = suffixLength + ? stringLabels[0].substr(1 - suffixLength) + : undefined; return { options: applyTrim(stringOptions, prefixLength, suffixLength), prefix, @@ -747,12 +792,13 @@ function trimOptions(options: MenuOption[]): * @returns A new array with all of the option text trimmed. */ function applyTrim( - options: [string, string][], prefixLength: number, - suffixLength: number): MenuOption[] { - return options.map( - ([text, value]) => - [text.substring(prefixLength, text.length - suffixLength), - value, + options: [string, string][], + prefixLength: number, + suffixLength: number +): MenuOption[] { + return options.map(([text, value]) => [ + text.substring(prefixLength, text.length - suffixLength), + value, ]); } @@ -775,23 +821,38 @@ function validateOptions(options: MenuOption[]) { if (!Array.isArray(tuple)) { foundError = true; console.error( - 'Invalid option[' + i + ']: Each FieldDropdown option must be an ' + - 'array. Found: ', - tuple); + 'Invalid option[' + + i + + ']: Each FieldDropdown option must be an ' + + 'array. Found: ', + tuple + ); } else if (typeof tuple[1] !== 'string') { foundError = true; console.error( - 'Invalid option[' + i + ']: Each FieldDropdown option id must be ' + - 'a string. Found ' + tuple[1] + ' in: ', - tuple); + 'Invalid option[' + + i + + ']: Each FieldDropdown option id must be ' + + 'a string. Found ' + + tuple[1] + + ' in: ', + tuple + ); } else if ( - tuple[0] && typeof tuple[0] !== 'string' && - typeof tuple[0].src !== 'string') { + tuple[0] && + typeof tuple[0] !== 'string' && + typeof tuple[0].src !== 'string' + ) { foundError = true; console.error( - 'Invalid option[' + i + ']: Each FieldDropdown option must have a ' + - 'string label or image description. Found' + tuple[0] + ' in: ', - tuple); + 'Invalid option[' + + i + + ']: Each FieldDropdown option must have a ' + + 'string label or image description. Found' + + tuple[0] + + ' in: ', + tuple + ); } } if (foundError) { diff --git a/core/field_image.ts b/core/field_image.ts index ecd702f3f..b8f04f2d4 100644 --- a/core/field_image.ts +++ b/core/field_image.ts @@ -29,13 +29,13 @@ export class FieldImage extends Field { */ private static readonly Y_PADDING = 1; protected override size_: Size; - private readonly imageHeight_: number; + private readonly imageHeight: number; /** The function to be called when this field is clicked. */ - private clickHandler_: ((p1: FieldImage) => void)|null = null; + private clickHandler: ((p1: FieldImage) => void) | null = null; /** The rendered field's image element. */ - private imageElement_: SVGImageElement|null = null; + private imageElement: SVGImageElement | null = null; /** * Editable fields usually show some sort of UI indicating they are @@ -51,10 +51,10 @@ export class FieldImage extends Field { protected override isDirty_ = false; /** Whether to flip this image in RTL. */ - private flipRtl_ = false; + private flipRtl = false; /** Alt text of this image. */ - private altText_ = ''; + private altText = ''; /** * @param src The URL of the image. @@ -73,22 +73,27 @@ export class FieldImage extends Field { * for a list of properties this parameter supports. */ constructor( - src: string|typeof Field.SKIP_SETUP, width: string|number, - height: string|number, alt?: string, onClick?: (p1: FieldImage) => void, - flipRtl?: boolean, config?: FieldImageConfig) { + src: string | typeof Field.SKIP_SETUP, + width: string | number, + height: string | number, + alt?: string, + onClick?: (p1: FieldImage) => void, + flipRtl?: boolean, + config?: FieldImageConfig + ) { super(Field.SKIP_SETUP); const imageHeight = Number(parsing.replaceMessageReferences(height)); const imageWidth = Number(parsing.replaceMessageReferences(width)); if (isNaN(imageHeight) || isNaN(imageWidth)) { throw Error( - 'Height and width values of an image field must cast to' + - ' numbers.'); + 'Height and width values of an image field must cast to' + ' numbers.' + ); } if (imageHeight <= 0 || imageWidth <= 0) { throw Error( - 'Height and width values of an image field must be greater' + - ' than 0.'); + 'Height and width values of an image field must be greater' + ' than 0.' + ); } /** The size of the area rendered by the field. */ @@ -97,10 +102,10 @@ export class FieldImage extends Field { /** * Store the image height, since it is different from the field height. */ - this.imageHeight_ = imageHeight; + this.imageHeight = imageHeight; if (typeof onClick === 'function') { - this.clickHandler_ = onClick; + this.clickHandler = onClick; } if (src === Field.SKIP_SETUP) return; @@ -108,8 +113,8 @@ export class FieldImage extends Field { if (config) { this.configure_(config); } else { - this.flipRtl_ = !!flipRtl; - this.altText_ = parsing.replaceMessageReferences(alt) || ''; + this.flipRtl = !!flipRtl; + this.altText = parsing.replaceMessageReferences(alt) || ''; } this.setValue(parsing.replaceMessageReferences(src)); } @@ -121,9 +126,9 @@ export class FieldImage extends Field { */ protected override configure_(config: FieldImageConfig) { super.configure_(config); - if (config.flipRtl) this.flipRtl_ = config.flipRtl; + if (config.flipRtl) this.flipRtl = config.flipRtl; if (config.alt) { - this.altText_ = parsing.replaceMessageReferences(config.alt); + this.altText = parsing.replaceMessageReferences(config.alt); } } @@ -133,18 +138,23 @@ export class FieldImage extends Field { * @internal */ override initView() { - this.imageElement_ = dom.createSvgElement( - Svg.IMAGE, { - 'height': this.imageHeight_ + 'px', - 'width': this.size_.width + 'px', - 'alt': this.altText_, - }, - this.fieldGroup_); - this.imageElement_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', this.value_ as string); + this.imageElement = dom.createSvgElement( + Svg.IMAGE, + { + 'height': this.imageHeight + 'px', + 'width': this.size_.width + 'px', + 'alt': this.altText, + }, + this.fieldGroup_ + ); + this.imageElement.setAttributeNS( + dom.XLINK_NS, + 'xlink:href', + this.value_ as string + ); - if (this.clickHandler_) { - this.imageElement_.style.cursor = 'pointer'; + if (this.clickHandler) { + this.imageElement.style.cursor = 'pointer'; } } @@ -157,7 +167,7 @@ export class FieldImage extends Field { * @param newValue The input value. * @returns A string, or null if invalid. */ - protected override doClassValidation_(newValue?: any): string|null { + protected override doClassValidation_(newValue?: any): string | null { if (typeof newValue !== 'string') { return null; } @@ -172,9 +182,8 @@ export class FieldImage extends Field { */ protected override doValueUpdate_(newValue: string) { this.value_ = newValue; - if (this.imageElement_) { - this.imageElement_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', this.value_); + if (this.imageElement) { + this.imageElement.setAttributeNS(dom.XLINK_NS, 'xlink:href', this.value_); } } @@ -184,7 +193,7 @@ export class FieldImage extends Field { * @returns True if we should flip in RTL. */ override getFlipRtl(): boolean { - return this.flipRtl_; + return this.flipRtl; } /** @@ -192,13 +201,13 @@ export class FieldImage extends Field { * * @param alt New alt text. */ - setAlt(alt: string|null) { - if (alt === this.altText_) { + setAlt(alt: string | null) { + if (alt === this.altText) { return; } - this.altText_ = alt || ''; - if (this.imageElement_) { - this.imageElement_.setAttribute('alt', this.altText_); + this.altText = alt || ''; + if (this.imageElement) { + this.imageElement.setAttribute('alt', this.altText); } } @@ -207,8 +216,8 @@ export class FieldImage extends Field { * call the handler. */ protected override showEditor_() { - if (this.clickHandler_) { - this.clickHandler_(this); + if (this.clickHandler) { + this.clickHandler(this); } } @@ -218,8 +227,8 @@ export class FieldImage extends Field { * @param func The function that is called when the image is clicked, or null * to remove. */ - setOnClickHandler(func: ((p1: FieldImage) => void)|null) { - this.clickHandler_ = func; + setOnClickHandler(func: ((p1: FieldImage) => void) | null) { + this.clickHandler = func; } /** @@ -229,8 +238,8 @@ export class FieldImage extends Field { * * @returns The image alt text. */ - protected override getText_(): string|null { - return this.altText_; + protected override getText_(): string | null { + return this.altText; } /** @@ -246,14 +255,21 @@ export class FieldImage extends Field { static fromJson(options: FieldImageFromJsonConfig): FieldImage { if (!options.src || !options.width || !options.height) { throw new Error( - 'src, width, and height values for an image field are' + - 'required. The width and height must be non-zero.'); + 'src, width, and height values for an image field are' + + 'required. The width and height must be non-zero.' + ); } // `this` might be a subclass of FieldImage if that class doesn't override // the static fromJson method. return new this( - options.src, options.width, options.height, undefined, undefined, - undefined, options); + options.src, + options.width, + options.height, + undefined, + undefined, + undefined, + options + ); } } diff --git a/core/field_input.ts b/core/field_input.ts index ba86b0449..59c161573 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -15,26 +15,33 @@ goog.declareModuleId('Blockly.FieldInput'); // Unused import preserved for side-effects. Remove if unneeded. import './events/events_block_change.js'; -import type {BlockSvg} from './block_svg.js'; +import {BlockSvg} from './block_svg.js'; +import * as bumpObjects from './bump_objects.js'; import * as browserEvents from './browser_events.js'; import * as dialog from './dialog.js'; import * as dom from './utils/dom.js'; import * as dropDownDiv from './dropdowndiv.js'; import * as eventUtils from './events/utils.js'; -import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js'; +import { + Field, + FieldConfig, + FieldValidator, + UnattachedFieldError, +} from './field.js'; import {Msg} from './msg.js'; import * as aria from './utils/aria.js'; import {Coordinate} from './utils/coordinate.js'; import * as userAgent from './utils/useragent.js'; import * as WidgetDiv from './widgetdiv.js'; import type {WorkspaceSvg} from './workspace_svg.js'; +import * as renderManagement from './render_management.js'; /** * Supported types for FieldInput subclasses. * * @internal */ -type InputTypes = string|number; +type InputTypes = string | number; /** * Abstract class for an editable input field. @@ -42,7 +49,9 @@ type InputTypes = string|number; * @typeParam T - The value stored on the field. * @internal */ -export abstract class FieldInput extends Field { +export abstract class FieldInput extends Field< + string | T +> { /** * Pixel size of input border radius. * Should match blocklyText's border-radius in CSS. @@ -53,7 +62,7 @@ export abstract class FieldInput extends Field { protected spellcheck_ = true; /** The HTML input element. */ - protected htmlInput_: HTMLInputElement|null = null; + protected htmlInput_: HTMLInputElement | null = null; /** True if the field's value is currently being edited via the UI. */ protected isBeingEdited_ = false; @@ -63,20 +72,27 @@ export abstract class FieldInput extends Field { */ protected isTextValid_ = false; + /** + * The intial value of the field when the user opened an editor to change its + * value. When the editor is disposed, an event will be fired that uses this + * as the event's oldValue. + */ + protected valueWhenEditorWasOpened_: string | T | null = null; + /** Key down event data. */ - private onKeyDownWrapper_: browserEvents.Data|null = null; + private onKeyDownWrapper_: browserEvents.Data | null = null; /** Key input event data. */ - private onKeyInputWrapper_: browserEvents.Data|null = null; + private onKeyInputWrapper_: browserEvents.Data | null = null; /** * Whether the field should consider the whole parent block to be its click * target. */ - fullBlockClickTarget_: boolean|null = false; + fullBlockClickTarget_: boolean | null = false; /** The workspace that this field belongs to. */ - protected workspace_: WorkspaceSvg|null = null; + protected workspace_: WorkspaceSvg | null = null; /** * Serializable fields are saved by the serializer, non-serializable fields @@ -102,8 +118,10 @@ export abstract class FieldInput extends Field { * for a list of properties this parameter supports. */ constructor( - value?: string|typeof Field.SKIP_SETUP, - validator?: FieldInputValidator|null, config?: FieldInputConfig) { + value?: string | typeof Field.SKIP_SETUP, + validator?: FieldInputValidator | null, + config?: FieldInputConfig + ) { super(Field.SKIP_SETUP); if (value === Field.SKIP_SETUP) return; @@ -135,7 +153,7 @@ export abstract class FieldInput extends Field { let nFields = 0; let nConnections = 0; // Count the number of fields, excluding text fields - for (let i = 0, input; input = block.inputList[i]; i++) { + for (let i = 0, input; (input = block.inputList[i]); i++) { for (let j = 0; input.fieldRow[j]; j++) { nFields++; } @@ -146,7 +164,7 @@ export abstract class FieldInput extends Field { // The special case is when this is the only non-label field on the block // and it has an output but no inputs. this.fullBlockClickTarget_ = - nFields <= 1 && block.outputConnection && !nConnections; + nFields <= 1 && block.outputConnection && !nConnections; } else { this.fullBlockClickTarget_ = false; } @@ -176,9 +194,15 @@ export abstract class FieldInput extends Field { // Revert value when the text becomes invalid. this.value_ = this.htmlInput_!.getAttribute('data-untyped-default-value'); if (this.sourceBlock_ && eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - this.sourceBlock_, 'field', this.name || null, oldValue, - this.value_)); + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this.sourceBlock_, + 'field', + this.name || null, + oldValue, + this.value_ + ) + ); } } } @@ -191,7 +215,7 @@ export abstract class FieldInput extends Field { * @param newValue The value to be saved. The default validator guarantees * that this is a string. */ - protected override doValueUpdate_(newValue: string|T) { + protected override doValueUpdate_(newValue: string | T) { this.isDirty_ = true; this.isTextValid_ = true; this.value_ = newValue; @@ -199,8 +223,6 @@ export abstract class FieldInput extends Field { /** * Updates text field to match the colour/style of the block. - * - * @internal */ override applyColour() { if (!this.sourceBlock_ || !this.getConstants()!.FULL_BLOCK_FIELDS) return; @@ -211,7 +233,9 @@ export abstract class FieldInput extends Field { this.borderRect_.setAttribute('stroke', source.style.colourTertiary); } else { source.pathObject.svgPath.setAttribute( - 'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR); + 'fill', + this.getConstants()!.FIELD_BORDER_RECT_COLOUR + ); } } @@ -250,7 +274,9 @@ export abstract class FieldInput extends Field { // AnyDuringMigration because: Argument of type 'boolean' is not // assignable to parameter of type 'string'. this.htmlInput_.setAttribute( - 'spellcheck', this.spellcheck_ as AnyDuringMigration); + 'spellcheck', + this.spellcheck_ as AnyDuringMigration + ); } } @@ -267,8 +293,11 @@ export abstract class FieldInput extends Field { */ protected override showEditor_(_e?: Event, quietInput = false) { this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace; - if (!quietInput && this.workspace_.options.modalInputs && - (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) { + if ( + !quietInput && + this.workspace_.options.modalInputs && + (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD) + ) { this.showPromptEditor_(); } else { this.showInlineEditor_(quietInput); @@ -282,12 +311,15 @@ export abstract class FieldInput extends Field { */ private showPromptEditor_() { dialog.prompt( - Msg['CHANGE_VALUE_TITLE'], this.getText(), (text: string|null) => { - // Text is null if user pressed cancel button. - if (text !== null) { - this.setValue(this.getValueFromEditorText_(text)); - } - }); + Msg['CHANGE_VALUE_TITLE'], + this.getText(), + (text: string | null) => { + // Text is null if user pressed cancel button. + if (text !== null) { + this.setValue(this.getValueFromEditorText_(text)); + } + } + ); } /** @@ -303,6 +335,7 @@ export abstract class FieldInput extends Field { WidgetDiv.show(this, block.RTL, this.widgetDispose_.bind(this)); this.htmlInput_ = this.widgetCreate_() as HTMLInputElement; this.isBeingEdited_ = true; + this.valueWhenEditorWasOpened_ = this.value_; if (!quietInput) { (this.htmlInput_ as HTMLElement).focus({ @@ -329,12 +362,14 @@ export abstract class FieldInput extends Field { if (!clickTarget) throw new Error('A click target has not been set.'); dom.addClass(clickTarget, 'editing'); - const htmlInput = (document.createElement('input')); + const htmlInput = document.createElement('input'); htmlInput.className = 'blocklyHtmlInput'; // AnyDuringMigration because: Argument of type 'boolean' is not assignable // to parameter of type 'string'. htmlInput.setAttribute( - 'spellcheck', this.spellcheck_ as AnyDuringMigration); + 'spellcheck', + this.spellcheck_ as AnyDuringMigration + ); const scale = this.workspace_!.getScale(); const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt'; div!.style.fontSize = fontSize; @@ -347,15 +382,15 @@ export abstract class FieldInput extends Field { // Override border radius. borderRadius = (bBox.bottom - bBox.top) / 2 + 'px'; // Pull stroke colour from the existing shadow block - const strokeColour = block.getParent() ? - (block.getParent() as BlockSvg).style.colourTertiary : - (this.sourceBlock_ as BlockSvg).style.colourTertiary; + const strokeColour = block.getParent() + ? (block.getParent() as BlockSvg).style.colourTertiary + : (this.sourceBlock_ as BlockSvg).style.colourTertiary; htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour; div!.style.borderRadius = borderRadius; div!.style.transition = 'box-shadow 0.25s ease 0s'; if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) { div!.style.boxShadow = - 'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px'; + 'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px'; } } htmlInput.style.borderRadius = borderRadius; @@ -383,6 +418,29 @@ export abstract class FieldInput extends Field { // Make sure the field's node matches the field's internal value. this.forceRerender(); this.onFinishEditing_(this.value_); + + if ( + this.sourceBlock_ && + eventUtils.isEnabled() && + this.valueWhenEditorWasOpened_ !== null && + this.valueWhenEditorWasOpened_ !== this.value_ + ) { + // When closing a field input widget, fire an event indicating that the + // user has completed a sequence of changes. The value may have changed + // multiple times while the editor was open, but this will fire an event + // containing the value when the editor was opened as well as the new one. + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this.sourceBlock_, + 'field', + this.name || null, + this.valueWhenEditorWasOpened_, + this.value_ + ) + ); + this.valueWhenEditorWasOpened_ = null; + } + eventUtils.setGroup(false); // Actual disposal. @@ -417,10 +475,18 @@ export abstract class FieldInput extends Field { protected bindInputEvents_(htmlInput: HTMLElement) { // Trap Enter without IME and Esc to hide. this.onKeyDownWrapper_ = browserEvents.conditionalBind( - htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); + htmlInput, + 'keydown', + this, + this.onHtmlInputKeyDown_ + ); // Resize after every input change. this.onKeyInputWrapper_ = browserEvents.conditionalBind( - htmlInput, 'input', this, this.onHtmlInputChange_); + htmlInput, + 'input', + this, + this.onHtmlInputChange_ + ); } /** Unbind handlers for user input and workspace size changes. */ @@ -446,7 +512,8 @@ export abstract class FieldInput extends Field { dropDownDiv.hideWithoutAnimation(); } else if (e.key === 'Escape') { this.setValue( - this.htmlInput_!.getAttribute('data-untyped-default-value')); + this.htmlInput_!.getAttribute('data-untyped-default-value') + ); WidgetDiv.hide(); dropDownDiv.hideWithoutAnimation(); } else if (e.key === 'Tab') { @@ -463,7 +530,34 @@ export abstract class FieldInput extends Field { * @param _e Keyboard event. */ private onHtmlInputChange_(_e: Event) { - this.setValue(this.getValueFromEditorText_(this.htmlInput_!.value)); + // Intermediate value changes from user input are not confirmed until the + // user closes the editor, and may be numerous. Inhibit reporting these as + // normal block change events, and instead report them as special + // intermediate changes that do not get recorded in undo history. + const oldValue = this.value_; + // Change the field's value without firing the normal change event. + this.setValue(this.getValueFromEditorText_(this.htmlInput_!.value), false); + if ( + this.sourceBlock_ && + eventUtils.isEnabled() && + this.value_ !== oldValue + ) { + // Fire a special event indicating that the value changed but the change + // isn't complete yet and normal field change listeners can wait. + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE))( + this.sourceBlock_, + this.name || null, + oldValue, + this.value_ + ) + ); + } + + // Resize the widget div after the block has finished rendering. + renderManagement.finishQueuedRenders().then(() => { + this.resizeEditor_(); + }); } /** @@ -472,8 +566,14 @@ export abstract class FieldInput extends Field { * value whilst editing. * * @param newValue New value. + * @param fireChangeEvent Whether to fire a change event. Defaults to true. + * Should usually be true unless the change will be reported some other + * way, e.g. an intermediate field change event. */ - protected setEditorValue_(newValue: AnyDuringMigration) { + protected setEditorValue_( + newValue: AnyDuringMigration, + fireChangeEvent = true + ) { this.isDirty_ = true; if (this.isBeingEdited_) { // In the case this method is passed an invalid value, we still @@ -482,7 +582,7 @@ export abstract class FieldInput extends Field { // with what's shown to the user. this.htmlInput_!.value = this.getEditorText_(newValue); } - this.setValue(newValue); + this.setValue(newValue, fireChangeEvent); } /** Resize the editor to fit the text. */ @@ -505,6 +605,31 @@ export abstract class FieldInput extends Field { div!.style.top = xy.y + 'px'; } + /** + * Handles repositioning the WidgetDiv used for input fields when the + * workspace is resized. Will bump the block into the viewport and update the + * position of the field if necessary. + * + * @returns True for rendered workspaces, as we never want to hide the widget + * div. + */ + override repositionForWindowResize(): boolean { + const block = this.getSourceBlock(); + // This shouldn't be possible. We should never have a WidgetDiv if not using + // rendered blocks. + if (!(block instanceof BlockSvg)) return false; + + bumpObjects.bumpIntoBounds( + this.workspace_!, + this.workspace_!.getMetricsManager().getViewMetrics(true), + block + ); + + this.resizeEditor_(); + + return true; + } + /** * Returns whether or not the field is tab navigable. * @@ -522,7 +647,7 @@ export abstract class FieldInput extends Field { * * @returns The HTML value if we're editing, otherwise null. */ - protected override getText_(): string|null { + protected override getText_(): string | null { if (this.isBeingEdited_ && this.htmlInput_) { // We are currently editing, return the HTML input value instead. return this.htmlInput_.value; @@ -583,5 +708,6 @@ export interface FieldInputConfig extends FieldConfig { * - `undefined` to set `newValue` as is. * @internal */ -export type FieldInputValidator = - FieldValidator; +export type FieldInputValidator = FieldValidator< + string | T +>; diff --git a/core/field_label.ts b/core/field_label.ts index 024646495..06426a4c2 100644 --- a/core/field_label.ts +++ b/core/field_label.ts @@ -23,7 +23,7 @@ import * as parsing from './utils/parsing.js'; */ export class FieldLabel extends Field { /** The HTML class name to use for this field. */ - private class_: string|null = null; + private class: string | null = null; /** * Editable fields usually show some sort of UI indicating they are @@ -31,6 +31,9 @@ export class FieldLabel extends Field { */ override EDITABLE = false; + /** Text labels should not truncate. */ + override maxDisplayLength = Infinity; + /** * @param value The initial value of the field. Should cast to a string. * Defaults to an empty string if null or undefined. Also accepts @@ -44,22 +47,24 @@ export class FieldLabel extends Field { * for a list of properties this parameter supports. */ constructor( - value?: string|typeof Field.SKIP_SETUP, textClass?: string, - config?: FieldLabelConfig) { + value?: string | typeof Field.SKIP_SETUP, + textClass?: string, + config?: FieldLabelConfig + ) { super(Field.SKIP_SETUP); if (value === Field.SKIP_SETUP) return; if (config) { this.configure_(config); } else { - this.class_ = textClass || null; + this.class = textClass || null; } this.setValue(value); } protected override configure_(config: FieldLabelConfig) { super.configure_(config); - if (config.class) this.class_ = config.class; + if (config.class) this.class = config.class; } /** @@ -69,8 +74,8 @@ export class FieldLabel extends Field { */ override initView() { this.createTextElement_(); - if (this.class_) { - dom.addClass(this.getTextElement(), this.class_); + if (this.class) { + dom.addClass(this.getTextElement(), this.class); } } @@ -80,8 +85,9 @@ export class FieldLabel extends Field { * @param newValue The input value. * @returns A valid string, or null if invalid. */ - protected override doClassValidation_(newValue?: AnyDuringMigration): string - |null { + protected override doClassValidation_( + newValue?: AnyDuringMigration + ): string | null { if (newValue === null || newValue === undefined) { return null; } @@ -93,16 +99,16 @@ export class FieldLabel extends Field { * * @param cssClass The new CSS class name, or null to remove. */ - setClass(cssClass: string|null) { + setClass(cssClass: string | null) { if (this.textElement_) { - if (this.class_) { - dom.removeClass(this.textElement_, this.class_); + if (this.class) { + dom.removeClass(this.textElement_, this.class); } if (cssClass) { dom.addClass(this.textElement_, cssClass); } } - this.class_ = cssClass; + this.class = cssClass; } /** @@ -136,7 +142,6 @@ export interface FieldLabelConfig extends FieldConfig { } // clang-format on - /** * fromJson config options for the label field. */ diff --git a/core/field_label_serializable.ts b/core/field_label_serializable.ts index 55ef8a1fb..d493aa598 100644 --- a/core/field_label_serializable.ts +++ b/core/field_label_serializable.ts @@ -14,7 +14,11 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.FieldLabelSerializable'); -import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js'; +import { + FieldLabel, + FieldLabelConfig, + FieldLabelFromJsonConfig, +} from './field_label.js'; import * as fieldRegistry from './field_registry.js'; import * as parsing from './utils/parsing.js'; @@ -57,8 +61,9 @@ export class FieldLabelSerializable extends FieldLabel { * @nocollapse * @internal */ - static override fromJson(options: FieldLabelFromJsonConfig): - FieldLabelSerializable { + static override fromJson( + options: FieldLabelFromJsonConfig + ): FieldLabelSerializable { const text = parsing.replaceMessageReferences(options.text); // `this` might be a subclass of FieldLabelSerializable if that class // doesn't override the static fromJson method. diff --git a/core/field_multilineinput.ts b/core/field_multilineinput.ts index 1472c8996..631d7e31e 100644 --- a/core/field_multilineinput.ts +++ b/core/field_multilineinput.ts @@ -15,7 +15,11 @@ goog.declareModuleId('Blockly.FieldMultilineInput'); import * as Css from './css.js'; import {Field, UnattachedFieldError} from './field.js'; import * as fieldRegistry from './field_registry.js'; -import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js'; +import { + FieldTextInput, + FieldTextInputConfig, + FieldTextInputValidator, +} from './field_textinput.js'; import * as aria from './utils/aria.js'; import * as dom from './utils/dom.js'; import * as parsing from './utils/parsing.js'; @@ -31,7 +35,7 @@ export class FieldMultilineInput extends FieldTextInput { * The SVG group element that will contain a text element for each text row * when initialized. */ - textGroup: SVGGElement|null = null; + textGroup: SVGGElement | null = null; /** * Defines the maximum number of lines of field. @@ -58,9 +62,10 @@ export class FieldMultilineInput extends FieldTextInput { * for a list of properties this parameter supports. */ constructor( - value?: string|typeof Field.SKIP_SETUP, - validator?: FieldMultilineInputValidator, - config?: FieldMultilineInputConfig) { + value?: string | typeof Field.SKIP_SETUP, + validator?: FieldMultilineInputValidator, + config?: FieldMultilineInputConfig + ) { super(Field.SKIP_SETUP); if (value === Field.SKIP_SETUP) return; @@ -96,8 +101,10 @@ export class FieldMultilineInput extends FieldTextInput { // needed so the plain-text representation of the XML produced by // `Blockly.Xml.domToText` will appear on a single line (this is a // limitation of the plain-text format). - fieldElement.textContent = - (this.getValue() as string).replace(/\n/g, ' '); + fieldElement.textContent = (this.getValue() as string).replace( + /\n/g, + ' ' + ); return fieldElement; } @@ -151,10 +158,12 @@ export class FieldMultilineInput extends FieldTextInput { override initView() { this.createBorderRect_(); this.textGroup = dom.createSvgElement( - Svg.G, { - 'class': 'blocklyEditableText', - }, - this.fieldGroup_); + Svg.G, + { + 'class': 'blocklyEditableText', + }, + this.fieldGroup_ + ); } /** @@ -175,8 +184,9 @@ export class FieldMultilineInput extends FieldTextInput { } const lines = textLines.split('\n'); textLines = ''; - const displayLinesNumber = - this.isOverflowedY_ ? this.maxLines_ : lines.length; + const displayLinesNumber = this.isOverflowedY_ + ? this.maxLines_ + : lines.length; for (let i = 0; i < displayLinesNumber; i++) { let text = lines[i]; if (text.length > this.maxDisplayLength) { @@ -226,7 +236,7 @@ export class FieldMultilineInput extends FieldTextInput { // Remove all text group children. let currentChild; const textGroup = this.textGroup; - while (currentChild = textGroup!.firstChild) { + while ((currentChild = textGroup!.firstChild)) { textGroup!.removeChild(currentChild); } @@ -234,16 +244,19 @@ export class FieldMultilineInput extends FieldTextInput { const lines = this.getDisplayText_().split('\n'); let y = 0; for (let i = 0; i < lines.length; i++) { - const lineHeight = this.getConstants()!.FIELD_TEXT_HEIGHT + - this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING; + const lineHeight = + this.getConstants()!.FIELD_TEXT_HEIGHT + + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING; const span = dom.createSvgElement( - Svg.TEXT, { - 'class': 'blocklyText blocklyMultilineText', - 'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING, - 'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING, - 'dy': this.getConstants()!.FIELD_TEXT_BASELINE, - }, - textGroup); + Svg.TEXT, + { + 'class': 'blocklyText blocklyMultilineText', + 'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING, + 'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING, + 'dy': this.getConstants()!.FIELD_TEXT_BASELINE, + }, + textGroup + ); span.appendChild(document.createTextNode(lines[i])); y += lineHeight; } @@ -289,13 +302,18 @@ export class FieldMultilineInput extends FieldTextInput { let totalHeight = 0; for (let i = 0; i < nodes.length; i++) { const tspan = nodes[i] as SVGTextElement; - const textWidth = - dom.getFastTextWidth(tspan, fontSize, fontWeight, fontFamily); + const textWidth = dom.getFastTextWidth( + tspan, + fontSize, + fontWeight, + fontFamily + ); if (textWidth > totalWidth) { totalWidth = textWidth; } - totalHeight += this.getConstants()!.FIELD_TEXT_HEIGHT + - (i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0); + totalHeight += + this.getConstants()!.FIELD_TEXT_HEIGHT + + (i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0); } if (this.isBeingEdited_) { // The default width is based on the longest line in the display text, @@ -304,24 +322,31 @@ export class FieldMultilineInput extends FieldTextInput { // Otherwise we would get wrong editor width when there are more // lines than this.maxLines_. const actualEditorLines = String(this.value_).split('\n'); - const dummyTextElement = dom.createSvgElement( - Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'}); + const dummyTextElement = dom.createSvgElement(Svg.TEXT, { + 'class': 'blocklyText blocklyMultilineText', + }); for (let i = 0; i < actualEditorLines.length; i++) { if (actualEditorLines[i].length > this.maxDisplayLength) { - actualEditorLines[i] = - actualEditorLines[i].substring(0, this.maxDisplayLength); + actualEditorLines[i] = actualEditorLines[i].substring( + 0, + this.maxDisplayLength + ); } dummyTextElement.textContent = actualEditorLines[i]; const lineWidth = dom.getFastTextWidth( - dummyTextElement, fontSize, fontWeight, fontFamily); + dummyTextElement, + fontSize, + fontWeight, + fontFamily + ); if (lineWidth > totalWidth) { totalWidth = lineWidth; } } const scrollbarWidth = - this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth; + this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth; totalWidth += scrollbarWidth; } if (this.borderRect_) { @@ -360,7 +385,7 @@ export class FieldMultilineInput extends FieldTextInput { const div = WidgetDiv.getDiv(); const scale = this.workspace_!.getScale(); - const htmlInput = (document.createElement('textarea')); + const htmlInput = document.createElement('textarea'); htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput'; htmlInput.setAttribute('spellcheck', String(this.spellcheck_)); const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt'; @@ -370,11 +395,12 @@ export class FieldMultilineInput extends FieldTextInput { htmlInput.style.borderRadius = borderRadius; const paddingX = this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * scale; const paddingY = - this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale / 2; - htmlInput.style.padding = paddingY + 'px ' + paddingX + 'px ' + paddingY + - 'px ' + paddingX + 'px'; - const lineHeight = this.getConstants()!.FIELD_TEXT_HEIGHT + - this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING; + (this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale) / 2; + htmlInput.style.padding = + paddingY + 'px ' + paddingX + 'px ' + paddingY + 'px ' + paddingX + 'px'; + const lineHeight = + this.getConstants()!.FIELD_TEXT_HEIGHT + + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING; htmlInput.style.lineHeight = lineHeight * scale + 'px'; div!.appendChild(htmlInput); @@ -401,8 +427,11 @@ export class FieldMultilineInput extends FieldTextInput { * scrolling functionality is enabled. */ setMaxLines(maxLines: number) { - if (typeof maxLines === 'number' && maxLines > 0 && - maxLines !== this.maxLines_) { + if ( + typeof maxLines === 'number' && + maxLines > 0 && + maxLines !== this.maxLines_ + ) { this.maxLines_ = maxLines; this.forceRerender(); } @@ -438,8 +467,9 @@ export class FieldMultilineInput extends FieldTextInput { * @nocollapse * @internal */ - static override fromJson(options: FieldMultilineInputFromJsonConfig): - FieldMultilineInput { + static override fromJson( + options: FieldMultilineInputFromJsonConfig + ): FieldMultilineInput { const text = parsing.replaceMessageReferences(options.text); // `this` might be a subclass of FieldMultilineInput if that class doesn't // override the static fromJson method. @@ -449,7 +479,6 @@ export class FieldMultilineInput extends FieldTextInput { fieldRegistry.register('field_multilinetext', FieldMultilineInput); - /** * CSS for multiline field. */ @@ -477,8 +506,8 @@ export interface FieldMultilineInputConfig extends FieldTextInputConfig { /** * fromJson config options for the multiline input field. */ -export interface FieldMultilineInputFromJsonConfig extends - FieldMultilineInputConfig { +export interface FieldMultilineInputFromJsonConfig + extends FieldMultilineInputConfig { text?: string; } diff --git a/core/field_number.ts b/core/field_number.ts index 72f383d01..511f27553 100644 --- a/core/field_number.ts +++ b/core/field_number.ts @@ -14,7 +14,11 @@ goog.declareModuleId('Blockly.FieldNumber'); import {Field} from './field.js'; import * as fieldRegistry from './field_registry.js'; -import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js'; +import { + FieldInput, + FieldInputConfig, + FieldInputValidator, +} from './field_input.js'; import * as aria from './utils/aria.js'; /** @@ -34,7 +38,7 @@ export class FieldNumber extends FieldInput { * The number of decimal places to allow, or null to allow any number of * decimal digits. */ - private decimalPlaces_: number|null = null; + private decimalPlaces: number | null = null; /** Don't spellcheck numbers. Our validator does a better job. */ protected override spellcheck_ = false; @@ -59,9 +63,13 @@ export class FieldNumber extends FieldInput { * for a list of properties this parameter supports. */ constructor( - value?: string|number|typeof Field.SKIP_SETUP, min?: string|number|null, - max?: string|number|null, precision?: string|number|null, - validator?: FieldNumberValidator|null, config?: FieldNumberConfig) { + value?: string | number | typeof Field.SKIP_SETUP, + min?: string | number | null, + max?: string | number | null, + precision?: string | number | null, + validator?: FieldNumberValidator | null, + config?: FieldNumberConfig + ) { // Pass SENTINEL so that we can define properties before value validation. super(Field.SKIP_SETUP); @@ -84,9 +92,9 @@ export class FieldNumber extends FieldInput { */ protected override configure_(config: FieldNumberConfig) { super.configure_(config); - this.setMinInternal_(config.min); - this.setMaxInternal_(config.max); - this.setPrecisionInternal_(config.precision); + this.setMinInternal(config.min); + this.setMaxInternal(config.max); + this.setPrecisionInternal(config.precision); } /** @@ -103,11 +111,13 @@ export class FieldNumber extends FieldInput { * @param precision Precision for value. */ setConstraints( - min: number|string|undefined|null, max: number|string|undefined|null, - precision: number|string|undefined|null) { - this.setMinInternal_(min); - this.setMaxInternal_(max); - this.setPrecisionInternal_(precision); + min: number | string | undefined | null, + max: number | string | undefined | null, + precision: number | string | undefined | null + ) { + this.setMinInternal(min); + this.setMaxInternal(max); + this.setPrecisionInternal(precision); this.setValue(this.getValue()); } @@ -117,8 +127,8 @@ export class FieldNumber extends FieldInput { * * @param min Minimum value. */ - setMin(min: number|string|undefined|null) { - this.setMinInternal_(min); + setMin(min: number | string | undefined | null) { + this.setMinInternal(min); this.setValue(this.getValue()); } @@ -128,7 +138,7 @@ export class FieldNumber extends FieldInput { * * @param min Minimum value. */ - private setMinInternal_(min: number|string|undefined|null) { + private setMinInternal(min: number | string | undefined | null) { if (min == null) { this.min_ = -Infinity; } else { @@ -155,8 +165,8 @@ export class FieldNumber extends FieldInput { * * @param max Maximum value. */ - setMax(max: number|string|undefined|null) { - this.setMaxInternal_(max); + setMax(max: number | string | undefined | null) { + this.setMaxInternal(max); this.setValue(this.getValue()); } @@ -166,7 +176,7 @@ export class FieldNumber extends FieldInput { * * @param max Maximum value. */ - private setMaxInternal_(max: number|string|undefined|null) { + private setMaxInternal(max: number | string | undefined | null) { if (max == null) { this.max_ = Infinity; } else { @@ -193,8 +203,8 @@ export class FieldNumber extends FieldInput { * * @param precision The number to which the field's value is rounded. */ - setPrecision(precision: number|string|undefined|null) { - this.setPrecisionInternal_(precision); + setPrecision(precision: number | string | undefined | null) { + this.setPrecisionInternal(precision); this.setValue(this.getValue()); } @@ -204,22 +214,23 @@ export class FieldNumber extends FieldInput { * * @param precision The number to which the field's value is rounded. */ - private setPrecisionInternal_(precision: number|string|undefined|null) { + private setPrecisionInternal(precision: number | string | undefined | null) { this.precision_ = Number(precision) || 0; let precisionString = String(this.precision_); if (precisionString.indexOf('e') !== -1) { // String() is fast. But it turns .0000001 into '1e-7'. // Use the much slower toLocaleString to access all the digits. - precisionString = - this.precision_.toLocaleString('en-US', {maximumFractionDigits: 20}); + precisionString = this.precision_.toLocaleString('en-US', { + maximumFractionDigits: 20, + }); } const decimalIndex = precisionString.indexOf('.'); if (decimalIndex === -1) { // If the precision is 0 (float) allow any number of decimals, // otherwise allow none. - this.decimalPlaces_ = precision ? 0 : null; + this.decimalPlaces = precision ? 0 : null; } else { - this.decimalPlaces_ = precisionString.length - decimalIndex - 1; + this.decimalPlaces = precisionString.length - decimalIndex - 1; } } @@ -241,8 +252,9 @@ export class FieldNumber extends FieldInput { * @param newValue The input value. * @returns A valid number, or null if invalid. */ - protected override doClassValidation_(newValue?: AnyDuringMigration): number - |null { + protected override doClassValidation_( + newValue?: AnyDuringMigration + ): number | null { if (newValue === null) { return null; } @@ -251,7 +263,7 @@ export class FieldNumber extends FieldInput { newValue = `${newValue}`; // TODO: Handle cases like 'ten', '1.203,14', etc. // 'O' is sometimes mistaken for '0' by inexperienced users. - newValue = newValue.replace(/O/ig, '0'); + newValue = newValue.replace(/O/gi, '0'); // Strip out thousands separators. newValue = newValue.replace(/,/g, ''); // Ignore case of 'Infinity'. @@ -270,8 +282,8 @@ export class FieldNumber extends FieldInput { n = Math.round(n / this.precision_) * this.precision_; } // Clean up floating point errors. - if (this.decimalPlaces_ !== null) { - n = Number(n.toFixed(this.decimalPlaces_)); + if (this.decimalPlaces !== null) { + n = Number(n.toFixed(this.decimalPlaces)); } return n; } @@ -283,7 +295,6 @@ export class FieldNumber extends FieldInput { */ protected override widgetCreate_(): HTMLInputElement { const htmlInput = super.widgetCreate_() as HTMLInputElement; - htmlInput.type = 'number'; // Set the accessibility state if (this.min_ > -Infinity) { @@ -309,7 +320,13 @@ export class FieldNumber extends FieldInput { // `this` might be a subclass of FieldNumber if that class doesn't override // the static fromJson method. return new this( - options.value, undefined, undefined, undefined, undefined, options); + options.value, + undefined, + undefined, + undefined, + undefined, + options + ); } } diff --git a/core/field_registry.ts b/core/field_registry.ts index 25364d448..119302723 100644 --- a/core/field_registry.ts +++ b/core/field_registry.ts @@ -50,7 +50,7 @@ export function unregister(type: string) { * given type name * @internal */ -export function fromJson(options: RegistryOptions): Field|null { +export function fromJson(options: RegistryOptions): Field | null { return TEST_ONLY.fromJsonInternal(options); } @@ -59,14 +59,16 @@ export function fromJson(options: RegistryOptions): Field|null { * * @param options */ -function fromJsonInternal(options: RegistryOptions): Field|null { +function fromJsonInternal(options: RegistryOptions): Field | null { const fieldObject = registry.getObject(registry.Type.FIELD, options.type); if (!fieldObject) { console.warn( - 'Blockly could not create a field of type ' + options['type'] + + 'Blockly could not create a field of type ' + + options['type'] + '. The field is probably not being registered. This could be because' + ' the file is not loaded, the field does not register itself (Issue' + - ' #1584), or the registration is not being reached.'); + ' #1584), or the registration is not being reached.' + ); return null; } else if (typeof (fieldObject as any).fromJson !== 'function') { throw new TypeError('returned Field was not a IRegistrableField'); diff --git a/core/field_textinput.ts b/core/field_textinput.ts index 600e14cb1..3dc1052b0 100644 --- a/core/field_textinput.ts +++ b/core/field_textinput.ts @@ -16,7 +16,11 @@ goog.declareModuleId('Blockly.FieldTextInput'); import './events/events_block_change.js'; import {Field} from './field.js'; -import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js'; +import { + FieldInput, + FieldInputConfig, + FieldInputValidator, +} from './field_input.js'; import * as fieldRegistry from './field_registry.js'; import * as parsing from './utils/parsing.js'; @@ -39,8 +43,10 @@ export class FieldTextInput extends FieldInput { * for a list of properties this parameter supports. */ constructor( - value?: string|typeof Field.SKIP_SETUP, - validator?: FieldTextInputValidator|null, config?: FieldTextInputConfig) { + value?: string | typeof Field.SKIP_SETUP, + validator?: FieldTextInputValidator | null, + config?: FieldTextInputConfig + ) { super(value, validator, config); } @@ -50,8 +56,9 @@ export class FieldTextInput extends FieldInput { * @param newValue The input value. * @returns A valid string, or null if invalid. */ - protected override doClassValidation_(newValue?: AnyDuringMigration): string - |null { + protected override doClassValidation_( + newValue?: AnyDuringMigration + ): string | null { if (newValue === undefined) { return null; } diff --git a/core/field_variable.ts b/core/field_variable.ts index 5bde39de1..6c9bfed41 100644 --- a/core/field_variable.ts +++ b/core/field_variable.ts @@ -17,7 +17,12 @@ import './events/events_block_change.js'; import type {Block} from './block.js'; import {Field, FieldConfig, UnattachedFieldError} from './field.js'; -import {FieldDropdown, FieldDropdownValidator, MenuGenerator, MenuOption} from './field_dropdown.js'; +import { + FieldDropdown, + FieldDropdownValidator, + MenuGenerator, + MenuOption, +} from './field_dropdown.js'; import * as fieldRegistry from './field_registry.js'; import * as internalConstants from './internal_constants.js'; import type {Menu} from './menu.js'; @@ -33,21 +38,21 @@ import * as Xml from './xml.js'; * Class for a variable's dropdown field. */ export class FieldVariable extends FieldDropdown { - protected override menuGenerator_: MenuGenerator|undefined; + protected override menuGenerator_: MenuGenerator | undefined; defaultVariableName: string; /** The type of the default variable for this field. */ - private defaultType_ = ''; + private defaultType = ''; /** * All of the types of variables that will be available in this field's * dropdown. */ - variableTypes: string[]|null = []; + variableTypes: string[] | null = []; protected override size_: Size; /** The variable model associated with this field. */ - private variable_: VariableModel|null = null; + private variable: VariableModel | null = null; /** * Serializable fields are saved by the serializer, non-serializable fields @@ -75,9 +80,12 @@ export class FieldVariable extends FieldDropdown { * for a list of properties this parameter supports. */ constructor( - varName: string|null|typeof Field.SKIP_SETUP, - validator?: FieldVariableValidator, variableTypes?: string[], - defaultType?: string, config?: FieldVariableConfig) { + varName: string | null | typeof Field.SKIP_SETUP, + validator?: FieldVariableValidator, + variableTypes?: string[], + defaultType?: string, + config?: FieldVariableConfig + ) { super(Field.SKIP_SETUP); /** @@ -101,7 +109,7 @@ export class FieldVariable extends FieldDropdown { if (config) { this.configure_(config); } else { - this.setTypes_(variableTypes, defaultType); + this.setTypes(variableTypes, defaultType); } if (validator) { this.setValidator(validator); @@ -115,7 +123,7 @@ export class FieldVariable extends FieldDropdown { */ protected override configure_(config: FieldVariableConfig) { super.configure_(config); - this.setTypes_(config.variableTypes, config.defaultType); + this.setTypes(config.variableTypes, config.defaultType); } /** @@ -130,11 +138,15 @@ export class FieldVariable extends FieldDropdown { if (!block) { throw new UnattachedFieldError(); } - if (this.variable_) { - return; // Initialization already happened. + if (this.variable) { + return; // Initialization already happened. } const variable = Variables.getOrCreateVariablePackage( - block.workspace, null, this.defaultVariableName, this.defaultType_); + block.workspace, + null, + this.defaultVariableName, + this.defaultType + ); // Don't call setValue because we don't want to cause a rerender. this.doValueUpdate_(variable.getId()); } @@ -144,9 +156,11 @@ export class FieldVariable extends FieldDropdown { if (!block) { throw new UnattachedFieldError(); } - return super.shouldAddBorderRect_() && - (!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || - block.type !== 'variables_get'); + return ( + super.shouldAddBorderRect_() && + (!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW || + block.type !== 'variables_get') + ); } /** @@ -164,21 +178,32 @@ export class FieldVariable extends FieldDropdown { const variableName = fieldElement.textContent; // 'variabletype' should be lowercase, but until July 2019 it was sometimes // recorded as 'variableType'. Thus we need to check for both. - const variableType = fieldElement.getAttribute('variabletype') || - fieldElement.getAttribute('variableType') || ''; + const variableType = + fieldElement.getAttribute('variabletype') || + fieldElement.getAttribute('variableType') || + ''; // AnyDuringMigration because: Argument of type 'string | null' is not // assignable to parameter of type 'string | undefined'. const variable = Variables.getOrCreateVariablePackage( - block.workspace, id, variableName as AnyDuringMigration, variableType); + block.workspace, + id, + variableName as AnyDuringMigration, + variableType + ); // This should never happen :) if (variableType !== null && variableType !== variable.type) { throw Error( - 'Serialized variable type with id \'' + variable.getId() + - '\' had type ' + variable.type + ', and ' + + "Serialized variable type with id '" + + variable.getId() + + "' had type " + + variable.type + + ', and ' + 'does not match variable field that references it: ' + - Xml.domToText(fieldElement) + '.'); + Xml.domToText(fieldElement) + + '.' + ); } this.setValue(variable.getId()); @@ -195,10 +220,10 @@ export class FieldVariable extends FieldDropdown { // Make sure the variable is initialized. this.initModel(); - fieldElement.id = this.variable_!.getId(); - fieldElement.textContent = this.variable_!.name; - if (this.variable_!.type) { - fieldElement.setAttribute('variabletype', this.variable_!.type); + fieldElement.id = this.variable!.getId(); + fieldElement.textContent = this.variable!.name; + if (this.variable!.type) { + fieldElement.setAttribute('variabletype', this.variable!.type); } return fieldElement; } @@ -219,10 +244,10 @@ export class FieldVariable extends FieldDropdown { } // Make sure the variable is initialized. this.initModel(); - const state = {'id': this.variable_!.getId()}; + const state = {'id': this.variable!.getId()}; if (doFullSerialization) { - (state as AnyDuringMigration)['name'] = this.variable_!.name; - (state as AnyDuringMigration)['type'] = this.variable_!.type; + (state as AnyDuringMigration)['name'] = this.variable!.name; + (state as AnyDuringMigration)['type'] = this.variable!.type; } return state; } @@ -243,8 +268,11 @@ export class FieldVariable extends FieldDropdown { } // This is necessary so that blocks in the flyout can have custom var names. const variable = Variables.getOrCreateVariablePackage( - block.workspace, state['id'] || null, state['name'], - state['type'] || ''); + block.workspace, + state['id'] || null, + state['name'], + state['type'] || '' + ); this.setValue(variable.getId()); } @@ -265,8 +293,8 @@ export class FieldVariable extends FieldDropdown { * * @returns Current variable's ID. */ - override getValue(): string|null { - return this.variable_ ? this.variable_.getId() : null; + override getValue(): string | null { + return this.variable ? this.variable.getId() : null; } /** @@ -276,7 +304,7 @@ export class FieldVariable extends FieldDropdown { * is selected. */ override getText(): string { - return this.variable_ ? this.variable_.name : ''; + return this.variable ? this.variable.name : ''; } /** @@ -287,8 +315,8 @@ export class FieldVariable extends FieldDropdown { * @returns The selected variable, or null if none was selected. * @internal */ - getVariable(): VariableModel|null { - return this.variable_; + getVariable(): VariableModel | null { + return this.variable; } /** @@ -299,11 +327,11 @@ export class FieldVariable extends FieldDropdown { * * @returns Validation function, or null. */ - override getValidator(): FieldVariableValidator|null { + override getValidator(): FieldVariableValidator | null { // Validators shouldn't operate on the initial setValue call. // Normally this is achieved by calling setValidator after setValue, but // this is not a possibility with variable fields. - if (this.variable_) { + if (this.variable) { return this.validator_; } return null; @@ -315,8 +343,9 @@ export class FieldVariable extends FieldDropdown { * @param newValue The ID of the new variable to set. * @returns The validated ID, or null if invalid. */ - protected override doClassValidation_(newValue?: AnyDuringMigration): string - |null { + protected override doClassValidation_( + newValue?: AnyDuringMigration + ): string | null { if (newValue === null) { return null; } @@ -328,15 +357,14 @@ export class FieldVariable extends FieldDropdown { const variable = Variables.getVariable(block.workspace, newId); if (!variable) { console.warn( - 'Variable id doesn\'t point to a real variable! ' + - 'ID was ' + newId); + "Variable id doesn't point to a real variable! " + 'ID was ' + newId + ); return null; } // Type Checks. const type = variable.type; - if (!this.typeIsAllowed_(type)) { - console.warn( - 'Variable type doesn\'t match this field! Type was ' + type); + if (!this.typeIsAllowed(type)) { + console.warn("Variable type doesn't match this field! Type was " + type); return null; } return newId; @@ -355,7 +383,7 @@ export class FieldVariable extends FieldDropdown { if (!block) { throw new UnattachedFieldError(); } - this.variable_ = Variables.getVariable(block.workspace, newId as string); + this.variable = Variables.getVariable(block.workspace, newId as string); super.doValueUpdate_(newId); } @@ -365,10 +393,10 @@ export class FieldVariable extends FieldDropdown { * @param type The type to check. * @returns True if the type is in the list of allowed types. */ - private typeIsAllowed_(type: string): boolean { - const typeList = this.getVariableTypes_(); + private typeIsAllowed(type: string): boolean { + const typeList = this.getVariableTypes(); if (!typeList) { - return true; // If it's null, all types are valid. + return true; // If it's null, all types are valid. } for (let i = 0; i < typeList.length; i++) { if (type === typeList[i]) { @@ -384,8 +412,7 @@ export class FieldVariable extends FieldDropdown { * @returns Array of variable types. * @throws {Error} if variableTypes is an empty array. */ - private getVariableTypes_(): string[] { - // TODO (#1513): Try to avoid calling this every time the field is edited. + private getVariableTypes(): string[] { let variableTypes = this.variableTypes; if (variableTypes === null) { // If variableTypes is null, return all variable types. @@ -398,7 +425,8 @@ export class FieldVariable extends FieldDropdown { // Throw an error if variableTypes is an empty list. const name = this.getText(); throw Error( - '\'variableTypes\' of field variable ' + name + ' was an empty list'); + "'variableTypes' of field variable " + name + ' was an empty list' + ); } return variableTypes; } @@ -413,7 +441,7 @@ export class FieldVariable extends FieldDropdown { * @param defaultType The type of the variable to create if this field's * value is not explicitly set. Defaults to ''. */ - private setTypes_(variableTypes: string[]|null = null, defaultType = '') { + private setTypes(variableTypes: string[] | null = null, defaultType = '') { // If you expected that the default type would be the same as the only entry // in the variable types array, tell the Blockly team by commenting on // #1499. @@ -429,16 +457,20 @@ export class FieldVariable extends FieldDropdown { } if (!isInArray) { throw Error( - 'Invalid default type \'' + defaultType + '\' in ' + - 'the definition of a FieldVariable'); + "Invalid default type '" + + defaultType + + "' in " + + 'the definition of a FieldVariable' + ); } } else if (variableTypes !== null) { throw Error( - '\'variableTypes\' was not an array in the definition of ' + - 'a FieldVariable'); + "'variableTypes' was not an array in the definition of " + + 'a FieldVariable' + ); } // Only update the field once all checks pass. - this.defaultType_ = defaultType; + this.defaultType = defaultType; this.variableTypes = variableTypes; } @@ -468,11 +500,13 @@ export class FieldVariable extends FieldDropdown { if (id === internalConstants.RENAME_VARIABLE_ID) { // Rename variable. Variables.renameVariable( - this.sourceBlock_.workspace, this.variable_ as VariableModel); + this.sourceBlock_.workspace, + this.variable as VariableModel + ); return; } else if (id === internalConstants.DELETE_VARIABLE_ID) { // Delete variable. - this.sourceBlock_.workspace.deleteVariableById(this.variable_!.getId()); + this.sourceBlock_.workspace.deleteVariableById(this.variable!.getId()); return; } } @@ -501,8 +535,9 @@ export class FieldVariable extends FieldDropdown { * @nocollapse * @internal */ - static override fromJson(options: FieldVariableFromJsonConfig): - FieldVariable { + static override fromJson( + options: FieldVariableFromJsonConfig + ): FieldVariable { const varName = parsing.replaceMessageReferences(options.variable); // `this` might be a subclass of FieldVariable if that class doesn't // override the static fromJson method. @@ -516,21 +551,22 @@ export class FieldVariable extends FieldDropdown { * @returns Array of variable names/id tuples. */ static dropdownCreate(this: FieldVariable): MenuOption[] { - if (!this.variable_) { + if (!this.variable) { throw Error( - 'Tried to call dropdownCreate on a variable field with no' + - ' variable selected.'); + 'Tried to call dropdownCreate on a variable field with no' + + ' variable selected.' + ); } const name = this.getText(); let variableModelList: VariableModel[] = []; if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) { - const variableTypes = this.getVariableTypes_(); + const variableTypes = this.getVariableTypes(); // Get a copy of the list, so that adding rename and new variable options // doesn't modify the workspace's list. for (let i = 0; i < variableTypes.length; i++) { const variableType = variableTypes[i]; const variables = - this.sourceBlock_.workspace.getVariablesOfType(variableType); + this.sourceBlock_.workspace.getVariablesOfType(variableType); variableModelList = variableModelList.concat(variables); } } @@ -541,8 +577,10 @@ export class FieldVariable extends FieldDropdown { // Set the UUID as the internal representation of the variable. options[i] = [variableModelList[i].name, variableModelList[i].getId()]; } - options.push( - [Msg['RENAME_VARIABLE'], internalConstants.RENAME_VARIABLE_ID]); + options.push([ + Msg['RENAME_VARIABLE'], + internalConstants.RENAME_VARIABLE_ID, + ]); if (Msg['DELETE_VARIABLE']) { options.push([ Msg['DELETE_VARIABLE'].replace('%1', name), diff --git a/core/flyout_base.ts b/core/flyout_base.ts index a517eebe7..25fe446e6 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -12,6 +12,7 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.Flyout'); +import type {Abstract as AbstractEvent} from './events/events_abstract.js'; import type {Block} from './block.js'; import type {BlockSvg} from './block_svg.js'; import * as browserEvents from './browser_events.js'; @@ -36,7 +37,6 @@ import {WorkspaceSvg} from './workspace_svg.js'; import * as utilsXml from './utils/xml.js'; import * as Xml from './xml.js'; - enum FlyoutItemType { BLOCK = 'block', BUTTON = 'button', @@ -69,7 +69,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * between 0 and 1 specifying the degree of scrolling and a * similar x property. */ - protected abstract setMetrics_(xyRatio: {x?: number, y?: number}): void; + protected abstract setMetrics_(xyRatio: {x?: number; y?: number}): void; /** * Lay out the blocks in the flyout. @@ -137,33 +137,36 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * Function that will be registered as a change listener on the workspace * to reflow when blocks in the flyout workspace change. */ - private reflowWrapper_: Function|null = null; + private reflowWrapper: ((e: AbstractEvent) => void) | null = null; /** * Function that disables blocks in the flyout based on max block counts * allowed in the target workspace. Registered as a change listener on the * target workspace. */ - private filterWrapper_: Function|null = null; + private filterWrapper: ((e: AbstractEvent) => void) | null = null; /** * List of background mats that lurk behind each block to catch clicks * landing in the blocks' lakes and bays. */ - private mats_: SVGElement[] = []; + private mats: SVGElement[] = []; + /** * List of visible buttons. */ protected buttons_: FlyoutButton[] = []; + /** * List of event listeners. */ - private listeners_: browserEvents.Data[] = []; + private listeners: browserEvents.Data[] = []; /** * List of blocks that should always be disabled. */ - private permanentlyDisabled_: Block[] = []; + private permanentlyDisabled: Block[] = []; + protected readonly tabWidth_: number; /** @@ -172,10 +175,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @internal */ targetWorkspace!: WorkspaceSvg; + /** * A list of blocks that can be reused. */ - private recycledBlocks_: BlockSvg[] = []; + private recycledBlocks: BlockSvg[] = []; + /** * Does the flyout automatically close when a block is created? */ @@ -189,7 +194,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { /** * Whether the workspace containing this flyout is visible. */ - private containerVisible_ = true; + private containerVisible = true; protected rectMap_: WeakMap; /** @@ -242,12 +247,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * The path around the background of the flyout, which will be filled with a * background colour. */ - protected svgBackground_: SVGPathElement|null = null; + protected svgBackground_: SVGPathElement | null = null; /** * The root SVG group for the button or label. */ - protected svgGroup_: SVGGElement|null = null; + protected svgGroup_: SVGGElement | null = null; /** * @param workspaceOptions Dictionary of options for the * workspace. @@ -258,7 +263,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout { this.workspace_ = new WorkspaceSvg(workspaceOptions); this.workspace_.setMetricsManager( - new FlyoutMetricsManager(this.workspace_, this)); + new FlyoutMetricsManager(this.workspace_, this) + ); this.workspace_.internalIsFlyout = true; // Keep the workspace visibility consistent with the flyout's visibility. @@ -321,7 +327,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * put the flyout in. This should be or . * @returns The flyout's SVG group. */ - createDom(tagName: string|Svg|Svg): SVGElement { + createDom( + tagName: string | Svg | Svg + ): SVGElement { /* @@ -330,15 +338,22 @@ export abstract class Flyout extends DeleteArea implements IFlyout { */ // Setting style to display:none to start. The toolbox and flyout // hide/show code will set up proper visibility and size later. - this.svgGroup_ = dom.createSvgElement( - tagName, {'class': 'blocklyFlyout', 'style': 'display: none'}); + this.svgGroup_ = dom.createSvgElement(tagName, { + 'class': 'blocklyFlyout', + 'style': 'display: none', + }); this.svgBackground_ = dom.createSvgElement( - Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); + Svg.PATH, + {'class': 'blocklyFlyoutBackground'}, + this.svgGroup_ + ); this.svgGroup_.appendChild(this.workspace_.createDom()); - this.workspace_.getThemeManager().subscribe( - this.svgBackground_, 'flyoutBackgroundColour', 'fill'); - this.workspace_.getThemeManager().subscribe( - this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); + this.workspace_ + .getThemeManager() + .subscribe(this.svgBackground_, 'flyoutBackgroundColour', 'fill'); + this.workspace_ + .getThemeManager() + .subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); return this.svgGroup_; } @@ -353,26 +368,42 @@ export abstract class Flyout extends DeleteArea implements IFlyout { this.workspace_.targetWorkspace = targetWorkspace; this.workspace_.scrollbar = new ScrollbarPair( - this.workspace_, this.horizontalLayout, !this.horizontalLayout, - 'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN); + this.workspace_, + this.horizontalLayout, + !this.horizontalLayout, + 'blocklyFlyoutScrollbar', + this.SCROLLBAR_MARGIN + ); this.hide(); - this.boundEvents.push(browserEvents.conditionalBind( - (this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_)); + this.boundEvents.push( + browserEvents.conditionalBind( + this.svgGroup_ as SVGGElement, + 'wheel', + this, + this.wheel_ + ) + ); if (!this.autoClose) { - this.filterWrapper_ = this.filterForCapacity_.bind(this); - this.targetWorkspace.addChangeListener(this.filterWrapper_); + this.filterWrapper = this.filterForCapacity.bind(this); + this.targetWorkspace.addChangeListener(this.filterWrapper); } // Dragging the flyout up and down. - this.boundEvents.push(browserEvents.conditionalBind( - (this.svgBackground_ as SVGPathElement), 'pointerdown', this, - this.onMouseDown_)); + this.boundEvents.push( + browserEvents.conditionalBind( + this.svgBackground_ as SVGPathElement, + 'pointerdown', + this, + this.onMouseDown + ) + ); // A flyout connected to a workspace doesn't have its own current gesture. - this.workspace_.getGesture = - this.targetWorkspace.getGesture.bind(this.targetWorkspace); + this.workspace_.getGesture = this.targetWorkspace.getGesture.bind( + this.targetWorkspace + ); // Get variables from the main workspace rather than the target workspace. this.workspace_.setVariableMap(this.targetWorkspace.getVariableMap()); @@ -392,8 +423,6 @@ export abstract class Flyout extends DeleteArea implements IFlyout { /** * Dispose of this flyout. * Unlink from all DOM elements to prevent memory leaks. - * - * @suppress {checkTypes} */ dispose() { this.hide(); @@ -402,9 +431,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout { browserEvents.unbind(event); } this.boundEvents.length = 0; - if (this.filterWrapper_) { - this.targetWorkspace.removeChangeListener(this.filterWrapper_); - this.filterWrapper_ = null; + if (this.filterWrapper) { + this.targetWorkspace.removeChangeListener(this.filterWrapper); } if (this.workspace_) { this.workspace_.getThemeManager().unsubscribe(this.svgBackground_!); @@ -412,9 +440,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { } if (this.svgGroup_) { dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; } - this.svgBackground_ = null; } /** @@ -480,7 +506,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { // auto-close flyouts need to have their drag target updated. this.workspace_.recordDragTargets(); } - this.updateDisplay_(); + this.updateDisplay(); } } @@ -490,10 +516,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param visible Whether the container is visible. */ setContainerVisible(visible: boolean) { - const visibilityChanged = visible !== this.containerVisible_; - this.containerVisible_ = visible; + const visibilityChanged = visible !== this.containerVisible; + this.containerVisible = visible; if (visibilityChanged) { - this.updateDisplay_(); + this.updateDisplay(); } } @@ -501,9 +527,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * Update the display property of the flyout based whether it thinks it should * be visible and whether its containing workspace is visible. */ - private updateDisplay_() { + private updateDisplay() { let show = true; - if (!this.containerVisible_) { + if (!this.containerVisible) { show = false; } else { show = this.isVisible(); @@ -544,11 +570,15 @@ export abstract class Flyout extends DeleteArea implements IFlyout { // reposition in resize, we need to call setPosition. See issue #4692. if (scrollbar.hScroll) { scrollbar.hScroll.setPosition( - scrollbar.hScroll.position.x, scrollbar.hScroll.position.y); + scrollbar.hScroll.position.x, + scrollbar.hScroll.position.y + ); } if (scrollbar.vScroll) { scrollbar.vScroll.setPosition( - scrollbar.vScroll.position.x, scrollbar.vScroll.position.y); + scrollbar.vScroll.position.x, + scrollbar.vScroll.position.y + ); } } } @@ -562,13 +592,13 @@ export abstract class Flyout extends DeleteArea implements IFlyout { } this.setVisible(false); // Delete all the event listeners. - for (const listen of this.listeners_) { + for (const listen of this.listeners) { browserEvents.unbind(listen); } - this.listeners_.length = 0; - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); - this.reflowWrapper_ = null; + this.listeners.length = 0; + if (this.reflowWrapper) { + this.workspace_.removeChangeListener(this.reflowWrapper); + this.reflowWrapper = null; } // Do NOT delete the blocks here. Wait until Flyout.show. // https://neil.fraser.name/news/2014/08/09/ @@ -581,20 +611,20 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * in the flyout. This is either an array of Nodes, a NodeList, a * toolbox definition, or a string with the name of the dynamic category. */ - show(flyoutDef: toolbox.FlyoutDefinition|string) { + show(flyoutDef: toolbox.FlyoutDefinition | string) { this.workspace_.setResizesEnabled(false); this.hide(); - this.clearOldBlocks_(); + this.clearOldBlocks(); // Handle dynamic categories, represented by a name instead of a list. if (typeof flyoutDef === 'string') { - flyoutDef = this.getDynamicCategoryContents_(flyoutDef); + flyoutDef = this.getDynamicCategoryContents(flyoutDef); } this.setVisible(true); // Parse the Array, Node or NodeList into a a list of flyout items. const parsedContent = toolbox.convertFlyoutDefToJsonArray(flyoutDef); - const flyoutInfo = this.createFlyoutInfo_(parsedContent); + const flyoutInfo = this.createFlyoutInfo(parsedContent); this.layout_(flyoutInfo.contents, flyoutInfo.gaps); @@ -606,14 +636,14 @@ export abstract class Flyout extends DeleteArea implements IFlyout { this.workspace_.setResizesEnabled(true); this.reflow(); - this.filterForCapacity_(); + this.filterForCapacity(); // Correctly position the flyout's scrollbar when it opens. this.position(); - this.reflowWrapper_ = this.reflow.bind(this); - this.workspace_.addChangeListener(this.reflowWrapper_); - this.emptyRecycledBlocks_(); + this.reflowWrapper = this.reflow.bind(this); + this.workspace_.addChangeListener(this.reflowWrapper); + this.emptyRecycledBlocks(); } /** @@ -624,49 +654,51 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * of objects to show in the flyout. * @returns The list of contents and gaps needed to lay out the flyout. */ - private createFlyoutInfo_(parsedContent: toolbox.FlyoutItemInfoArray): - {contents: FlyoutItem[], gaps: number[]} { + private createFlyoutInfo(parsedContent: toolbox.FlyoutItemInfoArray): { + contents: FlyoutItem[]; + gaps: number[]; + } { const contents: FlyoutItem[] = []; const gaps: number[] = []; - this.permanentlyDisabled_.length = 0; + this.permanentlyDisabled.length = 0; const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y; for (const info of parsedContent) { if ('custom' in info) { - const customInfo = (info as toolbox.DynamicCategoryInfo); + const customInfo = info as toolbox.DynamicCategoryInfo; const categoryName = customInfo['custom']; - const flyoutDef = this.getDynamicCategoryContents_(categoryName); + const flyoutDef = this.getDynamicCategoryContents(categoryName); const parsedDynamicContent = - toolbox.convertFlyoutDefToJsonArray(flyoutDef); + toolbox.convertFlyoutDefToJsonArray(flyoutDef); const {contents: dynamicContents, gaps: dynamicGaps} = - this.createFlyoutInfo_(parsedDynamicContent); + this.createFlyoutInfo(parsedDynamicContent); contents.push(...dynamicContents); gaps.push(...dynamicGaps); } switch (info['kind'].toUpperCase()) { case 'BLOCK': { - const blockInfo = (info as toolbox.BlockInfo); - const block = this.createFlyoutBlock_(blockInfo); + const blockInfo = info as toolbox.BlockInfo; + const block = this.createFlyoutBlock(blockInfo); contents.push({type: FlyoutItemType.BLOCK, block: block}); - this.addBlockGap_(blockInfo, gaps, defaultGap); + this.addBlockGap(blockInfo, gaps, defaultGap); break; } case 'SEP': { - const sepInfo = (info as toolbox.SeparatorInfo); - this.addSeparatorGap_(sepInfo, gaps, defaultGap); + const sepInfo = info as toolbox.SeparatorInfo; + this.addSeparatorGap(sepInfo, gaps, defaultGap); break; } case 'LABEL': { - const labelInfo = (info as toolbox.LabelInfo); + const labelInfo = info as toolbox.LabelInfo; // A label is a button with different styling. - const label = this.createButton_(labelInfo, /** isLabel */ true); + const label = this.createButton(labelInfo, /** isLabel */ true); contents.push({type: FlyoutItemType.BUTTON, button: label}); gaps.push(defaultGap); break; } case 'BUTTON': { - const buttonInfo = (info as toolbox.ButtonInfo); - const button = this.createButton_(buttonInfo, /** isLabel */ false); + const buttonInfo = info as toolbox.ButtonInfo; + const button = this.createButton(buttonInfo, /** isLabel */ false); contents.push({type: FlyoutItemType.BUTTON, button: button}); gaps.push(defaultGap); break; @@ -684,17 +716,18 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @returns The definition of the * flyout in one of its many forms. */ - private getDynamicCategoryContents_(categoryName: string): - toolbox.FlyoutDefinition { + private getDynamicCategoryContents( + categoryName: string + ): toolbox.FlyoutDefinition { // Look up the correct category generation function and call that to get a // valid XML list. const fnToApply = - this.workspace_.targetWorkspace!.getToolboxCategoryCallback( - categoryName); + this.workspace_.targetWorkspace!.getToolboxCategoryCallback(categoryName); if (typeof fnToApply !== 'function') { throw TypeError( - 'Couldn\'t find a callback function when opening' + - ' a toolbox category.'); + "Couldn't find a callback function when opening" + + ' a toolbox category.' + ); } return fnToApply(this.workspace_.targetWorkspace!); } @@ -707,11 +740,16 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @returns The object used to display the button in the * flyout. */ - private createButton_(btnInfo: toolbox.ButtonOrLabelInfo, isLabel: boolean): - FlyoutButton { + private createButton( + btnInfo: toolbox.ButtonOrLabelInfo, + isLabel: boolean + ): FlyoutButton { const curButton = new FlyoutButton( - this.workspace_, (this.targetWorkspace as WorkspaceSvg), btnInfo, - isLabel); + this.workspace_, + this.targetWorkspace as WorkspaceSvg, + btnInfo, + isLabel + ); return curButton; } @@ -722,33 +760,35 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param blockInfo The info of the block. * @returns The block created from the blockInfo. */ - private createFlyoutBlock_(blockInfo: toolbox.BlockInfo): BlockSvg { + private createFlyoutBlock(blockInfo: toolbox.BlockInfo): BlockSvg { let block; if (blockInfo['blockxml']) { - const xml = (typeof blockInfo['blockxml'] === 'string' ? - utilsXml.textToDom(blockInfo['blockxml']) : - blockInfo['blockxml']) as Element; - block = this.getRecycledBlock_(xml.getAttribute('type')!); + const xml = ( + typeof blockInfo['blockxml'] === 'string' + ? utilsXml.textToDom(blockInfo['blockxml']) + : blockInfo['blockxml'] + ) as Element; + block = this.getRecycledBlock(xml.getAttribute('type')!); if (!block) { block = Xml.domToBlock(xml, this.workspace_); } } else { - block = this.getRecycledBlock_(blockInfo['type']!); + block = this.getRecycledBlock(blockInfo['type']!); if (!block) { if (blockInfo['enabled'] === undefined) { - blockInfo['enabled'] = blockInfo['disabled'] !== 'true' && - blockInfo['disabled'] !== true; + blockInfo['enabled'] = + blockInfo['disabled'] !== 'true' && blockInfo['disabled'] !== true; } - block = blocks.append((blockInfo as blocks.State), this.workspace_); + block = blocks.append(blockInfo as blocks.State, this.workspace_); } } if (!block.isEnabled()) { // Record blocks that were initially disabled. // Do not enable these blocks as a result of capacity filtering. - this.permanentlyDisabled_.push(block); + this.permanentlyDisabled.push(block); } - return (block as BlockSvg); + return block as BlockSvg; } /** @@ -759,15 +799,15 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @returns The recycled block, or undefined if * one could not be recycled. */ - private getRecycledBlock_(blockType: string): BlockSvg|undefined { + private getRecycledBlock(blockType: string): BlockSvg | undefined { let index = -1; - for (let i = 0; i < this.recycledBlocks_.length; i++) { - if (this.recycledBlocks_[i].type === blockType) { + for (let i = 0; i < this.recycledBlocks.length; i++) { + if (this.recycledBlocks[i].type === blockType) { index = i; break; } } - return index === -1 ? undefined : this.recycledBlocks_.splice(index, 1)[0]; + return index === -1 ? undefined : this.recycledBlocks.splice(index, 1)[0]; } /** @@ -778,15 +818,20 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param defaultGap The default gap between one element and the * next. */ - private addBlockGap_( - blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) { + private addBlockGap( + blockInfo: toolbox.BlockInfo, + gaps: number[], + defaultGap: number + ) { let gap; if (blockInfo['gap']) { gap = parseInt(String(blockInfo['gap'])); } else if (blockInfo['blockxml']) { - const xml = (typeof blockInfo['blockxml'] === 'string' ? - utilsXml.textToDom(blockInfo['blockxml']) : - blockInfo['blockxml']) as Element; + const xml = ( + typeof blockInfo['blockxml'] === 'string' + ? utilsXml.textToDom(blockInfo['blockxml']) + : blockInfo['blockxml'] + ) as Element; gap = parseInt(xml.getAttribute('gap')!); } gaps.push(!gap || isNaN(gap) ? defaultGap : gap); @@ -801,8 +846,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param defaultGap The default gap between the button and next * element. */ - private addSeparatorGap_( - sepInfo: toolbox.SeparatorInfo, gaps: number[], defaultGap: number) { + private addSeparatorGap( + sepInfo: toolbox.SeparatorInfo, + gaps: number[], + defaultGap: number + ) { // Change the gap between two toolbox elements. // // The default gap is 24, can be set larger or smaller. @@ -819,27 +867,27 @@ export abstract class Flyout extends DeleteArea implements IFlyout { /** * Delete blocks, mats and buttons from a previous showing of the flyout. */ - private clearOldBlocks_() { + private clearOldBlocks() { // Delete any blocks from a previous showing. const oldBlocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; block = oldBlocks[i]; i++) { + for (let i = 0, block; (block = oldBlocks[i]); i++) { if (this.blockIsRecyclable_(block)) { - this.recycleBlock_(block); + this.recycleBlock(block); } else { block.dispose(false, false); } } // Delete any mats from a previous showing. - for (let j = 0; j < this.mats_.length; j++) { - const rect = this.mats_[j]; + for (let j = 0; j < this.mats.length; j++) { + const rect = this.mats[j]; if (rect) { Tooltip.unbindMouseEvents(rect); dom.removeNode(rect); } } - this.mats_.length = 0; + this.mats.length = 0; // Delete any buttons from a previous showing. - for (let i = 0, button; button = this.buttons_[i]; i++) { + for (let i = 0, button; (button = this.buttons_[i]); i++) { button.dispose(); } this.buttons_.length = 0; @@ -851,11 +899,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout { /** * Empties all of the recycled blocks, properly disposing of them. */ - private emptyRecycledBlocks_() { - for (let i = 0; i < this.recycledBlocks_.length; i++) { - this.recycledBlocks_[i].dispose(); + private emptyRecycledBlocks() { + for (let i = 0; i < this.recycledBlocks.length; i++) { + this.recycledBlocks[i].dispose(); } - this.recycledBlocks_ = []; + this.recycledBlocks = []; } /** @@ -876,10 +924,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * * @param block The block to recycle. */ - private recycleBlock_(block: BlockSvg) { + private recycleBlock(block: BlockSvg) { const xy = block.getRelativeToSurfaceXY(); block.moveBy(-xy.x, -xy.y); - this.recycledBlocks_.push(block); + this.recycledBlocks.push(block); } /** @@ -891,19 +939,38 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * as a mat for that block. */ protected addBlockListeners_( - root: SVGElement, block: BlockSvg, rect: SVGElement) { - this.listeners_.push(browserEvents.conditionalBind( - root, 'pointerdown', null, this.blockMouseDown_(block))); - this.listeners_.push(browserEvents.conditionalBind( - rect, 'pointerdown', null, this.blockMouseDown_(block))); - this.listeners_.push( - browserEvents.bind(root, 'pointerenter', block, block.addSelect)); - this.listeners_.push( - browserEvents.bind(root, 'pointerleave', block, block.removeSelect)); - this.listeners_.push( - browserEvents.bind(rect, 'pointerenter', block, block.addSelect)); - this.listeners_.push( - browserEvents.bind(rect, 'pointerleave', block, block.removeSelect)); + root: SVGElement, + block: BlockSvg, + rect: SVGElement + ) { + this.listeners.push( + browserEvents.conditionalBind( + root, + 'pointerdown', + null, + this.blockMouseDown(block) + ) + ); + this.listeners.push( + browserEvents.conditionalBind( + rect, + 'pointerdown', + null, + this.blockMouseDown(block) + ) + ); + this.listeners.push( + browserEvents.bind(root, 'pointerenter', block, block.addSelect) + ); + this.listeners.push( + browserEvents.bind(root, 'pointerleave', block, block.removeSelect) + ); + this.listeners.push( + browserEvents.bind(rect, 'pointerenter', block, block.addSelect) + ); + this.listeners.push( + browserEvents.bind(rect, 'pointerleave', block, block.removeSelect) + ); } /** @@ -912,7 +979,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param block The flyout block to copy. * @returns Function to call when block is clicked. */ - private blockMouseDown_(block: BlockSvg): Function { + private blockMouseDown(block: BlockSvg): Function { return (e: PointerEvent) => { const gesture = this.targetWorkspace.getGesture(e); if (gesture) { @@ -927,7 +994,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * * @param e Pointer down event. */ - private onMouseDown_(e: PointerEvent) { + private onMouseDown(e: PointerEvent) { const gesture = this.targetWorkspace.getGesture(e); if (gesture) { gesture.handleFlyoutStart(e, this); @@ -961,7 +1028,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { const variablesBeforeCreation = this.targetWorkspace.getAllVariables(); this.targetWorkspace.setResizesEnabled(false); try { - newBlock = this.placeNewBlock_(originalBlock); + newBlock = this.placeNewBlock(originalBlock); } finally { eventUtils.enable(); } @@ -970,7 +1037,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout { this.targetWorkspace.hideChaff(); const newVariables = Variables.getAddedVariables( - this.targetWorkspace, variablesBeforeCreation); + this.targetWorkspace, + variablesBeforeCreation + ); if (eventUtils.isEnabled()) { eventUtils.setGroup(true); @@ -978,7 +1047,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout { for (let i = 0; i < newVariables.length; i++) { const thisVariable = newVariables[i]; eventUtils.fire( - new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable)); + new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable) + ); } // Block events come after var events, in case they refer to newly created @@ -988,7 +1058,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { if (this.autoClose) { this.hide(); } else { - this.filterForCapacity_(); + this.filterForCapacity(); } return newBlock; } @@ -1007,8 +1077,14 @@ export abstract class Flyout extends DeleteArea implements IFlyout { button.show(); // Clicking on a flyout button or label is a lot like clicking on the // flyout background. - this.listeners_.push(browserEvents.conditionalBind( - buttonSvg, 'pointerdown', this, this.onMouseDown_)); + this.listeners.push( + browserEvents.conditionalBind( + buttonSvg, + 'pointerdown', + this, + this.onMouseDown + ) + ); this.buttons_.push(button); } @@ -1027,8 +1103,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * the block. */ protected createRect_( - block: BlockSvg, x: number, y: number, - blockHW: {height: number, width: number}, index: number): SVGElement { + block: BlockSvg, + x: number, + y: number, + blockHW: {height: number; width: number}, + index: number + ): SVGElement { // Create an invisible rectangle under the block to act as a button. Just // using the block as a button is poor, since blocks have holes in them. const rect = dom.createSvgElement(Svg.RECT, { @@ -1044,7 +1124,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); this.rectMap_.set(block, rect); - this.mats_[index] = rect; + this.mats[index] = rect; return rect; } @@ -1063,7 +1143,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout { const blockXY = block.getRelativeToSurfaceXY(); rect.setAttribute('y', String(blockXY.y)); rect.setAttribute( - 'x', String(this.RTL ? blockXY.x - blockHW.width : blockXY.x)); + 'x', + String(this.RTL ? blockXY.x - blockHW.width : blockXY.x) + ); } /** @@ -1072,12 +1154,13 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * on the workspace, an "a + b" block that has two shadow blocks would be * disabled. */ - private filterForCapacity_() { + private filterForCapacity() { const blocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; block = blocks[i]; i++) { - if (this.permanentlyDisabled_.indexOf(block) === -1) { + for (let i = 0, block; (block = blocks[i]); i++) { + if (this.permanentlyDisabled.indexOf(block) === -1) { const enable = this.targetWorkspace.isCapacityAvailable( - common.getBlockTypeCounts(block)); + common.getBlockTypeCounts(block) + ); while (block) { block.setEnabled(enable); block = block.getNextBlock(); @@ -1090,12 +1173,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * Reflow blocks and their mats. */ reflow() { - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); + if (this.reflowWrapper) { + this.workspace_.removeChangeListener(this.reflowWrapper); } this.reflowInternal_(); - if (this.reflowWrapper_) { - this.workspace_.addChangeListener(this.reflowWrapper_); + if (this.reflowWrapper) { + this.workspace_.addChangeListener(this.reflowWrapper); } } @@ -1105,8 +1188,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @internal */ isScrollable(): boolean { - return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() : - false; + return this.workspace_.scrollbar + ? this.workspace_.scrollbar.isVisible() + : false; } /** @@ -1115,20 +1199,20 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param oldBlock The flyout block to copy. * @returns The new block in the main workspace. */ - private placeNewBlock_(oldBlock: BlockSvg): BlockSvg { + private placeNewBlock(oldBlock: BlockSvg): BlockSvg { const targetWorkspace = this.targetWorkspace; const svgRootOld = oldBlock.getSvgRoot(); if (!svgRootOld) { - throw Error('oldBlock is not rendered.'); + throw Error('oldBlock is not rendered'); } // Clone the block. - const json = (blocks.save(oldBlock) as blocks.State); + const json = blocks.save(oldBlock) as blocks.State; // Normallly this resizes leading to weird jumps. Save it for terminateDrag. targetWorkspace.setResizesEnabled(false); - const block = (blocks.append(json, targetWorkspace) as BlockSvg); + const block = blocks.append(json, targetWorkspace) as BlockSvg; - this.positionNewBlock_(oldBlock, block); + this.positionNewBlock(oldBlock, block); return block; } @@ -1139,7 +1223,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout { * @param oldBlock The flyout block being copied. * @param block The block to posiiton. */ - private positionNewBlock_(oldBlock: BlockSvg, block: BlockSvg) { + private positionNewBlock(oldBlock: BlockSvg, block: BlockSvg) { const targetWorkspace = this.targetWorkspace; // The offset in pixels between the main workspace's origin and the upper @@ -1158,16 +1242,21 @@ export abstract class Flyout extends DeleteArea implements IFlyout { // The position of the old block in pixels relative to the upper left corner // of the injection div. - const oldBlockOffsetPixels = - Coordinate.sum(flyoutOffsetPixels, oldBlockPos); + const oldBlockOffsetPixels = Coordinate.sum( + flyoutOffsetPixels, + oldBlockPos + ); // The position of the old block in pixels relative to the origin of the // main workspace. - const finalOffset = - Coordinate.difference(oldBlockOffsetPixels, mainOffsetPixels); + const finalOffset = Coordinate.difference( + oldBlockOffsetPixels, + mainOffsetPixels + ); // The position of the old block in main workspace coordinates. finalOffset.scale(1 / targetWorkspace.scale); + // No 'reason' provided since events are disabled. block.moveTo(new Coordinate(finalOffset.x, finalOffset.y)); } } @@ -1177,6 +1266,6 @@ export abstract class Flyout extends DeleteArea implements IFlyout { */ export interface FlyoutItem { type: FlyoutItemType; - button?: FlyoutButton|undefined; - block?: BlockSvg|undefined; + button?: FlyoutButton | undefined; + block?: BlockSvg | undefined; } diff --git a/core/flyout_button.ts b/core/flyout_button.ts index 8a91d1c63..e9bf40d5f 100644 --- a/core/flyout_button.ts +++ b/core/flyout_button.ts @@ -22,7 +22,6 @@ import {Svg} from './utils/svg.js'; import type * as toolbox from './utils/toolbox.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for a button or label in the flyout. */ @@ -36,13 +35,13 @@ export class FlyoutButton { /** The radius of the flyout button's borders. */ static BORDER_RADIUS = 4; - private readonly text_: string; - private readonly position_: Coordinate; - private readonly callbackKey_: string; - private readonly cssClass_: string|null; + private readonly text: string; + private readonly position: Coordinate; + private readonly callbackKey: string; + private readonly cssClass: string | null; /** Mouse up event data. */ - private onMouseUpWrapper_: browserEvents.Data|null = null; + private onMouseUpWrapper: browserEvents.Data | null = null; info: toolbox.ButtonOrLabelInfo; /** The width of the button's rect. */ @@ -52,10 +51,10 @@ export class FlyoutButton { height = 0; /** The root SVG group for the button or label. */ - private svgGroup_: SVGGElement|null = null; + private svgGroup: SVGGElement | null = null; /** The SVG element with the text of the label or button. */ - private svgText_: SVGTextElement|null = null; + private svgText: SVGTextElement | null = null; /** * @param workspace The workspace in which to place this button. @@ -65,22 +64,25 @@ export class FlyoutButton { * @internal */ constructor( - private readonly workspace: WorkspaceSvg, - private readonly targetWorkspace: WorkspaceSvg, - json: toolbox.ButtonOrLabelInfo, private readonly isLabel_: boolean) { - this.text_ = json['text']; + private readonly workspace: WorkspaceSvg, + private readonly targetWorkspace: WorkspaceSvg, + json: toolbox.ButtonOrLabelInfo, + private readonly isLabel_: boolean + ) { + this.text = json['text']; - this.position_ = new Coordinate(0, 0); + this.position = new Coordinate(0, 0); /** The key to the function called when this button is clicked. */ - this.callbackKey_ = - (json as - AnyDuringMigration)['callbackKey'] || /* Check the lower case version - too to satisfy IE */ - (json as AnyDuringMigration)['callbackkey']; + this.callbackKey = + (json as AnyDuringMigration)[ + 'callbackKey' + ] /* Check the lower case version + too to satisfy IE */ || + (json as AnyDuringMigration)['callbackkey']; /** If specified, a CSS class to add to this button. */ - this.cssClass_ = (json as AnyDuringMigration)['web-class'] || null; + this.cssClass = (json as AnyDuringMigration)['web-class'] || null; /** The JSON specifying the label / button. */ this.info = json; @@ -93,63 +95,82 @@ export class FlyoutButton { */ createDom(): SVGElement { let cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton'; - if (this.cssClass_) { - cssClass += ' ' + this.cssClass_; + if (this.cssClass) { + cssClass += ' ' + this.cssClass; } - this.svgGroup_ = dom.createSvgElement( - Svg.G, {'class': cssClass}, this.workspace.getCanvas()); + this.svgGroup = dom.createSvgElement( + Svg.G, + {'class': cssClass}, + this.workspace.getCanvas() + ); let shadow; if (!this.isLabel_) { // Shadow rectangle (light source does not mirror in RTL). shadow = dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyFlyoutButtonShadow', - 'rx': FlyoutButton.BORDER_RADIUS, - 'ry': FlyoutButton.BORDER_RADIUS, - 'x': 1, - 'y': 1, - }, - this.svgGroup_!); + Svg.RECT, + { + 'class': 'blocklyFlyoutButtonShadow', + 'rx': FlyoutButton.BORDER_RADIUS, + 'ry': FlyoutButton.BORDER_RADIUS, + 'x': 1, + 'y': 1, + }, + this.svgGroup! + ); } // Background rectangle. const rect = dom.createSvgElement( - Svg.RECT, { - 'class': this.isLabel_ ? 'blocklyFlyoutLabelBackground' : - 'blocklyFlyoutButtonBackground', - 'rx': FlyoutButton.BORDER_RADIUS, - 'ry': FlyoutButton.BORDER_RADIUS, - }, - this.svgGroup_!); + Svg.RECT, + { + 'class': this.isLabel_ + ? 'blocklyFlyoutLabelBackground' + : 'blocklyFlyoutButtonBackground', + 'rx': FlyoutButton.BORDER_RADIUS, + 'ry': FlyoutButton.BORDER_RADIUS, + }, + this.svgGroup! + ); const svgText = dom.createSvgElement( - Svg.TEXT, { - 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', - 'x': 0, - 'y': 0, - 'text-anchor': 'middle', - }, - this.svgGroup_!); - let text = parsing.replaceMessageReferences(this.text_); + Svg.TEXT, + { + 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', + 'x': 0, + 'y': 0, + 'text-anchor': 'middle', + }, + this.svgGroup! + ); + let text = parsing.replaceMessageReferences(this.text); if (this.workspace.RTL) { // Force text to be RTL by adding an RLM. text += '\u200F'; } svgText.textContent = text; if (this.isLabel_) { - this.svgText_ = svgText; - this.workspace.getThemeManager().subscribe( - this.svgText_, 'flyoutForegroundColour', 'fill'); + this.svgText = svgText; + this.workspace + .getThemeManager() + .subscribe(this.svgText, 'flyoutForegroundColour', 'fill'); } const fontSize = style.getComputedStyle(svgText, 'fontSize'); const fontWeight = style.getComputedStyle(svgText, 'fontWeight'); const fontFamily = style.getComputedStyle(svgText, 'fontFamily'); this.width = dom.getFastTextWidthWithSizeString( - svgText, fontSize, fontWeight, fontFamily); - const fontMetrics = - dom.measureFontMetrics(text, fontSize, fontWeight, fontFamily); + svgText, + fontSize, + fontWeight, + fontFamily + ); + const fontMetrics = dom.measureFontMetrics( + text, + fontSize, + fontWeight, + fontFamily + ); this.height = fontMetrics.height; if (!this.isLabel_) { @@ -163,31 +184,35 @@ export class FlyoutButton { svgText.setAttribute('x', String(this.width / 2)); svgText.setAttribute( - 'y', - String( - this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline)); + 'y', + String(this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline) + ); - this.updateTransform_(); + this.updateTransform(); // AnyDuringMigration because: Argument of type 'SVGGElement | null' is not // assignable to parameter of type 'EventTarget'. - this.onMouseUpWrapper_ = browserEvents.conditionalBind( - this.svgGroup_ as AnyDuringMigration, 'pointerup', this, - this.onMouseUp_); - return this.svgGroup_!; + this.onMouseUpWrapper = browserEvents.conditionalBind( + this.svgGroup as AnyDuringMigration, + 'pointerup', + this, + this.onMouseUp + ); + return this.svgGroup!; } /** Correctly position the flyout button and make it visible. */ show() { - this.updateTransform_(); - this.svgGroup_!.setAttribute('display', 'block'); + this.updateTransform(); + this.svgGroup!.setAttribute('display', 'block'); } /** Update SVG attributes to match internal state. */ - private updateTransform_() { - this.svgGroup_!.setAttribute( - 'transform', - 'translate(' + this.position_.x + ',' + this.position_.y + ')'); + private updateTransform() { + this.svgGroup!.setAttribute( + 'transform', + 'translate(' + this.position.x + ',' + this.position.y + ')' + ); } /** @@ -197,9 +222,9 @@ export class FlyoutButton { * @param y The new y coordinate. */ moveTo(x: number, y: number) { - this.position_.x = x; - this.position_.y = y; - this.updateTransform_(); + this.position.x = x; + this.position.y = y; + this.updateTransform(); } /** @returns Whether or not the button is a label. */ @@ -214,12 +239,12 @@ export class FlyoutButton { * @internal */ getPosition(): Coordinate { - return this.position_; + return this.position; } /** @returns Text of the button. */ getButtonText(): string { - return this.text_; + return this.text; } /** @@ -233,14 +258,14 @@ export class FlyoutButton { /** Dispose of this button. */ dispose() { - if (this.onMouseUpWrapper_) { - browserEvents.unbind(this.onMouseUpWrapper_); + if (this.onMouseUpWrapper) { + browserEvents.unbind(this.onMouseUpWrapper); } - if (this.svgGroup_) { - dom.removeNode(this.svgGroup_); + if (this.svgGroup) { + dom.removeNode(this.svgGroup); } - if (this.svgText_) { - this.workspace.getThemeManager().unsubscribe(this.svgText_); + if (this.svgText) { + this.workspace.getThemeManager().unsubscribe(this.svgText); } } @@ -249,23 +274,26 @@ export class FlyoutButton { * * @param e Pointer up event. */ - private onMouseUp_(e: PointerEvent) { + private onMouseUp(e: PointerEvent) { const gesture = this.targetWorkspace.getGesture(e); if (gesture) { gesture.cancel(); } - if (this.isLabel_ && this.callbackKey_) { + if (this.isLabel_ && this.callbackKey) { console.warn( - 'Labels should not have callbacks. Label text: ' + this.text_); + 'Labels should not have callbacks. Label text: ' + this.text + ); } else if ( - !this.isLabel_ && - !(this.callbackKey_ && - this.targetWorkspace.getButtonCallback(this.callbackKey_))) { - console.warn('Buttons should have callbacks. Button text: ' + this.text_); + !this.isLabel_ && + !( + this.callbackKey && + this.targetWorkspace.getButtonCallback(this.callbackKey) + ) + ) { + console.warn('Buttons should have callbacks. Button text: ' + this.text); } else if (!this.isLabel_) { - const callback = - this.targetWorkspace.getButtonCallback(this.callbackKey_); + const callback = this.targetWorkspace.getButtonCallback(this.callbackKey); if (callback) { callback(this); } diff --git a/core/flyout_horizontal.ts b/core/flyout_horizontal.ts index 5a0c96f84..192b46797 100644 --- a/core/flyout_horizontal.ts +++ b/core/flyout_horizontal.ts @@ -24,7 +24,6 @@ import {Rect} from './utils/rect.js'; import * as toolbox from './utils/toolbox.js'; import * as WidgetDiv from './widgetdiv.js'; - /** * Class for a flyout. */ @@ -42,7 +41,7 @@ export class HorizontalFlyout extends Flyout { * @param xyRatio Contains a y property which is a float between 0 and 1 * specifying the degree of scrolling and a similar x property. */ - protected override setMetrics_(xyRatio: {x: number, y: number}) { + protected override setMetrics_(xyRatio: {x: number; y: number}) { if (!this.isVisible()) { return; } @@ -53,14 +52,16 @@ export class HorizontalFlyout extends Flyout { const absoluteMetrics = metricsManager.getAbsoluteMetrics(); if (typeof xyRatio.x === 'number') { - this.workspace_.scrollX = - -(scrollMetrics.left + - (scrollMetrics.width - viewMetrics.width) * xyRatio.x); + this.workspace_.scrollX = -( + scrollMetrics.left + + (scrollMetrics.width - viewMetrics.width) * xyRatio.x + ); } this.workspace_.translate( - this.workspace_.scrollX + absoluteMetrics.left, - this.workspace_.scrollY + absoluteMetrics.top); + this.workspace_.scrollX + absoluteMetrics.left, + this.workspace_.scrollY + absoluteMetrics.top + ); } /** @@ -134,7 +135,7 @@ export class HorizontalFlyout extends Flyout { const edgeWidth = targetWorkspaceViewMetrics.width - 2 * this.CORNER_RADIUS; const edgeHeight = this.height_ - this.CORNER_RADIUS; - this.setBackgroundPath_(edgeWidth, edgeHeight); + this.setBackgroundPath(edgeWidth, edgeHeight); const x = this.getX(); const y = this.getY(); @@ -148,10 +149,12 @@ export class HorizontalFlyout extends Flyout { * @param width The width of the flyout, not including the rounded corners. * @param height The height of the flyout, not including rounded corners. */ - private setBackgroundPath_(width: number, height: number) { + private setBackgroundPath(width: number, height: number) { const atTop = this.toolboxPosition_ === toolbox.Position.TOP; // Start at top left. - const path: (string|number)[] = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; + const path: (string | number)[] = [ + 'M 0,' + (atTop ? 0 : this.CORNER_RADIUS), + ]; if (atTop) { // Top. @@ -160,24 +163,52 @@ export class HorizontalFlyout extends Flyout { path.push('v', height); // Bottom. path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, this.CORNER_RADIUS); + 'a', + this.CORNER_RADIUS, + this.CORNER_RADIUS, + 0, + 0, + 1, + -this.CORNER_RADIUS, + this.CORNER_RADIUS + ); path.push('h', -width); // Left. path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, -this.CORNER_RADIUS); + 'a', + this.CORNER_RADIUS, + this.CORNER_RADIUS, + 0, + 0, + 1, + -this.CORNER_RADIUS, + -this.CORNER_RADIUS + ); path.push('z'); } else { // Top. path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, -this.CORNER_RADIUS); + 'a', + this.CORNER_RADIUS, + this.CORNER_RADIUS, + 0, + 0, + 1, + this.CORNER_RADIUS, + -this.CORNER_RADIUS + ); path.push('h', width); // Right. path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, this.CORNER_RADIUS); + 'a', + this.CORNER_RADIUS, + this.CORNER_RADIUS, + 0, + 0, + 1, + this.CORNER_RADIUS, + this.CORNER_RADIUS + ); path.push('v', height); // Bottom. path.push('h', -width - 2 * this.CORNER_RADIUS); @@ -234,17 +265,16 @@ export class HorizontalFlyout extends Flyout { contents = contents.reverse(); } - for (let i = 0, item; item = contents[i]; i++) { + for (let i = 0, item; (item = contents[i]); i++) { if (item.type === 'block') { const block = item.block; const allBlocks = block!.getDescendants(false); - for (let j = 0, child; child = allBlocks[j]; j++) { + for (let j = 0, child; (child = allBlocks[j]); j++) { // Mark blocks as being inside a flyout. This is used to detect and // prevent the closure of the flyout if the user right-clicks on such // a block. child.isInFlyout = true; } - block!.render(); const root = block!.getSvgRoot(); const blockHW = block!.getHeightWidth(); // Figure out where to place the block. @@ -283,12 +313,14 @@ export class HorizontalFlyout extends Flyout { const dx = currentDragDeltaXY.x; const dy = currentDragDeltaXY.y; // Direction goes from -180 to 180, with 0 toward the right and 90 on top. - const dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + const dragDirection = (Math.atan2(dy, dx) / Math.PI) * 180; const range = this.dragAngleRange_; // Check for up or down dragging. - if (dragDirection < 90 + range && dragDirection > 90 - range || - dragDirection > -90 - range && dragDirection < -90 + range) { + if ( + (dragDirection < 90 + range && dragDirection > 90 - range) || + (dragDirection > -90 - range && dragDirection < -90 + range) + ) { return true; } return false; @@ -301,7 +333,7 @@ export class HorizontalFlyout extends Flyout { * @returns The component's bounding box. Null if drag target area should be * ignored. */ - override getClientRect(): Rect|null { + override getClientRect(): Rect | null { if (!this.svgGroup_ || this.autoClose || !this.isVisible()) { // The bounding rectangle won't compute correctly if the flyout is closed // and auto-close flyouts aren't valid drag targets (or delete areas). @@ -319,7 +351,8 @@ export class HorizontalFlyout extends Flyout { if (this.toolboxPosition_ === toolbox.Position.TOP) { const height = flyoutRect.height; return new Rect(-BIG_NUM, top + height, -BIG_NUM, BIG_NUM); - } else { // Bottom. + } else { + // Bottom. return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM); } } @@ -332,11 +365,11 @@ export class HorizontalFlyout extends Flyout { this.workspace_.scale = this.getFlyoutScale(); let flyoutHeight = 0; const blocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); } const buttons = this.buttons_; - for (let i = 0, button; button = buttons[i]; i++) { + for (let i = 0, button; (button = buttons[i]); i++) { flyoutHeight = Math.max(flyoutHeight, button.height); } flyoutHeight += this.MARGIN * 1.5; @@ -344,21 +377,24 @@ export class HorizontalFlyout extends Flyout { flyoutHeight += Scrollbar.scrollbarThickness; if (this.height_ !== flyoutHeight) { - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { if (this.rectMap_.has(block)) { this.moveRectToBlock_(this.rectMap_.get(block)!, block); } } - if (this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ && - this.toolboxPosition_ === toolbox.Position.TOP && - !this.targetWorkspace!.getToolbox()) { + if ( + this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ && + this.toolboxPosition_ === toolbox.Position.TOP && + !this.targetWorkspace!.getToolbox() + ) { // This flyout is a simple toolbox. Reposition the workspace so that // (0,0) is in the correct position relative to the new absolute edge // (ie toolbox edge). this.targetWorkspace!.translate( - this.targetWorkspace!.scrollX, - this.targetWorkspace!.scrollY + flyoutHeight); + this.targetWorkspace!.scrollX, + this.targetWorkspace!.scrollY + flyoutHeight + ); } this.height_ = flyoutHeight; this.position(); @@ -368,5 +404,7 @@ export class HorizontalFlyout extends Flyout { } registry.register( - registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, registry.DEFAULT, - HorizontalFlyout); + registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, + registry.DEFAULT, + HorizontalFlyout +); diff --git a/core/flyout_metrics_manager.ts b/core/flyout_metrics_manager.ts index 48ca9379a..baccbbbd5 100644 --- a/core/flyout_metrics_manager.ts +++ b/core/flyout_metrics_manager.ts @@ -16,7 +16,6 @@ import type {IFlyout} from './interfaces/i_flyout.js'; import {ContainerRegion, MetricsManager} from './metrics_manager.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Calculates metrics for a flyout's workspace. * The metrics are mainly used to size scrollbars for the flyout. @@ -40,8 +39,9 @@ export class FlyoutMetricsManager extends MetricsManager { * * @returns The bounding box of the blocks on the workspace. */ - private getBoundingBox_(): SVGRect| - {height: number, y: number, width: number, x: number} { + private getBoundingBox_(): + | SVGRect + | {height: number; y: number; width: number; x: number} { let blockBoundingBox; try { blockBoundingBox = this.workspace_.getCanvas().getBBox(); @@ -68,8 +68,10 @@ export class FlyoutMetricsManager extends MetricsManager { } override getScrollMetrics( - opt_getWorkspaceCoordinates?: boolean, opt_viewMetrics?: ContainerRegion, - opt_contentMetrics?: ContainerRegion) { + opt_getWorkspaceCoordinates?: boolean, + opt_viewMetrics?: ContainerRegion, + opt_contentMetrics?: ContainerRegion + ) { const contentMetrics = opt_contentMetrics || this.getContentMetrics(); const margin = this.flyout_.MARGIN * this.workspace_.scale; const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; diff --git a/core/flyout_vertical.ts b/core/flyout_vertical.ts index b6c1ae7c2..2d24cfb33 100644 --- a/core/flyout_vertical.ts +++ b/core/flyout_vertical.ts @@ -24,7 +24,6 @@ import {Rect} from './utils/rect.js'; import * as toolbox from './utils/toolbox.js'; import * as WidgetDiv from './widgetdiv.js'; - /** * Class for a flyout. */ @@ -43,7 +42,7 @@ export class VerticalFlyout extends Flyout { * @param xyRatio Contains a y property which is a float between 0 and 1 * specifying the degree of scrolling and a similar x property. */ - protected override setMetrics_(xyRatio: {x: number, y: number}) { + protected override setMetrics_(xyRatio: {x: number; y: number}) { if (!this.isVisible()) { return; } @@ -53,13 +52,15 @@ export class VerticalFlyout extends Flyout { const absoluteMetrics = metricsManager.getAbsoluteMetrics(); if (typeof xyRatio.y === 'number') { - this.workspace_.scrollY = - -(scrollMetrics.top + - (scrollMetrics.height - viewMetrics.height) * xyRatio.y); + this.workspace_.scrollY = -( + scrollMetrics.top + + (scrollMetrics.height - viewMetrics.height) * xyRatio.y + ); } this.workspace_.translate( - this.workspace_.scrollX + absoluteMetrics.left, - this.workspace_.scrollY + absoluteMetrics.top); + this.workspace_.scrollX + absoluteMetrics.left, + this.workspace_.scrollY + absoluteMetrics.top + ); } /** @@ -132,8 +133,8 @@ export class VerticalFlyout extends Flyout { const edgeWidth = this.width_ - this.CORNER_RADIUS; const edgeHeight = - targetWorkspaceViewMetrics.height - 2 * this.CORNER_RADIUS; - this.setBackgroundPath_(edgeWidth, edgeHeight); + targetWorkspaceViewMetrics.height - 2 * this.CORNER_RADIUS; + this.setBackgroundPath(edgeWidth, edgeHeight); const x = this.getX(); const y = this.getY(); @@ -147,27 +148,42 @@ export class VerticalFlyout extends Flyout { * @param width The width of the flyout, not including the rounded corners. * @param height The height of the flyout, not including rounded corners. */ - private setBackgroundPath_(width: number, height: number) { + private setBackgroundPath(width: number, height: number) { const atRight = this.toolboxPosition_ === toolbox.Position.RIGHT; const totalWidth = width + this.CORNER_RADIUS; // Decide whether to start on the left or right. - const path: Array = - ['M ' + (atRight ? totalWidth : 0) + ',0']; + const path: Array = [ + 'M ' + (atRight ? totalWidth : 0) + ',0', + ]; // Top. - path.push('h', (atRight ? -width : width)); + path.push('h', atRight ? -width : width); // Rounded corner. path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1, - atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, this.CORNER_RADIUS); + 'a', + this.CORNER_RADIUS, + this.CORNER_RADIUS, + 0, + 0, + atRight ? 0 : 1, + atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, + this.CORNER_RADIUS + ); // Side closest to workspace. path.push('v', Math.max(0, height)); // Rounded corner. path.push( - 'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1, - atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, this.CORNER_RADIUS); + 'a', + this.CORNER_RADIUS, + this.CORNER_RADIUS, + 0, + 0, + atRight ? 0 : 1, + atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, + this.CORNER_RADIUS + ); // Bottom. - path.push('h', (atRight ? width : -width)); + path.push('h', atRight ? width : -width); path.push('z'); this.svgBackground_!.setAttribute('d', path.join(' ')); } @@ -215,26 +231,30 @@ export class VerticalFlyout extends Flyout { const cursorX = this.RTL ? margin : margin + this.tabWidth_; let cursorY = margin; - for (let i = 0, item; item = contents[i]; i++) { + for (let i = 0, item; (item = contents[i]); i++) { if (item.type === 'block') { const block = item.block; const allBlocks = block!.getDescendants(false); - for (let j = 0, child; child = allBlocks[j]; j++) { + for (let j = 0, child; (child = allBlocks[j]); j++) { // Mark blocks as being inside a flyout. This is used to detect and // prevent the closure of the flyout if the user right-clicks on such // a block. child.isInFlyout = true; } - block!.render(); const root = block!.getSvgRoot(); const blockHW = block!.getHeightWidth(); - const moveX = - block!.outputConnection ? cursorX - this.tabWidth_ : cursorX; + const moveX = block!.outputConnection + ? cursorX - this.tabWidth_ + : cursorX; block!.moveBy(moveX, cursorY); const rect = this.createRect_( - block!, this.RTL ? moveX - blockHW.width : moveX, cursorY, blockHW, - i); + block!, + this.RTL ? moveX - blockHW.width : moveX, + cursorY, + blockHW, + i + ); this.addBlockListeners_(root, block!, rect); @@ -261,12 +281,15 @@ export class VerticalFlyout extends Flyout { const dx = currentDragDeltaXY.x; const dy = currentDragDeltaXY.y; // Direction goes from -180 to 180, with 0 toward the right and 90 on top. - const dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + const dragDirection = (Math.atan2(dy, dx) / Math.PI) * 180; const range = this.dragAngleRange_; // Check for left or right dragging. - if (dragDirection < range && dragDirection > -range || - (dragDirection < -180 + range || dragDirection > 180 - range)) { + if ( + (dragDirection < range && dragDirection > -range) || + dragDirection < -180 + range || + dragDirection > 180 - range + ) { return true; } return false; @@ -279,7 +302,7 @@ export class VerticalFlyout extends Flyout { * @returns The component's bounding box. Null if drag target area should be * ignored. */ - override getClientRect(): Rect|null { + override getClientRect(): Rect | null { if (!this.svgGroup_ || this.autoClose || !this.isVisible()) { // The bounding rectangle won't compute correctly if the flyout is closed // and auto-close flyouts aren't valid drag targets (or delete areas). @@ -297,7 +320,8 @@ export class VerticalFlyout extends Flyout { if (this.toolboxPosition_ === toolbox.Position.LEFT) { const width = flyoutRect.width; return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, left + width); - } else { // Right + } else { + // Right return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM); } } @@ -310,14 +334,14 @@ export class VerticalFlyout extends Flyout { this.workspace_.scale = this.getFlyoutScale(); let flyoutWidth = 0; const blocks = this.workspace_.getTopBlocks(false); - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { let width = block.getHeightWidth().width; if (block.outputConnection) { width -= this.tabWidth_; } flyoutWidth = Math.max(flyoutWidth, width); } - for (let i = 0, button; button = this.buttons_[i]; i++) { + for (let i = 0, button; (button = this.buttons_[i]); i++) { flyoutWidth = Math.max(flyoutWidth, button.width); } flyoutWidth += this.MARGIN * 1.5 + this.tabWidth_; @@ -325,7 +349,7 @@ export class VerticalFlyout extends Flyout { flyoutWidth += Scrollbar.scrollbarThickness; if (this.width_ !== flyoutWidth) { - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { if (this.RTL) { // With the flyoutWidth known, right-align the blocks. const oldX = block.getRelativeToSurfaceXY().x; @@ -341,23 +365,29 @@ export class VerticalFlyout extends Flyout { } if (this.RTL) { // With the flyoutWidth known, right-align the buttons. - for (let i = 0, button; button = this.buttons_[i]; i++) { + for (let i = 0, button; (button = this.buttons_[i]); i++) { const y = button.getPosition().y; - const x = flyoutWidth / this.workspace_.scale - button.width - - this.MARGIN - this.tabWidth_; + const x = + flyoutWidth / this.workspace_.scale - + button.width - + this.MARGIN - + this.tabWidth_; button.moveTo(x, y); } } - if (this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ && - this.toolboxPosition_ === toolbox.Position.LEFT && - !this.targetWorkspace!.getToolbox()) { + if ( + this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ && + this.toolboxPosition_ === toolbox.Position.LEFT && + !this.targetWorkspace!.getToolbox() + ) { // This flyout is a simple toolbox. Reposition the workspace so that // (0,0) is in the correct position relative to the new absolute edge // (ie toolbox edge). this.targetWorkspace!.translate( - this.targetWorkspace!.scrollX + flyoutWidth, - this.targetWorkspace!.scrollY); + this.targetWorkspace!.scrollX + flyoutWidth, + this.targetWorkspace!.scrollY + ); } this.width_ = flyoutWidth; this.position(); @@ -367,4 +397,7 @@ export class VerticalFlyout extends Flyout { } registry.register( - registry.Type.FLYOUTS_VERTICAL_TOOLBOX, registry.DEFAULT, VerticalFlyout); + registry.Type.FLYOUTS_VERTICAL_TOOLBOX, + registry.DEFAULT, + VerticalFlyout +); diff --git a/core/generator.ts b/core/generator.ts index e3261dfa5..2485e5898 100644 --- a/core/generator.ts +++ b/core/generator.ts @@ -16,18 +16,33 @@ goog.declareModuleId('Blockly.CodeGenerator'); import type {Block} from './block.js'; import * as common from './common.js'; import {Names, NameType} from './names.js'; -import * as deprecation from './utils/deprecation.js'; import type {Workspace} from './workspace.js'; +import {warn} from './utils/deprecation.js'; +/** + * Type declaration for per-block-type generator functions. + * + * @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/generating-code} + * @param block The Block instance to generate code for. + * @param genearator The CodeGenerator calling the function. + * @returns A string containing the generated code (for statement blocks), + * or a [code, precedence] tuple (for value/expression blocks), or + * null if no code should be emitted for block. + */ +export type BlockGenerator = ( + block: Block, + generator: CodeGenerator +) => [string, number] | string | null; /** * Class for a code generator that translates the blocks into a language. - * - * @unrestricted */ export class CodeGenerator { name_: string; + /** A dictionary of block generator functions keyed by block type. */ + forBlock: {[type: string]: BlockGenerator} = Object.create(null); + /** * This is used as a placeholder in functions defined using * CodeGenerator.provideFunction_. It must not be legal code that could @@ -42,21 +57,21 @@ export class CodeGenerator { * Any instances of '%1' will be replaced by the block ID that failed. * E.g. ` checkTimeout(%1);\n` */ - INFINITE_LOOP_TRAP: string|null = null; + INFINITE_LOOP_TRAP: string | null = null; /** * Arbitrary code to inject before every statement. * Any instances of '%1' will be replaced by the block ID of the statement. * E.g. `highlight(%1);\n` */ - STATEMENT_PREFIX: string|null = null; + STATEMENT_PREFIX: string | null = null; /** * Arbitrary code to inject after every statement. * Any instances of '%1' will be replaced by the block ID of the statement. * E.g. `highlight(%1);\n` */ - STATEMENT_SUFFIX: string|null = null; + STATEMENT_SUFFIX: string | null = null; /** * The method of indenting. Defaults to two spaces, but language generators @@ -79,7 +94,7 @@ export class CodeGenerator { * will cause blockToCode to emit a warning if the generator has not been * initialized. If this flag is untouched, it will have no effect. */ - isInitialized: boolean|null = null; + isInitialized: boolean | null = null; /** Comma-separated list of reserved words. */ protected RESERVED_WORDS_ = ''; @@ -100,8 +115,10 @@ export class CodeGenerator { constructor(name: string) { this.name_ = name; - this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = - new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g'); + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = new RegExp( + this.FUNCTION_NAME_PLACEHOLDER_, + 'g' + ); } /** @@ -114,13 +131,14 @@ export class CodeGenerator { if (!workspace) { // Backwards compatibility from before there could be multiple workspaces. console.warn( - 'No workspace specified in workspaceToCode call. Guessing.'); + 'No workspace specified in workspaceToCode call. Guessing.' + ); workspace = common.getMainWorkspace(); } const code = []; this.init(workspace); const blocks = workspace.getTopBlocks(true); - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { let line = this.blockToCode(block); if (Array.isArray(line)) { // Value blocks return tuples of code and operator order. @@ -200,11 +218,14 @@ export class CodeGenerator { * For value blocks, an array containing the generated code and an * operator order value. Returns '' if block is null. */ - blockToCode(block: Block|null, opt_thisOnly?: boolean): string - |[string, number] { + blockToCode( + block: Block | null, + opt_thisOnly?: boolean + ): string | [string, number] { if (this.isInitialized === false) { console.warn( - 'CodeGenerator init was not called before blockToCode was called.'); + 'CodeGenerator init was not called before blockToCode was called.' + ); } if (!block) { return ''; @@ -218,18 +239,30 @@ export class CodeGenerator { return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]); } - const func = (this as any)[block.type]; + // Look up block generator function in dictionary - but fall back + // to looking up on this if not found, for backwards compatibility. + let func = this.forBlock[block.type]; + if (!func && (this as any)[block.type]) { + warn( + 'block generator functions on CodeGenerator objects', + '10.0', + '11.0', + 'the .forBlock[blockType] dictionary' + ); + func = (this as any)[block.type]; + } if (typeof func !== 'function') { throw Error( - 'Language "' + this.name_ + '" does not know how to generate ' + - 'code for block type "' + block.type + '".'); + `${this.name_} generator does not know how to generate code` + + `for block type "${block.type}".` + ); } // First argument to func.call is the value of 'this' in the generator. // Prior to 24 September 2013 'this' was the only way to access the block. // The current preferred method of accessing the block is through the second // argument to func.call, which becomes the first parameter to the // generator. - let code = func.call(block, block); + let code = func.call(block, block, this); if (Array.isArray(code)) { // Value blocks return tuples of code and operator order. if (!block.outputConnection) { @@ -278,15 +311,17 @@ export class CodeGenerator { // Statement blocks must only return code. if (!Array.isArray(tuple)) { throw TypeError( - `Expecting tuple from value block: ${targetBlock.type} See ` + + `Expecting tuple from value block: ${targetBlock.type} See ` + `developers.google.com/blockly/guides/create-custom-blocks/generating-code ` + - `for more information`); + `for more information` + ); } let code = tuple[0]; const innerOrder = tuple[1]; if (isNaN(innerOrder)) { throw TypeError( - 'Expecting valid order from value block: ' + targetBlock.type); + 'Expecting valid order from value block: ' + targetBlock.type + ); } if (!code) { return ''; @@ -297,8 +332,10 @@ export class CodeGenerator { const outerOrderClass = Math.floor(outerOrder); const innerOrderClass = Math.floor(innerOrder); if (outerOrderClass <= innerOrderClass) { - if (outerOrderClass === innerOrderClass && - (outerOrderClass === 0 || outerOrderClass === 99)) { + if ( + outerOrderClass === innerOrderClass && + (outerOrderClass === 0 || outerOrderClass === 99) + ) { // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. // 0 is the atomic order, 99 is the none order. No parentheses needed. // In all known languages multiple such code blocks are not order @@ -310,8 +347,10 @@ export class CodeGenerator { parensNeeded = true; // Check for special exceptions. for (let i = 0; i < this.ORDER_OVERRIDES.length; i++) { - if (this.ORDER_OVERRIDES[i][0] === outerOrder && - this.ORDER_OVERRIDES[i][1] === innerOrder) { + if ( + this.ORDER_OVERRIDES[i][0] === outerOrder && + this.ORDER_OVERRIDES[i][1] === innerOrder + ) { parensNeeded = false; break; } @@ -343,11 +382,12 @@ export class CodeGenerator { // Statement blocks must only return code. if (typeof code !== 'string') { throw TypeError( - 'Expecting code from statement block: ' + - (targetBlock && targetBlock.type)); + 'Expecting code from statement block: ' + + (targetBlock && targetBlock.type) + ); } if (code) { - code = this.prefixLines((code), this.INDENT); + code = this.prefixLines(code, this.INDENT); } return code; } @@ -364,19 +404,26 @@ export class CodeGenerator { */ addLoopTrap(branch: string, block: Block): string { if (this.INFINITE_LOOP_TRAP) { - branch = this.prefixLines( - this.injectId(this.INFINITE_LOOP_TRAP, block), this.INDENT) + - branch; + branch = + this.prefixLines( + this.injectId(this.INFINITE_LOOP_TRAP, block), + this.INDENT + ) + branch; } if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) { - branch = this.prefixLines( - this.injectId(this.STATEMENT_SUFFIX, block), this.INDENT) + - branch; + branch = + this.prefixLines( + this.injectId(this.STATEMENT_SUFFIX, block), + this.INDENT + ) + branch; } if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) { - branch = branch + - this.prefixLines( - this.injectId(this.STATEMENT_PREFIX, block), this.INDENT); + branch = + branch + + this.prefixLines( + this.injectId(this.STATEMENT_PREFIX, block), + this.INDENT + ); } return branch; } @@ -390,8 +437,8 @@ export class CodeGenerator { * @returns Code snippet with ID. */ injectId(msg: string, block: Block): string { - const id = block.id.replace(/\$/g, '$$$$'); // Issue 251. - return msg.replace(/%1/g, '\'' + id + '\''); + const id = block.id.replace(/\$/g, '$$$$'); // Issue 251. + return msg.replace(/%1/g, "'" + id + "'"); } /** @@ -424,17 +471,22 @@ export class CodeGenerator { * @returns The actual name of the new function. This may differ from * desiredName if the former has already been taken by the user. */ - protected provideFunction_(desiredName: string, code: string[]|string): - string { + protected provideFunction_( + desiredName: string, + code: string[] | string + ): string { if (!this.definitions_[desiredName]) { - const functionName = - this.nameDB_!.getDistinctName(desiredName, NameType.PROCEDURE); + const functionName = this.nameDB_!.getDistinctName( + desiredName, + NameType.PROCEDURE + ); this.functionNames_[desiredName] = functionName; if (Array.isArray(code)) { code = code.join('\n'); } - let codeText = code.trim().replace( - this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); + let codeText = code + .trim() + .replace(this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); // Change all ' ' indents into the desired indent. // To avoid an infinite loop of replacements, change all indents to '\0' // character first, then replace them all with the indent. @@ -479,8 +531,11 @@ export class CodeGenerator { * @param _opt_thisOnly True to generate code for only this statement. * @returns Code with comments and subsequent blocks added. */ - protected scrub_(_block: Block, code: string, _opt_thisOnly?: boolean): - string { + protected scrub_( + _block: Block, + code: string, + _opt_thisOnly?: boolean + ): string { // Optionally override return code; } @@ -515,26 +570,3 @@ export class CodeGenerator { return line; } } - -Object.defineProperties(CodeGenerator.prototype, { - /** - * A database of variable names. - * - * @name Blockly.CodeGenerator.prototype.variableDB_ - * @deprecated 'variableDB_' was renamed to 'nameDB_' (May 2021). - * @suppress {checkTypes} - */ - variableDB_: ({ - /** @returns Name database. */ - get(this: CodeGenerator): Names | - undefined { - deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_'); - return this.nameDB_; - }, - /** @param nameDb New name database. */ - set(this: CodeGenerator, nameDb: Names|undefined) { - deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_'); - this.nameDB_ = nameDb; - }, - }), -}); diff --git a/core/gesture.ts b/core/gesture.ts index 279d3527c..2c882027f 100644 --- a/core/gesture.ts +++ b/core/gesture.ts @@ -36,7 +36,7 @@ import {Coordinate} from './utils/coordinate.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import {WorkspaceDragger} from './workspace_dragger.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - +import type {IIcon} from './interfaces/i_icon.js'; /** * Note: In this file "start" refers to pointerdown @@ -58,26 +58,32 @@ export class Gesture { * pixels, with (0, 0) at the top left of the browser window (pointer event * clientX/Y). */ - private mouseDownXY_ = new Coordinate(0, 0); - private currentDragDeltaXY_: Coordinate; + private mouseDownXY = new Coordinate(0, 0); + private currentDragDeltaXY: Coordinate; /** * The bubble that the gesture started on, or null if it did not start on a * bubble. */ - private startBubble_: IBubble|null = null; + private startBubble: IBubble | null = null; /** * The field that the gesture started on, or null if it did not start on a * field. */ - private startField_: Field|null = null; + private startField: Field | null = null; + + /** + * The icon that the gesture started on, or null if it did not start on an + * icon. + */ + private startIcon: IIcon | null = null; /** * The block that the gesture started on, or null if it did not start on a * block. */ - private startBlock_: BlockSvg|null = null; + private startBlock: BlockSvg | null = null; /** * The block that this gesture targets. If the gesture started on a @@ -85,21 +91,21 @@ export class Gesture { * gesture started in the flyout, this is the root block of the block group * that was clicked or dragged. */ - private targetBlock_: BlockSvg|null = null; + private targetBlock: BlockSvg | null = null; /** * The workspace that the gesture started on. There may be multiple * workspaces on a page; this is more accurate than using * Blockly.common.getMainWorkspace(). */ - protected startWorkspace_: WorkspaceSvg|null = null; + protected startWorkspace_: WorkspaceSvg | null = null; /** * Whether the pointer has at any point moved out of the drag radius. * A gesture that exceeds the drag radius is a drag even if it ends exactly * at its start point. */ - private hasExceededDragRadius_ = false; + private hasExceededDragRadius = false; /** * Array holding info needed to unbind events. @@ -109,38 +115,38 @@ export class Gesture { private boundEvents: browserEvents.Data[] = []; /** The object tracking a bubble drag, or null if none is in progress. */ - private bubbleDragger_: BubbleDragger|null = null; + private bubbleDragger: BubbleDragger | null = null; /** The object tracking a block drag, or null if none is in progress. */ - private blockDragger_: IBlockDragger|null = null; + private blockDragger: IBlockDragger | null = null; /** * The object tracking a workspace or flyout workspace drag, or null if none * is in progress. */ - private workspaceDragger_: WorkspaceDragger|null = null; + private workspaceDragger: WorkspaceDragger | null = null; /** The flyout a gesture started in, if any. */ - private flyout_: IFlyout|null = null; + private flyout: IFlyout | null = null; /** Boolean for sanity-checking that some code is only called once. */ - private calledUpdateIsDragging_ = false; + private calledUpdateIsDragging = false; /** Boolean for sanity-checking that some code is only called once. */ - private hasStarted_ = false; + private gestureHasStarted = false; /** Boolean used internally to break a cycle in disposal. */ protected isEnding_ = false; - private healStack_: boolean; + private healStack: boolean; /** The event that most recently updated this gesture. */ - private mostRecentEvent_: PointerEvent; + private mostRecentEvent: PointerEvent; /** Boolean for whether or not this gesture is a multi-touch gesture. */ private isMultiTouch_ = false; /** A map of cached points used for tracking multi-touch gestures. */ - private cachedPoints = new Map(); + private cachedPoints = new Map(); /** * This is the ratio between the starting distance between the touch points @@ -148,13 +154,13 @@ export class Gesture { * Scales between 0 and 1 mean the most recent zoom was a zoom out. * Scales above 1.0 mean the most recent zoom was a zoom in. */ - private previousScale_ = 0; + private previousScale = 0; /** The starting distance between two touch points. */ - private startDistance_ = 0; + private startDistance = 0; /** Boolean for whether or not the workspace supports pinch-zoom. */ - private isPinchZoomEnabled_: boolean|null = null; + private isPinchZoomEnabled: boolean | null = null; /** * The owner of the dropdownDiv when this gesture first starts. @@ -162,7 +168,7 @@ export class Gesture { * act on their events, and some fields care about who owns * the dropdown. */ - currentDropdownOwner: Field|null = null; + currentDropdownOwner: Field | null = null; /** * @param e The event that kicked off this gesture. @@ -170,20 +176,22 @@ export class Gesture { * reference to it. */ constructor( - e: PointerEvent, private readonly creatorWorkspace: WorkspaceSvg) { - this.mostRecentEvent_ = e; + e: PointerEvent, + private readonly creatorWorkspace: WorkspaceSvg + ) { + this.mostRecentEvent = e; /** * How far the pointer has moved during this drag, in pixel units. * (0, 0) is at this.mouseDownXY_. */ - this.currentDragDeltaXY_ = new Coordinate(0, 0); + this.currentDragDeltaXY = new Coordinate(0, 0); /** * Boolean used to indicate whether or not to heal the stack after * disconnecting a block. */ - this.healStack_ = !internalConstants.DRAG_STACK; + this.healStack = !internalConstants.DRAG_STACK; } /** @@ -202,11 +210,11 @@ export class Gesture { } this.boundEvents.length = 0; - if (this.blockDragger_) { - this.blockDragger_.dispose(); + if (this.blockDragger) { + this.blockDragger.dispose(); } - if (this.workspaceDragger_) { - this.workspaceDragger_.dispose(); + if (this.workspaceDragger) { + this.workspaceDragger.dispose(); } } @@ -215,15 +223,15 @@ export class Gesture { * * @param e The most recent pointer event. */ - private updateFromEvent_(e: PointerEvent) { + private updateFromEvent(e: PointerEvent) { const currentXY = new Coordinate(e.clientX, e.clientY); - const changed = this.updateDragDelta_(currentXY); + const changed = this.updateDragDelta(currentXY); // Exceeded the drag radius for the first time. if (changed) { - this.updateIsDragging_(); + this.updateIsDragging(); Touch.longStop(); } - this.mostRecentEvent_ = e; + this.mostRecentEvent = e; } /** @@ -234,19 +242,22 @@ export class Gesture { * with (0, 0) at the window's top left corner. * @returns True if the drag just exceeded the drag radius for the first time. */ - private updateDragDelta_(currentXY: Coordinate): boolean { - this.currentDragDeltaXY_ = - Coordinate.difference(currentXY, (this.mouseDownXY_)); + private updateDragDelta(currentXY: Coordinate): boolean { + this.currentDragDeltaXY = Coordinate.difference( + currentXY, + this.mouseDownXY + ); - if (!this.hasExceededDragRadius_) { - const currentDragDelta = Coordinate.magnitude(this.currentDragDeltaXY_); + if (!this.hasExceededDragRadius) { + const currentDragDelta = Coordinate.magnitude(this.currentDragDeltaXY); // The flyout has a different drag radius from the rest of Blockly. - const limitRadius = - this.flyout_ ? config.flyoutDragRadius : config.dragRadius; + const limitRadius = this.flyout + ? config.flyoutDragRadius + : config.dragRadius; - this.hasExceededDragRadius_ = currentDragDelta > limitRadius; - return this.hasExceededDragRadius_; + this.hasExceededDragRadius = currentDragDelta > limitRadius; + return this.hasExceededDragRadius; } return false; } @@ -262,18 +273,19 @@ export class Gesture { * * @returns True if a block is being dragged from the flyout. */ - private updateIsDraggingFromFlyout_(): boolean { - if (!this.targetBlock_ || - !this.flyout_?.isBlockCreatable(this.targetBlock_)) { + private updateIsDraggingFromFlyout(): boolean { + if (!this.targetBlock || !this.flyout?.isBlockCreatable(this.targetBlock)) { return false; } - if (!this.flyout_.targetWorkspace) { + if (!this.flyout.targetWorkspace) { throw new Error(`Cannot update dragging from the flyout because the ' + 'flyout's target workspace is undefined`); } - if (!this.flyout_.isScrollable() || - this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) { - this.startWorkspace_ = this.flyout_.targetWorkspace; + if ( + !this.flyout.isScrollable() || + this.flyout.isDragTowardWorkspace(this.currentDragDeltaXY) + ) { + this.startWorkspace_ = this.flyout.targetWorkspace; this.startWorkspace_.updateScreenCalculationsIfScrolled(); // Start the event group now, so that the same event group is used for // block creation and block dragging. @@ -281,9 +293,9 @@ export class Gesture { eventUtils.setGroup(true); } // The start block is no longer relevant, because this is a drag. - this.startBlock_ = null; - this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_); - this.targetBlock_.select(); + this.startBlock = null; + this.targetBlock = this.flyout.createBlock(this.targetBlock); + this.targetBlock.select(); return true; } return false; @@ -298,12 +310,12 @@ export class Gesture { * * @returns True if a bubble is being dragged. */ - private updateIsDraggingBubble_(): boolean { - if (!this.startBubble_) { + private updateIsDraggingBubble(): boolean { + if (!this.startBubble) { return false; } - this.startDraggingBubble_(); + this.startDraggingBubble(); return true; } @@ -320,17 +332,17 @@ export class Gesture { * * @returns True if a block is being dragged. */ - private updateIsDraggingBlock_(): boolean { - if (!this.targetBlock_) { + private updateIsDraggingBlock(): boolean { + if (!this.targetBlock) { return false; } - if (this.flyout_) { - if (this.updateIsDraggingFromFlyout_()) { - this.startDraggingBlock_(); + if (this.flyout) { + if (this.updateIsDraggingFromFlyout()) { + this.startDraggingBlock(); return true; } - } else if (this.targetBlock_.isMovable()) { - this.startDraggingBlock_(); + } else if (this.targetBlock.isMovable()) { + this.startDraggingBlock(); return true; } return false; @@ -345,21 +357,22 @@ export class Gesture { * gesture. If a workspace is being dragged this function creates the * necessary WorkspaceDragger and starts the drag. */ - private updateIsDraggingWorkspace_() { + private updateIsDraggingWorkspace() { if (!this.startWorkspace_) { throw new Error( - 'Cannot update dragging the workspace because the ' + - 'start workspace is undefined'); + 'Cannot update dragging the workspace because the ' + + 'start workspace is undefined' + ); } - const wsMovable = this.flyout_ ? - this.flyout_.isScrollable() : - this.startWorkspace_ && this.startWorkspace_.isDraggable(); + const wsMovable = this.flyout + ? this.flyout.isScrollable() + : this.startWorkspace_ && this.startWorkspace_.isDraggable(); if (!wsMovable) return; - this.workspaceDragger_ = new WorkspaceDragger(this.startWorkspace_); + this.workspaceDragger = new WorkspaceDragger(this.startWorkspace_); - this.workspaceDragger_.startDrag(); + this.workspaceDragger.startDrag(); } /** @@ -368,56 +381,66 @@ export class Gesture { * the drag radius is exceeded. It should be called no more than once per * gesture. */ - private updateIsDragging_() { + private updateIsDragging() { // Sanity check. - if (this.calledUpdateIsDragging_) { + if (this.calledUpdateIsDragging) { throw Error('updateIsDragging_ should only be called once per gesture.'); } - this.calledUpdateIsDragging_ = true; + this.calledUpdateIsDragging = true; // First check if it was a bubble drag. Bubbles always sit on top of // blocks. - if (this.updateIsDraggingBubble_()) { + if (this.updateIsDraggingBubble()) { return; } // Then check if it was a block drag. - if (this.updateIsDraggingBlock_()) { + if (this.updateIsDraggingBlock()) { return; } // Then check if it's a workspace drag. - this.updateIsDraggingWorkspace_(); + this.updateIsDraggingWorkspace(); } /** Create a block dragger and start dragging the selected block. */ - private startDraggingBlock_() { + private startDraggingBlock() { const BlockDraggerClass = registry.getClassFromOptions( - registry.Type.BLOCK_DRAGGER, this.creatorWorkspace.options, true); + registry.Type.BLOCK_DRAGGER, + this.creatorWorkspace.options, + true + ); - this.blockDragger_ = - new BlockDraggerClass!((this.targetBlock_), (this.startWorkspace_)); - this.blockDragger_!.startDrag(this.currentDragDeltaXY_, this.healStack_); - this.blockDragger_!.drag(this.mostRecentEvent_, this.currentDragDeltaXY_); + this.blockDragger = new BlockDraggerClass!( + this.targetBlock, + this.startWorkspace_ + ); + this.blockDragger!.startDrag(this.currentDragDeltaXY, this.healStack); + this.blockDragger!.drag(this.mostRecentEvent, this.currentDragDeltaXY); } - // TODO (fenichel): Possibly combine this and startDraggingBlock_. /** Create a bubble dragger and start dragging the selected bubble. */ - private startDraggingBubble_() { - if (!this.startBubble_) { + private startDraggingBubble() { + if (!this.startBubble) { throw new Error( - 'Cannot update dragging the bubble because the start ' + - 'bubble is undefined'); + 'Cannot update dragging the bubble because the start ' + + 'bubble is undefined' + ); } if (!this.startWorkspace_) { throw new Error( - 'Cannot update dragging the bubble because the start ' + - 'workspace is undefined'); + 'Cannot update dragging the bubble because the start ' + + 'workspace is undefined' + ); } - this.bubbleDragger_ = - new BubbleDragger(this.startBubble_, this.startWorkspace_); - this.bubbleDragger_.startBubbleDrag(); - this.bubbleDragger_.dragBubble( - this.mostRecentEvent_, this.currentDragDeltaXY_); + this.bubbleDragger = new BubbleDragger( + this.startBubble, + this.startWorkspace_ + ); + this.bubbleDragger.startBubbleDrag(); + this.bubbleDragger.dragBubble( + this.mostRecentEvent, + this.currentDragDeltaXY + ); } /** @@ -430,18 +453,20 @@ export class Gesture { doStart(e: PointerEvent) { if (!this.startWorkspace_) { throw new Error( - 'Cannot start the touch gesture becauase the start ' + - 'workspace is undefined'); + 'Cannot start the touch gesture becauase the start ' + + 'workspace is undefined' + ); } - this.isPinchZoomEnabled_ = this.startWorkspace_.options.zoomOptions && - this.startWorkspace_.options.zoomOptions.pinch; + this.isPinchZoomEnabled = + this.startWorkspace_.options.zoomOptions && + this.startWorkspace_.options.zoomOptions.pinch; if (browserEvents.isTargetInput(e)) { this.cancel(); return; } - this.hasStarted_ = true; + this.gestureHasStarted = true; blockAnimations.disconnectUiStop(); @@ -456,15 +481,15 @@ export class Gesture { this.currentDropdownOwner = dropDownDiv.getOwner(); // Hide chaff also hides the flyout, so don't do it if the click is in a // flyout. - this.startWorkspace_.hideChaff(!!this.flyout_); + this.startWorkspace_.hideChaff(!!this.flyout); this.startWorkspace_.markFocused(); - this.mostRecentEvent_ = e; + this.mostRecentEvent = e; Tooltip.block(); - if (this.targetBlock_) { - this.targetBlock_.select(); + if (this.targetBlock) { + this.targetBlock.select(); } if (browserEvents.isRightButton(e)) { @@ -476,8 +501,8 @@ export class Gesture { Touch.longStart(e, this); } - this.mouseDownXY_ = new Coordinate(e.clientX, e.clientY); - this.healStack_ = e.altKey || e.ctrlKey || e.metaKey; + this.mouseDownXY = new Coordinate(e.clientX, e.clientY); + this.healStack = e.altKey || e.ctrlKey || e.metaKey; this.bindMouseEvents(e); @@ -493,15 +518,33 @@ export class Gesture { * @internal */ bindMouseEvents(e: PointerEvent) { - this.boundEvents.push(browserEvents.conditionalBind( - document, 'pointerdown', null, this.handleStart.bind(this), - /* opt_noCaptureIdentifier */ true)); - this.boundEvents.push(browserEvents.conditionalBind( - document, 'pointermove', null, this.handleMove.bind(this), - /* opt_noCaptureIdentifier */ true)); - this.boundEvents.push(browserEvents.conditionalBind( - document, 'pointerup', null, this.handleUp.bind(this), - /* opt_noCaptureIdentifier */ true)); + this.boundEvents.push( + browserEvents.conditionalBind( + document, + 'pointerdown', + null, + this.handleStart.bind(this), + /* opt_noCaptureIdentifier */ true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + document, + 'pointermove', + null, + this.handleMove.bind(this), + /* opt_noCaptureIdentifier */ true + ) + ); + this.boundEvents.push( + browserEvents.conditionalBind( + document, + 'pointerup', + null, + this.handleUp.bind(this), + /* opt_noCaptureIdentifier */ true + ) + ); e.preventDefault(); e.stopPropagation(); @@ -532,17 +575,20 @@ export class Gesture { * @internal */ handleMove(e: PointerEvent) { - if ((this.isDragging() && Touch.shouldHandleEvent(e)) || - !this.isMultiTouch()) { - this.updateFromEvent_(e); - if (this.workspaceDragger_) { - this.workspaceDragger_.drag(this.currentDragDeltaXY_); - } else if (this.blockDragger_) { - this.blockDragger_.drag( - this.mostRecentEvent_, this.currentDragDeltaXY_); - } else if (this.bubbleDragger_) { - this.bubbleDragger_.dragBubble( - this.mostRecentEvent_, this.currentDragDeltaXY_); + if ( + (this.isDragging() && Touch.shouldHandleEvent(e)) || + !this.isMultiTouch() + ) { + this.updateFromEvent(e); + if (this.workspaceDragger) { + this.workspaceDragger.drag(this.currentDragDeltaXY); + } else if (this.blockDragger) { + this.blockDragger.drag(this.mostRecentEvent, this.currentDragDeltaXY); + } else if (this.bubbleDragger) { + this.bubbleDragger.dragBubble( + this.mostRecentEvent, + this.currentDragDeltaXY + ); } e.preventDefault(); e.stopPropagation(); @@ -566,7 +612,7 @@ export class Gesture { if (!Touch.shouldHandleEvent(e)) { return; } - this.updateFromEvent_(e); + this.updateFromEvent(e); Touch.longStop(); if (this.isEnding_) { @@ -575,24 +621,26 @@ export class Gesture { } this.isEnding_ = true; // The ordering of these checks is important: drags have higher priority - // than clicks. Fields have higher priority than blocks; blocks have - // higher priority than workspaces. The ordering within drags does not - // matter, because the three types of dragging are exclusive. - if (this.bubbleDragger_) { - this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_); - } else if (this.blockDragger_) { - this.blockDragger_.endDrag(e, this.currentDragDeltaXY_); - } else if (this.workspaceDragger_) { - this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); - } else if (this.isBubbleClick_()) { + // than clicks. Fields and icons have higher priority than blocks; blocks + // have higher priority than workspaces. The ordering within drags does + // not matter, because the three types of dragging are exclusive. + if (this.bubbleDragger) { + this.bubbleDragger.endBubbleDrag(e, this.currentDragDeltaXY); + } else if (this.blockDragger) { + this.blockDragger.endDrag(e, this.currentDragDeltaXY); + } else if (this.workspaceDragger) { + this.workspaceDragger.endDrag(this.currentDragDeltaXY); + } else if (this.isBubbleClick()) { // Bubbles are in front of all fields and blocks. - this.doBubbleClick_(); - } else if (this.isFieldClick_()) { - this.doFieldClick_(); - } else if (this.isBlockClick_()) { - this.doBlockClick_(); - } else if (this.isWorkspaceClick_()) { - this.doWorkspaceClick_(e); + this.doBubbleClick(); + } else if (this.isFieldClick()) { + this.doFieldClick(); + } else if (this.isIconClick()) { + this.doIconClick(); + } else if (this.isBlockClick()) { + this.doBlockClick(); + } else if (this.isWorkspaceClick()) { + this.doWorkspaceClick(e); } e.preventDefault(); @@ -621,9 +669,9 @@ export class Gesture { const pointers = Array.from(this.cachedPoints.keys()); // If two pointers are down, store info if (pointers.length === 2) { - const point0 = (this.cachedPoints.get(pointers[0]))!; - const point1 = (this.cachedPoints.get(pointers[1]))!; - this.startDistance_ = Coordinate.distance(point0, point1); + const point0 = this.cachedPoints.get(pointers[0])!; + const point1 = this.cachedPoints.get(pointers[1])!; + this.startDistance = Coordinate.distance(point0, point1); this.isMultiTouch_ = true; e.preventDefault(); } @@ -641,8 +689,8 @@ export class Gesture { // Update the cache this.cachedPoints.set(pointerId, this.getTouchPoint(e)); - if (this.isPinchZoomEnabled_ && this.cachedPoints.size === 2) { - this.handlePinch_(e); + if (this.isPinchZoomEnabled && this.cachedPoints.size === 2) { + this.handlePinch(e); } else { this.handleMove(e); } @@ -653,29 +701,34 @@ export class Gesture { * * @param e A pointermove event. */ - private handlePinch_(e: PointerEvent) { + private handlePinch(e: PointerEvent) { const pointers = Array.from(this.cachedPoints.keys()); // Calculate the distance between the two pointers - const point0 = (this.cachedPoints.get(pointers[0]))!; - const point1 = (this.cachedPoints.get(pointers[1]))!; + const point0 = this.cachedPoints.get(pointers[0])!; + const point1 = this.cachedPoints.get(pointers[1])!; const moveDistance = Coordinate.distance(point0, point1); - const scale = moveDistance / this.startDistance_; + const scale = moveDistance / this.startDistance; - if (this.previousScale_ > 0 && this.previousScale_ < Infinity) { - const gestureScale = scale - this.previousScale_; - const delta = gestureScale > 0 ? gestureScale * ZOOM_IN_MULTIPLIER : - gestureScale * ZOOM_OUT_MULTIPLIER; + if (this.previousScale > 0 && this.previousScale < Infinity) { + const gestureScale = scale - this.previousScale; + const delta = + gestureScale > 0 + ? gestureScale * ZOOM_IN_MULTIPLIER + : gestureScale * ZOOM_OUT_MULTIPLIER; if (!this.startWorkspace_) { throw new Error( - 'Cannot handle a pinch because the start workspace ' + - 'is undefined'); + 'Cannot handle a pinch because the start workspace ' + 'is undefined' + ); } const workspace = this.startWorkspace_; const position = browserEvents.mouseToSvg( - e, workspace.getParentSvg(), workspace.getInverseScreenCTM()); + e, + workspace.getParentSvg(), + workspace.getInverseScreenCTM() + ); workspace.zoom(position.x, position.y, delta); } - this.previousScale_ = scale; + this.previousScale = scale; e.preventDefault(); } @@ -692,7 +745,7 @@ export class Gesture { } if (this.cachedPoints.size < 2) { this.cachedPoints.clear(); - this.previousScale_ = 0; + this.previousScale = 0; } } @@ -703,7 +756,7 @@ export class Gesture { * @returns The current touch point coordinate * @internal */ - getTouchPoint(e: PointerEvent): Coordinate|null { + getTouchPoint(e: PointerEvent): Coordinate | null { if (!this.startWorkspace_) { return null; } @@ -734,14 +787,15 @@ export class Gesture { return; } Touch.longStop(); - if (this.bubbleDragger_) { - this.bubbleDragger_.endBubbleDrag( - this.mostRecentEvent_, this.currentDragDeltaXY_); - } else if (this.blockDragger_) { - this.blockDragger_.endDrag( - this.mostRecentEvent_, this.currentDragDeltaXY_); - } else if (this.workspaceDragger_) { - this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + if (this.bubbleDragger) { + this.bubbleDragger.endBubbleDrag( + this.mostRecentEvent, + this.currentDragDeltaXY + ); + } else if (this.blockDragger) { + this.blockDragger.endDrag(this.mostRecentEvent, this.currentDragDeltaXY); + } else if (this.workspaceDragger) { + this.workspaceDragger.endDrag(this.currentDragDeltaXY); } this.dispose(); } @@ -753,13 +807,13 @@ export class Gesture { * @internal */ handleRightClick(e: PointerEvent) { - if (this.targetBlock_) { - this.bringBlockToFront_(); - this.targetBlock_.workspace.hideChaff(!!this.flyout_); - this.targetBlock_.showContextMenu(e); - } else if (this.startBubble_) { - this.startBubble_.showContextMenu(e); - } else if (this.startWorkspace_ && !this.flyout_) { + if (this.targetBlock) { + this.bringBlockToFront(); + this.targetBlock.workspace.hideChaff(!!this.flyout); + this.targetBlock.showContextMenu(e); + } else if (this.startBubble) { + this.startBubble.showContextMenu(e); + } else if (this.startWorkspace_ && !this.flyout) { this.startWorkspace_.hideChaff(); this.startWorkspace_.showContextMenu(e); } @@ -779,13 +833,14 @@ export class Gesture { * @internal */ handleWsStart(e: PointerEvent, ws: WorkspaceSvg) { - if (this.hasStarted_) { + if (this.gestureHasStarted) { throw Error( - 'Tried to call gesture.handleWsStart, ' + - 'but the gesture had already been started.'); + 'Tried to call gesture.handleWsStart, ' + + 'but the gesture had already been started.' + ); } - this.setStartWorkspace_(ws); - this.mostRecentEvent_ = e; + this.setStartWorkspace(ws); + this.mostRecentEvent = e; this.doStart(e); } @@ -794,9 +849,10 @@ export class Gesture { * * @param ws The workspace that a user clicks on. */ - private fireWorkspaceClick_(ws: WorkspaceSvg) { + private fireWorkspaceClick(ws: WorkspaceSvg) { eventUtils.fire( - new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace')); + new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace') + ); } /** @@ -807,12 +863,13 @@ export class Gesture { * @internal */ handleFlyoutStart(e: PointerEvent, flyout: IFlyout) { - if (this.hasStarted_) { + if (this.gestureHasStarted) { throw Error( - 'Tried to call gesture.handleFlyoutStart, ' + - 'but the gesture had already been started.'); + 'Tried to call gesture.handleFlyoutStart, ' + + 'but the gesture had already been started.' + ); } - this.setStartFlyout_(flyout); + this.setStartFlyout(flyout); this.handleWsStart(e, flyout.getWorkspace()); } @@ -824,13 +881,14 @@ export class Gesture { * @internal */ handleBlockStart(e: PointerEvent, block: BlockSvg) { - if (this.hasStarted_) { + if (this.gestureHasStarted) { throw Error( - 'Tried to call gesture.handleBlockStart, ' + - 'but the gesture had already been started.'); + 'Tried to call gesture.handleBlockStart, ' + + 'but the gesture had already been started.' + ); } this.setStartBlock(block); - this.mostRecentEvent_ = e; + this.mostRecentEvent = e; } /** @@ -841,13 +899,14 @@ export class Gesture { * @internal */ handleBubbleStart(e: PointerEvent, bubble: IBubble) { - if (this.hasStarted_) { + if (this.gestureHasStarted) { throw Error( - 'Tried to call gesture.handleBubbleStart, ' + - 'but the gesture had already been started.'); + 'Tried to call gesture.handleBubbleStart, ' + + 'but the gesture had already been started.' + ); } this.setStartBubble(bubble); - this.mostRecentEvent_ = e; + this.mostRecentEvent = e; } /* Begin functions defining what actions to take to execute clicks on each @@ -855,59 +914,74 @@ export class Gesture { * modify only this code. */ /** Execute a bubble click. */ - private doBubbleClick_() { + private doBubbleClick() { // TODO (#1673): Consistent handling of single clicks. - if (this.startBubble_ instanceof WorkspaceCommentSvg) { - this.startBubble_.setFocus(); - this.startBubble_.select(); + if (this.startBubble instanceof WorkspaceCommentSvg) { + this.startBubble.setFocus(); + this.startBubble.select(); } } /** Execute a field click. */ - private doFieldClick_() { - if (!this.startField_) { + private doFieldClick() { + if (!this.startField) { throw new Error( - 'Cannot do a field click because the start field is ' + - 'undefined'); + 'Cannot do a field click because the start field is undefined' + ); } // Only show the editor if the field's editor wasn't already open // right before this gesture started. - const dropdownAlreadyOpen = this.currentDropdownOwner === this.startField_; + const dropdownAlreadyOpen = this.currentDropdownOwner === this.startField; if (!dropdownAlreadyOpen) { - this.startField_.showEditor(this.mostRecentEvent_); + this.startField.showEditor(this.mostRecentEvent); } - this.bringBlockToFront_(); + this.bringBlockToFront(); + } + + /** Execute an icon click. */ + private doIconClick() { + if (!this.startIcon) { + throw new Error( + 'Cannot do an icon click because the start icon is undefined' + ); + } + + this.startIcon.onClick(); } /** Execute a block click. */ - private doBlockClick_() { + private doBlockClick() { // Block click in an autoclosing flyout. - if (this.flyout_ && this.flyout_.autoClose) { - if (!this.targetBlock_) { + if (this.flyout && this.flyout.autoClose) { + if (!this.targetBlock) { throw new Error( - 'Cannot do a block click because the target block is ' + - 'undefined'); + 'Cannot do a block click because the target block is ' + 'undefined' + ); } - if (this.targetBlock_.isEnabled()) { + if (this.targetBlock.isEnabled()) { if (!eventUtils.getGroup()) { eventUtils.setGroup(true); } - const newBlock = this.flyout_.createBlock(this.targetBlock_); + const newBlock = this.flyout.createBlock(this.targetBlock); newBlock.scheduleSnapAndBump(); } } else { if (!this.startWorkspace_) { throw new Error( - 'Cannot do a block click because the start workspace ' + - 'is undefined'); + 'Cannot do a block click because the start workspace ' + + 'is undefined' + ); } // Clicks events are on the start block, even if it was a shadow. const event = new (eventUtils.get(eventUtils.CLICK))( - this.startBlock_, this.startWorkspace_.id, 'block'); + this.startBlock, + this.startWorkspace_.id, + 'block' + ); eventUtils.fire(event); } - this.bringBlockToFront_(); + this.bringBlockToFront(); eventUtils.setGroup(false); } @@ -917,12 +991,12 @@ export class Gesture { * * @param _e A pointerup event. */ - private doWorkspaceClick_(_e: PointerEvent) { + private doWorkspaceClick(_e: PointerEvent) { const ws = this.creatorWorkspace; if (common.getSelected()) { common.getSelected()!.unselect(); } - this.fireWorkspaceClick_(this.startWorkspace_ || ws); + this.fireWorkspaceClick(this.startWorkspace_ || ws); } /* End functions defining what actions to take to execute clicks on each type @@ -934,10 +1008,10 @@ export class Gesture { * Move the dragged/clicked block to the front of the workspace so that it is * not occluded by other blocks. */ - private bringBlockToFront_() { + private bringBlockToFront() { // Blocks in the flyout don't overlap, so skip the work. - if (this.targetBlock_ && !this.flyout_) { - this.targetBlock_.bringToFront(); + if (this.targetBlock && !this.flyout) { + this.targetBlock.bringToFront(); } } @@ -950,16 +1024,34 @@ export class Gesture { * @internal */ setStartField(field: Field) { - if (this.hasStarted_) { + if (this.gestureHasStarted) { throw Error( - 'Tried to call gesture.setStartField, ' + - 'but the gesture had already been started.'); + 'Tried to call gesture.setStartField, ' + + 'but the gesture had already been started.' + ); } - if (!this.startField_) { - this.startField_ = field as Field; + if (!this.startField) { + this.startField = field as Field; } } + /** + * Record the icon that a gesture started on. + * + * @param icon The icon the gesture started on. + * @internal + */ + setStartIcon(icon: IIcon) { + if (this.gestureHasStarted) { + throw Error( + 'Tried to call gesture.setStartIcon, ' + + 'but the gesture had already been started.' + ); + } + + if (!this.startIcon) this.startIcon = icon; + } + /** * Record the bubble that a gesture started on * @@ -967,8 +1059,8 @@ export class Gesture { * @internal */ setStartBubble(bubble: IBubble) { - if (!this.startBubble_) { - this.startBubble_ = bubble; + if (!this.startBubble) { + this.startBubble = bubble; } } @@ -981,12 +1073,12 @@ export class Gesture { */ setStartBlock(block: BlockSvg) { // If the gesture already went through a bubble, don't set the start block. - if (!this.startBlock_ && !this.startBubble_) { - this.startBlock_ = block; + if (!this.startBlock && !this.startBubble) { + this.startBlock = block; if (block.isInFlyout && block !== block.getRootBlock()) { - this.setTargetBlock_(block.getRootBlock()); + this.setTargetBlock(block.getRootBlock()); } else { - this.setTargetBlock_(block); + this.setTargetBlock(block); } } } @@ -998,13 +1090,13 @@ export class Gesture { * * @param block The block the gesture targets. */ - private setTargetBlock_(block: BlockSvg) { + private setTargetBlock(block: BlockSvg) { if (block.isShadow()) { // Non-null assertion is fine b/c it is an invariant that shadows always // have parents. - this.setTargetBlock_(block.getParent()!); + this.setTargetBlock(block.getParent()!); } else { - this.targetBlock_ = block; + this.targetBlock = block; } } @@ -1013,7 +1105,7 @@ export class Gesture { * * @param ws The workspace the gesture started on. */ - private setStartWorkspace_(ws: WorkspaceSvg) { + private setStartWorkspace(ws: WorkspaceSvg) { if (!this.startWorkspace_) { this.startWorkspace_ = ws; } @@ -1024,9 +1116,9 @@ export class Gesture { * * @param flyout The flyout the gesture started on. */ - private setStartFlyout_(flyout: IFlyout) { - if (!this.flyout_) { - this.flyout_ = flyout; + private setStartFlyout(flyout: IFlyout) { + if (!this.flyout) { + this.flyout = flyout; } } @@ -1041,10 +1133,10 @@ export class Gesture { * * @returns Whether this gesture was a click on a bubble. */ - private isBubbleClick_(): boolean { + private isBubbleClick(): boolean { // A bubble click starts on a bubble and never escapes the drag radius. - const hasStartBubble = !!this.startBubble_; - return hasStartBubble && !this.hasExceededDragRadius_; + const hasStartBubble = !!this.startBubble; + return hasStartBubble && !this.hasExceededDragRadius; } /** @@ -1053,12 +1145,16 @@ export class Gesture { * * @returns Whether this gesture was a click on a block. */ - private isBlockClick_(): boolean { + private isBlockClick(): boolean { // A block click starts on a block, never escapes the drag radius, and is // not a field click. - const hasStartBlock = !!this.startBlock_; - return hasStartBlock && !this.hasExceededDragRadius_ && - !this.isFieldClick_(); + const hasStartBlock = !!this.startBlock; + return ( + hasStartBlock && + !this.hasExceededDragRadius && + !this.isFieldClick() && + !this.isIconClick() + ); } /** @@ -1067,11 +1163,20 @@ export class Gesture { * * @returns Whether this gesture was a click on a field. */ - private isFieldClick_(): boolean { - const fieldClickable = - this.startField_ ? this.startField_.isClickable() : false; - return fieldClickable && !this.hasExceededDragRadius_ && - (!this.flyout_ || !this.flyout_.autoClose); + private isFieldClick(): boolean { + const fieldClickable = this.startField + ? this.startField.isClickable() + : false; + return ( + fieldClickable && + !this.hasExceededDragRadius && + (!this.flyout || !this.flyout.autoClose) + ); + } + + /** @returns Whether this gesture is a click on an icon. */ + private isIconClick(): boolean { + return !!this.startIcon && !this.hasExceededDragRadius; } /** @@ -1080,10 +1185,10 @@ export class Gesture { * * @returns Whether this gesture was a click on a workspace. */ - private isWorkspaceClick_(): boolean { + private isWorkspaceClick(): boolean { const onlyTouchedWorkspace = - !this.startBlock_ && !this.startBubble_ && !this.startField_; - return onlyTouchedWorkspace && !this.hasExceededDragRadius_; + !this.startBlock && !this.startBubble && !this.startField; + return onlyTouchedWorkspace && !this.hasExceededDragRadius; } /* End helper functions defining types of clicks. */ @@ -1097,8 +1202,9 @@ export class Gesture { * @internal */ isDragging(): boolean { - return !!this.workspaceDragger_ || !!this.blockDragger_ || - !!this.bubbleDragger_; + return ( + !!this.workspaceDragger || !!this.blockDragger || !!this.bubbleDragger + ); } /** @@ -1110,7 +1216,7 @@ export class Gesture { * @internal */ hasStarted(): boolean { - return this.hasStarted_; + return this.gestureHasStarted; } /** @@ -1121,8 +1227,8 @@ export class Gesture { * @internal */ getInsertionMarkers(): BlockSvg[] { - if (this.blockDragger_) { - return this.blockDragger_.getInsertionMarkers(); + if (this.blockDragger) { + return this.blockDragger.getInsertionMarkers(); } return []; } @@ -1134,8 +1240,8 @@ export class Gesture { * @returns The dragger that is currently in use or null if no drag is in * progress. */ - getCurrentDragger(): WorkspaceDragger|BubbleDragger|IBlockDragger|null { - return this.blockDragger_ ?? this.workspaceDragger_ ?? this.bubbleDragger_; + getCurrentDragger(): WorkspaceDragger | BubbleDragger | IBlockDragger | null { + return this.blockDragger ?? this.workspaceDragger ?? this.bubbleDragger; } /** @@ -1145,7 +1251,7 @@ export class Gesture { */ static inProgress(): boolean { const workspaces = common.getAllWorkspaces(); - for (let i = 0, workspace; workspace = workspaces[i]; i++) { + for (let i = 0, workspace; (workspace = workspaces[i]); i++) { // Not actually necessarily a WorkspaceSvg, but it doesn't matter b/c // we're just checking if the property exists. Theoretically we would // want to use instanceof, but that causes a circular dependency. diff --git a/core/grid.ts b/core/grid.ts index 079ae5d19..491ca5df4 100644 --- a/core/grid.ts +++ b/core/grid.ts @@ -17,7 +17,6 @@ import * as dom from './utils/dom.js'; import {Svg} from './utils/svg.js'; import {GridOptions} from './options.js'; - /** * Class for a workspace's grid. */ @@ -45,7 +44,7 @@ export class Grid { this.line1 = pattern.firstChild as SVGElement; /** The vertical grid line, if it exists. */ - this.line2 = this.line1 && this.line1.nextSibling as SVGElement; + this.line2 = this.line1 && (this.line1.nextSibling as SVGElement); /** Whether blocks should snap to the grid. */ this.snapToGrid = options['snap'] ?? false; @@ -118,8 +117,13 @@ export class Grid { * @param y2 The new y end position of the line (in px). */ private setLineAttributes( - line: SVGElement, width: number, x1: number, x2: number, y1: number, - y2: number) { + line: SVGElement, + width: number, + x1: number, + x2: number, + y1: number, + y2: number + ) { if (line) { line.setAttribute('stroke-width', `${width}`); line.setAttribute('x1', `${x1}`); @@ -151,8 +155,11 @@ export class Grid { * @returns The SVG element for the grid pattern. * @internal */ - static createDom(rnd: string, gridOptions: GridOptions, defs: SVGElement): - SVGElement { + static createDom( + rnd: string, + gridOptions: GridOptions, + defs: SVGElement + ): SVGElement { /* @@ -160,16 +167,23 @@ export class Grid { */ const gridPattern = dom.createSvgElement( - Svg.PATTERN, - {'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'}, - defs); + Svg.PATTERN, + {'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'}, + defs + ); // x1, y1, x1, x2 properties will be set later in update. if ((gridOptions['length'] ?? 1) > 0 && (gridOptions['spacing'] ?? 0) > 0) { dom.createSvgElement( - Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern); + Svg.LINE, + {'stroke': gridOptions['colour']}, + gridPattern + ); if (gridOptions['length'] ?? 1 > 1) { dom.createSvgElement( - Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern); + Svg.LINE, + {'stroke': gridOptions['colour']}, + gridPattern + ); } } else { // Edge 16 doesn't handle empty patterns diff --git a/core/icon.ts b/core/icon.ts deleted file mode 100644 index 5d8002165..000000000 --- a/core/icon.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * @license - * Copyright 2013 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing an icon on a block. - * - * @class - */ -import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Icon'); - -import type {BlockSvg} from './block_svg.js'; -import * as browserEvents from './browser_events.js'; -import type {Bubble} from './bubble.js'; -import {Coordinate} from './utils/coordinate.js'; -import * as dom from './utils/dom.js'; -import {Size} from './utils/size.js'; -import {Svg} from './utils/svg.js'; -import * as svgMath from './utils/svg_math.js'; -import * as deprecation from './utils/deprecation.js'; - - -/** - * Class for an icon. - */ -export abstract class Icon { - protected block_: BlockSvg|null; - /** The icon SVG group. */ - iconGroup_: SVGGElement|null = null; - - /** Whether this icon gets hidden when the block is collapsed. */ - collapseHidden = true; - - /** Height and width of icons. */ - readonly SIZE = 17; - - /** Bubble UI (if visible). */ - protected bubble_: Bubble|null = null; - - /** Absolute coordinate of icon's center. */ - protected iconXY_: Coordinate|null = null; - - /** @param block The block associated with this icon. */ - constructor(block: BlockSvg|null) { - if (!block) { - deprecation.warn( - 'Calling the Icon constructor with a null block', 'version 9', - 'version 10', 'a non-null block'); - } - this.block_ = block; - } - - /** Create the icon on the block. */ - createIcon() { - if (this.iconGroup_) { - // Icon already exists. - return; - } - /* Here's the markup that will be generated: - - ... - - */ - this.iconGroup_ = - dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'}); - if (this.getBlock().isInFlyout) { - dom.addClass(this.iconGroup_, 'blocklyIconGroupReadonly'); - } - this.drawIcon_(this.iconGroup_); - - this.getBlock().getSvgRoot().appendChild(this.iconGroup_); - browserEvents.conditionalBind( - this.iconGroup_, 'pointerup', this, this.iconClick_); - this.updateEditable(); - } - - /** Dispose of this icon. */ - dispose() { - if (!this.getBlock().isDeadOrDying()) { - dom.removeNode(this.iconGroup_); - } - this.setVisible(false); // Dispose of and unlink the bubble. - } - - /** Add or remove the UI indicating if this icon may be clicked or not. */ - updateEditable() { - // No-op on the base class. - } - - /** - * Is the associated bubble visible? - * - * @returns True if the bubble is visible. - */ - isVisible(): boolean { - return !!this.bubble_; - } - - /** - * Clicking on the icon toggles if the bubble is visible. - * - * @param e Mouse click event. - */ - protected iconClick_(e: PointerEvent) { - if (this.getBlock().workspace.isDragging()) { - // Drag operation is concluding. Don't open the editor. - return; - } - if (!this.getBlock().isInFlyout && !browserEvents.isRightButton(e)) { - this.setVisible(!this.isVisible()); - } - } - - /** Change the colour of the associated bubble to match its block. */ - applyColour() { - if (this.bubble_ && this.isVisible()) { - this.bubble_.setColour(this.getBlock().style.colourPrimary); - } - } - - /** - * Notification that the icon has moved. Update the arrow accordingly. - * - * @param xy Absolute location in workspace coordinates. - */ - setIconLocation(xy: Coordinate) { - this.iconXY_ = xy; - if (this.bubble_ && this.isVisible()) { - this.bubble_.setAnchorLocation(xy); - } - } - - /** - * Notification that the icon has moved, but we don't really know where. - * Recompute the icon's location from scratch. - */ - computeIconLocation() { - // Find coordinates for the centre of the icon and update the arrow. - const blockXY = this.getBlock().getRelativeToSurfaceXY(); - const iconXY = svgMath.getRelativeXY(this.iconGroup_ as SVGElement); - const newXY = new Coordinate( - blockXY.x + iconXY.x + this.SIZE / 2, - blockXY.y + iconXY.y + this.SIZE / 2); - if (!Coordinate.equals(this.getIconLocation(), newXY)) { - this.setIconLocation(newXY); - } - } - - /** - * Returns the center of the block's icon relative to the surface. - * - * @returns Object with x and y properties in workspace coordinates. - */ - getIconLocation(): Coordinate|null { - return this.iconXY_; - } - - /** - * Get the size of the icon as used for rendering. - * This differs from the actual size of the icon, because it bulges slightly - * out of its row rather than increasing the height of its row. - * - * @returns Height and width. - */ - getCorrectedSize(): Size { - // TODO (#2562): Remove getCorrectedSize. - return new Size(this.SIZE, this.SIZE - 2); - } - - /** - * Draw the icon. - * - * @param _group The icon group. - */ - protected drawIcon_(_group: Element) {} - // No-op on base class. - - /** - * Show or hide the bubble. - * - * @param _visible True if the bubble should be visible. - */ - setVisible(_visible: boolean) {} - - /** - * @returns The block this icon is attached to. - */ - protected getBlock(): BlockSvg { - if (!this.block_) { - throw new Error('Block is not set for this icon.'); - } - - return this.block_; - } -} -// No-op on base class diff --git a/core/icons.ts b/core/icons.ts new file mode 100644 index 000000000..61d1594b8 --- /dev/null +++ b/core/icons.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Icon} from './icons/icon.js'; +import {CommentIcon, CommentState} from './icons/comment_icon.js'; +import {MutatorIcon} from './icons/mutator_icon.js'; +import {WarningIcon} from './icons/warning_icon.js'; +import {IconType} from './icons/icon_types.js'; +import * as exceptions from './icons/exceptions.js'; +import * as registry from './icons/registry.js'; + +export { + Icon, + CommentIcon, + CommentState, + MutatorIcon, + WarningIcon, + IconType, + exceptions, + registry, +}; diff --git a/core/icons/comment_icon.ts b/core/icons/comment_icon.ts new file mode 100644 index 000000000..4533c6f39 --- /dev/null +++ b/core/icons/comment_icon.ts @@ -0,0 +1,331 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Comment'); + +import type {Block} from '../block.js'; +import type {BlockSvg} from '../block_svg.js'; +import {IconType} from './icon_types.js'; +import {Coordinate} from '../utils.js'; +import * as dom from '../utils/dom.js'; +import * as eventUtils from '../events/utils.js'; +import {Icon} from './icon.js'; +import type {IHasBubble} from '../interfaces/i_has_bubble.js'; +import type {ISerializable} from '../interfaces/i_serializable.js'; +import {Rect} from '../utils/rect.js'; +import * as registry from './registry.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import {TextBubble} from '../bubbles/text_bubble.js'; +import {TextInputBubble} from '../bubbles/textinput_bubble.js'; +import type {WorkspaceSvg} from '../workspace_svg.js'; + +/** The size of the comment icon in workspace-scale units. */ +const SIZE = 17; + +/** The default width in workspace-scale units of the text input bubble. */ +const DEFAULT_BUBBLE_WIDTH = 160; + +/** The default height in workspace-scale units of the text input bubble. */ +const DEFAULT_BUBBLE_HEIGHT = 80; + +/** + * An icon which allows the user to add comment text to a block. + */ +export class CommentIcon extends Icon implements IHasBubble, ISerializable { + /** The type string used to identify this icon. */ + static readonly TYPE = IconType.COMMENT; + + /** + * The weight this icon has relative to other icons. Icons with more positive + * weight values are rendered farther toward the end of the block. + */ + static readonly WEIGHT = 3; + + /** The bubble used to show editable text to the user. */ + private textInputBubble: TextInputBubble | null = null; + + /** The bubble used to show non-editable text to the user. */ + private textBubble: TextBubble | null = null; + + /** The text of this comment. */ + private text = ''; + + /** The size of this comment (which is applied to the editable bubble). */ + private bubbleSize = new Size(DEFAULT_BUBBLE_WIDTH, DEFAULT_BUBBLE_HEIGHT); + + /** + * The visibility of the bubble for this comment. + * + * This is used to track what the visibile state /should/ be, not necessarily + * what it currently /is/. E.g. sometimes this will be true, but the block + * hasn't been rendered yet, so the bubble will not currently be visible. + */ + private bubbleVisiblity = false; + + constructor(protected readonly sourceBlock: Block) { + super(sourceBlock); + } + + override getType(): IconType { + return CommentIcon.TYPE; + } + + override initView(pointerdownListener: (e: PointerEvent) => void): void { + if (this.svgRoot) return; // Already initialized. + + super.initView(pointerdownListener); + + // Circle. + dom.createSvgElement( + Svg.CIRCLE, + {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, + this.svgRoot + ); + // Can't use a real '?' text character since different browsers and + // operating systems render it differently. Body of question mark. + dom.createSvgElement( + Svg.PATH, + { + 'class': 'blocklyIconSymbol', + 'd': + 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' + + '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' + + '-1.201,0.998 -1.201,1.528 -1.204,2.19z', + }, + this.svgRoot + ); + // Dot of question mark. + dom.createSvgElement( + Svg.RECT, + { + 'class': 'blocklyIconSymbol', + 'x': '6.8', + 'y': '10.78', + 'height': '2', + 'width': '2', + }, + this.svgRoot + ); + } + + override dispose() { + super.dispose(); + this.textInputBubble?.dispose(); + this.textBubble?.dispose(); + } + + override getWeight(): number { + return CommentIcon.WEIGHT; + } + + override getSize(): Size { + return new Size(SIZE, SIZE); + } + + override applyColour(): void { + super.applyColour(); + const colour = (this.sourceBlock as BlockSvg).style.colourPrimary; + this.textInputBubble?.setColour(colour); + this.textBubble?.setColour(colour); + } + + /** + * Updates the state of the bubble (editable / noneditable) to reflect the + * state of the bubble if the bubble is currently shown. + */ + override updateEditable(): void { + super.updateEditable(); + if (this.bubbleIsVisible()) { + // Close and reopen the bubble to display the correct UI. + this.setBubbleVisible(false); + this.setBubbleVisible(true); + } + } + + override onLocationChange(blockOrigin: Coordinate): void { + super.onLocationChange(blockOrigin); + const anchorLocation = this.getAnchorLocation(); + this.textInputBubble?.setAnchorLocation(anchorLocation); + this.textBubble?.setAnchorLocation(anchorLocation); + } + + /** Sets the text of this comment. Updates any bubbles if they are visible. */ + setText(text: string) { + this.text = text; + this.textInputBubble?.setText(this.text); + this.textBubble?.setText(this.text); + } + + /** Returns the text of this comment. */ + getText(): string { + return this.text; + } + + /** + * Sets the size of the editable bubble for this comment. Resizes the + * bubble if it is visible. + */ + setBubbleSize(size: Size) { + this.bubbleSize = size; + this.textInputBubble?.setSize(this.bubbleSize, true); + } + + /** @returns the size of the editable bubble for this comment. */ + getBubbleSize(): Size { + return this.bubbleSize; + } + + /** + * @returns the state of the comment as a JSON serializable value if the + * comment has text. Otherwise returns null. + */ + saveState(): CommentState | null { + if (this.text) { + return { + 'text': this.text, + 'pinned': this.bubbleIsVisible(), + 'height': this.bubbleSize.height, + 'width': this.bubbleSize.width, + }; + } + return null; + } + + /** Applies the given state to this comment. */ + loadState(state: CommentState) { + this.text = state['text'] ?? ''; + this.bubbleSize = new Size( + state['width'] ?? DEFAULT_BUBBLE_WIDTH, + state['height'] ?? DEFAULT_BUBBLE_HEIGHT + ); + this.bubbleVisiblity = state['pinned'] ?? false; + // Give the block a chance to be positioned and rendered before showing. + setTimeout(() => this.setBubbleVisible(this.bubbleVisiblity), 1); + } + + override onClick(): void { + super.onClick(); + this.setBubbleVisible(!this.bubbleIsVisible()); + } + + /** + * Updates the text of this comment in response to changes in the text of + * the input bubble. + */ + onTextChange(): void { + if (this.textInputBubble) { + this.text = this.textInputBubble.getText(); + } + } + + /** + * Updates the size of this icon in response to changes in the size of the + * input bubble. + */ + onSizeChange(): void { + if (this.textInputBubble) { + this.bubbleSize = this.textInputBubble.getSize(); + } + } + + bubbleIsVisible(): boolean { + return this.bubbleVisiblity; + } + + setBubbleVisible(visible: boolean): void { + if (visible && (this.textBubble || this.textInputBubble)) return; + if (!visible && !(this.textBubble || this.textInputBubble)) return; + + this.bubbleVisiblity = visible; + + if (!this.sourceBlock.rendered || this.sourceBlock.isInFlyout) return; + + if (visible) { + if (this.sourceBlock.isEditable()) { + this.showEditableBubble(); + } else { + this.showNonEditableBubble(); + } + this.applyColour(); + } else { + this.hideBubble(); + } + + eventUtils.fire( + new (eventUtils.get(eventUtils.BUBBLE_OPEN))( + this.sourceBlock, + visible, + 'comment' + ) + ); + } + + /** + * Shows the editable text bubble for this comment, and adds change listeners + * to update the state of this icon in response to changes in the bubble. + */ + private showEditableBubble() { + this.textInputBubble = new TextInputBubble( + this.sourceBlock.workspace as WorkspaceSvg, + this.getAnchorLocation(), + this.getBubbleOwnerRect() + ); + this.textInputBubble.setText(this.getText()); + this.textInputBubble.setSize(this.bubbleSize, true); + this.textInputBubble.addTextChangeListener(() => this.onTextChange()); + this.textInputBubble.addSizeChangeListener(() => this.onSizeChange()); + } + + /** Shows the non editable text bubble for this comment. */ + private showNonEditableBubble() { + this.textBubble = new TextBubble( + this.getText(), + this.sourceBlock.workspace as WorkspaceSvg, + this.getAnchorLocation(), + this.getBubbleOwnerRect() + ); + } + + /** Hides any open bubbles owned by this comment. */ + private hideBubble() { + this.textInputBubble?.dispose(); + this.textInputBubble = null; + this.textBubble?.dispose(); + this.textBubble = null; + } + + /** + * @returns the location the bubble should be anchored to. + * I.E. the middle of this icon. + */ + private getAnchorLocation(): Coordinate { + const midIcon = SIZE / 2; + return Coordinate.sum( + this.workspaceLocation, + new Coordinate(midIcon, midIcon) + ); + } + + /** + * @returns the rect the bubble should avoid overlapping. + * I.E. the block that owns this icon. + */ + private getBubbleOwnerRect(): Rect { + const bbox = (this.sourceBlock as BlockSvg).getSvgRoot().getBBox(); + return new Rect(bbox.y, bbox.y + bbox.height, bbox.x, bbox.x + bbox.width); + } +} + +export interface CommentState { + text?: string; + pinned?: boolean; + height?: number; + width?: number; +} + +registry.register(CommentIcon.TYPE, CommentIcon); diff --git a/core/icons/exceptions.ts b/core/icons/exceptions.ts new file mode 100644 index 000000000..55d0ef830 --- /dev/null +++ b/core/icons/exceptions.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {IIcon} from '../interfaces/i_icon.js'; + +/** + * Thrown when you add more than one icon of the same type to a block. + */ +export class DuplicateIconType extends Error { + /** + * @internal + */ + constructor(public icon: IIcon) { + super( + `Tried to append an icon of type ${icon.getType()} when an icon of ` + + `that type already exists on the block. ` + + `Use getIcon to access the existing icon.` + ); + } +} diff --git a/core/icons/icon.ts b/core/icons/icon.ts new file mode 100644 index 000000000..bdc5129c4 --- /dev/null +++ b/core/icons/icon.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import type {BlockSvg} from '../block_svg.js'; +import * as browserEvents from '../browser_events.js'; +import {hasBubble} from '../interfaces/i_has_bubble.js'; +import type {IIcon} from '../interfaces/i_icon.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import type {IconType} from './icon_types.js'; +import * as deprecation from '../utils/deprecation.js'; + +/** + * The abstract icon class. Icons are visual elements that live in the top-start + * corner of the block. Usually they provide more "meta" information about a + * block (such as warnings or comments) as opposed to fields, which provide + * "actual" information, related to how a block functions. + */ +export abstract class Icon implements IIcon { + /** + * The position of this icon relative to its blocks top-start, + * in workspace units. + */ + protected offsetInBlock: Coordinate = new Coordinate(0, 0); + + /** The position of this icon in workspace coordinates. */ + protected workspaceLocation: Coordinate = new Coordinate(0, 0); + + /** The root svg element visually representing this icon. */ + protected svgRoot: SVGGElement | null = null; + + constructor(protected sourceBlock: Block) {} + + getType(): IconType { + throw new Error('Icons must implement getType'); + } + + initView(pointerdownListener: (e: PointerEvent) => void): void { + if (this.svgRoot) return; // The icon has already been initialized. + + const svgBlock = this.sourceBlock as BlockSvg; + this.svgRoot = dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'}); + svgBlock.getSvgRoot().appendChild(this.svgRoot); + this.updateSvgRootOffset(); + browserEvents.conditionalBind( + this.svgRoot, + 'pointerdown', + this, + pointerdownListener + ); + } + + dispose(): void { + dom.removeNode(this.svgRoot); + } + + getWeight(): number { + return -1; + } + + getSize(): Size { + return new Size(0, 0); + } + + applyColour(): void {} + + updateEditable(): void {} + + updateCollapsed(): void { + if (!this.svgRoot) return; + if (this.sourceBlock.isCollapsed()) { + this.svgRoot.style.display = 'none'; + } else { + this.svgRoot.style.display = 'block'; + } + if (hasBubble(this)) { + this.setBubbleVisible(false); + } + } + + hideForInsertionMarker(): void { + if (!this.svgRoot) return; + this.svgRoot.style.display = 'none'; + } + + isShownWhenCollapsed(): boolean { + return false; + } + + setOffsetInBlock(offset: Coordinate): void { + this.offsetInBlock = offset; + this.updateSvgRootOffset(); + } + + private updateSvgRootOffset(): void { + this.svgRoot?.setAttribute( + 'transform', + `translate(${this.offsetInBlock.x}, ${this.offsetInBlock.y})` + ); + } + + onLocationChange(blockOrigin: Coordinate): void { + this.workspaceLocation = Coordinate.sum(blockOrigin, this.offsetInBlock); + } + + onClick(): void {} + + /** + * Sets the visibility of the icon's bubble if one exists. + * + * @deprecated Use `setBubbleVisible` instead. To be removed in v11. + */ + setVisible(visibility: boolean): void { + deprecation.warn('setVisible', 'v10', 'v11', 'setBubbleVisible'); + if (hasBubble(this)) this.setBubbleVisible(visibility); + } +} diff --git a/core/icons/icon_types.ts b/core/icons/icon_types.ts new file mode 100644 index 000000000..25773c5bb --- /dev/null +++ b/core/icons/icon_types.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {IIcon} from '../interfaces/i_icon.js'; +import {CommentIcon} from './comment_icon.js'; +import {MutatorIcon} from './mutator_icon.js'; +import {WarningIcon} from './warning_icon.js'; + +/** + * Defines the type of an icon, so that it can be retrieved from block.getIcon + */ +export class IconType<_T extends IIcon> { + /** @param name The name of the registry type. */ + constructor(private readonly name: string) {} + + /** @returns the name of the type. */ + toString(): string { + return this.name; + } + + /** @returns true if this icon type is equivalent to the given icon type. */ + equals(type: IconType): boolean { + return this.name === type.toString(); + } + + static MUTATOR = new IconType('mutator'); + static WARNING = new IconType('warning'); + static COMMENT = new IconType('comment'); +} diff --git a/core/icons/mutator_icon.ts b/core/icons/mutator_icon.ts new file mode 100644 index 000000000..eee9e585d --- /dev/null +++ b/core/icons/mutator_icon.ts @@ -0,0 +1,359 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Mutator'); + +import type {Abstract} from '../events/events_abstract.js'; +import type {Block} from '../block.js'; +import {BlockChange} from '../events/events_block_change.js'; +import type {BlocklyOptions} from '../blockly_options.js'; +import type {BlockSvg} from '../block_svg.js'; +import type {Connection} from '../connection.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import * as eventUtils from '../events/utils.js'; +import type {IHasBubble} from '../interfaces/i_has_bubble.js'; +import {Icon} from './icon.js'; +import {MiniWorkspaceBubble} from '../bubbles/mini_workspace_bubble.js'; +import {Rect} from '../utils/rect.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import type {WorkspaceSvg} from '../workspace_svg.js'; +import * as deprecation from '../utils/deprecation.js'; +import {IconType} from './icon_types.js'; + +/** The size of the mutator icon in workspace-scale units. */ +const SIZE = 17; + +/** + * The distance between the root block in the mini workspace and that + * workspace's edges. + */ +const WORKSPACE_MARGIN = 16; + +/** + * An icon that allows the user to change the shape of the block. + * + * For example, it could be used to add additional fields or inputs to + * the block. + */ +export class MutatorIcon extends Icon implements IHasBubble { + /** The type string used to identify this icon. */ + static readonly TYPE = IconType.MUTATOR; + + /** + * The weight this icon has relative to other icons. Icons with more positive + * weight values are rendered farther toward the end of the block. + */ + static readonly WEIGHT = 1; + + /** The bubble used to show the mini workspace to the user. */ + private miniWorkspaceBubble: MiniWorkspaceBubble | null = null; + + /** The root block in the mini workspace. */ + private rootBlock: BlockSvg | null = null; + + /** The PID tracking updating the workkspace in response to user events. */ + private updateWorkspacePid: ReturnType | null = null; + + constructor( + private readonly flyoutBlockTypes: string[], + protected readonly sourceBlock: BlockSvg + ) { + super(sourceBlock); + } + + override getType(): IconType { + return MutatorIcon.TYPE; + } + + override initView(pointerdownListener: (e: PointerEvent) => void): void { + if (this.svgRoot) return; // Already initialized. + + super.initView(pointerdownListener); + + // Square with rounded corners. + dom.createSvgElement( + Svg.RECT, + { + 'class': 'blocklyIconShape', + 'rx': '4', + 'ry': '4', + 'height': '16', + 'width': '16', + }, + this.svgRoot + ); + // Gear teeth. + dom.createSvgElement( + Svg.PATH, + { + 'class': 'blocklyIconSymbol', + 'd': + 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,' + + '0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,' + + '-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,' + + '-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,' + + '-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 ' + + '-0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,' + + '0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z', + }, + this.svgRoot + ); + // Axle hole. + dom.createSvgElement( + Svg.CIRCLE, + {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, + this.svgRoot + ); + } + + override dispose(): void { + super.dispose(); + this.miniWorkspaceBubble?.dispose(); + } + + override getWeight(): number { + return MutatorIcon.WEIGHT; + } + + override getSize(): Size { + return new Size(SIZE, SIZE); + } + + override applyColour(): void { + super.applyColour(); + this.miniWorkspaceBubble?.setColour(this.sourceBlock.style.colourPrimary); + this.miniWorkspaceBubble?.updateBlockStyles(); + } + + override updateCollapsed(): void { + super.updateCollapsed(); + if (this.sourceBlock.isCollapsed()) this.setBubbleVisible(false); + } + + override onLocationChange(blockOrigin: Coordinate): void { + super.onLocationChange(blockOrigin); + this.miniWorkspaceBubble?.setAnchorLocation(this.getAnchorLocation()); + } + + override onClick(): void { + super.onClick(); + this.setBubbleVisible(!this.bubbleIsVisible()); + } + + bubbleIsVisible(): boolean { + return !!this.miniWorkspaceBubble; + } + + setBubbleVisible(visible: boolean): void { + if (this.bubbleIsVisible() === visible) return; + + if (visible) { + this.miniWorkspaceBubble = new MiniWorkspaceBubble( + this.getMiniWorkspaceConfig(), + this.sourceBlock.workspace, + this.getAnchorLocation(), + this.getBubbleOwnerRect() + ); + this.applyColour(); + this.createRootBlock(); + this.addSaveConnectionsListener(); + this.miniWorkspaceBubble?.addWorkspaceChangeListener( + this.createMiniWorkspaceChangeListener() + ); + } else { + this.miniWorkspaceBubble?.dispose(); + this.miniWorkspaceBubble = null; + } + + eventUtils.fire( + new (eventUtils.get(eventUtils.BUBBLE_OPEN))( + this.sourceBlock, + visible, + 'mutator' + ) + ); + } + + /** @returns the configuration the mini workspace should have. */ + private getMiniWorkspaceConfig() { + const options: BlocklyOptions = { + 'disable': false, + 'media': this.sourceBlock.workspace.options.pathToMedia, + 'rtl': this.sourceBlock.RTL, + 'renderer': this.sourceBlock.workspace.options.renderer, + 'rendererOverrides': + this.sourceBlock.workspace.options.rendererOverrides ?? undefined, + }; + + if (this.flyoutBlockTypes.length) { + options.toolbox = { + 'kind': 'flyoutToolbox', + 'contents': this.flyoutBlockTypes.map((type) => ({ + 'kind': 'block', + 'type': type, + })), + }; + } + + return options; + } + + /** + * @returns the location the bubble should be anchored to. + * I.E. the middle of this icon. + */ + private getAnchorLocation(): Coordinate { + const midIcon = SIZE / 2; + return Coordinate.sum( + this.workspaceLocation, + new Coordinate(midIcon, midIcon) + ); + } + + /** + * @returns the rect the bubble should avoid overlapping. + * I.E. the block that owns this icon. + */ + private getBubbleOwnerRect(): Rect { + const bbox = this.sourceBlock.getSvgRoot().getBBox(); + return new Rect(bbox.y, bbox.y + bbox.height, bbox.x, bbox.x + bbox.width); + } + + /** Decomposes the source block to create blocks in the mini workspace. */ + private createRootBlock() { + this.rootBlock = this.sourceBlock.decompose!( + this.miniWorkspaceBubble!.getWorkspace() + )!; + + for (const child of this.rootBlock.getDescendants(false)) { + child.queueRender(); + } + + this.rootBlock.setMovable(false); + this.rootBlock.setDeletable(false); + + const flyoutWidth = + this.miniWorkspaceBubble?.getWorkspace()?.getFlyout()?.getWidth() ?? 0; + this.rootBlock.moveBy( + this.rootBlock.RTL ? -(flyoutWidth + WORKSPACE_MARGIN) : WORKSPACE_MARGIN, + WORKSPACE_MARGIN + ); + } + + /** Adds a listen to the source block that triggers saving connections. */ + private addSaveConnectionsListener() { + if (!this.sourceBlock.saveConnections || !this.rootBlock) return; + const saveConnectionsListener = () => { + if (!this.sourceBlock.saveConnections || !this.rootBlock) return; + this.sourceBlock.saveConnections(this.rootBlock); + }; + saveConnectionsListener(); + this.sourceBlock.workspace.addChangeListener(saveConnectionsListener); + } + + /** + * Creates a change listener to add to the mini workspace which recomposes + * the block. + */ + private createMiniWorkspaceChangeListener() { + return (e: Abstract) => { + if (!MutatorIcon.isIgnorableMutatorEvent(e) && !this.updateWorkspacePid) { + this.updateWorkspacePid = setTimeout(() => { + this.updateWorkspacePid = null; + this.recomposeSourceBlock(); + }, 0); + } + }; + } + + /** + * Returns true if the given event is not one the mutator needs to + * care about. + * + * @internal + */ + static isIgnorableMutatorEvent(e: Abstract) { + return ( + e.isUiEvent || + e.type === eventUtils.CREATE || + (e.type === eventUtils.CHANGE && + (e as BlockChange).element === 'disabled') + ); + } + + /** Recomposes the source block based on changes to the mini workspace. */ + private recomposeSourceBlock() { + if (!this.rootBlock) return; + + const existingGroup = eventUtils.getGroup(); + if (!existingGroup) eventUtils.setGroup(true); + + const oldExtraState = BlockChange.getExtraBlockState_(this.sourceBlock); + this.sourceBlock.compose!(this.rootBlock); + const newExtraState = BlockChange.getExtraBlockState_(this.sourceBlock); + + if (oldExtraState !== newExtraState) { + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + this.sourceBlock, + 'mutation', + null, + oldExtraState, + newExtraState + ) + ); + } + + eventUtils.setGroup(existingGroup); + } + + /** + * @returns The workspace of the mini workspace bubble, if the bubble is + * currently open. + */ + getWorkspace(): WorkspaceSvg | undefined { + return this.miniWorkspaceBubble?.getWorkspace(); + } + + /** + * Reconnects the given connection to the mutated input on the given block. + * + * @deprecated Use connection.reconnect instead. To be removed in v11. + */ + static reconnect( + connectionChild: Connection | null, + block: Block, + inputName: string + ): boolean { + deprecation.warn( + 'MutatorIcon.reconnect', + 'v10', + 'v11', + 'connection.reconnect' + ); + if (!connectionChild) return false; + return connectionChild.reconnect(block, inputName); + } + + /** + * Returns the parent workspace of a workspace that is inside a mini workspace + * bubble, taking into account whether the workspace is a flyout. + * + * @deprecated Use workspace.getRootWorkspace. To be removed in v11. + */ + static findParentWs(workspace: WorkspaceSvg): WorkspaceSvg | null { + deprecation.warn( + 'MutatorIcon.findParentWs', + 'v10', + 'v11', + 'workspace.getRootWorkspace' + ); + return workspace.getRootWorkspace(); + } +} diff --git a/core/icons/registry.ts b/core/icons/registry.ts new file mode 100644 index 000000000..5585a32a3 --- /dev/null +++ b/core/icons/registry.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import type {IIcon} from '../interfaces/i_icon.js'; +import * as registry from '../registry.js'; +import {IconType} from './icon_types.js'; + +/** + * Registers the given icon so that it can be deserialized. + * + * @param type The type of the icon to register. This should be the same string + * that is returned from its `getType` method. + * @param iconConstructor The icon class/constructor to register. + */ +export function register( + type: IconType, + iconConstructor: new (block: Block) => IIcon +) { + registry.register(registry.Type.ICON, type.toString(), iconConstructor); +} + +/** + * Unregisters the icon associated with the given type. + * + * @param type The type of the icon to unregister. + */ +export function unregister(type: string) { + registry.unregister(registry.Type.ICON, type); +} diff --git a/core/icons/warning_icon.ts b/core/icons/warning_icon.ts new file mode 100644 index 000000000..f28e5effd --- /dev/null +++ b/core/icons/warning_icon.ts @@ -0,0 +1,212 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Warning'); + +import type {BlockSvg} from '../block_svg.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import * as eventUtils from '../events/utils.js'; +import {Icon} from './icon.js'; +import type {IHasBubble} from '../interfaces/i_has_bubble.js'; +import {Rect} from '../utils/rect.js'; +import {Size} from '../utils.js'; +import {Svg} from '../utils/svg.js'; +import {TextBubble} from '../bubbles/text_bubble.js'; +import {IconType} from './icon_types.js'; + +/** The size of the warning icon in workspace-scale units. */ +const SIZE = 17; + +/** + * An icon that warns the user that something is wrong with their block. + * + * For example, this could be used to warn them about incorrect field values, + * or incorrect placement of the block (putting it somewhere it doesn't belong). + */ +export class WarningIcon extends Icon implements IHasBubble { + /** The type string used to identify this icon. */ + static readonly TYPE = IconType.WARNING; + + /** + * The weight this icon has relative to other icons. Icons with more positive + * weight values are rendered farther toward the end of the block. + */ + static readonly WEIGHT = 2; + + /** A map of warning IDs to warning text. */ + private textMap: Map = new Map(); + + /** The bubble used to display the warnings to the user. */ + private textBubble: TextBubble | null = null; + + /** @internal */ + constructor(protected readonly sourceBlock: BlockSvg) { + super(sourceBlock); + } + + override getType(): IconType { + return WarningIcon.TYPE; + } + + override initView(pointerdownListener: (e: PointerEvent) => void): void { + if (this.svgRoot) return; // Already initialized. + + super.initView(pointerdownListener); + + // Triangle with rounded corners. + dom.createSvgElement( + Svg.PATH, + { + 'class': 'blocklyIconShape', + 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z', + }, + this.svgRoot + ); + // Can't use a real '!' text character since different browsers and + // operating systems render it differently. Body of exclamation point. + dom.createSvgElement( + Svg.PATH, + { + 'class': 'blocklyIconSymbol', + 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z', + }, + this.svgRoot + ); + // Dot of exclamation point. + dom.createSvgElement( + Svg.RECT, + { + 'class': 'blocklyIconSymbol', + 'x': '7', + 'y': '11', + 'height': '2', + 'width': '2', + }, + this.svgRoot + ); + } + + override dispose() { + super.dispose(); + this.textBubble?.dispose(); + } + + override getWeight(): number { + return WarningIcon.WEIGHT; + } + + override getSize(): Size { + return new Size(SIZE, SIZE); + } + + override applyColour(): void { + super.applyColour(); + this.textBubble?.setColour(this.sourceBlock.style.colourPrimary); + } + + override updateCollapsed(): void { + // We are shown when collapsed, so do nothing! I.e. skip the default + // behavior of hiding. + } + + /** Tells blockly that this icon is shown when the block is collapsed. */ + override isShownWhenCollapsed(): boolean { + return true; + } + + /** Updates the location of the icon's bubble if it is open. */ + override onLocationChange(blockOrigin: Coordinate): void { + super.onLocationChange(blockOrigin); + this.textBubble?.setAnchorLocation(this.getAnchorLocation()); + } + + /** + * Adds a warning message to this warning icon. + * + * @param text The text of the message to add. + * @param id The id of the message to add. + * @internal + */ + addMessage(text: string, id: string): this { + if (this.textMap.get(id) === text) return this; + + if (text) { + this.textMap.set(id, text); + } else { + this.textMap.delete(id); + } + + this.textBubble?.setText(this.getText()); + return this; + } + + /** + * @returns the display text for this icon. Includes all warning messages + * concatenated together with newlines. + * @internal + */ + getText(): string { + return [...this.textMap.values()].join('\n'); + } + + /** Toggles the visibility of the bubble. */ + override onClick(): void { + super.onClick(); + this.setBubbleVisible(!this.bubbleIsVisible()); + } + + bubbleIsVisible(): boolean { + return !!this.textBubble; + } + + setBubbleVisible(visible: boolean): void { + if (this.bubbleIsVisible() === visible) return; + + if (visible) { + this.textBubble = new TextBubble( + this.getText(), + this.sourceBlock.workspace, + this.getAnchorLocation(), + this.getBubbleOwnerRect() + ); + this.applyColour(); + } else { + this.textBubble?.dispose(); + this.textBubble = null; + } + + eventUtils.fire( + new (eventUtils.get(eventUtils.BUBBLE_OPEN))( + this.sourceBlock, + visible, + 'warning' + ) + ); + } + + /** + * @returns the location the bubble should be anchored to. + * I.E. the middle of this icon. + */ + private getAnchorLocation(): Coordinate { + const midIcon = SIZE / 2; + return Coordinate.sum( + this.workspaceLocation, + new Coordinate(midIcon, midIcon) + ); + } + + /** + * @returns the rect the bubble should avoid overlapping. + * I.E. the block that owns this icon. + */ + private getBubbleOwnerRect(): Rect { + const bbox = this.sourceBlock.getSvgRoot().getBBox(); + return new Rect(bbox.y, bbox.y + bbox.height, bbox.x, bbox.x + bbox.width); + } +} diff --git a/core/inject.ts b/core/inject.ts index a02ee52b3..be9891e4c 100644 --- a/core/inject.ts +++ b/core/inject.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.inject'); -import {BlockDragSurfaceSvg} from './block_drag_surface.js'; import type {BlocklyOptions} from './blockly_options.js'; import * as browserEvents from './browser_events.js'; import * as bumpObjects from './bump_objects.js'; @@ -26,10 +25,8 @@ import * as dom from './utils/dom.js'; import {Svg} from './utils/svg.js'; import * as userAgent from './utils/useragent.js'; import * as WidgetDiv from './widgetdiv.js'; -import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js'; import {WorkspaceSvg} from './workspace_svg.js'; - /** * Inject a Blockly editor into the specified container element (usually a div). * @@ -38,54 +35,44 @@ import {WorkspaceSvg} from './workspace_svg.js'; * @returns Newly created main workspace. */ export function inject( - container: Element|string, opt_options?: BlocklyOptions): WorkspaceSvg { + container: Element | string, + opt_options?: BlocklyOptions +): WorkspaceSvg { + let containerElement: Element | null = null; if (typeof container === 'string') { - // AnyDuringMigration because: Type 'Element | null' is not assignable to - // type 'string | Element'. - container = (document.getElementById(container) || - document.querySelector(container)) as AnyDuringMigration; + containerElement = + document.getElementById(container) || document.querySelector(container); + } else { + containerElement = container; } // Verify that the container is in document. - // AnyDuringMigration because: Argument of type 'string | Element' is not - // assignable to parameter of type 'Node'. - if (!container || - !dom.containsNode(document, container as AnyDuringMigration)) { - throw Error('Error: container is not in current document.'); + if ( + !document.contains(containerElement) && + document !== containerElement?.ownerDocument + ) { + throw Error('Error: container is not in current document'); } - const options = new Options(opt_options || {} as BlocklyOptions); - const subContainer = (document.createElement('div')); + const options = new Options(opt_options || ({} as BlocklyOptions)); + const subContainer = document.createElement('div'); subContainer.className = 'injectionDiv'; subContainer.tabIndex = 0; aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']); - // AnyDuringMigration because: Property 'appendChild' does not exist on type - // 'string | Element'. - (container as AnyDuringMigration).appendChild(subContainer); + containerElement!.appendChild(subContainer); const svg = createDom(subContainer, options); - // Create surfaces for dragging things. These are optimizations - // so that the browser does not repaint during the drag. - const blockDragSurface = new BlockDragSurfaceSvg(subContainer); - - const workspaceDragSurface = new WorkspaceDragSurfaceSvg(subContainer); - - const workspace = - createMainWorkspace(svg, options, blockDragSurface, workspaceDragSurface); + const workspace = createMainWorkspace(svg, options); init(workspace); // Keep focus on the first workspace so entering keyboard navigation looks // correct. - // AnyDuringMigration because: Argument of type 'WorkspaceSvg' is not - // assignable to parameter of type 'Workspace'. - common.setMainWorkspace(workspace as AnyDuringMigration); + common.setMainWorkspace(workspace); common.svgResize(workspace); - subContainer.addEventListener('focusin', function() { - // AnyDuringMigration because: Argument of type 'WorkspaceSvg' is not - // assignable to parameter of type 'Workspace'. - common.setMainWorkspace(workspace as AnyDuringMigration); + subContainer.addEventListener('focusin', function () { + common.setMainWorkspace(workspace); }); return workspace; @@ -119,15 +106,17 @@ function createDom(container: Element, options: Options): SVGElement { */ const svg = dom.createSvgElement( - Svg.SVG, { - 'xmlns': dom.SVG_NS, - 'xmlns:html': dom.HTML_NS, - 'xmlns:xlink': dom.XLINK_NS, - 'version': '1.1', - 'class': 'blocklySvg', - 'tabindex': '0', - }, - container); + Svg.SVG, + { + 'xmlns': dom.SVG_NS, + 'xmlns:html': dom.HTML_NS, + 'xmlns:xlink': dom.XLINK_NS, + 'version': '1.1', + 'class': 'blocklySvg', + 'tabindex': '0', + }, + container + ); /* ... filters go here ... @@ -148,16 +137,11 @@ function createDom(container: Element, options: Options): SVGElement { * * @param svg SVG element with pattern defined. * @param options Dictionary of options. - * @param blockDragSurface Drag surface SVG for the blocks. - * @param workspaceDragSurface Drag surface SVG for the workspace. * @returns Newly created main workspace. */ -function createMainWorkspace( - svg: SVGElement, options: Options, blockDragSurface: BlockDragSurfaceSvg, - workspaceDragSurface: WorkspaceDragSurfaceSvg): WorkspaceSvg { +function createMainWorkspace(svg: SVGElement, options: Options): WorkspaceSvg { options.parentWorkspace = null; - const mainWorkspace = - new WorkspaceSvg(options, blockDragSurface, workspaceDragSurface); + const mainWorkspace = new WorkspaceSvg(options); const wsOptions = mainWorkspace.options; mainWorkspace.scale = wsOptions.zoomOptions.startScale; svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); @@ -185,14 +169,16 @@ function createMainWorkspace( mainWorkspace.addZoomControls(); } // Register the workspace svg as a UI component. - mainWorkspace.getThemeManager().subscribe( - svg, 'workspaceBackgroundColour', 'background-color'); + mainWorkspace + .getThemeManager() + .subscribe(svg, 'workspaceBackgroundColour', 'background-color'); // A null translation will also apply the correct initial scale. mainWorkspace.translate(0, 0); mainWorkspace.addChangeListener( - bumpObjects.bumpIntoBoundsHandler(mainWorkspace)); + bumpObjects.bumpIntoBoundsHandler(mainWorkspace) + ); // The SVG is now fully assembled. common.svgResize(mainWorkspace); @@ -213,19 +199,31 @@ function init(mainWorkspace: WorkspaceSvg) { // Suppress the browser's context menu. browserEvents.conditionalBind( - svg.parentNode as Element, 'contextmenu', null, - function(e: AnyDuringMigration) { - if (!browserEvents.isTargetInput(e)) { - e.preventDefault(); - } - }); + svg.parentNode as Element, + 'contextmenu', + null, + function (e: Event) { + if (!browserEvents.isTargetInput(e)) { + e.preventDefault(); + } + } + ); - const workspaceResizeHandler = - browserEvents.conditionalBind(window, 'resize', null, function() { - mainWorkspace.hideChaff(true); - common.svgResize(mainWorkspace); - bumpObjects.bumpTopObjectsIntoBounds(mainWorkspace); - }); + const workspaceResizeHandler = browserEvents.conditionalBind( + window, + 'resize', + null, + function () { + // Don't hide all the chaff. Leave the dropdown and widget divs open if + // possible. + Tooltip.hide(); + mainWorkspace.hideComponents(true); + dropDownDiv.repositionForWindowResize(); + WidgetDiv.repositionForWindowResize(); + common.svgResize(mainWorkspace); + bumpObjects.bumpTopObjectsIntoBounds(mainWorkspace); + } + ); mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler); bindDocumentEvents(); @@ -253,13 +251,18 @@ function init(mainWorkspace: WorkspaceSvg) { } if (options.moveOptions && options.moveOptions.scrollbars) { - const horizontalScroll = options.moveOptions.scrollbars === true || - !!options.moveOptions.scrollbars.horizontal; - const verticalScroll = options.moveOptions.scrollbars === true || - !!options.moveOptions.scrollbars.vertical; + const horizontalScroll = + options.moveOptions.scrollbars === true || + !!options.moveOptions.scrollbars.horizontal; + const verticalScroll = + options.moveOptions.scrollbars === true || + !!options.moveOptions.scrollbars.vertical; mainWorkspace.scrollbar = new ScrollbarPair( - mainWorkspace, horizontalScroll, verticalScroll, - 'blocklyMainWorkspaceScrollbar'); + mainWorkspace, + horizontalScroll, + verticalScroll, + 'blocklyMainWorkspaceScrollbar' + ); mainWorkspace.scrollbar.resize(); } else { mainWorkspace.setMetrics({x: 0.5, y: 0.5}); @@ -285,8 +288,10 @@ function onKeyDown(e: KeyboardEvent) { return; } - if (browserEvents.isTargetInput(e) || - mainWorkspace.rendered && !mainWorkspace.isVisible()) { + if ( + browserEvents.isTargetInput(e) || + (mainWorkspace.rendered && !mainWorkspace.isVisible()) + ) { // When focused on an HTML text input widget, don't trap any keys. // Ignore keypresses on rendered workspaces that have been explicitly // hidden. @@ -313,9 +318,9 @@ let documentEventsBound = false; */ function bindDocumentEvents() { if (!documentEventsBound) { - browserEvents.conditionalBind(document, 'scroll', null, function() { + browserEvents.conditionalBind(document, 'scroll', null, function () { const workspaces = common.getAllWorkspaces(); - for (let i = 0, workspace; workspace = workspaces[i]; i++) { + for (let i = 0, workspace; (workspace = workspaces[i]); i++) { if (workspace instanceof WorkspaceSvg) { workspace.updateInverseScreenCTM(); } @@ -329,10 +334,14 @@ function bindDocumentEvents() { // Some iPad versions don't fire resize after portrait to landscape change. if (userAgent.IPAD) { browserEvents.conditionalBind( - window, 'orientationchange', document, function() { - // TODO (#397): Fix for multiple Blockly workspaces. - common.svgResize(common.getMainWorkspace() as WorkspaceSvg); - }); + window, + 'orientationchange', + document, + function () { + // TODO (#397): Fix for multiple Blockly workspaces. + common.svgResize(common.getMainWorkspace() as WorkspaceSvg); + } + ); } } documentEventsBound = true; @@ -347,35 +356,41 @@ function bindDocumentEvents() { function loadSounds(pathToMedia: string, workspace: WorkspaceSvg) { const audioMgr = workspace.getAudioManager(); audioMgr.load( - [ - pathToMedia + 'click.mp3', - pathToMedia + 'click.wav', - pathToMedia + 'click.ogg', - ], - 'click'); + [ + pathToMedia + 'click.mp3', + pathToMedia + 'click.wav', + pathToMedia + 'click.ogg', + ], + 'click' + ); audioMgr.load( - [ - pathToMedia + 'disconnect.wav', - pathToMedia + 'disconnect.mp3', - pathToMedia + 'disconnect.ogg', - ], - 'disconnect'); + [ + pathToMedia + 'disconnect.wav', + pathToMedia + 'disconnect.mp3', + pathToMedia + 'disconnect.ogg', + ], + 'disconnect' + ); audioMgr.load( - [ - pathToMedia + 'delete.mp3', - pathToMedia + 'delete.ogg', - pathToMedia + 'delete.wav', - ], - 'delete'); + [ + pathToMedia + 'delete.mp3', + pathToMedia + 'delete.ogg', + pathToMedia + 'delete.wav', + ], + 'delete' + ); // Bind temporary hooks that preload the sounds. - const soundBinds: AnyDuringMigration[] = []; + const soundBinds: browserEvents.Data[] = []; /** * */ function unbindSounds() { while (soundBinds.length) { - browserEvents.unbind(soundBinds.pop()); + const oldSoundBinding = soundBinds.pop(); + if (oldSoundBinding) { + browserEvents.unbind(oldSoundBinding); + } } audioMgr.preload(); } @@ -386,8 +401,22 @@ function loadSounds(pathToMedia: string, workspace: WorkspaceSvg) { // happens on a click, not a drag, so that's not necessary. // Android ignores any sound not loaded as a result of a user action. - soundBinds.push(browserEvents.conditionalBind( - document, 'pointermove', null, unbindSounds, true)); - soundBinds.push(browserEvents.conditionalBind( - document, 'touchstart', null, unbindSounds, true)); + soundBinds.push( + browserEvents.conditionalBind( + document, + 'pointermove', + null, + unbindSounds, + true + ) + ); + soundBinds.push( + browserEvents.conditionalBind( + document, + 'touchstart', + null, + unbindSounds, + true + ) + ); } diff --git a/core/inputs.ts b/core/inputs.ts new file mode 100644 index 000000000..8bd23d790 --- /dev/null +++ b/core/inputs.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Align} from './inputs/align.js'; +import {Input} from './inputs/input.js'; +import {DummyInput} from './inputs/dummy_input.js'; +import {StatementInput} from './inputs/statement_input.js'; +import {ValueInput} from './inputs/value_input.js'; +import {inputTypes} from './inputs/input_types.js'; + +export {Align, Input, DummyInput, StatementInput, ValueInput, inputTypes}; diff --git a/core/inputs/align.ts b/core/inputs/align.ts new file mode 100644 index 000000000..b62846f5d --- /dev/null +++ b/core/inputs/align.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright 2012 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Enum for alignment of inputs. + */ +export enum Align { + LEFT = -1, + CENTRE = 0, + RIGHT = 1, +} diff --git a/core/inputs/dummy_input.ts b/core/inputs/dummy_input.ts new file mode 100644 index 000000000..3521b9df0 --- /dev/null +++ b/core/inputs/dummy_input.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import {Input} from './input.js'; +import {inputTypes} from './input_types.js'; + +/** Represents an input on a block with no connection. */ +export class DummyInput extends Input { + readonly type = inputTypes.DUMMY; + + /** + * @param name Language-neutral identifier which may used to find this input + * again. + * @param block The block containing this input. + */ + constructor(public name: string, block: Block) { + super(name, block); + } +} diff --git a/core/input.ts b/core/inputs/input.ts similarity index 76% rename from core/input.ts rename to core/inputs/input.ts index 09a8a2dc6..557f0d939 100644 --- a/core/input.ts +++ b/core/inputs/input.ts @@ -9,26 +9,23 @@ * * @class */ -import * as goog from '../closure/goog/goog.js'; +import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Input'); // Unused import preserved for side-effects. Remove if unneeded. -import './field_label.js'; +import '../field_label.js'; -import type {Block} from './block.js'; -import type {BlockSvg} from './block_svg.js'; -import type {Connection} from './connection.js'; -import type {Field} from './field.js'; -import * as fieldRegistry from './field_registry.js'; +import type {Block} from '../block.js'; +import type {BlockSvg} from '../block_svg.js'; +import type {Connection} from '../connection.js'; +import type {ConnectionType} from '../connection_type.js'; +import type {Field} from '../field.js'; +import * as fieldRegistry from '../field_registry.js'; +import type {RenderedConnection} from '../rendered_connection.js'; import {inputTypes} from './input_types.js'; -import type {RenderedConnection} from './rendered_connection.js'; - -/** - * Class for an input with an optional field. - */ +/** Class for an input with optional fields. */ export class Input { - private sourceBlock: Block; fieldRow: Field[] = []; /** Alignment of input's fields (left, right or centre). */ align = Align.LEFT; @@ -36,22 +33,16 @@ export class Input { /** Is the input visible? */ private visible = true; + public readonly type: inputTypes = inputTypes.CUSTOM; + + public connection: Connection | null = null; + /** - * @param type The type of the input. * @param name Language-neutral identifier which may used to find this input * again. - * @param block The block containing this input. - * @param connection Optional connection for this input. + * @param sourceBlock The block containing this input. */ - constructor( - public type: number, public name: string, block: Block, - public connection: Connection|null) { - if (type !== inputTypes.DUMMY && !name) { - throw Error( - 'Value inputs and statement inputs must have non-empty name.'); - } - this.sourceBlock = block; - } + constructor(public name: string, private sourceBlock: Block) {} /** * Get the source block for this input. @@ -71,7 +62,7 @@ export class Input { * field again. Should be unique to the host block. * @returns The input being append to (to allow chaining). */ - appendField(field: string|Field, opt_name?: string): Input { + appendField(field: string | Field, opt_name?: string): Input { this.insertFieldAt(this.fieldRow.length, field, opt_name); return this; } @@ -86,8 +77,11 @@ export class Input { * field again. Should be unique to the host block. * @returns The index following the last inserted field. */ - insertFieldAt(index: number, field: string|Field, opt_name?: string): - number { + insertFieldAt( + index: number, + field: string | Field, + opt_name?: string + ): number { if (index < 0 || index > this.fieldRow.length) { throw Error('index ' + index + ' out of bounds.'); } @@ -143,7 +137,7 @@ export class Input { * @throws {Error} if the field is not present and opt_quiet is false. */ removeField(name: string, opt_quiet?: boolean): boolean { - for (let i = 0, field; field = this.fieldRow[i]; i++) { + for (let i = 0, field; (field = this.fieldRow[i]); i++) { if (field.name === name) { field.dispose(); this.fieldRow.splice(i, 1); @@ -188,7 +182,7 @@ export class Input { } this.visible = visible; - for (let y = 0, field; field = this.fieldRow[y]; y++) { + for (let y = 0, field; (field = this.fieldRow[y]); y++) { field.setVisible(visible); } if (this.connection) { @@ -213,7 +207,7 @@ export class Input { * @internal */ markDirty() { - for (let y = 0, field; field = this.fieldRow[y]; y++) { + for (let y = 0, field; (field = this.fieldRow[y]); y++) { field.markDirty(); } } @@ -225,7 +219,7 @@ export class Input { * types are compatible. * @returns The input being modified (to allow chaining). */ - setCheck(check: string|string[]|null): Input { + setCheck(check: string | string[] | null): Input { if (!this.connection) { throw Error('This input does not have a connection.'); } @@ -255,7 +249,7 @@ export class Input { * @param shadow DOM representation of a block or null. * @returns The input being modified (to allow chaining). */ - setShadowDom(shadow: Element|null): Input { + setShadowDom(shadow: Element | null): Input { if (!this.connection) { throw Error('This input does not have a connection.'); } @@ -268,7 +262,7 @@ export class Input { * * @returns Shadow DOM representation of a block or null. */ - getShadowDom(): Element|null { + getShadowDom(): Element | null { if (!this.connection) { throw Error('This input does not have a connection.'); } @@ -278,7 +272,7 @@ export class Input { /** Initialize the fields on this input. */ init() { if (!this.sourceBlock.workspace.rendered) { - return; // Headless blocks don't need fields initialized. + return; // Headless blocks don't need fields initialized. } for (let i = 0; i < this.fieldRow.length; i++) { this.fieldRow[i].init(); @@ -287,23 +281,36 @@ export class Input { /** * Sever all links to this input. - * - * @suppress {checkTypes} */ dispose() { - for (let i = 0, field; field = this.fieldRow[i]; i++) { + for (let i = 0, field; (field = this.fieldRow[i]); i++) { field.dispose(); } if (this.connection) { this.connection.dispose(); } } + + /** + * Constructs a connection based on the type of this input's source block. + * Properly handles constructing headless connections for headless blocks + * and rendered connections for rendered blocks. + * + * @returns a connection of the given type, which is either a headless + * or rendered connection, based on the type of this input's source block. + */ + protected makeConnection(type: ConnectionType): Connection { + return this.sourceBlock.makeConnection_(type); + } } export namespace Input { + // TODO(v11): When this is removed in v11, also re-enable errors on access + // of deprecated things (in build_tasks.js). /** * Enum for alignment of inputs. * + * @deprecated Use Blockly.inputs.Align. To be removed in v11. */ export enum Align { LEFT = -1, @@ -312,5 +319,9 @@ export namespace Input { } } +/** @deprecated Use Blockly.inputs.Align. To be removed in v11. */ +/** @suppress {deprecated} */ export type Align = Input.Align; +/** @deprecated Use Blockly.inputs.Align. To be removed in v11. */ +/** @suppress {deprecated} */ export const Align = Input.Align; diff --git a/core/input_types.ts b/core/inputs/input_types.ts similarity index 70% rename from core/input_types.ts rename to core/inputs/input_types.ts index 8f50346ad..ff768deb2 100644 --- a/core/input_types.ts +++ b/core/inputs/input_types.ts @@ -4,11 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as goog from '../closure/goog/goog.js'; +import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.inputTypes'); -import {ConnectionType} from './connection_type.js'; - +import {ConnectionType} from '../connection_type.js'; /** * Enum for the type of a connection or input. @@ -19,5 +18,7 @@ export enum inputTypes { // A down-facing block stack. E.g. 'if-do' or 'else'. STATEMENT = ConnectionType.NEXT_STATEMENT, // A dummy input. Used to add field(s) with no input. - DUMMY = 5 + DUMMY = 5, + // An unknown type of input defined by an external developer. + CUSTOM = 6, } diff --git a/core/inputs/statement_input.ts b/core/inputs/statement_input.ts new file mode 100644 index 000000000..ee0663459 --- /dev/null +++ b/core/inputs/statement_input.ts @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import type {Connection} from '../connection.js'; +import {ConnectionType} from '../connection_type.js'; +import {Input} from './input.js'; +import {inputTypes} from './input_types.js'; + +/** Represents an input on a block with a statement connection. */ +export class StatementInput extends Input { + readonly type = inputTypes.STATEMENT; + + public connection: Connection; + + /** + * @param name Language-neutral identifier which may used to find this input + * again. + * @param block The block containing this input. + */ + constructor(public name: string, block: Block) { + // Errors are maintained for people not using typescript. + if (!name) throw new Error('Statement inputs must have a non-empty name'); + + super(name, block); + this.connection = this.makeConnection(ConnectionType.NEXT_STATEMENT); + } +} diff --git a/core/inputs/value_input.ts b/core/inputs/value_input.ts new file mode 100644 index 000000000..ef88ac58e --- /dev/null +++ b/core/inputs/value_input.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import {ConnectionType} from '../connection_type.js'; +import {Input} from './input.js'; +import {inputTypes} from './input_types.js'; + +/** Represents an input on a block with a value connection. */ +export class ValueInput extends Input { + readonly type = inputTypes.VALUE; + + /** + * @param name Language-neutral identifier which may used to find this input + * again. + * @param block The block containing this input. + */ + constructor(public name: string, block: Block) { + // Errors are maintained for people not using typescript. + if (!name) throw new Error('Value inputs must have a non-empty name'); + super(name, block); + this.connection = this.makeConnection(ConnectionType.INPUT_VALUE); + } +} diff --git a/core/insertion_marker_manager.ts b/core/insertion_marker_manager.ts index 54859e3d2..016d76b90 100644 --- a/core/insertion_marker_manager.ts +++ b/core/insertion_marker_manager.ts @@ -12,6 +12,7 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.InsertionMarkerManager'); +import {finishQueuedRenders} from './render_management.js'; import * as blockAnimations from './block_animations.js'; import type {BlockSvg} from './block_svg.js'; import * as common from './common.js'; @@ -25,7 +26,6 @@ import type {RenderedConnection} from './rendered_connection.js'; import type {Coordinate} from './utils/coordinate.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** Represents a nearby valid connection. */ interface CandidateConnection { /** @@ -46,10 +46,11 @@ interface CandidateConnection { * An error message to throw if the block created by createMarkerBlock_ is * missing any components. */ -const DUPLICATE_BLOCK_ERROR = 'The insertion marker ' + - 'manager tried to create a marker but the result is missing %1. If ' + - 'you are using a mutator, make sure your domToMutation method is ' + - 'properly defined.'; +const DUPLICATE_BLOCK_ERROR = + 'The insertion marker ' + + 'manager tried to create a marker but the result is missing %1. If ' + + 'you are using a mutator, make sure your domToMutation method is ' + + 'properly defined.'; /** * Class that controls updates to connections during drags. It is primarily @@ -74,14 +75,14 @@ export class InsertionMarkerManager { * first block. * Set in initAvailableConnections, if at all. */ - private lastOnStack: RenderedConnection|null = null; + private lastOnStack: RenderedConnection | null = null; /** * The insertion marker corresponding to the last block in the stack, if * that's not the same as the first block in the stack. * Set in initAvailableConnections, if at all */ - private lastMarker: BlockSvg|null = null; + private lastMarker: BlockSvg | null = null; /** * The insertion marker that shows up between blocks to show where a block @@ -93,7 +94,7 @@ export class InsertionMarkerManager { * Information about the connection that would be made if the dragging block * were released immediately. Updated on every mouse move. */ - private activeCandidate: CandidateConnection|null = null; + private activeCandidate: CandidateConnection | null = null; /** * Whether the block would be deleted if it were dropped immediately. @@ -107,13 +108,13 @@ export class InsertionMarkerManager { * Connection on the insertion marker block that corresponds to * the active candidate's local connection on the currently dragged block. */ - private markerConnection: RenderedConnection|null = null; + private markerConnection: RenderedConnection | null = null; /** The block that currently has an input being highlighted, or null. */ - private highlightedBlock: BlockSvg|null = null; + private highlightedBlock: BlockSvg | null = null; /** The block being faded to indicate replacement, or null. */ - private fadedBlock: BlockSvg|null = null; + private fadedBlock: BlockSvg | null = null; /** * The connections on the dragging blocks that are available to connect to @@ -134,8 +135,9 @@ export class InsertionMarkerManager { this.availableConnections = this.initAvailableConnections(); if (this.lastOnStack) { - this.lastMarker = - this.createMarkerBlock(this.lastOnStack.getSourceBlock()); + this.lastMarker = this.createMarkerBlock( + this.lastOnStack.getSourceBlock() + ); } } @@ -188,9 +190,9 @@ export class InsertionMarkerManager { const inferiorConnection = local.isSuperior() ? closest : local; const rootBlock = this.topBlock.getRootBlock(); - // bringToFront is incredibly expensive. Delay by at least a frame. - requestAnimationFrame(() => { + finishQueuedRenders().then(() => { blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock()); + // bringToFront is incredibly expensive. Delay until the next frame. setTimeout(() => { rootBlock.bringToFront(); }, 0); @@ -205,13 +207,13 @@ export class InsertionMarkerManager { * @param dragTarget The drag target that the block is currently over. * @internal */ - update(dxy: Coordinate, dragTarget: IDragTarget|null) { + update(dxy: Coordinate, dragTarget: IDragTarget | null) { const newCandidate = this.getCandidate(dxy); this.wouldDeleteBlock = this.shouldDelete(!!newCandidate, dragTarget); const shouldUpdate = - this.wouldDeleteBlock || this.shouldUpdatePreviews(newCandidate, dxy); + this.wouldDeleteBlock || this.shouldUpdatePreviews(newCandidate, dxy); if (shouldUpdate) { // Don't fire events for insertion marker creation or movement. @@ -253,7 +255,7 @@ export class InsertionMarkerManager { for (let i = 0; i < sourceBlock.inputList.length; i++) { const sourceInput = sourceBlock.inputList[i]; if (sourceInput.name === constants.COLLAPSED_INPUT_NAME) { - continue; // Ignore the collapsed input. + continue; // Ignore the collapsed input. } const resultInput = result.inputList[i]; if (!resultInput) { @@ -308,7 +310,9 @@ export class InsertionMarkerManager { * @returns Whether the preview should be updated. */ private shouldUpdatePreviews( - newCandidate: CandidateConnection|null, dxy: Coordinate): boolean { + newCandidate: CandidateConnection | null, + dxy: Coordinate + ): boolean { // Only need to update if we were showing a preview before. if (!newCandidate) return !!this.activeCandidate; @@ -318,8 +322,10 @@ export class InsertionMarkerManager { // We're already showing an insertion marker. // Decide whether the new connection has higher priority. const {local: activeLocal, closest: activeClosest} = this.activeCandidate; - if (activeClosest === newCandidate.closest && - activeLocal === newCandidate.local) { + if ( + activeClosest === newCandidate.closest && + activeLocal === newCandidate.local + ) { // The connection was the same as the current connection. return false; } @@ -329,7 +335,8 @@ export class InsertionMarkerManager { const curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff); // Slightly prefer the existing preview over a new preview. return ( - newCandidate.radius < curDistance - config.currentConnectionPreference); + newCandidate.radius < curDistance - config.currentConnectionPreference + ); } /** @@ -340,7 +347,7 @@ export class InsertionMarkerManager { * @returns An object containing a local connection, a closest connection, and * a radius. */ - private getCandidate(dxy: Coordinate): CandidateConnection|null { + private getCandidate(dxy: Coordinate): CandidateConnection | null { // It's possible that a block has added or removed connections during a // drag, (e.g. in a drag/move event handler), so let's update the available // connections. Note that this will be called on every move while dragging, @@ -382,8 +389,9 @@ export class InsertionMarkerManager { // insertion marker is created, which could cause the connection became out // of range. By increasing radiusConnection when a connection already // exists, we never "lose" the connection from the offset. - return this.activeCandidate ? config.connectingSnapRadius : - config.snapRadius; + return this.activeCandidate + ? config.connectingSnapRadius + : config.snapRadius; } /** @@ -394,15 +402,21 @@ export class InsertionMarkerManager { * @param dragTarget The drag target that the block is currently over. * @returns Whether dropping the block immediately would delete the block. */ - private shouldDelete(newCandidate: boolean, dragTarget: IDragTarget|null): - boolean { + private shouldDelete( + newCandidate: boolean, + dragTarget: IDragTarget | null + ): boolean { if (dragTarget) { const componentManager = this.workspace.getComponentManager(); const isDeleteArea = componentManager.hasCapability( - dragTarget.id, ComponentManager.Capability.DELETE_AREA); + dragTarget.id, + ComponentManager.Capability.DELETE_AREA + ); if (isDeleteArea) { - return (dragTarget as IDeleteArea) - .wouldDelete(this.topBlock, newCandidate); + return (dragTarget as IDeleteArea).wouldDelete( + this.topBlock, + newCandidate + ); } } return false; @@ -416,16 +430,18 @@ export class InsertionMarkerManager { * @param newCandidate A new candidate connection that may replace the current * best candidate. */ - private maybeShowPreview(newCandidate: CandidateConnection|null) { - if (this.wouldDeleteBlock) return; // Nope, don't add a marker. - if (!newCandidate) return; // Nothing to connect to. + private maybeShowPreview(newCandidate: CandidateConnection | null) { + if (this.wouldDeleteBlock) return; // Nope, don't add a marker. + if (!newCandidate) return; // Nothing to connect to. const closest = newCandidate.closest; // Something went wrong and we're trying to connect to an invalid // connection. - if (closest === this.activeCandidate?.closest || - closest.getSourceBlock().isInsertionMarker()) { + if ( + closest === this.activeCandidate?.closest || + closest.getSourceBlock().isInsertionMarker() + ) { console.log('Trying to connect to an insertion marker'); return; } @@ -444,7 +460,10 @@ export class InsertionMarkerManager { private showPreview(activeCandidate: CandidateConnection) { const renderer = this.workspace.getRenderer(); const method = renderer.getConnectionPreviewMethod( - activeCandidate.closest, activeCandidate.local, this.topBlock); + activeCandidate.closest, + activeCandidate.local, + this.topBlock + ); switch (method) { case InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE: @@ -473,7 +492,7 @@ export class InsertionMarkerManager { * @param newCandidate A new candidate connection that may replace the current * best candidate. */ - private maybeHidePreview(newCandidate: CandidateConnection|null) { + private maybeHidePreview(newCandidate: CandidateConnection | null) { // If there's no new preview, remove the old one but don't bother deleting // it. We might need it later, and this saves disposing of it and recreating // it. @@ -482,14 +501,14 @@ export class InsertionMarkerManager { } else { if (this.activeCandidate) { const closestChanged = - this.activeCandidate.closest !== newCandidate.closest; + this.activeCandidate.closest !== newCandidate.closest; const localChanged = this.activeCandidate.local !== newCandidate.local; // If there's a new preview and there was a preview before, and either // connection has changed, remove the old preview. // Also hide if we had a preview before but now we're going to delete // instead. - if ((closestChanged || localChanged || this.wouldDeleteBlock)) { + if (closestChanged || localChanged || this.wouldDeleteBlock) { this.hidePreview(); } } @@ -506,8 +525,11 @@ export class InsertionMarkerManager { */ private hidePreview() { const closest = this.activeCandidate?.closest; - if (closest && closest.targetBlock() && - this.workspace.getRenderer().shouldHighlightConnection(closest)) { + if ( + closest && + closest.targetBlock() && + this.workspace.getRenderer().shouldHighlightConnection(closest) + ) { closest.unhighlight(); } this.hideReplacementFade(); @@ -529,13 +551,16 @@ export class InsertionMarkerManager { let insertionMarker = isLastInStack ? this.lastMarker : this.firstMarker; if (!insertionMarker) { throw new Error( - 'Cannot show the insertion marker because there is no insertion ' + - 'marker block'); + 'Cannot show the insertion marker because there is no insertion ' + + 'marker block' + ); } let imConn; try { - imConn = - insertionMarker.getMatchingConnection(local.getSourceBlock(), local); + imConn = insertionMarker.getMatchingConnection( + local.getSourceBlock(), + local + ); } catch (e) { // It's possible that the number of connections on the local block has // changed since the insertion marker was originally created. Let's @@ -545,8 +570,9 @@ export class InsertionMarkerManager { // might be too slow, so we only do it if necessary. if (isLastInStack && this.lastOnStack) { this.disposeInsertionMarker(this.lastMarker); - this.lastMarker = - this.createMarkerBlock(this.lastOnStack.getSourceBlock()); + this.lastMarker = this.createMarkerBlock( + this.lastOnStack.getSourceBlock() + ); insertionMarker = this.lastMarker; } else { this.disposeInsertionMarker(this.firstMarker); @@ -556,23 +582,28 @@ export class InsertionMarkerManager { if (!insertionMarker) { throw new Error( - 'Cannot show the insertion marker because there is no insertion ' + - 'marker block'); + 'Cannot show the insertion marker because there is no insertion ' + + 'marker block' + ); } - imConn = - insertionMarker.getMatchingConnection(local.getSourceBlock(), local); + imConn = insertionMarker.getMatchingConnection( + local.getSourceBlock(), + local + ); } if (!imConn) { throw new Error( - 'Cannot show the insertion marker because there is no ' + - 'associated connection'); + 'Cannot show the insertion marker because there is no ' + + 'associated connection' + ); } if (imConn === this.markerConnection) { throw new Error( - 'Made it to showInsertionMarker_ even though the marker isn\'t ' + - 'changing'); + "Made it to showInsertionMarker_ even though the marker isn't " + + 'changing' + ); } // Render disconnected from everything else so that we have a valid @@ -616,8 +647,9 @@ export class InsertionMarkerManager { if (markerConn.targetConnection) { throw Error( - 'markerConnection still connected at the end of ' + - 'disconnectInsertionMarker'); + 'markerConnection still connected at the end of ' + + 'disconnectInsertionMarker' + ); } this.markerConnection = null; @@ -645,11 +677,14 @@ export class InsertionMarkerManager { if (!this.activeCandidate) { throw new Error( - 'Cannot hide the insertion marker outline because ' + - 'there is no active candidate'); + 'Cannot hide the insertion marker outline because ' + + 'there is no active candidate' + ); } this.highlightedBlock.highlightShapeForInput( - this.activeCandidate.closest, false); + this.activeCandidate.closest, + false + ); this.highlightedBlock = null; } @@ -664,8 +699,9 @@ export class InsertionMarkerManager { this.fadedBlock = activeCandidate.closest.targetBlock(); if (!this.fadedBlock) { throw new Error( - 'Cannot show the replacement fade because the ' + - 'closest connection does not have a target block'); + 'Cannot show the replacement fade because the ' + + 'closest connection does not have a target block' + ); } this.fadedBlock.fadeForReplacement(true); } @@ -701,7 +737,7 @@ export class InsertionMarkerManager { /** * Safely disposes of an insertion marker. */ - private disposeInsertionMarker(marker: BlockSvg|null) { + private disposeInsertionMarker(marker: BlockSvg | null) { if (marker) { eventUtils.disable(); try { diff --git a/core/interfaces/i_ast_node_location.ts b/core/interfaces/i_ast_node_location.ts index 36c5e7eec..72f3ce2c5 100644 --- a/core/interfaces/i_ast_node_location.ts +++ b/core/interfaces/i_ast_node_location.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IASTNodeLocation'); - /** * An AST node location interface. */ diff --git a/core/interfaces/i_ast_node_location_svg.ts b/core/interfaces/i_ast_node_location_svg.ts index 0617b5d8e..263c9d54d 100644 --- a/core/interfaces/i_ast_node_location_svg.ts +++ b/core/interfaces/i_ast_node_location_svg.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.IASTNodeLocationSvg'); import type {IASTNodeLocation} from './i_ast_node_location.js'; - /** * An AST node location SVG interface. */ @@ -19,12 +18,12 @@ export interface IASTNodeLocationSvg extends IASTNodeLocation { * * @param markerSvg The SVG root of the marker to be added to the SVG group. */ - setMarkerSvg(markerSvg: SVGElement|null): void; + setMarkerSvg(markerSvg: SVGElement | null): void; /** * Add the cursor SVG to this node's SVG group. * * @param cursorSvg The SVG root of the cursor to be added to the SVG group. */ - setCursorSvg(cursorSvg: SVGElement|null): void; + setCursorSvg(cursorSvg: SVGElement | null): void; } diff --git a/core/interfaces/i_ast_node_location_with_block.ts b/core/interfaces/i_ast_node_location_with_block.ts index 56e12b3ee..a4a7a8880 100644 --- a/core/interfaces/i_ast_node_location_with_block.ts +++ b/core/interfaces/i_ast_node_location_with_block.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.IASTNodeLocationWithBlock'); import type {IASTNodeLocation} from './i_ast_node_location.js'; import type {Block} from '../block.js'; - /** * An AST node location that has an associated block. */ @@ -20,5 +19,5 @@ export interface IASTNodeLocationWithBlock extends IASTNodeLocation { * * @returns The source block. */ - getSourceBlock(): Block|null; + getSourceBlock(): Block | null; } diff --git a/core/interfaces/i_autohideable.ts b/core/interfaces/i_autohideable.ts index d7abcad1e..f5e351010 100644 --- a/core/interfaces/i_autohideable.ts +++ b/core/interfaces/i_autohideable.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.IAutoHideable'); import type {IComponent} from './i_component.js'; - /** * Interface for a component that can be automatically hidden. */ diff --git a/core/interfaces/i_block_dragger.ts b/core/interfaces/i_block_dragger.ts index d0418224f..ea6969686 100644 --- a/core/interfaces/i_block_dragger.ts +++ b/core/interfaces/i_block_dragger.ts @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.IBlockDragger'); */ export interface IBlockDragger { /** - * Start dragging a block. This includes moving it to the drag surface. + * Start dragging a block. * * @param currentDragDeltaXY How far the pointer has moved from the position * at mouse down, in pixel units. diff --git a/core/interfaces/i_bounded_element.ts b/core/interfaces/i_bounded_element.ts index 870d7b1e8..f6535b5bf 100644 --- a/core/interfaces/i_bounded_element.ts +++ b/core/interfaces/i_bounded_element.ts @@ -25,6 +25,7 @@ export interface IBoundedElement { * * @param dx Horizontal offset in workspace units. * @param dy Vertical offset in workspace units. + * @param reason Why is this move happening? 'user', 'bump', 'snap'... */ - moveBy(dx: number, dy: number): void; + moveBy(dx: number, dy: number, reason?: string[]): void; } diff --git a/core/interfaces/i_bubble.ts b/core/interfaces/i_bubble.ts index d8a1b686a..db0d4d86c 100644 --- a/core/interfaces/i_bubble.ts +++ b/core/interfaces/i_bubble.ts @@ -6,13 +6,11 @@ import * as goog from '../../closure/goog/goog.js'; import type {Coordinate} from '../utils/coordinate.js'; -import type {BlockDragSurfaceSvg} from '../block_drag_surface.js'; goog.declareModuleId('Blockly.IBubble'); import type {IContextMenu} from './i_contextmenu.js'; import type {IDraggable} from './i_draggable.js'; - /** * A bubble interface. */ @@ -32,15 +30,6 @@ export interface IBubble extends IDraggable, IContextMenu { */ getSvgRoot(): SVGElement; - /** - * Set whether auto-layout of this bubble is enabled. The first time a bubble - * is shown it positions itself to not cover any blocks. Once a user has - * dragged it to reposition, it renders where the user put it. - * - * @param enable True if auto-layout should be enabled, false otherwise. - */ - setAutoLayout(enable: boolean): void; - /** * Sets whether or not this bubble is being dragged. * @@ -49,15 +38,11 @@ export interface IBubble extends IDraggable, IContextMenu { setDragging(dragging: boolean): void; /** - * Move this bubble during a drag, taking into account whether or not there is - * a drag surface. + * Move this bubble during a drag. * - * @param dragSurface The surface that carries rendered items during a drag, - * or null if no drag surface is in use. * @param newLoc The location to translate to, in workspace coordinates. */ - moveDuringDrag(dragSurface: BlockDragSurfaceSvg|null, newLoc: Coordinate): - void; + moveDuringDrag(newLoc: Coordinate): void; /** * Move the bubble to the specified location in workspace coordinates. diff --git a/core/interfaces/i_collapsible_toolbox_item.ts b/core/interfaces/i_collapsible_toolbox_item.ts index 8e499e276..bc3a74b4a 100644 --- a/core/interfaces/i_collapsible_toolbox_item.ts +++ b/core/interfaces/i_collapsible_toolbox_item.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.ICollapsibleToolboxItem'); import type {ISelectableToolboxItem} from './i_selectable_toolbox_item.js'; import type {IToolboxItem} from './i_toolbox_item.js'; - /** * Interface for an item in the toolbox that can be collapsed. */ diff --git a/core/interfaces/i_component.ts b/core/interfaces/i_component.ts index 3a55702fd..8f3e3aede 100644 --- a/core/interfaces/i_component.ts +++ b/core/interfaces/i_component.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IComponent'); - /** * The interface for a workspace component that can be registered with the * ComponentManager. diff --git a/core/interfaces/i_connection_checker.ts b/core/interfaces/i_connection_checker.ts index 4384511cc..7264895df 100644 --- a/core/interfaces/i_connection_checker.ts +++ b/core/interfaces/i_connection_checker.ts @@ -25,8 +25,11 @@ export interface IConnectionChecker { * @returns Whether the connection is legal. */ canConnect( - a: Connection|null, b: Connection|null, isDragging: boolean, - opt_distance?: number): boolean; + a: Connection | null, + b: Connection | null, + isDragging: boolean, + opt_distance?: number + ): boolean; /** * Checks whether the current connection can connect with the target @@ -41,8 +44,11 @@ export interface IConnectionChecker { * otherwise. */ canConnectWithReason( - a: Connection|null, b: Connection|null, isDragging: boolean, - opt_distance?: number): number; + a: Connection | null, + b: Connection | null, + isDragging: boolean, + opt_distance?: number + ): number; /** * Helper method that translates a connection error code into a string. @@ -52,8 +58,11 @@ export interface IConnectionChecker { * @param b The second of the two connections being checked. * @returns A developer-readable error string. */ - getErrorMessage(errorCode: number, a: Connection|null, b: Connection|null): - string; + getErrorMessage( + errorCode: number, + a: Connection | null, + b: Connection | null + ): string; /** * Check that connecting the given connections is safe, meaning that it would @@ -63,7 +72,7 @@ export interface IConnectionChecker { * @param b The second of the connections to check. * @returns An enum with the reason this connection is safe or unsafe. */ - doSafetyChecks(a: Connection|null, b: Connection|null): number; + doSafetyChecks(a: Connection | null, b: Connection | null): number; /** * Check whether this connection is compatible with another connection with @@ -84,6 +93,9 @@ export interface IConnectionChecker { * @param distance The maximum allowable distance between connections. * @returns True if the connection is allowed during a drag. */ - doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number): - boolean; + doDragChecks( + a: RenderedConnection, + b: RenderedConnection, + distance: number + ): boolean; } diff --git a/core/interfaces/i_contextmenu.ts b/core/interfaces/i_contextmenu.ts index f205e9e2e..2ac29052b 100644 --- a/core/interfaces/i_contextmenu.ts +++ b/core/interfaces/i_contextmenu.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IContextMenu'); - export interface IContextMenu { /** * Show the context menu for this object. diff --git a/core/interfaces/i_copyable.ts b/core/interfaces/i_copyable.ts index cd3fe3678..d62d9ef83 100644 --- a/core/interfaces/i_copyable.ts +++ b/core/interfaces/i_copyable.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.ICopyable'); import type {WorkspaceSvg} from '../workspace_svg.js'; import type {ISelectable} from './i_selectable.js'; - export interface ICopyable extends ISelectable { /** * Encode for copying. @@ -18,14 +17,14 @@ export interface ICopyable extends ISelectable { * @returns Copy metadata. * @internal */ - toCopyData(): CopyData|null; + toCopyData(): CopyData | null; } export namespace ICopyable { export interface CopyData { - saveInfo: Object|Element; + saveInfo: Object | Element; source: WorkspaceSvg; - typeCounts: {[key: string]: number}|null; + typeCounts: {[key: string]: number} | null; } } diff --git a/core/interfaces/i_deletable.ts b/core/interfaces/i_deletable.ts index 3593dd878..0a8c12deb 100644 --- a/core/interfaces/i_deletable.ts +++ b/core/interfaces/i_deletable.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IDeletable'); - /** * The interface for an object that can be deleted. */ diff --git a/core/interfaces/i_delete_area.ts b/core/interfaces/i_delete_area.ts index 9a72ec779..2dd3e3111 100644 --- a/core/interfaces/i_delete_area.ts +++ b/core/interfaces/i_delete_area.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.IDeleteArea'); import type {IDragTarget} from './i_drag_target.js'; import type {IDraggable} from './i_draggable.js'; - /** * Interface for a component that can delete a block or bubble that is dropped * on top of it. diff --git a/core/interfaces/i_drag_target.ts b/core/interfaces/i_drag_target.ts index 5d2ba2bb8..813036dd4 100644 --- a/core/interfaces/i_drag_target.ts +++ b/core/interfaces/i_drag_target.ts @@ -13,7 +13,6 @@ goog.declareModuleId('Blockly.IDragTarget'); import type {IComponent} from './i_component.js'; - /** * Interface for a component with custom behaviour when a block or bubble is * dragged over or dropped on top of it. @@ -26,7 +25,7 @@ export interface IDragTarget extends IComponent { * @returns The component's bounding box. Null if drag target area should be * ignored. */ - getClientRect(): Rect|null; + getClientRect(): Rect | null; /** * Handles when a cursor with a block or bubble enters this drag target. diff --git a/core/interfaces/i_draggable.ts b/core/interfaces/i_draggable.ts index 49e6976ca..014899e5a 100644 --- a/core/interfaces/i_draggable.ts +++ b/core/interfaces/i_draggable.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.IDraggable'); import type {IDeletable} from './i_deletable.js'; - /** * The interface for an object that can be dragged. */ diff --git a/core/interfaces/i_flyout.ts b/core/interfaces/i_flyout.ts index 5f8503161..540f9ad69 100644 --- a/core/interfaces/i_flyout.ts +++ b/core/interfaces/i_flyout.ts @@ -14,7 +14,6 @@ import type {FlyoutDefinition} from '../utils/toolbox.js'; import type {Svg} from '../utils/svg.js'; import type {IRegistrable} from './i_registrable.js'; - /** * Interface for a flyout. */ @@ -26,7 +25,7 @@ export interface IFlyout extends IRegistrable { RTL: boolean; /** The target workspace */ - targetWorkspace: WorkspaceSvg|null; + targetWorkspace: WorkspaceSvg | null; /** Margin around the edges of the blocks in the flyout. */ readonly MARGIN: number; @@ -46,7 +45,9 @@ export interface IFlyout extends IRegistrable { * or . * @returns The flyout's SVG group. */ - createDom(tagName: string|Svg|Svg): SVGElement; + createDom( + tagName: string | Svg | Svg + ): SVGElement; /** * Initializes the flyout. @@ -115,7 +116,7 @@ export interface IFlyout extends IRegistrable { * of Nodes, a NodeList, a toolbox definition, or a string with the name * of the dynamic category. */ - show(flyoutDef: FlyoutDefinition|string): void; + show(flyoutDef: FlyoutDefinition | string): void; /** * Create a copy of this block on the workspace. diff --git a/core/interfaces/i_has_bubble.ts b/core/interfaces/i_has_bubble.ts new file mode 100644 index 000000000..a2ba6093a --- /dev/null +++ b/core/interfaces/i_has_bubble.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface IHasBubble { + /** @returns True if the bubble is currently open, false otherwise. */ + bubbleIsVisible(): boolean; + + /** Sets whether the bubble is open or not. */ + setBubbleVisible(visible: boolean): void; +} + +/** Type guard that checks whether the given object is a IHasBubble. */ +export function hasBubble(obj: any): obj is IHasBubble { + return ( + obj.bubbleIsVisible !== undefined && obj.setBubbleVisible !== undefined + ); +} diff --git a/core/interfaces/i_icon.ts b/core/interfaces/i_icon.ts new file mode 100644 index 000000000..8c1477997 --- /dev/null +++ b/core/interfaces/i_icon.ts @@ -0,0 +1,105 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {IconType} from '../icons/icon_types.js'; +import type {Coordinate} from '../utils/coordinate.js'; +import type {Size} from '../utils/size.js'; + +export interface IIcon { + /** + * @returns the IconType representing the type of the icon. This value should + * also be used to register the icon via `Blockly.icons.registry.register`. + */ + getType(): IconType; + + /** + * Creates the SVG elements for the icon that will live on the block. + * + * @param pointerdownListener An event listener that must be attached to the + * root SVG element by the implementation of `initView`. Used by Blockly's + * gesture system to properly handle clicks and drags. + */ + initView(pointerdownListener: (e: PointerEvent) => void): void; + + /** + * Disposes of any elements of the icon. + * + * @remarks + * + * In particular, if this icon is currently showing a bubble, this should be + * used to hide it. + */ + dispose(): void; + + /** + * @returns the "weight" of the icon, which determines the static order which + * icons should be rendered in. More positive numbers are rendered farther + * toward the end of the block. + */ + getWeight(): number; + + /** @returns The dimensions of the icon for use in rendering. */ + getSize(): Size; + + /** Updates the icon's color when the block's color changes.. */ + applyColour(): void; + + /** Hides the icon when it is part of an insertion marker. */ + hideForInsertionMarker(): void; + + /** Updates the icon's editability when the block's editability changes. */ + updateEditable(): void; + + /** + * Updates the icon's collapsed-ness/view when the block's collapsed-ness + * changes. + */ + updateCollapsed(): void; + + /** + * @returns Whether this icon is shown when the block is collapsed. Used + * to allow renderers to account for padding. + */ + isShownWhenCollapsed(): boolean; + + /** + * Notifies the icon where it is relative to its block's top-start, in + * workspace units. + */ + setOffsetInBlock(offset: Coordinate): void; + + /** + * Notifies the icon that it has changed locations. + * + * @param blockOrigin The location of this icon's block's top-start corner + * in workspace coordinates. + */ + onLocationChange(blockOrigin: Coordinate): void; + + /** + * Notifies the icon that it has been clicked. + */ + onClick(): void; +} + +/** Type guard that checks whether the given object is an IIcon. */ +export function isIcon(obj: any): obj is IIcon { + return ( + obj.getType !== undefined && + obj.initView !== undefined && + obj.dispose !== undefined && + obj.getWeight !== undefined && + obj.getSize !== undefined && + obj.applyColour !== undefined && + obj.hideForInsertionMarker !== undefined && + obj.updateEditable !== undefined && + obj.updateCollapsed !== undefined && + obj.isShownWhenCollapsed !== undefined && + obj.setOffsetInBlock !== undefined && + obj.onLocationChange !== undefined && + obj.onClick !== undefined + ); +} diff --git a/core/interfaces/i_legacy_procedure_blocks.ts b/core/interfaces/i_legacy_procedure_blocks.ts index 410048e4e..cab4dc93f 100644 --- a/core/interfaces/i_legacy_procedure_blocks.ts +++ b/core/interfaces/i_legacy_procedure_blocks.ts @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - /** * Legacy means of representing a procedure signature. The elements are * respectively: name, parameter names, and whether it has a return value. @@ -24,12 +23,13 @@ export interface ProcedureBlock { /** @internal */ export interface LegacyProcedureDefBlock { - getProcedureDef: () => ProcedureTuple + getProcedureDef: () => ProcedureTuple; } /** @internal */ -export function isLegacyProcedureDefBlock(block: Object): - block is LegacyProcedureDefBlock { +export function isLegacyProcedureDefBlock( + block: Object +): block is LegacyProcedureDefBlock { return (block as any).getProcedureDef !== undefined; } @@ -40,8 +40,11 @@ export interface LegacyProcedureCallBlock { } /** @internal */ -export function isLegacyProcedureCallBlock(block: Object): - block is LegacyProcedureCallBlock { - return (block as any).getProcedureCall !== undefined && - (block as any).renameProcedure !== undefined; +export function isLegacyProcedureCallBlock( + block: Object +): block is LegacyProcedureCallBlock { + return ( + (block as any).getProcedureCall !== undefined && + (block as any).renameProcedure !== undefined + ); } diff --git a/core/interfaces/i_metrics_manager.ts b/core/interfaces/i_metrics_manager.ts index a3d28d0fa..a06e0ca4a 100644 --- a/core/interfaces/i_metrics_manager.ts +++ b/core/interfaces/i_metrics_manager.ts @@ -5,7 +5,12 @@ */ import * as goog from '../../closure/goog/goog.js'; -import type {ContainerRegion, ToolboxMetrics, AbsoluteMetrics, UiMetrics} from '../metrics_manager.js'; +import type { + ContainerRegion, + ToolboxMetrics, + AbsoluteMetrics, + UiMetrics, +} from '../metrics_manager.js'; import type {Size} from '../utils/size.js'; import type {Metrics} from '../utils/metrics.js'; goog.declareModuleId('Blockly.IMetricsManager'); @@ -36,8 +41,10 @@ export interface IMetricsManager { * @returns The metrics for the scroll container */ getScrollMetrics( - opt_getWorkspaceCoordinates?: boolean, opt_viewMetrics?: ContainerRegion, - opt_contentMetrics?: ContainerRegion): ContainerRegion; + opt_getWorkspaceCoordinates?: boolean, + opt_viewMetrics?: ContainerRegion, + opt_contentMetrics?: ContainerRegion + ): ContainerRegion; /** * Gets the width and the height of the flyout in pixel diff --git a/core/interfaces/i_movable.ts b/core/interfaces/i_movable.ts index 2c249273a..8ef11462c 100644 --- a/core/interfaces/i_movable.ts +++ b/core/interfaces/i_movable.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IMovable'); - /** * The interface for an object that is movable. */ diff --git a/core/interfaces/i_observable.ts b/core/interfaces/i_observable.ts index c6b02c9e6..96a2a0bc4 100644 --- a/core/interfaces/i_observable.ts +++ b/core/interfaces/i_observable.ts @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - /** * An object that fires events optionally. * diff --git a/core/interfaces/i_parameter_model.ts b/core/interfaces/i_parameter_model.ts index c712fdf22..1b0239703 100644 --- a/core/interfaces/i_parameter_model.ts +++ b/core/interfaces/i_parameter_model.ts @@ -6,7 +6,6 @@ import {IProcedureModel} from './i_procedure_model'; - /** * A data model for a procedure. */ diff --git a/core/interfaces/i_positionable.ts b/core/interfaces/i_positionable.ts index 52ad96f90..0485b51bf 100644 --- a/core/interfaces/i_positionable.ts +++ b/core/interfaces/i_positionable.ts @@ -11,7 +11,6 @@ goog.declareModuleId('Blockly.IPositionable'); import type {IComponent} from './i_component.js'; - /** * Interface for a component that is positioned on top of the workspace. */ @@ -31,5 +30,5 @@ export interface IPositionable extends IComponent { * @returns The UI elements's bounding box. Null if bounding box should be * ignored by other UI elements. */ - getBoundingRectangle(): Rect|null; + getBoundingRectangle(): Rect | null; } diff --git a/core/interfaces/i_procedure_block.ts b/core/interfaces/i_procedure_block.ts index 133441013..e24e2ced3 100644 --- a/core/interfaces/i_procedure_block.ts +++ b/core/interfaces/i_procedure_block.ts @@ -9,7 +9,6 @@ import {IProcedureModel} from './i_procedure_model.js'; import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.procedures.IProcedureBlock'); - /** The interface for a block which models a procedure. */ export interface IProcedureBlock { getProcedureModel(): IProcedureModel; @@ -18,9 +17,12 @@ export interface IProcedureBlock { } /** A type guard which checks if the given block is a procedure block. */ -export function isProcedureBlock(block: Block| - IProcedureBlock): block is IProcedureBlock { - return (block as IProcedureBlock).getProcedureModel !== undefined && - (block as IProcedureBlock).doProcedureUpdate !== undefined && - (block as IProcedureBlock).isProcedureDef !== undefined; +export function isProcedureBlock( + block: Block | IProcedureBlock +): block is IProcedureBlock { + return ( + (block as IProcedureBlock).getProcedureModel !== undefined && + (block as IProcedureBlock).doProcedureUpdate !== undefined && + (block as IProcedureBlock).isProcedureDef !== undefined + ); } diff --git a/core/interfaces/i_procedure_map.ts b/core/interfaces/i_procedure_map.ts index 0eead4025..e14d53a33 100644 --- a/core/interfaces/i_procedure_map.ts +++ b/core/interfaces/i_procedure_map.ts @@ -6,7 +6,6 @@ import {IProcedureModel} from './i_procedure_model.js'; - export interface IProcedureMap extends Map { /** * Adds the given ProcedureModel to the map of procedure models, so that @@ -14,7 +13,6 @@ export interface IProcedureMap extends Map { */ add(proc: IProcedureModel): this; - /** Returns all of the procedures stored in this map. */ getProcedures(): IProcedureModel[]; } diff --git a/core/interfaces/i_procedure_model.ts b/core/interfaces/i_procedure_model.ts index dffdc984e..cb5fda09f 100644 --- a/core/interfaces/i_procedure_model.ts +++ b/core/interfaces/i_procedure_model.ts @@ -4,10 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ - import {IParameterModel} from './i_parameter_model.js'; - /** * A data model for a procedure. */ @@ -30,7 +28,7 @@ export interface IProcedureModel { * * Pass null to represent a procedure that does not return. */ - setReturnTypes(types: string[]|null): this; + setReturnTypes(types: string[] | null): this; /** * Sets whether this procedure is enabled/disabled. If a procedure is disabled @@ -55,7 +53,7 @@ export interface IProcedureModel { * * Null represents a procedure that does not return a value. */ - getReturnTypes(): string[]|null; + getReturnTypes(): string[] | null; /** * Returns whether the procedure is enabled/disabled. If a procedure is diff --git a/core/interfaces/i_registrable.ts b/core/interfaces/i_registrable.ts index f95e3c559..b8ea4b57f 100644 --- a/core/interfaces/i_registrable.ts +++ b/core/interfaces/i_registrable.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IRegistrable'); - /** * The interface for a Blockly component that can be registered. */ diff --git a/core/interfaces/i_selectable.ts b/core/interfaces/i_selectable.ts index 0e99a5847..7997c90f8 100644 --- a/core/interfaces/i_selectable.ts +++ b/core/interfaces/i_selectable.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.ISelectable'); import type {IDeletable} from './i_deletable.js'; import type {IMovable} from './i_movable.js'; - /** * The interface for an object that is selectable. */ diff --git a/core/interfaces/i_selectable_toolbox_item.ts b/core/interfaces/i_selectable_toolbox_item.ts index 8f454372b..453608c95 100644 --- a/core/interfaces/i_selectable_toolbox_item.ts +++ b/core/interfaces/i_selectable_toolbox_item.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.ISelectableToolboxItem'); import type {IToolboxItem} from './i_toolbox_item.js'; - /** * Interface for an item in the toolbox that can be selected. */ @@ -28,7 +27,7 @@ export interface ISelectableToolboxItem extends IToolboxItem { * * @returns The definition of items to be displayed in the flyout. */ - getContents(): FlyoutItemInfoArray|string; + getContents(): FlyoutItemInfoArray | string; /** * Sets the current toolbox item as selected. @@ -58,7 +57,8 @@ export interface ISelectableToolboxItem extends IToolboxItem { /** * Type guard that checks whether an IToolboxItem is an ISelectableToolboxItem. */ -export function isSelectableToolboxItem(toolboxItem: IToolboxItem): - toolboxItem is ISelectableToolboxItem { +export function isSelectableToolboxItem( + toolboxItem: IToolboxItem +): toolboxItem is ISelectableToolboxItem { return toolboxItem.isSelectable(); } diff --git a/core/interfaces/i_serializable.ts b/core/interfaces/i_serializable.ts new file mode 100644 index 000000000..380a27709 --- /dev/null +++ b/core/interfaces/i_serializable.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface ISerializable { + /** + * @param doFullSerialization If true, this signals that any backing data + * structures used by this ISerializable should also be serialized. This + * is used for copy-paste. + * @returns a JSON serializable value that records the ISerializable's state. + */ + saveState(doFullSerialization: boolean): any; + + /** + * Takes in a JSON serializable value and sets the ISerializable's state + * based on that. + * + * @param state The state to apply to the ISerializable. + */ + loadState(state: any): void; +} + +/** Type guard that checks whether the given object is a ISerializable. */ +export function isSerializable(obj: any): obj is ISerializable { + return obj.saveState !== undefined && obj.loadState !== undefined; +} diff --git a/core/interfaces/i_serializer.ts b/core/interfaces/i_serializer.ts index 37efa4626..30e682013 100644 --- a/core/interfaces/i_serializer.ts +++ b/core/interfaces/i_serializer.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.serialization.ISerializer'); import type {Workspace} from '../workspace.js'; - /** * Serializes and deserializes a plugin or system. */ @@ -33,7 +32,7 @@ export interface ISerializer { * @returns A JS object containing the system's state, or null if there is no * state to record. */ - save(workspace: Workspace): Object|null; + save(workspace: Workspace): Object | null; /* eslint-enable valid-jsdoc */ /** diff --git a/core/interfaces/i_styleable.ts b/core/interfaces/i_styleable.ts index 86d9f2c00..d153bf02f 100644 --- a/core/interfaces/i_styleable.ts +++ b/core/interfaces/i_styleable.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IStyleable'); - /** * Interface for an object that a style can be added to. */ diff --git a/core/interfaces/i_toolbox.ts b/core/interfaces/i_toolbox.ts index cb4eeceb4..6cb7f0f74 100644 --- a/core/interfaces/i_toolbox.ts +++ b/core/interfaces/i_toolbox.ts @@ -13,7 +13,6 @@ import type {ToolboxInfo} from '../utils/toolbox.js'; import type {IFlyout} from './i_flyout.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; - /** * Interface for a toolbox. */ @@ -47,7 +46,7 @@ export interface IToolbox extends IRegistrable { * * @returns The toolbox flyout. */ - getFlyout(): IFlyout|null; + getFlyout(): IFlyout | null; /** * Gets the workspace for the toolbox. @@ -107,7 +106,7 @@ export interface IToolbox extends IRegistrable { * * @returns The selected item, or null if no item is currently selected. */ - getSelectedItem(): IToolboxItem|null; + getSelectedItem(): IToolboxItem | null; /** Disposes of this toolbox. */ dispose(): void; diff --git a/core/interfaces/i_toolbox_item.ts b/core/interfaces/i_toolbox_item.ts index f7398acec..bd46e4f80 100644 --- a/core/interfaces/i_toolbox_item.ts +++ b/core/interfaces/i_toolbox_item.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.IToolboxItem'); - /** * Interface for an item in the toolbox. */ @@ -24,7 +23,7 @@ export interface IToolboxItem { * * @returns The div for the toolbox item. */ - getDiv(): Element|null; + getDiv(): Element | null; /** * Gets a unique identifier for this toolbox item. @@ -39,7 +38,7 @@ export interface IToolboxItem { * @returns The parent toolbox item, or null if this toolbox item is not * nested. */ - getParent(): IToolboxItem|null; + getParent(): IToolboxItem | null; /** * Gets the nested level of the category. @@ -71,7 +70,7 @@ export interface IToolboxItem { * * @returns The HTML element that receives clicks. */ - getClickTarget(): Element|null; + getClickTarget(): Element | null; /** * Sets whether the category is visible or not. diff --git a/core/interfaces/i_variable_backed_parameter_model.ts b/core/interfaces/i_variable_backed_parameter_model.ts index 39530b81e..71433f94f 100644 --- a/core/interfaces/i_variable_backed_parameter_model.ts +++ b/core/interfaces/i_variable_backed_parameter_model.ts @@ -7,7 +7,6 @@ import type {VariableModel} from '../variable_model.js'; import {IParameterModel} from './i_parameter_model.js'; - /** Interface for a parameter model that holds a variable model. */ export interface IVariableBackedParameterModel extends IParameterModel { /** Returns the variable model held by this type. */ @@ -17,7 +16,8 @@ export interface IVariableBackedParameterModel extends IParameterModel { /** * Returns whether the given object is a variable holder or not. */ -export function isVariableBackedParameterModel(param: IParameterModel): - param is IVariableBackedParameterModel { +export function isVariableBackedParameterModel( + param: IParameterModel +): param is IVariableBackedParameterModel { return (param as any).getVariableModel !== undefined; } diff --git a/core/internal_constants.ts b/core/internal_constants.ts index 5d6a0340d..fe0c2bbdd 100644 --- a/core/internal_constants.ts +++ b/core/internal_constants.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.internalConstants'); import {ConnectionType} from './connection_type.js'; - /** * Number of characters to truncate a collapsed block to. * @@ -34,9 +33,9 @@ export const OPPOSITE_TYPE: number[] = []; OPPOSITE_TYPE[ConnectionType.INPUT_VALUE] = ConnectionType.OUTPUT_VALUE; OPPOSITE_TYPE[ConnectionType.OUTPUT_VALUE] = ConnectionType.INPUT_VALUE; OPPOSITE_TYPE[ConnectionType.NEXT_STATEMENT] = - ConnectionType.PREVIOUS_STATEMENT; + ConnectionType.PREVIOUS_STATEMENT; OPPOSITE_TYPE[ConnectionType.PREVIOUS_STATEMENT] = - ConnectionType.NEXT_STATEMENT; + ConnectionType.NEXT_STATEMENT; /** * String for use in the dropdown created in field_variable. diff --git a/core/keyboard_nav/ast_node.ts b/core/keyboard_nav/ast_node.ts index 624dc6a45..ce01b1a7b 100644 --- a/core/keyboard_nav/ast_node.ts +++ b/core/keyboard_nav/ast_node.ts @@ -17,13 +17,12 @@ import type {Block} from '../block.js'; import type {Connection} from '../connection.js'; import {ConnectionType} from '../connection_type.js'; import type {Field} from '../field.js'; -import type {Input} from '../input.js'; +import type {Input} from '../inputs/input.js'; import type {IASTNodeLocation} from '../interfaces/i_ast_node_location.js'; import type {IASTNodeLocationWithBlock} from '../interfaces/i_ast_node_location_with_block.js'; import {Coordinate} from '../utils/coordinate.js'; import type {Workspace} from '../workspace.js'; - /** * Class for an AST node. * It is recommended that you use one of the createNode methods instead of @@ -40,14 +39,14 @@ export class ASTNode { * workspace. */ private static readonly DEFAULT_OFFSET_Y: number = -20; - private readonly type_: string; - private readonly isConnection_: boolean; - private readonly location_: IASTNodeLocation; + private readonly type: string; + private readonly isConnectionLocation: boolean; + private readonly location: IASTNodeLocation; /** The coordinate on the workspace. */ // AnyDuringMigration because: Type 'null' is not assignable to type // 'Coordinate'. - private wsCoordinate_: Coordinate = null as AnyDuringMigration; + private wsCoordinate: Coordinate = null as AnyDuringMigration; /** * @param type The type of the location. @@ -64,15 +63,15 @@ export class ASTNode { * The type of the location. * One of ASTNode.types */ - this.type_ = type; + this.type = type; /** Whether the location points to a connection. */ - this.isConnection_ = ASTNode.isConnectionType_(type); + this.isConnectionLocation = ASTNode.isConnectionType(type); /** The location of the AST node. */ - this.location_ = location; + this.location = location; - this.processParams_(opt_params || null); + this.processParams(opt_params || null); } /** @@ -80,12 +79,12 @@ export class ASTNode { * * @param params The user specified parameters. */ - private processParams_(params: Params|null) { + private processParams(params: Params | null) { if (!params) { return; } if (params.wsCoordinate) { - this.wsCoordinate_ = params.wsCoordinate; + this.wsCoordinate = params.wsCoordinate; } } @@ -98,7 +97,7 @@ export class ASTNode { * on. */ getLocation(): IASTNodeLocation { - return this.location_; + return this.location; } /** @@ -108,7 +107,7 @@ export class ASTNode { * @returns The type of the location. */ getType(): string { - return this.type_; + return this.type; } /** @@ -118,7 +117,7 @@ export class ASTNode { * workspace. */ getWsCoordinate(): Coordinate { - return this.wsCoordinate_; + return this.wsCoordinate; } /** @@ -128,7 +127,7 @@ export class ASTNode { * @internal */ isConnection(): boolean { - return this.isConnection_; + return this.isConnectionLocation; } /** @@ -139,8 +138,8 @@ export class ASTNode { * @returns The AST node holding the next field or connection or null if there * is no editable field or input connection after the given input. */ - private findNextForInput_(): ASTNode|null { - const location = this.location_ as Connection; + private findNextForInput(): ASTNode | null { + const location = this.location as Connection; const parentInput = location.getParentInput(); const block = parentInput!.getSourceBlock(); // AnyDuringMigration because: Argument of type 'Input | null' is not @@ -169,13 +168,14 @@ export class ASTNode { * @returns The AST node pointing to the next field or connection or null if * there is no editable field or input connection after the given input. */ - private findNextForField_(): ASTNode|null { - const location = this.location_ as Field; + private findNextForField(): ASTNode | null { + const location = this.location as Field; const input = location.getParentInput(); const block = location.getSourceBlock(); if (!block) { throw new Error( - 'The current AST location is not associated with a block'); + 'The current AST location is not associated with a block' + ); } const curIdx = block.inputList.indexOf(input); let fieldIdx = input.fieldRow.indexOf(location) + 1; @@ -203,8 +203,8 @@ export class ASTNode { * * @returns The AST node holding the previous field or connection. */ - private findPrevForInput_(): ASTNode|null { - const location = this.location_ as Connection; + private findPrevForInput(): ASTNode | null { + const location = this.location as Connection; const parentInput = location.getParentInput(); const block = parentInput!.getSourceBlock(); // AnyDuringMigration because: Argument of type 'Input | null' is not @@ -232,13 +232,14 @@ export class ASTNode { * * @returns The AST node holding the previous input or field. */ - private findPrevForField_(): ASTNode|null { - const location = this.location_ as Field; + private findPrevForField(): ASTNode | null { + const location = this.location as Field; const parentInput = location.getParentInput(); const block = location.getSourceBlock(); if (!block) { throw new Error( - 'The current AST location is not associated with a block'); + 'The current AST location is not associated with a block' + ); } const curIdx = block.inputList.indexOf(parentInput); let fieldIdx = parentInput.fieldRow.indexOf(location) - 1; @@ -270,7 +271,7 @@ export class ASTNode { * @returns The first block of the next stack or null if there are no blocks * on the workspace. */ - private navigateBetweenStacks_(forward: boolean): ASTNode|null { + private navigateBetweenStacks(forward: boolean): ASTNode | null { let curLocation = this.getLocation(); // TODO(#6097): Use instanceof checks to exit early for values of // curLocation that don't make sense. @@ -300,7 +301,8 @@ export class ASTNode { } } throw Error( - 'Couldn\'t find ' + (forward ? 'next' : 'previous') + ' stack?!'); + "Couldn't find " + (forward ? 'next' : 'previous') + ' stack?!' + ); } /** @@ -311,7 +313,7 @@ export class ASTNode { * @param block The block that we want to find the top connection on. * @returns The AST node containing the top connection. */ - private findTopASTNodeForBlock_(block: Block): ASTNode|null { + private findTopASTNodeForBlock(block: Block): ASTNode | null { const topConnection = getParentConnection(block); if (topConnection) { return ASTNode.createConnectionNode(topConnection); @@ -328,7 +330,7 @@ export class ASTNode { * @returns The AST node pointing to the input connection or the top block of * the stack this block is in. */ - private getOutAstNodeForBlock_(block: Block): ASTNode|null { + private getOutAstNodeForBlock(block: Block): ASTNode | null { if (!block) { return null; } @@ -338,13 +340,16 @@ export class ASTNode { const topConnection = getParentConnection(topBlock); // If the top connection has a parentInput, create an AST node pointing to // that input. - if (topConnection && topConnection.targetConnection && - topConnection.targetConnection.getParentInput()) { + if ( + topConnection && + topConnection.targetConnection && + topConnection.targetConnection.getParentInput() + ) { // AnyDuringMigration because: Argument of type 'Input | null' is not // assignable to parameter of type 'Input'. return ASTNode.createInputNode( - topConnection.targetConnection.getParentInput() as - AnyDuringMigration); + topConnection.targetConnection.getParentInput() as AnyDuringMigration + ); } else { // Go to stack level if you are not underneath an input. return ASTNode.createStackNode(topBlock); @@ -359,7 +364,7 @@ export class ASTNode { * Null if there are no editable fields or inputs with connections on the * block. */ - private findFirstFieldOrInput_(block: Block): ASTNode|null { + private findFirstFieldOrInput(block: Block): ASTNode | null { const inputs = block.inputList; for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; @@ -383,7 +388,7 @@ export class ASTNode { * @returns The source block of the location, or null if the node is of type * workspace. */ - getSourceBlock(): Block|null { + getSourceBlock(): Block | null { if (this.getType() === ASTNode.types.BLOCK) { return this.getLocation() as Block; } else if (this.getType() === ASTNode.types.STACK) { @@ -401,33 +406,33 @@ export class ASTNode { * @returns An AST node that wraps the next field, connection, block, or * workspace. Or null if there is no node to the right. */ - next(): ASTNode|null { - switch (this.type_) { + next(): ASTNode | null { + switch (this.type) { case ASTNode.types.STACK: - return this.navigateBetweenStacks_(true); + return this.navigateBetweenStacks(true); case ASTNode.types.OUTPUT: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; return ASTNode.createBlockNode(connection.getSourceBlock()); } case ASTNode.types.FIELD: - return this.findNextForField_(); + return this.findNextForField(); case ASTNode.types.INPUT: - return this.findNextForInput_(); + return this.findNextForInput(); case ASTNode.types.BLOCK: { - const block = this.location_ as Block; + const block = this.location as Block; const nextConnection = block.nextConnection; if (!nextConnection) return null; return ASTNode.createConnectionNode(nextConnection); } case ASTNode.types.PREVIOUS: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; return ASTNode.createBlockNode(connection.getSourceBlock()); } case ASTNode.types.NEXT: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; const targetConnection = connection.targetConnection; return ASTNode.createConnectionNode(targetConnection!); } @@ -443,10 +448,10 @@ export class ASTNode { * @returns An AST node that wraps the next field, connection, workspace, or * block. Or null if there is nothing below this node. */ - in(): ASTNode|null { - switch (this.type_) { + in(): ASTNode | null { + switch (this.type) { case ASTNode.types.WORKSPACE: { - const workspace = this.location_ as Workspace; + const workspace = this.location as Workspace; const topBlocks = workspace.getTopBlocks(true); if (topBlocks.length > 0) { return ASTNode.createStackNode(topBlocks[0]); @@ -454,15 +459,15 @@ export class ASTNode { break; } case ASTNode.types.STACK: { - const block = this.location_ as Block; - return this.findTopASTNodeForBlock_(block); + const block = this.location as Block; + return this.findTopASTNodeForBlock(block); } case ASTNode.types.BLOCK: { - const block = this.location_ as Block; - return this.findFirstFieldOrInput_(block); + const block = this.location as Block; + return this.findFirstFieldOrInput(block); } case ASTNode.types.INPUT: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; const targetConnection = connection.targetConnection; return ASTNode.createConnectionNode(targetConnection!); } @@ -477,28 +482,28 @@ export class ASTNode { * @returns An AST node that wraps the previous field, connection, workspace * or block. Or null if no node exists to the left. null. */ - prev(): ASTNode|null { - switch (this.type_) { + prev(): ASTNode | null { + switch (this.type) { case ASTNode.types.STACK: - return this.navigateBetweenStacks_(false); + return this.navigateBetweenStacks(false); case ASTNode.types.OUTPUT: return null; case ASTNode.types.FIELD: - return this.findPrevForField_(); + return this.findPrevForField(); case ASTNode.types.INPUT: - return this.findPrevForInput_(); + return this.findPrevForInput(); case ASTNode.types.BLOCK: { - const block = this.location_ as Block; + const block = this.location as Block; const topConnection = getParentConnection(block); if (!topConnection) return null; return ASTNode.createConnectionNode(topConnection); } case ASTNode.types.PREVIOUS: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; const targetConnection = connection.targetConnection; if (targetConnection && !targetConnection.getParentInput()) { return ASTNode.createConnectionNode(targetConnection); @@ -506,7 +511,7 @@ export class ASTNode { break; } case ASTNode.types.NEXT: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; return ASTNode.createBlockNode(connection.getSourceBlock()); } } @@ -521,18 +526,20 @@ export class ASTNode { * @returns An AST node that wraps the next field, connection, workspace or * block. Or null if we are at the workspace level. */ - out(): ASTNode|null { - switch (this.type_) { + out(): ASTNode | null { + switch (this.type) { case ASTNode.types.STACK: { - const block = this.location_ as Block; + const block = this.location as Block; const blockPos = block.getRelativeToSurfaceXY(); // TODO: Make sure this is in the bounds of the workspace. - const wsCoordinate = - new Coordinate(blockPos.x, blockPos.y + ASTNode.DEFAULT_OFFSET_Y); + const wsCoordinate = new Coordinate( + blockPos.x, + blockPos.y + ASTNode.DEFAULT_OFFSET_Y + ); return ASTNode.createWorkspaceNode(block.workspace, wsCoordinate); } case ASTNode.types.OUTPUT: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; const target = connection.targetConnection; if (target) { return ASTNode.createConnectionNode(target); @@ -540,29 +547,30 @@ export class ASTNode { return ASTNode.createStackNode(connection.getSourceBlock()); } case ASTNode.types.FIELD: { - const field = this.location_ as Field; + const field = this.location as Field; const block = field.getSourceBlock(); if (!block) { throw new Error( - 'The current AST location is not associated with a block'); + 'The current AST location is not associated with a block' + ); } return ASTNode.createBlockNode(block); } case ASTNode.types.INPUT: { - const connection = this.location_ as Connection; + const connection = this.location as Connection; return ASTNode.createBlockNode(connection.getSourceBlock()); } case ASTNode.types.BLOCK: { - const block = this.location_ as Block; - return this.getOutAstNodeForBlock_(block); + const block = this.location as Block; + return this.getOutAstNodeForBlock(block); } case ASTNode.types.PREVIOUS: { - const connection = this.location_ as Connection; - return this.getOutAstNodeForBlock_(connection.getSourceBlock()); + const connection = this.location as Connection; + return this.getOutAstNodeForBlock(connection.getSourceBlock()); } case ASTNode.types.NEXT: { - const connection = this.location_ as Connection; - return this.getOutAstNodeForBlock_(connection.getSourceBlock()); + const connection = this.location as Connection; + return this.getOutAstNodeForBlock(connection.getSourceBlock()); } } @@ -575,7 +583,7 @@ export class ASTNode { * @param type The type to check. One of ASTNode.types. * @returns True if a node of the given type points to a connection. */ - private static isConnectionType_(type: string): boolean { + private static isConnectionType(type: string): boolean { switch (type) { case ASTNode.types.PREVIOUS: case ASTNode.types.NEXT: @@ -592,7 +600,7 @@ export class ASTNode { * @param field The location of the AST node. * @returns An AST node pointing to a field. */ - static createFieldNode(field: Field): ASTNode|null { + static createFieldNode(field: Field): ASTNode | null { if (!field) { return null; } @@ -607,7 +615,7 @@ export class ASTNode { * @param connection This is the connection the node will point to. * @returns An AST node pointing to a connection. */ - static createConnectionNode(connection: Connection): ASTNode|null { + static createConnectionNode(connection: Connection): ASTNode | null { if (!connection) { return null; } @@ -616,13 +624,17 @@ export class ASTNode { // AnyDuringMigration because: Argument of type 'Input | null' is not // assignable to parameter of type 'Input'. return ASTNode.createInputNode( - connection.getParentInput() as AnyDuringMigration); + connection.getParentInput() as AnyDuringMigration + ); } else if ( - type === ConnectionType.NEXT_STATEMENT && connection.getParentInput()) { + type === ConnectionType.NEXT_STATEMENT && + connection.getParentInput() + ) { // AnyDuringMigration because: Argument of type 'Input | null' is not // assignable to parameter of type 'Input'. return ASTNode.createInputNode( - connection.getParentInput() as AnyDuringMigration); + connection.getParentInput() as AnyDuringMigration + ); } else if (type === ConnectionType.NEXT_STATEMENT) { return new ASTNode(ASTNode.types.NEXT, connection); } else if (type === ConnectionType.OUTPUT_VALUE) { @@ -640,7 +652,7 @@ export class ASTNode { * @param input The input used to create an AST node. * @returns An AST node pointing to a input. */ - static createInputNode(input: Input): ASTNode|null { + static createInputNode(input: Input): ASTNode | null { if (!input || !input.connection) { return null; } @@ -653,7 +665,7 @@ export class ASTNode { * @param block The block used to create an AST node. * @returns An AST node pointing to a block. */ - static createBlockNode(block: Block): ASTNode|null { + static createBlockNode(block: Block): ASTNode | null { if (!block) { return null; } @@ -670,7 +682,7 @@ export class ASTNode { * @returns An AST node of type stack that points to the top block on the * stack. */ - static createStackNode(topBlock: Block): ASTNode|null { + static createStackNode(topBlock: Block): ASTNode | null { if (!topBlock) { return null; } @@ -686,7 +698,9 @@ export class ASTNode { * workspace. */ static createWorkspaceNode( - workspace: Workspace|null, wsCoordinate: Coordinate|null): ASTNode|null { + workspace: Workspace | null, + wsCoordinate: Coordinate | null + ): ASTNode | null { if (!wsCoordinate || !workspace) { return null; } @@ -701,7 +715,7 @@ export class ASTNode { * @param block The block to find the top most AST node on. * @returns The AST node holding the top most position on the block. */ - static createTopNode(block: Block): ASTNode|null { + static createTopNode(block: Block): ASTNode | null { let astNode; const topConnection = getParentConnection(block); if (topConnection) { @@ -735,7 +749,6 @@ export type Params = ASTNode.Params; // wasn't automatically converted by the automatic migration script, (2) the // name doesn't follow the styleguide. - /** * Gets the parent connection on a block. * This is either an output connection, previous connection or undefined. @@ -745,10 +758,12 @@ export type Params = ASTNode.Params; * @param block The block to find the parent connection on. * @returns The connection connecting to the parent of the block. */ -function getParentConnection(block: Block): Connection|null { +function getParentConnection(block: Block): Connection | null { let topConnection = block.outputConnection; - if (!topConnection || - block.previousConnection && block.previousConnection.isConnected()) { + if ( + !topConnection || + (block.previousConnection && block.previousConnection.isConnected()) + ) { topConnection = block.previousConnection; } return topConnection; diff --git a/core/keyboard_nav/basic_cursor.ts b/core/keyboard_nav/basic_cursor.ts index 5f0b4dfa4..96566a4c0 100644 --- a/core/keyboard_nav/basic_cursor.ts +++ b/core/keyboard_nav/basic_cursor.ts @@ -18,7 +18,6 @@ import * as registry from '../registry.js'; import {ASTNode} from './ast_node.js'; import {Cursor} from './cursor.js'; - /** * Class for a basic cursor. * This will allow the user to get to all nodes in the AST by hitting next or @@ -38,7 +37,7 @@ export class BasicCursor extends Cursor { * @returns The next node, or null if the current node is not set or there is * no next value. */ - override next(): ASTNode|null { + override next(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; @@ -59,7 +58,7 @@ export class BasicCursor extends Cursor { * @returns The next node, or null if the current node is not set or there is * no next value. */ - override in(): ASTNode|null { + override in(): ASTNode | null { return this.next(); } @@ -69,7 +68,7 @@ export class BasicCursor extends Cursor { * @returns The previous node, or null if the current node is not set or there * is no previous value. */ - override prev(): ASTNode|null { + override prev(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; @@ -90,7 +89,7 @@ export class BasicCursor extends Cursor { * @returns The previous node, or null if the current node is not set or there * is no previous value. */ - override out(): ASTNode|null { + override out(): ASTNode | null { return this.prev(); } @@ -105,8 +104,9 @@ export class BasicCursor extends Cursor { * @returns The next node in the traversal. */ protected getNextNode_( - node: ASTNode|null, isValid: (p1: ASTNode|null) => boolean): ASTNode - |null { + node: ASTNode | null, + isValid: (p1: ASTNode | null) => boolean + ): ASTNode | null { if (!node) { return null; } @@ -116,7 +116,7 @@ export class BasicCursor extends Cursor { } else if (newNode) { return this.getNextNode_(newNode, isValid); } - const siblingOrParent = this.findSiblingOrParent_(node.out()); + const siblingOrParent = this.findSiblingOrParent(node.out()); if (isValid(siblingOrParent)) { return siblingOrParent; } else if (siblingOrParent) { @@ -137,15 +137,16 @@ export class BasicCursor extends Cursor { * exists. */ protected getPreviousNode_( - node: ASTNode|null, isValid: (p1: ASTNode|null) => boolean): ASTNode - |null { + node: ASTNode | null, + isValid: (p1: ASTNode | null) => boolean + ): ASTNode | null { if (!node) { return null; } - let newNode: ASTNode|null = node.prev(); + let newNode: ASTNode | null = node.prev(); if (newNode) { - newNode = this.getRightMostChild_(newNode); + newNode = this.getRightMostChild(newNode); } else { newNode = node.out(); } @@ -164,12 +165,17 @@ export class BasicCursor extends Cursor { * @param node The AST node to check whether it is valid. * @returns True if the node should be visited, false otherwise. */ - protected validNode_(node: ASTNode|null): boolean { + protected validNode_(node: ASTNode | null): boolean { let isValid = false; const type = node && node.getType(); - if (type === ASTNode.types.OUTPUT || type === ASTNode.types.INPUT || - type === ASTNode.types.FIELD || type === ASTNode.types.NEXT || - type === ASTNode.types.PREVIOUS || type === ASTNode.types.WORKSPACE) { + if ( + type === ASTNode.types.OUTPUT || + type === ASTNode.types.INPUT || + type === ASTNode.types.FIELD || + type === ASTNode.types.NEXT || + type === ASTNode.types.PREVIOUS || + type === ASTNode.types.WORKSPACE + ) { isValid = true; } return isValid; @@ -181,7 +187,7 @@ export class BasicCursor extends Cursor { * @param node The current position in the AST. * @returns The parent AST node or null if there are no valid parents. */ - private findSiblingOrParent_(node: ASTNode|null): ASTNode|null { + private findSiblingOrParent(node: ASTNode | null): ASTNode | null { if (!node) { return null; } @@ -189,7 +195,7 @@ export class BasicCursor extends Cursor { if (nextNode) { return nextNode; } - return this.findSiblingOrParent_(node.out()); + return this.findSiblingOrParent(node.out()); } /** @@ -199,7 +205,7 @@ export class BasicCursor extends Cursor { * @returns The right most child of the given node, or the node if no child * exists. */ - private getRightMostChild_(node: ASTNode|null): ASTNode|null { + private getRightMostChild(node: ASTNode | null): ASTNode | null { if (!node!.in()) { return node; } @@ -207,9 +213,12 @@ export class BasicCursor extends Cursor { while (newNode && newNode.next()) { newNode = newNode.next(); } - return this.getRightMostChild_(newNode); + return this.getRightMostChild(newNode); } } registry.register( - registry.Type.CURSOR, BasicCursor.registrationName, BasicCursor); + registry.Type.CURSOR, + BasicCursor.registrationName, + BasicCursor +); diff --git a/core/keyboard_nav/cursor.ts b/core/keyboard_nav/cursor.ts index 31ca083d6..adc62acbf 100644 --- a/core/keyboard_nav/cursor.ts +++ b/core/keyboard_nav/cursor.ts @@ -18,7 +18,6 @@ import * as registry from '../registry.js'; import {ASTNode} from './ast_node.js'; import {Marker} from './marker.js'; - /** * Class for a cursor. * A cursor controls how a user navigates the Blockly AST. @@ -36,16 +35,19 @@ export class Cursor extends Marker { * @returns The next element, or null if the current node is not set or there * is no next value. */ - next(): ASTNode|null { + next(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; } let newNode = curNode.next(); - while (newNode && newNode.next() && - (newNode.getType() === ASTNode.types.NEXT || - newNode.getType() === ASTNode.types.BLOCK)) { + while ( + newNode && + newNode.next() && + (newNode.getType() === ASTNode.types.NEXT || + newNode.getType() === ASTNode.types.BLOCK) + ) { newNode = newNode.next(); } @@ -61,15 +63,17 @@ export class Cursor extends Marker { * @returns The in element, or null if the current node is not set or there is * no in value. */ - in(): ASTNode|null { - let curNode: ASTNode|null = this.getCurNode(); + in(): ASTNode | null { + let curNode: ASTNode | null = this.getCurNode(); if (!curNode) { return null; } // If we are on a previous or output connection, go to the block level // before performing next operation. - if (curNode.getType() === ASTNode.types.PREVIOUS || - curNode.getType() === ASTNode.types.OUTPUT) { + if ( + curNode.getType() === ASTNode.types.PREVIOUS || + curNode.getType() === ASTNode.types.OUTPUT + ) { curNode = curNode.next(); } const newNode = curNode?.in() ?? null; @@ -86,16 +90,19 @@ export class Cursor extends Marker { * @returns The previous element, or null if the current node is not set or * there is no previous value. */ - prev(): ASTNode|null { + prev(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; } let newNode = curNode.prev(); - while (newNode && newNode.prev() && - (newNode.getType() === ASTNode.types.NEXT || - newNode.getType() === ASTNode.types.BLOCK)) { + while ( + newNode && + newNode.prev() && + (newNode.getType() === ASTNode.types.NEXT || + newNode.getType() === ASTNode.types.BLOCK) + ) { newNode = newNode.prev(); } @@ -111,7 +118,7 @@ export class Cursor extends Marker { * @returns The out element, or null if the current node is not set or there * is no out value. */ - out(): ASTNode|null { + out(): ASTNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; diff --git a/core/keyboard_nav/marker.ts b/core/keyboard_nav/marker.ts index d569f3637..489aa95ac 100644 --- a/core/keyboard_nav/marker.ts +++ b/core/keyboard_nav/marker.ts @@ -18,19 +18,18 @@ import type {MarkerSvg} from '../renderers/common/marker_svg.js'; import type {ASTNode} from './ast_node.js'; - /** * Class for a marker. * This is used in keyboard navigation to save a location in the Blockly AST. */ export class Marker { /** The colour of the marker. */ - colour: string|null = null; + colour: string | null = null; /** The current location of the marker. */ // AnyDuringMigration because: Type 'null' is not assignable to type // 'ASTNode'. - private curNode_: ASTNode = null as AnyDuringMigration; + private curNode: ASTNode = null as AnyDuringMigration; /** * The object in charge of drawing the visual representation of the current @@ -38,7 +37,7 @@ export class Marker { */ // AnyDuringMigration because: Type 'null' is not assignable to type // 'MarkerSvg'. - private drawer_: MarkerSvg = null as AnyDuringMigration; + private drawer: MarkerSvg = null as AnyDuringMigration; /** The type of the marker. */ type = 'marker'; @@ -52,7 +51,7 @@ export class Marker { * @param drawer The object in charge of drawing the marker. */ setDrawer(drawer: MarkerSvg) { - this.drawer_ = drawer; + this.drawer = drawer; } /** @@ -61,7 +60,7 @@ export class Marker { * @returns The object in charge of drawing the marker. */ getDrawer(): MarkerSvg { - return this.drawer_; + return this.drawer; } /** @@ -70,7 +69,7 @@ export class Marker { * @returns The current field, connection, or block the marker is on. */ getCurNode(): ASTNode { - return this.curNode_; + return this.curNode; } /** @@ -81,10 +80,10 @@ export class Marker { * @param newNode The new location of the marker. */ setCurNode(newNode: ASTNode) { - const oldNode = this.curNode_; - this.curNode_ = newNode; - if (this.drawer_) { - this.drawer_.draw(oldNode, this.curNode_); + const oldNode = this.curNode; + this.curNode = newNode; + if (this.drawer) { + this.drawer.draw(oldNode, this.curNode); } } @@ -94,15 +93,15 @@ export class Marker { * @internal */ draw() { - if (this.drawer_) { - this.drawer_.draw(this.curNode_, this.curNode_); + if (this.drawer) { + this.drawer.draw(this.curNode, this.curNode); } } /** Hide the marker SVG. */ hide() { - if (this.drawer_) { - this.drawer_.hide(); + if (this.drawer) { + this.drawer.hide(); } } diff --git a/core/keyboard_nav/tab_navigate_cursor.ts b/core/keyboard_nav/tab_navigate_cursor.ts index eb4395969..7462358cf 100644 --- a/core/keyboard_nav/tab_navigate_cursor.ts +++ b/core/keyboard_nav/tab_navigate_cursor.ts @@ -18,7 +18,6 @@ import type {Field} from '../field.js'; import {ASTNode} from './ast_node.js'; import {BasicCursor} from './basic_cursor.js'; - /** * A cursor for navigating between tab navigable fields. */ @@ -29,13 +28,17 @@ export class TabNavigateCursor extends BasicCursor { * @param node The AST node to check whether it is valid. * @returns True if the node should be visited, false otherwise. */ - override validNode_(node: ASTNode|null): boolean { + override validNode_(node: ASTNode | null): boolean { let isValid = false; const type = node && node.getType(); if (node) { const location = node.getLocation() as Field; - if (type === ASTNode.types.FIELD && location && - location.isTabNavigable() && location.isClickable()) { + if ( + type === ASTNode.types.FIELD && + location && + location.isTabNavigable() && + location.isClickable() + ) { isValid = true; } } diff --git a/core/main.js b/core/main.js index 015157ab0..8672273cd 100644 --- a/core/main.js +++ b/core/main.js @@ -13,280 +13,9 @@ goog.module('Blockly.main'); -const Blockly = goog.require('Blockly'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Events = goog.require('Blockly.Events'); +/** @suppress {extraRequire} */ +goog.require('Blockly'); const Msg = goog.require('Blockly.Msg'); -const Tooltip = goog.require('Blockly.Tooltip'); -const WidgetDiv = goog.require('Blockly.WidgetDiv'); -const colour = goog.require('Blockly.utils.colour'); -const common = goog.require('Blockly.common'); -const deprecation = goog.require('Blockly.utils.deprecation'); -const dialog = goog.require('Blockly.dialog'); -const eventUtils = goog.require('Blockly.Events.utils'); - -/* - * Aliased functions and properties that used to be on the Blockly namespace. - * Everything in this section is deprecated. Both external and internal code - * should avoid using these functions and use the designated replacements. - * Everything in this section will be removed in a future version of Blockly. - */ - -// Add accessors for properties on Blockly that have now been deprecated. -Object.defineProperties(Blockly, { - /** - * Wrapper to window.alert() that app developers may override to - * provide alternatives to the modal browser window. - * @name Blockly.alert - * @type {!function(string, function()=)} - * @deprecated Use Blockly.dialog.alert / .setAlert() instead. - * (December 2021) - * @suppress {checkTypes} - */ - alert: { - set: function(newAlert) { - deprecation.warn( - 'Blockly.alert', 'version 9', 'version 10', - 'Blockly.dialog.setAlert'); - dialog.setAlert(newAlert); - }, - get: function() { - deprecation.warn( - 'Blockly.alert', 'version 9', 'version 10', 'Blockly.dialog.alert'); - return dialog.alert; - }, - }, - /** - * Wrapper to window.confirm() that app developers may override to - * provide alternatives to the modal browser window. - * @name Blockly.confirm - * @type {!function(string, function()=)} - * @deprecated Use Blockly.dialog.confirm / .setConfirm() instead. - * (December 2021) - * @suppress {checkTypes} - */ - confirm: { - set: function(newConfirm) { - deprecation.warn( - 'Blockly.confirm', 'version 9', 'version 10', - 'Blockly.dialog.setConfirm'); - dialog.setConfirm(newConfirm); - }, - get: function() { - deprecation.warn( - 'Blockly.confirm', 'version 9', 'version 10', - 'Blockly.dialog.confirm'); - return dialog.confirm; - }, - }, - /** - * The main workspace most recently used. - * Set by Blockly.WorkspaceSvg.prototype.markFocused - * @name Blockly.mainWorkspace - * @type {Workspace} - * @suppress {checkTypes} - */ - mainWorkspace: { - set: function(x) { - deprecation.warn( - 'Blockly.mainWorkspace', 'version 9', 'version 10', - 'Blockly.getMainWorkspace'); - common.setMainWorkspace(x); - }, - get: function() { - deprecation.warn( - 'Blockly.mainWorkspace', 'version 9', 'version 10', - 'Blockly.getMainWorkspace'); - return common.getMainWorkspace(); - }, - }, - /** - * Wrapper to window.prompt() that app developers may override to - * provide alternatives to the modal browser window. Built-in - * browser prompts are often used for better text input experience - * on mobile device. We strongly recommend testing mobile when - * overriding this. - * @name Blockly.prompt - * @type {!function(string, string, function()=)} - * @deprecated Use Blockly.dialog.prompt / .setPrompt() instead. - * (December 2021) - * @suppress {checkTypes} - */ - prompt: { - set: function(newPrompt) { - deprecation.warn( - 'Blockly.prompt', 'version 9', 'version 10', - 'Blockly.dialog.setPrompt'); - dialog.setPrompt(newPrompt); - }, - get: function() { - deprecation.warn( - 'Blockly.prompt', 'version 9', 'version 10', 'Blockly.dialog.prompt'); - return dialog.prompt; - }, - }, - /** - * Currently selected block. - * @name Blockly.selected - * @type {?ICopyable} - * @suppress {checkTypes} - */ - selected: { - get: function() { - deprecation.warn( - 'Blockly.selected', 'version 9', 'version 10', 'Blockly.getSelected'); - return common.getSelected(); - }, - set: function(newSelection) { - deprecation.warn( - 'Blockly.selected', 'version 9', 'version 10', 'Blockly.getSelected'); - common.setSelected(newSelection); - }, - }, - /** - * The richness of block colours, regardless of the hue. - * Must be in the range of 0 (inclusive) to 1 (exclusive). - * @name Blockly.HSV_SATURATION - * @type {number} - * @suppress {checkTypes} - */ - HSV_SATURATION: { - get: function() { - return colour.getHsvSaturation(); - }, - set: function(newValue) { - colour.setHsvSaturation(newValue); - }, - }, - /** - * The intensity of block colours, regardless of the hue. - * Must be in the range of 0 (inclusive) to 1 (exclusive). - * @name Blockly.HSV_VALUE - * @type {number} - * @suppress {checkTypes} - */ - HSV_VALUE: { - get: function() { - return colour.getHsvValue(); - }, - set: function(newValue) { - colour.setHsvValue(newValue); - }, - }, -}); - -// Add accessors for properties on Blockly.ContextMenu that have now -// been deprecated. -Object.defineProperties(ContextMenu, { - /** - * Which block is the context menu attached to? - * @name Blockly.ContextMenu.currentBlock - * @type {Block} - * @deprecated Use Blockly.Tooltip.getCurrentBlock() / - * .setCurrentBlock() instead. (September 2021) - * @suppress {checkTypes} - */ - currentBlock: { - get: function() { - deprecation.warn( - 'Blockly.ContextMenu.currentBlock', 'September 2021', - 'September 2022', 'Blockly.Tooltip.getCurrentBlock()'); - return ContextMenu.getCurrentBlock(); - }, - set: function(block) { - deprecation.warn( - 'Blockly.ContextMenu.currentBlock', 'September 2021', - 'September 2022', 'Blockly.Tooltip.setCurrentBlock(block)'); - ContextMenu.setCurrentBlock(block); - }, - }, -}); - -// Add accessors for properties on Blockly.Events that have now been -// deprecated. -Object.defineProperties(Events, { - /** - * Sets whether the next event should be added to the undo stack. - * @name Blockly.Evenents.recordUndo - * @type {boolean} - * @deprecated Use Blockly.Events.getRecordUndo() and - * .setRecordUndo(). (September 2021) - * @suppress {checkTypes} - */ - recordUndo: { - get: function() { - deprecation.warn( - 'Blockly.Events.recordUndo', 'September 2021', 'September 2022', - 'Blockly.Events.getRecordUndo()'); - return eventUtils.getRecordUndo(); - }, - set: function(record) { - deprecation.warn( - 'Blockly.Events.recordUndo', 'September 2021', 'September 2022', - 'Blockly.Events.setRecordUndo()'); - eventUtils.setRecordUndo(record); - }, - }, -}); - - -// Add accessors for properties on Blockly.Tooltip that have now been -// deprecated. -Object.defineProperties(Tooltip, { - /** - * Is a tooltip currently showing? - * @name Blockly.Tooltip.visible - * @type {boolean} - * @deprecated Use Blockly.Tooltip.isVisible() instead. (September - * 2021) - * @suppress {checkTypes} - */ - visible: { - get: function() { - deprecation.warn( - 'Blockly.Tooltip.visible', 'September 2021', 'September 2022', - 'Blockly.Tooltip.isVisible()'); - return Tooltip.isVisible(); - }, - }, - /** - * The HTML container. Set once by createDom. - * @name Blockly.Tooltip.DIV - * @type {HTMLDivElement} - * @deprecated Use Blockly.Tooltip.getDiv() and .setDiv(). - * (September 2021) - * @suppress {checkTypes} - */ - DIV: { - get: function() { - deprecation.warn( - 'Blockly.Tooltip.DIV', 'September 2021', 'September 2022', - 'Blockly.Tooltip.getDiv()'); - return Tooltip.getDiv(); - }, - }, -}); - -// Add accessors for properties on Blockly.WidgetDiv that have now been -// deprecated. -Object.defineProperties(WidgetDiv, { - /** - * The HTML container for popup overlays (e.g. editor widgets). - * @name Blockly.WidgetDiv.DIV - * @type {?Element} - * @deprecated Use Blockly.WidgetDiv.getDiv() and .setDiv(). - * (September 2021) - * @suppress {checkTypes} - */ - DIV: { - get: function() { - deprecation.warn( - 'Blockly.WidgetDiv.DIV', 'September 2021', 'September 2022', - 'Blockly.WidgetDiv.getDiv()'); - return WidgetDiv.getDiv(); - }, - }, -}); // If Blockly is compiled with ADVANCED_COMPILATION and/or loaded as a // CJS or ES module there will not be a Blockly global variable diff --git a/core/marker_manager.ts b/core/marker_manager.ts index 653e220a9..c811c7e33 100644 --- a/core/marker_manager.ts +++ b/core/marker_manager.ts @@ -16,7 +16,6 @@ import type {Cursor} from './keyboard_nav/cursor.js'; import type {Marker} from './keyboard_nav/marker.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class to manage the multiple markers and the cursor on a workspace. */ @@ -25,16 +24,16 @@ export class MarkerManager { static readonly LOCAL_MARKER = 'local_marker_1'; /** The cursor. */ - private cursor_: Cursor|null = null; + private cursor_: Cursor | null = null; /** The cursor's SVG element. */ - private cursorSvg_: SVGElement|null = null; + private cursorSvg_: SVGElement | null = null; /** The map of markers for the workspace. */ private markers = new Map(); /** The marker's SVG element. */ - private markerSvg_: SVGElement|null = null; + private markerSvg_: SVGElement | null = null; /** * @param workspace The workspace for the marker manager. @@ -53,7 +52,8 @@ export class MarkerManager { this.unregisterMarker(id); } marker.setDrawer( - this.workspace.getRenderer().makeMarkerDrawer(this.workspace, marker)); + this.workspace.getRenderer().makeMarkerDrawer(this.workspace, marker) + ); this.setMarkerSvg(marker.getDrawer().createDom()); this.markers.set(id, marker); } @@ -70,8 +70,11 @@ export class MarkerManager { this.markers.delete(id); } else { throw Error( - 'Marker with ID ' + id + ' does not exist. ' + - 'Can only unregister markers that exist.'); + 'Marker with ID ' + + id + + ' does not exist. ' + + 'Can only unregister markers that exist.' + ); } } @@ -80,7 +83,7 @@ export class MarkerManager { * * @returns The cursor for this workspace. */ - getCursor(): Cursor|null { + getCursor(): Cursor | null { return this.cursor_; } @@ -91,7 +94,7 @@ export class MarkerManager { * @returns The marker that corresponds to the given ID, or null if none * exists. */ - getMarker(id: string): Marker|null { + getMarker(id: string): Marker | null { return this.markers.get(id) || null; } @@ -107,8 +110,9 @@ export class MarkerManager { } this.cursor_ = cursor; if (this.cursor_) { - const drawer = this.workspace.getRenderer().makeMarkerDrawer( - this.workspace, this.cursor_); + const drawer = this.workspace + .getRenderer() + .makeMarkerDrawer(this.workspace, this.cursor_); this.cursor_.setDrawer(drawer); this.setCursorSvg(this.cursor_.getDrawer().createDom()); } @@ -121,7 +125,7 @@ export class MarkerManager { * SVG group. * @internal */ - setCursorSvg(cursorSvg: SVGElement|null) { + setCursorSvg(cursorSvg: SVGElement | null) { if (!cursorSvg) { this.cursorSvg_ = null; return; @@ -138,7 +142,7 @@ export class MarkerManager { * SVG group. * @internal */ - setMarkerSvg(markerSvg: SVGElement|null) { + setMarkerSvg(markerSvg: SVGElement | null) { if (!markerSvg) { this.markerSvg_ = null; return; @@ -146,8 +150,9 @@ export class MarkerManager { if (this.workspace.getBlockCanvas()) { if (this.cursorSvg_) { - this.workspace.getBlockCanvas()!.insertBefore( - markerSvg, this.cursorSvg_); + this.workspace + .getBlockCanvas()! + .insertBefore(markerSvg, this.cursorSvg_); } else { this.workspace.getBlockCanvas()!.appendChild(markerSvg); } @@ -169,12 +174,11 @@ export class MarkerManager { * Dispose of the marker manager. * Go through and delete all markers associated with this marker manager. * - * @suppress {checkTypes} * @internal */ dispose() { const markerIds = Object.keys(this.markers); - for (let i = 0, markerId; markerId = markerIds[i]; i++) { + for (let i = 0, markerId; (markerId = markerIds[i]); i++) { this.unregisterMarker(markerId); } this.markers.clear(); diff --git a/core/menu.ts b/core/menu.ts index d2ff7d53c..b5a214515 100644 --- a/core/menu.ts +++ b/core/menu.ts @@ -20,7 +20,6 @@ import * as dom from './utils/dom.js'; import type {Size} from './utils/size.js'; import * as style from './utils/style.js'; - /** * A basic menu class. */ @@ -37,34 +36,34 @@ export class Menu { * prevent the consequent mouseup event due to a simple click from * activating a menu item immediately. */ - openingCoords: Coordinate|null = null; + openingCoords: Coordinate | null = null; /** * This is the element that we will listen to the real focus events on. * A value of null means no menu item is highlighted. */ - private highlightedItem: MenuItem|null = null; + private highlightedItem: MenuItem | null = null; /** Mouse over event data. */ - private mouseOverHandler: browserEvents.Data|null = null; + private mouseOverHandler: browserEvents.Data | null = null; /** Click event data. */ - private clickHandler: browserEvents.Data|null = null; + private clickHandler: browserEvents.Data | null = null; /** Mouse enter event data. */ - private mouseEnterHandler: browserEvents.Data|null = null; + private mouseEnterHandler: browserEvents.Data | null = null; /** Mouse leave event data. */ - private mouseLeaveHandler: browserEvents.Data|null = null; + private mouseLeaveHandler: browserEvents.Data | null = null; /** Key down event data. */ - private onKeyDownHandler: browserEvents.Data|null = null; + private onKeyDownHandler: browserEvents.Data | null = null; /** The menu's root DOM element. */ - private element: HTMLDivElement|null = null; + private element: HTMLDivElement | null = null; /** ARIA name for this menu. */ - private roleName: aria.Role|null = null; + private roleName: aria.Role | null = null; /** Constructs a new Menu instance. */ constructor() {} @@ -86,7 +85,7 @@ export class Menu { * @returns The menu's root DOM element. */ render(container: Element): HTMLDivElement { - const element = (document.createElement('div')); + const element = document.createElement('div'); // goog-menu is deprecated, use blocklyMenu. May 2020. element.className = 'blocklyMenu goog-menu blocklyNonSelectable'; element.tabIndex = 0; @@ -96,21 +95,45 @@ export class Menu { this.element = element; // Add menu items. - for (let i = 0, menuItem; menuItem = this.menuItems[i]; i++) { + for (let i = 0, menuItem; (menuItem = this.menuItems[i]); i++) { element.appendChild(menuItem.createDom()); } // Add event handlers. this.mouseOverHandler = browserEvents.conditionalBind( - element, 'pointerover', this, this.handleMouseOver, true); + element, + 'pointerover', + this, + this.handleMouseOver, + true + ); this.clickHandler = browserEvents.conditionalBind( - element, 'pointerup', this, this.handleClick, true); + element, + 'pointerup', + this, + this.handleClick, + true + ); this.mouseEnterHandler = browserEvents.conditionalBind( - element, 'pointerenter', this, this.handleMouseEnter, true); + element, + 'pointerenter', + this, + this.handleMouseEnter, + true + ); this.mouseLeaveHandler = browserEvents.conditionalBind( - element, 'pointerleave', this, this.handleMouseLeave, true); + element, + 'pointerleave', + this, + this.handleMouseLeave, + true + ); this.onKeyDownHandler = browserEvents.conditionalBind( - element, 'keydown', this, this.handleKeyEvent); + element, + 'keydown', + this, + this.handleKeyEvent + ); container.appendChild(element); return element; @@ -122,7 +145,7 @@ export class Menu { * @returns The DOM element. * @internal */ - getElement(): HTMLDivElement|null { + getElement(): HTMLDivElement | null { return this.element; } @@ -183,7 +206,7 @@ export class Menu { } // Remove menu items. - for (let i = 0, menuItem; menuItem = this.menuItems[i]; i++) { + for (let i = 0, menuItem; (menuItem = this.menuItems[i]); i++) { menuItem.dispose(); } this.element = null; @@ -198,17 +221,17 @@ export class Menu { * @param elem DOM element whose owner is to be returned. * @returns Menu item for which the DOM element belongs to. */ - private getMenuItem(elem: Element): MenuItem|null { + private getMenuItem(elem: Element): MenuItem | null { const menuElem = this.getElement(); // Node might be the menu border (resulting in no associated menu item), or // a menu item's div, or some element within the menu item. // Walk up parents until one meets either the menu's root element, or // a menu item's div. - let currentElement: Element|null = elem; + let currentElement: Element | null = elem; while (currentElement && currentElement !== menuElem) { if (currentElement.classList.contains('blocklyMenuItem')) { // Having found a menu item's div, locate that menu item in this menu. - for (let i = 0, menuItem; menuItem = this.menuItems[i]; i++) { + for (let i = 0, menuItem; (menuItem = this.menuItems[i]); i++) { if (menuItem.getElement() === currentElement) { return menuItem; } @@ -227,7 +250,7 @@ export class Menu { * @param item Item to highlight, or null. * @internal */ - setHighlighted(item: MenuItem|null) { + setHighlighted(item: MenuItem | null) { const currentHighlighted = this.highlightedItem; if (currentHighlighted) { currentHighlighted.setHighlighted(false); @@ -252,9 +275,9 @@ export class Menu { * @internal */ highlightNext() { - const index = this.highlightedItem ? - this.menuItems.indexOf(this.highlightedItem) : - -1; + const index = this.highlightedItem + ? this.menuItems.indexOf(this.highlightedItem) + : -1; this.highlightHelper(index, 1); } @@ -265,9 +288,9 @@ export class Menu { * @internal */ highlightPrevious() { - const index = this.highlightedItem ? - this.menuItems.indexOf(this.highlightedItem) : - -1; + const index = this.highlightedItem + ? this.menuItems.indexOf(this.highlightedItem) + : -1; this.highlightHelper(index < 0 ? this.menuItems.length : index, -1); } @@ -291,7 +314,7 @@ export class Menu { private highlightHelper(startIndex: number, delta: number) { let index = startIndex + delta; let menuItem; - while (menuItem = this.menuItems[index]) { + while ((menuItem = this.menuItems[index])) { if (menuItem.isEnabled()) { this.setHighlighted(menuItem); break; @@ -384,8 +407,12 @@ export class Menu { return; } const keyboardEvent = e as KeyboardEvent; - if (keyboardEvent.shiftKey || keyboardEvent.ctrlKey || - keyboardEvent.metaKey || keyboardEvent.altKey) { + if ( + keyboardEvent.shiftKey || + keyboardEvent.ctrlKey || + keyboardEvent.metaKey || + keyboardEvent.altKey + ) { // Do not handle the key event if any modifier key is pressed. return; } diff --git a/core/menuitem.ts b/core/menuitem.ts index 55c623ff8..f2df7f94e 100644 --- a/core/menuitem.ts +++ b/core/menuitem.ts @@ -16,7 +16,6 @@ import * as aria from './utils/aria.js'; import * as dom from './utils/dom.js'; import * as idGenerator from './utils/idgenerator.js'; - /** * Class representing an item in a menu. */ @@ -25,13 +24,13 @@ export class MenuItem { private enabled = true; /** The DOM element for the menu item. */ - private element: HTMLDivElement|null = null; + private element: HTMLDivElement | null = null; /** Whether the menu item is rendered right-to-left. */ private rightToLeft = false; /** ARIA name for this menu. */ - private roleName: aria.Role|null = null; + private roleName: aria.Role | null = null; /** Is this menu item checkable. */ private checkable = false; @@ -43,7 +42,7 @@ export class MenuItem { private highlight = false; /** Bound function to call when this menu item is clicked. */ - private actionHandler: Function|null = null; + private actionHandler: Function | null = null; /** * @param content Text caption to display as the content of the item, or a @@ -51,8 +50,9 @@ export class MenuItem { * @param opt_value Data/model associated with the menu item. */ constructor( - private readonly content: string|HTMLElement, - private readonly opt_value?: string) {} + private readonly content: string | HTMLElement, + private readonly opt_value?: string + ) {} /** * Creates the menuitem's DOM. @@ -60,25 +60,26 @@ export class MenuItem { * @returns Completed DOM. */ createDom(): Element { - const element = (document.createElement('div')); + const element = document.createElement('div'); element.id = idGenerator.getNextUniqueId(); this.element = element; // Set class and style // goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020. - element.className = 'blocklyMenuItem goog-menuitem ' + - (this.enabled ? '' : - 'blocklyMenuItemDisabled goog-menuitem-disabled ') + - (this.checked ? 'blocklyMenuItemSelected goog-option-selected ' : '') + - (this.highlight ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' : - '') + - (this.rightToLeft ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : ''); + element.className = + 'blocklyMenuItem goog-menuitem ' + + (this.enabled ? '' : 'blocklyMenuItemDisabled goog-menuitem-disabled ') + + (this.checked ? 'blocklyMenuItemSelected goog-option-selected ' : '') + + (this.highlight + ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' + : '') + + (this.rightToLeft ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : ''); - const content = (document.createElement('div')); + const content = document.createElement('div'); content.className = 'blocklyMenuItemContent goog-menuitem-content'; // Add a checkbox for checkable menu items. if (this.checkable) { - const checkbox = (document.createElement('div')); + const checkbox = document.createElement('div'); checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox'; content.appendChild(checkbox); } @@ -95,7 +96,10 @@ export class MenuItem { aria.setRole(element, this.roleName); } aria.setState( - element, aria.State.SELECTED, this.checkable && this.checked || false); + element, + aria.State.SELECTED, + (this.checkable && this.checked) || false + ); aria.setState(element, aria.State.DISABLED, !this.enabled); return element; @@ -112,7 +116,7 @@ export class MenuItem { * @returns The DOM element. * @internal */ - getElement(): Element|null { + getElement(): Element | null { return this.element; } @@ -132,7 +136,7 @@ export class MenuItem { * @returns value Value associated with the menu item. * @internal */ - getValue(): string|null { + getValue(): string | null { return this.opt_value ?? null; } diff --git a/core/metrics_manager.ts b/core/metrics_manager.ts index 615d19385..a24d257d1 100644 --- a/core/metrics_manager.ts +++ b/core/metrics_manager.ts @@ -21,7 +21,6 @@ import {Size} from './utils/size.js'; import * as toolboxUtils from './utils/toolbox.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * The manager for all workspace metrics calculations. */ @@ -42,7 +41,7 @@ export class MetricsManager implements IMetricsManager { * @returns An object containing width and height attributes, which will both * be zero if elem did not exist. */ - protected getDimensionsPx_(elem: IToolbox|null|IFlyout): Size { + protected getDimensionsPx_(elem: IToolbox | null | IFlyout): Size { let width = 0; let height = 0; if (elem) { @@ -64,8 +63,9 @@ export class MetricsManager implements IMetricsManager { * @returns The width and height of the flyout. */ getFlyoutMetrics(opt_own?: boolean): ToolboxMetrics { - const flyoutDimensions = - this.getDimensionsPx_(this.workspace_.getFlyout(opt_own)); + const flyoutDimensions = this.getDimensionsPx_( + this.workspace_.getFlyout(opt_own) + ); return { width: flyoutDimensions.width, height: flyoutDimensions.height, @@ -82,8 +82,9 @@ export class MetricsManager implements IMetricsManager { * @returns The object with the width, height and position of the toolbox. */ getToolboxMetrics(): ToolboxMetrics { - const toolboxDimensions = - this.getDimensionsPx_(this.workspace_.getToolbox()); + const toolboxDimensions = this.getDimensionsPx_( + this.workspace_.getToolbox() + ); return { width: toolboxDimensions.width, @@ -115,8 +116,9 @@ export class MetricsManager implements IMetricsManager { const flyoutMetrics = this.getFlyoutMetrics(true); const doesToolboxExist = !!this.workspace_.getToolbox(); const doesFlyoutExist = !!this.workspace_.getFlyout(true); - const toolboxPosition = - doesToolboxExist ? toolboxMetrics.position : flyoutMetrics.position; + const toolboxPosition = doesToolboxExist + ? toolboxMetrics.position + : flyoutMetrics.position; const atLeft = toolboxPosition === toolboxUtils.Position.LEFT; const atTop = toolboxPosition === toolboxUtils.Position.TOP; @@ -153,25 +155,32 @@ export class MetricsManager implements IMetricsManager { const toolboxMetrics = this.getToolboxMetrics(); const flyoutMetrics = this.getFlyoutMetrics(true); const doesToolboxExist = !!this.workspace_.getToolbox(); - const toolboxPosition = - doesToolboxExist ? toolboxMetrics.position : flyoutMetrics.position; + const toolboxPosition = doesToolboxExist + ? toolboxMetrics.position + : flyoutMetrics.position; if (this.workspace_.getToolbox()) { - if (toolboxPosition === toolboxUtils.Position.TOP || - toolboxPosition === toolboxUtils.Position.BOTTOM) { + if ( + toolboxPosition === toolboxUtils.Position.TOP || + toolboxPosition === toolboxUtils.Position.BOTTOM + ) { svgMetrics.height -= toolboxMetrics.height; } else if ( - toolboxPosition === toolboxUtils.Position.LEFT || - toolboxPosition === toolboxUtils.Position.RIGHT) { + toolboxPosition === toolboxUtils.Position.LEFT || + toolboxPosition === toolboxUtils.Position.RIGHT + ) { svgMetrics.width -= toolboxMetrics.width; } } else if (this.workspace_.getFlyout(true)) { - if (toolboxPosition === toolboxUtils.Position.TOP || - toolboxPosition === toolboxUtils.Position.BOTTOM) { + if ( + toolboxPosition === toolboxUtils.Position.TOP || + toolboxPosition === toolboxUtils.Position.BOTTOM + ) { svgMetrics.height -= flyoutMetrics.height; } else if ( - toolboxPosition === toolboxUtils.Position.LEFT || - toolboxPosition === toolboxUtils.Position.RIGHT) { + toolboxPosition === toolboxUtils.Position.LEFT || + toolboxPosition === toolboxUtils.Position.RIGHT + ) { svgMetrics.width -= flyoutMetrics.width; } } @@ -213,8 +222,10 @@ export class MetricsManager implements IMetricsManager { */ hasFixedEdges(): boolean { // This exists for optimization of bump logic. - return !this.workspace_.isMovableHorizontally() || - !this.workspace_.isMovableVertically(); + return ( + !this.workspace_.isMovableHorizontally() || + !this.workspace_.isMovableVertically() + ); } /** @@ -225,8 +236,9 @@ export class MetricsManager implements IMetricsManager { * again, if it is needed. * @returns The fixed edges of the scroll area. */ - protected getComputedFixedEdges_(opt_viewMetrics?: ContainerRegion): - FixedEdges { + protected getComputedFixedEdges_( + opt_viewMetrics?: ContainerRegion + ): FixedEdges { if (!this.hasFixedEdges()) { // Return early if there are no edges. return {}; @@ -257,8 +269,9 @@ export class MetricsManager implements IMetricsManager { * @returns The padded content area. */ protected getPaddedContent_( - viewMetrics: ContainerRegion, contentMetrics: ContainerRegion): - {top: number, bottom: number, left: number, right: number} { + viewMetrics: ContainerRegion, + contentMetrics: ContainerRegion + ): {top: number; bottom: number; left: number; right: number} { const contentBottom = contentMetrics.top + contentMetrics.height; const contentRight = contentMetrics.left + contentMetrics.width; @@ -269,14 +282,22 @@ export class MetricsManager implements IMetricsManager { // Add a padding around the content that is at least half a screen wide. // Ensure padding is wide enough that blocks can scroll over entire screen. - const top = - Math.min(contentMetrics.top - halfHeight, contentBottom - viewHeight); - const left = - Math.min(contentMetrics.left - halfWidth, contentRight - viewWidth); - const bottom = - Math.max(contentBottom + halfHeight, contentMetrics.top + viewHeight); - const right = - Math.max(contentRight + halfWidth, contentMetrics.left + viewWidth); + const top = Math.min( + contentMetrics.top - halfHeight, + contentBottom - viewHeight + ); + const left = Math.min( + contentMetrics.left - halfWidth, + contentRight - viewWidth + ); + const bottom = Math.max( + contentBottom + halfHeight, + contentMetrics.top + viewHeight + ); + const right = Math.max( + contentRight + halfWidth, + contentMetrics.left + viewWidth + ); return {top, bottom, left, right}; } @@ -295,8 +316,10 @@ export class MetricsManager implements IMetricsManager { * @returns The metrics for the scroll container. */ getScrollMetrics( - opt_getWorkspaceCoordinates?: boolean, opt_viewMetrics?: ContainerRegion, - opt_contentMetrics?: ContainerRegion): ContainerRegion { + opt_getWorkspaceCoordinates?: boolean, + opt_viewMetrics?: ContainerRegion, + opt_contentMetrics?: ContainerRegion + ): ContainerRegion { const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1; const viewMetrics = opt_viewMetrics || this.getViewMetrics(false); const contentMetrics = opt_contentMetrics || this.getContentMetrics(); @@ -307,13 +330,15 @@ export class MetricsManager implements IMetricsManager { // Use combination of fixed bounds and padded content to make scroll area. const top = - fixedEdges.top !== undefined ? fixedEdges.top : paddedContent.top; + fixedEdges.top !== undefined ? fixedEdges.top : paddedContent.top; const left = - fixedEdges.left !== undefined ? fixedEdges.left : paddedContent.left; - const bottom = fixedEdges.bottom !== undefined ? fixedEdges.bottom : - paddedContent.bottom; + fixedEdges.left !== undefined ? fixedEdges.left : paddedContent.left; + const bottom = + fixedEdges.bottom !== undefined + ? fixedEdges.bottom + : paddedContent.bottom; const right = - fixedEdges.right !== undefined ? fixedEdges.right : paddedContent.right; + fixedEdges.right !== undefined ? fixedEdges.right : paddedContent.right; return { top: top / scale, @@ -378,8 +403,11 @@ export class MetricsManager implements IMetricsManager { const absoluteMetrics = this.getAbsoluteMetrics(); const viewMetrics = this.getViewMetrics(); const contentMetrics = this.getContentMetrics(); - const scrollMetrics = - this.getScrollMetrics(false, viewMetrics, contentMetrics); + const scrollMetrics = this.getScrollMetrics( + false, + viewMetrics, + contentMetrics + ); return { contentHeight: contentMetrics.height, @@ -464,4 +492,7 @@ export type FixedEdges = MetricsManager.FixedEdges; export type UiMetrics = MetricsManager.UiMetrics; registry.register( - registry.Type.METRICS_MANAGER, registry.DEFAULT, MetricsManager); + registry.Type.METRICS_MANAGER, + registry.DEFAULT, + MetricsManager +); diff --git a/core/msg.ts b/core/msg.ts index 50fdf3a8f..7ca611253 100644 --- a/core/msg.ts +++ b/core/msg.ts @@ -7,7 +7,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.Msg'); - /** A dictionary of localised messages. */ export const Msg: {[key: string]: string} = Object.create(null); @@ -22,8 +21,8 @@ export const Msg: {[key: string]: string} = Object.create(null); * * @param locale An object defining the messages for a given language. */ -export const setLocale = function(locale: {[key: string]: string}) { - Object.keys(locale).forEach(function(k) { +export const setLocale = function (locale: {[key: string]: string}) { + Object.keys(locale).forEach(function (k) { Msg[k] = locale[k]; }); }; diff --git a/core/mutator.ts b/core/mutator.ts deleted file mode 100644 index 58ce528e9..000000000 --- a/core/mutator.ts +++ /dev/null @@ -1,564 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing a mutator dialog. A mutator allows the - * user to change the shape of a block using a nested blocks editor. - * - * @class - */ -import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Mutator'); - -// Unused import preserved for side-effects. Remove if unneeded. -import './events/events_bubble_open.js'; - -import type {Block} from './block.js'; -import type {BlockSvg} from './block_svg.js'; -import type {BlocklyOptions} from './blockly_options.js'; -import {Bubble} from './bubble.js'; -import {config} from './config.js'; -import type {Connection} from './connection.js'; -import type {Abstract} from './events/events_abstract.js'; -import {BlockChange} from './events/events_block_change.js'; -import * as eventUtils from './events/utils.js'; -import {Icon} from './icon.js'; -import {Options} from './options.js'; -import type {Coordinate} from './utils/coordinate.js'; -import * as dom from './utils/dom.js'; -import {Svg} from './utils/svg.js'; -import * as toolbox from './utils/toolbox.js'; -import * as xml from './utils/xml.js'; -import * as deprecation from './utils/deprecation.js'; -import type {WorkspaceSvg} from './workspace_svg.js'; - - -/** - * Class for a mutator dialog. - */ -export class Mutator extends Icon { - private quarkNames: string[]; - - /** - * Workspace in the mutator's bubble. - * Due to legacy code in procedure block definitions, this name - * cannot change. - */ - private workspace_: WorkspaceSvg|null = null; - - /** Width of workspace. */ - private workspaceWidth = 0; - - /** Height of workspace. */ - private workspaceHeight = 0; - - /** - * The SVG element that is the parent of the mutator workspace, or null if - * not created. - */ - private svgDialog: SVGSVGElement|null = null; - - /** - * The root block of the mutator workspace, created by decomposing the - * source block. - */ - private rootBlock: BlockSvg|null = null; - - /** - * Function registered on the main workspace to update the mutator contents - * when the main workspace changes. - */ - private sourceListener: Function|null = null; - - /** - * The PID associated with the updateWorkpace_ timeout, or null if no timeout - * is currently running. - */ - private updateWorkspacePid: ReturnType|null = null; - - /** @param quarkNames List of names of sub-blocks for flyout. */ - constructor(quarkNames: string[], block?: BlockSvg) { - if (!block) { - deprecation.warn( - 'Calling the Mutator constructor without passing the block it is attached to', - 'version 9', 'version 10', - 'the constructor by passing the list of subblocks and the block instance to attach the mutator to'); - } - super(block ?? null); - this.quarkNames = quarkNames; - } - - /** - * Set the block this mutator is associated with. - * - * @param block The block associated with this mutator. - * @internal - */ - setBlock(block: BlockSvg) { - this.block_ = block; - } - - /** - * Returns the workspace inside this mutator icon's bubble. - * - * @returns The workspace inside this mutator icon's bubble or null if the - * mutator isn't open. - * @internal - */ - getWorkspace(): WorkspaceSvg|null { - return this.workspace_; - } - - /** - * Draw the mutator icon. - * - * @param group The icon group. - */ - protected override drawIcon_(group: Element) { - // Square with rounded corners. - dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyIconShape', - 'rx': '4', - 'ry': '4', - 'height': '16', - 'width': '16', - }, - group); - // Gear teeth. - dom.createSvgElement( - Svg.PATH, { - 'class': 'blocklyIconSymbol', - 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,' + - '0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,' + - '-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,' + - '-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,' + - '-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 ' + - '-0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,' + - '0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z', - }, - group); - // Axle hole. - dom.createSvgElement( - Svg.CIRCLE, - {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, group); - } - - /** - * Clicking on the icon toggles if the mutator bubble is visible. - * Disable if block is uneditable. - * - * @param e Mouse click event. - */ - protected override iconClick_(e: PointerEvent) { - if (this.getBlock().isEditable()) { - super.iconClick_(e); - } - } - - /** - * Create the editor for the mutator's bubble. - * - * @returns The top-level node of the editor. - */ - private createEditor(): SVGSVGElement { - /* Create the editor. Here's the markup that will be generated: - - [Workspace] - - */ - this.svgDialog = dom.createSvgElement( - Svg.SVG, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH}); - // Convert the list of names into a list of XML objects for the flyout. - let quarkXml; - if (this.quarkNames.length) { - quarkXml = xml.createElement('xml'); - for (let i = 0, quarkName; quarkName = this.quarkNames[i]; i++) { - const element = xml.createElement('block'); - element.setAttribute('type', quarkName); - quarkXml.appendChild(element); - } - } else { - quarkXml = null; - } - const block = this.getBlock(); - const workspaceOptions = new Options(({ - // If you want to enable disabling, also remove the - // event filter from workspaceChanged_ . - 'disable': false, - 'parentWorkspace': block.workspace, - 'media': block.workspace.options.pathToMedia, - 'rtl': block.RTL, - 'horizontalLayout': false, - 'renderer': block.workspace.options.renderer, - 'rendererOverrides': block.workspace.options.rendererOverrides, - } as BlocklyOptions)); - workspaceOptions.toolboxPosition = - block.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT; - const hasFlyout = !!quarkXml; - if (hasFlyout) { - workspaceOptions.languageTree = toolbox.convertToolboxDefToJson(quarkXml); - } - this.workspace_ = this.newWorkspaceSvg(workspaceOptions); - this.workspace_.internalIsMutator = true; - this.workspace_.addChangeListener(eventUtils.disableOrphans); - - // Mutator flyouts go inside the mutator workspace's rather than in - // a top level SVG. Instead of handling scale themselves, mutators - // inherit scale from the parent workspace. - // To fix this, scale needs to be applied at a different level in the DOM. - const flyoutSvg = hasFlyout ? this.workspace_.addFlyout(Svg.G) : null; - const background = this.workspace_.createDom('blocklyMutatorBackground'); - - if (flyoutSvg) { - // Insert the flyout after the but before the block canvas so that - // the flyout is underneath in z-order. This makes blocks layering during - // dragging work properly. - background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_); - } - this.svgDialog.appendChild(background); - - return this.svgDialog; - } - - /** - * @internal - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - newWorkspaceSvg(options: Options): WorkspaceSvg { - throw new Error( - 'The implementation of newWorkspaceSvg should be ' + - 'monkey-patched in by blockly.ts'); - } - - /** Add or remove the UI indicating if this icon may be clicked or not. */ - override updateEditable() { - super.updateEditable(); - if (!this.getBlock().isInFlyout) { - if (this.getBlock().isEditable()) { - if (this.iconGroup_) { - dom.removeClass(this.iconGroup_, 'blocklyIconGroupReadonly'); - } - } else { - // Close any mutator bubble. Icon is not clickable. - this.setVisible(false); - if (this.iconGroup_) { - dom.addClass(this.iconGroup_, 'blocklyIconGroupReadonly'); - } - } - } - } - - /** Resize the bubble to match the size of the workspace. */ - private resizeBubble() { - // If the bubble exists, the workspace also exists. - if (!this.workspace_) { - return; - } - const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH; - const canvas = this.workspace_.getCanvas(); - const workspaceSize = canvas.getBBox(); - let width = workspaceSize.width + workspaceSize.x; - let height = workspaceSize.height + doubleBorderWidth * 3; - const flyout = this.workspace_.getFlyout(); - if (flyout) { - const flyoutScrollMetrics = - flyout.getWorkspace().getMetricsManager().getScrollMetrics(); - height = Math.max(height, flyoutScrollMetrics.height + 20); - width += flyout.getWidth(); - } - const isRtl = this.getBlock().RTL; - if (isRtl) { - width = -workspaceSize.x; - } - width += doubleBorderWidth * 3; - // Only resize if the size difference is significant. Eliminates - // shuddering. - if (Math.abs(this.workspaceWidth - width) > doubleBorderWidth || - Math.abs(this.workspaceHeight - height) > doubleBorderWidth) { - // Record some layout information for workspace metrics. - this.workspaceWidth = width; - this.workspaceHeight = height; - // Resize the bubble. - this.bubble_!.setBubbleSize( - width + doubleBorderWidth, height + doubleBorderWidth); - this.svgDialog!.setAttribute('width', `${width}`); - this.svgDialog!.setAttribute('height', `${height}`); - this.workspace_.setCachedParentSvgSize(width, height); - } - - if (isRtl) { - // Scroll the workspace to always left-align. - canvas.setAttribute('transform', `translate(${this.workspaceWidth}, 0)`); - } - this.workspace_.resize(); - } - - /** A method handler for when the bubble is moved. */ - private onBubbleMove() { - if (this.workspace_) { - this.workspace_.recordDragTargets(); - } - } - - /** - * Show or hide the mutator bubble. - * - * @param visible True if the bubble should be visible. - */ - override setVisible(visible: boolean) { - if (visible === this.isVisible()) { - // No change. - return; - } - const block = this.getBlock(); - if (visible) { - // Create the bubble. - this.bubble_ = new Bubble( - block.workspace, this.createEditor(), block.pathObject.svgPath, - (this.iconXY_ as Coordinate), null, null); - // The workspace was created in createEditor. - const ws = this.workspace_!; - // Expose this mutator's block's ID on its top-level SVG group. - this.bubble_.setSvgId(block.id); - this.bubble_.registerMoveEvent(this.onBubbleMove.bind(this)); - const tree = ws.options.languageTree; - const flyout = ws.getFlyout(); - if (tree) { - flyout!.init(ws); - flyout!.show(tree); - } - - this.rootBlock = block.decompose!(ws)!; - const blocks = this.rootBlock.getDescendants(false); - for (let i = 0, child; child = blocks[i]; i++) { - child.render(); - } - // The root block should not be draggable or deletable. - this.rootBlock.setMovable(false); - this.rootBlock.setDeletable(false); - let margin; - let x; - if (flyout) { - margin = flyout.CORNER_RADIUS * 2; - x = this.rootBlock.RTL ? flyout.getWidth() + margin : margin; - } else { - margin = 16; - x = margin; - } - if (block.RTL) { - x = -x; - } - this.rootBlock.moveBy(x, margin); - // Save the initial connections, then listen for further changes. - if (block.saveConnections) { - const thisRootBlock = this.rootBlock; - block.saveConnections(thisRootBlock); - this.sourceListener = () => { - const currentBlock = this.getBlock(); - if (currentBlock.saveConnections) { - currentBlock.saveConnections(thisRootBlock); - } - }; - block.workspace.addChangeListener(this.sourceListener); - } - this.resizeBubble(); - // When the mutator's workspace changes, update the source block. - const boundListener = this.workspaceChanged.bind(this); - ws.addChangeListener(boundListener); - if (flyout) flyout.getWorkspace().addChangeListener(boundListener); - // Update the source block immediately after the bubble becomes visible. - this.updateWorkspace(); - this.applyColour(); - } else { - // Dispose of the bubble. - this.svgDialog = null; - this.workspace_!.dispose(); - this.workspace_ = null; - this.rootBlock = null; - this.bubble_?.dispose(); - this.bubble_ = null; - this.workspaceWidth = 0; - this.workspaceHeight = 0; - if (this.sourceListener) { - block.workspace.removeChangeListener(this.sourceListener); - this.sourceListener = null; - } - } - eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( - block, visible, 'mutator')); - } - - /** - * Fired whenever a change is made to the mutator's workspace. - * - * @param e Custom data for event. - */ - private workspaceChanged(e: Abstract) { - if (!this.shouldIgnoreMutatorEvent_(e) && !this.updateWorkspacePid) { - this.updateWorkspacePid = setTimeout(() => { - this.updateWorkspacePid = null; - this.updateWorkspace(); - }, 0); - } - } - - /** - * Returns whether the given event in the mutator workspace should be ignored - * when deciding whether to update the workspace and compose the block or not. - * - * @param e The event. - * @returns Whether to ignore the event or not. - */ - shouldIgnoreMutatorEvent_(e: Abstract) { - return e.isUiEvent || e.type === eventUtils.CREATE || - e.type === eventUtils.CHANGE && - (e as BlockChange).element === 'disabled'; - } - - /** - * Updates the source block when the mutator's blocks are changed. - * Bump down any block that's too high. - */ - private updateWorkspace() { - if (!this.workspace_!.isDragging()) { - const blocks = this.workspace_!.getTopBlocks(false); - const MARGIN = 20; - - for (let b = 0, block; block = blocks[b]; b++) { - const blockXY = block.getRelativeToSurfaceXY(); - - // Bump any block that's above the top back inside. - if (blockXY.y < MARGIN) { - block.moveBy(0, MARGIN - blockXY.y); - } - // Bump any block overlapping the flyout back inside. - if (block.RTL) { - let right = -MARGIN; - const flyout = this.workspace_!.getFlyout(); - if (flyout) { - right -= flyout.getWidth(); - } - if (blockXY.x > right) { - block.moveBy(right - blockXY.x, 0); - } - } else if (blockXY.x < MARGIN) { - block.moveBy(MARGIN - blockXY.x, 0); - } - } - } - - // When the mutator's workspace changes, update the source block. - if (this.rootBlock && this.rootBlock.workspace === this.workspace_) { - const existingGroup = eventUtils.getGroup(); - if (!existingGroup) { - eventUtils.setGroup(true); - } - const block = this.getBlock(); - const oldExtraState = BlockChange.getExtraBlockState_(block); - - block.compose!(this.rootBlock); - - const newExtraState = BlockChange.getExtraBlockState_(block); - if (oldExtraState !== newExtraState) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - block, 'mutation', null, oldExtraState, newExtraState)); - // Ensure that any bump is part of this mutation's event group. - const mutationGroup = eventUtils.getGroup(); - setTimeout(function() { - const oldGroup = eventUtils.getGroup(); - eventUtils.setGroup(mutationGroup); - block.bumpNeighbours(); - eventUtils.setGroup(oldGroup); - }, config.bumpDelay); - } - - // Don't update the bubble until the drag has ended, to avoid moving - // blocks under the cursor. - if (!this.workspace_!.isDragging()) { - setTimeout(() => this.resizeBubble(), 0); - } - eventUtils.setGroup(existingGroup); - } - } - - /** Dispose of this mutator. */ - override dispose() { - this.getBlock().mutator = null; - super.dispose(); - } - - /** Update the styles on all blocks in the mutator. */ - updateBlockStyle() { - const ws = this.workspace_; - - if (ws && ws.getAllBlocks(false)) { - const workspaceBlocks = ws.getAllBlocks(false); - for (let i = 0, block; block = workspaceBlocks[i]; i++) { - block.setStyle(block.getStyleName()); - } - - const flyout = ws.getFlyout(); - if (flyout) { - const flyoutBlocks = flyout.getWorkspace().getAllBlocks(false); - for (let i = 0, block; block = flyoutBlocks[i]; i++) { - block.setStyle(block.getStyleName()); - } - } - } - } - - /** - * Reconnect an block to a mutated input. - * - * @param connectionChild Connection on child block. - * @param block Parent block. - * @param inputName Name of input on parent block. - * @returns True iff a reconnection was made, false otherwise. - */ - static reconnect( - connectionChild: Connection, block: Block, inputName: string): boolean { - if (!connectionChild || !connectionChild.getSourceBlock().workspace) { - return false; // No connection or block has been deleted. - } - const connectionParent = block.getInput(inputName)!.connection; - const currentParent = connectionChild.targetBlock(); - if ((!currentParent || currentParent === block) && connectionParent && - connectionParent.targetConnection !== connectionChild) { - if (connectionParent.isConnected()) { - // There's already something connected here. Get rid of it. - connectionParent.disconnect(); - } - connectionParent.connect(connectionChild); - return true; - } - return false; - } - - /** - * Get the parent workspace of a workspace that is inside a mutator, taking - * into account whether it is a flyout. - * - * @param workspace The workspace that is inside a mutator. - * @returns The mutator's parent workspace or null. - */ - static findParentWs(workspace: WorkspaceSvg): WorkspaceSvg|null { - let outerWs = null; - if (workspace && workspace.options) { - const parent = workspace.options.parentWorkspace; - // If we were in a flyout in a mutator, need to go up two levels to find - // the actual parent. - if (workspace.isFlyout) { - if (parent && parent.options) { - outerWs = parent.options.parentWorkspace; - } - } else if (parent) { - outerWs = parent; - } - } - return outerWs; - } -} diff --git a/core/names.ts b/core/names.ts index 0a7ca3888..8f8b45c07 100644 --- a/core/names.ts +++ b/core/names.ts @@ -18,13 +18,12 @@ import type {VariableMap} from './variable_map.js'; import * as Variables from './variables.js'; import type {Workspace} from './workspace.js'; - /** * Class for a database of entity names (variables, procedures, etc). */ export class Names { static DEVELOPER_VARIABLE_TYPE: NameType; - private readonly variablePrefix_: string; + private readonly variablePrefix: string; /** A set of reserved words. */ private readonly reservedWords: Set; @@ -41,7 +40,7 @@ export class Names { /** * The variable map from the workspace, containing Blockly variable models. */ - private variableMap_: VariableMap|null = null; + private variableMap: VariableMap | null = null; /** * @param reservedWordsList A comma-separated string of words that are illegal @@ -51,10 +50,11 @@ export class Names { */ constructor(reservedWordsList: string, opt_variablePrefix?: string) { /** The prefix to attach to variable names in generated code. */ - this.variablePrefix_ = opt_variablePrefix || ''; + this.variablePrefix = opt_variablePrefix || ''; - this.reservedWords = - new Set(reservedWordsList ? reservedWordsList.split(',') : []); + this.reservedWords = new Set( + reservedWordsList ? reservedWordsList.split(',') : [] + ); } /** @@ -63,7 +63,7 @@ export class Names { reset() { this.db.clear(); this.dbReverse.clear(); - this.variableMap_ = null; + this.variableMap = null; } /** @@ -72,7 +72,7 @@ export class Names { * @param map The map to track. */ setVariableMap(map: VariableMap) { - this.variableMap_ = map; + this.variableMap = map; } /** @@ -83,17 +83,18 @@ export class Names { * @returns The name of the referenced variable, or null if there was no * variable map or the variable was not found in the map. */ - private getNameForUserVariable_(id: string): string|null { - if (!this.variableMap_) { + private getNameForUserVariable(id: string): string | null { + if (!this.variableMap) { console.warn( - 'Deprecated call to Names.prototype.getName without ' + + 'Deprecated call to Names.prototype.getName without ' + 'defining a variable map. To fix, add the following code in your ' + - 'generator\'s init() function:\n' + + "generator's init() function:\n" + 'Blockly.YourGeneratorName.nameDB_.setVariableMap(' + - 'workspace.getVariableMap());'); + 'workspace.getVariableMap());' + ); return null; } - const variable = this.variableMap_.getVariableById(id); + const variable = this.variableMap.getVariableById(id); if (variable) { return variable.name; } @@ -120,8 +121,9 @@ export class Names { // eslint-disable-next-line @typescript-eslint/no-unused-vars populateProcedures(workspace: Workspace) { throw new Error( - 'The implementation of populateProcedures should be ' + - 'monkey-patched in by blockly.ts'); + 'The implementation of populateProcedures should be ' + + 'monkey-patched in by blockly.ts' + ); } /** @@ -132,10 +134,10 @@ export class Names { * 'DEVELOPER_VARIABLE', etc...). * @returns An entity name that is legal in the exported language. */ - getName(nameOrId: string, type: NameType|string): string { + getName(nameOrId: string, type: NameType | string): string { let name = nameOrId; if (type === NameType.VARIABLE) { - const varName = this.getNameForUserVariable_(nameOrId); + const varName = this.getNameForUserVariable(nameOrId); if (varName) { // Successful ID lookup. name = varName; @@ -144,9 +146,9 @@ export class Names { const normalizedName = name.toLowerCase(); const isVar = - type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; + type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; - const prefix = isVar ? this.variablePrefix_ : ''; + const prefix = isVar ? this.variablePrefix : ''; if (!this.db.has(type)) { this.db.set(type, new Map()); } @@ -166,7 +168,7 @@ export class Names { * 'DEVELOPER_VARIABLE', etc...). * @returns A list of Blockly entity names (no constraints). */ - getUserNames(type: NameType|string): string[] { + getUserNames(type: NameType | string): string[] { const userNames = this.db.get(type)?.keys(); return userNames ? Array.from(userNames) : []; } @@ -182,19 +184,21 @@ export class Names { * 'DEVELOPER_VARIABLE', etc...). * @returns An entity name that is legal in the exported language. */ - getDistinctName(name: string, type: NameType|string): string { - let safeName = this.safeName_(name); - let i: number|null = null; - while (this.dbReverse.has(safeName + (i ?? '')) || - this.reservedWords.has(safeName + (i ?? ''))) { + getDistinctName(name: string, type: NameType | string): string { + let safeName = this.safeName(name); + let i: number | null = null; + while ( + this.dbReverse.has(safeName + (i ?? '')) || + this.reservedWords.has(safeName + (i ?? '')) + ) { // Collision with existing name. Create a unique name. i = i ? i + 1 : 2; } - safeName += (i ?? ''); + safeName += i ?? ''; this.dbReverse.add(safeName); const isVar = - type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; - const prefix = isVar ? this.variablePrefix_ : ''; + type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE; + const prefix = isVar ? this.variablePrefix : ''; return prefix + safeName; } @@ -206,7 +210,7 @@ export class Names { * @param name Potentially illegal entity name. * @returns Safe entity name. */ - private safeName_(name: string): string { + private safeName(name: string): string { if (!name) { name = Msg['UNNAMED_KEY'] || 'unnamed'; } else { diff --git a/core/observable_procedure_map.ts b/core/observable_procedure_map.ts index 8105c905e..e8722bcbf 100644 --- a/core/observable_procedure_map.ts +++ b/core/observable_procedure_map.ts @@ -8,9 +8,10 @@ import {IProcedureMap} from './interfaces/i_procedure_map.js'; import type {IProcedureModel} from './interfaces/i_procedure_model.js'; import {isObservable} from './interfaces/i_observable.js'; - -export class ObservableProcedureMap extends - Map implements IProcedureMap { +export class ObservableProcedureMap + extends Map + implements IProcedureMap +{ /** @internal */ constructor() { super(); diff --git a/core/options.ts b/core/options.ts index 4f66582bb..03060f9f3 100644 --- a/core/options.ts +++ b/core/options.ts @@ -22,7 +22,6 @@ import type {Metrics} from './utils/metrics.js'; import * as toolbox from './utils/toolbox.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Parse the user-specified options, using reasonable defaults where behaviour * is unspecified. @@ -35,7 +34,7 @@ export class Options { disable: boolean; readOnly: boolean; maxBlocks: number; - maxInstances: {[key: string]: number}|null; + maxInstances: {[key: string]: number} | null; modalInputs: boolean; pathToMedia: string; hasCategories: boolean; @@ -46,21 +45,21 @@ export class Options { hasSounds: boolean; hasCss: boolean; horizontalLayout: boolean; - languageTree: toolbox.ToolboxInfo|null; + languageTree: toolbox.ToolboxInfo | null; gridOptions: GridOptions; zoomOptions: ZoomOptions; toolboxPosition: toolbox.Position; theme: Theme; renderer: string; - rendererOverrides: {[rendererConstant: string]: any}|null; + rendererOverrides: {[rendererConstant: string]: any} | null; /** * The SVG element for the grid pattern. * Created during injection. */ - gridPattern: SVGElement|null = null; - parentWorkspace: WorkspaceSvg|null; - plugins: {[key: string]: (new(...p1: any[]) => any)|string}; + gridPattern: SVGElement | null = null; + parentWorkspace: WorkspaceSvg | null; + plugins: {[key: string]: (new (...p1: any[]) => any) | string}; /** * If set, sets the translation of the workspace to match the scrollbars. @@ -69,13 +68,13 @@ export class Options { * argument Contains an x and/or y property which is a float between 0 * and 1 specifying the degree of scrolling. */ - setMetrics?: ((p1: {x?: number, y?: number}) => void) = undefined; + setMetrics?: (p1: {x?: number; y?: number}) => void = undefined; /** * A function that returns a metrics * object that describes the current workspace. */ - getMetrics?: (() => Metrics) = undefined; + getMetrics?: () => Metrics = undefined; /** * @param options Dictionary of options. @@ -92,18 +91,19 @@ export class Options { let hasSounds = false; const readOnly = !!options['readOnly']; if (!readOnly) { - toolboxJsonDef = - toolbox.convertToolboxDefToJson(options['toolbox'] ?? null); + toolboxJsonDef = toolbox.convertToolboxDefToJson( + options['toolbox'] ?? null + ); hasCategories = toolbox.hasCategories(toolboxJsonDef); const rawHasTrashcan = options['trashcan']; hasTrashcan = - rawHasTrashcan === undefined ? hasCategories : rawHasTrashcan; + rawHasTrashcan === undefined ? hasCategories : rawHasTrashcan; const rawHasCollapse = options['collapse']; hasCollapse = - rawHasCollapse === undefined ? hasCategories : rawHasCollapse; + rawHasCollapse === undefined ? hasCategories : rawHasCollapse; const rawHasComments = options['comments']; hasComments = - rawHasComments === undefined ? hasCategories : rawHasComments; + rawHasComments === undefined ? hasCategories : rawHasComments; const rawHasDisable = options['disable']; hasDisable = rawHasDisable === undefined ? hasCategories : rawHasDisable; const rawHasSounds = options['sounds']; @@ -127,11 +127,12 @@ export class Options { let toolboxPosition: toolbox.Position; if (horizontalLayout) { - toolboxPosition = - toolboxAtStart ? toolbox.Position.TOP : toolbox.Position.BOTTOM; + toolboxPosition = toolboxAtStart + ? toolbox.Position.TOP + : toolbox.Position.BOTTOM; } else { - toolboxPosition = toolboxAtStart === rtl ? toolbox.Position.RIGHT : - toolbox.Position.LEFT; + toolboxPosition = + toolboxAtStart === rtl ? toolbox.Position.RIGHT : toolbox.Position.LEFT; } let hasCss = options['css']; @@ -140,8 +141,9 @@ export class Options { } let pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; if (options['media']) { - pathToMedia = options['media'].endsWith('/') ? options['media'] : - options['media'] + '/'; + pathToMedia = options['media'].endsWith('/') + ? options['media'] + : options['media'] + '/'; } else if ('path' in options) { // 'path' is a deprecated option which has been replaced by 'media'. deprecation.warn('path', 'Nov 2014', 'Jul 2023', 'media'); @@ -149,7 +151,7 @@ export class Options { } const rawOneBasedIndex = options['oneBasedIndex']; const oneBasedIndex = - rawOneBasedIndex === undefined ? true : rawOneBasedIndex; + rawOneBasedIndex === undefined ? true : rawOneBasedIndex; const renderer = options['renderer'] || 'geras'; const plugins = options['plugins'] || {}; @@ -206,11 +208,15 @@ export class Options { * @returns Normalized move options. */ private static parseMoveOptions_( - options: BlocklyOptions, hasCategories: boolean): MoveOptions { + options: BlocklyOptions, + hasCategories: boolean + ): MoveOptions { const move = options['move'] || {}; const moveOptions = {} as MoveOptions; - if (move['scrollbars'] === undefined && - options['scrollbars'] === undefined) { + if ( + move['scrollbars'] === undefined && + options['scrollbars'] === undefined + ) { moveOptions.scrollbars = hasCategories; } else if (typeof move['scrollbars'] === 'object') { moveOptions.scrollbars = { @@ -220,12 +226,15 @@ export class Options { // Convert scrollbars object to boolean if they have the same value. // This allows us to easily check for whether any scrollbars exist using // !!moveOptions.scrollbars. - if (moveOptions.scrollbars.horizontal && - moveOptions.scrollbars.vertical) { + if ( + moveOptions.scrollbars.horizontal && + moveOptions.scrollbars.vertical + ) { moveOptions.scrollbars = true; } else if ( - !moveOptions.scrollbars.horizontal && - !moveOptions.scrollbars.vertical) { + !moveOptions.scrollbars.horizontal && + !moveOptions.scrollbars.vertical + ) { moveOptions.scrollbars = false; } } else { @@ -312,7 +321,7 @@ export class Options { gridOptions.spacing = Number(grid['spacing']) || 0; gridOptions.colour = grid['colour'] || '#888'; gridOptions.length = - grid['length'] === undefined ? 1 : Number(grid['length']); + grid['length'] === undefined ? 1 : Number(grid['length']); gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap']; return gridOptions; } @@ -332,7 +341,9 @@ export class Options { return theme; } return Theme.defineTheme( - theme.name || 'builtin' + idGenerator.getNextUniqueId(), theme); + theme.name || 'builtin' + idGenerator.getNextUniqueId(), + theme + ); } } @@ -346,7 +357,7 @@ export namespace Options { export interface MoveOptions { drag: boolean; - scrollbars: boolean|ScrollbarOptions; + scrollbars: boolean | ScrollbarOptions; wheel: boolean; } diff --git a/core/positionable_helpers.ts b/core/positionable_helpers.ts index b0967c48d..ae221468a 100644 --- a/core/positionable_helpers.ts +++ b/core/positionable_helpers.ts @@ -14,7 +14,6 @@ import type {Size} from './utils/size.js'; import * as toolbox from './utils/toolbox.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Enum for vertical positioning. * @@ -22,7 +21,7 @@ import type {WorkspaceSvg} from './workspace_svg.js'; */ export enum verticalPosition { TOP, - BOTTOM + BOTTOM, } /** @@ -32,7 +31,7 @@ export enum verticalPosition { */ export enum horizontalPosition { LEFT, - RIGHT + RIGHT, } /** @@ -52,7 +51,7 @@ export interface Position { */ export enum bumpDirection { UP, - DOWN + DOWN, } /** @@ -71,21 +70,29 @@ export enum bumpDirection { * @internal */ export function getStartPositionRect( - position: Position, size: Size, horizontalPadding: number, - verticalPadding: number, metrics: UiMetrics, - workspace: WorkspaceSvg): Rect { + position: Position, + size: Size, + horizontalPadding: number, + verticalPadding: number, + metrics: UiMetrics, + workspace: WorkspaceSvg +): Rect { // Horizontal positioning. let left = 0; const hasVerticalScrollbar = - workspace.scrollbar && workspace.scrollbar.canScrollVertically(); + workspace.scrollbar && workspace.scrollbar.canScrollVertically(); if (position.horizontal === horizontalPosition.LEFT) { left = metrics.absoluteMetrics.left + horizontalPadding; if (hasVerticalScrollbar && workspace.RTL) { left += Scrollbar.scrollbarThickness; } - } else { // position.horizontal === horizontalPosition.RIGHT - left = metrics.absoluteMetrics.left + metrics.viewMetrics.width - - size.width - horizontalPadding; + } else { + // position.horizontal === horizontalPosition.RIGHT + left = + metrics.absoluteMetrics.left + + metrics.viewMetrics.width - + size.width - + horizontalPadding; if (hasVerticalScrollbar && !workspace.RTL) { left -= Scrollbar.scrollbarThickness; } @@ -94,9 +101,13 @@ export function getStartPositionRect( let top = 0; if (position.vertical === verticalPosition.TOP) { top = metrics.absoluteMetrics.top + verticalPadding; - } else { // position.vertical === verticalPosition.BOTTOM - top = metrics.absoluteMetrics.top + metrics.viewMetrics.height - - size.height - verticalPadding; + } else { + // position.vertical === verticalPosition.BOTTOM + top = + metrics.absoluteMetrics.top + + metrics.viewMetrics.height - + size.height - + verticalPadding; if (workspace.scrollbar && workspace.scrollbar.canScrollHorizontally()) { // The scrollbars are always positioned on the bottom if they exist. top -= Scrollbar.scrollbarThickness; @@ -117,13 +128,16 @@ export function getStartPositionRect( * @internal */ export function getCornerOppositeToolbox( - workspace: WorkspaceSvg, metrics: UiMetrics): Position { + workspace: WorkspaceSvg, + metrics: UiMetrics +): Position { const leftCorner = - metrics.toolboxMetrics.position !== toolbox.Position.LEFT && - (!workspace.horizontalLayout || workspace.RTL); + metrics.toolboxMetrics.position !== toolbox.Position.LEFT && + (!workspace.horizontalLayout || workspace.RTL); const topCorner = metrics.toolboxMetrics.position === toolbox.Position.BOTTOM; - const hPosition = - leftCorner ? horizontalPosition.LEFT : horizontalPosition.RIGHT; + const hPosition = leftCorner + ? horizontalPosition.LEFT + : horizontalPosition.RIGHT; const vPosition = topCorner ? verticalPosition.TOP : verticalPosition.BOTTOM; return {horizontal: hPosition, vertical: vPosition}; } @@ -143,8 +157,11 @@ export function getCornerOppositeToolbox( * @internal */ export function bumpPositionRect( - startRect: Rect, margin: number, bumpDir: bumpDirection, - savedPositions: Rect[]): Rect { + startRect: Rect, + margin: number, + bumpDir: bumpDirection, + savedPositions: Rect[] +): Rect { let top = startRect.top; const left = startRect.left; const width = startRect.right - startRect.left; @@ -157,7 +174,8 @@ export function bumpPositionRect( if (boundingRect.intersects(otherEl)) { if (bumpDir === bumpDirection.UP) { top = otherEl.top - height - margin; - } else { // bumpDir === bumpDirection.DOWN + } else { + // bumpDir === bumpDirection.DOWN top = otherEl.bottom + margin; } // Recheck other savedPositions diff --git a/core/procedures.ts b/core/procedures.ts index 1bc1d9d80..03e4db229 100644 --- a/core/procedures.ts +++ b/core/procedures.ts @@ -23,14 +23,22 @@ import {Names} from './names.js'; import {IParameterModel} from './interfaces/i_parameter_model.js'; import {IProcedureMap} from './interfaces/i_procedure_map.js'; import {IProcedureModel} from './interfaces/i_procedure_model.js'; -import {IProcedureBlock, isProcedureBlock} from './interfaces/i_procedure_block.js'; -import {isLegacyProcedureCallBlock, isLegacyProcedureDefBlock, ProcedureBlock, ProcedureTuple} from './interfaces/i_legacy_procedure_blocks.js'; +import { + IProcedureBlock, + isProcedureBlock, +} from './interfaces/i_procedure_block.js'; +import { + isLegacyProcedureCallBlock, + isLegacyProcedureDefBlock, + ProcedureBlock, + ProcedureTuple, +} from './interfaces/i_legacy_procedure_blocks.js'; import {ObservableProcedureMap} from './observable_procedure_map.js'; import * as utilsXml from './utils/xml.js'; import * as Variables from './variables.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - +import {MutatorIcon} from './icons.js'; /** * String for use in the "custom" attribute of a category in toolbox XML. @@ -54,34 +62,33 @@ export const DEFAULT_ARG = 'x'; * variables, the second with. Each procedure is defined by a three-element * list of name, parameter list, and return value boolean. */ -export function allProcedures(root: Workspace): - [ProcedureTuple[], ProcedureTuple[]] { - const proceduresNoReturn: ProcedureTuple[] = - root.getProcedureMap() - .getProcedures() - .filter((p) => !p.getReturnTypes()) - .map( - (p) => - [p.getName(), - p.getParameters().map((pa) => pa.getName()), - false, - ]); +export function allProcedures( + root: Workspace +): [ProcedureTuple[], ProcedureTuple[]] { + const proceduresNoReturn: ProcedureTuple[] = root + .getProcedureMap() + .getProcedures() + .filter((p) => !p.getReturnTypes()) + .map((p) => [ + p.getName(), + p.getParameters().map((pa) => pa.getName()), + false, + ]); root.getBlocksByType('procedures_defnoreturn', false).forEach((b) => { if (!isProcedureBlock(b) && isLegacyProcedureDefBlock(b)) { proceduresNoReturn.push(b.getProcedureDef()); } }); - const proceduresReturn: ProcedureTuple[] = - root.getProcedureMap() - .getProcedures() - .filter((p) => !!p.getReturnTypes()) - .map( - (p) => - [p.getName(), - p.getParameters().map((pa) => pa.getName()), - true, - ]); + const proceduresReturn: ProcedureTuple[] = root + .getProcedureMap() + .getProcedures() + .filter((p) => !!p.getReturnTypes()) + .map((p) => [ + p.getName(), + p.getParameters().map((pa) => pa.getName()), + true, + ]); root.getBlocksByType('procedures_defreturn', false).forEach((b) => { if (!isProcedureBlock(b) && isLegacyProcedureDefBlock(b)) { proceduresReturn.push(b.getProcedureDef()); @@ -141,7 +148,10 @@ export function findLegalName(name: string, block: Block): string { * @returns True if the name is legal. */ function isLegalName( - name: string, workspace: Workspace, opt_exclude?: Block): boolean { + name: string, + workspace: Workspace, + opt_exclude?: Block +): boolean { return !isNameUsed(name, workspace, opt_exclude); } @@ -155,19 +165,25 @@ function isLegalName( * @returns True if the name is used, otherwise return false. */ export function isNameUsed( - name: string, workspace: Workspace, opt_exclude?: Block): boolean { + name: string, + workspace: Workspace, + opt_exclude?: Block +): boolean { for (const block of workspace.getAllBlocks(false)) { if (block === opt_exclude) continue; - if (isLegacyProcedureDefBlock(block) && - Names.equals(block.getProcedureDef()[0], name)) { + if ( + isLegacyProcedureDefBlock(block) && + Names.equals(block.getProcedureDef()[0], name) + ) { return true; } } - const excludeModel = opt_exclude && isProcedureBlock(opt_exclude) ? - opt_exclude?.getProcedureModel() : - undefined; + const excludeModel = + opt_exclude && isProcedureBlock(opt_exclude) + ? opt_exclude?.getProcedureModel() + : undefined; for (const model of workspace.getProcedureMap().getProcedures()) { if (model === excludeModel) continue; if (Names.equals(model.getName(), name)) return true; @@ -227,7 +243,8 @@ export function flyoutCategory(workspace: WorkspaceSvg): Element[] { const nameField = utilsXml.createElement('field'); nameField.setAttribute('name', 'NAME'); nameField.appendChild( - utilsXml.createTextNode(Msg['PROCEDURES_DEFNORETURN_PROCEDURE'])); + utilsXml.createTextNode(Msg['PROCEDURES_DEFNORETURN_PROCEDURE']) + ); block.appendChild(nameField); xmlList.push(block); } @@ -241,7 +258,8 @@ export function flyoutCategory(workspace: WorkspaceSvg): Element[] { const nameField = utilsXml.createElement('field'); nameField.setAttribute('name', 'NAME'); nameField.appendChild( - utilsXml.createTextNode(Msg['PROCEDURES_DEFRETURN_PROCEDURE'])); + utilsXml.createTextNode(Msg['PROCEDURES_DEFRETURN_PROCEDURE']) + ); block.appendChild(nameField); xmlList.push(block); } @@ -265,7 +283,9 @@ export function flyoutCategory(workspace: WorkspaceSvg): Element[] { * @param templateName The type of the block to generate. */ function populateProcedures( - procedureList: ProcedureTuple[], templateName: string) { + procedureList: ProcedureTuple[], + templateName: string + ) { for (let i = 0; i < procedureList.length; i++) { const name = procedureList[i][0]; const args = procedureList[i][1]; @@ -305,7 +325,7 @@ export function flyoutCategory(workspace: WorkspaceSvg): Element[] { function updateMutatorFlyout(workspace: WorkspaceSvg) { const usedNames = []; const blocks = workspace.getBlocksByType('procedures_mutatorarg', false); - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { usedNames.push(block.getFieldValue('NAME')); } @@ -314,8 +334,10 @@ function updateMutatorFlyout(workspace: WorkspaceSvg) { argBlock.setAttribute('type', 'procedures_mutatorarg'); const nameField = utilsXml.createElement('field'); nameField.setAttribute('name', 'NAME'); - const argValue = - Variables.generateUniqueNameFromOptions(DEFAULT_ARG, usedNames); + const argValue = Variables.generateUniqueNameFromOptions( + DEFAULT_ARG, + usedNames + ); const fieldContent = utilsXml.createTextNode(argValue); nameField.appendChild(fieldContent); @@ -337,18 +359,23 @@ export function mutatorOpenListener(e: Abstract) { return; } const bubbleEvent = e as BubbleOpen; - if (!(bubbleEvent.bubbleType === 'mutator' && bubbleEvent.isOpen) || - !bubbleEvent.blockId) { + if ( + !(bubbleEvent.bubbleType === 'mutator' && bubbleEvent.isOpen) || + !bubbleEvent.blockId + ) { return; } - const workspaceId = (bubbleEvent.workspaceId); - const block = common.getWorkspaceById(workspaceId)!.getBlockById( - bubbleEvent.blockId) as BlockSvg; + const workspaceId = bubbleEvent.workspaceId; + const block = common + .getWorkspaceById(workspaceId)! + .getBlockById(bubbleEvent.blockId) as BlockSvg; const type = block.type; if (type !== 'procedures_defnoreturn' && type !== 'procedures_defreturn') { return; } - const workspace = block.mutator!.getWorkspace() as WorkspaceSvg; + const workspace = ( + block.getIcon(MutatorIcon.TYPE) as MutatorIcon + ).getWorkspace()!; updateMutatorFlyout(workspace); workspace.addChangeListener(mutatorChangeListener); } @@ -359,9 +386,12 @@ export function mutatorOpenListener(e: Abstract) { * @param e The event that triggered this listener. */ function mutatorChangeListener(e: Abstract) { - if (e.type !== eventUtils.BLOCK_CREATE && - e.type !== eventUtils.BLOCK_DELETE && - e.type !== eventUtils.BLOCK_CHANGE) { + if ( + e.type !== eventUtils.BLOCK_CREATE && + e.type !== eventUtils.BLOCK_DELETE && + e.type !== eventUtils.BLOCK_CHANGE && + e.type !== eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE + ) { return; } const workspaceId = e.workspaceId as string; @@ -378,9 +408,11 @@ function mutatorChangeListener(e: Abstract) { */ export function getCallers(name: string, workspace: Workspace): Block[] { return workspace.getAllBlocks(false).filter((block) => { - return blockIsModernCallerFor(block, name) || - (isLegacyProcedureCallBlock(block) && - Names.equals(block.getProcedureCall(), name)); + return ( + blockIsModernCallerFor(block, name) || + (isLegacyProcedureCallBlock(block) && + Names.equals(block.getProcedureCall(), name)) + ); }); } @@ -389,9 +421,12 @@ export function getCallers(name: string, workspace: Workspace): Block[] { * procedure name. */ function blockIsModernCallerFor(block: Block, procName: string): boolean { - return isProcedureBlock(block) && !block.isProcedureDef() && - block.getProcedureModel() && - Names.equals(block.getProcedureModel().getName(), procName); + return ( + isProcedureBlock(block) && + !block.isProcedureDef() && + block.getProcedureModel() && + Names.equals(block.getProcedureModel().getName(), procName) + ); } /** @@ -406,7 +441,7 @@ export function mutateCallers(defBlock: Block) { const name = procedureBlock.getProcedureDef()[0]; const xmlElement = defBlock.mutationToDom!(true); const callers = getCallers(name, defBlock.workspace); - for (let i = 0, caller; caller = callers[i]; i++) { + for (let i = 0, caller; (caller = callers[i]); i++) { const oldMutationDom = caller.mutationToDom!(); const oldMutation = oldMutationDom && utilsXml.domToText(oldMutationDom); if (caller.domToMutation) { @@ -419,8 +454,15 @@ export function mutateCallers(defBlock: Block) { // undo action since it is deterministically tied to the procedure's // definition mutation. eventUtils.setRecordUndo(false); - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))( - caller, 'mutation', null, oldMutation, newMutation)); + eventUtils.fire( + new (eventUtils.get(eventUtils.BLOCK_CHANGE))( + caller, + 'mutation', + null, + oldMutation, + newMutation + ) + ); eventUtils.setRecordUndo(oldRecordUndo); } } @@ -433,17 +475,25 @@ export function mutateCallers(defBlock: Block) { * @param workspace The workspace to search. * @returns The procedure definition block, or null not found. */ -export function getDefinition(name: string, workspace: Workspace): Block|null { +export function getDefinition( + name: string, + workspace: Workspace +): Block | null { // Do not assume procedure is a top block. Some languages allow nested // procedures. Also do not assume it is one of the built-in blocks. Only // rely on isProcedureDef and getProcedureDef. for (const block of workspace.getAllBlocks(false)) { - if (isProcedureBlock(block) && block.isProcedureDef() && - Names.equals(block.getProcedureModel().getName(), name)) { + if ( + isProcedureBlock(block) && + block.isProcedureDef() && + Names.equals(block.getProcedureModel().getName(), name) + ) { return block; } - if (isLegacyProcedureDefBlock(block) && - Names.equals(block.getProcedureDef()[0], name)) { + if ( + isLegacyProcedureDefBlock(block) && + Names.equals(block.getProcedureDef()[0], name) + ) { return block; } } diff --git a/core/registry.ts b/core/registry.ts index 32abc14d7..68bd53685 100644 --- a/core/registry.ts +++ b/core/registry.ts @@ -13,6 +13,8 @@ import type {IBlockDragger} from './interfaces/i_block_dragger.js'; import type {IConnectionChecker} from './interfaces/i_connection_checker.js'; import type {IFlyout} from './interfaces/i_flyout.js'; import type {IMetricsManager} from './interfaces/i_metrics_manager.js'; +import type {IIcon} from './interfaces/i_icon.js'; +import type {Input} from './inputs/input.js'; import type {ISerializer} from './interfaces/i_serializer.js'; import type {IToolbox} from './interfaces/i_toolbox.js'; import type {Cursor} from './keyboard_nav/cursor.js'; @@ -21,15 +23,15 @@ import type {Renderer} from './renderers/common/renderer.js'; import type {Theme} from './theme.js'; import type {ToolboxItem} from './toolbox/toolbox_item.js'; - /** * A map of maps. With the keys being the type and name of the class we are * registering and the value being the constructor function. * e.g. {'field': {'field_angle': Blockly.FieldAngle}} */ const typeMap: { - [key: string]: - {[key: string]: (new () => AnyDuringMigration)|AnyDuringMigration} + [key: string]: { + [key: string]: (new () => AnyDuringMigration) | AnyDuringMigration; + }; } = Object.create(null); export const TEST_ONLY = {typeMap}; @@ -69,6 +71,8 @@ export class Type<_T> { static FIELD = new Type('field'); + static INPUT = new Type */ - this.defs_ = dom.createSvgElement(Svg.DEFS, {}, svg); + this.defs = dom.createSvgElement(Svg.DEFS, {}, svg); /* @@ -903,46 +933,57 @@ export class ConstantProvider { */ const embossFilter = dom.createSvgElement( - Svg.FILTER, {'id': 'blocklyEmbossFilter' + this.randomIdentifier}, - this.defs_); + Svg.FILTER, + {'id': 'blocklyEmbossFilter' + this.randomIdentifier}, + this.defs + ); dom.createSvgElement( - Svg.FEGAUSSIANBLUR, - {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, - embossFilter); + Svg.FEGAUSSIANBLUR, + {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, + embossFilter + ); const feSpecularLighting = dom.createSvgElement( - Svg.FESPECULARLIGHTING, { - 'in': 'blur', - 'surfaceScale': 1, - 'specularConstant': 0.5, - 'specularExponent': 10, - 'lighting-color': 'white', - 'result': 'specOut', - }, - embossFilter); + Svg.FESPECULARLIGHTING, + { + 'in': 'blur', + 'surfaceScale': 1, + 'specularConstant': 0.5, + 'specularExponent': 10, + 'lighting-color': 'white', + 'result': 'specOut', + }, + embossFilter + ); dom.createSvgElement( - Svg.FEPOINTLIGHT, {'x': -5000, 'y': -10000, 'z': 20000}, - feSpecularLighting); + Svg.FEPOINTLIGHT, + {'x': -5000, 'y': -10000, 'z': 20000}, + feSpecularLighting + ); dom.createSvgElement( - Svg.FECOMPOSITE, { - 'in': 'specOut', - 'in2': 'SourceAlpha', - 'operator': 'in', - 'result': 'specOut', - }, - embossFilter); + Svg.FECOMPOSITE, + { + 'in': 'specOut', + 'in2': 'SourceAlpha', + 'operator': 'in', + 'result': 'specOut', + }, + embossFilter + ); dom.createSvgElement( - Svg.FECOMPOSITE, { - 'in': 'SourceGraphic', - 'in2': 'specOut', - 'operator': 'arithmetic', - 'k1': 0, - 'k2': 1, - 'k3': 1, - 'k4': 0, - }, - embossFilter); + Svg.FECOMPOSITE, + { + 'in': 'SourceGraphic', + 'in2': 'specOut', + 'operator': 'arithmetic', + 'k1': 0, + 'k2': 1, + 'k3': 1, + 'k4': 0, + }, + embossFilter + ); this.embossFilterId = embossFilter.id; - this.embossFilter_ = embossFilter; + this.embossFilter = embossFilter; /* */ const disabledPattern = dom.createSvgElement( - Svg.PATTERN, { - 'id': 'blocklyDisabledPattern' + this.randomIdentifier, - 'patternUnits': 'userSpaceOnUse', - 'width': 10, - 'height': 10, - }, - this.defs_); + Svg.PATTERN, + { + 'id': 'blocklyDisabledPattern' + this.randomIdentifier, + 'patternUnits': 'userSpaceOnUse', + 'width': 10, + 'height': 10, + }, + this.defs + ); dom.createSvgElement( - Svg.RECT, {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); + Svg.RECT, + {'width': 10, 'height': 10, 'fill': '#aaa'}, + disabledPattern + ); dom.createSvgElement( - Svg.PATH, {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, - disabledPattern); + Svg.PATH, + {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, + disabledPattern + ); this.disabledPatternId = disabledPattern.id; - this.disabledPattern_ = disabledPattern; + this.disabledPattern = disabledPattern; this.createDebugFilter(); } @@ -976,41 +1024,51 @@ export class ConstantProvider { */ private createDebugFilter() { // Only create the debug filter once. - if (!this.debugFilter_) { + if (!this.debugFilter) { const debugFilter = dom.createSvgElement( - Svg.FILTER, { - 'id': 'blocklyDebugFilter' + this.randomIdentifier, - 'height': '160%', - 'width': '180%', - 'y': '-30%', - 'x': '-40%', - }, - this.defs_); + Svg.FILTER, + { + 'id': 'blocklyDebugFilter' + this.randomIdentifier, + 'height': '160%', + 'width': '180%', + 'y': '-30%', + 'x': '-40%', + }, + this.defs + ); // Set all gaussian blur pixels to 1 opacity before applying flood const debugComponentTransfer = dom.createSvgElement( - Svg.FECOMPONENTTRANSFER, {'result': 'outBlur'}, debugFilter); + Svg.FECOMPONENTTRANSFER, + {'result': 'outBlur'}, + debugFilter + ); dom.createSvgElement( - Svg.FEFUNCA, - {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, - debugComponentTransfer); + Svg.FEFUNCA, + {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, + debugComponentTransfer + ); // Color the highlight dom.createSvgElement( - Svg.FEFLOOD, { - 'flood-color': '#ff0000', - 'flood-opacity': 0.5, - 'result': 'outColor', - }, - debugFilter); + Svg.FEFLOOD, + { + 'flood-color': '#ff0000', + 'flood-opacity': 0.5, + 'result': 'outColor', + }, + debugFilter + ); dom.createSvgElement( - Svg.FECOMPOSITE, { - 'in': 'outColor', - 'in2': 'outBlur', - 'operator': 'in', - 'result': 'outGlow', - }, - debugFilter); + Svg.FECOMPOSITE, + { + 'in': 'outColor', + 'in2': 'outBlur', + 'operator': 'in', + 'result': 'outGlow', + }, + debugFilter + ); this.debugFilterId = debugFilter.id; - this.debugFilter_ = debugFilter; + this.debugFilter = debugFilter; } } @@ -1023,20 +1081,20 @@ export class ConstantProvider { protected injectCSS_(tagName: string, selector: string) { const cssArray = this.getCSS_(selector); const cssNodeId = 'blockly-renderer-style-' + tagName; - this.cssNode_ = document.getElementById(cssNodeId) as HTMLStyleElement; + this.cssNode = document.getElementById(cssNodeId) as HTMLStyleElement; const text = cssArray.join('\n'); - if (this.cssNode_) { + if (this.cssNode) { // Already injected, update if the theme changed. - this.cssNode_.firstChild!.textContent = text; + this.cssNode.firstChild!.textContent = text; return; } // Inject CSS tag at start of head. - const cssNode = (document.createElement('style')); + const cssNode = document.createElement('style'); cssNode.id = cssNodeId; const cssTextNode = document.createTextNode(text); cssNode.appendChild(cssTextNode); document.head.insertBefore(cssNode, document.head.firstChild); - this.cssNode_ = cssNode; + this.cssNode = cssNode; } /** @@ -1046,9 +1104,8 @@ export class ConstantProvider { * @returns Array of CSS strings. */ protected getCSS_(selector: string): string[] { + // prettier-ignore return [ - /* eslint-disable indent */ - /* clang-format off */ // Text. `${selector} .blocklyText, `, `${selector} .blocklyFlyoutLabelText {`, @@ -1118,10 +1175,6 @@ export class ConstantProvider { `fill-opacity: ${this.INSERTION_MARKER_OPACITY};`, `stroke: none;`, `}`, - /* clang-format on */ - /* eslint-enable indent */ ]; } } -/* clang-format on */ -/* eslint-enable indent */ diff --git a/core/renderers/common/debug.ts b/core/renderers/common/debug.ts deleted file mode 100644 index fed30a2b5..000000000 --- a/core/renderers/common/debug.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as goog from '../../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.blockRendering.debug'); - -import * as deprecation from '../../utils/deprecation.js'; - - -/** Whether or not the debugger is turned on. */ -let useDebugger = false; -/** - * Returns whether the debugger is turned on. - * - * @returns Whether the debugger is turned on. - * @internal - */ -export function isDebuggerEnabled(): boolean { - return useDebugger; -} - -/** - * Turn on the blocks debugger. - * - * @deprecated March 2022. Use the rendering debugger in @blockly/dev-tools. - * See https://www.npmjs.com/package/@blockly/dev-tools for more information. - * @internal - */ -export function startDebugger() { - deprecation.warn( - 'Blockly.blockRendering.debug.startDebugger()', 'version 8', 'version 10', - 'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)'); - useDebugger = true; -} - -/** - * Turn off the blocks debugger. - * - * @deprecated March 2022. Use the rendering debugger in @blockly/dev-tools. - * See https://www.npmjs.com/package/@blockly/dev-tools for more information. - * @internal - */ -export function stopDebugger() { - deprecation.warn( - 'Blockly.blockRendering.debug.stopDebugger()', 'version 8', 'version 10', - 'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)'); - useDebugger = false; -} diff --git a/core/renderers/common/debugger.ts b/core/renderers/common/debugger.ts deleted file mode 100644 index 0490c7bce..000000000 --- a/core/renderers/common/debugger.ts +++ /dev/null @@ -1,414 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as goog from '../../../closure/goog/goog.js'; -goog.declareModuleId('Blockly.blockRendering.Debug'); - -import type {BlockSvg} from '../../block_svg.js'; -import {ConnectionType} from '../../connection_type.js'; -import {FieldLabel} from '../../field_label.js'; -import type {RenderedConnection} from '../../rendered_connection.js'; -import * as dom from '../../utils/dom.js'; -import {Svg} from '../../utils/svg.js'; -import type {Measurable} from '../measurables/base.js'; -import {Field} from '../measurables/field.js'; -import type {InRowSpacer} from '../measurables/in_row_spacer.js'; -import {InputConnection} from '../measurables/input_connection.js'; -import type {Row} from '../measurables/row.js'; -import {Types} from '../measurables/types.js'; -import type {RenderInfo as ZelosInfo} from '../zelos/info.js'; - -import type {ConstantProvider} from './constants.js'; -import type {RenderInfo} from './info.js'; - - -/** - * An object that renders rectangles and dots for debugging rendering code. - */ -export class Debug { - /** - * Configuration object containing booleans to enable and disable debug - * rendering of specific rendering components. - */ - static config = { - rowSpacers: true, - elemSpacers: true, - rows: true, - elems: true, - connections: true, - blockBounds: true, - connectedBlockBounds: true, - render: true, - }; - - /** An array of SVG elements that have been created by this object. */ - private debugElements_: SVGElement[] = []; - - /** - * The SVG root of the block that is being rendered. Debug elements will - * be attached to this root. - */ - private svgRoot_: SVGElement|null = null; - - private randomColour_ = ''; - - /** - * @param constants The renderer's constants. - */ - constructor(private readonly constants: ConstantProvider) {} - - /** - * Remove all elements the this object created on the last pass. - */ - protected clearElems() { - for (let i = 0; i < this.debugElements_.length; i++) { - const elem = this.debugElements_[i]; - dom.removeNode(elem); - } - - this.debugElements_ = []; - } - - /** - * Draw a debug rectangle for a spacer (empty) row. - * - * @param row The row to render. - * @param cursorY The y position of the top of the row. - * @param isRtl Whether the block is rendered RTL. - */ - protected drawSpacerRow(row: Row, cursorY: number, isRtl: boolean) { - if (!Debug.config.rowSpacers) { - return; - } - - const height = Math.abs(row.height); - const isNegativeSpacing = row.height < 0; - if (isNegativeSpacing) { - cursorY -= height; - } - - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'rowSpacerRect blockRenderDebug', - 'x': isRtl ? -(row.xPos + row.width) : row.xPos, - 'y': cursorY, - 'width': row.width, - 'height': height, - 'stroke': isNegativeSpacing ? 'black' : 'blue', - 'fill': 'blue', - 'fill-opacity': '0.5', - 'stroke-width': '1px', - }, - this.svgRoot_)); - } - - /** - * Draw a debug rectangle for a horizontal spacer. - * - * @param elem The spacer to render. - * @param rowHeight The height of the container row. - * @param isRtl Whether the block is rendered RTL. - */ - protected drawSpacerElem( - elem: InRowSpacer, rowHeight: number, isRtl: boolean) { - if (!Debug.config.elemSpacers) { - return; - } - - const width = Math.abs(elem.width); - const isNegativeSpacing = elem.width < 0; - let xPos = isNegativeSpacing ? elem.xPos - width : elem.xPos; - if (isRtl) { - xPos = -(xPos + width); - } - const yPos = elem.centerline - elem.height / 2; - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'elemSpacerRect blockRenderDebug', - 'x': xPos, - 'y': yPos, - 'width': width, - 'height': elem.height, - 'stroke': 'pink', - 'fill': isNegativeSpacing ? 'black' : 'pink', - 'fill-opacity': '0.5', - 'stroke-width': '1px', - }, - this.svgRoot_)); - } - - /** - * Draw a debug rectangle for an in-row element. - * - * @param elem The element to render. - * @param isRtl Whether the block is rendered RTL. - */ - protected drawRenderedElem(elem: Measurable, isRtl: boolean) { - if (Debug.config.elems) { - let xPos = elem.xPos; - if (isRtl) { - xPos = -(xPos + elem.width); - } - const yPos = elem.centerline - elem.height / 2; - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'rowRenderingRect blockRenderDebug', - 'x': xPos, - 'y': yPos, - 'width': elem.width, - 'height': elem.height, - 'stroke': 'black', - 'fill': 'none', - 'stroke-width': '1px', - }, - this.svgRoot_)); - - if (Types.isField(elem) && elem instanceof Field && - elem.field instanceof FieldLabel) { - const baseline = this.constants.FIELD_TEXT_BASELINE; - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'rowRenderingRect blockRenderDebug', - 'x': xPos, - 'y': yPos + baseline, - 'width': elem.width, - 'height': '0.1px', - 'stroke': 'red', - 'fill': 'none', - 'stroke-width': '0.5px', - }, - this.svgRoot_)); - } - } - - if (Types.isInput(elem) && elem instanceof InputConnection && - Debug.config.connections) { - this.drawConnection(elem.connectionModel); - } - } - - /** - * Draw a circle at the location of the given connection. Inputs and outputs - * share the same colours, as do previous and next. When positioned correctly - * a connected pair will look like a bullseye. - * - * @param conn The connection to circle. - * @suppress {visibility} Suppress visibility of conn.offsetInBlock_ since - * this is a debug module. - */ - protected drawConnection(conn: RenderedConnection) { - if (!Debug.config.connections) { - return; - } - - let colour = ''; - let size = 0; - let fill = ''; - if (conn.type === ConnectionType.INPUT_VALUE) { - size = 4; - colour = 'magenta'; - fill = 'none'; - } else if (conn.type === ConnectionType.OUTPUT_VALUE) { - size = 2; - colour = 'magenta'; - fill = colour; - } else if (conn.type === ConnectionType.NEXT_STATEMENT) { - size = 4; - colour = 'goldenrod'; - fill = 'none'; - } else if (conn.type === ConnectionType.PREVIOUS_STATEMENT) { - size = 2; - colour = 'goldenrod'; - fill = colour; - } - this.debugElements_.push(dom.createSvgElement( - Svg.CIRCLE, { - 'class': 'blockRenderDebug', - 'cx': conn.getOffsetInBlock().x, - 'cy': conn.getOffsetInBlock().y, - 'r': size, - 'fill': fill, - 'stroke': colour, - }, - this.svgRoot_)); - } - - /** - * Draw a debug rectangle for a non-empty row. - * - * @param row The non-empty row to render. - * @param cursorY The y position of the top of the row. - * @param isRtl Whether the block is rendered RTL. - */ - protected drawRenderedRow(row: Row, cursorY: number, isRtl: boolean) { - if (!Debug.config.rows) { - return; - } - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'elemRenderingRect blockRenderDebug', - 'x': isRtl ? -(row.xPos + row.width) : row.xPos, - 'y': row.yPos, - 'width': row.width, - 'height': row.height, - 'stroke': 'red', - 'fill': 'none', - 'stroke-width': '1px', - }, - this.svgRoot_)); - - if (Types.isTopOrBottomRow(row)) { - return; - } - - if (Debug.config.connectedBlockBounds) { - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'connectedBlockWidth blockRenderDebug', - 'x': isRtl ? -(row.xPos + row.widthWithConnectedBlocks) : row.xPos, - 'y': row.yPos, - 'width': row.widthWithConnectedBlocks, - 'height': row.height, - 'stroke': this.randomColour_, - 'fill': 'none', - 'stroke-width': '1px', - 'stroke-dasharray': '3,3', - }, - this.svgRoot_)); - } - } - - /** - * Draw debug rectangles for a non-empty row and all of its subcomponents. - * - * @param row The non-empty row to render. - * @param cursorY The y position of the top of the row. - * @param isRtl Whether the block is rendered RTL. - */ - protected drawRowWithElements(row: Row, cursorY: number, isRtl: boolean) { - for (let i = 0; i < row.elements.length; i++) { - const elem = row.elements[i]; - if (!elem) { - console.warn('A row has an undefined or null element.', row, elem); - continue; - } - if (Types.isSpacer(elem)) { - this.drawSpacerElem(elem as InRowSpacer, row.height, isRtl); - } else { - this.drawRenderedElem(elem, isRtl); - } - } - this.drawRenderedRow(row, cursorY, isRtl); - } - - /** - * Draw a debug rectangle around the entire block. - * - * @param info Rendering information about the block to debug. - */ - protected drawBoundingBox(info: RenderInfo) { - if (!Debug.config.blockBounds) { - return; - } - // Bounding box without children. - let xPos = info.RTL ? -info.width : 0; - const yPos = 0; - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'blockBoundingBox blockRenderDebug', - 'x': xPos, - 'y': yPos, - 'width': info.width, - 'height': info.height, - 'stroke': 'black', - 'fill': 'none', - 'stroke-width': '1px', - 'stroke-dasharray': '5,5', - }, - this.svgRoot_)); - - if (Debug.config.connectedBlockBounds) { - // Bounding box with children. - xPos = info.RTL ? -info.widthWithChildren : 0; - this.debugElements_.push(dom.createSvgElement( - Svg.RECT, { - 'class': 'blockRenderDebug', - 'x': xPos, - 'y': yPos, - 'width': info.widthWithChildren, - 'height': info.height, - 'stroke': '#DF57BC', - 'fill': 'none', - 'stroke-width': '1px', - 'stroke-dasharray': '3,3', - }, - this.svgRoot_)); - } - } - - /** - * Do all of the work to draw debug information for the whole block. - * - * @param block The block to draw debug information for. - * @param info Rendering information about the block to debug. - */ - drawDebug(block: BlockSvg, info: RenderInfo) { - this.clearElems(); - this.svgRoot_ = block.getSvgRoot(); - - this.randomColour_ = - '#' + Math.floor(Math.random() * 16777215).toString(16); - - let cursorY = 0; - for (let i = 0; i < info.rows.length; i++) { - const row = info.rows[i]; - if (Types.isBetweenRowSpacer(row)) { - this.drawSpacerRow(row, cursorY, info.RTL); - } else { - this.drawRowWithElements(row, cursorY, info.RTL); - } - cursorY += row.height; - } - - if (block.previousConnection) { - this.drawConnection(block.previousConnection); - } - if (block.nextConnection) { - this.drawConnection(block.nextConnection); - } - if (block.outputConnection) { - this.drawConnection(block.outputConnection); - } - /** - * TODO: Find a better way to do this check without pulling in all of - * zelos, or just delete this line or the whole debug renderer. - */ - const maybeZelosInfo = info as ZelosInfo; - if (maybeZelosInfo.rightSide) { - this.drawRenderedElem(maybeZelosInfo.rightSide, info.RTL); - } - - this.drawBoundingBox(info); - - this.drawRender(block.pathObject.svgPath); - } - - /** - * Show a debug filter to highlight that a block has been rendered. - * - * @param svgPath The block's SVG path. - */ - protected drawRender(svgPath: SVGElement) { - if (!Debug.config.render) { - return; - } - svgPath.setAttribute( - 'filter', 'url(#' + this.constants.debugFilterId + ')'); - setTimeout(function() { - svgPath.setAttribute('filter', ''); - }, 100); - } -} diff --git a/core/renderers/common/drawer.ts b/core/renderers/common/drawer.ts index fa81e7eb1..8e334cca4 100644 --- a/core/renderers/common/drawer.ts +++ b/core/renderers/common/drawer.ts @@ -21,9 +21,8 @@ import {Types} from '../measurables/types.js'; import {isDynamicShape} from './constants.js'; import type {ConstantProvider, Notch, PuzzleTab} from './constants.js'; -import * as debug from './debug.js'; import type {RenderInfo} from './info.js'; - +import * as deprecation from '../../utils/deprecation.js'; /** * An object that draws a block based on the given rendering information. @@ -60,7 +59,6 @@ export class Drawer { * required. */ draw() { - this.hideHiddenIcons_(); this.drawOutline_(); this.drawInternals_(); @@ -68,12 +66,19 @@ export class Drawer { if (this.info_.RTL) { this.block_.pathObject.flipRTL(); } - if (debug.isDebuggerEnabled()) { - this.block_.renderingDebugger?.drawDebug(this.block_, this.info_); - } this.recordSizeOnBlock_(); } + /** + * Hide icons that were marked as hidden. + * + * @deprecated Manually hiding icons is no longer necessary. To be removed + * in v11. + */ + protected hideHiddenIcons_() { + deprecation.warn('hideHiddenIcons_', 'v10', 'v11'); + } + /** * Save sizing information back to the block * Most of the rendering information can be thrown away at the end of the @@ -87,13 +92,6 @@ export class Drawer { this.block_.width = this.info_.widthWithChildren; } - /** Hide icons that were marked as hidden. */ - protected hideHiddenIcons_() { - for (let i = 0, iconInfo; iconInfo = this.info_.hiddenIcons[i]; i++) { - iconInfo.icon.iconGroup_?.setAttribute('display', 'none'); - } - } - /** Create the outline of the block. This is a single continuous path. */ protected drawOutline_() { this.drawTop_(); @@ -123,15 +121,18 @@ export class Drawer { this.positionPreviousConnection_(); this.outlinePath_ += svgPaths.moveBy(topRow.xPos, this.info_.startY); - for (let i = 0, elem; elem = elements[i]; i++) { + for (let i = 0, elem; (elem = elements[i]); i++) { if (Types.isLeftRoundedCorner(elem)) { this.outlinePath_ += this.constants_.OUTSIDE_CORNERS.topLeft; } else if (Types.isRightRoundedCorner(elem)) { this.outlinePath_ += this.constants_.OUTSIDE_CORNERS.topRight; } else if ( - Types.isPreviousConnection(elem) && elem instanceof Connection) { - this.outlinePath_ += - ((elem as PreviousConnection).shape as Notch).pathLeft; + Types.isPreviousConnection(elem) && + elem instanceof Connection + ) { + this.outlinePath_ += ( + (elem as PreviousConnection).shape as Notch + ).pathLeft; } else if (Types.isHat(elem)) { this.outlinePath_ += this.constants_.START_HAT.path; } else if (Types.isSpacer(elem)) { @@ -150,7 +151,7 @@ export class Drawer { protected drawJaggedEdge_(row: Row) { const remainder = row.height - this.constants_.JAGGED_TEETH.height; this.outlinePath_ += - this.constants_.JAGGED_TEETH.path + svgPaths.lineOnAxis('v', remainder); + this.constants_.JAGGED_TEETH.path + svgPaths.lineOnAxis('v', remainder); } /** @@ -163,13 +164,14 @@ export class Drawer { const input = row.getLastInput() as ExternalValueInput | InlineInput; this.positionExternalValueConnection_(row); - const pathDown = isDynamicShape(input.shape) ? - input.shape.pathDown(input.height) : - (input.shape as PuzzleTab).pathDown; + const pathDown = isDynamicShape(input.shape) + ? input.shape.pathDown(input.height) + : (input.shape as PuzzleTab).pathDown; - this.outlinePath_ += svgPaths.lineOnAxis('H', input.xPos + input.width) + - pathDown + - svgPaths.lineOnAxis('v', row.height - input.connectionHeight); + this.outlinePath_ += + svgPaths.lineOnAxis('H', input.xPos + input.width) + + pathDown + + svgPaths.lineOnAxis('v', row.height - input.connectionHeight); } /** @@ -183,17 +185,22 @@ export class Drawer { // Where to start drawing the notch, which is on the right side in LTR. const x = input.xPos + input.notchOffset + (input.shape as Notch).width; - const innerTopLeftCorner = (input.shape as Notch).pathRight + - svgPaths.lineOnAxis( - 'h', -(input.notchOffset - this.constants_.INSIDE_CORNERS.width)) + - this.constants_.INSIDE_CORNERS.pathTop; + const innerTopLeftCorner = + (input.shape as Notch).pathRight + + svgPaths.lineOnAxis( + 'h', + -(input.notchOffset - this.constants_.INSIDE_CORNERS.width) + ) + + this.constants_.INSIDE_CORNERS.pathTop; const innerHeight = row.height - 2 * this.constants_.INSIDE_CORNERS.height; - this.outlinePath_ += svgPaths.lineOnAxis('H', x) + innerTopLeftCorner + - svgPaths.lineOnAxis('v', innerHeight) + - this.constants_.INSIDE_CORNERS.pathBottom + - svgPaths.lineOnAxis('H', row.xPos + row.width); + this.outlinePath_ += + svgPaths.lineOnAxis('H', x) + + innerTopLeftCorner + + svgPaths.lineOnAxis('v', innerHeight) + + this.constants_.INSIDE_CORNERS.pathBottom + + svgPaths.lineOnAxis('H', row.xPos + row.width); this.positionStatementInputConnection_(row); } @@ -219,7 +226,7 @@ export class Drawer { let rightCornerYOffset = 0; let outlinePath = ''; - for (let i = elems.length - 1, elem; elem = elems[i]; i--) { + for (let i = elems.length - 1, elem; (elem = elems[i]); i--) { if (Types.isNextConnection(elem) && elem instanceof Connection) { outlinePath += (elem.shape as Notch).pathRight; } else if (Types.isLeftSquareCorner(elem)) { @@ -234,8 +241,10 @@ export class Drawer { } } - this.outlinePath_ += - svgPaths.lineOnAxis('V', bottomRow.baseline - rightCornerYOffset); + this.outlinePath_ += svgPaths.lineOnAxis( + 'V', + bottomRow.baseline - rightCornerYOffset + ); this.outlinePath_ += outlinePath; } @@ -249,10 +258,10 @@ export class Drawer { if (outputConnection) { const tabBottom = - outputConnection.connectionOffsetY + outputConnection.height; - const pathUp = isDynamicShape(outputConnection.shape) ? - outputConnection.shape.pathUp(outputConnection.height) : - (outputConnection.shape as PuzzleTab).pathUp; + outputConnection.connectionOffsetY + outputConnection.height; + const pathUp = isDynamicShape(outputConnection.shape) + ? outputConnection.shape.pathUp(outputConnection.height) + : (outputConnection.shape as PuzzleTab).pathUp; // Draw a line up to the bottom of the tab. this.outlinePath_ += svgPaths.lineOnAxis('V', tabBottom) + pathUp; @@ -267,8 +276,8 @@ export class Drawer { * do not depend on the outer path for placement. */ protected drawInternals_() { - for (let i = 0, row; row = this.info_.rows[i]; i++) { - for (let j = 0, elem; elem = row.elements[j]; j++) { + for (let i = 0, row; (row = this.info_.rows[i]); i++) { + for (let j = 0, elem; (elem = row.elements[j]); j++) { if (Types.isInlineInput(elem)) { this.drawInlineInput_(elem as InlineInput); } else if (Types.isIcon(elem) || Types.isField(elem)) { @@ -283,11 +292,7 @@ export class Drawer { * * @param fieldInfo The rendering information for the field or icon. */ - protected layoutField_(fieldInfo: Icon|Field) { - const svgGroup = Types.isField(fieldInfo) ? - (fieldInfo as Field).field.getSvgRoot()! : - (fieldInfo as Icon).icon.iconGroup_!; // Never null in rendered case. - + protected layoutField_(fieldInfo: Icon | Field) { const yPos = fieldInfo.centerline - fieldInfo.height / 2; let xPos = fieldInfo.xPos; let scale = ''; @@ -298,20 +303,22 @@ export class Drawer { scale = 'scale(-1 1)'; } } - if (Types.isIcon(fieldInfo)) { - svgGroup.setAttribute('display', 'block'); - svgGroup.setAttribute( - 'transform', 'translate(' + xPos + ',' + yPos + ')'); - (fieldInfo as Icon).icon.computeIconLocation(); - } else { - svgGroup.setAttribute( - 'transform', 'translate(' + xPos + ',' + yPos + ')' + scale); - } - if (this.info_.isInsertionMarker) { - // Fields and icons are invisible on insertion marker. They still have to - // be rendered so that the block can be sized correctly. - svgGroup.setAttribute('display', 'none'); + if (Types.isIcon(fieldInfo)) { + const icon = (fieldInfo as Icon).icon; + icon.setOffsetInBlock(new Coordinate(xPos, yPos)); + if (this.info_.isInsertionMarker) { + icon.hideForInsertionMarker(); + } + } else { + const svgGroup = (fieldInfo as Field).field.getSvgRoot()!; + svgGroup.setAttribute( + 'transform', + 'translate(' + xPos + ',' + yPos + ')' + scale + ); + if (this.info_.isInsertionMarker) { + svgGroup.setAttribute('display', 'none'); + } } } @@ -329,12 +336,14 @@ export class Drawer { const connectionBottom = input.connectionHeight + connectionTop; const connectionRight = input.xPos + input.connectionWidth; - this.inlinePath_ += svgPaths.moveTo(connectionRight, yPos) + - svgPaths.lineOnAxis('v', connectionTop) + - (input.shape as PuzzleTab).pathDown + - svgPaths.lineOnAxis('v', height - connectionBottom) + - svgPaths.lineOnAxis('h', width - input.connectionWidth) + - svgPaths.lineOnAxis('v', -height) + 'z'; + this.inlinePath_ += + svgPaths.moveTo(connectionRight, yPos) + + svgPaths.lineOnAxis('v', connectionTop) + + (input.shape as PuzzleTab).pathDown + + svgPaths.lineOnAxis('v', height - connectionBottom) + + svgPaths.lineOnAxis('h', width - input.connectionWidth) + + svgPaths.lineOnAxis('v', -height) + + 'z'; this.positionInlineInputConnection_(input); } @@ -356,7 +365,9 @@ export class Drawer { connX *= -1; } input.connectionModel.setOffsetInBlock( - connX, yPos + input.connectionOffsetY); + connX, + yPos + input.connectionOffsetY + ); } } @@ -412,7 +423,7 @@ export class Drawer { if (bottomRow.connection) { const connInfo = bottomRow.connection; - const x = connInfo.xPos; // Already contains info about startX. + const x = connInfo.xPos; // Already contains info about startX. const connX = this.info_.RTL ? -x : x; connInfo.connectionModel.setOffsetInBlock(connX, bottomRow.baseline); } @@ -422,10 +433,12 @@ export class Drawer { protected positionOutputConnection_() { if (this.info_.outputConnection) { const x = - this.info_.startX + this.info_.outputConnection.connectionOffsetX; + this.info_.startX + this.info_.outputConnection.connectionOffsetX; const connX = this.info_.RTL ? -x : x; this.block_.outputConnection.setOffsetInBlock( - connX, this.info_.outputConnection.connectionOffsetY); + connX, + this.info_.outputConnection.connectionOffsetY + ); } } } diff --git a/core/renderers/common/i_path_object.ts b/core/renderers/common/i_path_object.ts index 68608910e..60fc67e19 100644 --- a/core/renderers/common/i_path_object.ts +++ b/core/renderers/common/i_path_object.ts @@ -12,7 +12,6 @@ import type {BlockSvg} from '../../block_svg.js'; import type {ConstantProvider} from './constants.js'; import {RenderedConnection} from '../../rendered_connection.js'; - /** * An interface for a block's path object. * @@ -35,13 +34,13 @@ export interface IPathObject { * Holds the cursors SVG element when the cursor is attached to the block. * This is null if there is no cursor on the block. */ - cursorSvg: SVGElement|null; + cursorSvg: SVGElement | null; /** * Holds the markers SVG element when the marker is attached to the block. * This is null if there is no marker on the block. */ - markerSvg: SVGElement|null; + markerSvg: SVGElement | null; /** * Set the path generated by the renderer onto the respective SVG element. diff --git a/core/renderers/common/info.ts b/core/renderers/common/info.ts index 1a162a676..6b5511ad2 100644 --- a/core/renderers/common/info.ts +++ b/core/renderers/common/info.ts @@ -8,11 +8,12 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; -import {Align, Input} from '../../input.js'; -import {inputTypes} from '../../input_types.js'; +import {Input} from '../../inputs/input.js'; +import {Align} from '../../inputs/align.js'; import type {RenderedConnection} from '../../rendered_connection.js'; import type {Measurable} from '../measurables/base.js'; import {BottomRow} from '../measurables/bottom_row.js'; +import {DummyInput} from '../../inputs/dummy_input.js'; import {ExternalValueInput} from '../measurables/external_value_input.js'; import {Field} from '../measurables/field.js'; import {Hat} from '../measurables/hat.js'; @@ -28,14 +29,15 @@ import {RoundCorner} from '../measurables/round_corner.js'; import type {Row} from '../measurables/row.js'; import {SpacerRow} from '../measurables/spacer_row.js'; import {SquareCorner} from '../measurables/square_corner.js'; -import {StatementInput} from '../measurables/statement_input.js'; +import {StatementInput as StatementInputMeasurable} from '../measurables/statement_input.js'; +import {StatementInput} from '../../inputs/statement_input.js'; import {TopRow} from '../measurables/top_row.js'; import {Types} from '../measurables/types.js'; +import {ValueInput} from '../../inputs/value_input.js'; import type {ConstantProvider} from './constants.js'; import type {Renderer} from './renderer.js'; - /** * An object containing all sizing information needed to draw this block. * @@ -46,7 +48,7 @@ import type {Renderer} from './renderer.js'; export class RenderInfo { block_: BlockSvg; protected constants_: ConstantProvider; - outputConnection: OutputConnection|null; + outputConnection: OutputConnection | null; isInline: boolean; isCollapsed: boolean; isInsertionMarker: boolean; @@ -74,8 +76,6 @@ export class RenderInfo { /** An array of input rows on the block. */ inputRows: InputRow[] = []; - /** An array of measurable objects containing hidden icons. */ - hiddenIcons: Icon[] = []; topRow: TopRow; bottomRow: BottomRow; @@ -100,9 +100,9 @@ export class RenderInfo { * A measurable representing the output connection if the block has one. * Otherwise null. */ - this.outputConnection = block.outputConnection ? - new OutputConnection(this.constants_, block.outputConnection) : - null; + this.outputConnection = block.outputConnection + ? new OutputConnection(this.constants_, block.outputConnection) + : null; /** * Whether the block should be rendered as a single line, either because @@ -171,11 +171,9 @@ export class RenderInfo { // Icons always go on the first row, before anything else. const icons = this.block_.getIcons(); - for (let i = 0, icon; icon = icons[i]; i++) { + for (let i = 0, icon; (icon = icons[i]); i++) { const iconInfo = new Icon(this.constants_, icon); - if (this.isCollapsed && icon.collapseHidden) { - this.hiddenIcons.push(iconInfo); - } else { + if (!this.isCollapsed || icon.isShownWhenCollapsed()) { activeRow.elements.push(iconInfo); } } @@ -183,7 +181,7 @@ export class RenderInfo { let lastInput = undefined; // Loop across all of the inputs on the block, creating objects for anything // that needs to be rendered and breaking the block up into visual rows. - for (let i = 0, input; input = this.block_.inputList[i]; i++) { + for (let i = 0, input; (input = this.block_.inputList[i]); i++) { if (!input.isVisible()) { continue; } @@ -195,7 +193,7 @@ export class RenderInfo { } // All of the fields in an input go on the same row. - for (let j = 0, field; field = input.fieldRow[j]; j++) { + for (let j = 0, field; (field = input.fieldRow[j]); j++) { activeRow.elements.push(new Field(this.constants_, field, input)); } this.addInput_(input, activeRow); @@ -219,13 +217,16 @@ export class RenderInfo { */ protected populateTopRow_() { const hasPrevious = !!this.block_.previousConnection; - const hasHat = (this.block_.hat ? this.block_.hat === 'cap' : - this.constants_.ADD_START_HATS) && - !this.outputConnection && !hasPrevious; + const hasHat = + (this.block_.hat + ? this.block_.hat === 'cap' + : this.constants_.ADD_START_HATS) && + !this.outputConnection && + !hasPrevious; - let cornerClass = this.topRow.hasLeftSquareCorner(this.block_) ? - SquareCorner : - RoundCorner; + let cornerClass = this.topRow.hasLeftSquareCorner(this.block_) + ? SquareCorner + : RoundCorner; this.topRow.elements.push(new cornerClass(this.constants_)); if (hasHat) { @@ -235,25 +236,28 @@ export class RenderInfo { } else if (hasPrevious) { this.topRow.hasPreviousConnection = true; this.topRow.connection = new PreviousConnection( - this.constants_, - (this.block_.previousConnection as RenderedConnection)); + this.constants_, + this.block_.previousConnection as RenderedConnection + ); this.topRow.elements.push(this.topRow.connection); } - const precedesStatement = this.block_.inputList.length && - this.block_.inputList[0].type === inputTypes.STATEMENT; + const precedesStatement = + this.block_.inputList.length && + this.block_.inputList[0] instanceof StatementInput; // This is the minimum height for the row. If one of its elements has a // greater height it will be overwritten in the compute pass. if (precedesStatement && !this.block_.isCollapsed()) { this.topRow.minHeight = - this.constants_.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT; + this.constants_.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT; } else { this.topRow.minHeight = this.constants_.TOP_ROW_MIN_HEIGHT; } - cornerClass = this.topRow.hasRightSquareCorner(this.block_) ? SquareCorner : - RoundCorner; + cornerClass = this.topRow.hasRightSquareCorner(this.block_) + ? SquareCorner + : RoundCorner; this.topRow.elements.push(new cornerClass(this.constants_, 'right')); } @@ -263,15 +267,16 @@ export class RenderInfo { protected populateBottomRow_() { this.bottomRow.hasNextConnection = !!this.block_.nextConnection; - const followsStatement = this.block_.inputList.length && - this.block_.inputList[this.block_.inputList.length - 1].type === - inputTypes.STATEMENT; + const followsStatement = + this.block_.inputList.length && + this.block_.inputList[this.block_.inputList.length - 1] instanceof + StatementInput; // This is the minimum height for the row. If one of its elements has a // greater height it will be overwritten in the compute pass. if (followsStatement) { this.bottomRow.minHeight = - this.constants_.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT; + this.constants_.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT; } else { this.bottomRow.minHeight = this.constants_.BOTTOM_ROW_MIN_HEIGHT; } @@ -286,7 +291,9 @@ export class RenderInfo { if (this.bottomRow.hasNextConnection) { this.bottomRow.connection = new NextConnection( - this.constants_, (this.block_.nextConnection as RenderedConnection)); + this.constants_, + this.block_.nextConnection as RenderedConnection + ); this.bottomRow.elements.push(this.bottomRow.connection); } @@ -308,23 +315,26 @@ export class RenderInfo { */ protected addInput_(input: Input, activeRow: Row) { // Non-dummy inputs have visual representations onscreen. - if (this.isInline && input.type === inputTypes.VALUE) { + if (this.isInline && input instanceof ValueInput) { activeRow.elements.push(new InlineInput(this.constants_, input)); activeRow.hasInlineInput = true; - } else if (input.type === inputTypes.STATEMENT) { - activeRow.elements.push(new StatementInput(this.constants_, input)); + } else if (input instanceof StatementInput) { + activeRow.elements.push( + new StatementInputMeasurable(this.constants_, input) + ); activeRow.hasStatement = true; - } else if (input.type === inputTypes.VALUE) { + } else if (input instanceof ValueInput) { activeRow.elements.push(new ExternalValueInput(this.constants_, input)); activeRow.hasExternalInput = true; - } else if (input.type === inputTypes.DUMMY) { + } else if (input instanceof DummyInput) { // Dummy inputs have no visual representation, but the information is // still important. activeRow.minHeight = Math.max( - activeRow.minHeight, - input.getSourceBlock() && input.getSourceBlock()!.isShadow() ? - this.constants_.DUMMY_INPUT_SHADOW_MIN_HEIGHT : - this.constants_.DUMMY_INPUT_MIN_HEIGHT); + activeRow.minHeight, + input.getSourceBlock() && input.getSourceBlock()!.isShadow() + ? this.constants_.DUMMY_INPUT_SHADOW_MIN_HEIGHT + : this.constants_.DUMMY_INPUT_MIN_HEIGHT + ); activeRow.hasDummyInput = true; } if (activeRow.align === null) { @@ -346,12 +356,14 @@ export class RenderInfo { return false; } // A statement input or an input following one always gets a new row. - if (input.type === inputTypes.STATEMENT || - lastInput.type === inputTypes.STATEMENT) { + if ( + input instanceof StatementInput || + lastInput instanceof StatementInput + ) { return true; } // Value and dummy inputs get new row if inputs are not inlined. - if (input.type === inputTypes.VALUE || input.type === inputTypes.DUMMY) { + if (input instanceof ValueInput || input instanceof DummyInput) { return !this.isInline; } return false; @@ -359,14 +371,18 @@ export class RenderInfo { /** Add horizontal spacing between and around elements within each row. */ protected addElemSpacing_() { - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { const oldElems = row.elements; row.elements = []; // No spacing needed before the corner on the top row or the bottom row. if (row.startsWithElemSpacer()) { // There's a spacer before the first element in the row. - row.elements.push(new InRowSpacer( - this.constants_, this.getInRowSpacing_(null, oldElems[0]))); + row.elements.push( + new InRowSpacer( + this.constants_, + this.getInRowSpacing_(null, oldElems[0]) + ) + ); } if (!oldElems.length) { continue; @@ -379,9 +395,12 @@ export class RenderInfo { row.elements.push(oldElems[oldElems.length - 1]); if (row.endsWithElemSpacer()) { // There's a spacer after the last element in the row. - row.elements.push(new InRowSpacer( + row.elements.push( + new InRowSpacer( this.constants_, - this.getInRowSpacing_(oldElems[oldElems.length - 1], null))); + this.getInRowSpacing_(oldElems[oldElems.length - 1], null) + ) + ); } } } @@ -395,8 +414,10 @@ export class RenderInfo { * @param next The element after the spacer. * @returns The size of the spacing between the two elements. */ - protected getInRowSpacing_(prev: Measurable|null, next: Measurable|null): - number { + protected getInRowSpacing_( + prev: Measurable | null, + next: Measurable | null + ): number { if (!prev) { // Statement input padding. if (next && Types.isStatementInput(next)) { @@ -439,23 +460,27 @@ export class RenderInfo { let widestStatementRowFields = 0; let blockWidth = 0; let widestRowWithConnectedBlocks = 0; - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { row.measure(); blockWidth = Math.max(blockWidth, row.width); if (row.hasStatement) { const statementInput = row.getLastInput(); const innerWidth = row.width - (statementInput?.width ?? 0); - widestStatementRowFields = - Math.max(widestStatementRowFields, innerWidth); + widestStatementRowFields = Math.max( + widestStatementRowFields, + innerWidth + ); } - widestRowWithConnectedBlocks = - Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks); + widestRowWithConnectedBlocks = Math.max( + widestRowWithConnectedBlocks, + row.widthWithConnectedBlocks + ); } this.statementEdge = widestStatementRowFields; this.width = blockWidth; - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { if (row.hasStatement) { row.statementEdge = this.statementEdge; } @@ -476,7 +501,7 @@ export class RenderInfo { * the sizes of all rows. */ protected alignRowElements_() { - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { if (row.hasStatement) { this.alignStatementRow_(row as InputRow); } else { @@ -561,8 +586,10 @@ export class RenderInfo { statementInput.width += desiredWidth - currentWidth; statementInput.height = Math.max(statementInput.height, row.height); row.width += desiredWidth - currentWidth; - row.widthWithConnectedBlocks = - Math.max(row.width, this.statementEdge + row.connectedBlockWidths); + row.widthWithConnectedBlocks = Math.max( + row.width, + this.statementEdge + row.connectedBlockWidths + ); } /** Add spacers between rows and set their sizes. */ @@ -638,7 +665,7 @@ export class RenderInfo { if (Types.isBottomRow(row)) { const bottomRow = row as BottomRow; const baseline = - bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight; + bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight; if (Types.isNextConnection(elem)) { return baseline + elem.height / 2; } @@ -662,7 +689,7 @@ export class RenderInfo { */ protected recordElemPositions_(row: Row) { let xCursor = row.xPos; - for (let j = 0, elem; elem = row.elements[j]; j++) { + for (let j = 0, elem; (elem = row.elements[j]); j++) { // Now that row heights are finalized, make spacers use the row height. if (Types.isSpacer(elem)) { elem.height = row.height; @@ -683,13 +710,15 @@ export class RenderInfo { // accesses and sets properties that already exist on the objects. let widestRowWithConnectedBlocks = 0; let yCursor = 0; - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { row.yPos = yCursor; row.xPos = this.startX; yCursor += row.height; - widestRowWithConnectedBlocks = - Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks); + widestRowWithConnectedBlocks = Math.max( + widestRowWithConnectedBlocks, + row.widthWithConnectedBlocks + ); this.recordElemPositions_(row); } if (this.outputConnection && this.block_.nextConnection) { @@ -697,7 +726,9 @@ export class RenderInfo { if (target) { // Include width of connected block in value to stack width measurement. widestRowWithConnectedBlocks = Math.max( - widestRowWithConnectedBlocks, target.getHeightWidth().width); + widestRowWithConnectedBlocks, + target.getHeightWidth().width + ); } } diff --git a/core/renderers/common/marker_svg.ts b/core/renderers/common/marker_svg.ts index 503df4565..13db9ea0b 100644 --- a/core/renderers/common/marker_svg.ts +++ b/core/renderers/common/marker_svg.ts @@ -26,7 +26,6 @@ import type {WorkspaceSvg} from '../../workspace_svg.js'; import type {ConstantProvider, Notch, PuzzleTab} from './constants.js'; - /** The name of the CSS class for a cursor. */ const CURSOR_CLASS = 'blocklyCursor'; @@ -48,22 +47,22 @@ export class MarkerSvg { * The workspace, field, or block that the marker SVG element should be * attached to. */ - private parent_: IASTNodeLocationSvg|null = null; + private parent: IASTNodeLocationSvg | null = null; /** The current SVG element for the marker. */ - currentMarkerSvg: SVGElement|null = null; + currentMarkerSvg: SVGElement | null = null; colour_: string; /** The root SVG group containing the marker. */ - protected markerSvg_: SVGGElement|null = null; - protected svgGroup_: SVGGElement|null = null; + protected markerSvg_: SVGGElement | null = null; + protected svgGroup_: SVGGElement | null = null; - protected markerBlock_: SVGPathElement|null = null; + protected markerBlock_: SVGPathElement | null = null; - protected markerInput_: SVGPathElement|null = null; - protected markerSvgLine_: SVGRectElement|null = null; + protected markerInput_: SVGPathElement | null = null; + protected markerSvgLine_: SVGRectElement | null = null; - protected markerSvgRect_: SVGRectElement|null = null; + protected markerSvgRect_: SVGRectElement | null = null; /** The constants necessary to draw the marker. */ protected constants_: ConstantProvider; @@ -74,12 +73,15 @@ export class MarkerSvg { * @param marker The marker to draw. */ constructor( - private readonly workspace: WorkspaceSvg, constants: ConstantProvider, - private readonly marker: Marker) { + private readonly workspace: WorkspaceSvg, + constants: ConstantProvider, + private readonly marker: Marker + ) { this.constants_ = constants; - const defaultColour = this.isCursor() ? this.constants_.CURSOR_COLOUR : - this.constants_.MARKER_COLOUR; + const defaultColour = this.isCursor() + ? this.constants_.CURSOR_COLOUR + : this.constants_.MARKER_COLOUR; /** The colour of the marker. */ this.colour_ = marker.colour || defaultColour; @@ -90,7 +92,7 @@ export class MarkerSvg { * * @returns The root SVG node. */ - getSvgRoot(): SVGElement|null { + getSvgRoot(): SVGElement | null { return this.svgGroup_; } @@ -135,17 +137,17 @@ export class MarkerSvg { */ protected setParent_(newParent: IASTNodeLocationSvg) { if (!this.isCursor()) { - if (this.parent_) { - this.parent_.setMarkerSvg(null); + if (this.parent) { + this.parent.setMarkerSvg(null); } newParent.setMarkerSvg(this.getSvgRoot()); } else { - if (this.parent_) { - this.parent_.setCursorSvg(null); + if (this.parent) { + this.parent.setCursorSvg(null); } newParent.setCursorSvg(this.getSvgRoot()); } - this.parent_ = newParent; + this.parent = newParent; } /** @@ -162,20 +164,21 @@ export class MarkerSvg { this.constants_ = this.workspace.getRenderer().getConstants(); - const defaultColour = this.isCursor() ? this.constants_.CURSOR_COLOUR : - this.constants_.MARKER_COLOUR; + const defaultColour = this.isCursor() + ? this.constants_.CURSOR_COLOUR + : this.constants_.MARKER_COLOUR; this.colour_ = this.marker.colour || defaultColour; this.applyColour_(curNode); this.showAtLocation_(curNode); - this.fireMarkerEvent_(oldNode, curNode); + this.fireMarkerEvent(oldNode, curNode); // Ensures the marker will be visible immediately after the move. const animate = this.currentMarkerSvg!.childNodes[0]; if (animate !== undefined) { (animate as SVGAnimationElement).beginElement && - (animate as SVGAnimationElement).beginElement(); + (animate as SVGAnimationElement).beginElement(); } } @@ -216,7 +219,7 @@ export class MarkerSvg { * * @param curNode The node to draw the marker for. */ - private showWithBlockPrevOutput_(curNode: ASTNode) { + private showWithBlockPrevOutput(curNode: ASTNode) { const block = curNode.getSourceBlock() as BlockSvg; const width = block.width; const height = block.height; @@ -224,13 +227,19 @@ export class MarkerSvg { const markerOffset = this.constants_.CURSOR_BLOCK_PADDING; if (block.previousConnection) { - const connectionShape = - this.constants_.shapeFor(block.previousConnection) as Notch; + const connectionShape = this.constants_.shapeFor( + block.previousConnection + ) as Notch; this.positionPrevious_( - width, markerOffset, markerHeight, connectionShape); + width, + markerOffset, + markerHeight, + connectionShape + ); } else if (block.outputConnection) { - const connectionShape = - this.constants_.shapeFor(block.outputConnection) as PuzzleTab; + const connectionShape = this.constants_.shapeFor( + block.outputConnection + ) as PuzzleTab; this.positionOutput_(width, height, connectionShape); } else { this.positionBlock_(width, markerOffset, markerHeight); @@ -245,7 +254,7 @@ export class MarkerSvg { * @param curNode The node to draw the marker for. */ protected showWithBlock_(curNode: ASTNode) { - this.showWithBlockPrevOutput_(curNode); + this.showWithBlockPrevOutput(curNode); } /** @@ -254,7 +263,7 @@ export class MarkerSvg { * @param curNode The node to draw the marker for. */ protected showWithPrevious_(curNode: ASTNode) { - this.showWithBlockPrevOutput_(curNode); + this.showWithBlockPrevOutput(curNode); } /** @@ -263,7 +272,7 @@ export class MarkerSvg { * @param curNode The node to draw the marker for. */ protected showWithOutput_(curNode: ASTNode) { - this.showWithBlockPrevOutput_(curNode); + this.showWithBlockPrevOutput(curNode); } /** @@ -310,7 +319,7 @@ export class MarkerSvg { */ protected showWithInput_(curNode: ASTNode) { const connection = curNode.getLocation() as RenderedConnection; - const sourceBlock = (connection.getSourceBlock()); + const sourceBlock = connection.getSourceBlock(); this.positionInput_(connection); this.setParent_(sourceBlock); @@ -325,7 +334,7 @@ export class MarkerSvg { */ protected showWithNext_(curNode: ASTNode) { const connection = curNode.getLocation() as RenderedConnection; - const targetBlock = (connection.getSourceBlock()); + const targetBlock = connection.getSourceBlock(); let x = 0; const y = connection.getOffsetInBlock().y; const width = targetBlock.getHeightWidth().width; @@ -391,18 +400,23 @@ export class MarkerSvg { * @param markerHeight The height of the marker. */ protected positionBlock_( - width: number, markerOffset: number, markerHeight: number) { - const markerPath = svgPaths.moveBy(-markerOffset, markerHeight) + - svgPaths.lineOnAxis('V', -markerOffset) + - svgPaths.lineOnAxis('H', width + markerOffset * 2) + - svgPaths.lineOnAxis('V', markerHeight); + width: number, + markerOffset: number, + markerHeight: number + ) { + const markerPath = + svgPaths.moveBy(-markerOffset, markerHeight) + + svgPaths.lineOnAxis('V', -markerOffset) + + svgPaths.lineOnAxis('H', width + markerOffset * 2) + + svgPaths.lineOnAxis('V', markerHeight); if (!this.markerBlock_) { throw new Error( - 'createDom should be called before positioning the marker'); + 'createDom should be called before positioning the marker' + ); } this.markerBlock_.setAttribute('d', markerPath); if (this.workspace.RTL) { - this.flipRtl_(this.markerBlock_); + this.flipRtl(this.markerBlock_); } this.currentMarkerSvg = this.markerBlock_; } @@ -417,14 +431,20 @@ export class MarkerSvg { const x = connection.getOffsetInBlock().x; const y = connection.getOffsetInBlock().y; - const path = svgPaths.moveTo(0, 0) + - (this.constants_.shapeFor(connection) as PuzzleTab).pathDown; + const path = + svgPaths.moveTo(0, 0) + + (this.constants_.shapeFor(connection) as PuzzleTab).pathDown; this.markerInput_!.setAttribute('d', path); this.markerInput_!.setAttribute( - 'transform', - 'translate(' + x + ',' + y + ')' + - (this.workspace.RTL ? ' scale(-1 1)' : '')); + 'transform', + 'translate(' + + x + + ',' + + y + + ')' + + (this.workspace.RTL ? ' scale(-1 1)' : '') + ); this.currentMarkerSvg = this.markerInput_; } @@ -455,19 +475,25 @@ export class MarkerSvg { * @param connectionShape The shape object for the connection. */ protected positionOutput_( - width: number, height: number, connectionShape: PuzzleTab) { + width: number, + height: number, + connectionShape: PuzzleTab + ) { if (!this.markerBlock_) { throw new Error( - 'createDom should be called before positioning the output'); + 'createDom should be called before positioning the output' + ); } - const markerPath = svgPaths.moveBy(width, 0) + - svgPaths.lineOnAxis('h', -(width - connectionShape.width)) + - svgPaths.lineOnAxis('v', this.constants_.TAB_OFFSET_FROM_TOP) + - connectionShape.pathDown + svgPaths.lineOnAxis('V', height) + - svgPaths.lineOnAxis('H', width); + const markerPath = + svgPaths.moveBy(width, 0) + + svgPaths.lineOnAxis('h', -(width - connectionShape.width)) + + svgPaths.lineOnAxis('v', this.constants_.TAB_OFFSET_FROM_TOP) + + connectionShape.pathDown + + svgPaths.lineOnAxis('V', height) + + svgPaths.lineOnAxis('H', width); this.markerBlock_.setAttribute('d', markerPath); if (this.workspace.RTL) { - this.flipRtl_(this.markerBlock_); + this.flipRtl(this.markerBlock_); } this.currentMarkerSvg = this.markerBlock_; } @@ -483,21 +509,26 @@ export class MarkerSvg { * @param connectionShape The shape object for the connection. */ protected positionPrevious_( - width: number, markerOffset: number, markerHeight: number, - connectionShape: Notch) { + width: number, + markerOffset: number, + markerHeight: number, + connectionShape: Notch + ) { if (!this.markerBlock_) { throw new Error( - 'createDom should be called before positioning the previous connection marker'); + 'createDom should be called before positioning the previous connection marker' + ); } - const markerPath = svgPaths.moveBy(-markerOffset, markerHeight) + - svgPaths.lineOnAxis('V', -markerOffset) + - svgPaths.lineOnAxis('H', this.constants_.NOTCH_OFFSET_LEFT) + - connectionShape.pathLeft + - svgPaths.lineOnAxis('H', width + markerOffset * 2) + - svgPaths.lineOnAxis('V', markerHeight); + const markerPath = + svgPaths.moveBy(-markerOffset, markerHeight) + + svgPaths.lineOnAxis('V', -markerOffset) + + svgPaths.lineOnAxis('H', this.constants_.NOTCH_OFFSET_LEFT) + + connectionShape.pathLeft + + svgPaths.lineOnAxis('H', width + markerOffset * 2) + + svgPaths.lineOnAxis('V', markerHeight); this.markerBlock_.setAttribute('d', markerPath); if (this.workspace.RTL) { - this.flipRtl_(this.markerBlock_); + this.flipRtl(this.markerBlock_); } this.currentMarkerSvg = this.markerBlock_; } @@ -527,14 +558,18 @@ export class MarkerSvg { * * @param markerSvg The marker that we want to flip. */ - private flipRtl_(markerSvg: SVGElement) { + private flipRtl(markerSvg: SVGElement) { markerSvg.setAttribute('transform', 'scale(-1 1)'); } /** Hide the marker. */ hide() { - if (!this.markerSvgLine_ || !this.markerSvgRect_ || !this.markerInput_ || - !this.markerBlock_) { + if ( + !this.markerSvgLine_ || + !this.markerSvgRect_ || + !this.markerInput_ || + !this.markerBlock_ + ) { throw new Error('createDom should be called before hiding the marker'); } this.markerSvgLine_.style.display = 'none'; @@ -549,10 +584,14 @@ export class MarkerSvg { * @param oldNode The old node the marker used to be on. * @param curNode The new node the marker is currently on. */ - private fireMarkerEvent_(oldNode: ASTNode, curNode: ASTNode) { + private fireMarkerEvent(oldNode: ASTNode, curNode: ASTNode) { const curBlock = curNode.getSourceBlock(); const event = new (eventUtils.get(eventUtils.MARKER_MOVE))( - curBlock, this.isCursor(), oldNode, curNode); + curBlock, + this.isCursor(), + oldNode, + curNode + ); eventUtils.fire(event); } @@ -588,46 +627,57 @@ export class MarkerSvg { */ this.markerSvg_ = dom.createSvgElement( - Svg.G, { - 'width': this.constants_.CURSOR_WS_WIDTH, - 'height': this.constants_.WS_CURSOR_HEIGHT, - }, - this.svgGroup_); + Svg.G, + { + 'width': this.constants_.CURSOR_WS_WIDTH, + 'height': this.constants_.WS_CURSOR_HEIGHT, + }, + this.svgGroup_ + ); // A horizontal line used to represent a workspace coordinate or next // connection. this.markerSvgLine_ = dom.createSvgElement( - Svg.RECT, { - 'width': this.constants_.CURSOR_WS_WIDTH, - 'height': this.constants_.WS_CURSOR_HEIGHT, - 'style': 'display: none', - }, - this.markerSvg_); + Svg.RECT, + { + 'width': this.constants_.CURSOR_WS_WIDTH, + 'height': this.constants_.WS_CURSOR_HEIGHT, + 'style': 'display: none', + }, + this.markerSvg_ + ); // A filled in rectangle used to represent a stack. this.markerSvgRect_ = dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyVerticalMarker', - 'rx': 10, - 'ry': 10, - 'style': 'display: none', - }, - this.markerSvg_); + Svg.RECT, + { + 'class': 'blocklyVerticalMarker', + 'rx': 10, + 'ry': 10, + 'style': 'display: none', + }, + this.markerSvg_ + ); // A filled in puzzle piece used to represent an input value. this.markerInput_ = dom.createSvgElement( - Svg.PATH, {'transform': '', 'style': 'display: none'}, this.markerSvg_); + Svg.PATH, + {'transform': '', 'style': 'display: none'}, + this.markerSvg_ + ); // A path used to represent a previous connection and a block, an output // connection and a block, or a block. this.markerBlock_ = dom.createSvgElement( - Svg.PATH, { - 'transform': '', - 'style': 'display: none', - 'fill': 'none', - 'stroke-width': this.constants_.CURSOR_STROKE_WIDTH, - }, - this.markerSvg_); + Svg.PATH, + { + 'transform': '', + 'style': 'display: none', + 'fill': 'none', + 'stroke-width': this.constants_.CURSOR_STROKE_WIDTH, + }, + this.markerSvg_ + ); // Markers and stack markers don't blink. if (this.isCursor()) { @@ -635,8 +685,10 @@ export class MarkerSvg { dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerSvgLine_); dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerInput_); dom.createSvgElement( - Svg.ANIMATE, {...blinkProperties, attributeName: 'stroke'}, - this.markerBlock_); + Svg.ANIMATE, + {...blinkProperties, attributeName: 'stroke'}, + this.markerBlock_ + ); } return this.markerSvg_; @@ -648,10 +700,15 @@ export class MarkerSvg { * @param _curNode The node that we want to draw the marker for. */ protected applyColour_(_curNode: ASTNode) { - if (!this.markerSvgLine_ || !this.markerSvgRect_ || !this.markerInput_ || - !this.markerBlock_) { + if ( + !this.markerSvgLine_ || + !this.markerSvgRect_ || + !this.markerInput_ || + !this.markerBlock_ + ) { throw new Error( - 'createDom should be called before applying color to the markerj'); + 'createDom should be called before applying color to the markerj' + ); } this.markerSvgLine_.setAttribute('fill', this.colour_); this.markerSvgRect_.setAttribute('stroke', this.colour_); diff --git a/core/renderers/common/path_object.ts b/core/renderers/common/path_object.ts index a262fef99..eb054141f 100644 --- a/core/renderers/common/path_object.ts +++ b/core/renderers/common/path_object.ts @@ -16,7 +16,6 @@ import {Svg} from '../../utils/svg.js'; import type {ConstantProvider} from './constants.js'; import type {IPathObject} from './i_path_object.js'; - /** * An object that handles creating and setting each of the SVG elements * used by the renderer. @@ -29,13 +28,13 @@ export class PathObject implements IPathObject { * Holds the cursors svg element when the cursor is attached to the block. * This is null if there is no cursor on the block. */ - cursorSvg: SVGElement|null = null; + cursorSvg: SVGElement | null = null; /** * Holds the markers svg element when the marker is attached to the block. * This is null if there is no marker on the block. */ - markerSvg: SVGElement|null = null; + markerSvg: SVGElement | null = null; constants: ConstantProvider; style: BlockStyle; @@ -46,14 +45,20 @@ export class PathObject implements IPathObject { * @param constants The renderer's constants. */ constructor( - root: SVGElement, style: BlockStyle, constants: ConstantProvider) { + root: SVGElement, + style: BlockStyle, + constants: ConstantProvider + ) { this.constants = constants; this.style = style; this.svgRoot = root; /** The primary path of the block. */ - this.svgPath = - dom.createSvgElement(Svg.PATH, {'class': 'blocklyPath'}, this.svgRoot); + this.svgPath = dom.createSvgElement( + Svg.PATH, + {'class': 'blocklyPath'}, + this.svgRoot + ); } /** @@ -159,7 +164,9 @@ export class PathObject implements IPathObject { updateHighlighted(enable: boolean) { if (enable) { this.svgPath.setAttribute( - 'filter', 'url(#' + this.constants.embossFilterId + ')'); + 'filter', + 'url(#' + this.constants.embossFilterId + ')' + ); } else { this.svgPath.setAttribute('filter', 'none'); } @@ -186,7 +193,9 @@ export class PathObject implements IPathObject { this.setClass_('blocklyDisabled', disabled); if (disabled) { this.svgPath.setAttribute( - 'fill', 'url(#' + this.constants.disabledPatternId + ')'); + 'fill', + 'url(#' + this.constants.disabledPatternId + ')' + ); } } diff --git a/core/renderers/common/renderer.ts b/core/renderers/common/renderer.ts index 3b99d23ba..d29b61639 100644 --- a/core/renderers/common/renderer.ts +++ b/core/renderers/common/renderer.ts @@ -11,7 +11,10 @@ import type {Block} from '../../block.js'; import type {BlockSvg} from '../../block_svg.js'; import {Connection} from '../../connection.js'; import {ConnectionType} from '../../connection_type.js'; -import {InsertionMarkerManager, PreviewType} from '../../insertion_marker_manager.js'; +import { + InsertionMarkerManager, + PreviewType, +} from '../../insertion_marker_manager.js'; import type {IRegistrable} from '../../interfaces/i_registrable.js'; import type {Marker} from '../../keyboard_nav/marker.js'; import type {RenderedConnection} from '../../rendered_connection.js'; @@ -19,15 +22,12 @@ import type {BlockStyle, Theme} from '../../theme.js'; import type {WorkspaceSvg} from '../../workspace_svg.js'; import {ConstantProvider} from './constants.js'; -import * as debug from './debug.js'; -import {Debug} from './debugger.js'; import {Drawer} from './drawer.js'; import type {IPathObject} from './i_path_object.js'; import {RenderInfo} from './info.js'; import {MarkerSvg} from './marker_svg.js'; import {PathObject} from './path_object.js'; - /** * The base class for a block renderer. */ @@ -40,7 +40,7 @@ export class Renderer implements IRegistrable { /** * Rendering constant overrides, passed in through options. */ - protected overrides: object|null = null; + protected overrides: object | null = null; /** * @param name The renderer name. @@ -65,7 +65,9 @@ export class Renderer implements IRegistrable { * @param opt_rendererOverrides Rendering constant overrides. */ init( - theme: Theme, opt_rendererOverrides?: {[rendererConstant: string]: any}) { + theme: Theme, + opt_rendererOverrides?: {[rendererConstant: string]: any} + ) { this.constants_ = this.makeConstants_(); if (opt_rendererOverrides) { this.overrides = opt_rendererOverrides; @@ -86,8 +88,10 @@ export class Renderer implements IRegistrable { */ createDom(svg: SVGElement, theme: Theme) { this.constants_.createDom( - svg, this.name + '-' + theme.name, - '.' + this.getClassName() + '.' + theme.getClassName()); + svg, + this.name + '-' + theme.name, + '.' + this.getClassName() + '.' + theme.getClassName() + ); } /** @@ -151,17 +155,6 @@ export class Renderer implements IRegistrable { return new Drawer(block, info); } - /** - * Create a new instance of the renderer's debugger. - * - * @returns The renderer debugger. - * @suppress {strictModuleDepCheck} Debug renderer only included in - * playground. - */ - protected makeDebugger_(): Debug { - return new Debug(this.getConstants()); - } - /** * Create a new instance of the renderer's marker drawer. * @@ -181,7 +174,7 @@ export class Renderer implements IRegistrable { * @returns The renderer path object. */ makePathObject(root: SVGElement, style: BlockStyle): IPathObject { - return new PathObject(root, style, (this.constants_)); + return new PathObject(root, style, this.constants_); } /** @@ -217,12 +210,18 @@ export class Renderer implements IRegistrable { * @returns Whether there is a home for the orphan or not. */ protected orphanCanConnectAtEnd( - topBlock: BlockSvg, orphanBlock: BlockSvg, localType: number): boolean { - const orphanConnection = localType === ConnectionType.OUTPUT_VALUE ? - orphanBlock.outputConnection : - orphanBlock.previousConnection; + topBlock: BlockSvg, + orphanBlock: BlockSvg, + localType: number + ): boolean { + const orphanConnection = + localType === ConnectionType.OUTPUT_VALUE + ? orphanBlock.outputConnection + : orphanBlock.previousConnection; return !!Connection.getConnectionForOrphanedConnection( - topBlock as Block, orphanConnection as Connection); + topBlock as Block, + orphanConnection as Connection + ); } /** @@ -235,13 +234,22 @@ export class Renderer implements IRegistrable { * @returns The preview type to display. */ getConnectionPreviewMethod( - closest: RenderedConnection, local: RenderedConnection, - topBlock: BlockSvg): PreviewType { - if (local.type === ConnectionType.OUTPUT_VALUE || - local.type === ConnectionType.PREVIOUS_STATEMENT) { - if (!closest.isConnected() || - this.orphanCanConnectAtEnd( - topBlock, closest.targetBlock() as BlockSvg, local.type)) { + closest: RenderedConnection, + local: RenderedConnection, + topBlock: BlockSvg + ): PreviewType { + if ( + local.type === ConnectionType.OUTPUT_VALUE || + local.type === ConnectionType.PREVIOUS_STATEMENT + ) { + if ( + !closest.isConnected() || + this.orphanCanConnectAtEnd( + topBlock, + closest.targetBlock() as BlockSvg, + local.type + ) + ) { return InsertionMarkerManager.PREVIEW_TYPE.INSERTION_MARKER; } return InsertionMarkerManager.PREVIEW_TYPE.REPLACEMENT_FADE; @@ -257,9 +265,6 @@ export class Renderer implements IRegistrable { * @internal */ render(block: BlockSvg) { - if (debug.isDebuggerEnabled() && !block.renderingDebugger) { - block.renderingDebugger = this.makeDebugger_(); - } const info = this.makeRenderInfo_(block); info.measure(); this.makeDrawer_(block, info).draw(); diff --git a/core/renderers/geras/constants.ts b/core/renderers/geras/constants.ts index c7d0c498a..37e3b2f9c 100644 --- a/core/renderers/geras/constants.ts +++ b/core/renderers/geras/constants.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.geras.ConstantProvider'); import {ConstantProvider as BaseConstantProvider} from '../common/constants.js'; - /** * An object that provides constants for rendering blocks in Geras mode. */ diff --git a/core/renderers/geras/drawer.ts b/core/renderers/geras/drawer.ts index 0ccff67bc..d96933de7 100644 --- a/core/renderers/geras/drawer.ts +++ b/core/renderers/geras/drawer.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.geras.Drawer'); import type {BlockSvg} from '../../block_svg.js'; import * as svgPaths from '../../utils/svg_paths.js'; -import * as debug from '../common/debug.js'; import {Drawer as BaseDrawer} from '../common/drawer.js'; import type {Row} from '../measurables/row.js'; @@ -19,7 +18,6 @@ import type {RenderInfo} from './info.js'; import type {InlineInput} from './measurables/inline_input.js'; import type {PathObject} from './path_object.js'; - /** * An object that draws a block based on the given rendering information, * customized for the geras renderer. @@ -41,7 +39,6 @@ export class Drawer extends BaseDrawer { } override draw() { - this.hideHiddenIcons_(); this.drawOutline_(); this.drawInternals_(); @@ -51,9 +48,6 @@ export class Drawer extends BaseDrawer { if (this.info_.RTL) { pathObject.flipRTL(); } - if (debug.isDebuggerEnabled()) { - this.block_?.renderingDebugger?.drawDebug(this.block_, this.info_); - } this.recordSizeOnBlock_(); } @@ -85,8 +79,9 @@ export class Drawer extends BaseDrawer { override drawRightSideRow_(row: Row) { this.highlighter_.drawRightSideRow(row); - this.outlinePath_ += svgPaths.lineOnAxis('H', row.xPos + row.width) + - svgPaths.lineOnAxis('V', row.yPos + row.height); + this.outlinePath_ += + svgPaths.lineOnAxis('H', row.xPos + row.width) + + svgPaths.lineOnAxis('V', row.yPos + row.height); } override drawBottom_() { @@ -117,13 +112,14 @@ export class Drawer extends BaseDrawer { if (input.connectionModel) { // xPos already contains info about startX let connX = - input.xPos + input.connectionWidth + this.constants_.DARK_PATH_OFFSET; + input.xPos + input.connectionWidth + this.constants_.DARK_PATH_OFFSET; if (this.info_.RTL) { connX *= -1; } input.connectionModel.setOffsetInBlock( - connX, - yPos + input.connectionOffsetY + this.constants_.DARK_PATH_OFFSET); + connX, + yPos + input.connectionOffsetY + this.constants_.DARK_PATH_OFFSET + ); } } @@ -137,7 +133,9 @@ export class Drawer extends BaseDrawer { connX += this.constants_.DARK_PATH_OFFSET; } input.connectionModel.setOffsetInBlock( - connX, row.yPos + this.constants_.DARK_PATH_OFFSET); + connX, + row.yPos + this.constants_.DARK_PATH_OFFSET + ); } } @@ -157,11 +155,13 @@ export class Drawer extends BaseDrawer { if (bottomRow.connection) { const connInfo = bottomRow.connection; - const x = connInfo.xPos; // Already contains info about startX. + const x = connInfo.xPos; // Already contains info about startX. const connX = - (this.info_.RTL ? -x : x) + this.constants_.DARK_PATH_OFFSET / 2; + (this.info_.RTL ? -x : x) + this.constants_.DARK_PATH_OFFSET / 2; connInfo.connectionModel.setOffsetInBlock( - connX, bottomRow.baseline + this.constants_.DARK_PATH_OFFSET); + connX, + bottomRow.baseline + this.constants_.DARK_PATH_OFFSET + ); } } } diff --git a/core/renderers/geras/geras.ts b/core/renderers/geras/geras.ts index 6aa960a8c..03c4f034a 100644 --- a/core/renderers/geras/geras.ts +++ b/core/renderers/geras/geras.ts @@ -19,7 +19,6 @@ import {StatementInput} from './measurables/statement_input.js'; import {PathObject} from './path_object.js'; import {Renderer} from './renderer.js'; - export { ConstantProvider, Drawer, diff --git a/core/renderers/geras/highlight_constants.ts b/core/renderers/geras/highlight_constants.ts index 736e914ee..e904c5527 100644 --- a/core/renderers/geras/highlight_constants.ts +++ b/core/renderers/geras/highlight_constants.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.geras.HighlightConstantProvider'); import * as svgPaths from '../../utils/svg_paths.js'; import type {ConstantProvider} from '../common/constants.js'; - /** An object containing sizing and path information about an outside corner. */ export interface OutsideCorner { height: number; @@ -146,22 +145,30 @@ export class HighlightConstantProvider { */ const distance45outside = (1 - Math.SQRT1_2) * (radius + offset) - offset; - const pathTopRtl = svgPaths.moveBy(distance45outside, distance45outside) + - svgPaths.arc( - 'a', '0 0,0', radius, - svgPaths.point( - -distance45outside - offset, radius - distance45outside)); + const pathTopRtl = + svgPaths.moveBy(distance45outside, distance45outside) + + svgPaths.arc( + 'a', + '0 0,0', + radius, + svgPaths.point(-distance45outside - offset, radius - distance45outside) + ); const pathBottomRtl = svgPaths.arc( - 'a', '0 0,0', radius + offset, - svgPaths.point(radius + offset, radius + offset)); + 'a', + '0 0,0', + radius + offset, + svgPaths.point(radius + offset, radius + offset) + ); const pathBottomLtr = - svgPaths.moveBy(distance45outside, -distance45outside) + - svgPaths.arc( - 'a', '0 0,0', radius + offset, - svgPaths.point( - radius - distance45outside, distance45outside + offset)); + svgPaths.moveBy(distance45outside, -distance45outside) + + svgPaths.arc( + 'a', + '0 0,0', + radius + offset, + svgPaths.point(radius - distance45outside, distance45outside + offset) + ); return { width: radius + offset, @@ -192,25 +199,35 @@ export class HighlightConstantProvider { const topLeftStartX = distance45inside; const topLeftStartY = distance45inside; const topLeftCornerHighlightRtl = - svgPaths.moveBy(topLeftStartX, topLeftStartY) + - svgPaths.arc( - 'a', '0 0,1', radius - offset, - svgPaths.point(radius - topLeftStartX, -topLeftStartY + offset)); + svgPaths.moveBy(topLeftStartX, topLeftStartY) + + svgPaths.arc( + 'a', + '0 0,1', + radius - offset, + svgPaths.point(radius - topLeftStartX, -topLeftStartY + offset) + ); /** * SVG path for drawing the highlight on the rounded top-left corner. */ - const topLeftCornerHighlightLtr = svgPaths.moveBy(offset, radius) + - svgPaths.arc( - 'a', '0 0,1', radius - offset, - svgPaths.point(radius, -radius + offset)); + const topLeftCornerHighlightLtr = + svgPaths.moveBy(offset, radius) + + svgPaths.arc( + 'a', + '0 0,1', + radius - offset, + svgPaths.point(radius, -radius + offset) + ); const bottomLeftStartX = distance45inside; const bottomLeftStartY = -distance45inside; - const bottomLeftPath = svgPaths.moveBy(bottomLeftStartX, bottomLeftStartY) + - svgPaths.arc( - 'a', '0 0,1', radius - offset, - svgPaths.point( - -bottomLeftStartX + offset, -bottomLeftStartY - radius)); + const bottomLeftPath = + svgPaths.moveBy(bottomLeftStartX, bottomLeftStartY) + + svgPaths.arc( + 'a', + '0 0,1', + radius - offset, + svgPaths.point(-bottomLeftStartX + offset, -bottomLeftStartY - radius) + ); return { height: radius, @@ -236,29 +253,30 @@ export class HighlightConstantProvider { const verticalOverlap = 2.5; const highlightRtlUp = - svgPaths.moveBy(-2, -height + verticalOverlap + 0.9) + - svgPaths.lineTo(width * -0.45, -2.1); + svgPaths.moveBy(-2, -height + verticalOverlap + 0.9) + + svgPaths.lineTo(width * -0.45, -2.1); - const highlightRtlDown = svgPaths.lineOnAxis('v', verticalOverlap) + - svgPaths.moveBy(-width * 0.97, 2.5) + - svgPaths.curve( - 'q', - [ - svgPaths.point(-width * 0.05, 10), - svgPaths.point(width * 0.3, 9.5), - ]) + - svgPaths.moveBy(width * 0.67, -1.9) + - svgPaths.lineOnAxis('v', verticalOverlap); + const highlightRtlDown = + svgPaths.lineOnAxis('v', verticalOverlap) + + svgPaths.moveBy(-width * 0.97, 2.5) + + svgPaths.curve('q', [ + svgPaths.point(-width * 0.05, 10), + svgPaths.point(width * 0.3, 9.5), + ]) + + svgPaths.moveBy(width * 0.67, -1.9) + + svgPaths.lineOnAxis('v', verticalOverlap); - const highlightLtrUp = svgPaths.lineOnAxis('v', -1.5) + - svgPaths.moveBy(width * -0.92, -0.5) + - svgPaths.curve( - 'q', - [svgPaths.point(width * -0.19, -5.5), svgPaths.point(0, -11)]) + - svgPaths.moveBy(width * 0.92, 1); + const highlightLtrUp = + svgPaths.lineOnAxis('v', -1.5) + + svgPaths.moveBy(width * -0.92, -0.5) + + svgPaths.curve('q', [ + svgPaths.point(width * -0.19, -5.5), + svgPaths.point(0, -11), + ]) + + svgPaths.moveBy(width * 0.92, 1); const highlightLtrDown = - svgPaths.moveBy(-5, height - 0.7) + svgPaths.lineTo(width * 0.46, -2.1); + svgPaths.moveBy(-5, height - 0.7) + svgPaths.lineTo(width * 0.46, -2.1); return { width, @@ -278,8 +296,9 @@ export class HighlightConstantProvider { */ protected makeNotch(): Notch { // This is only for the previous connection. - const pathLeft = svgPaths.lineOnAxis('h', this.OFFSET) + - this.constantProvider.NOTCH.pathLeft; + const pathLeft = + svgPaths.lineOnAxis('h', this.OFFSET) + + this.constantProvider.NOTCH.pathLeft; return {pathLeft}; } @@ -288,8 +307,10 @@ export class HighlightConstantProvider { * block edge highlights. */ protected makeJaggedTeeth(): JaggedTeeth { - const pathLeft = svgPaths.lineTo(5.1, 2.6) + svgPaths.moveBy(-10.2, 6.8) + - svgPaths.lineTo(5.1, 2.6); + const pathLeft = + svgPaths.lineTo(5.1, 2.6) + + svgPaths.moveBy(-10.2, 6.8) + + svgPaths.lineTo(5.1, 2.6); return {pathLeft, height: 12, width: 10.2}; } @@ -299,17 +320,20 @@ export class HighlightConstantProvider { */ protected makeStartHat(): StartHat { const hatHeight = this.constantProvider.START_HAT.height; - const pathRtl = svgPaths.moveBy(25, -8.7) + svgPaths.curve('c', [ - svgPaths.point(29.7, -6.2), - svgPaths.point(57.2, -0.5), - svgPaths.point(75, 8.7), - ]); + const pathRtl = + svgPaths.moveBy(25, -8.7) + + svgPaths.curve('c', [ + svgPaths.point(29.7, -6.2), + svgPaths.point(57.2, -0.5), + svgPaths.point(75, 8.7), + ]); - const pathLtr = svgPaths.curve('c', [ - svgPaths.point(17.8, -9.2), - svgPaths.point(45.3, -14.9), - svgPaths.point(75, -8.7), - ]) + svgPaths.moveTo(100.5, hatHeight + 0.5); + const pathLtr = + svgPaths.curve('c', [ + svgPaths.point(17.8, -9.2), + svgPaths.point(45.3, -14.9), + svgPaths.point(75, -8.7), + ]) + svgPaths.moveTo(100.5, hatHeight + 0.5); return { path(rtl) { return rtl ? pathRtl : pathLtr; diff --git a/core/renderers/geras/highlighter.ts b/core/renderers/geras/highlighter.ts index cb2c7e2c5..c9aee9a2e 100644 --- a/core/renderers/geras/highlighter.ts +++ b/core/renderers/geras/highlighter.ts @@ -15,11 +15,18 @@ import {SpacerRow} from '../measurables/spacer_row.js'; import type {TopRow} from '../measurables/top_row.js'; import {Types} from '../measurables/types.js'; -import type {HighlightConstantProvider, InsideCorner, JaggedTeeth, Notch, OutsideCorner, PuzzleTab, StartHat} from './highlight_constants.js'; +import type { + HighlightConstantProvider, + InsideCorner, + JaggedTeeth, + Notch, + OutsideCorner, + PuzzleTab, + StartHat, +} from './highlight_constants.js'; import type {RenderInfo} from './info.js'; import type {InlineInput} from './measurables/inline_input.js'; - /** * An object that adds highlights to a block based on the given rendering * information. @@ -38,7 +45,7 @@ export class Highlighter { RTL_: boolean; constants_: ConstantProvider; highlightConstants_: HighlightConstantProvider; - private readonly highlightOffset_: number; + private readonly highlightOffset: number; outsideCornerPaths_: OutsideCorner; insideCornerPaths_: InsideCorner; puzzleTabPaths_: PuzzleTab; @@ -55,14 +62,14 @@ export class Highlighter { this.RTL_ = this.info_.RTL; - const renderer = (info.getRenderer()); + const renderer = info.getRenderer(); /** The renderer's constant provider. */ this.constants_ = renderer.getConstants(); this.highlightConstants_ = renderer.getHighlightConstants(); /** The offset between the block's main path and highlight path. */ - this.highlightOffset_ = this.highlightConstants_.OFFSET; + this.highlightOffset = this.highlightConstants_.OFFSET; this.outsideCornerPaths_ = this.highlightConstants_.OUTSIDE_CORNER; this.insideCornerPaths_ = this.highlightConstants_.INSIDE_CORNER; @@ -88,7 +95,7 @@ export class Highlighter { */ drawTopCorner(row: TopRow) { this.steps_ += svgPaths.moveBy(row.xPos, this.info_.startY); - for (let i = 0, elem; elem = row.elements[i]; i++) { + for (let i = 0, elem; (elem = row.elements[i]); i++) { if (Types.isLeftSquareCorner(elem)) { this.steps_ += this.highlightConstants_.START_POINT; } else if (Types.isLeftRoundedCorner(elem)) { @@ -103,11 +110,13 @@ export class Highlighter { // horizontal, use its width and position for an absolute horizontal // move. this.steps_ += svgPaths.lineOnAxis( - 'H', elem.xPos + elem.width - this.highlightOffset_); + 'H', + elem.xPos + elem.width - this.highlightOffset + ); } } - const right = row.xPos + row.width - this.highlightOffset_; + const right = row.xPos + row.width - this.highlightOffset; this.steps_ += svgPaths.lineOnAxis('H', right); } @@ -119,9 +128,9 @@ export class Highlighter { drawJaggedEdge_(row: Row) { if (this.info_.RTL) { const remainder = - row.height - this.jaggedTeethPaths_.height - this.highlightOffset_; + row.height - this.jaggedTeethPaths_.height - this.highlightOffset; this.steps_ += - this.jaggedTeethPaths_.pathLeft + svgPaths.lineOnAxis('v', remainder); + this.jaggedTeethPaths_.pathLeft + svgPaths.lineOnAxis('v', remainder); } } @@ -136,13 +145,16 @@ export class Highlighter { const belowTabHeight = row.height - input.connectionHeight; this.steps_ += - svgPaths.moveTo( - input.xPos + input.width - this.highlightOffset_, row.yPos) + - this.puzzleTabPaths_.pathDown(this.RTL_) + - svgPaths.lineOnAxis('v', belowTabHeight); + svgPaths.moveTo( + input.xPos + input.width - this.highlightOffset, + row.yPos + ) + + this.puzzleTabPaths_.pathDown(this.RTL_) + + svgPaths.lineOnAxis('v', belowTabHeight); } else { - this.steps_ += svgPaths.moveTo(input.xPos + input.width, row.yPos) + - this.puzzleTabPaths_.pathDown(this.RTL_); + this.steps_ += + svgPaths.moveTo(input.xPos + input.width, row.yPos) + + this.puzzleTabPaths_.pathDown(this.RTL_); } } @@ -156,17 +168,23 @@ export class Highlighter { if (!input) return; if (this.RTL_) { const innerHeight = row.height - 2 * this.insideCornerPaths_.height; - this.steps_ += svgPaths.moveTo(input.xPos, row.yPos) + - this.insideCornerPaths_.pathTop(this.RTL_) + - svgPaths.lineOnAxis('v', innerHeight) + - this.insideCornerPaths_.pathBottom(this.RTL_) + - svgPaths.lineTo( - row.width - input.xPos - this.insideCornerPaths_.width, 0); + this.steps_ += + svgPaths.moveTo(input.xPos, row.yPos) + + this.insideCornerPaths_.pathTop(this.RTL_) + + svgPaths.lineOnAxis('v', innerHeight) + + this.insideCornerPaths_.pathBottom(this.RTL_) + + svgPaths.lineTo( + row.width - input.xPos - this.insideCornerPaths_.width, + 0 + ); } else { - this.steps_ += svgPaths.moveTo(input.xPos, row.yPos + row.height) + - this.insideCornerPaths_.pathBottom(this.RTL_) + - svgPaths.lineTo( - row.width - input.xPos - this.insideCornerPaths_.width, 0); + this.steps_ += + svgPaths.moveTo(input.xPos, row.yPos + row.height) + + this.insideCornerPaths_.pathBottom(this.RTL_) + + svgPaths.lineTo( + row.width - input.xPos - this.insideCornerPaths_.width, + 0 + ); } } @@ -176,15 +194,17 @@ export class Highlighter { * @param row The row to highlight. */ drawRightSideRow(row: Row) { - const rightEdge = row.xPos + row.width - this.highlightOffset_; + const rightEdge = row.xPos + row.width - this.highlightOffset; if (row instanceof SpacerRow && row.followsStatement) { this.steps_ += svgPaths.lineOnAxis('H', rightEdge); } if (this.RTL_) { this.steps_ += svgPaths.lineOnAxis('H', rightEdge); - if (row.height > this.highlightOffset_) { + if (row.height > this.highlightOffset) { this.steps_ += svgPaths.lineOnAxis( - 'V', row.yPos + row.height - this.highlightOffset_); + 'V', + row.yPos + row.height - this.highlightOffset + ); } } } @@ -198,14 +218,17 @@ export class Highlighter { // Highlight the vertical edge of the bottom row on the input side. // Highlighting is always from the top left, both in LTR and RTL. if (this.RTL_) { - this.steps_ += - svgPaths.lineOnAxis('V', row.baseline - this.highlightOffset_); + this.steps_ += svgPaths.lineOnAxis( + 'V', + row.baseline - this.highlightOffset + ); } else { const cornerElem = this.info_.bottomRow.elements[0]; if (Types.isLeftSquareCorner(cornerElem)) { this.steps_ += svgPaths.moveTo( - row.xPos + this.highlightOffset_, - row.baseline - this.highlightOffset_); + row.xPos + this.highlightOffset, + row.baseline - this.highlightOffset + ); } else if (Types.isLeftRoundedCorner(cornerElem)) { this.steps_ += svgPaths.moveTo(row.xPos, row.baseline); this.steps_ += this.outsideCornerPaths_.bottomLeft(); @@ -220,13 +243,13 @@ export class Highlighter { const outputConnection = this.info_.outputConnection; if (outputConnection) { const tabBottom = - outputConnection.connectionOffsetY + outputConnection.height; + outputConnection.connectionOffsetY + outputConnection.height; // Draw a line up to the bottom of the tab. if (this.RTL_) { this.steps_ += svgPaths.moveTo(this.info_.startX, tabBottom); } else { - const left = this.info_.startX + this.highlightOffset_; - const bottom = this.info_.bottomRow.baseline - this.highlightOffset_; + const left = this.info_.startX + this.highlightOffset; + const bottom = this.info_.bottomRow.baseline - this.highlightOffset; this.steps_ += svgPaths.moveTo(left, bottom); this.steps_ += svgPaths.lineOnAxis('V', tabBottom); } @@ -236,11 +259,15 @@ export class Highlighter { if (!this.RTL_) { const topRow = this.info_.topRow; if (Types.isLeftRoundedCorner(topRow.elements[0])) { - this.steps_ += - svgPaths.lineOnAxis('V', this.outsideCornerPaths_.height); + this.steps_ += svgPaths.lineOnAxis( + 'V', + this.outsideCornerPaths_.height + ); } else { - this.steps_ += - svgPaths.lineOnAxis('V', topRow.capline + this.highlightOffset_); + this.steps_ += svgPaths.lineOnAxis( + 'V', + topRow.capline + this.highlightOffset + ); } } } @@ -251,7 +278,7 @@ export class Highlighter { * @param input The input to highlight. */ drawInlineInput(input: InlineInput) { - const offset = this.highlightOffset_; + const offset = this.highlightOffset; // Relative to the block's left. const connectionRight = input.xPos + input.connectionWidth; @@ -261,30 +288,27 @@ export class Highlighter { if (this.RTL_) { const aboveTabHeight = input.connectionOffsetY - offset; - const belowTabHeight = input.height - - (input.connectionOffsetY + input.connectionHeight) + offset; + const belowTabHeight = + input.height - + (input.connectionOffsetY + input.connectionHeight) + + offset; const startX = connectionRight - offset; this.inlineSteps_ += - svgPaths.moveTo(startX, startY) + // Right edge above tab. - svgPaths.lineOnAxis('v', aboveTabHeight) + // Back of tab. - this.puzzleTabPaths_.pathDown(this.RTL_) + // Right edge below tab. - svgPaths.lineOnAxis('v', belowTabHeight) + // Bottom. - svgPaths.lineOnAxis('h', bottomHighlightWidth); + svgPaths.moveTo(startX, startY) + // Right edge above tab. + svgPaths.lineOnAxis('v', aboveTabHeight) + // Back of tab. + this.puzzleTabPaths_.pathDown(this.RTL_) + // Right edge below tab. + svgPaths.lineOnAxis('v', belowTabHeight) + // Bottom. + svgPaths.lineOnAxis('h', bottomHighlightWidth); } else { - this.inlineSteps_ += // Go to top right corner. - svgPaths.moveTo( - input.xPos + input.width + offset, - startY) + // Highlight right edge, bottom. - svgPaths.lineOnAxis('v', input.height) + - svgPaths.lineOnAxis( - 'h', -bottomHighlightWidth) + // Go to top of tab. - svgPaths.moveTo( - connectionRight, - yPos + input.connectionOffsetY) + // Short highlight glint at - // bottom of tab. - this.puzzleTabPaths_.pathDown(this.RTL_); + this.inlineSteps_ += // Go to top right corner. + svgPaths.moveTo(input.xPos + input.width + offset, startY) + // Highlight right edge, bottom. + svgPaths.lineOnAxis('v', input.height) + + svgPaths.lineOnAxis('h', -bottomHighlightWidth) + // Go to top of tab. + svgPaths.moveTo(connectionRight, yPos + input.connectionOffsetY) + // Short highlight glint at + // bottom of tab. + this.puzzleTabPaths_.pathDown(this.RTL_); } } } diff --git a/core/renderers/geras/info.ts b/core/renderers/geras/info.ts index 78262cdb9..6dc5e6675 100644 --- a/core/renderers/geras/info.ts +++ b/core/renderers/geras/info.ts @@ -8,25 +8,26 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.geras.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; -import type {Input} from '../../input.js'; -import {inputTypes} from '../../input_types.js'; +import type {Input} from '../../inputs/input.js'; import {RenderInfo as BaseRenderInfo} from '../common/info.js'; import type {Measurable} from '../measurables/base.js'; import type {BottomRow} from '../measurables/bottom_row.js'; +import {DummyInput} from '../../inputs/dummy_input.js'; import {ExternalValueInput} from '../measurables/external_value_input.js'; import type {Field} from '../measurables/field.js'; import {InRowSpacer} from '../measurables/in_row_spacer.js'; import type {InputRow} from '../measurables/input_row.js'; import type {Row} from '../measurables/row.js'; +import {StatementInput} from '../../inputs/statement_input.js'; import type {TopRow} from '../measurables/top_row.js'; import {Types} from '../measurables/types.js'; +import {ValueInput} from '../../inputs/value_input.js'; import type {ConstantProvider} from './constants.js'; import {InlineInput} from './measurables/inline_input.js'; -import {StatementInput} from './measurables/statement_input.js'; +import {StatementInput as StatementInputMeasurable} from './measurables/statement_input.js'; import type {Renderer} from './renderer.js'; - /** * An object containing all sizing information needed to draw this block, * customized for the geras renderer. @@ -62,35 +63,40 @@ export class RenderInfo extends BaseRenderInfo { override populateBottomRow_() { super.populateBottomRow_(); - const followsStatement = this.block_.inputList.length && - this.block_.inputList[this.block_.inputList.length - 1].type === - inputTypes.STATEMENT; + const followsStatement = + this.block_.inputList.length && + this.block_.inputList[this.block_.inputList.length - 1] instanceof + StatementInput; // The minimum height of the bottom row is smaller in Geras than in other // renderers, because the dark path adds a pixel. // If one of the row's elements has a greater height this will be // overwritten in the compute pass. if (!followsStatement) { this.bottomRow.minHeight = - this.constants_.MEDIUM_PADDING - this.constants_.DARK_PATH_OFFSET; + this.constants_.MEDIUM_PADDING - this.constants_.DARK_PATH_OFFSET; } } override addInput_(input: Input, activeRow: Row) { // Non-dummy inputs have visual representations onscreen. - if (this.isInline && input.type === inputTypes.VALUE) { + if (this.isInline && input instanceof ValueInput) { activeRow.elements.push(new InlineInput(this.constants_, input)); activeRow.hasInlineInput = true; - } else if (input.type === inputTypes.STATEMENT) { - activeRow.elements.push(new StatementInput(this.constants_, input)); + } else if (input instanceof StatementInput) { + activeRow.elements.push( + new StatementInputMeasurable(this.constants_, input) + ); activeRow.hasStatement = true; - } else if (input.type === inputTypes.VALUE) { + } else if (input instanceof ValueInput) { activeRow.elements.push(new ExternalValueInput(this.constants_, input)); activeRow.hasExternalInput = true; - } else if (input.type === inputTypes.DUMMY) { + } else if (input instanceof DummyInput) { // Dummy inputs have no visual representation, but the information is // still important. - activeRow.minHeight = - Math.max(activeRow.minHeight, this.constants_.DUMMY_INPUT_MIN_HEIGHT); + activeRow.minHeight = Math.max( + activeRow.minHeight, + this.constants_.DUMMY_INPUT_MIN_HEIGHT + ); activeRow.hasDummyInput = true; } // Ignore row alignment if inline. @@ -101,19 +107,23 @@ export class RenderInfo extends BaseRenderInfo { override addElemSpacing_() { let hasExternalInputs = false; - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { if (row.hasExternalInput) { hasExternalInputs = true; } } - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { const oldElems = row.elements; row.elements = []; // No spacing needed before the corner on the top row or the bottom row. if (row.startsWithElemSpacer()) { // There's a spacer before the first element in the row. - row.elements.push(new InRowSpacer( - this.constants_, this.getInRowSpacing_(null, oldElems[0]))); + row.elements.push( + new InRowSpacer( + this.constants_, + this.getInRowSpacing_(null, oldElems[0]) + ) + ); } if (!oldElems.length) { continue; @@ -125,8 +135,10 @@ export class RenderInfo extends BaseRenderInfo { } row.elements.push(oldElems[oldElems.length - 1]); if (row.endsWithElemSpacer()) { - let spacing = - this.getInRowSpacing_(oldElems[oldElems.length - 1], null); + let spacing = this.getInRowSpacing_( + oldElems[oldElems.length - 1], + null + ); if (hasExternalInputs && row.hasDummyInput) { spacing += this.constants_.TAB_WIDTH; } @@ -136,7 +148,7 @@ export class RenderInfo extends BaseRenderInfo { } } - override getInRowSpacing_(prev: Measurable|null, next: Measurable|null) { + override getInRowSpacing_(prev: Measurable | null, next: Measurable | null) { if (!prev) { // Between an editable field and the beginning of the row. if (next && Types.isField(next) && (next as Field).isEditable) { @@ -243,7 +255,7 @@ export class RenderInfo extends BaseRenderInfo { // RTL) to make the dark path under the previous connection show // through. const offset = - (this.RTL ? 1 : -1) * this.constants_.DARK_PATH_OFFSET / 2; + ((this.RTL ? 1 : -1) * this.constants_.DARK_PATH_OFFSET) / 2; return next.notchOffset + offset; } } @@ -257,14 +269,18 @@ export class RenderInfo extends BaseRenderInfo { // RTL) to make the dark path under the previous connection show // through. const offset = - (this.RTL ? 1 : -1) * this.constants_.DARK_PATH_OFFSET / 2; + ((this.RTL ? 1 : -1) * this.constants_.DARK_PATH_OFFSET) / 2; return next.notchOffset - this.constants_.CORNER_RADIUS + offset; } } // Spacing between two fields of the same editability. - if (Types.isField(prev) && next && Types.isField(next) && - (prev as Field).isEditable === (next as Field).isEditable) { + if ( + Types.isField(prev) && + next && + Types.isField(next) && + (prev as Field).isEditable === (next as Field).isEditable + ) { return this.constants_.LARGE_PADDING; } @@ -310,7 +326,7 @@ export class RenderInfo extends BaseRenderInfo { if (Types.isBottomRow(row)) { const bottomRow = row as BottomRow; const baseline = - bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight; + bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight; if (Types.isNextConnection(elem)) { return baseline + elem.height / 2; } @@ -327,9 +343,10 @@ export class RenderInfo extends BaseRenderInfo { let result = row.yPos; if (Types.isField(elem) || Types.isIcon(elem)) { result += elem.height / 2; - if ((row.hasInlineInput || row.hasStatement) && - elem.height + this.constants_.TALL_INPUT_FIELD_OFFSET_Y <= - row.height) { + if ( + (row.hasInlineInput || row.hasStatement) && + elem.height + this.constants_.TALL_INPUT_FIELD_OFFSET_Y <= row.height + ) { result += this.constants_.TALL_INPUT_FIELD_OFFSET_Y; } } else if (Types.isInlineInput(elem)) { @@ -350,14 +367,17 @@ export class RenderInfo extends BaseRenderInfo { let nextRightEdge = 0; const rowNextRightEdges = new WeakMap(); let prevInput = null; - for (let i = this.rows.length - 1, row; row = this.rows[i]; i--) { + for (let i = this.rows.length - 1, row; (row = this.rows[i]); i--) { rowNextRightEdges.set(row, nextRightEdge); if (Types.isInputRow(row)) { if (row.hasStatement) { this.alignStatementRow_(row as InputRow); } - if (prevInput && prevInput.hasStatement && - row.width < prevInput.width) { + if ( + prevInput && + prevInput.hasStatement && + row.width < prevInput.width + ) { rowNextRightEdges.set(row, prevInput.width); } else { nextRightEdge = row.width; @@ -368,7 +388,7 @@ export class RenderInfo extends BaseRenderInfo { // Walk down each row from the top, comparing the prev and next right input // edges and setting the desired width to the max of the two. let prevRightEdge = 0; - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { if (row.hasStatement) { prevRightEdge = this.getDesiredRowWidth_(row); } else if (Types.isSpacer(row)) { @@ -376,8 +396,10 @@ export class RenderInfo extends BaseRenderInfo { row.width = Math.max(prevRightEdge, rowNextRightEdges.get(row)); } else { const currentWidth = row.width; - const desiredWidth = - Math.max(prevRightEdge, rowNextRightEdges.get(row)); + const desiredWidth = Math.max( + prevRightEdge, + rowNextRightEdges.get(row) + ); const missingSpace = desiredWidth - currentWidth; if (missingSpace > 0) { this.addAlignmentPadding_(row, missingSpace); @@ -390,8 +412,9 @@ export class RenderInfo extends BaseRenderInfo { override getDesiredRowWidth_(row: Row) { // Limit the width of a statement row when a block is inline. if (this.isInline && row.hasStatement) { - return this.statementEdge + this.constants_.MAX_BOTTOM_WIDTH + - this.startX; + return ( + this.statementEdge + this.constants_.MAX_BOTTOM_WIDTH + this.startX + ); } return super.getDesiredRowWidth_(row); } @@ -402,17 +425,21 @@ export class RenderInfo extends BaseRenderInfo { // accesses and sets properties that already exist on the objects. let widestRowWithConnectedBlocks = 0; let yCursor = 0; - for (let i = 0, row; row = this.rows[i]; i++) { + for (let i = 0, row; (row = this.rows[i]); i++) { row.yPos = yCursor; row.xPos = this.startX; yCursor += row.height; - widestRowWithConnectedBlocks = - Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks); + widestRowWithConnectedBlocks = Math.max( + widestRowWithConnectedBlocks, + row.widthWithConnectedBlocks + ); // Add padding to the bottom row if block height is less than minimum const heightWithoutHat = yCursor - this.topRow.ascenderHeight; - if (row === this.bottomRow && - heightWithoutHat < this.constants_.MIN_BLOCK_HEIGHT) { + if ( + row === this.bottomRow && + heightWithoutHat < this.constants_.MIN_BLOCK_HEIGHT + ) { // But the hat height shouldn't be part of this. const diff = this.constants_.MIN_BLOCK_HEIGHT - heightWithoutHat; this.bottomRow.height += diff; @@ -420,20 +447,26 @@ export class RenderInfo extends BaseRenderInfo { } this.recordElemPositions_(row); } - if (this.outputConnection && this.block_.nextConnection && - this.block_.nextConnection.isConnected()) { + if ( + this.outputConnection && + this.block_.nextConnection && + this.block_.nextConnection.isConnected() + ) { const target = this.block_.nextConnection.targetBlock(); if (target) { // Include width of connected block in value to stack width measurement. widestRowWithConnectedBlocks = Math.max( - widestRowWithConnectedBlocks, - target.getHeightWidth().width - this.constants_.DARK_PATH_OFFSET); + widestRowWithConnectedBlocks, + target.getHeightWidth().width - this.constants_.DARK_PATH_OFFSET + ); } } this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight; - this.widthWithChildren = widestRowWithConnectedBlocks + this.startX + - this.constants_.DARK_PATH_OFFSET; + this.widthWithChildren = + widestRowWithConnectedBlocks + + this.startX + + this.constants_.DARK_PATH_OFFSET; this.width += this.constants_.DARK_PATH_OFFSET; this.height = yCursor + this.constants_.DARK_PATH_OFFSET; this.startY = this.topRow.capline; diff --git a/core/renderers/geras/measurables/inline_input.ts b/core/renderers/geras/measurables/inline_input.ts index b361528f0..7d764262b 100644 --- a/core/renderers/geras/measurables/inline_input.ts +++ b/core/renderers/geras/measurables/inline_input.ts @@ -8,12 +8,11 @@ import * as goog from '../../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.geras.InlineInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../../input.js'; +import type {Input} from '../../../inputs/input.js'; import type {ConstantProvider as BaseConstantProvider} from '../../../renderers/common/constants.js'; import {InlineInput as BaseInlineInput} from '../../../renderers/measurables/inline_input.js'; import type {ConstantProvider as GerasConstantProvider} from '../constants.js'; - /** * An object containing information about the space an inline input takes up * during rendering. diff --git a/core/renderers/geras/measurables/statement_input.ts b/core/renderers/geras/measurables/statement_input.ts index 3720fce01..def96ee91 100644 --- a/core/renderers/geras/measurables/statement_input.ts +++ b/core/renderers/geras/measurables/statement_input.ts @@ -8,12 +8,11 @@ import * as goog from '../../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.geras.StatementInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../../input.js'; +import type {Input} from '../../../inputs/input.js'; import type {ConstantProvider as BaseConstantProvider} from '../../../renderers/common/constants.js'; import {StatementInput as BaseStatementInput} from '../../../renderers/measurables/statement_input.js'; import type {ConstantProvider as GerasConstantProvider} from '../constants.js'; - /** * An object containing information about the space a statement input takes up * during rendering. diff --git a/core/renderers/geras/path_object.ts b/core/renderers/geras/path_object.ts index dcdcb6673..71a8dde16 100644 --- a/core/renderers/geras/path_object.ts +++ b/core/renderers/geras/path_object.ts @@ -16,7 +16,6 @@ import {PathObject as BasePathObject} from '../common/path_object.js'; import type {ConstantProvider} from './constants.js'; - /** * An object that handles creating and setting each of the SVG elements * used by the renderer. @@ -36,13 +35,17 @@ export class PathObject extends BasePathObject { * @param constants The renderer's constants. */ constructor( - root: SVGElement, style: BlockStyle, - public override constants: ConstantProvider) { + root: SVGElement, + style: BlockStyle, + public override constants: ConstantProvider + ) { super(root, style, constants); /** The dark path of the block. */ - this.svgPathDark = dom.createSvgElement( - Svg.PATH, {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'}); + this.svgPathDark = dom.createSvgElement(Svg.PATH, { + 'class': 'blocklyPathDark', + 'transform': 'translate(1,1)', + }); // SVG draw order is based on the order of elements (top most = back most) // So we need to insert the dark path before the base path to make sure it // gets drawn first. @@ -50,7 +53,10 @@ export class PathObject extends BasePathObject { /** The light path of the block. */ this.svgPathLight = dom.createSvgElement( - Svg.PATH, {'class': 'blocklyPathLight'}, this.svgRoot); + Svg.PATH, + {'class': 'blocklyPathLight'}, + this.svgRoot + ); } override setPath(mainPath: string) { @@ -79,8 +85,9 @@ export class PathObject extends BasePathObject { this.svgPathDark.style.display = ''; if (!this.style.colourTertiary) { throw new Error( - 'The renderer did not properly initialize the tertiary colour of ' + - 'the block style'); + 'The renderer did not properly initialize the tertiary colour of ' + + 'the block style' + ); } this.svgPathLight.setAttribute('stroke', this.style.colourTertiary); this.svgPathDark.setAttribute('fill', this.colourDark); @@ -93,13 +100,15 @@ export class PathObject extends BasePathObject { override setStyle(blockStyle: BlockStyle) { this.style = blockStyle; this.colourDark = - colour.blend('#000', this.style.colourPrimary, 0.2) || this.colourDark; + colour.blend('#000', this.style.colourPrimary, 0.2) || this.colourDark; } override updateHighlighted(highlighted: boolean) { if (highlighted) { this.svgPath.setAttribute( - 'filter', 'url(#' + this.constants.embossFilterId + ')'); + 'filter', + 'url(#' + this.constants.embossFilterId + ')' + ); this.svgPathLight.style.display = 'none'; } else { this.svgPath.setAttribute('filter', 'none'); @@ -112,8 +121,9 @@ export class PathObject extends BasePathObject { this.svgPathLight.style.display = 'none'; if (!this.style.colourSecondary) { throw new Error( - 'The renderer did not properly initialize the secondary colour ' + - 'of the block style block style'); + 'The renderer did not properly initialize the secondary colour ' + + 'of the block style block style' + ); } this.svgPathDark.setAttribute('fill', this.style.colourSecondary); this.svgPath.setAttribute('stroke', 'none'); diff --git a/core/renderers/geras/renderer.ts b/core/renderers/geras/renderer.ts index 54b36cfc1..ddee103b7 100644 --- a/core/renderers/geras/renderer.ts +++ b/core/renderers/geras/renderer.ts @@ -19,7 +19,6 @@ import {HighlightConstantProvider} from './highlight_constants.js'; import {RenderInfo} from './info.js'; import {PathObject} from './path_object.js'; - /** * The geras renderer. This renderer was designed to be backwards compatible * with pre-2019 Blockly. Newer projects that are not constrained by backwards @@ -30,7 +29,7 @@ import {PathObject} from './path_object.js'; */ export class Renderer extends BaseRenderer { /** The renderer's highlight constant provider. */ - private highlightConstants_: HighlightConstantProvider|null = null; + private highlightConstants: HighlightConstantProvider | null = null; /** * @param name The renderer name. @@ -44,10 +43,12 @@ export class Renderer extends BaseRenderer { * the normal constant provider. */ override init( - theme: Theme, opt_rendererOverrides?: {[rendererConstant: string]: any}) { + theme: Theme, + opt_rendererOverrides?: {[rendererConstant: string]: any} + ) { super.init(theme, opt_rendererOverrides); - this.highlightConstants_ = this.makeHighlightConstants_(); - this.highlightConstants_.init(); + this.highlightConstants = this.makeHighlightConstants_(); + this.highlightConstants.init(); } override refreshDom(svg: SVGElement, theme: Theme) { @@ -77,9 +78,11 @@ export class Renderer extends BaseRenderer { * block. * @returns The drawer. */ - protected override makeDrawer_(block: BlockSvg, info: BaseRenderInfo): - Drawer { - return new Drawer(block, (info as RenderInfo)); + protected override makeDrawer_( + block: BlockSvg, + info: BaseRenderInfo + ): Drawer { + return new Drawer(block, info as RenderInfo); } /** @@ -90,8 +93,7 @@ export class Renderer extends BaseRenderer { * @returns The renderer path object. */ override makePathObject(root: SVGElement, style: BlockStyle): PathObject { - return new PathObject( - root, style, (this.getConstants() as ConstantProvider)); + return new PathObject(root, style, this.getConstants() as ConstantProvider); } /** @@ -100,7 +102,7 @@ export class Renderer extends BaseRenderer { * @returns The highlight constant provider. */ protected makeHighlightConstants_(): HighlightConstantProvider { - return new HighlightConstantProvider((this.getConstants())); + return new HighlightConstantProvider(this.getConstants()); } /** @@ -110,12 +112,13 @@ export class Renderer extends BaseRenderer { * @returns The highlight constant provider. */ getHighlightConstants(): HighlightConstantProvider { - if (!this.highlightConstants_) { + if (!this.highlightConstants) { throw new Error( - 'Cannot access the highlight constants because init has not ' + - 'been called'); + 'Cannot access the highlight constants because init has not ' + + 'been called' + ); } - return this.highlightConstants_; + return this.highlightConstants; } } diff --git a/core/renderers/measurables/base.ts b/core/renderers/measurables/base.ts index 98a9510c8..3439a746c 100644 --- a/core/renderers/measurables/base.ts +++ b/core/renderers/measurables/base.ts @@ -11,7 +11,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Types} from './types.js'; - /** * The base class to represent a part of a block that takes up space during * rendering. The constructor for each non-spacer Measurable records the size diff --git a/core/renderers/measurables/bottom_row.ts b/core/renderers/measurables/bottom_row.ts index 36418afad..7760c909d 100644 --- a/core/renderers/measurables/bottom_row.ts +++ b/core/renderers/measurables/bottom_row.ts @@ -14,7 +14,6 @@ import type {NextConnection} from './next_connection.js'; import {Row} from './row.js'; import {Types} from './types.js'; - /** * An object containing information about what elements are in the bottom row of * a block as well as spacing information for the bottom row. @@ -30,7 +29,7 @@ export class BottomRow extends Row { /** * The next connection on the row, if any. */ - connection: NextConnection|null = null; + connection: NextConnection | null = null; /** * The amount that the bottom of the block extends below the horizontal diff --git a/core/renderers/measurables/connection.ts b/core/renderers/measurables/connection.ts index d69374a90..788404663 100644 --- a/core/renderers/measurables/connection.ts +++ b/core/renderers/measurables/connection.ts @@ -14,7 +14,6 @@ import type {ConstantProvider, Shape} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * The base class to represent a connection and the space that it takes up on * the block. @@ -29,7 +28,9 @@ export class Connection extends Measurable { * represents. */ constructor( - constants: ConstantProvider, public connectionModel: RenderedConnection) { + constants: ConstantProvider, + public connectionModel: RenderedConnection + ) { super(constants); this.shape = this.constants_.shapeFor(connectionModel); diff --git a/core/renderers/measurables/external_value_input.ts b/core/renderers/measurables/external_value_input.ts index bcb0aea4d..4270625e5 100644 --- a/core/renderers/measurables/external_value_input.ts +++ b/core/renderers/measurables/external_value_input.ts @@ -8,13 +8,12 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.ExternalValueInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {InputConnection} from './input_connection.js'; import {Types} from './types.js'; - /** * An object containing information about the space an external value input * takes up during rendering @@ -36,12 +35,15 @@ export class ExternalValueInput extends InputConnection { if (!this.connectedBlock) { this.height = this.shape.height as number; } else { - this.height = this.connectedBlockHeight - - this.constants_.TAB_OFFSET_FROM_TOP - this.constants_.MEDIUM_PADDING; + this.height = + this.connectedBlockHeight - + this.constants_.TAB_OFFSET_FROM_TOP - + this.constants_.MEDIUM_PADDING; } - this.width = this.shape.width as - number + this.constants_.EXTERNAL_VALUE_INPUT_PADDING; + this.width = + (this.shape.width as number) + + this.constants_.EXTERNAL_VALUE_INPUT_PADDING; this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP; diff --git a/core/renderers/measurables/field.ts b/core/renderers/measurables/field.ts index 46c043d69..9cfe35035 100644 --- a/core/renderers/measurables/field.ts +++ b/core/renderers/measurables/field.ts @@ -9,13 +9,12 @@ goog.declareModuleId('Blockly.blockRendering.Field'); /* eslint-disable-next-line no-unused-vars */ import type {Field as BlocklyField} from '../../field.js'; -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * An object containing information about the space a field takes up during * rendering @@ -32,8 +31,10 @@ export class Field extends Measurable { * @param parentInput The parent input for the field. */ constructor( - constants: ConstantProvider, public field: BlocklyField, - public parentInput: Input) { + constants: ConstantProvider, + public field: BlocklyField, + public parentInput: Input + ) { super(constants); this.isEditable = field.EDITABLE; diff --git a/core/renderers/measurables/hat.ts b/core/renderers/measurables/hat.ts index c35f8947b..217175dc5 100644 --- a/core/renderers/measurables/hat.ts +++ b/core/renderers/measurables/hat.ts @@ -12,7 +12,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * An object containing information about the space a hat takes up during * rendering. diff --git a/core/renderers/measurables/icon.ts b/core/renderers/measurables/icon.ts index afba526ed..6efb16b51 100644 --- a/core/renderers/measurables/icon.ts +++ b/core/renderers/measurables/icon.ts @@ -8,18 +8,22 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.Icon'); /* eslint-disable-next-line no-unused-vars */ -import type {Icon as BlocklyIcon} from '../../icon.js'; +import type {IIcon as BlocklyIcon} from '../../interfaces/i_icon.js'; import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - +import {hasBubble} from '../../interfaces/i_has_bubble.js'; /** * An object containing information about the space an icon takes up during * rendering. */ export class Icon extends Measurable { + /** + * @deprecated Will be removed in v11. Create a subclass of the Icon + * measurable if this data is necessary for you. + */ isVisible: boolean; flipRtl = false; @@ -33,10 +37,10 @@ export class Icon extends Measurable { constructor(constants: ConstantProvider, public icon: BlocklyIcon) { super(constants); - this.isVisible = icon.isVisible(); + this.isVisible = hasBubble(icon) && icon.bubbleIsVisible(); this.type |= Types.ICON; - const size = icon.getCorrectedSize(); + const size = icon.getSize(); this.height = size.height; this.width = size.width; } diff --git a/core/renderers/measurables/in_row_spacer.ts b/core/renderers/measurables/in_row_spacer.ts index 589b14608..0f99976d9 100644 --- a/core/renderers/measurables/in_row_spacer.ts +++ b/core/renderers/measurables/in_row_spacer.ts @@ -12,7 +12,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * An object containing information about a spacer between two elements on a * row. diff --git a/core/renderers/measurables/inline_input.ts b/core/renderers/measurables/inline_input.ts index 5b04370fc..8b3371493 100644 --- a/core/renderers/measurables/inline_input.ts +++ b/core/renderers/measurables/inline_input.ts @@ -8,13 +8,12 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.InlineInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {InputConnection} from './input_connection.js'; import {Types} from './types.js'; - /** * An object containing information about the space an inline input takes up * during rendering. @@ -41,23 +40,25 @@ export class InlineInput extends InputConnection { this.height = this.connectedBlockHeight; } - this.connectionHeight = !this.isDynamicShape ? - this.shape.height as number : - (this.shape.height as (p1: number) => number)(this.height); + this.connectionHeight = !this.isDynamicShape + ? (this.shape.height as number) + : (this.shape.height as (p1: number) => number)(this.height); - this.connectionWidth = !this.isDynamicShape ? - this.shape.width as number : - (this.shape.width as (p1: number) => number)(this.height); + this.connectionWidth = !this.isDynamicShape + ? (this.shape.width as number) + : (this.shape.width as (p1: number) => number)(this.height); if (!this.connectedBlock) { this.width += this.connectionWidth * (this.isDynamicShape ? 2 : 1); } - this.connectionOffsetY = 'connectionOffsetY' in this.shape ? - this.shape.connectionOffsetY(this.connectionHeight) : - this.constants_.TAB_OFFSET_FROM_TOP; + this.connectionOffsetY = + 'connectionOffsetY' in this.shape + ? this.shape.connectionOffsetY(this.connectionHeight) + : this.constants_.TAB_OFFSET_FROM_TOP; - this.connectionOffsetX = 'connectionOffsetX' in this.shape ? - this.shape.connectionOffsetX(this.connectionWidth) : - 0; + this.connectionOffsetX = + 'connectionOffsetX' in this.shape + ? this.shape.connectionOffsetX(this.connectionWidth) + : 0; } } diff --git a/core/renderers/measurables/input_connection.ts b/core/renderers/measurables/input_connection.ts index 64d0769be..bcd5d245f 100644 --- a/core/renderers/measurables/input_connection.ts +++ b/core/renderers/measurables/input_connection.ts @@ -8,21 +8,20 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.InputConnection'); import type {BlockSvg} from '../../block_svg.js'; -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {RenderedConnection} from '../../rendered_connection.js'; import type {ConstantProvider} from '../common/constants.js'; import {Connection} from './connection.js'; import {Types} from './types.js'; - /** * The base class to represent an input that takes up space on a block * during rendering. */ export class InputConnection extends Connection { align: number; - connectedBlock: BlockSvg|null; + connectedBlock: BlockSvg | null; connectedBlockWidth: number; connectedBlockHeight: number; connectionOffsetX = 0; @@ -40,9 +39,9 @@ export class InputConnection extends Connection { this.align = input.align; this.connectedBlock = - (input.connection && input.connection.targetBlock() ? - input.connection.targetBlock() as BlockSvg : - null); + input.connection && input.connection.targetBlock() + ? (input.connection.targetBlock() as BlockSvg) + : null; if (this.connectedBlock) { const bBox = this.connectedBlock.getHeightWidth(); diff --git a/core/renderers/measurables/input_row.ts b/core/renderers/measurables/input_row.ts index 65bf43480..2ae21a7eb 100644 --- a/core/renderers/measurables/input_row.ts +++ b/core/renderers/measurables/input_row.ts @@ -15,7 +15,6 @@ import {Row} from './row.js'; import {StatementInput} from './statement_input.js'; import {Types} from './types.js'; - /** * An object containing information about a row that holds one or more inputs. */ @@ -47,10 +46,12 @@ export class InputRow extends Row { if (Types.isStatementInput(elem) && elem instanceof StatementInput) { connectedBlockWidths += elem.connectedBlockWidth; } else if ( - Types.isExternalInput(elem) && elem instanceof ExternalValueInput && - elem.connectedBlockWidth !== 0) { + Types.isExternalInput(elem) && + elem instanceof ExternalValueInput && + elem.connectedBlockWidth !== 0 + ) { connectedBlockWidths += - elem.connectedBlockWidth - elem.connectionWidth; + elem.connectedBlockWidth - elem.connectionWidth; } } if (!Types.isSpacer(elem)) { diff --git a/core/renderers/measurables/jagged_edge.ts b/core/renderers/measurables/jagged_edge.ts index 72ef7fb60..ff48a1146 100644 --- a/core/renderers/measurables/jagged_edge.ts +++ b/core/renderers/measurables/jagged_edge.ts @@ -12,7 +12,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * An object containing information about the space the jagged edge of a * collapsed block takes up during rendering. diff --git a/core/renderers/measurables/next_connection.ts b/core/renderers/measurables/next_connection.ts index 997e4c0f9..f68ad2c7c 100644 --- a/core/renderers/measurables/next_connection.ts +++ b/core/renderers/measurables/next_connection.ts @@ -13,7 +13,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Connection} from './connection.js'; import {Types} from './types.js'; - /** * An object containing information about the space a next connection takes * up during rendering. @@ -25,7 +24,9 @@ export class NextConnection extends Connection { * represents. */ constructor( - constants: ConstantProvider, connectionModel: RenderedConnection) { + constants: ConstantProvider, + connectionModel: RenderedConnection + ) { super(constants, connectionModel); this.type |= Types.NEXT_CONNECTION; this.height = this.shape.height as number; diff --git a/core/renderers/measurables/output_connection.ts b/core/renderers/measurables/output_connection.ts index 7d493b002..69747d313 100644 --- a/core/renderers/measurables/output_connection.ts +++ b/core/renderers/measurables/output_connection.ts @@ -13,7 +13,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Connection} from './connection.js'; import {Types} from './types.js'; - /** * An object containing information about the space an output connection takes * up during rendering. @@ -29,12 +28,14 @@ export class OutputConnection extends Connection { * represents. */ constructor( - constants: ConstantProvider, connectionModel: RenderedConnection) { + constants: ConstantProvider, + connectionModel: RenderedConnection + ) { super(constants, connectionModel); this.type |= Types.OUTPUT_CONNECTION; - this.height = !this.isDynamicShape ? this.shape.height as number : 0; - this.width = !this.isDynamicShape ? this.shape.width as number : 0; + this.height = !this.isDynamicShape ? (this.shape.height as number) : 0; + this.width = !this.isDynamicShape ? (this.shape.width as number) : 0; this.startX = this.width; diff --git a/core/renderers/measurables/previous_connection.ts b/core/renderers/measurables/previous_connection.ts index b2b2444a5..d7f769d9f 100644 --- a/core/renderers/measurables/previous_connection.ts +++ b/core/renderers/measurables/previous_connection.ts @@ -13,7 +13,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Connection} from './connection.js'; import {Types} from './types.js'; - /** * An object containing information about the space a previous connection takes * up during rendering. @@ -25,7 +24,9 @@ export class PreviousConnection extends Connection { * represents. */ constructor( - constants: ConstantProvider, connectionModel: RenderedConnection) { + constants: ConstantProvider, + connectionModel: RenderedConnection + ) { super(constants, connectionModel); this.type |= Types.PREVIOUS_CONNECTION; this.height = this.shape.height as number; diff --git a/core/renderers/measurables/round_corner.ts b/core/renderers/measurables/round_corner.ts index ad7ebab02..586161e31 100644 --- a/core/renderers/measurables/round_corner.ts +++ b/core/renderers/measurables/round_corner.ts @@ -12,7 +12,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * An object containing information about the space a rounded corner takes up * during rendering. @@ -25,9 +24,9 @@ export class RoundCorner extends Measurable { constructor(constants: ConstantProvider, opt_position?: string) { super(constants); this.type = - (!opt_position || opt_position === 'left' ? Types.LEFT_ROUND_CORNER : - Types.RIGHT_ROUND_CORNER) | - Types.CORNER; + (!opt_position || opt_position === 'left' + ? Types.LEFT_ROUND_CORNER + : Types.RIGHT_ROUND_CORNER) | Types.CORNER; this.width = this.constants_.CORNER_RADIUS; // The rounded corner extends into the next row by 4 so we only take the // height that is aligned with this row. diff --git a/core/renderers/measurables/row.ts b/core/renderers/measurables/row.ts index 77e9472f0..c8116a2fc 100644 --- a/core/renderers/measurables/row.ts +++ b/core/renderers/measurables/row.ts @@ -14,7 +14,6 @@ import type {InRowSpacer} from './in_row_spacer.js'; import type {InputConnection} from './input_connection.js'; import {Types} from './types.js'; - /** * An object representing a single row on a rendered block and all of its * subcomponents. @@ -103,7 +102,7 @@ export class Row { /** * Alignment of the row. */ - align: number|null = null; + align: number | null = null; protected readonly constants_: ConstantProvider; @@ -125,7 +124,7 @@ export class Row { * * @returns The last input on the row, or null. */ - getLastInput(): InputConnection|null { + getLastInput(): InputConnection | null { // TODO: Consider moving this to InputRow, if possible. for (let i = this.elements.length - 1; i >= 0; i--) { const elem = this.elements[i]; @@ -166,7 +165,7 @@ export class Row { * * @returns The first spacer element on this row. */ - getFirstSpacer(): InRowSpacer|null { + getFirstSpacer(): InRowSpacer | null { for (let i = 0; i < this.elements.length; i++) { const elem = this.elements[i]; if (Types.isSpacer(elem)) { @@ -181,7 +180,7 @@ export class Row { * * @returns The last spacer element on this row. */ - getLastSpacer(): InRowSpacer|null { + getLastSpacer(): InRowSpacer | null { for (let i = this.elements.length - 1; i >= 0; i--) { const elem = this.elements[i]; if (Types.isSpacer(elem)) { diff --git a/core/renderers/measurables/spacer_row.ts b/core/renderers/measurables/spacer_row.ts index 47970ea00..b8a237d70 100644 --- a/core/renderers/measurables/spacer_row.ts +++ b/core/renderers/measurables/spacer_row.ts @@ -13,7 +13,6 @@ import {InRowSpacer} from './in_row_spacer.js'; import {Row} from './row.js'; import {Types} from './types.js'; - /** * An object containing information about a spacer between two rows. */ @@ -31,8 +30,10 @@ export class SpacerRow extends Row { * @param width The width of the spacer. */ constructor( - constants: ConstantProvider, public override height: number, - public override width: number) { + constants: ConstantProvider, + public override height: number, + public override width: number + ) { super(constants); this.type |= Types.SPACER | Types.BETWEEN_ROW_SPACER; diff --git a/core/renderers/measurables/square_corner.ts b/core/renderers/measurables/square_corner.ts index d30b36fa0..cdf88e6c2 100644 --- a/core/renderers/measurables/square_corner.ts +++ b/core/renderers/measurables/square_corner.ts @@ -12,7 +12,6 @@ import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; - /** * An object containing information about the space a square corner takes up * during rendering. @@ -25,9 +24,9 @@ export class SquareCorner extends Measurable { constructor(constants: ConstantProvider, opt_position?: string) { super(constants); this.type = - (!opt_position || opt_position === 'left' ? Types.LEFT_SQUARE_CORNER : - Types.RIGHT_SQUARE_CORNER) | - Types.CORNER; + (!opt_position || opt_position === 'left' + ? Types.LEFT_SQUARE_CORNER + : Types.RIGHT_SQUARE_CORNER) | Types.CORNER; this.height = this.constants_.NO_PADDING; this.width = this.constants_.NO_PADDING; } diff --git a/core/renderers/measurables/statement_input.ts b/core/renderers/measurables/statement_input.ts index ac5a0bfb2..70a358bce 100644 --- a/core/renderers/measurables/statement_input.ts +++ b/core/renderers/measurables/statement_input.ts @@ -8,13 +8,12 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.StatementInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {InputConnection} from './input_connection.js'; import {Types} from './types.js'; - /** * An object containing information about the space a statement input takes up * during rendering @@ -34,9 +33,10 @@ export class StatementInput extends InputConnection { // We allow the dark path to show on the parent block so that the child // block looks embossed. This takes up an extra pixel in both x and y. this.height = - this.connectedBlockHeight + this.constants_.STATEMENT_BOTTOM_SPACER; + this.connectedBlockHeight + this.constants_.STATEMENT_BOTTOM_SPACER; } - this.width = this.constants_.STATEMENT_INPUT_NOTCH_OFFSET + - (this.shape.width as number); + this.width = + this.constants_.STATEMENT_INPUT_NOTCH_OFFSET + + (this.shape.width as number); } } diff --git a/core/renderers/measurables/top_row.ts b/core/renderers/measurables/top_row.ts index d8ebf9b9f..ef71fc1b0 100644 --- a/core/renderers/measurables/top_row.ts +++ b/core/renderers/measurables/top_row.ts @@ -15,7 +15,6 @@ import type {PreviousConnection} from './previous_connection.js'; import {Row} from './row.js'; import {Types} from './types.js'; - /** * An object containing information about what elements are in the top row of a * block as well as sizing information for the top row. @@ -39,7 +38,7 @@ export class TopRow extends Row { hasPreviousConnection = false; /** The previous connection on the block, if any. */ - connection: PreviousConnection|null = null; + connection: PreviousConnection | null = null; /** * @param constants The rendering constants provider. @@ -58,12 +57,16 @@ export class TopRow extends Row { */ hasLeftSquareCorner(block: BlockSvg): boolean { const hasHat = - (block.hat ? block.hat === 'cap' : this.constants_.ADD_START_HATS) && - !block.outputConnection && !block.previousConnection; + (block.hat ? block.hat === 'cap' : this.constants_.ADD_START_HATS) && + !block.outputConnection && + !block.previousConnection; const prevBlock = block.getPreviousBlock(); - return !!block.outputConnection || hasHat || - (prevBlock ? prevBlock.getNextBlock() === block : false); + return ( + !!block.outputConnection || + hasHat || + (prevBlock ? prevBlock.getNextBlock() === block : false) + ); } /** diff --git a/core/renderers/measurables/types.ts b/core/renderers/measurables/types.ts index da3bcbeff..f84ed931b 100644 --- a/core/renderers/measurables/types.ts +++ b/core/renderers/measurables/types.ts @@ -10,38 +10,37 @@ goog.declareModuleId('Blockly.blockRendering.Types'); import type {Measurable} from './base.js'; import type {Row} from './row.js'; - /** * Types of rendering elements. */ class TypesContainer { - [index: string]: number|Function; + [index: string]: number | Function; - NONE = 0; // None - FIELD = 1 << 0; // Field. - HAT = 1 << 1; // Hat. - ICON = 1 << 2; // Icon. - SPACER = 1 << 3; // Spacer. - BETWEEN_ROW_SPACER = 1 << 4; // Between Row Spacer. - IN_ROW_SPACER = 1 << 5; // In Row Spacer. - EXTERNAL_VALUE_INPUT = 1 << 6; // External Value Input. - INPUT = 1 << 7; // Input. - INLINE_INPUT = 1 << 8; // Inline Input. - STATEMENT_INPUT = 1 << 9; // Statement Input. - CONNECTION = 1 << 10; // Connection. - PREVIOUS_CONNECTION = 1 << 11; // Previous Connection. - NEXT_CONNECTION = 1 << 12; // Next Connection. - OUTPUT_CONNECTION = 1 << 13; // Output Connection. - CORNER = 1 << 14; // Corner. - LEFT_SQUARE_CORNER = 1 << 15; // Square Corner. - LEFT_ROUND_CORNER = 1 << 16; // Round Corner. - RIGHT_SQUARE_CORNER = 1 << 17; // Right Square Corner. - RIGHT_ROUND_CORNER = 1 << 18; // Right Round Corner. - JAGGED_EDGE = 1 << 19; // Jagged Edge. - ROW = 1 << 20; // Row. - TOP_ROW = 1 << 21; // Top Row. - BOTTOM_ROW = 1 << 22; // Bottom Row. - INPUT_ROW = 1 << 23; // Input Row. + NONE = 0; // None + FIELD = 1 << 0; // Field. + HAT = 1 << 1; // Hat. + ICON = 1 << 2; // Icon. + SPACER = 1 << 3; // Spacer. + BETWEEN_ROW_SPACER = 1 << 4; // Between Row Spacer. + IN_ROW_SPACER = 1 << 5; // In Row Spacer. + EXTERNAL_VALUE_INPUT = 1 << 6; // External Value Input. + INPUT = 1 << 7; // Input. + INLINE_INPUT = 1 << 8; // Inline Input. + STATEMENT_INPUT = 1 << 9; // Statement Input. + CONNECTION = 1 << 10; // Connection. + PREVIOUS_CONNECTION = 1 << 11; // Previous Connection. + NEXT_CONNECTION = 1 << 12; // Next Connection. + OUTPUT_CONNECTION = 1 << 13; // Output Connection. + CORNER = 1 << 14; // Corner. + LEFT_SQUARE_CORNER = 1 << 15; // Square Corner. + LEFT_ROUND_CORNER = 1 << 16; // Round Corner. + RIGHT_SQUARE_CORNER = 1 << 17; // Right Square Corner. + RIGHT_ROUND_CORNER = 1 << 18; // Right Round Corner. + JAGGED_EDGE = 1 << 19; // Jagged Edge. + ROW = 1 << 20; // Row. + TOP_ROW = 1 << 21; // Top Row. + BOTTOM_ROW = 1 << 22; // Bottom Row. + INPUT_ROW = 1 << 23; // Input Row. /** * A Left Corner Union Type. @@ -111,7 +110,7 @@ class TypesContainer { * @param elem The element to check. * @returns 1 if the object stores information about a spacer. */ - isSpacer(elem: Measurable|Row): number { + isSpacer(elem: Measurable | Row): number { return elem.type & this.SPACER; } diff --git a/core/renderers/minimalist/constants.ts b/core/renderers/minimalist/constants.ts index 9ca86e839..2b40d645c 100644 --- a/core/renderers/minimalist/constants.ts +++ b/core/renderers/minimalist/constants.ts @@ -8,14 +8,27 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.minimalist.ConstantProvider'); import {ConstantProvider as BaseConstantProvider} from '../common/constants.js'; - +import * as deprecation from '../../utils/deprecation.js'; /** * An object that provides constants for rendering blocks in the minimalist * renderer. + * + * @deprecated Use Blockly.blockRendering.ConstantProvider instead. + * To be removed in v11. */ export class ConstantProvider extends BaseConstantProvider { + /** + * @deprecated Use Blockly.blockRendering.ConstantProvider instead. + * To be removed in v11. + */ constructor() { super(); + deprecation.warn( + 'Blockly.minimalist.ConstantProvider', + 'v10', + 'v11', + 'Blockly.blockRendering.ConstantProvider' + ); } } diff --git a/core/renderers/minimalist/drawer.ts b/core/renderers/minimalist/drawer.ts index a6325d351..1e9ab1076 100644 --- a/core/renderers/minimalist/drawer.ts +++ b/core/renderers/minimalist/drawer.ts @@ -9,20 +9,32 @@ goog.declareModuleId('Blockly.minimalist.Drawer'); import type {BlockSvg} from '../../block_svg.js'; import {Drawer as BaseDrawer} from '../common/drawer.js'; +import * as deprecation from '../../utils/deprecation.js'; import type {RenderInfo} from './info.js'; - /** * An object that draws a block based on the given rendering information. + * + * @deprecated Use Blockly.blockRendering.Drawer instead. + * To be removed in v11. */ export class Drawer extends BaseDrawer { /** * @param block The block to render. * @param info An object containing all information needed to render this * block. + * + * @deprecated Use Blockly.blockRendering.Drawer instead. + * To be removed in v11. */ constructor(block: BlockSvg, info: RenderInfo) { super(block, info); + deprecation.warn( + 'Blockly.minimalist.Drawer', + 'v10', + 'v11', + 'Blockly.blockRendering.Drawer' + ); } } diff --git a/core/renderers/minimalist/info.ts b/core/renderers/minimalist/info.ts index 887820dfe..350762fd6 100644 --- a/core/renderers/minimalist/info.ts +++ b/core/renderers/minimalist/info.ts @@ -9,16 +9,19 @@ goog.declareModuleId('Blockly.minimalist.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; import {RenderInfo as BaseRenderInfo} from '../common/info.js'; +import * as deprecation from '../../utils/deprecation.js'; import type {Renderer} from './renderer.js'; - /** * An object containing all sizing information needed to draw this block. * * This measure pass does not propagate changes to the block (although fields * may choose to rerender when getSize() is called). However, calling it * repeatedly may be expensive. + * + * @deprecated Use Blockly.blockRendering.RenderInfo instead. To be removed + * in v11. */ export class RenderInfo extends BaseRenderInfo { // Exclamation is fine b/c this is assigned by the super constructor. @@ -27,9 +30,17 @@ export class RenderInfo extends BaseRenderInfo { /** * @param renderer The renderer in use. * @param block The block to measure. + * @deprecated Use Blockly.blockRendering.RenderInfo instead. To be removed + * in v11. */ constructor(renderer: Renderer, block: BlockSvg) { super(renderer, block); + deprecation.warn( + 'Blockly.minimalist.RenderInfo', + 'v10', + 'v11', + 'Blockly.blockRendering.RenderInfo' + ); } /** diff --git a/core/renderers/minimalist/minimalist.ts b/core/renderers/minimalist/minimalist.ts index 807fe2a86..cd0fc9884 100644 --- a/core/renderers/minimalist/minimalist.ts +++ b/core/renderers/minimalist/minimalist.ts @@ -14,5 +14,4 @@ import {Drawer} from './drawer.js'; import {RenderInfo} from './info.js'; import {Renderer} from './renderer.js'; - export {ConstantProvider, Drawer, Renderer, RenderInfo}; diff --git a/core/renderers/minimalist/renderer.ts b/core/renderers/minimalist/renderer.ts index 86f1c78d4..93168a96e 100644 --- a/core/renderers/minimalist/renderer.ts +++ b/core/renderers/minimalist/renderer.ts @@ -11,21 +11,32 @@ import type {BlockSvg} from '../../block_svg.js'; import * as blockRendering from '../common/block_rendering.js'; import type {RenderInfo as BaseRenderInfo} from '../common/info.js'; import {Renderer as BaseRenderer} from '../common/renderer.js'; +import * as deprecation from '../../utils/deprecation.js'; import {ConstantProvider} from './constants.js'; import {Drawer} from './drawer.js'; import {RenderInfo} from './info.js'; - /** * The minimalist renderer. + * + * @deprecated Use Blockly.blockRendering.Renderer instead. To be removed + * in v11. */ export class Renderer extends BaseRenderer { /** * @param name The renderer name. + * @deprecated Use Blockly.blockRendering.Renderer instead. To be removed + * in v11. */ constructor(name: string) { super(name); + deprecation.warn( + 'Blockly.minimalist.Renderer', + 'v10', + 'v11', + 'Blockly.blockRendering.Renderer' + ); } /** @@ -55,9 +66,11 @@ export class Renderer extends BaseRenderer { * block. * @returns The drawer. */ - protected override makeDrawer_(block: BlockSvg, info: BaseRenderInfo): - Drawer { - return new Drawer(block, (info as RenderInfo)); + protected override makeDrawer_( + block: BlockSvg, + info: BaseRenderInfo + ): Drawer { + return new Drawer(block, info as RenderInfo); } } diff --git a/core/renderers/thrasos/info.ts b/core/renderers/thrasos/info.ts index 26f899c4c..7c6bbb4e8 100644 --- a/core/renderers/thrasos/info.ts +++ b/core/renderers/thrasos/info.ts @@ -19,7 +19,6 @@ import {Types} from '../measurables/types.js'; import type {Renderer} from './renderer.js'; - /** * An object containing all sizing information needed to draw this block. * @@ -64,8 +63,12 @@ export class RenderInfo extends BaseRenderInfo { // No spacing needed before the corner on the top row or the bottom row. if (row.startsWithElemSpacer()) { // There's a spacer before the first element in the row. - row.elements.push(new InRowSpacer( - this.constants_, this.getInRowSpacing_(null, oldElems[0]))); + row.elements.push( + new InRowSpacer( + this.constants_, + this.getInRowSpacing_(null, oldElems[0]) + ) + ); } if (!oldElems.length) { continue; @@ -77,8 +80,10 @@ export class RenderInfo extends BaseRenderInfo { } row.elements.push(oldElems[oldElems.length - 1]); if (row.endsWithElemSpacer()) { - let spacing = - this.getInRowSpacing_(oldElems[oldElems.length - 1], null); + let spacing = this.getInRowSpacing_( + oldElems[oldElems.length - 1], + null + ); if (hasExternalInputs && row.hasDummyInput) { spacing += this.constants_.TAB_WIDTH; } @@ -88,7 +93,7 @@ export class RenderInfo extends BaseRenderInfo { } } - override getInRowSpacing_(prev: Measurable|null, next: Measurable|null) { + override getInRowSpacing_(prev: Measurable | null, next: Measurable | null) { if (!prev) { // Between an editable field and the beginning of the row. if (next && Types.isField(next) && (next as Field).isEditable) { @@ -198,8 +203,12 @@ export class RenderInfo extends BaseRenderInfo { } // Spacing between two fields of the same editability. - if (Types.isField(prev) && next && Types.isField(next) && - (prev as Field).isEditable === (next as Field).isEditable) { + if ( + Types.isField(prev) && + next && + Types.isField(next) && + (prev as Field).isEditable === (next as Field).isEditable + ) { return this.constants_.LARGE_PADDING; } @@ -242,7 +251,7 @@ export class RenderInfo extends BaseRenderInfo { if (Types.isBottomRow(row)) { const bottomRow = row as BottomRow; const baseline = - bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight; + bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight; if (Types.isNextConnection(elem)) { return baseline + elem.height / 2; } @@ -259,7 +268,7 @@ export class RenderInfo extends BaseRenderInfo { let result = row.yPos; if (Types.isField(elem) && row.hasStatement) { const offset = - this.constants_.TALL_INPUT_FIELD_OFFSET_Y + elem.height / 2; + this.constants_.TALL_INPUT_FIELD_OFFSET_Y + elem.height / 2; result += offset; } else { result += row.height / 2; @@ -279,12 +288,16 @@ export class RenderInfo extends BaseRenderInfo { row.xPos = this.startX; yCursor += row.height; - widestRowWithConnectedBlocks = - Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks); + widestRowWithConnectedBlocks = Math.max( + widestRowWithConnectedBlocks, + row.widthWithConnectedBlocks + ); // Add padding to the bottom row if block height is less than minimum const heightWithoutHat = yCursor - this.topRow.ascenderHeight; - if (row === this.bottomRow && - heightWithoutHat < this.constants_.MIN_BLOCK_HEIGHT) { + if ( + row === this.bottomRow && + heightWithoutHat < this.constants_.MIN_BLOCK_HEIGHT + ) { // But the hat height shouldn't be part of this. const diff = this.constants_.MIN_BLOCK_HEIGHT - heightWithoutHat; this.bottomRow.height += diff; @@ -292,13 +305,18 @@ export class RenderInfo extends BaseRenderInfo { } this.recordElemPositions_(row); } - if (this.outputConnection && this.block_.nextConnection && - this.block_.nextConnection.isConnected()) { + if ( + this.outputConnection && + this.block_.nextConnection && + this.block_.nextConnection.isConnected() + ) { const target = this.block_.nextConnection.targetBlock(); if (target) { // Include width of connected block in value to stack width measurement. widestRowWithConnectedBlocks = Math.max( - widestRowWithConnectedBlocks, target.getHeightWidth().width); + widestRowWithConnectedBlocks, + target.getHeightWidth().width + ); } } diff --git a/core/renderers/thrasos/renderer.ts b/core/renderers/thrasos/renderer.ts index 65be1d206..4ea01a458 100644 --- a/core/renderers/thrasos/renderer.ts +++ b/core/renderers/thrasos/renderer.ts @@ -13,7 +13,6 @@ import {Renderer as BaseRenderer} from '../common/renderer.js'; import {RenderInfo} from './info.js'; - /** * The thrasos renderer. This is a more modern take on the legacy geras * renderer. diff --git a/core/renderers/thrasos/thrasos.ts b/core/renderers/thrasos/thrasos.ts index 5b2b419f5..80a03faf3 100644 --- a/core/renderers/thrasos/thrasos.ts +++ b/core/renderers/thrasos/thrasos.ts @@ -12,5 +12,4 @@ goog.declareModuleId('Blockly.thrasos'); import {RenderInfo} from './info.js'; import {Renderer} from './renderer.js'; - export {Renderer, RenderInfo}; diff --git a/core/renderers/zelos/constants.ts b/core/renderers/zelos/constants.ts index 73ee82e61..8e0156455 100644 --- a/core/renderers/zelos/constants.ts +++ b/core/renderers/zelos/constants.ts @@ -17,7 +17,6 @@ import * as svgPaths from '../../utils/svg_paths.js'; import {ConstantProvider as BaseConstantProvider} from '../common/constants.js'; import type {Shape} from '../common/constants.js'; - /** An object containing sizing and path information about inside corners. */ export interface InsideCorners { width: number; @@ -63,24 +62,24 @@ export class ConstantProvider extends BaseConstantProvider { SHAPE_IN_SHAPE_PADDING: {[key: number]: {[key: number]: number}} = { 1: { // Outer shape: hexagon. - 0: 5 * this.GRID_UNIT, // Field in hexagon. - 1: 2 * this.GRID_UNIT, // Hexagon in hexagon. - 2: 5 * this.GRID_UNIT, // Round in hexagon. - 3: 5 * this.GRID_UNIT, // Square in hexagon. + 0: 5 * this.GRID_UNIT, // Field in hexagon. + 1: 2 * this.GRID_UNIT, // Hexagon in hexagon. + 2: 5 * this.GRID_UNIT, // Round in hexagon. + 3: 5 * this.GRID_UNIT, // Square in hexagon. }, 2: { // Outer shape: round. - 0: 3 * this.GRID_UNIT, // Field in round. - 1: 3 * this.GRID_UNIT, // Hexagon in round. - 2: 1 * this.GRID_UNIT, // Round in round. - 3: 2 * this.GRID_UNIT, // Square in round. + 0: 3 * this.GRID_UNIT, // Field in round. + 1: 3 * this.GRID_UNIT, // Hexagon in round. + 2: 1 * this.GRID_UNIT, // Round in round. + 3: 2 * this.GRID_UNIT, // Square in round. }, 3: { // Outer shape: square. - 0: 2 * this.GRID_UNIT, // Field in square. - 1: 2 * this.GRID_UNIT, // Hexagon in square. - 2: 2 * this.GRID_UNIT, // Round in square. - 3: 2 * this.GRID_UNIT, // Square in square. + 0: 2 * this.GRID_UNIT, // Field in square. + 1: 2 * this.GRID_UNIT, // Hexagon in square. + 2: 2 * this.GRID_UNIT, // Round in square. + 3: 2 * this.GRID_UNIT, // Square in square. }, }; @@ -89,7 +88,7 @@ export class ConstantProvider extends BaseConstantProvider { override FIELD_TEXT_FONTWEIGHT = 'bold'; override FIELD_TEXT_FONTFAMILY = - '"Helvetica Neue", "Segoe UI", Helvetica, sans-serif'; + '"Helvetica Neue", "Segoe UI", Helvetica, sans-serif'; override FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW = true; @@ -122,7 +121,7 @@ export class ConstantProvider extends BaseConstantProvider { /** * The element to use for a selected glow, or null if not set. */ - private selectedGlowFilter_: SVGElement|null = null; + private selectedGlowFilter: SVGElement | null = null; /** * The ID of the replacement glow filter, or the empty string if no filter @@ -133,25 +132,25 @@ export class ConstantProvider extends BaseConstantProvider { /** * The element to use for a replacement glow, or null if not set. */ - private replacementGlowFilter_: SVGElement|null = null; + private replacementGlowFilter: SVGElement | null = null; /** * The object containing information about the hexagon used for a boolean * reporter block. Null before init is called. */ - HEXAGONAL: Shape|null = null; + HEXAGONAL: Shape | null = null; /** * The object containing information about the hexagon used for a number or * string reporter block. Null before init is called. */ - ROUNDED: Shape|null = null; + ROUNDED: Shape | null = null; /** * The object containing information about the hexagon used for a * rectangular reporter block. Null before init is called. */ - SQUARED: Shape|null = null; + SQUARED: Shape | null = null; constructor() { super(); @@ -233,7 +232,7 @@ export class ConstantProvider extends BaseConstantProvider { super.setFontConstants_(theme); this.FIELD_BORDER_RECT_HEIGHT = - this.FIELD_TEXT_HEIGHT + this.FIELD_BORDER_RECT_Y_PADDING * 2; + this.FIELD_TEXT_HEIGHT + this.FIELD_BORDER_RECT_Y_PADDING * 2; this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT; } @@ -243,38 +242,43 @@ export class ConstantProvider extends BaseConstantProvider { this.ROUNDED = this.makeRounded(); this.SQUARED = this.makeSquared(); - this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT + - (this.INSIDE_CORNERS as InsideCorners).rightWidth; + this.STATEMENT_INPUT_NOTCH_OFFSET = + this.NOTCH_OFFSET_LEFT + + (this.INSIDE_CORNERS as InsideCorners).rightWidth; } override setDynamicProperties_(theme: Theme) { super.setDynamicProperties_(theme); - this.SELECTED_GLOW_COLOUR = theme.getComponentStyle('selectedGlowColour') || - this.SELECTED_GLOW_COLOUR; - const selectedGlowSize = - Number(theme.getComponentStyle('selectedGlowSize')); - this.SELECTED_GLOW_SIZE = selectedGlowSize && !isNaN(selectedGlowSize) ? - selectedGlowSize : - this.SELECTED_GLOW_SIZE; + this.SELECTED_GLOW_COLOUR = + theme.getComponentStyle('selectedGlowColour') || + this.SELECTED_GLOW_COLOUR; + const selectedGlowSize = Number( + theme.getComponentStyle('selectedGlowSize') + ); + this.SELECTED_GLOW_SIZE = + selectedGlowSize && !isNaN(selectedGlowSize) + ? selectedGlowSize + : this.SELECTED_GLOW_SIZE; this.REPLACEMENT_GLOW_COLOUR = - theme.getComponentStyle('replacementGlowColour') || - this.REPLACEMENT_GLOW_COLOUR; - const replacementGlowSize = - Number(theme.getComponentStyle('replacementGlowSize')); + theme.getComponentStyle('replacementGlowColour') || + this.REPLACEMENT_GLOW_COLOUR; + const replacementGlowSize = Number( + theme.getComponentStyle('replacementGlowSize') + ); this.REPLACEMENT_GLOW_SIZE = - replacementGlowSize && !isNaN(replacementGlowSize) ? - replacementGlowSize : - this.REPLACEMENT_GLOW_SIZE; + replacementGlowSize && !isNaN(replacementGlowSize) + ? replacementGlowSize + : this.REPLACEMENT_GLOW_SIZE; } override dispose() { super.dispose(); - if (this.selectedGlowFilter_) { - dom.removeNode(this.selectedGlowFilter_); + if (this.selectedGlowFilter) { + dom.removeNode(this.selectedGlowFilter); } - if (this.replacementGlowFilter_) { - dom.removeNode(this.replacementGlowFilter_); + if (this.replacementGlowFilter) { + dom.removeNode(this.replacementGlowFilter); } } @@ -317,9 +321,11 @@ export class ConstantProvider extends BaseConstantProvider { const width = halfHeight > maxWidth ? maxWidth : halfHeight; const forward = up ? -1 : 1; const direction = right ? -1 : 1; - const dy = forward * height / 2; - return svgPaths.lineTo(-direction * width, dy) + - svgPaths.lineTo(direction * width, dy); + const dy = (forward * height) / 2; + return ( + svgPaths.lineTo(-direction * width, dy) + + svgPaths.lineTo(direction * width, dy) + ); } return { @@ -379,19 +385,29 @@ export class ConstantProvider extends BaseConstantProvider { * @returns A path fragment describing a rounded connection. */ function makeMainPath( - blockHeight: number, up: boolean, right: boolean): string { + blockHeight: number, + up: boolean, + right: boolean + ): string { const remainingHeight = - blockHeight > maxHeight ? blockHeight - maxHeight : 0; + blockHeight > maxHeight ? blockHeight - maxHeight : 0; const height = blockHeight > maxHeight ? maxHeight : blockHeight; const radius = height / 2; - return svgPaths.arc( - 'a', '0 0,1', radius, - svgPaths.point( - (up ? -1 : 1) * radius, (up ? -1 : 1) * radius)) + - svgPaths.lineOnAxis('v', (right ? 1 : -1) * remainingHeight) + - svgPaths.arc( - 'a', '0 0,1', radius, - svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius)); + return ( + svgPaths.arc( + 'a', + '0 0,1', + radius, + svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius) + ) + + svgPaths.lineOnAxis('v', (right ? 1 : -1) * remainingHeight) + + svgPaths.arc( + 'a', + '0 0,1', + radius, + svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius) + ) + ); } return { @@ -450,14 +466,21 @@ export class ConstantProvider extends BaseConstantProvider { */ function makeMainPath(height: number, up: boolean, right: boolean): string { const innerHeight = height - radius * 2; - return svgPaths.arc( - 'a', '0 0,1', radius, - svgPaths.point( - (up ? -1 : 1) * radius, (up ? -1 : 1) * radius)) + - svgPaths.lineOnAxis('v', (right ? 1 : -1) * innerHeight) + - svgPaths.arc( - 'a', '0 0,1', radius, - svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius)); + return ( + svgPaths.arc( + 'a', + '0 0,1', + radius, + svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius) + ) + + svgPaths.lineOnAxis('v', (right ? 1 : -1) * innerHeight) + + svgPaths.arc( + 'a', + '0 0,1', + radius, + svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius) + ) + ); } return { @@ -548,35 +571,31 @@ export class ConstantProvider extends BaseConstantProvider { * @returns A path fragment describing a notch. */ function makeMainPath(dir: number): string { - return svgPaths.curve( - 'c', - [ - svgPaths.point(dir * curveWidth / 2, 0), - svgPaths.point(dir * curveWidth * 3 / 4, quarterHeight / 2), - svgPaths.point(dir * curveWidth, quarterHeight), - ]) + - svgPaths.line([svgPaths.point(dir * curveWidth, halfHeight)]) + - svgPaths.curve( - 'c', - [ - svgPaths.point(dir * curveWidth / 4, quarterHeight / 2), - svgPaths.point(dir * curveWidth / 2, quarterHeight), - svgPaths.point(dir * curveWidth, quarterHeight), - ]) + - svgPaths.lineOnAxis('h', dir * innerWidth) + - svgPaths.curve( - 'c', - [ - svgPaths.point(dir * curveWidth / 2, 0), - svgPaths.point(dir * curveWidth * 3 / 4, -(quarterHeight / 2)), - svgPaths.point(dir * curveWidth, -quarterHeight), - ]) + - svgPaths.line([svgPaths.point(dir * curveWidth, -halfHeight)]) + - svgPaths.curve('c', [ - svgPaths.point(dir * curveWidth / 4, -(quarterHeight / 2)), - svgPaths.point(dir * curveWidth / 2, -quarterHeight), - svgPaths.point(dir * curveWidth, -quarterHeight), - ]); + return ( + svgPaths.curve('c', [ + svgPaths.point((dir * curveWidth) / 2, 0), + svgPaths.point((dir * curveWidth * 3) / 4, quarterHeight / 2), + svgPaths.point(dir * curveWidth, quarterHeight), + ]) + + svgPaths.line([svgPaths.point(dir * curveWidth, halfHeight)]) + + svgPaths.curve('c', [ + svgPaths.point((dir * curveWidth) / 4, quarterHeight / 2), + svgPaths.point((dir * curveWidth) / 2, quarterHeight), + svgPaths.point(dir * curveWidth, quarterHeight), + ]) + + svgPaths.lineOnAxis('h', dir * innerWidth) + + svgPaths.curve('c', [ + svgPaths.point((dir * curveWidth) / 2, 0), + svgPaths.point((dir * curveWidth * 3) / 4, -(quarterHeight / 2)), + svgPaths.point(dir * curveWidth, -quarterHeight), + ]) + + svgPaths.line([svgPaths.point(dir * curveWidth, -halfHeight)]) + + svgPaths.curve('c', [ + svgPaths.point((dir * curveWidth) / 4, -(quarterHeight / 2)), + svgPaths.point((dir * curveWidth) / 2, -quarterHeight), + svgPaths.point(dir * curveWidth, -quarterHeight), + ]) + ); } const pathLeft = makeMainPath(1); @@ -594,17 +613,33 @@ export class ConstantProvider extends BaseConstantProvider { override makeInsideCorners() { const radius = this.CORNER_RADIUS; - const innerTopLeftCorner = - svgPaths.arc('a', '0 0,0', radius, svgPaths.point(-radius, radius)); + const innerTopLeftCorner = svgPaths.arc( + 'a', + '0 0,0', + radius, + svgPaths.point(-radius, radius) + ); - const innerTopRightCorner = - svgPaths.arc('a', '0 0,1', radius, svgPaths.point(-radius, radius)); + const innerTopRightCorner = svgPaths.arc( + 'a', + '0 0,1', + radius, + svgPaths.point(-radius, radius) + ); - const innerBottomLeftCorner = - svgPaths.arc('a', '0 0,0', radius, svgPaths.point(radius, radius)); + const innerBottomLeftCorner = svgPaths.arc( + 'a', + '0 0,0', + radius, + svgPaths.point(radius, radius) + ); - const innerBottomRightCorner = - svgPaths.arc('a', '0 0,1', radius, svgPaths.point(radius, radius)); + const innerBottomRightCorner = svgPaths.arc( + 'a', + '0 0,1', + radius, + svgPaths.point(radius, radius) + ); return { width: radius, @@ -637,91 +672,115 @@ export class ConstantProvider extends BaseConstantProvider { // Using a dilate distorts the block shape. // Instead use a gaussian blur, and then set all alpha to 1 with a transfer. const selectedGlowFilter = dom.createSvgElement( - Svg.FILTER, { - 'id': 'blocklySelectedGlowFilter' + this.randomIdentifier, - 'height': '160%', - 'width': '180%', - 'y': '-30%', - 'x': '-40%', - }, - defs); + Svg.FILTER, + { + 'id': 'blocklySelectedGlowFilter' + this.randomIdentifier, + 'height': '160%', + 'width': '180%', + 'y': '-30%', + 'x': '-40%', + }, + defs + ); dom.createSvgElement( - Svg.FEGAUSSIANBLUR, - {'in': 'SourceGraphic', 'stdDeviation': this.SELECTED_GLOW_SIZE}, - selectedGlowFilter); + Svg.FEGAUSSIANBLUR, + {'in': 'SourceGraphic', 'stdDeviation': this.SELECTED_GLOW_SIZE}, + selectedGlowFilter + ); // Set all gaussian blur pixels to 1 opacity before applying flood const selectedComponentTransfer = dom.createSvgElement( - Svg.FECOMPONENTTRANSFER, {'result': 'outBlur'}, selectedGlowFilter); + Svg.FECOMPONENTTRANSFER, + {'result': 'outBlur'}, + selectedGlowFilter + ); dom.createSvgElement( - Svg.FEFUNCA, - {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, - selectedComponentTransfer); + Svg.FEFUNCA, + {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, + selectedComponentTransfer + ); // Color the highlight dom.createSvgElement( - Svg.FEFLOOD, { - 'flood-color': this.SELECTED_GLOW_COLOUR, - 'flood-opacity': 1, - 'result': 'outColor', - }, - selectedGlowFilter); + Svg.FEFLOOD, + { + 'flood-color': this.SELECTED_GLOW_COLOUR, + 'flood-opacity': 1, + 'result': 'outColor', + }, + selectedGlowFilter + ); dom.createSvgElement( - Svg.FECOMPOSITE, { - 'in': 'outColor', - 'in2': 'outBlur', - 'operator': 'in', - 'result': 'outGlow', - }, - selectedGlowFilter); + Svg.FECOMPOSITE, + { + 'in': 'outColor', + 'in2': 'outBlur', + 'operator': 'in', + 'result': 'outGlow', + }, + selectedGlowFilter + ); this.selectedGlowFilterId = selectedGlowFilter.id; - this.selectedGlowFilter_ = selectedGlowFilter; + this.selectedGlowFilter = selectedGlowFilter; // Using a dilate distorts the block shape. // Instead use a gaussian blur, and then set all alpha to 1 with a transfer. const replacementGlowFilter = dom.createSvgElement( - Svg.FILTER, { - 'id': 'blocklyReplacementGlowFilter' + this.randomIdentifier, - 'height': '160%', - 'width': '180%', - 'y': '-30%', - 'x': '-40%', - }, - defs); + Svg.FILTER, + { + 'id': 'blocklyReplacementGlowFilter' + this.randomIdentifier, + 'height': '160%', + 'width': '180%', + 'y': '-30%', + 'x': '-40%', + }, + defs + ); dom.createSvgElement( - Svg.FEGAUSSIANBLUR, - {'in': 'SourceGraphic', 'stdDeviation': this.REPLACEMENT_GLOW_SIZE}, - replacementGlowFilter); + Svg.FEGAUSSIANBLUR, + {'in': 'SourceGraphic', 'stdDeviation': this.REPLACEMENT_GLOW_SIZE}, + replacementGlowFilter + ); // Set all gaussian blur pixels to 1 opacity before applying flood const replacementComponentTransfer = dom.createSvgElement( - Svg.FECOMPONENTTRANSFER, {'result': 'outBlur'}, replacementGlowFilter); + Svg.FECOMPONENTTRANSFER, + {'result': 'outBlur'}, + replacementGlowFilter + ); dom.createSvgElement( - Svg.FEFUNCA, - {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, - replacementComponentTransfer); + Svg.FEFUNCA, + {'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'}, + replacementComponentTransfer + ); // Color the highlight dom.createSvgElement( - Svg.FEFLOOD, { - 'flood-color': this.REPLACEMENT_GLOW_COLOUR, - 'flood-opacity': 1, - 'result': 'outColor', - }, - replacementGlowFilter); + Svg.FEFLOOD, + { + 'flood-color': this.REPLACEMENT_GLOW_COLOUR, + 'flood-opacity': 1, + 'result': 'outColor', + }, + replacementGlowFilter + ); dom.createSvgElement( - Svg.FECOMPOSITE, { - 'in': 'outColor', - 'in2': 'outBlur', - 'operator': 'in', - 'result': 'outGlow', - }, - replacementGlowFilter); + Svg.FECOMPOSITE, + { + 'in': 'outColor', + 'in2': 'outBlur', + 'operator': 'in', + 'result': 'outGlow', + }, + replacementGlowFilter + ); dom.createSvgElement( - Svg.FECOMPOSITE, { - 'in': 'SourceGraphic', - 'in2': 'outGlow', - 'operator': 'over', - }, - replacementGlowFilter); + Svg.FECOMPOSITE, + { + 'in': 'SourceGraphic', + 'in2': 'outGlow', + 'operator': 'over', + }, + replacementGlowFilter + ); this.replacementGlowFilterId = replacementGlowFilter.id; - this.replacementGlowFilter_ = replacementGlowFilter; + this.replacementGlowFilter = replacementGlowFilter; } override getCSS_(selector: string) { @@ -731,7 +790,7 @@ export class ConstantProvider extends BaseConstantProvider { `${selector} .blocklyText,`, `${selector} .blocklyFlyoutLabelText {`, `font: ${this.FIELD_TEXT_FONTWEIGHT} ${this.FIELD_TEXT_FONTSIZE}` + - `pt ${this.FIELD_TEXT_FONTFAMILY};`, + `pt ${this.FIELD_TEXT_FONTFAMILY};`, `}`, // Fields. diff --git a/core/renderers/zelos/drawer.ts b/core/renderers/zelos/drawer.ts index 931afc5d2..a2d615e06 100644 --- a/core/renderers/zelos/drawer.ts +++ b/core/renderers/zelos/drawer.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.zelos.Drawer'); import type {BlockSvg} from '../../block_svg.js'; import * as svgPaths from '../../utils/svg_paths.js'; import type {BaseShape, DynamicShape, Notch} from '../common/constants.js'; -import * as debug from '../common/debug.js'; import {Drawer as BaseDrawer} from '../common/drawer.js'; import type {InlineInput} from '../measurables/inline_input.js'; import type {Row} from '../measurables/row.js'; @@ -22,7 +21,6 @@ import type {RenderInfo} from './info.js'; import type {StatementInput} from './measurables/inputs.js'; import type {PathObject} from './path_object.js'; - /** * An object that draws a block based on the given rendering information. */ @@ -42,7 +40,6 @@ export class Drawer extends BaseDrawer { override draw() { const pathObject = this.block_.pathObject as PathObject; pathObject.beginDrawing(); - this.hideHiddenIcons_(); this.drawOutline_(); this.drawInternals_(); @@ -50,9 +47,6 @@ export class Drawer extends BaseDrawer { if (this.info_.RTL) { pathObject.flipRTL(); } - if (debug.isDebuggerEnabled()) { - this.block_?.renderingDebugger?.drawDebug(this.block_, this.info_); - } this.recordSizeOnBlock_(); if (this.info_.outputConnection) { // Store the output connection shape type for parent blocks to use during @@ -63,10 +57,12 @@ export class Drawer extends BaseDrawer { } override drawOutline_() { - if (this.info_.outputConnection && - this.info_.outputConnection.isDynamicShape && - !this.info_.hasStatementInput && - !this.info_.bottomRow.hasNextConnection) { + if ( + this.info_.outputConnection && + this.info_.outputConnection.isDynamicShape && + !this.info_.hasStatementInput && + !this.info_.bottomRow.hasNextConnection + ) { this.drawFlatTop_(); this.drawRightDynamicConnection_(); this.drawFlatBottom_(); @@ -77,8 +73,10 @@ export class Drawer extends BaseDrawer { } override drawLeft_() { - if (this.info_.outputConnection && - this.info_.outputConnection.isDynamicShape) { + if ( + this.info_.outputConnection && + this.info_.outputConnection.isDynamicShape + ) { this.drawLeftDynamicConnection_(); } else { super.drawLeft_(); @@ -97,23 +95,25 @@ export class Drawer extends BaseDrawer { } if (Types.isSpacer(row)) { const spacerRow = row as SpacerRow; - if (spacerRow.precedesStatement || spacerRow.followsStatement) { - const cornerHeight = - (this.constants_.INSIDE_CORNERS as InsideCorners).rightHeight; + const precedesStatement = spacerRow.precedesStatement; + const followsStatement = spacerRow.followsStatement; + if (precedesStatement || followsStatement) { + const insideCorners = this.constants_.INSIDE_CORNERS as InsideCorners; + const cornerHeight = insideCorners.rightHeight; const remainingHeight = - spacerRow.height - (spacerRow.precedesStatement ? cornerHeight : 0); - this.outlinePath_ += - (spacerRow.followsStatement ? - (this.constants_.INSIDE_CORNERS as InsideCorners) - .pathBottomRight : - '') + - (remainingHeight > 0 ? - svgPaths.lineOnAxis('V', spacerRow.yPos + remainingHeight) : - '') + - (spacerRow.precedesStatement ? - (this.constants_.INSIDE_CORNERS as InsideCorners) - .pathTopRight : - ''); + spacerRow.height - (precedesStatement ? cornerHeight : 0); + const bottomRightPath = followsStatement + ? insideCorners.pathBottomRight + : ''; + const verticalPath = + remainingHeight > 0 + ? svgPaths.lineOnAxis('V', spacerRow.yPos + remainingHeight) + : ''; + const topRightPath = precedesStatement + ? insideCorners.pathTopRight + : ''; + // Put all of the partial paths together. + this.outlinePath_ += bottomRightPath + verticalPath + topRightPath; return; } } @@ -126,10 +126,12 @@ export class Drawer extends BaseDrawer { protected drawRightDynamicConnection_() { if (!this.info_.outputConnection) { throw new Error( - `Cannot draw the output connection of a block that doesn't have one`); + `Cannot draw the output connection of a block that doesn't have one` + ); } - this.outlinePath_ += (this.info_.outputConnection.shape as DynamicShape) - .pathRightDown(this.info_.outputConnection.height); + this.outlinePath_ += ( + this.info_.outputConnection.shape as DynamicShape + ).pathRightDown(this.info_.outputConnection.height); } /** @@ -138,12 +140,14 @@ export class Drawer extends BaseDrawer { protected drawLeftDynamicConnection_() { if (!this.info_.outputConnection) { throw new Error( - `Cannot draw the output connection of a block that doesn't have one`); + `Cannot draw the output connection of a block that doesn't have one` + ); } this.positionOutputConnection_(); - this.outlinePath_ += (this.info_.outputConnection.shape as DynamicShape) - .pathUp(this.info_.outputConnection.height); + this.outlinePath_ += ( + this.info_.outputConnection.shape as DynamicShape + ).pathUp(this.info_.outputConnection.height); // Close off the path. This draws a vertical line up to the start of the // block's path, which may be either a rounded or a sharp corner. @@ -156,7 +160,6 @@ export class Drawer extends BaseDrawer { this.positionPreviousConnection_(); this.outlinePath_ += svgPaths.moveBy(topRow.xPos, this.info_.startY); - this.outlinePath_ += svgPaths.lineOnAxis('h', topRow.width); } @@ -166,7 +169,6 @@ export class Drawer extends BaseDrawer { this.positionNextConnection_(); this.outlinePath_ += svgPaths.lineOnAxis('V', bottomRow.baseline); - this.outlinePath_ += svgPaths.lineOnAxis('h', -bottomRow.width); } @@ -184,11 +186,13 @@ export class Drawer extends BaseDrawer { const connectionRight = input.xPos + input.connectionWidth; - const outlinePath = svgPaths.moveTo(connectionRight, yPos) + - svgPaths.lineOnAxis('h', width) + - (input.shape as DynamicShape).pathRightDown(input.height) + - svgPaths.lineOnAxis('h', -width) + - (input.shape as DynamicShape).pathUp(input.height) + 'z'; + const outlinePath = + svgPaths.moveTo(connectionRight, yPos) + + svgPaths.lineOnAxis('h', width) + + (input.shape as DynamicShape).pathRightDown(input.height) + + svgPaths.lineOnAxis('h', -width) + + (input.shape as DynamicShape).pathUp(input.height) + + 'z'; const pathObject = this.block_.pathObject as PathObject; pathObject.setOutlinePath(inputName, outlinePath); } @@ -198,22 +202,27 @@ export class Drawer extends BaseDrawer { // Where to start drawing the notch, which is on the right side in LTR. const x = input.xPos + input.notchOffset + (input.shape as BaseShape).width; - const innerTopLeftCorner = (input.shape as Notch).pathRight + - svgPaths.lineOnAxis( - 'h', -(input.notchOffset - this.constants_.INSIDE_CORNERS.width)) + - this.constants_.INSIDE_CORNERS.pathTop; + const insideCorners = this.constants_.INSIDE_CORNERS; + const innerTopLeftCorner = + (input.shape as Notch).pathRight + + svgPaths.lineOnAxis('h', -(input.notchOffset - insideCorners.width)) + + insideCorners.pathTop; - const innerHeight = row.height - 2 * this.constants_.INSIDE_CORNERS.height; + const innerHeight = row.height - 2 * insideCorners.height; - const innerBottomLeftCorner = this.constants_.INSIDE_CORNERS.pathBottom + - svgPaths.lineOnAxis( - 'h', input.notchOffset - this.constants_.INSIDE_CORNERS.width) + - (input.connectedBottomNextConnection ? '' : - (input.shape as Notch).pathLeft); + const innerBottomLeftCorner = + insideCorners.pathBottom + + svgPaths.lineOnAxis('h', input.notchOffset - insideCorners.width) + + (input.connectedBottomNextConnection + ? '' + : (input.shape as Notch).pathLeft); - this.outlinePath_ += svgPaths.lineOnAxis('H', x) + innerTopLeftCorner + - svgPaths.lineOnAxis('v', innerHeight) + innerBottomLeftCorner + - svgPaths.lineOnAxis('H', row.xPos + row.width); + this.outlinePath_ += + svgPaths.lineOnAxis('H', x) + + innerTopLeftCorner + + svgPaths.lineOnAxis('v', innerHeight) + + innerBottomLeftCorner + + svgPaths.lineOnAxis('H', row.xPos + row.width); this.positionStatementInputConnection_(row); } diff --git a/core/renderers/zelos/info.ts b/core/renderers/zelos/info.ts index 30f179cba..a6a4cfe8f 100644 --- a/core/renderers/zelos/info.ts +++ b/core/renderers/zelos/info.ts @@ -8,29 +8,31 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.zelos.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; +import {DummyInput} from '../../inputs/dummy_input.js'; import {FieldImage} from '../../field_image.js'; import {FieldLabel} from '../../field_label.js'; import {FieldTextInput} from '../../field_textinput.js'; -import {Align, Input} from '../../input.js'; -import {inputTypes} from '../../input_types.js'; +import {Input} from '../../inputs/input.js'; +import {Align} from '../../inputs/align.js'; import {RenderInfo as BaseRenderInfo} from '../common/info.js'; import type {Measurable} from '../measurables/base.js'; import {Field} from '../measurables/field.js'; import {InRowSpacer} from '../measurables/in_row_spacer.js'; import {InputConnection} from '../measurables/input_connection.js'; +import {StatementInput} from '../../inputs/statement_input.js'; import type {Row} from '../measurables/row.js'; import type {SpacerRow} from '../measurables/spacer_row.js'; import {Types} from '../measurables/types.js'; +import {ValueInput} from '../../inputs/value_input.js'; import type {ConstantProvider, InsideCorners} from './constants.js'; import {BottomRow} from './measurables/bottom_row.js'; -import {StatementInput} from './measurables/inputs.js'; +import {StatementInput as StatementInputMeasurable} from './measurables/inputs.js'; import {RightConnectionShape} from './measurables/row_elements.js'; import {TopRow} from './measurables/top_row.js'; import type {PathObject} from './path_object.js'; import type {Renderer} from './renderer.js'; - /** * An object containing all sizing information needed to draw this block. * @@ -47,8 +49,8 @@ export class RenderInfo extends BaseRenderInfo { override isInline = true; isMultiRow: boolean; hasStatementInput: boolean; - rightSide: RightConnectionShape|null; - private readonly rightAlignedDummyInputs_: WeakMap; + rightSide: RightConnectionShape | null; + private readonly rightAlignedDummyInputs: WeakMap; /** * @param renderer The renderer in use. @@ -85,15 +87,15 @@ export class RenderInfo extends BaseRenderInfo { /** * An object with rendering information about the right connection shape. */ - this.rightSide = this.outputConnection ? - new RightConnectionShape(this.constants_) : - null; + this.rightSide = this.outputConnection + ? new RightConnectionShape(this.constants_) + : null; /** * A map of rows to right aligned dummy inputs within those rows. Used to * add padding between left and right aligned inputs. */ - this.rightAlignedDummyInputs_ = new WeakMap(); + this.rightAlignedDummyInputs = new WeakMap(); } /** @@ -123,12 +125,14 @@ export class RenderInfo extends BaseRenderInfo { return false; } // A statement input or an input following one always gets a new row. - if (input.type === inputTypes.STATEMENT || - lastInput.type === inputTypes.STATEMENT) { + if ( + input instanceof StatementInput || + lastInput instanceof StatementInput + ) { return true; } // Value and dummy inputs get new row if inputs are not inlined. - if (input.type === inputTypes.VALUE || input.type === inputTypes.DUMMY) { + if (input instanceof ValueInput || input instanceof DummyInput) { return !this.isInline || this.isMultiRow; } return false; @@ -137,19 +141,25 @@ export class RenderInfo extends BaseRenderInfo { override getDesiredRowWidth_(row: Row): number { if (row.hasStatement) { const rightCornerWidth = - (this.constants_.INSIDE_CORNERS as InsideCorners).rightWidth || 0; + (this.constants_.INSIDE_CORNERS as InsideCorners).rightWidth || 0; return this.width - this.startX - rightCornerWidth; } return super.getDesiredRowWidth_(row); } - override getInRowSpacing_(prev: Measurable|null, next: Measurable|null): - number { + override getInRowSpacing_( + prev: Measurable | null, + next: Measurable | null + ): number { if (!prev || !next) { // No need for padding at the beginning or end of the row if the // output shape is dynamic. - if (this.outputConnection && this.outputConnection.isDynamicShape && - !this.hasStatementInput && !this.bottomRow.hasNextConnection) { + if ( + this.outputConnection && + this.outputConnection.isDynamicShape && + !this.hasStatementInput && + !this.bottomRow.hasNextConnection + ) { return this.constants_.NO_PADDING; } } @@ -181,35 +191,41 @@ export class RenderInfo extends BaseRenderInfo { const precedesStatement = Types.isInputRow(next) && next.hasStatement; if (precedesStatement || followsStatement) { const cornerHeight = - (this.constants_.INSIDE_CORNERS as InsideCorners).rightHeight || 0; + (this.constants_.INSIDE_CORNERS as InsideCorners).rightHeight || 0; const height = Math.max(this.constants_.NOTCH_HEIGHT, cornerHeight); - return precedesStatement && followsStatement ? - Math.max(height, this.constants_.DUMMY_INPUT_MIN_HEIGHT) : - height; + return precedesStatement && followsStatement + ? Math.max(height, this.constants_.DUMMY_INPUT_MIN_HEIGHT) + : height; } // Top and bottom rows act as a spacer so we don't need any extra padding. if (Types.isTopRow(prev)) { const topRow = prev as TopRow; - if (!topRow.hasPreviousConnection && - (!this.outputConnection || this.hasStatementInput)) { + if ( + !topRow.hasPreviousConnection && + (!this.outputConnection || this.hasStatementInput) + ) { return Math.abs( - this.constants_.NOTCH_HEIGHT - this.constants_.CORNER_RADIUS); + this.constants_.NOTCH_HEIGHT - this.constants_.CORNER_RADIUS + ); } return this.constants_.NO_PADDING; } if (Types.isBottomRow(next)) { const bottomRow = next as BottomRow; if (!this.outputConnection) { - const topHeight = Math.max( - this.topRow.minHeight, - Math.max( - this.constants_.NOTCH_HEIGHT, - this.constants_.CORNER_RADIUS)) - - this.constants_.CORNER_RADIUS; + const topHeight = + Math.max( + this.topRow.minHeight, + Math.max( + this.constants_.NOTCH_HEIGHT, + this.constants_.CORNER_RADIUS + ) + ) - this.constants_.CORNER_RADIUS; return topHeight; } else if (!bottomRow.hasNextConnection && this.hasStatementInput) { return Math.abs( - this.constants_.NOTCH_HEIGHT - this.constants_.CORNER_RADIUS); + this.constants_.NOTCH_HEIGHT - this.constants_.CORNER_RADIUS + ); } return this.constants_.NO_PADDING; } @@ -218,22 +234,30 @@ export class RenderInfo extends BaseRenderInfo { override getSpacerRowWidth_(prev: Row, next: Row): number { const width = this.width - this.startX; - if (Types.isInputRow(prev) && prev.hasStatement || - Types.isInputRow(next) && next.hasStatement) { + if ( + (Types.isInputRow(prev) && prev.hasStatement) || + (Types.isInputRow(next) && next.hasStatement) + ) { return Math.max(width, this.constants_.STATEMENT_INPUT_SPACER_MIN_WIDTH); } return width; } override getElemCenterline_(row: Row, elem: Measurable): number { - if (row.hasStatement && !Types.isSpacer(elem) && - !Types.isStatementInput(elem)) { + if ( + row.hasStatement && + !Types.isSpacer(elem) && + !Types.isStatementInput(elem) + ) { return row.yPos + this.constants_.EMPTY_STATEMENT_INPUT_HEIGHT / 2; } if (Types.isInlineInput(elem) && elem instanceof InputConnection) { const connectedBlock = elem.connectedBlock; - if (connectedBlock && connectedBlock.outputConnection && - connectedBlock.nextConnection) { + if ( + connectedBlock && + connectedBlock.outputConnection && + connectedBlock.nextConnection + ) { return row.yPos + connectedBlock.height / 2; } } @@ -244,12 +268,18 @@ export class RenderInfo extends BaseRenderInfo { // If we have two dummy inputs on the same row, one aligned left and the // other right, keep track of the right aligned dummy input so we can add // padding later. - if (input.type === inputTypes.DUMMY && activeRow.hasDummyInput && - activeRow.align === Align.LEFT && input.align === Align.RIGHT) { - this.rightAlignedDummyInputs_.set(activeRow, input); - } else if (input.type === inputTypes.STATEMENT) { + if ( + input instanceof DummyInput && + activeRow.hasDummyInput && + activeRow.align === Align.LEFT && + input.align === Align.RIGHT + ) { + this.rightAlignedDummyInputs.set(activeRow, input); + } else if (input instanceof StatementInput) { // Handle statements without next connections correctly. - activeRow.elements.push(new StatementInput(this.constants_, input)); + activeRow.elements.push( + new StatementInputMeasurable(this.constants_, input) + ); activeRow.hasStatement = true; if (activeRow.align === null) { @@ -261,15 +291,18 @@ export class RenderInfo extends BaseRenderInfo { } override addAlignmentPadding_(row: Row, missingSpace: number) { - if (this.rightAlignedDummyInputs_.get(row)) { + if (this.rightAlignedDummyInputs.get(row)) { let alignmentDivider; for (let i = 0; i < row.elements.length; i++) { const elem = row.elements[i]; if (Types.isSpacer(elem)) { alignmentDivider = elem; } - if (Types.isField(elem) && elem instanceof Field && - elem.parentInput === this.rightAlignedDummyInputs_.get(row)) { + if ( + Types.isField(elem) && + elem instanceof Field && + elem.parentInput === this.rightAlignedDummyInputs.get(row) + ) { break; } } @@ -289,7 +322,7 @@ export class RenderInfo extends BaseRenderInfo { */ protected adjustXPosition_() { const notchTotalWidth = - this.constants_.NOTCH_OFFSET_LEFT + this.constants_.NOTCH_WIDTH; + this.constants_.NOTCH_OFFSET_LEFT + this.constants_.NOTCH_WIDTH; let minXPos = notchTotalWidth; // Run through every input row on the block and only apply bump logic to the // first input row (if the block has prev connection) and every input row @@ -299,19 +332,25 @@ export class RenderInfo extends BaseRenderInfo { const row = this.rows[i]; const nextSpacer = this.rows[i + 1] as SpacerRow; - const hasPrevNotch = i === 2 ? !!this.topRow.hasPreviousConnection : - !!prevSpacer.followsStatement; - const hasNextNotch = i + 2 >= this.rows.length - 1 ? - !!this.bottomRow.hasNextConnection : - !!nextSpacer.precedesStatement; + const hasPrevNotch = + i === 2 + ? !!this.topRow.hasPreviousConnection + : !!prevSpacer.followsStatement; + const hasNextNotch = + i + 2 >= this.rows.length - 1 + ? !!this.bottomRow.hasNextConnection + : !!nextSpacer.precedesStatement; if (Types.isInputRow(row) && row.hasStatement) { row.measure(); minXPos = - row.width - (row.getLastInput()?.width ?? 0) + notchTotalWidth; + row.width - (row.getLastInput()?.width ?? 0) + notchTotalWidth; } else if ( - hasPrevNotch && (i === 2 || hasNextNotch) && Types.isInputRow(row) && - !row.hasStatement) { + hasPrevNotch && + (i === 2 || hasNextNotch) && + Types.isInputRow(row) && + !row.hasStatement + ) { let xCursor = row.xPos; let prevInRowSpacer = null; for (let j = 0; j < row.elements.length; j++) { @@ -320,10 +359,15 @@ export class RenderInfo extends BaseRenderInfo { prevInRowSpacer = elem; } if (prevInRowSpacer && (Types.isField(elem) || Types.isInput(elem))) { - if (xCursor < minXPos && - !(Types.isField(elem) && elem instanceof Field && - (elem.field instanceof FieldLabel || - elem.field instanceof FieldImage))) { + if ( + xCursor < minXPos && + !( + Types.isField(elem) && + elem instanceof Field && + (elem.field instanceof FieldLabel || + elem.field instanceof FieldImage) + ) + ) { const difference = minXPos - xCursor; prevInRowSpacer.width += difference; } @@ -346,8 +390,9 @@ export class RenderInfo extends BaseRenderInfo { return; } const outputConnectionShape = this.outputConnection.shape; - if (!('isDynamic' in outputConnectionShape && - outputConnectionShape.isDynamic)) { + if ( + !('isDynamic' in outputConnectionShape && outputConnectionShape.isDynamic) + ) { return; } @@ -361,9 +406,9 @@ export class RenderInfo extends BaseRenderInfo { this.height = yCursor; // Adjust the height of the output connection. - const blockHeight = this.bottomRow.hasNextConnection ? - this.height - this.bottomRow.descenderHeight : - this.height; + const blockHeight = this.bottomRow.hasNextConnection + ? this.height - this.bottomRow.descenderHeight + : this.height; const connectionHeight = outputConnectionShape.height(blockHeight); const connectionWidth = outputConnectionShape.width(blockHeight); @@ -371,9 +416,9 @@ export class RenderInfo extends BaseRenderInfo { this.outputConnection.width = connectionWidth; this.outputConnection.startX = connectionWidth; this.outputConnection.connectionOffsetY = - outputConnectionShape.connectionOffsetY(connectionHeight); + outputConnectionShape.connectionOffsetY(connectionHeight); this.outputConnection.connectionOffsetX = - outputConnectionShape.connectionOffsetX(connectionWidth); + outputConnectionShape.connectionOffsetX(connectionWidth); // Add the right connection measurable. // Don't add it if we have a value-to-statement or a value-to-stack block. @@ -397,8 +442,11 @@ export class RenderInfo extends BaseRenderInfo { * spacers. */ protected finalizeHorizontalAlignment_() { - if (!this.outputConnection || this.hasStatementInput || - this.bottomRow.hasNextConnection) { + if ( + !this.outputConnection || + this.hasStatementInput || + this.bottomRow.hasNextConnection + ) { return; } let totalNegativeSpacing = 0; @@ -413,7 +461,7 @@ export class RenderInfo extends BaseRenderInfo { let rightNegPadding = this.getNegativeSpacing_(lastElem); totalNegativeSpacing = leftNegPadding + rightNegPadding; const minBlockWidth = - this.constants_.MIN_BLOCK_WIDTH + this.outputConnection.width * 2; + this.constants_.MIN_BLOCK_WIDTH + this.outputConnection.width * 2; if (this.width - totalNegativeSpacing < minBlockWidth) { // Maintain a minimum block width, split negative spacing between left // and right edge. @@ -453,7 +501,7 @@ export class RenderInfo extends BaseRenderInfo { } const connectionWidth = this.outputConnection.width; const outerShape = this.outputConnection.shape.type; - const constants = (this.constants_); + const constants = this.constants_; if (this.isMultiRow && this.inputRows.length > 1) { switch (outerShape) { case constants.SHAPES.ROUND: { @@ -462,7 +510,7 @@ export class RenderInfo extends BaseRenderInfo { const width = this.height / 2 > maxWidth ? maxWidth : this.height / 2; const topPadding = this.constants_.SMALL_PADDING; const roundPadding = - width * (1 - Math.sin(Math.acos((width - topPadding) / width))); + width * (1 - Math.sin(Math.acos((width - topPadding) / width))); return connectionWidth - roundPadding; } default: @@ -471,33 +519,42 @@ export class RenderInfo extends BaseRenderInfo { } if (Types.isInlineInput(elem) && elem instanceof InputConnection) { const connectedBlock = elem.connectedBlock; - const innerShape = connectedBlock ? - (connectedBlock.pathObject as PathObject).outputShapeType : - elem.shape.type; + const innerShape = connectedBlock + ? (connectedBlock.pathObject as PathObject).outputShapeType + : elem.shape.type; if (innerShape == null) { return 0; } // Special case for value to stack / value to statement blocks. - if (connectedBlock && connectedBlock.outputConnection && - (connectedBlock.statementInputCount || - connectedBlock.nextConnection)) { + if ( + connectedBlock && + connectedBlock.outputConnection && + (connectedBlock.statementInputCount || connectedBlock.nextConnection) + ) { return 0; } // Special case for hexagonal output. - if (outerShape === constants.SHAPES.HEXAGONAL && - outerShape !== innerShape) { + if ( + outerShape === constants.SHAPES.HEXAGONAL && + outerShape !== innerShape + ) { return 0; } - return connectionWidth - - this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][innerShape]; + return ( + connectionWidth - + this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][innerShape] + ); } else if (Types.isField(elem) && elem instanceof Field) { // Special case for text inputs. - if (outerShape === constants.SHAPES.ROUND && - elem.field instanceof FieldTextInput) { + if ( + outerShape === constants.SHAPES.ROUND && + elem.field instanceof FieldTextInput + ) { return connectionWidth - 2.75 * constants.GRID_UNIT; } - return connectionWidth - - this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][0]; + return ( + connectionWidth - this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][0] + ); } else if (Types.isIcon(elem)) { return this.constants_.SMALL_PADDING; } @@ -521,18 +578,21 @@ export class RenderInfo extends BaseRenderInfo { const nextSpacer = this.rows[i + 1] as SpacerRow; const firstRow = i === 2; - const hasPrevNotch = firstRow ? !!this.topRow.hasPreviousConnection : - !!prevSpacer.followsStatement; - const hasNextNotch = i + 2 >= this.rows.length - 1 ? - !!this.bottomRow.hasNextConnection : - !!nextSpacer.precedesStatement; + const hasPrevNotch = firstRow + ? !!this.topRow.hasPreviousConnection + : !!prevSpacer.followsStatement; + const hasNextNotch = + i + 2 >= this.rows.length - 1 + ? !!this.bottomRow.hasNextConnection + : !!nextSpacer.precedesStatement; if (hasPrevNotch) { const elem = row.elements[1]; - const hasSingleTextOrImageField = row.elements.length === 3 && - elem instanceof Field && - (elem.field instanceof FieldLabel || - elem.field instanceof FieldImage); + const hasSingleTextOrImageField = + row.elements.length === 3 && + elem instanceof Field && + (elem.field instanceof FieldLabel || + elem.field instanceof FieldImage); if (!firstRow && hasSingleTextOrImageField) { // Remove some padding if we have a single image or text field. prevSpacer.height -= this.constants_.SMALL_PADDING; @@ -547,10 +607,14 @@ export class RenderInfo extends BaseRenderInfo { const minVerticalTightNestingHeight = 40; for (let j = 0; j < row.elements.length; j++) { const elem = row.elements[j]; - if (elem instanceof InputConnection && Types.isInlineInput(elem) && - elem.connectedBlock && !elem.connectedBlock.isShadow() && - elem.connectedBlock.getHeightWidth().height >= - minVerticalTightNestingHeight) { + if ( + elem instanceof InputConnection && + Types.isInlineInput(elem) && + elem.connectedBlock && + !elem.connectedBlock.isShadow() && + elem.connectedBlock.getHeightWidth().height >= + minVerticalTightNestingHeight + ) { hasNonShadowConnectedBlocks = true; break; } diff --git a/core/renderers/zelos/marker_svg.ts b/core/renderers/zelos/marker_svg.ts index 7e8ce2522..f386babe6 100644 --- a/core/renderers/zelos/marker_svg.ts +++ b/core/renderers/zelos/marker_svg.ts @@ -19,7 +19,6 @@ import {MarkerSvg as BaseMarkerSvg} from '../common/marker_svg.js'; import type {ConstantProvider as ZelosConstantProvider} from './constants.js'; - /** * Class to draw a marker. */ @@ -27,7 +26,7 @@ export class MarkerSvg extends BaseMarkerSvg { // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. constants_!: ZelosConstantProvider; - private markerCircle_: SVGCircleElement|null = null; + private markerCircle: SVGCircleElement | null = null; /** * @param workspace The workspace the marker belongs to. @@ -35,8 +34,10 @@ export class MarkerSvg extends BaseMarkerSvg { * @param marker The marker to draw. */ constructor( - workspace: WorkspaceSvg, constants: BaseConstantProvider, - marker: Marker) { + workspace: WorkspaceSvg, + constants: BaseConstantProvider, + marker: Marker + ) { super(workspace, constants, marker); } @@ -45,22 +46,22 @@ export class MarkerSvg extends BaseMarkerSvg { * * @param curNode The node to draw the marker for. */ - private showWithInputOutput_(curNode: ASTNode) { + private showWithInputOutput(curNode: ASTNode) { const block = curNode.getSourceBlock() as BlockSvg; const connection = curNode.getLocation() as RenderedConnection; const offsetInBlock = connection.getOffsetInBlock(); - this.positionCircle_(offsetInBlock.x, offsetInBlock.y); + this.positionCircle(offsetInBlock.x, offsetInBlock.y); this.setParent_(block); this.showCurrent_(); } override showWithOutput_(curNode: ASTNode) { - this.showWithInputOutput_(curNode); + this.showWithInputOutput(curNode); } override showWithInput_(curNode: ASTNode) { - this.showWithInputOutput_(curNode); + this.showWithInputOutput(curNode); } /** @@ -86,16 +87,16 @@ export class MarkerSvg extends BaseMarkerSvg { * @param x The x position of the circle. * @param y The y position of the circle. */ - private positionCircle_(x: number, y: number) { - this.markerCircle_?.setAttribute('cx', `${x}`); - this.markerCircle_?.setAttribute('cy', `${y}`); - this.currentMarkerSvg = this.markerCircle_; + private positionCircle(x: number, y: number) { + this.markerCircle?.setAttribute('cx', `${x}`); + this.markerCircle?.setAttribute('cy', `${y}`); + this.currentMarkerSvg = this.markerCircle; } override hide() { super.hide(); - if (this.markerCircle_) { - this.markerCircle_.style.display = 'none'; + if (this.markerCircle) { + this.markerCircle.style.display = 'none'; } } @@ -112,18 +113,20 @@ export class MarkerSvg extends BaseMarkerSvg { /* clang-format on */ super.createDomInternal_(); - this.markerCircle_ = dom.createSvgElement( - Svg.CIRCLE, { - 'r': this.constants_.CURSOR_RADIUS, - 'style': 'display: none', - 'stroke-width': this.constants_.CURSOR_STROKE_WIDTH, - }, - this.markerSvg_); + this.markerCircle = dom.createSvgElement( + Svg.CIRCLE, + { + 'r': this.constants_.CURSOR_RADIUS, + 'style': 'display: none', + 'stroke-width': this.constants_.CURSOR_STROKE_WIDTH, + }, + this.markerSvg_ + ); // Markers and stack cursors don't blink. if (this.isCursor()) { const blinkProperties = this.getBlinkProperties_(); - dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerCircle_!); + dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerCircle!); } return this.markerSvg_!; @@ -132,12 +135,12 @@ export class MarkerSvg extends BaseMarkerSvg { override applyColour_(curNode: ASTNode) { super.applyColour_(curNode); - this.markerCircle_?.setAttribute('fill', this.colour_); - this.markerCircle_?.setAttribute('stroke', this.colour_); + this.markerCircle?.setAttribute('fill', this.colour_); + this.markerCircle?.setAttribute('stroke', this.colour_); if (this.isCursor()) { const values = this.colour_ + ';transparent;transparent;'; - this.markerCircle_?.firstElementChild!.setAttribute('values', values); + this.markerCircle?.firstElementChild!.setAttribute('values', values); } } } diff --git a/core/renderers/zelos/measurables/bottom_row.ts b/core/renderers/zelos/measurables/bottom_row.ts index 429f120ac..663e4353f 100644 --- a/core/renderers/zelos/measurables/bottom_row.ts +++ b/core/renderers/zelos/measurables/bottom_row.ts @@ -11,7 +11,6 @@ import type {BlockSvg} from '../../../block_svg.js'; import type {ConstantProvider} from '../../../renderers/common/constants.js'; import {BottomRow as BaseBottomRow} from '../../../renderers/measurables/bottom_row.js'; - /** * An object containing information about what elements are in the bottom row of * a block as well as spacing information for the top row. @@ -37,7 +36,10 @@ export class BottomRow extends BaseBottomRow { /** Render a round corner unless the block has an output connection. */ override hasRightSquareCorner(block: BlockSvg) { - return !!block.outputConnection && !block.statementInputCount && - !block.nextConnection; + return ( + !!block.outputConnection && + !block.statementInputCount && + !block.nextConnection + ); } } diff --git a/core/renderers/zelos/measurables/inputs.ts b/core/renderers/zelos/measurables/inputs.ts index 4699bdd64..ca371b186 100644 --- a/core/renderers/zelos/measurables/inputs.ts +++ b/core/renderers/zelos/measurables/inputs.ts @@ -8,11 +8,10 @@ import * as goog from '../../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.zelos.StatementInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../../input.js'; +import type {Input} from '../../../inputs/input.js'; import type {ConstantProvider} from '../../../renderers/common/constants.js'; import {StatementInput as BaseStatementInput} from '../../../renderers/measurables/statement_input.js'; - /** * An object containing information about the space a statement input takes up * during rendering. @@ -31,7 +30,7 @@ export class StatementInput extends BaseStatementInput { // Find the bottom-most connected block in the stack. let block = this.connectedBlock; let nextBlock; - while (nextBlock = block.getNextBlock()) { + while ((nextBlock = block.getNextBlock())) { block = nextBlock; } if (!block.nextConnection) { diff --git a/core/renderers/zelos/measurables/row_elements.ts b/core/renderers/zelos/measurables/row_elements.ts index db97735fc..3c1e4847f 100644 --- a/core/renderers/zelos/measurables/row_elements.ts +++ b/core/renderers/zelos/measurables/row_elements.ts @@ -11,7 +11,6 @@ import type {ConstantProvider} from '../../../renderers/common/constants.js'; import {Measurable} from '../../../renderers/measurables/base.js'; import {Types} from '../../../renderers/measurables/types.js'; - /** * An object containing information about the space a right connection shape * takes up during rendering. diff --git a/core/renderers/zelos/measurables/top_row.ts b/core/renderers/zelos/measurables/top_row.ts index 531b10759..be1c8597b 100644 --- a/core/renderers/zelos/measurables/top_row.ts +++ b/core/renderers/zelos/measurables/top_row.ts @@ -11,7 +11,6 @@ import type {BlockSvg} from '../../../block_svg.js'; import type {ConstantProvider} from '../../../renderers/common/constants.js'; import {TopRow as BaseTopRow} from '../../../renderers/measurables/top_row.js'; - /** * An object containing information about what elements are in the top row of a * block as well as sizing information for the top row. @@ -35,14 +34,18 @@ export class TopRow extends BaseTopRow { /** Render a round corner unless the block has an output connection. */ override hasLeftSquareCorner(block: BlockSvg) { const hasHat = - (block.hat ? block.hat === 'cap' : this.constants_.ADD_START_HATS) && - !block.outputConnection && !block.previousConnection; + (block.hat ? block.hat === 'cap' : this.constants_.ADD_START_HATS) && + !block.outputConnection && + !block.previousConnection; return !!block.outputConnection || hasHat; } /** Render a round corner unless the block has an output connection. */ override hasRightSquareCorner(block: BlockSvg) { - return !!block.outputConnection && !block.statementInputCount && - !block.nextConnection; + return ( + !!block.outputConnection && + !block.statementInputCount && + !block.nextConnection + ); } } diff --git a/core/renderers/zelos/path_object.ts b/core/renderers/zelos/path_object.ts index ca99f4c77..68f7ca2c1 100644 --- a/core/renderers/zelos/path_object.ts +++ b/core/renderers/zelos/path_object.ts @@ -16,14 +16,13 @@ import {PathObject as BasePathObject} from '../common/path_object.js'; import type {ConstantProvider} from './constants.js'; - /** * An object that handles creating and setting each of the SVG elements * used by the renderer. */ export class PathObject extends BasePathObject { /** The selected path of the block. */ - private svgPathSelected_: SVGElement|null = null; + private svgPathSelected: SVGElement | null = null; /** The outline paths on the block. */ private readonly outlines = new Map(); @@ -40,7 +39,7 @@ export class PathObject extends BasePathObject { * The type of block's output connection shape. This is set when a block * with an output connection is drawn. */ - outputShapeType: number|null = null; + outputShapeType: number | null = null; public override constants: ConstantProvider; @@ -50,7 +49,10 @@ export class PathObject extends BasePathObject { * @param constants The renderer's constants. */ constructor( - root: SVGElement, style: BlockStyle, constants: ConstantProvider) { + root: SVGElement, + style: BlockStyle, + constants: ConstantProvider + ) { super(root, style, constants); this.constants = constants; @@ -58,8 +60,8 @@ export class PathObject extends BasePathObject { override setPath(pathString: string) { super.setPath(pathString); - if (this.svgPathSelected_) { - this.svgPathSelected_.setAttribute('d', pathString); + if (this.svgPathSelected) { + this.svgPathSelected.setAttribute('d', pathString); } } @@ -88,17 +90,19 @@ export class PathObject extends BasePathObject { override updateSelected(enable: boolean) { this.setClass_('blocklySelected', enable); if (enable) { - if (!this.svgPathSelected_) { - this.svgPathSelected_ = this.svgPath.cloneNode(true) as SVGElement; - this.svgPathSelected_.setAttribute('fill', 'none'); - this.svgPathSelected_.setAttribute( - 'filter', 'url(#' + this.constants.selectedGlowFilterId + ')'); - this.svgRoot.appendChild(this.svgPathSelected_); + if (!this.svgPathSelected) { + this.svgPathSelected = this.svgPath.cloneNode(true) as SVGElement; + this.svgPathSelected.setAttribute('fill', 'none'); + this.svgPathSelected.setAttribute( + 'filter', + 'url(#' + this.constants.selectedGlowFilterId + ')' + ); + this.svgRoot.appendChild(this.svgPathSelected); } } else { - if (this.svgPathSelected_) { - this.svgRoot.removeChild(this.svgPathSelected_); - this.svgPathSelected_ = null; + if (this.svgPathSelected) { + this.svgRoot.removeChild(this.svgPathSelected); + this.svgPathSelected = null; } } } @@ -107,7 +111,9 @@ export class PathObject extends BasePathObject { this.setClass_('blocklyReplaceable', enable); if (enable) { this.svgPath.setAttribute( - 'filter', 'url(#' + this.constants.replacementGlowFilterId + ')'); + 'filter', + 'url(#' + this.constants.replacementGlowFilterId + ')' + ); } else { this.svgPath.removeAttribute('filter'); } @@ -115,13 +121,15 @@ export class PathObject extends BasePathObject { override updateShapeForInputHighlight(conn: Connection, enable: boolean) { const name = conn.getParentInput()!.name; - const outlinePath = this.getOutlinePath_(name); + const outlinePath = this.getOutlinePath(name); if (!outlinePath) { return; } if (enable) { outlinePath.setAttribute( - 'filter', 'url(#' + this.constants.replacementGlowFilterId + ')'); + 'filter', + 'url(#' + this.constants.replacementGlowFilterId + ')' + ); } else { outlinePath.removeAttribute('filter'); } @@ -145,7 +153,7 @@ export class PathObject extends BasePathObject { // remove them. if (this.remainingOutlines.size) { for (const key of this.remainingOutlines) { - this.removeOutlinePath_(key); + this.removeOutlinePath(key); } } this.remainingOutlines.clear(); @@ -159,7 +167,7 @@ export class PathObject extends BasePathObject { * @param pathString The path. */ setOutlinePath(name: string, pathString: string) { - const outline = this.getOutlinePath_(name); + const outline = this.getOutlinePath(name); outline.setAttribute('d', pathString); outline.setAttribute('fill', this.style.colourTertiary); } @@ -170,19 +178,21 @@ export class PathObject extends BasePathObject { * @param name The input name. * @returns The SVG outline path. */ - private getOutlinePath_(name: string): SVGElement { + private getOutlinePath(name: string): SVGElement { if (!this.outlines.has(name)) { this.outlines.set( - name, - dom.createSvgElement( - Svg.PATH, { - 'class': - 'blocklyOutlinePath', // IE doesn't like paths without the - // data definition, set empty - // default - 'd': '', - }, - this.svgRoot)); + name, + dom.createSvgElement( + Svg.PATH, + { + 'class': 'blocklyOutlinePath', // IE doesn't like paths without the + // data definition, set empty + // default + 'd': '', + }, + this.svgRoot + ) + ); } this.remainingOutlines.delete(name); return this.outlines.get(name)!; @@ -193,7 +203,7 @@ export class PathObject extends BasePathObject { * * @param name The input name. */ - private removeOutlinePath_(name: string) { + private removeOutlinePath(name: string) { this.outlines.get(name)?.parentNode?.removeChild(this.outlines.get(name)!); this.outlines.delete(name); } diff --git a/core/renderers/zelos/renderer.ts b/core/renderers/zelos/renderer.ts index cbef6c5a2..1f2114dcc 100644 --- a/core/renderers/zelos/renderer.ts +++ b/core/renderers/zelos/renderer.ts @@ -25,7 +25,6 @@ import {RenderInfo} from './info.js'; import {MarkerSvg} from './marker_svg.js'; import {PathObject} from './path_object.js'; - /** * The zelos renderer. This renderer emulates Scratch-style and MakeCode-style * rendering. @@ -69,9 +68,11 @@ export class Renderer extends BaseRenderer { * block. * @returns The drawer. */ - protected override makeDrawer_(block: BlockSvg, info: BaseRenderInfo): - Drawer { - return new Drawer(block, (info as RenderInfo)); + protected override makeDrawer_( + block: BlockSvg, + info: BaseRenderInfo + ): Drawer { + return new Drawer(block, info as RenderInfo); } /** @@ -81,8 +82,10 @@ export class Renderer extends BaseRenderer { * @param marker The marker. * @returns The object in charge of drawing the marker. */ - override makeMarkerDrawer(workspace: WorkspaceSvg, marker: Marker): - MarkerSvg { + override makeMarkerDrawer( + workspace: WorkspaceSvg, + marker: Marker + ): MarkerSvg { return new MarkerSvg(workspace, this.getConstants(), marker); } @@ -94,8 +97,7 @@ export class Renderer extends BaseRenderer { * @returns The renderer path object. */ override makePathObject(root: SVGElement, style: BlockStyle): PathObject { - return new PathObject( - root, style, (this.getConstants() as ConstantProvider)); + return new PathObject(root, style, this.getConstants() as ConstantProvider); } /** @@ -109,13 +111,17 @@ export class Renderer extends BaseRenderer { } override shouldHighlightConnection(conn: Connection) { - return conn.type !== ConnectionType.INPUT_VALUE && - conn.type !== ConnectionType.OUTPUT_VALUE; + return ( + conn.type !== ConnectionType.INPUT_VALUE && + conn.type !== ConnectionType.OUTPUT_VALUE + ); } override getConnectionPreviewMethod( - closest: RenderedConnection, local: RenderedConnection, - topBlock: BlockSvg) { + closest: RenderedConnection, + local: RenderedConnection, + topBlock: BlockSvg + ) { if (local.type === ConnectionType.OUTPUT_VALUE) { if (!closest.isConnected()) { return InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE; diff --git a/core/renderers/zelos/zelos.ts b/core/renderers/zelos/zelos.ts index edecc16f9..6b866e639 100644 --- a/core/renderers/zelos/zelos.ts +++ b/core/renderers/zelos/zelos.ts @@ -20,7 +20,6 @@ import {TopRow} from './measurables/top_row.js'; import {PathObject} from './path_object.js'; import {Renderer} from './renderer.js'; - export { BottomRow, ConstantProvider, diff --git a/core/scrollbar.ts b/core/scrollbar.ts index f01bccf5f..68aa433c1 100644 --- a/core/scrollbar.ts +++ b/core/scrollbar.ts @@ -21,7 +21,6 @@ import {Svg} from './utils/svg.js'; import * as svgMath from './utils/svg_math.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * A note on units: most of the numbers that are in CSS pixels are scaled if the * scrollbar is in a mutator. @@ -57,7 +56,7 @@ export class Scrollbar { private readonly margin: number; /** Previously recorded metrics from the workspace. */ - private oldHostMetrics: Metrics|null = null; + private oldHostMetrics: Metrics | null = null; /** * The ratio of handle position offset to workspace content displacement. @@ -144,10 +143,10 @@ export class Scrollbar { onMouseDownHandleWrapper_: browserEvents.Data; /** Handler for mouse move events during scrollbar drags. */ - onMouseUpWrapper_: browserEvents.Data|null = null; + onMouseUpWrapper_: browserEvents.Data | null = null; /** Handler for mouse up events to end scrollbar drags. */ - onMouseMoveWrapper_: browserEvents.Data|null = null; + onMouseMoveWrapper_: browserEvents.Data | null = null; /** * @param workspace Workspace to bind the scrollbar to. @@ -157,15 +156,21 @@ export class Scrollbar { * @param opt_margin The margin to apply to this scrollbar. */ constructor( - private workspace: WorkspaceSvg, private readonly horizontal: boolean, - opt_pair?: boolean, opt_class?: string, opt_margin?: number) { + private workspace: WorkspaceSvg, + private readonly horizontal: boolean, + opt_pair?: boolean, + opt_class?: string, + opt_margin?: number + ) { this.pair = opt_pair || false; - this.margin = opt_margin !== undefined ? opt_margin : - Scrollbar.DEFAULT_SCROLLBAR_MARGIN; + this.margin = + opt_margin !== undefined + ? opt_margin + : Scrollbar.DEFAULT_SCROLLBAR_MARGIN; let className = - 'blocklyScrollbar' + (this.horizontal ? 'Horizontal' : 'Vertical'); + 'blocklyScrollbar' + (this.horizontal ? 'Horizontal' : 'Vertical'); if (opt_class) { className += ' ' + opt_class; } @@ -183,17 +188,24 @@ export class Scrollbar { const group = dom.createSvgElement(Svg.G, {}, this.outerSvg); this.svgBackground = dom.createSvgElement( - Svg.RECT, {'class': 'blocklyScrollbarBackground'}, group); + Svg.RECT, + {'class': 'blocklyScrollbarBackground'}, + group + ); const radius = Math.floor((Scrollbar.scrollbarThickness - 5) / 2); this.svgHandle = dom.createSvgElement( - Svg.RECT, - {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius}, group); + Svg.RECT, + {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius}, + group + ); - this.workspace.getThemeManager().subscribe( - this.svgHandle, 'scrollbarColour', 'fill'); - this.workspace.getThemeManager().subscribe( - this.svgHandle, 'scrollbarOpacity', 'fill-opacity'); + this.workspace + .getThemeManager() + .subscribe(this.svgHandle, 'scrollbarColour', 'fill'); + this.workspace + .getThemeManager() + .subscribe(this.svgHandle, 'scrollbarOpacity', 'fill-opacity'); // Add everything to the DOM. dom.insertAfter(this.outerSvg, this.workspace.getParentSvg()); @@ -209,9 +221,17 @@ export class Scrollbar { } this.onMouseDownBarWrapper_ = browserEvents.conditionalBind( - this.svgBackground, 'pointerdown', this, this.onMouseDownBar); + this.svgBackground, + 'pointerdown', + this, + this.onMouseDownBar + ); this.onMouseDownHandleWrapper_ = browserEvents.conditionalBind( - this.svgHandle, 'pointerdown', this, this.onMouseDownHandle); + this.svgHandle, + 'pointerdown', + this, + this.onMouseDownHandle + ); } /** @@ -235,8 +255,6 @@ export class Scrollbar { /** * Dispose of this scrollbar. Remove DOM elements, event listeners, * and theme subscriptions. - * - * @suppress {checkTypes} */ dispose() { this.cleanUp(); @@ -272,7 +290,9 @@ export class Scrollbar { private setHandleLength(newLength: number) { this.handleLength = newLength; this.svgHandle.setAttribute( - this.lengthAttribute_, String(this.handleLength)); + this.lengthAttribute_, + String(this.handleLength) + ); } /** @@ -303,7 +323,9 @@ export class Scrollbar { setHandlePosition(newPosition: number) { this.handlePosition = newPosition; this.svgHandle.setAttribute( - this.positionAttribute_, String(this.handlePosition)); + this.positionAttribute_, + String(this.handlePosition) + ); } /** @@ -315,9 +337,13 @@ export class Scrollbar { private setScrollbarLength(newSize: number) { this.scrollbarLength = newSize; this.outerSvg.setAttribute( - this.lengthAttribute_, String(this.scrollbarLength)); + this.lengthAttribute_, + String(this.scrollbarLength) + ); this.svgBackground.setAttribute( - this.lengthAttribute_, String(this.scrollbarLength)); + this.lengthAttribute_, + String(this.scrollbarLength) + ); } /** @@ -356,8 +382,10 @@ export class Scrollbar { } } - if (this.oldHostMetrics && - Scrollbar.metricsAreEquivalent(hostMetrics, this.oldHostMetrics)) { + if ( + this.oldHostMetrics && + Scrollbar.metricsAreEquivalent(hostMetrics, this.oldHostMetrics) + ) { return; } @@ -385,10 +413,12 @@ export class Scrollbar { if (!this.oldHostMetrics) { return true; } - return this.oldHostMetrics.viewWidth !== hostMetrics.viewWidth || - this.oldHostMetrics.viewHeight !== hostMetrics.viewHeight || - this.oldHostMetrics.absoluteLeft !== hostMetrics.absoluteLeft || - this.oldHostMetrics.absoluteTop !== hostMetrics.absoluteTop; + return ( + this.oldHostMetrics.viewWidth !== hostMetrics.viewWidth || + this.oldHostMetrics.viewHeight !== hostMetrics.viewHeight || + this.oldHostMetrics.absoluteLeft !== hostMetrics.absoluteLeft || + this.oldHostMetrics.absoluteTop !== hostMetrics.absoluteTop + ); } /** @@ -428,8 +458,11 @@ export class Scrollbar { // Horizontal toolbar should always be just above the bottom of the // workspace. - const yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight - - Scrollbar.scrollbarThickness - this.margin; + const yCoordinate = + hostMetrics.absoluteTop + + hostMetrics.viewHeight - + Scrollbar.scrollbarThickness - + this.margin; this.setPosition(xCoordinate, yCoordinate); // If the view has been resized, a content resize will also be necessary. @@ -464,7 +497,7 @@ export class Scrollbar { // Resize the handle. let handleLength = - this.scrollbarLength * hostMetrics.viewWidth / hostMetrics.scrollWidth; + (this.scrollbarLength * hostMetrics.viewWidth) / hostMetrics.scrollWidth; handleLength = this.constrainHandleLength(handleLength); this.setHandleLength(handleLength); @@ -521,10 +554,12 @@ export class Scrollbar { } this.setScrollbarLength(Math.max(0, viewSize)); - const xCoordinate = this.workspace.RTL ? - hostMetrics.absoluteLeft + this.margin : - hostMetrics.absoluteLeft + hostMetrics.viewWidth - - Scrollbar.scrollbarThickness - this.margin; + const xCoordinate = this.workspace.RTL + ? hostMetrics.absoluteLeft + this.margin + : hostMetrics.absoluteLeft + + hostMetrics.viewWidth - + Scrollbar.scrollbarThickness - + this.margin; const yCoordinate = hostMetrics.absoluteTop + this.margin; this.setPosition(xCoordinate, yCoordinate); @@ -560,8 +595,9 @@ export class Scrollbar { } // Resize the handle. - let handleLength = this.scrollbarLength * hostMetrics.viewHeight / - hostMetrics.scrollHeight; + let handleLength = + (this.scrollbarLength * hostMetrics.viewHeight) / + hostMetrics.scrollHeight; handleLength = this.constrainHandleLength(handleLength); this.setHandleLength(handleLength); @@ -663,7 +699,7 @@ export class Scrollbar { */ private onMouseDownBar(e: MouseEvent) { this.workspace.markFocused(); - Touch.clearTouchIdentifier(); // This is really a click. + Touch.clearTouchIdentifier(); // This is really a click. this.cleanUp(); if (browserEvents.isRightButton(e)) { // Right-click. @@ -672,7 +708,10 @@ export class Scrollbar { return; } const mouseXY = browserEvents.mouseToSvg( - e, this.workspace.getParentSvg(), this.workspace.getInverseScreenCTM()); + e, + this.workspace.getParentSvg(), + this.workspace.getInverseScreenCTM() + ); const mouseLocation = this.horizontal ? mouseXY.x : mouseXY.y; const handleXY = svgMath.getInjectionDivXY(this.svgHandle); @@ -713,17 +752,20 @@ export class Scrollbar { // Look up the current translation and record it. this.startDragHandle = this.handlePosition; - // Tell the workspace to setup its drag surface since it is about to move. - // onMouseMoveHandle will call onScroll which actually tells the workspace - // to move. - this.workspace.setupDragSurface(); - // Record the current mouse position. this.startDragMouse = this.horizontal ? e.clientX : e.clientY; this.onMouseUpWrapper_ = browserEvents.conditionalBind( - document, 'pointerup', this, this.onMouseUpHandle); + document, + 'pointerup', + this, + this.onMouseUpHandle + ); this.onMouseMoveWrapper_ = browserEvents.conditionalBind( - document, 'pointermove', this, this.onMouseMoveHandle); + document, + 'pointermove', + this, + this.onMouseMoveHandle + ); e.stopPropagation(); e.preventDefault(); } @@ -744,8 +786,6 @@ export class Scrollbar { /** Release the scrollbar handle and reset state accordingly. */ private onMouseUpHandle() { - // Tell the workspace to clean up now that the workspace is done moving. - this.workspace.resetDragSurface(); Touch.clearTouchIdentifier(); this.cleanUp(); } @@ -827,17 +867,21 @@ export class Scrollbar { * workspace. * @returns Whether the two sets of metrics are equivalent. */ - private static metricsAreEquivalent(first: Metrics, second: Metrics): - boolean { - return first.viewWidth === second.viewWidth && - first.viewHeight === second.viewHeight && - first.viewLeft === second.viewLeft && - first.viewTop === second.viewTop && - first.absoluteTop === second.absoluteTop && - first.absoluteLeft === second.absoluteLeft && - first.scrollWidth === second.scrollWidth && - first.scrollHeight === second.scrollHeight && - first.scrollLeft === second.scrollLeft && - first.scrollTop === second.scrollTop; + private static metricsAreEquivalent( + first: Metrics, + second: Metrics + ): boolean { + return ( + first.viewWidth === second.viewWidth && + first.viewHeight === second.viewHeight && + first.viewLeft === second.viewLeft && + first.viewTop === second.viewTop && + first.absoluteTop === second.absoluteTop && + first.absoluteLeft === second.absoluteLeft && + first.scrollWidth === second.scrollWidth && + first.scrollHeight === second.scrollHeight && + first.scrollLeft === second.scrollLeft && + first.scrollTop === second.scrollTop + ); } } diff --git a/core/scrollbar_pair.ts b/core/scrollbar_pair.ts index 35e3a0cd3..39e7bea8b 100644 --- a/core/scrollbar_pair.ts +++ b/core/scrollbar_pair.ts @@ -19,17 +19,16 @@ import type {Metrics} from './utils/metrics.js'; import {Svg} from './utils/svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for a pair of scrollbars. Horizontal and vertical. */ export class ScrollbarPair { - hScroll: Scrollbar|null = null; - vScroll: Scrollbar|null = null; - corner_: SVGRectElement|null = null; + hScroll: Scrollbar | null = null; + vScroll: Scrollbar | null = null; + corner_: SVGRectElement | null = null; /** Previously recorded metrics from the workspace. */ - private oldHostMetrics_: Metrics|null = null; + private oldHostMetrics_: Metrics | null = null; /** * @param workspace Workspace to bind the scrollbars to. @@ -40,19 +39,33 @@ export class ScrollbarPair { * @param opt_margin The margin to apply to these scrollbars. */ constructor( - private workspace: WorkspaceSvg, addHorizontal?: boolean, - addVertical?: boolean, opt_class?: string, opt_margin?: number) { + private workspace: WorkspaceSvg, + addHorizontal?: boolean, + addVertical?: boolean, + opt_class?: string, + opt_margin?: number + ) { addHorizontal = addHorizontal === undefined ? true : addHorizontal; addVertical = addVertical === undefined ? true : addVertical; const isPair = addHorizontal && addVertical; if (addHorizontal) { - this.hScroll = - new Scrollbar(workspace, true, isPair, opt_class, opt_margin); + this.hScroll = new Scrollbar( + workspace, + true, + isPair, + opt_class, + opt_margin + ); } if (addVertical) { - this.vScroll = - new Scrollbar(workspace, false, isPair, opt_class, opt_margin); + this.vScroll = new Scrollbar( + workspace, + false, + isPair, + opt_class, + opt_margin + ); } if (isPair) { @@ -68,8 +81,6 @@ export class ScrollbarPair { /** * Dispose of this pair of scrollbars. * Unlink from all DOM elements to prevent memory leaks. - * - * @suppress {checkTypes} */ dispose() { dom.removeNode(this.corner_); @@ -100,26 +111,32 @@ export class ScrollbarPair { // Only change the scrollbars if there has been a change in metrics. let resizeH = false; let resizeV = false; - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || - this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || - this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop || - this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) { + if ( + !this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || + this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop || + this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft + ) { // The window has been resized or repositioned. resizeH = true; resizeV = true; } else { // Has the content been resized or moved? - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.scrollWidth !== hostMetrics.scrollWidth || - this.oldHostMetrics_.viewLeft !== hostMetrics.viewLeft || - this.oldHostMetrics_.scrollLeft !== hostMetrics.scrollLeft) { + if ( + !this.oldHostMetrics_ || + this.oldHostMetrics_.scrollWidth !== hostMetrics.scrollWidth || + this.oldHostMetrics_.viewLeft !== hostMetrics.viewLeft || + this.oldHostMetrics_.scrollLeft !== hostMetrics.scrollLeft + ) { resizeH = true; } - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.scrollHeight !== hostMetrics.scrollHeight || - this.oldHostMetrics_.viewTop !== hostMetrics.viewTop || - this.oldHostMetrics_.scrollTop !== hostMetrics.scrollTop) { + if ( + !this.oldHostMetrics_ || + this.oldHostMetrics_.scrollHeight !== hostMetrics.scrollHeight || + this.oldHostMetrics_.viewTop !== hostMetrics.viewTop || + this.oldHostMetrics_.scrollTop !== hostMetrics.scrollTop + ) { resizeV = true; } } @@ -141,14 +158,18 @@ export class ScrollbarPair { if (this.hScroll && this.vScroll) { // Reposition the corner square. - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || - this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft) { + if ( + !this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth !== hostMetrics.viewWidth || + this.oldHostMetrics_.absoluteLeft !== hostMetrics.absoluteLeft + ) { this.corner_?.setAttribute('x', String(this.vScroll.position.x)); } - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || - this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop) { + if ( + !this.oldHostMetrics_ || + this.oldHostMetrics_.viewHeight !== hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop !== hostMetrics.absoluteTop + ) { this.corner_?.setAttribute('y', String(this.hScroll.position.y)); } } @@ -219,7 +240,7 @@ export class ScrollbarPair { if (updateMetrics || updateMetrics === undefined) { // Update metrics. - const xyRatio: {x?: number, y?: number} = {}; + const xyRatio: {x?: number; y?: number} = {}; if (this.hScroll) { xyRatio.x = this.hScroll.getRatio_(); } diff --git a/core/serialization/blocks.ts b/core/serialization/blocks.ts index e9bb0bd0c..23de10dba 100644 --- a/core/serialization/blocks.ts +++ b/core/serialization/blocks.ts @@ -11,18 +11,24 @@ import type {Block} from '../block.js'; import type {BlockSvg} from '../block_svg.js'; import type {Connection} from '../connection.js'; import * as eventUtils from '../events/utils.js'; -import {inputTypes} from '../input_types.js'; +import {inputTypes} from '../inputs/input_types.js'; +import {isSerializable} from '../interfaces/i_serializable.js'; import type {ISerializer} from '../interfaces/i_serializer.js'; -import {Size} from '../utils/size.js'; +import * as registry from '../registry.js'; import * as utilsXml from '../utils/xml.js'; import type {Workspace} from '../workspace.js'; import * as Xml from '../xml.js'; -import {BadConnectionCheck, MissingBlockType, MissingConnection, RealChildOfShadow} from './exceptions.js'; +import { + BadConnectionCheck, + MissingBlockType, + MissingConnection, + RealChildOfShadow, + UnregisteredIcon, +} from './exceptions.js'; import * as priorities from './priorities.js'; import * as serializationRegistry from './registry.js'; - // TODO(#5160): Remove this once lint is fixed. /* eslint-disable no-use-before-define */ @@ -30,8 +36,8 @@ import * as serializationRegistry from './registry.js'; * Represents the state of a connection. */ export interface ConnectionState { - shadow: State|undefined; - block: State|undefined; + shadow?: State; + block?: State; } /** @@ -73,17 +79,20 @@ export interface State { * @returns The serialized state of the block, or null if the block could not be * serialied (eg it was an insertion marker). */ -export function save(block: Block, { - addCoordinates = false, - addInputBlocks = true, - addNextBlocks = true, - doFullSerialization = true, -}: { - addCoordinates?: boolean, - addInputBlocks?: boolean, - addNextBlocks?: boolean, - doFullSerialization?: boolean -} = {}): State|null { +export function save( + block: Block, + { + addCoordinates = false, + addInputBlocks = true, + addNextBlocks = true, + doFullSerialization = true, + }: { + addCoordinates?: boolean; + addInputBlocks?: boolean; + addNextBlocks?: boolean; + doFullSerialization?: boolean; + } = {} +): State | null { if (block.isInsertionMarker()) { return null; } @@ -106,7 +115,7 @@ export function save(block: Block, { saveExtraState(block, state as AnyDuringMigration); // AnyDuringMigration because: Argument of type '{ type: string; id: string; // }' is not assignable to parameter of type 'State'. - saveIcons(block, state as AnyDuringMigration); + saveIcons(block, state as AnyDuringMigration, doFullSerialization); // AnyDuringMigration because: Argument of type '{ type: string; id: string; // }' is not assignable to parameter of type 'State'. saveFields(block, state as AnyDuringMigration, doFullSerialization); @@ -149,8 +158,10 @@ function saveAttributes(block: Block, state: State) { if (!block.isOwnEditable()) { state['editable'] = false; } - if (block.inputsInline !== undefined && - block.inputsInline !== block.inputsInlineDefault) { + if ( + block.inputsInline !== undefined && + block.inputsInline !== block.inputsInlineDefault + ) { state['inline'] = block.inputsInline; } // Data is a nullable string, so we don't need to worry about falsy values. @@ -186,10 +197,10 @@ function saveExtraState(block: Block, state: State) { } else if (block.mutationToDom) { const extraState = block.mutationToDom(); if (extraState !== null) { - state['extraState'] = - Xml.domToText(extraState) - .replace( - ' xmlns="https://developers.google.com/blockly/xml"', ''); + state['extraState'] = Xml.domToText(extraState).replace( + ' xmlns="https://developers.google.com/blockly/xml"', + '' + ); } } } @@ -199,18 +210,20 @@ function saveExtraState(block: Block, state: State) { * * @param block The block to serialize the icon state of. * @param state The state object to append to. + * @param doFullSerialization Whether or not to serialize the full state of the + * icon (rather than possibly saving a reference to some state). */ -function saveIcons(block: Block, state: State) { - // TODO(#2105): Remove this logic and put it in the icon. - if (block.getCommentText()) { - state['icons'] = { - 'comment': { - 'text': block.getCommentText(), - 'pinned': block.commentModel.pinned, - 'height': Math.round(block.commentModel.size.height), - 'width': Math.round(block.commentModel.size.width), - }, - }; +function saveIcons(block: Block, state: State, doFullSerialization: boolean) { + const icons = Object.create(null); + for (const icon of block.getIcons()) { + if (isSerializable(icon)) { + const state = icon.saveState(doFullSerialization); + if (state) icons[icon.getType().toString()] = state; + } + } + + if (Object.keys(icons).length) { + state['icons'] = icons; } } @@ -247,15 +260,18 @@ function saveFields(block: Block, state: State, doFullSerialization: boolean) { * @param doFullSerialization Whether or not to do full serialization. */ function saveInputBlocks( - block: Block, state: State, doFullSerialization: boolean) { + block: Block, + state: State, + doFullSerialization: boolean +) { const inputs = Object.create(null); for (let i = 0; i < block.inputList.length; i++) { const input = block.inputList[i]; - if (input.type === inputTypes.DUMMY) { - continue; - } - const connectionState = - saveConnection(input.connection as Connection, doFullSerialization); + if (!input.connection) continue; + const connectionState = saveConnection( + input.connection as Connection, + doFullSerialization + ); if (connectionState) { inputs[input.name] = connectionState; } @@ -275,12 +291,17 @@ function saveInputBlocks( * @param doFullSerialization Whether or not to do full serialization. */ function saveNextBlocks( - block: Block, state: State, doFullSerialization: boolean) { + block: Block, + state: State, + doFullSerialization: boolean +) { if (!block.nextConnection) { return; } - const connectionState = - saveConnection(block.nextConnection, doFullSerialization); + const connectionState = saveConnection( + block.nextConnection, + doFullSerialization + ); if (connectionState) { state['next'] = connectionState; } @@ -295,8 +316,10 @@ function saveNextBlocks( * connected real block. * @param doFullSerialization Whether or not to do full serialization. */ -function saveConnection(connection: Connection, doFullSerialization: boolean): - ConnectionState|null { +function saveConnection( + connection: Connection, + doFullSerialization: boolean +): ConnectionState | null { const shadow = connection.getShadowState(true); const child = connection.targetBlock(); if (!shadow && !child) { @@ -322,8 +345,10 @@ function saveConnection(connection: Connection, doFullSerialization: boolean): * @returns The block that was just loaded. */ export function append( - state: State, workspace: Workspace, - {recordUndo = false}: {recordUndo?: boolean} = {}): Block { + state: State, + workspace: Workspace, + {recordUndo = false}: {recordUndo?: boolean} = {} +): Block { return appendInternal(state, workspace, {recordUndo}); } @@ -345,12 +370,18 @@ export function append( * @internal */ export function appendInternal( - state: State, workspace: Workspace, - {parentConnection = undefined, isShadow = false, recordUndo = false}: { - parentConnection?: Connection, - isShadow?: boolean, - recordUndo?: boolean - } = {}): Block { + state: State, + workspace: Workspace, + { + parentConnection = undefined, + isShadow = false, + recordUndo = false, + }: { + parentConnection?: Connection; + isShadow?: boolean; + recordUndo?: boolean; + } = {} +): Block { const prevRecordUndo = eventUtils.getRecordUndo(); eventUtils.setRecordUndo(recordUndo); const existingGroup = eventUtils.getGroup(); @@ -395,9 +426,13 @@ export function appendInternal( * @returns The block that was just appended. */ function appendPrivate( - state: State, workspace: Workspace, - {parentConnection = undefined, isShadow = false}: - {parentConnection?: Connection, isShadow?: boolean} = {}): Block { + state: State, + workspace: Workspace, + { + parentConnection = undefined, + isShadow = false, + }: {parentConnection?: Connection; isShadow?: boolean} = {} +): Block { if (!state['type']) { throw new MissingBlockType(state); } @@ -490,7 +525,10 @@ function loadExtraState(block: Block, state: State) { * @param state The state which defines the given block */ function tryToConnectParent( - parentConnection: Connection|undefined, child: Block, state: State) { + parentConnection: Connection | undefined, + child: Block, + state: State +) { if (!parentConnection) { return; } @@ -507,7 +545,8 @@ function tryToConnectParent( throw new MissingConnection('output', child, state); } connected = parentConnection.connect(childConnection); - } else { // Statement type. + } else { + // Statement type. childConnection = child.previousConnection; if (!childConnection) { throw new MissingConnection('previous', child, state); @@ -518,13 +557,17 @@ function tryToConnectParent( if (!connected) { const checker = child.workspace.connectionChecker; throw new BadConnectionCheck( - checker.getErrorMessage( - checker.canConnectWithReason( - childConnection, parentConnection, false), - childConnection, parentConnection), - parentConnection.type === inputTypes.VALUE ? 'output connection' : - 'previous connection', - child, state); + checker.getErrorMessage( + checker.canConnectWithReason(childConnection, parentConnection, false), + childConnection, + parentConnection + ), + parentConnection.type === inputTypes.VALUE + ? 'output connection' + : 'previous connection', + child, + state + ); } } @@ -536,25 +579,23 @@ function tryToConnectParent( * @param state The state object to reference. */ function loadIcons(block: Block, state: State) { - if (!state['icons']) { - return; - } - // TODO(#2105): Remove this logic and put it in the icon. - const comment = state['icons']['comment']; - if (comment) { - block.setCommentText(comment['text']); - // Load if saved. (Cleaned unnecessary attributes when in the trashcan.) - if ('pinned' in comment) { - block.commentModel.pinned = comment['pinned']; - } - if ('width' in comment && 'height' in comment) { - block.commentModel.size = new Size(comment['width'], comment['height']); - } - if (comment['pinned'] && block.rendered && !block.isInFlyout) { - // Give the block a chance to be positioned and rendered before showing. - const blockSvg = block as BlockSvg; - setTimeout(() => blockSvg.getCommentIcon()!.setVisible(true), 1); + if (!state['icons']) return; + + const iconTypes = Object.keys(state['icons']); + for (const iconType of iconTypes) { + const iconState = state['icons'][iconType]; + let icon = block.getIcon(iconType); + if (!icon) { + const constructor = registry.getClass( + registry.Type.ICON, + iconType, + false + ); + if (!constructor) throw new UnregisteredIcon(iconType, block, state); + icon = new constructor(block); + block.addIcon(icon); } + if (isSerializable(icon)) icon.loadState(iconState); } } @@ -575,7 +616,8 @@ function loadFields(block: Block, state: State) { const field = block.getField(fieldName); if (!field) { console.warn( - `Ignoring non-existant field ${fieldName} in block ${block.type}`); + `Ignoring non-existant field ${fieldName} in block ${block.type}` + ); continue; } field.loadState(fieldState); @@ -629,14 +671,18 @@ function loadNextBlocks(block: Block, state: State) { * shadow block, or any connected real block. */ function loadConnection( - connection: Connection, connectionState: ConnectionState) { + connection: Connection, + connectionState: ConnectionState +) { if (connectionState['shadow']) { connection.setShadowState(connectionState['shadow']); } if (connectionState['block']) { appendPrivate( - connectionState['block'], connection.getSourceBlock().workspace, - {parentConnection: connection}); + connectionState['block'], + connection.getSourceBlock().workspace, + {parentConnection: connection} + ); } } @@ -658,9 +704,8 @@ function initBlock(block: Block, rendered: boolean) { blockSvg.render(false); // fixes #6076 JSO deserialization doesn't // set .iconXY_ property so here it will be set - const icons = blockSvg.getIcons(); - for (let i = 0; i < icons.length; i++) { - icons[i].computeIconLocation(); + for (const icon of blockSvg.getIcons()) { + icon.onLocationChange(blockSvg.getRelativeToSurfaceXY()); } } else { block.initModel(); @@ -689,18 +734,22 @@ export class BlockSerializer implements ISerializer { * @returns The state of the workspace's blocks, or null if there are no * blocks. */ - save(workspace: Workspace): {languageVersion: number, blocks: State[]}|null { + save( + workspace: Workspace + ): {languageVersion: number; blocks: State[]} | null { const blockStates = []; for (const block of workspace.getTopBlocks(false)) { - const state = - saveBlock(block, {addCoordinates: true, doFullSerialization: false}); + const state = saveBlock(block, { + addCoordinates: true, + doFullSerialization: false, + }); if (state) { blockStates.push(state); } } if (blockStates.length) { return { - 'languageVersion': 0, // Currently unused. + 'languageVersion': 0, // Currently unused. 'blocks': blockStates, }; } @@ -715,7 +764,9 @@ export class BlockSerializer implements ISerializer { * @param workspace The workspace to deserialize into. */ load( - state: {languageVersion: number, blocks: State[]}, workspace: Workspace) { + state: {languageVersion: number; blocks: State[]}, + workspace: Workspace + ) { const blockStates = state['blocks']; for (const state of blockStates) { append(state, workspace, {recordUndo: eventUtils.getRecordUndo()}); diff --git a/core/serialization/exceptions.ts b/core/serialization/exceptions.ts index ff720f1bd..da454362f 100644 --- a/core/serialization/exceptions.ts +++ b/core/serialization/exceptions.ts @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.serialization.exceptions'); import type {Block} from '../block.js'; import type {State} from './blocks.js'; - export class DeserializationError extends Error {} /** @@ -60,8 +59,11 @@ export class BadConnectionCheck extends DeserializationError { * @internal */ constructor( - reason: string, childConnection: string, public childBlock: Block, - public childState: State) { + reason: string, + childConnection: string, + public childBlock: Block, + public childState: State + ) { super(`The block ${childBlock.toDevString()} could not connect its ${childConnection} to its parent, because: ${reason}`); } @@ -84,3 +86,20 @@ block. It is an invariant of Blockly that shadow blocks only have shadow children`); } } + +export class UnregisteredIcon extends DeserializationError { + /** + * @param iconType The type of the unregistered icon we are attempting to + * deserialize. + * @param block The block we are attempting to add the unregistered icon to. + * @param state The state object representing the block. + */ + constructor(iconType: string, public block: Block, public state: State) { + super( + `Cannot add an icon of type '${iconType}' to the block ` + + `${block.toDevString()}, because there is no icon registered with ` + + `type '${iconType}'. Make sure that all of your icons have been ` + + `registered.` + ); + } +} diff --git a/core/serialization/priorities.ts b/core/serialization/priorities.ts index 27eda2e8a..6965a445a 100644 --- a/core/serialization/priorities.ts +++ b/core/serialization/priorities.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.serialization.priorities'); - /** * The priority for deserializing variables. */ diff --git a/core/serialization/procedures.ts b/core/serialization/procedures.ts index 06ec428df..cb17fe606 100644 --- a/core/serialization/procedures.ts +++ b/core/serialization/procedures.ts @@ -10,21 +10,24 @@ import type {ISerializer} from '../interfaces/i_serializer.js'; import * as priorities from './priorities.js'; import type {Workspace} from '../workspace.js'; - /** * Representation of a procedure data model. */ export interface State { // TODO: This should also handle enabled. - id: string, name: string, returnTypes: string[]|null, - parameters?: ParameterState[], + id: string; + name: string; + returnTypes: string[] | null; + parameters?: ParameterState[]; } /** * Representation of a parameter data model. */ export interface ParameterState { - id: string, name: string, types?: string[], + id: string; + name: string; + types?: string[]; } /** @@ -34,8 +37,11 @@ export interface ParameterState { * https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics * for what is going on with this. */ -type ProcedureModelConstructor = - new (workspace: Workspace, name: string, id: string) => ProcedureModel; +type ProcedureModelConstructor = new ( + workspace: Workspace, + name: string, + id: string +) => ProcedureModel; /** * A newable signature for an IParameterModel. @@ -44,9 +50,11 @@ type ProcedureModelConstructor = * https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics * for what is going on with this. */ -type ParameterModelConstructor = - new (workspace: Workspace, name: string, id: string) => ParameterModel; - +type ParameterModelConstructor = new ( + workspace: Workspace, + name: string, + id: string +) => ParameterModel; /** * Serializes the given IProcedureModel to JSON. @@ -84,18 +92,26 @@ export function saveParameter(param: IParameterModel): ParameterState { * * @internal */ -export function -loadProcedure( - procedureModelClass: ProcedureModelConstructor, - parameterModelClass: ParameterModelConstructor, - state: State, workspace: Workspace): ProcedureModel { - const proc = new procedureModelClass(workspace, state.name, state.id) - .setReturnTypes(state.returnTypes); +export function loadProcedure< + ProcedureModel extends IProcedureModel, + ParameterModel extends IParameterModel +>( + procedureModelClass: ProcedureModelConstructor, + parameterModelClass: ParameterModelConstructor, + state: State, + workspace: Workspace +): ProcedureModel { + const proc = new procedureModelClass( + workspace, + state.name, + state.id + ).setReturnTypes(state.returnTypes); if (!state.parameters) return proc; for (const [index, param] of state.parameters.entries()) { proc.insertParameter( - loadParameter(parameterModelClass, param, workspace), index); + loadParameter(parameterModelClass, param, workspace), + index + ); } return proc; } @@ -106,17 +122,21 @@ loadProcedure( - parameterModelClass: ParameterModelConstructor, - state: ParameterState, workspace: Workspace): ParameterModel { + parameterModelClass: ParameterModelConstructor, + state: ParameterState, + workspace: Workspace +): ParameterModel { const model = new parameterModelClass(workspace, state.name, state.id); if (state.types) model.setTypes(state.types); return model; } /** Serializer for saving and loading procedure state. */ -export class ProcedureSerializer implements ISerializer { +export class ProcedureSerializer< + ProcedureModel extends IProcedureModel, + ParameterModel extends IParameterModel +> implements ISerializer +{ public priority = priorities.PROCEDURES; /** @@ -131,15 +151,16 @@ export class ProcedureSerializer, - private readonly parameterModelClass: - ParameterModelConstructor) {} + private readonly procedureModelClass: ProcedureModelConstructor, + private readonly parameterModelClass: ParameterModelConstructor + ) {} /** Serializes the procedure models of the given workspace. */ - save(workspace: Workspace): State[]|null { - const save = workspace.getProcedureMap().getProcedures().map( - (proc) => saveProcedure(proc)); + save(workspace: Workspace): State[] | null { + const save = workspace + .getProcedureMap() + .getProcedures() + .map((proc) => saveProcedure(proc)); return save.length ? save : null; } @@ -150,9 +171,14 @@ export class ProcedureSerializer (b[1] as ISerializer)!.priority - - (a[1] as ISerializer)!.priority); + const deserializers = Object.entries(serializerMap).sort( + (a, b) => (b[1] as ISerializer)!.priority - (a[1] as ISerializer)!.priority + ); const prevRecordUndo = eventUtils.getRecordUndo(); eventUtils.setRecordUndo(recordUndo); diff --git a/core/shortcut_items.ts b/core/shortcut_items.ts index d07d1fbe5..efe876649 100644 --- a/core/shortcut_items.ts +++ b/core/shortcut_items.ts @@ -16,7 +16,6 @@ import {KeyboardShortcut, ShortcutRegistry} from './shortcut_registry.js'; import {KeyCodes} from './utils/keycodes.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Object holding the names of the default shortcut items. */ @@ -58,8 +57,11 @@ export function registerDelete() { name: names.DELETE, preconditionFn(workspace) { const selected = common.getSelected(); - return !workspace.options.readOnly && selected != null && - selected.isDeletable(); + return ( + !workspace.options.readOnly && + selected != null && + selected.isDeletable() + ); }, callback(workspace, e) { // Delete or backspace. @@ -83,19 +85,27 @@ export function registerDelete() { * Keyboard shortcut to copy a block on ctrl+c, cmd+c, or alt+c. */ export function registerCopy() { - const ctrlC = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.C, [KeyCodes.CTRL]); - const altC = - ShortcutRegistry.registry.createSerializedKey(KeyCodes.C, [KeyCodes.ALT]); - const metaC = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.C, [KeyCodes.META]); + const ctrlC = ShortcutRegistry.registry.createSerializedKey(KeyCodes.C, [ + KeyCodes.CTRL, + ]); + const altC = ShortcutRegistry.registry.createSerializedKey(KeyCodes.C, [ + KeyCodes.ALT, + ]); + const metaC = ShortcutRegistry.registry.createSerializedKey(KeyCodes.C, [ + KeyCodes.META, + ]); const copyShortcut: KeyboardShortcut = { name: names.COPY, preconditionFn(workspace) { const selected = common.getSelected(); - return !workspace.options.readOnly && !Gesture.inProgress() && - selected != null && selected.isDeletable() && selected.isMovable(); + return ( + !workspace.options.readOnly && + !Gesture.inProgress() && + selected != null && + selected.isDeletable() && + selected.isMovable() + ); }, callback(workspace, e) { // Prevent the default copy behavior, which may beep or otherwise indicate @@ -116,21 +126,29 @@ export function registerCopy() { * Keyboard shortcut to copy and delete a block on ctrl+x, cmd+x, or alt+x. */ export function registerCut() { - const ctrlX = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.X, [KeyCodes.CTRL]); - const altX = - ShortcutRegistry.registry.createSerializedKey(KeyCodes.X, [KeyCodes.ALT]); - const metaX = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.X, [KeyCodes.META]); + const ctrlX = ShortcutRegistry.registry.createSerializedKey(KeyCodes.X, [ + KeyCodes.CTRL, + ]); + const altX = ShortcutRegistry.registry.createSerializedKey(KeyCodes.X, [ + KeyCodes.ALT, + ]); + const metaX = ShortcutRegistry.registry.createSerializedKey(KeyCodes.X, [ + KeyCodes.META, + ]); const cutShortcut: KeyboardShortcut = { name: names.CUT, preconditionFn(workspace) { const selected = common.getSelected(); - return !workspace.options.readOnly && !Gesture.inProgress() && - selected != null && selected instanceof BlockSvg && - selected.isDeletable() && selected.isMovable() && - !selected.workspace!.isFlyout; + return ( + !workspace.options.readOnly && + !Gesture.inProgress() && + selected != null && + selected instanceof BlockSvg && + selected.isDeletable() && + selected.isMovable() && + !selected.workspace!.isFlyout + ); }, callback() { const selected = common.getSelected(); @@ -152,12 +170,15 @@ export function registerCut() { * Keyboard shortcut to paste a block on ctrl+v, cmd+v, or alt+v. */ export function registerPaste() { - const ctrlV = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.V, [KeyCodes.CTRL]); - const altV = - ShortcutRegistry.registry.createSerializedKey(KeyCodes.V, [KeyCodes.ALT]); - const metaV = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.V, [KeyCodes.META]); + const ctrlV = ShortcutRegistry.registry.createSerializedKey(KeyCodes.V, [ + KeyCodes.CTRL, + ]); + const altV = ShortcutRegistry.registry.createSerializedKey(KeyCodes.V, [ + KeyCodes.ALT, + ]); + const metaV = ShortcutRegistry.registry.createSerializedKey(KeyCodes.V, [ + KeyCodes.META, + ]); const pasteShortcut: KeyboardShortcut = { name: names.PASTE, @@ -165,7 +186,7 @@ export function registerPaste() { return !workspace.options.readOnly && !Gesture.inProgress(); }, callback() { - return !!(clipboard.paste()); + return !!clipboard.paste(); }, keyCodes: [ctrlV, altV, metaV], }; @@ -177,12 +198,15 @@ export function registerPaste() { * Keyboard shortcut to undo the previous action on ctrl+z, cmd+z, or alt+z. */ export function registerUndo() { - const ctrlZ = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.Z, [KeyCodes.CTRL]); - const altZ = - ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [KeyCodes.ALT]); - const metaZ = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.Z, [KeyCodes.META]); + const ctrlZ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.CTRL, + ]); + const altZ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.ALT, + ]); + const metaZ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.META, + ]); const undoShortcut: KeyboardShortcut = { name: names.UNDO, @@ -205,15 +229,22 @@ export function registerUndo() { * or alt+shift+z. */ export function registerRedo() { - const ctrlShiftZ = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.Z, [KeyCodes.SHIFT, KeyCodes.CTRL]); - const altShiftZ = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.Z, [KeyCodes.SHIFT, KeyCodes.ALT]); - const metaShiftZ = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.Z, [KeyCodes.SHIFT, KeyCodes.META]); + const ctrlShiftZ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.SHIFT, + KeyCodes.CTRL, + ]); + const altShiftZ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.SHIFT, + KeyCodes.ALT, + ]); + const metaShiftZ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.SHIFT, + KeyCodes.META, + ]); // Ctrl-y is redo in Windows. Command-y is never valid on Macs. - const ctrlY = ShortcutRegistry.registry.createSerializedKey( - KeyCodes.Y, [KeyCodes.CTRL]); + const ctrlY = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Y, [ + KeyCodes.CTRL, + ]); const redoShortcut: KeyboardShortcut = { name: names.REDO, diff --git a/core/shortcut_registry.ts b/core/shortcut_registry.ts index 8f03c5ca8..cb6263fea 100644 --- a/core/shortcut_registry.ts +++ b/core/shortcut_registry.ts @@ -17,7 +17,6 @@ import {KeyCodes} from './utils/keycodes.js'; import * as object from './utils/object.js'; import type {Workspace} from './workspace.js'; - /** * Class for the registry of keyboard shortcuts. This is intended to be a * singleton. You should not create a new instance, and only access this class @@ -62,7 +61,10 @@ export class ShortcutRegistry { if (keyCodes && keyCodes.length > 0) { for (let i = 0; i < keyCodes.length; i++) { this.addKeyMapping( - keyCodes[i], shortcut.name, !!shortcut.allowCollision); + keyCodes[i], + shortcut.name, + !!shortcut.allowCollision + ); } } } @@ -101,13 +103,16 @@ export class ShortcutRegistry { * @throws {Error} if the given key code is already mapped to a shortcut. */ addKeyMapping( - keyCode: string|number|KeyCodes, shortcutName: string, - opt_allowCollision?: boolean) { + keyCode: string | number | KeyCodes, + shortcutName: string, + opt_allowCollision?: boolean + ) { keyCode = `${keyCode}`; const shortcutNames = this.keyMap.get(keyCode); if (shortcutNames && !opt_allowCollision) { - throw new Error(`Shortcut named "${ - shortcutName}" collides with shortcuts "${shortcutNames}"`); + throw new Error( + `Shortcut named "${shortcutName}" collides with shortcuts "${shortcutNames}"` + ); } else if (shortcutNames && opt_allowCollision) { shortcutNames.unshift(shortcutName); } else { @@ -127,14 +132,18 @@ export class ShortcutRegistry { * remove. * @returns True if a key mapping was removed, false otherwise. */ - removeKeyMapping(keyCode: string, shortcutName: string, opt_quiet?: boolean): - boolean { + removeKeyMapping( + keyCode: string, + shortcutName: string, + opt_quiet?: boolean + ): boolean { const shortcutNames = this.keyMap.get(keyCode); if (!shortcutNames) { if (!opt_quiet) { - console.warn(`No keyboard shortcut named "${ - shortcutName}" registered with key code "${keyCode}"`); + console.warn( + `No keyboard shortcut named "${shortcutName}" registered with key code "${keyCode}"` + ); } return false; } @@ -148,8 +157,9 @@ export class ShortcutRegistry { return true; } if (!opt_quiet) { - console.warn(`No keyboard shortcut named "${ - shortcutName}" registered with key code "${keyCode}"`); + console.warn( + `No keyboard shortcut named "${shortcutName}" registered with key code "${keyCode}"` + ); } return false; } @@ -200,7 +210,7 @@ export class ShortcutRegistry { */ getRegistry(): {[key: string]: KeyboardShortcut} { const legacyRegistry: {[key: string]: KeyboardShortcut} = - Object.create(null); + Object.create(null); for (const [key, value] of this.shortcuts) { legacyRegistry[key] = value; } @@ -220,7 +230,7 @@ export class ShortcutRegistry { if (!shortcutNames) { return false; } - for (let i = 0, shortcutName; shortcutName = shortcutNames[i]; i++) { + for (let i = 0, shortcutName; (shortcutName = shortcutNames[i]); i++) { const shortcut = this.shortcuts.get(shortcutName); if (!shortcut?.preconditionFn || shortcut?.preconditionFn(workspace)) { // If the key has been handled, stop processing shortcuts. @@ -239,7 +249,7 @@ export class ShortcutRegistry { * @returns The list of shortcuts to call when the given keyCode is used. * Undefined if no shortcuts exist. */ - getShortcutNamesByKeyCode(keyCode: string): string[]|undefined { + getShortcutNamesByKeyCode(keyCode: string): string[] | undefined { return this.keyMap.get(keyCode) || []; } @@ -292,7 +302,7 @@ export class ShortcutRegistry { * @throws {Error} if the modifier is not in the valid modifiers list. */ private checkModifiers_(modifiers: KeyCodes[]) { - for (let i = 0, modifier; modifier = modifiers[i]; i++) { + for (let i = 0, modifier; (modifier = modifiers[i]); i++) { if (!(modifier in ShortcutRegistry.modifierKeys)) { throw new Error(modifier + ' is not a valid modifier key.'); } @@ -307,14 +317,15 @@ export class ShortcutRegistry { * valid modifiers can be found in the ShortcutRegistry.modifierKeys. * @returns The serialized key code for the given modifiers and key. */ - createSerializedKey(keyCode: number, modifiers: KeyCodes[]|null): string { + createSerializedKey(keyCode: number, modifiers: KeyCodes[] | null): string { let serializedKey = ''; if (modifiers) { this.checkModifiers_(modifiers); for (const modifier in ShortcutRegistry.modifierKeys) { - const modifierKeyCode = - (ShortcutRegistry.modifierKeys as AnyDuringMigration)[modifier]; + const modifierKeyCode = ( + ShortcutRegistry.modifierKeys as AnyDuringMigration + )[modifier]; if (modifiers.indexOf(modifierKeyCode) > -1) { if (serializedKey !== '') { serializedKey += '+'; @@ -335,11 +346,11 @@ export class ShortcutRegistry { export namespace ShortcutRegistry { export interface KeyboardShortcut { - callback?: ((p1: Workspace, p2: Event, p3: KeyboardShortcut) => boolean); + callback?: (p1: Workspace, p2: Event, p3: KeyboardShortcut) => boolean; name: string; - preconditionFn?: ((p1: Workspace) => boolean); + preconditionFn?: (p1: Workspace) => boolean; metadata?: object; - keyCodes?: (number|string)[]; + keyCodes?: (number | string)[]; allowCollision?: boolean; } diff --git a/core/theme.ts b/core/theme.ts index c9fb2981c..e52039031 100644 --- a/core/theme.ts +++ b/core/theme.ts @@ -15,14 +15,13 @@ goog.declareModuleId('Blockly.Theme'); import * as registry from './registry.js'; import * as object from './utils/object.js'; - export interface ITheme { blockStyles?: {[key: string]: Partial}; categoryStyles?: {[key: string]: CategoryStyle}; componentStyles?: ComponentStyle; fontStyle?: FontStyle; startHats?: boolean; - base?: string|Theme; + base?: string | Theme; name: string; } @@ -56,10 +55,11 @@ export class Theme implements ITheme { * @param opt_componentStyles A map of Blockly component names to style value. */ constructor( - public name: string, - opt_blockStyles?: {[key: string]: Partial}, - opt_categoryStyles?: {[key: string]: CategoryStyle}, - opt_componentStyles?: ComponentStyle) { + public name: string, + opt_blockStyles?: {[key: string]: Partial}, + opt_categoryStyles?: {[key: string]: CategoryStyle}, + opt_componentStyles?: ComponentStyle + ) { /** The block styles map. */ this.blockStyles = opt_blockStyles || Object.create(null); @@ -68,13 +68,13 @@ export class Theme implements ITheme { /** The UI components styles map. */ this.componentStyles = - opt_componentStyles || Object.create(null) as ComponentStyle; + opt_componentStyles || (Object.create(null) as ComponentStyle); /** The font style. */ this.fontStyle = Object.create(null) as FontStyle; // Register the theme by name. - registry.register(registry.Type.THEME, name, this); + registry.register(registry.Type.THEME, name, this, true); } /** @@ -114,7 +114,7 @@ export class Theme implements ITheme { * @param componentName The name of the component. * @returns The style value. */ - getComponentStyle(componentName: string): string|null { + getComponentStyle(componentName: string): string | null { const style = (this.componentStyles as AnyDuringMigration)[componentName]; if (!style) { return null; diff --git a/core/theme/classic.ts b/core/theme/classic.ts index 706964f8c..d4298d0a5 100644 --- a/core/theme/classic.ts +++ b/core/theme/classic.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.Themes.Classic'); import {Theme} from '../theme.js'; - const defaultBlockStyles = { 'colour_blocks': {'colourPrimary': '20'}, 'list_blocks': {'colourPrimary': '260'}, @@ -39,5 +38,4 @@ const categoryStyles = { * Classic theme. * Contains multi-coloured border to create shadow effect. */ -export const Classic = new Theme( - 'classic', defaultBlockStyles as AnyDuringMigration, categoryStyles); +export const Classic = new Theme('classic', defaultBlockStyles, categoryStyles); diff --git a/core/theme/themes.ts b/core/theme/themes.ts index 157133484..43c1a2343 100644 --- a/core/theme/themes.ts +++ b/core/theme/themes.ts @@ -10,5 +10,4 @@ goog.declareModuleId('Blockly.Themes'); import {Classic} from './classic.js'; import {Zelos} from './zelos.js'; - export {Classic, Zelos}; diff --git a/core/theme/zelos.ts b/core/theme/zelos.ts index f4bffe64c..7df97977b 100644 --- a/core/theme/zelos.ts +++ b/core/theme/zelos.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.Themes.Zelos'); import {Theme} from '../theme.js'; - const defaultBlockStyles = { 'colour_blocks': { 'colourPrimary': '#CF63CF', diff --git a/core/theme_manager.ts b/core/theme_manager.ts index b4f8fb7c8..63448093f 100644 --- a/core/theme_manager.ts +++ b/core/theme_manager.ts @@ -19,7 +19,6 @@ import * as dom from './utils/dom.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for storing and updating a workspace's theme and UI components. */ @@ -70,7 +69,11 @@ export class ThemeManager { } // Refresh all subscribed workspaces. - for (let i = 0, workspace; workspace = this.subscribedWorkspaces_[i]; i++) { + for ( + let i = 0, workspace; + (workspace = this.subscribedWorkspaces_[i]); + i++ + ) { (workspace as WorkspaceSvg).refreshTheme(); } @@ -109,7 +112,8 @@ export class ThemeManager { unsubscribeWorkspace(workspace: Workspace) { if (!arrayUtils.removeElem(this.subscribedWorkspaces_, workspace)) { throw Error( - 'Cannot unsubscribe a workspace that hasn\'t been subscribed.'); + "Cannot unsubscribe a workspace that hasn't been subscribed." + ); } } @@ -124,8 +128,10 @@ export class ThemeManager { * @internal */ subscribe( - element: HTMLElement|SVGElement, componentName: string, - propertyName: string) { + element: HTMLElement | SVGElement, + componentName: string, + propertyName: string + ) { if (!this.componentDB.has(componentName)) { this.componentDB.set(componentName, []); } @@ -144,7 +150,7 @@ export class ThemeManager { * @param element The element to unsubscribe. * @internal */ - unsubscribe(element: HTMLElement|SVGElement) { + unsubscribe(element: HTMLElement | SVGElement) { if (!element) { return; } @@ -165,7 +171,6 @@ export class ThemeManager { /** * Dispose of this theme manager. * - * @suppress {checkTypes} * @internal */ dispose() { @@ -177,7 +182,7 @@ export class ThemeManager { export namespace ThemeManager { /** The type for a Blockly UI Component. */ export interface Component { - element: HTMLElement|SVGElement; + element: HTMLElement | SVGElement; propertyName: string; } } diff --git a/core/toolbox/category.ts b/core/toolbox/category.ts index 16f814f26..6d23d1a69 100644 --- a/core/toolbox/category.ts +++ b/core/toolbox/category.ts @@ -17,7 +17,14 @@ import type {ICollapsibleToolboxItem} from '../interfaces/i_collapsible_toolbox_ import type {ISelectableToolboxItem} from '../interfaces/i_selectable_toolbox_item.js'; import type {IToolbox} from '../interfaces/i_toolbox.js'; import type {IToolboxItem} from '../interfaces/i_toolbox_item.js'; -import type {CategoryInfo, DynamicCategoryInfo, FlyoutDefinition, FlyoutItemInfo, FlyoutItemInfoArray, StaticCategoryInfo} from '../utils/toolbox.js'; +import type { + CategoryInfo, + DynamicCategoryInfo, + FlyoutDefinition, + FlyoutItemInfo, + FlyoutItemInfoArray, + StaticCategoryInfo, +} from '../utils/toolbox.js'; import * as registry from '../registry.js'; import * as aria from '../utils/aria.js'; import * as colourUtils from '../utils/colour.js'; @@ -27,12 +34,13 @@ import * as toolbox from '../utils/toolbox.js'; import {ToolboxItem} from './toolbox_item.js'; - /** * Class for a category in a toolbox. */ -export class ToolboxCategory extends ToolboxItem implements - ISelectableToolboxItem { +export class ToolboxCategory + extends ToolboxItem + implements ISelectableToolboxItem +{ /** Name used for registering a toolbox category. */ static registrationName = 'category'; @@ -58,19 +66,19 @@ export class ToolboxCategory extends ToolboxItem implements protected colour_ = ''; /** The HTML container for the category. */ - protected htmlDiv_: HTMLDivElement|null = null; + protected htmlDiv_: HTMLDivElement | null = null; /** The HTML element for the category row. */ - protected rowDiv_: HTMLDivElement|null = null; + protected rowDiv_: HTMLDivElement | null = null; /** The HTML element that holds children elements of the category row. */ - protected rowContents_: HTMLDivElement|null = null; + protected rowContents_: HTMLDivElement | null = null; /** The HTML element for the toolbox icon. */ - protected iconDom_: Element|null = null; + protected iconDom_: Element | null = null; /** The HTML element for the toolbox label. */ - protected labelDom_: Element|null = null; + protected labelDom_: Element | null = null; protected cssConfig_: CssConfig; /** True if the category is meant to be hidden, false otherwise. */ @@ -80,7 +88,7 @@ export class ToolboxCategory extends ToolboxItem implements protected isDisabled_ = false; /** The flyout items for this category. */ - protected flyoutItems_: string|FlyoutItemInfoArray = []; + protected flyoutItems_: string | FlyoutItemInfoArray = []; /** * @param categoryDef The information needed to create a category in the @@ -90,8 +98,10 @@ export class ToolboxCategory extends ToolboxItem implements * a parent. */ constructor( - categoryDef: CategoryInfo, parentToolbox: IToolbox, - opt_parent?: ICollapsibleToolboxItem) { + categoryDef: CategoryInfo, + parentToolbox: IToolbox, + opt_parent?: ICollapsibleToolboxItem + ) { super(categoryDef, parentToolbox, opt_parent); /** All the css class names that are used to create a category. */ @@ -163,13 +173,15 @@ export class ToolboxCategory extends ToolboxItem implements * @param categoryDef The information needed to create a category. */ protected parseCategoryDef_(categoryDef: CategoryInfo) { - this.name_ = 'name' in categoryDef ? - parsing.replaceMessageReferences(categoryDef['name']) : - ''; + this.name_ = + 'name' in categoryDef + ? parsing.replaceMessageReferences(categoryDef['name']) + : ''; this.colour_ = this.getColour_(categoryDef); Object.assign( - this.cssConfig_, - categoryDef['cssconfig'] || (categoryDef as any)['cssConfig']); + this.cssConfig_, + categoryDef['cssconfig'] || (categoryDef as any)['cssConfig'] + ); } /** @@ -181,7 +193,7 @@ export class ToolboxCategory extends ToolboxItem implements this.htmlDiv_ = this.createContainer_(); aria.setRole(this.htmlDiv_, aria.Role.TREEITEM); aria.setState(this.htmlDiv_, aria.State.SELECTED, false); - aria.setState(this.htmlDiv_, aria.State.LEVEL, this.level_); + aria.setState(this.htmlDiv_, aria.State.LEVEL, this.level_ + 1); this.rowDiv_ = this.createRowContainer_(); this.rowDiv_.style.pointerEvents = 'auto'; @@ -234,10 +246,12 @@ export class ToolboxCategory extends ToolboxItem implements if (className) { dom.addClass(rowDiv, className); } - const nestedPadding = - `${ToolboxCategory.nestedPadding * this.getLevel()}px`; - this.workspace_.RTL ? rowDiv.style.paddingRight = nestedPadding : - rowDiv.style.paddingLeft = nestedPadding; + const nestedPadding = `${ + ToolboxCategory.nestedPadding * this.getLevel() + }px`; + this.workspace_.RTL + ? (rowDiv.style.paddingRight = nestedPadding) + : (rowDiv.style.paddingLeft = nestedPadding); return rowDiv; } @@ -306,7 +320,7 @@ export class ToolboxCategory extends ToolboxItem implements protected addColourBorder_(colour: string) { if (colour) { const border = - ToolboxCategory.borderWidth + 'px solid ' + (colour || '#ddd'); + ToolboxCategory.borderWidth + 'px solid ' + (colour || '#ddd'); if (this.workspace_.RTL) { this.rowDiv_!.style.borderRight = border; } else { @@ -323,13 +337,15 @@ export class ToolboxCategory extends ToolboxItem implements */ protected getColour_(categoryDef: CategoryInfo): string { const styleName = - categoryDef['categorystyle'] || (categoryDef as any)['categoryStyle']; + categoryDef['categorystyle'] || (categoryDef as any)['categoryStyle']; const colour = categoryDef['colour']; if (colour && styleName) { console.warn( - 'Toolbox category "' + this.name_ + - '" must not have both a style and a colour'); + 'Toolbox category "' + + this.name_ + + '" must not have both a style and a colour' + ); } else if (styleName) { return this.getColourfromStyle_(styleName); } else if (colour) { @@ -353,7 +369,8 @@ export class ToolboxCategory extends ToolboxItem implements return this.parseColour_(style.colour); } else { console.warn( - 'Style "' + styleName + '" must exist and contain a colour value'); + 'Style "' + styleName + '" must exist and contain a colour value' + ); } } return ''; @@ -378,7 +395,7 @@ export class ToolboxCategory extends ToolboxItem implements * reference string pointing to one of those two values. * @returns The hex colour for the category. */ - private parseColour_(colourValue: number|string): string { + private parseColour_(colourValue: number | string): string { // Decode the colour for any potential message references // (eg. `%{BKY_MATH_HUE}`). const colour = parsing.replaceMessageReferences(colourValue); @@ -395,8 +412,11 @@ export class ToolboxCategory extends ToolboxItem implements return hex; } else { console.warn( - 'Toolbox category "' + this.name_ + - '" has unrecognized colour attribute: ' + colour); + 'Toolbox category "' + + this.name_ + + '" has unrecognized colour attribute: ' + + colour + ); return ''; } } @@ -408,7 +428,7 @@ export class ToolboxCategory extends ToolboxItem implements * * @param iconDiv The div that holds the icon. */ - protected openIcon_(iconDiv: Element|null) { + protected openIcon_(iconDiv: Element | null) { if (!iconDiv) { return; } @@ -427,7 +447,7 @@ export class ToolboxCategory extends ToolboxItem implements * * @param iconDiv The div that holds the icon. */ - protected closeIcon_(iconDiv: Element|null) { + protected closeIcon_(iconDiv: Element | null) { if (!iconDiv) { return; } @@ -521,8 +541,9 @@ export class ToolboxCategory extends ToolboxItem implements } const className = this.cssConfig_['selected']; if (isSelected) { - const defaultColour = - this.parseColour_(ToolboxCategory.defaultBackgroundColour); + const defaultColour = this.parseColour_( + ToolboxCategory.defaultBackgroundColour + ); this.rowDiv_.style.backgroundColor = this.colour_ || defaultColour; if (className) { dom.addClass(this.rowDiv_, className); @@ -544,8 +565,9 @@ export class ToolboxCategory extends ToolboxItem implements setDisabled(isDisabled: boolean) { this.isDisabled_ = isDisabled; this.getDiv()!.setAttribute('disabled', `${isDisabled}`); - isDisabled ? this.getDiv()!.setAttribute('disabled', 'true') : - this.getDiv()!.removeAttribute('disabled'); + isDisabled + ? this.getDiv()!.setAttribute('disabled', 'true') + : this.getDiv()!.removeAttribute('disabled'); } /** @@ -571,7 +593,7 @@ export class ToolboxCategory extends ToolboxItem implements * * @returns The definition of items to be displayed in the flyout. */ - getContents(): FlyoutItemInfoArray|string { + getContents(): FlyoutItemInfoArray | string { return this.flyoutItems_; } @@ -583,7 +605,7 @@ export class ToolboxCategory extends ToolboxItem implements * @param contents The contents to be displayed in the flyout. A string can be * supplied to create a dynamic category. */ - updateFlyoutContents(contents: FlyoutDefinition|string) { + updateFlyoutContents(contents: FlyoutDefinition | string) { this.flyoutItems_ = []; if (typeof contents === 'string') { @@ -600,8 +622,8 @@ export class ToolboxCategory extends ToolboxItem implements } else { const newDefinition: StaticCategoryInfo = { kind: this.toolboxItemDef_.kind, - name: 'name' in this.toolboxItemDef_ ? this.toolboxItemDef_['name'] : - '', + name: + 'name' in this.toolboxItemDef_ ? this.toolboxItemDef_['name'] : '', contents: toolbox.convertFlyoutDefToJsonArray(contents), id: this.toolboxItemDef_.id, categorystyle: this.toolboxItemDef_.categorystyle, @@ -712,5 +734,7 @@ Css.register(` `); registry.register( - registry.Type.TOOLBOX_ITEM, ToolboxCategory.registrationName, - ToolboxCategory); + registry.Type.TOOLBOX_ITEM, + ToolboxCategory.registrationName, + ToolboxCategory +); diff --git a/core/toolbox/collapsible_category.ts b/core/toolbox/collapsible_category.ts index 0cb3df7b4..cb5270995 100644 --- a/core/toolbox/collapsible_category.ts +++ b/core/toolbox/collapsible_category.ts @@ -23,17 +23,18 @@ import * as toolbox from '../utils/toolbox.js'; import {ToolboxCategory} from './category.js'; import {ToolboxSeparator} from './separator.js'; - /** * Class for a category in a toolbox that can be collapsed. */ -export class CollapsibleToolboxCategory extends ToolboxCategory implements - ICollapsibleToolboxItem { +export class CollapsibleToolboxCategory + extends ToolboxCategory + implements ICollapsibleToolboxItem +{ /** Name used for registering a collapsible toolbox category. */ static override registrationName = 'collapsibleCategory'; /** Container for any child categories. */ - protected subcategoriesDiv_: HTMLDivElement|null = null; + protected subcategoriesDiv_: HTMLDivElement | null = null; /** Whether or not the category should display its subcategories. */ protected expanded_ = false; @@ -49,8 +50,10 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements * a parent. */ constructor( - categoryDef: toolbox.CategoryInfo, toolbox: IToolbox, - opt_parent?: ICollapsibleToolboxItem) { + categoryDef: toolbox.CategoryInfo, + toolbox: IToolbox, + opt_parent?: ICollapsibleToolboxItem + ) { super(categoryDef, toolbox, opt_parent); } @@ -72,10 +75,12 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements const itemDef = contents[i]; // Separators can exist as either a flyout item or a toolbox item so // decide where it goes based on the type of the previous item. - if (!registry.hasItem(registry.Type.TOOLBOX_ITEM, itemDef['kind']) || - itemDef['kind'].toLowerCase() === - ToolboxSeparator.registrationName && - prevIsFlyoutItem) { + if ( + !registry.hasItem(registry.Type.TOOLBOX_ITEM, itemDef['kind']) || + (itemDef['kind'].toLowerCase() === + ToolboxSeparator.registrationName && + prevIsFlyoutItem) + ) { const flyoutItem = itemDef as toolbox.FlyoutItemInfo; this.flyoutItems_.push(flyoutItem); prevIsFlyoutItem = true; @@ -97,14 +102,21 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements const categoryDef = itemDef as toolbox.CategoryInfo; // Categories that are collapsible are created using a class registered // under a different name. - if (registryName.toUpperCase() === 'CATEGORY' && - toolbox.isCategoryCollapsible(categoryDef)) { + if ( + registryName.toUpperCase() === 'CATEGORY' && + toolbox.isCategoryCollapsible(categoryDef) + ) { registryName = CollapsibleToolboxCategory.registrationName; } - const ToolboxItemClass = - registry.getClass(registry.Type.TOOLBOX_ITEM, registryName); - const toolboxItem = - new ToolboxItemClass!(itemDef, this.parentToolbox_, this); + const ToolboxItemClass = registry.getClass( + registry.Type.TOOLBOX_ITEM, + registryName + ); + const toolboxItem = new ToolboxItemClass!( + itemDef, + this.parentToolbox_, + this + ); this.toolboxItems_.push(toolboxItem); } @@ -112,8 +124,9 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements super.init(); this.setExpanded( - this.toolboxItemDef_['expanded'] === 'true' || - !!this.toolboxItemDef_['expanded']); + this.toolboxItemDef_['expanded'] === 'true' || + this.toolboxItemDef_['expanded'] === true + ); } override createDom_() { @@ -123,6 +136,8 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements this.subcategoriesDiv_ = this.createSubCategoriesDom_(subCategories); aria.setRole(this.subcategoriesDiv_, aria.Role.GROUP); this.htmlDiv_!.appendChild(this.subcategoriesDiv_); + this.closeIcon_(this.iconDom_); + aria.setState(this.htmlDiv_ as HTMLDivElement, aria.State.EXPANDED, false); return this.htmlDiv_!; } @@ -147,9 +162,11 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements * @param subcategories The subcategories. * @returns The div holding all the subcategories. */ - protected createSubCategoriesDom_(subcategories: IToolboxItem[]): - HTMLDivElement { + protected createSubCategoriesDom_( + subcategories: IToolboxItem[] + ): HTMLDivElement { const contentsContainer = document.createElement('div'); + contentsContainer.style.display = 'none'; const className = this.cssConfig_['contents']; if (className) { dom.addClass(contentsContainer, className); @@ -173,9 +190,8 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements * @param isExpanded True to expand the category, false to close. */ setExpanded(isExpanded: boolean) { - if (this.expanded_ === isExpanded) { - return; - } + if (this.expanded_ === isExpanded) return; + this.expanded_ = isExpanded; if (isExpanded) { this.subcategoriesDiv_!.style.display = 'block'; @@ -185,7 +201,10 @@ export class CollapsibleToolboxCategory extends ToolboxCategory implements this.closeIcon_(this.iconDom_); } aria.setState( - this.htmlDiv_ as HTMLDivElement, aria.State.EXPANDED, isExpanded); + this.htmlDiv_ as HTMLDivElement, + aria.State.EXPANDED, + isExpanded + ); this.parentToolbox_.handleToolboxItemResize(); } @@ -248,20 +267,22 @@ export namespace CollapsibleToolboxCategory { * contents. */ export interface CssConfig { - container: string|null; - row: string|null; - rowcontentcontainer: string|null; - icon: string|null; - label: string|null; - selected: string|null; - openicon: string|null; - closedicon: string|null; - contents: string|null; + container: string | null; + row: string | null; + rowcontentcontainer: string | null; + icon: string | null; + label: string | null; + selected: string | null; + openicon: string | null; + closedicon: string | null; + contents: string | null; } } export type CssConfig = CollapsibleToolboxCategory.CssConfig; registry.register( - registry.Type.TOOLBOX_ITEM, CollapsibleToolboxCategory.registrationName, - CollapsibleToolboxCategory); + registry.Type.TOOLBOX_ITEM, + CollapsibleToolboxCategory.registrationName, + CollapsibleToolboxCategory +); diff --git a/core/toolbox/separator.ts b/core/toolbox/separator.ts index 41c600655..5308268a1 100644 --- a/core/toolbox/separator.ts +++ b/core/toolbox/separator.ts @@ -20,7 +20,6 @@ import type * as toolbox from '../utils/toolbox.js'; import {ToolboxItem} from './toolbox_item.js'; - /** * Class for a toolbox separator. This is the thin visual line that appears on * the toolbox. This item is not interactable. @@ -32,7 +31,7 @@ export class ToolboxSeparator extends ToolboxItem { /** All the CSS class names that are used to create a separator. */ protected cssConfig_: CssConfig = {'container': 'blocklyTreeSeparator'}; - private htmlDiv_: HTMLDivElement|null = null; + private htmlDiv_: HTMLDivElement | null = null; /** * @param separatorDef The information needed to create a separator. @@ -42,7 +41,7 @@ export class ToolboxSeparator extends ToolboxItem { super(separatorDef, toolbox); const cssConfig = - separatorDef['cssconfig'] || (separatorDef as any)['cssConfig']; + separatorDef['cssconfig'] || (separatorDef as any)['cssConfig']; Object.assign(this.cssConfig_, cssConfig); } @@ -76,7 +75,7 @@ export class ToolboxSeparator extends ToolboxItem { export namespace ToolboxSeparator { export interface CssConfig { - container: string|undefined; + container: string | undefined; } } @@ -101,5 +100,7 @@ Css.register(` `); registry.register( - registry.Type.TOOLBOX_ITEM, ToolboxSeparator.registrationName, - ToolboxSeparator); + registry.Type.TOOLBOX_ITEM, + ToolboxSeparator.registrationName, + ToolboxSeparator +); diff --git a/core/toolbox/toolbox.ts b/core/toolbox/toolbox.ts index 469515639..dcea49787 100644 --- a/core/toolbox/toolbox.ts +++ b/core/toolbox/toolbox.ts @@ -46,14 +46,14 @@ import type {WorkspaceSvg} from '../workspace_svg.js'; import type {ToolboxCategory} from './category.js'; import {CollapsibleToolboxCategory} from './collapsible_category.js'; - /** * Class for a Toolbox. * Creates the toolbox's DOM. */ -export class Toolbox extends DeleteArea implements IAutoHideable, - IKeyboardAccessible, - IStyleable, IToolbox { +export class Toolbox + extends DeleteArea + implements IAutoHideable, IKeyboardAccessible, IStyleable, IToolbox +{ /** * The unique ID for this component that is used to register with the * ComponentManager. @@ -63,10 +63,10 @@ export class Toolbox extends DeleteArea implements IAutoHideable, private readonly horizontalLayout_: boolean; /** The HTML container for the toolbox. */ - HtmlDiv: HTMLDivElement|null = null; + HtmlDiv: HTMLDivElement | null = null; /** The HTML container for the contents of a toolbox. */ - protected contentsDiv_: HTMLDivElement|null = null; + protected contentsDiv_: HTMLDivElement | null = null; /** Whether the Toolbox is visible. */ protected isVisible_ = false; @@ -82,15 +82,15 @@ export class Toolbox extends DeleteArea implements IAutoHideable, RTL: boolean; /** The flyout for the toolbox. */ - private flyout_: IFlyout|null = null; + private flyout_: IFlyout | null = null; protected contentMap_: {[key: string]: IToolboxItem}; toolboxPosition: toolbox.Position; /** The currently selected item. */ - protected selectedItem_: ISelectableToolboxItem|null = null; + protected selectedItem_: ISelectableToolboxItem | null = null; /** The previously selected item. */ - protected previouslySelectedItem_: ISelectableToolboxItem|null = null; + protected previouslySelectedItem_: ISelectableToolboxItem | null = null; /** * Array holding info needed to unbind event handlers. @@ -109,9 +109,9 @@ export class Toolbox extends DeleteArea implements IAutoHideable, this.workspace_ = workspace; /** The JSON describing the contents of this toolbox. */ - this.toolboxDef_ = - (workspace.options.languageTree || - {contents: new Array()}); + this.toolboxDef_ = workspace.options.languageTree || { + contents: new Array(), + }; /** Whether the toolbox should be laid out horizontally. */ this.horizontalLayout_ = workspace.options.horizontalLayout; @@ -151,7 +151,10 @@ export class Toolbox extends DeleteArea implements IAutoHideable, this.render(this.toolboxDef_); const themeManager = workspace.getThemeManager(); themeManager.subscribe( - this.HtmlDiv, 'toolboxBackgroundColour', 'background-color'); + this.HtmlDiv, + 'toolboxBackgroundColour', + 'background-color' + ); themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color'); this.workspace_.getComponentManager().addComponent({ component: this, @@ -192,7 +195,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @returns The HTML container for the toolbox. */ protected createContainer_(): HTMLDivElement { - const toolboxContainer = (document.createElement('div')); + const toolboxContainer = document.createElement('div'); toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v'); dom.addClass(toolboxContainer, 'blocklyToolboxDiv'); dom.addClass(toolboxContainer, 'blocklyNonSelectable'); @@ -206,7 +209,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @returns The HTML container for the toolbox contents. */ protected createContentsContainer_(): HTMLDivElement { - const contentsContainer = (document.createElement('div')); + const contentsContainer = document.createElement('div'); dom.addClass(contentsContainer, 'blocklyToolboxContents'); if (this.isHorizontal()) { contentsContainer.style.flexDirection = 'row'; @@ -222,16 +225,26 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * toolbox. */ protected attachEvents_( - container: HTMLDivElement, contentsContainer: HTMLDivElement) { + container: HTMLDivElement, + contentsContainer: HTMLDivElement + ) { // Clicking on toolbox closes popups. const clickEvent = browserEvents.conditionalBind( - container, 'pointerdown', this, this.onClick_, - /* opt_noCaptureIdentifier */ false); + container, + 'pointerdown', + this, + this.onClick_, + /* opt_noCaptureIdentifier */ false + ); this.boundEvents_.push(clickEvent); const keyDownEvent = browserEvents.conditionalBind( - contentsContainer, 'keydown', this, this.onKeyDown_, - /* opt_noCaptureIdentifier */ false); + contentsContainer, + 'keydown', + this, + this.onKeyDown_, + /* opt_noCaptureIdentifier */ false + ); this.boundEvents_.push(keyDownEvent); } @@ -315,7 +328,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, protected createFlyout_(): IFlyout { const workspace = this.workspace_; // TODO (#4247): Look into adding a makeFlyout method to Blockly Options. - const workspaceOptions = new Options(({ + const workspaceOptions = new Options({ 'parentWorkspace': workspace, 'rtl': workspace.RTL, 'oneBasedIndex': workspace.options.oneBasedIndex, @@ -325,17 +338,23 @@ export class Toolbox extends DeleteArea implements IAutoHideable, 'move': { 'scrollbars': true, }, - } as BlocklyOptions)); + } as BlocklyOptions); // Options takes in either 'end' or 'start'. This has already been parsed to // be either 0 or 1, so set it after. workspaceOptions.toolboxPosition = workspace.options.toolboxPosition; let FlyoutClass = null; if (workspace.horizontalLayout) { FlyoutClass = registry.getClassFromOptions( - registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, workspace.options, true); + registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, + workspace.options, + true + ); } else { FlyoutClass = registry.getClassFromOptions( - registry.Type.FLYOUTS_VERTICAL_TOOLBOX, workspace.options, true); + registry.Type.FLYOUTS_VERTICAL_TOOLBOX, + workspace.options, + true + ); } return new FlyoutClass!(workspaceOptions); } @@ -386,18 +405,24 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @param fragment The document fragment to add the child toolbox elements to. */ private createToolboxItem_( - toolboxItemDef: toolbox.ToolboxItemInfo, fragment: DocumentFragment) { + toolboxItemDef: toolbox.ToolboxItemInfo, + fragment: DocumentFragment + ) { let registryName = toolboxItemDef['kind']; // Categories that are collapsible are created using a class registered // under a different name. - if (registryName.toUpperCase() === 'CATEGORY' && - toolbox.isCategoryCollapsible(toolboxItemDef as toolbox.CategoryInfo)) { + if ( + registryName.toUpperCase() === 'CATEGORY' && + toolbox.isCategoryCollapsible(toolboxItemDef as toolbox.CategoryInfo) + ) { registryName = CollapsibleToolboxCategory.registrationName; } const ToolboxItemClass = registry.getClass( - registry.Type.TOOLBOX_ITEM, registryName.toLowerCase()); + registry.Type.TOOLBOX_ITEM, + registryName.toLowerCase() + ); if (ToolboxItemClass) { const toolboxItem = new ToolboxItemClass(toolboxItemDef, this); toolboxItem.init(); @@ -472,7 +497,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @returns The component's bounding box. Null if drag target area should be * ignored. */ - override getClientRect(): Rect|null { + override getClientRect(): Rect | null { if (!this.HtmlDiv || !this.isVisible_) { return null; } @@ -495,7 +520,8 @@ export class Toolbox extends DeleteArea implements IAutoHideable, return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM); } else if (this.toolboxPosition === toolbox.Position.LEFT) { return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, right); - } else { // Right + } else { + // Right return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM); } } @@ -513,7 +539,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, */ override wouldDelete(element: IDraggable, _couldConnect: boolean): boolean { if (element instanceof BlockSvg) { - const block = (element); + const block = element; // Prefer dragging to the toolbox over connecting to other blocks. this.updateWouldDelete_(!block.getParent() && block.isDeletable()); } else { @@ -577,8 +603,9 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @param addStyle Whether the style should be added or removed. */ protected updateCursorDeleteStyle_(addStyle: boolean) { - const style = - this.wouldDelete_ ? 'blocklyToolboxDelete' : 'blocklyToolboxGrab'; + const style = this.wouldDelete_ + ? 'blocklyToolboxDelete' + : 'blocklyToolboxGrab'; if (addStyle) { this.addStyle(style); } else { @@ -592,7 +619,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @param id The ID of the toolbox item. * @returns The toolbox item with the given ID, or null if no item exists. */ - getToolboxItemById(id: string): IToolboxItem|null { + getToolboxItemById(id: string): IToolboxItem | null { return this.contentMap_[id] || null; } @@ -619,7 +646,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * * @returns The toolbox flyout. */ - getFlyout(): IFlyout|null { + getFlyout(): IFlyout | null { return this.flyout_; } @@ -637,7 +664,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * * @returns The selected item, or null if no item is currently selected. */ - getSelectedItem(): ISelectableToolboxItem|null { + getSelectedItem(): ISelectableToolboxItem | null { return this.selectedItem_; } @@ -647,7 +674,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @returns The previously selected item, or null if no item was previously * selected. */ - getPreviouslySelectedItem(): ISelectableToolboxItem|null { + getPreviouslySelectedItem(): ISelectableToolboxItem | null { return this.previouslySelectedItem_; } @@ -681,13 +708,15 @@ export class Toolbox extends DeleteArea implements IAutoHideable, this.width_ = workspaceMetrics.viewWidth; if (this.toolboxPosition === toolbox.Position.TOP) { toolboxDiv.style.top = '0'; - } else { // Bottom + } else { + // Bottom toolboxDiv.style.bottom = '0'; } } else { if (this.toolboxPosition === toolbox.Position.RIGHT) { toolboxDiv.style.right = '0'; - } else { // Left + } else { + // Left toolboxDiv.style.left = '0'; } toolboxDiv.style.height = '100%'; @@ -707,12 +736,14 @@ export class Toolbox extends DeleteArea implements IAutoHideable, // relative to the new absolute edge (ie toolbox edge). const workspace = this.workspace_; const rect = this.HtmlDiv!.getBoundingClientRect(); - const newX = this.toolboxPosition === toolbox.Position.LEFT ? - workspace.scrollX + rect.width : - workspace.scrollX; - const newY = this.toolboxPosition === toolbox.Position.TOP ? - workspace.scrollY + rect.height : - workspace.scrollY; + const newX = + this.toolboxPosition === toolbox.Position.LEFT + ? workspace.scrollX + rect.width + : workspace.scrollX; + const newY = + this.toolboxPosition === toolbox.Position.TOP + ? workspace.scrollY + rect.height + : workspace.scrollY; workspace.translate(newX, newY); // Even though the div hasn't changed size, the visible workspace @@ -747,8 +778,11 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * procedures. */ refreshSelection() { - if (this.selectedItem_ && this.selectedItem_.isSelectable() && - this.selectedItem_.getContents().length) { + if ( + this.selectedItem_ && + this.selectedItem_.isSelectable() && + this.selectedItem_.getContents().length + ) { this.flyout_!.show(this.selectedItem_.getContents()); } } @@ -788,10 +822,13 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * * @param newItem The toolbox item to select. */ - setSelectedItem(newItem: IToolboxItem|null) { + setSelectedItem(newItem: IToolboxItem | null) { const oldItem = this.selectedItem_; - if (!newItem && !oldItem || newItem && !isSelectableToolboxItem(newItem)) { + if ( + (!newItem && !oldItem) || + (newItem && !isSelectableToolboxItem(newItem)) + ) { return; } @@ -815,12 +852,14 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @returns True if the old item should be deselected, false otherwise. */ protected shouldDeselectItem_( - oldItem: ISelectableToolboxItem|null, - newItem: ISelectableToolboxItem|null): boolean { + oldItem: ISelectableToolboxItem | null, + newItem: ISelectableToolboxItem | null + ): boolean { // Deselect the old item unless the old item is collapsible and has been // previously clicked on. - return oldItem !== null && - (!oldItem.isCollapsible() || oldItem !== newItem); + return ( + oldItem !== null && (!oldItem.isCollapsible() || oldItem !== newItem) + ); } /** @@ -831,8 +870,9 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @returns True if the new item should be selected, false otherwise. */ protected shouldSelectItem_( - oldItem: ISelectableToolboxItem|null, - newItem: ISelectableToolboxItem|null): boolean { + oldItem: ISelectableToolboxItem | null, + newItem: ISelectableToolboxItem | null + ): boolean { // Select the new item unless the old item equals the new item. return newItem !== null && newItem !== oldItem; } @@ -848,7 +888,10 @@ export class Toolbox extends DeleteArea implements IAutoHideable, this.previouslySelectedItem_ = item; item.setSelected(false); aria.setState( - this.contentsDiv_ as Element, aria.State.ACTIVEDESCENDANT, ''); + this.contentsDiv_ as Element, + aria.State.ACTIVEDESCENDANT, + '' + ); } /** @@ -858,13 +901,17 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @param newItem The newly selected toolbox item. */ protected selectItem_( - oldItem: ISelectableToolboxItem|null, newItem: ISelectableToolboxItem) { + oldItem: ISelectableToolboxItem | null, + newItem: ISelectableToolboxItem + ) { this.selectedItem_ = newItem; this.previouslySelectedItem_ = oldItem; newItem.setSelected(true); aria.setState( - this.contentsDiv_ as Element, aria.State.ACTIVEDESCENDANT, - newItem.getId()); + this.contentsDiv_ as Element, + aria.State.ACTIVEDESCENDANT, + newItem.getId() + ); } /** @@ -888,10 +935,14 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @param newItem The newly selected toolbox item. */ protected updateFlyout_( - oldItem: ISelectableToolboxItem|null, - newItem: ISelectableToolboxItem|null) { - if (!newItem || oldItem === newItem && !newItem.isCollapsible() || - !newItem.getContents().length) { + oldItem: ISelectableToolboxItem | null, + newItem: ISelectableToolboxItem | null + ) { + if ( + !newItem || + (oldItem === newItem && !newItem.isCollapsible()) || + !newItem.getContents().length + ) { this.flyout_!.hide(); } else { this.flyout_!.show(newItem.getContents()); @@ -906,8 +957,9 @@ export class Toolbox extends DeleteArea implements IAutoHideable, * @param newItem The newly selected toolbox item. */ private fireSelectEvent_( - oldItem: ISelectableToolboxItem|null, - newItem: ISelectableToolboxItem|null) { + oldItem: ISelectableToolboxItem | null, + newItem: ISelectableToolboxItem | null + ) { const oldElement = oldItem && oldItem.getName(); let newElement = newItem && newItem.getName(); // In this case the toolbox closes, so the newElement should be null. @@ -915,7 +967,10 @@ export class Toolbox extends DeleteArea implements IAutoHideable, newElement = null; } const event = new (eventUtils.get(eventUtils.TOOLBOX_ITEM_SELECT))( - oldElement, newElement, this.workspace_.id); + oldElement, + newElement, + this.workspace_.id + ); eventUtils.fire(event); } @@ -929,14 +984,17 @@ export class Toolbox extends DeleteArea implements IAutoHideable, return false; } - if (this.selectedItem_.isCollapsible() && - (this.selectedItem_ as ICollapsibleToolboxItem).isExpanded()) { + if ( + this.selectedItem_.isCollapsible() && + (this.selectedItem_ as ICollapsibleToolboxItem).isExpanded() + ) { const collapsibleItem = this.selectedItem_ as ICollapsibleToolboxItem; collapsibleItem.toggleExpanded(); return true; } else if ( - this.selectedItem_.getParent() && - this.selectedItem_.getParent()!.isSelectable()) { + this.selectedItem_.getParent() && + this.selectedItem_.getParent()!.isSelectable() + ) { this.setSelectedItem(this.selectedItem_.getParent()); return true; } diff --git a/core/toolbox/toolbox_item.ts b/core/toolbox/toolbox_item.ts index 3fe7ffb5b..f17273a7a 100644 --- a/core/toolbox/toolbox_item.ts +++ b/core/toolbox/toolbox_item.ts @@ -19,15 +19,14 @@ import * as idGenerator from '../utils/idgenerator.js'; import type * as toolbox from '../utils/toolbox.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; - /** * Class for an item in the toolbox. */ export class ToolboxItem implements IToolboxItem { protected id_: string; - protected parent_: ICollapsibleToolboxItem|null; + protected parent_: ICollapsibleToolboxItem | null; protected level_: number; - protected toolboxItemDef_: toolbox.ToolboxItemInfo|null; + protected toolboxItemDef_: toolbox.ToolboxItemInfo | null; protected workspace_: WorkspaceSvg; /** The toolbox this category belongs to. */ protected readonly parentToolbox_: IToolbox; @@ -39,11 +38,14 @@ export class ToolboxItem implements IToolboxItem { * have a parent. */ constructor( - toolboxItemDef: toolbox.ToolboxItemInfo, parentToolbox: IToolbox, - opt_parent?: ICollapsibleToolboxItem) { + toolboxItemDef: toolbox.ToolboxItemInfo, + parentToolbox: IToolbox, + opt_parent?: ICollapsibleToolboxItem + ) { /** The ID for the category. */ - this.id_ = (toolboxItemDef as AnyDuringMigration)['toolboxitemid'] || - idGenerator.getNextUniqueId(); + this.id_ = + (toolboxItemDef as AnyDuringMigration)['toolboxitemid'] || + idGenerator.getNextUniqueId(); /** The parent of the category. */ this.parent_ = opt_parent || null; @@ -73,7 +75,7 @@ export class ToolboxItem implements IToolboxItem { * * @returns The div for the toolbox item. */ - getDiv(): Element|null { + getDiv(): Element | null { return null; } @@ -86,7 +88,7 @@ export class ToolboxItem implements IToolboxItem { * @returns The HTML element that receives clicks, or null if this item should * not receive clicks. */ - getClickTarget(): Element|null { + getClickTarget(): Element | null { return null; } @@ -105,7 +107,7 @@ export class ToolboxItem implements IToolboxItem { * @returns The parent toolbox item, or null if this toolbox item is not * nested. */ - getParent(): ICollapsibleToolboxItem|null { + getParent(): ICollapsibleToolboxItem | null { return null; } diff --git a/core/tooltip.ts b/core/tooltip.ts index 1eb608a83..2fa428911 100644 --- a/core/tooltip.ts +++ b/core/tooltip.ts @@ -11,7 +11,6 @@ import * as browserEvents from './browser_events.js'; import * as common from './common.js'; import * as blocklyString from './utils/string.js'; - /** * A type which can define a tooltip. * Either a string, an object containing a tooltip property, or a function which @@ -19,7 +18,9 @@ import * as blocklyString from './utils/string.js'; * eventually unwinds to a string. */ export type TipInfo = - string|{tooltip: AnyDuringMigration}|(() => TipInfo|string|Function); + | string + | {tooltip: AnyDuringMigration} + | (() => TipInfo | string | Function); /** * A function that renders custom tooltip UI. @@ -34,7 +35,7 @@ export type CustomTooltip = (p1: Element, p2: Element) => AnyDuringMigration; * this is defined, the function will be called instead of rendering the default * tooltip UI. */ -let customTooltip: CustomTooltip|undefined = undefined; +let customTooltip: CustomTooltip | undefined = undefined; /** * Sets a custom function that will be called if present instead of the default @@ -51,7 +52,7 @@ export function setCustomTooltip(customFn: CustomTooltip) { * * @returns The custom tooltip function, if defined. */ -export function getCustomTooltip(): CustomTooltip|undefined { +export function getCustomTooltip(): CustomTooltip | undefined { return customTooltip; } @@ -126,14 +127,14 @@ export const HOVER_MS = 750; export const MARGINS = 5; /** The HTML container. Set once by createDom. */ -let containerDiv: HTMLDivElement|null = null; +let containerDiv: HTMLDivElement | null = null; /** * Returns the HTML tooltip container. * * @returns The HTML tooltip container. */ -export function getDiv(): HTMLDivElement|null { +export function getDiv(): HTMLDivElement | null { return containerDiv; } @@ -143,7 +144,7 @@ export function getDiv(): HTMLDivElement|null { * @param object The object to get the tooltip text of. * @returns The tooltip text of the element. */ -export function getTooltipOfObject(object: AnyDuringMigration|null): string { +export function getTooltipOfObject(object: AnyDuringMigration | null): string { const obj = getTargetObject(object); if (obj) { let tooltip = obj.tooltip; @@ -165,10 +166,14 @@ export function getTooltipOfObject(object: AnyDuringMigration|null): string { * @param obj The object are trying to find the target tooltip object of. * @returns The target tooltip object. */ -function getTargetObject(obj: object|null): {tooltip: AnyDuringMigration}|null { +function getTargetObject( + obj: object | null +): {tooltip: AnyDuringMigration} | null { while (obj && (obj as any).tooltip) { - if (typeof (obj as any).tooltip === 'string' || - typeof (obj as any).tooltip === 'function') { + if ( + typeof (obj as any).tooltip === 'string' || + typeof (obj as any).tooltip === 'function' + ) { return obj as {tooltip: string | (() => string)}; } obj = (obj as any).tooltip; @@ -181,7 +186,7 @@ function getTargetObject(obj: object|null): {tooltip: AnyDuringMigration}|null { */ export function createDom() { if (containerDiv) { - return; // Already created. + return; // Already created. } // Create an HTML container for popup overlays (e.g. editor widgets). containerDiv = document.createElement('div'); @@ -197,10 +202,18 @@ export function createDom() { */ export function bindMouseEvents(element: Element) { // TODO (#6097): Don't stash wrapper info on the DOM. - (element as AnyDuringMigration).mouseOverWrapper_ = - browserEvents.bind(element, 'pointerover', null, onMouseOver); - (element as AnyDuringMigration).mouseOutWrapper_ = - browserEvents.bind(element, 'pointerout', null, onMouseOut); + (element as AnyDuringMigration).mouseOverWrapper_ = browserEvents.bind( + element, + 'pointerover', + null, + onMouseOver + ); + (element as AnyDuringMigration).mouseOutWrapper_ = browserEvents.bind( + element, + 'pointerout', + null, + onMouseOut + ); // Don't use bindEvent_ for mousemove since that would create a // corresponding touch handler, even though this only makes sense in the @@ -213,7 +226,7 @@ export function bindMouseEvents(element: Element) { * * @param element SVG element onto which tooltip is bound. */ -export function unbindMouseEvents(element: Element|null) { +export function unbindMouseEvents(element: Element | null) { if (!element) { return; } @@ -260,7 +273,7 @@ function onMouseOut(_e: PointerEvent) { // a mouseOut followed instantly by a mouseOver. Fork off the mouseOut // event and kill it if a mouseOver is received immediately. // This way the task only fully executes if mousing into the void. - mouseOutPid = setTimeout(function() { + mouseOutPid = setTimeout(function () { element = null; poisonedElement = null; hide(); @@ -378,7 +391,7 @@ function renderDefaultContent() { // Create new text, line by line. const lines = tip.split('\n'); for (let i = 0; i < lines.length; i++) { - const div = (document.createElement('div')); + const div = document.createElement('div'); div.appendChild(document.createTextNode(lines[i])); containerDiv!.appendChild(div); } @@ -391,7 +404,7 @@ function renderDefaultContent() { * @param rtl True if the tooltip should be in right-to-left layout. * @returns Coordinates at which the tooltip div should be placed. */ -function getPosition(rtl: boolean): {x: number, y: number} { +function getPosition(rtl: boolean): {x: number; y: number} { // Position the tooltip just below the cursor. const windowWidth = document.documentElement.clientWidth; const windowHeight = document.documentElement.clientHeight; @@ -413,8 +426,10 @@ function getPosition(rtl: boolean): {x: number, y: number} { // Prevent falling off left edge in RTL mode. anchorX = Math.max(MARGINS - window.scrollX, anchorX); } else { - if (anchorX + containerDiv!.offsetWidth > - windowWidth + window.scrollX - 2 * MARGINS) { + if ( + anchorX + containerDiv!.offsetWidth > + windowWidth + window.scrollX - 2 * MARGINS + ) { // Falling off the right edge of the screen; // clamp the tooltip on the edge. anchorX = windowWidth - containerDiv!.offsetWidth - 2 * MARGINS; diff --git a/core/touch.ts b/core/touch.ts index 9de586c44..de830ecf4 100644 --- a/core/touch.ts +++ b/core/touch.ts @@ -8,20 +8,6 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.Touch'); import type {Gesture} from './gesture.js'; -import * as deprecation from './utils/deprecation.js'; - - -/** - * A mock event, created from either a mouse or touch event, - * with no more than one entry in the changedTouches array. - */ -interface PseudoEvent { - type: string; - changedTouches: Touch[]; - target: Element; - stopPropagation: () => void; - preventDefault: () => void; -} /** Length in ms for a touch to become a long press. */ const LONGPRESS = 750; @@ -30,17 +16,22 @@ const LONGPRESS = 750; * Whether touch is enabled in the browser. * Copied from Closure's goog.events.BrowserFeature.TOUCH_ENABLED */ -export const TOUCH_ENABLED = 'ontouchstart' in globalThis || - !!(globalThis['document'] && document.documentElement && - 'ontouchstart' in - document.documentElement) || // IE10 uses non-standard touch events, - // so it has a different check. - !!(globalThis['navigator'] && - (globalThis['navigator']['maxTouchPoints'] || - (globalThis['navigator'] as any)['msMaxTouchPoints'])); +export const TOUCH_ENABLED = + 'ontouchstart' in globalThis || + !!( + globalThis['document'] && + document.documentElement && + 'ontouchstart' in document.documentElement + ) || // IE10 uses non-standard touch events, + // so it has a different check. + !!( + globalThis['navigator'] && + (globalThis['navigator']['maxTouchPoints'] || + (globalThis['navigator'] as any)['msMaxTouchPoints']) + ); /** Which touch events are we currently paying attention to? */ -let touchIdentifier_: string|null = null; +let touchIdentifier_: string | null = null; /** * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, @@ -74,7 +65,7 @@ let longPid_: AnyDuringMigration = 0; */ export function longStart(e: PointerEvent, gesture: Gesture) { longStop(); - longPid_ = setTimeout(function() { + longPid_ = setTimeout(function () { // Let the gesture route the right-click correctly. if (gesture) { gesture.handleRightClick(e); @@ -118,8 +109,10 @@ export function shouldHandleEvent(e: Event): boolean { // `click` and `contextmenu` are PointerEvents in some browsers, // despite not starting with `pointer`, but we want to always handle them // without worrying about touch identifiers. - return !(e.type.startsWith('pointer')) || - (e instanceof PointerEvent && checkTouchIdentifier(e)); + return ( + !e.type.startsWith('pointer') || + (e instanceof PointerEvent && checkTouchIdentifier(e)) + ); } /** @@ -161,93 +154,3 @@ export function checkTouchIdentifier(e: PointerEvent): boolean { // pointer was down. return false; } - -/** - * Set an event's clientX and clientY from its first changed touch. Use this to - * make a touch event work in a mouse event handler. - * - * @param e A touch event. - */ -export function setClientFromTouch(e: Event|PseudoEvent) { - deprecation.warn('setClientFromTouch()', 'version 9', 'version 10'); - // AnyDuringMigration because: Property 'changedTouches' does not exist on - // type 'PseudoEvent | Event'. - if (e.type.startsWith('touch') && (e as AnyDuringMigration).changedTouches) { - // Map the touch event's properties to the event. - // AnyDuringMigration because: Property 'changedTouches' does not exist on - // type 'PseudoEvent | Event'. - const touchPoint = (e as AnyDuringMigration).changedTouches[0]; - // AnyDuringMigration because: Property 'clientX' does not exist on type - // 'PseudoEvent | Event'. - (e as AnyDuringMigration).clientX = touchPoint.clientX; - // AnyDuringMigration because: Property 'clientY' does not exist on type - // 'PseudoEvent | Event'. - (e as AnyDuringMigration).clientY = touchPoint.clientY; - } -} - -/** - * Check whether a given event is a mouse, touch, or pointer event. - * - * @param e An event. - * @returns True if it is a mouse, touch, or pointer event; false otherwise. - */ -export function isMouseOrTouchEvent(e: Event|PseudoEvent): boolean { - deprecation.warn('isMouseOrTouchEvent()', 'version 9', 'version 10'); - return e.type.startsWith('touch') || e.type.startsWith('mouse') || - e.type.startsWith('pointer'); -} - -/** - * Check whether a given event is a touch event or a pointer event. - * - * @param e An event. - * @returns True if it is a touch or pointer event; false otherwise. - */ -export function isTouchEvent(e: Event|PseudoEvent): boolean { - deprecation.warn('isTouchEvent()', 'version 9', 'version 10'); - return e.type.startsWith('touch') || e.type.startsWith('pointer'); -} - -/** - * Split an event into an array of events, one per changed touch or mouse - * point. - * - * @param e A mouse event or a touch event with one or more changed touches. - * @returns An array of events or pseudo events. - * Each pseudo-touch event will have exactly one changed touch and there - * will be no real touch events. - */ -export function splitEventByTouches(e: Event): Array { - deprecation.warn('splitEventByTouches()', 'version 9', 'version 10'); - const events = []; - // AnyDuringMigration because: Property 'changedTouches' does not exist on - // type 'PseudoEvent | Event'. - if ((e as AnyDuringMigration).changedTouches) { - // AnyDuringMigration because: Property 'changedTouches' does not exist on - // type 'PseudoEvent | Event'. - for (let i = 0; i < (e as AnyDuringMigration).changedTouches.length; i++) { - const newEvent = { - type: e.type, - // AnyDuringMigration because: Property 'changedTouches' does not exist - // on type 'PseudoEvent | Event'. - changedTouches: [(e as AnyDuringMigration).changedTouches[i]], - target: e.target, - stopPropagation() { - e.stopPropagation(); - }, - preventDefault() { - e.preventDefault(); - }, - }; - events[i] = newEvent; - } - } else { - events.push(e); - } - // AnyDuringMigration because: Type '(Event | { type: string; changedTouches: - // Touch[]; target: EventTarget | null; stopPropagation(): void; - // preventDefault(): void; })[]' is not assignable to type '(PseudoEvent | - // Event)[]'. - return events as AnyDuringMigration; -} diff --git a/core/trashcan.ts b/core/trashcan.ts index 5d4d87f58..0794151d8 100644 --- a/core/trashcan.ts +++ b/core/trashcan.ts @@ -40,12 +40,13 @@ import {BlockInfo} from './utils/toolbox.js'; import * as toolbox from './utils/toolbox.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for a trash can. */ -export class Trashcan extends DeleteArea implements IAutoHideable, - IPositionable { +export class Trashcan + extends DeleteArea + implements IAutoHideable, IPositionable +{ /** * The unique id for this component that is used to register with the * ComponentManager. @@ -55,14 +56,14 @@ export class Trashcan extends DeleteArea implements IAutoHideable, /** * A list of JSON (stored as strings) representing blocks in the trashcan. */ - private readonly contents_: string[] = []; + private readonly contents: string[] = []; /** * The trashcan flyout. * * @internal */ - flyout: IFlyout|null = null; + flyout: IFlyout | null = null; /** Current open/close state of the lid. */ isLidOpen = false; @@ -71,28 +72,28 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * The minimum openness of the lid. Used to indicate if the trashcan * contains blocks. */ - private minOpenness_ = 0; + private minOpenness = 0; /** The SVG group containing the trash can. */ - private svgGroup_: SVGElement|null = null; + private svgGroup: SVGElement | null = null; /** The SVG image element of the trash can lid. */ - private svgLid_: SVGElement|null = null; + private svgLid: SVGElement | null = null; /** Task ID of opening/closing animation. */ - private lidTask_: ReturnType|null = null; + private lidTask: ReturnType | null = null; /** Current state of lid opening (0.0 = closed, 1.0 = open). */ - private lidOpen_ = 0; + private lidOpen = 0; /** Left coordinate of the trash can. */ - private left_ = 0; + private left = 0; /** Top coordinate of the trash can. */ - private top_ = 0; + private top = 0; /** Whether this trash can has been initialized. */ - private initialized_ = false; + private initialized = false; /** @param workspace The workspace to sit in. */ constructor(private workspace: WorkspaceSvg) { @@ -103,7 +104,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, } // Create flyout options. - const flyoutWorkspaceOptions = new Options(({ + const flyoutWorkspaceOptions = new Options({ 'scrollbars': true, 'parentWorkspace': this.workspace, 'rtl': this.workspace.RTL, @@ -113,27 +114,32 @@ export class Trashcan extends DeleteArea implements IAutoHideable, 'move': { 'scrollbars': true, }, - } as BlocklyOptions)); + } as BlocklyOptions); // Create vertical or horizontal flyout. if (this.workspace.horizontalLayout) { flyoutWorkspaceOptions.toolboxPosition = - this.workspace.toolboxPosition === toolbox.Position.TOP ? - toolbox.Position.BOTTOM : - toolbox.Position.TOP; + this.workspace.toolboxPosition === toolbox.Position.TOP + ? toolbox.Position.BOTTOM + : toolbox.Position.TOP; const HorizontalFlyout = registry.getClassFromOptions( - registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, this.workspace.options, - true); + registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, + this.workspace.options, + true + ); this.flyout = new HorizontalFlyout!(flyoutWorkspaceOptions); } else { flyoutWorkspaceOptions.toolboxPosition = - this.workspace.toolboxPosition === toolbox.Position.RIGHT ? - toolbox.Position.LEFT : - toolbox.Position.RIGHT; + this.workspace.toolboxPosition === toolbox.Position.RIGHT + ? toolbox.Position.LEFT + : toolbox.Position.RIGHT; const VerticalFlyout = registry.getClassFromOptions( - registry.Type.FLYOUTS_VERTICAL_TOOLBOX, this.workspace.options, true); + registry.Type.FLYOUTS_VERTICAL_TOOLBOX, + this.workspace.options, + true + ); this.flyout = new VerticalFlyout!(flyoutWorkspaceOptions); } - this.workspace.addChangeListener(this.onDelete_.bind(this)); + this.workspace.addChangeListener(this.onDelete.bind(this)); } /** @@ -156,63 +162,88 @@ export class Trashcan extends DeleteArea implements IAutoHideable, clip-path="url(#blocklyTrashLidClipPath837493)"> */ - this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': 'blocklyTrash'}); + this.svgGroup = dom.createSvgElement(Svg.G, {'class': 'blocklyTrash'}); let clip; const rnd = String(Math.random()).substring(2); clip = dom.createSvgElement( - Svg.CLIPPATH, {'id': 'blocklyTrashBodyClipPath' + rnd}, this.svgGroup_); + Svg.CLIPPATH, + {'id': 'blocklyTrashBodyClipPath' + rnd}, + this.svgGroup + ); dom.createSvgElement( - Svg.RECT, {'width': WIDTH, 'height': BODY_HEIGHT, 'y': LID_HEIGHT}, - clip); + Svg.RECT, + {'width': WIDTH, 'height': BODY_HEIGHT, 'y': LID_HEIGHT}, + clip + ); const body = dom.createSvgElement( - Svg.IMAGE, { - 'width': SPRITE.width, - 'x': -SPRITE_LEFT, - 'height': SPRITE.height, - 'y': -SPRITE_TOP, - 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')', - }, - this.svgGroup_); + Svg.IMAGE, + { + 'width': SPRITE.width, + 'x': -SPRITE_LEFT, + 'height': SPRITE.height, + 'y': -SPRITE_TOP, + 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')', + }, + this.svgGroup + ); body.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.workspace.options.pathToMedia + SPRITE.url); + dom.XLINK_NS, + 'xlink:href', + this.workspace.options.pathToMedia + SPRITE.url + ); clip = dom.createSvgElement( - Svg.CLIPPATH, {'id': 'blocklyTrashLidClipPath' + rnd}, this.svgGroup_); + Svg.CLIPPATH, + {'id': 'blocklyTrashLidClipPath' + rnd}, + this.svgGroup + ); dom.createSvgElement( - Svg.RECT, {'width': WIDTH, 'height': LID_HEIGHT}, clip); - this.svgLid_ = dom.createSvgElement( - Svg.IMAGE, { - 'width': SPRITE.width, - 'x': -SPRITE_LEFT, - 'height': SPRITE.height, - 'y': -SPRITE_TOP, - 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')', - }, - this.svgGroup_); - this.svgLid_.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.workspace.options.pathToMedia + SPRITE.url); + Svg.RECT, + {'width': WIDTH, 'height': LID_HEIGHT}, + clip + ); + this.svgLid = dom.createSvgElement( + Svg.IMAGE, + { + 'width': SPRITE.width, + 'x': -SPRITE_LEFT, + 'height': SPRITE.height, + 'y': -SPRITE_TOP, + 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')', + }, + this.svgGroup + ); + this.svgLid.setAttributeNS( + dom.XLINK_NS, + 'xlink:href', + this.workspace.options.pathToMedia + SPRITE.url + ); // bindEventWithChecks_ quashes events too aggressively. See: // https://groups.google.com/forum/#!topic/blockly/QF4yB9Wx00s // Using bindEventWithChecks_ for blocking mousedown causes issue in mobile. // See #4303 browserEvents.bind( - this.svgGroup_, 'pointerdown', this, this.blockMouseDownWhenOpenable_); - browserEvents.bind(this.svgGroup_, 'pointerup', this, this.click); - // Bind to body instead of this.svgGroup_ so that we don't get lid jitters - browserEvents.bind(body, 'pointerover', this, this.mouseOver_); - browserEvents.bind(body, 'pointerout', this, this.mouseOut_); - this.animateLid_(); - return this.svgGroup_; + this.svgGroup, + 'pointerdown', + this, + this.blockMouseDownWhenOpenable + ); + browserEvents.bind(this.svgGroup, 'pointerup', this, this.click); + // Bind to body instead of this.svgGroup so that we don't get lid jitters + browserEvents.bind(body, 'pointerover', this, this.mouseOver); + browserEvents.bind(body, 'pointerout', this, this.mouseOut); + this.animateLid(); + return this.svgGroup; } /** Initializes the trash can. */ init() { if (this.workspace.options.maxTrashcanContents > 0) { dom.insertAfter( - this.flyout!.createDom(Svg.SVG)!, this.workspace.getParentSvg()); + this.flyout!.createDom(Svg.SVG)!, + this.workspace.getParentSvg() + ); this.flyout!.init(this.workspace); } this.workspace.getComponentManager().addComponent({ @@ -225,25 +256,21 @@ export class Trashcan extends DeleteArea implements IAutoHideable, ComponentManager.Capability.POSITIONABLE, ], }); - this.initialized_ = true; + this.initialized = true; this.setLidOpen(false); } /** * Dispose of this trash can. * Unlink from all DOM elements to prevent memory leaks. - * - * @suppress {checkTypes} */ dispose() { this.workspace.getComponentManager().removeComponent('trashcan'); - if (this.svgGroup_) { - dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; + if (this.svgGroup) { + dom.removeNode(this.svgGroup); } - this.svgLid_ = null; - if (this.lidTask_) { - clearTimeout(this.lidTask_); + if (this.lidTask) { + clearTimeout(this.lidTask); } } @@ -252,8 +279,8 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * * @returns True if the trashcan has contents. */ - private hasContents_(): boolean { - return !!this.contents_.length; + private hasContents(): boolean { + return !!this.contents.length; } /** @@ -270,7 +297,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, if (this.contentsIsOpen()) { return; } - const contents = this.contents_.map(function(string) { + const contents = this.contents.map(function (string) { return JSON.parse(string); }); // Trashcans with lots of blocks can take a second to render. @@ -280,7 +307,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, this.flyout?.show(contents); blocklyStyle.cursor = ''; }, 10); - this.fireUiEvent_(true); + this.fireUiEvent(true); } /** Closes the trashcan flyout. */ @@ -289,7 +316,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, return; } this.flyout?.hide(); - this.fireUiEvent_(false); + this.fireUiEvent(false); this.workspace.recordDragTargets(); } @@ -312,11 +339,11 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * it will be closed. */ emptyContents() { - if (!this.hasContents_()) { + if (!this.hasContents()) { return; } - this.contents_.length = 0; - this.setMinOpenness_(0); + this.contents.length = 0; + this.setMinOpenness(0); this.closeFlyout(); } @@ -330,29 +357,43 @@ export class Trashcan extends DeleteArea implements IAutoHideable, */ position(metrics: UiMetrics, savedPositions: Rect[]) { // Not yet initialized. - if (!this.initialized_) { + if (!this.initialized) { return; } - const cornerPosition = - uiPosition.getCornerOppositeToolbox(this.workspace, metrics); + const cornerPosition = uiPosition.getCornerOppositeToolbox( + this.workspace, + metrics + ); const height = BODY_HEIGHT + LID_HEIGHT; const startRect = uiPosition.getStartPositionRect( - cornerPosition, new Size(WIDTH, height), MARGIN_HORIZONTAL, - MARGIN_VERTICAL, metrics, this.workspace); + cornerPosition, + new Size(WIDTH, height), + MARGIN_HORIZONTAL, + MARGIN_VERTICAL, + metrics, + this.workspace + ); const verticalPosition = cornerPosition.vertical; - const bumpDirection = verticalPosition === uiPosition.verticalPosition.TOP ? - uiPosition.bumpDirection.DOWN : - uiPosition.bumpDirection.UP; + const bumpDirection = + verticalPosition === uiPosition.verticalPosition.TOP + ? uiPosition.bumpDirection.DOWN + : uiPosition.bumpDirection.UP; const positionRect = uiPosition.bumpPositionRect( - startRect, MARGIN_VERTICAL, bumpDirection, savedPositions); + startRect, + MARGIN_VERTICAL, + bumpDirection, + savedPositions + ); - this.top_ = positionRect.top; - this.left_ = positionRect.left; - this.svgGroup_?.setAttribute( - 'transform', 'translate(' + this.left_ + ',' + this.top_ + ')'); + this.top = positionRect.top; + this.left = positionRect.left; + this.svgGroup?.setAttribute( + 'transform', + 'translate(' + this.left + ',' + this.top + ')' + ); } /** @@ -362,10 +403,10 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * @returns The UI elements's bounding box. Null if bounding box should be * ignored by other UI elements. */ - getBoundingRectangle(): Rect|null { - const bottom = this.top_ + BODY_HEIGHT + LID_HEIGHT; - const right = this.left_ + WIDTH; - return new Rect(this.top_, bottom, this.left_, right); + getBoundingRectangle(): Rect | null { + const bottom = this.top + BODY_HEIGHT + LID_HEIGHT; + const right = this.left + WIDTH; + return new Rect(this.top, bottom, this.left, right); } /** @@ -375,12 +416,12 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * @returns The component's bounding box. Null if drag target area should be * ignored. */ - override getClientRect(): Rect|null { - if (!this.svgGroup_) { + override getClientRect(): Rect | null { + if (!this.svgGroup) { return null; } - const trashRect = this.svgGroup_.getBoundingClientRect(); + const trashRect = this.svgGroup.getBoundingClientRect(); const top = trashRect.top + SPRITE_TOP - MARGIN_HOTSPOT; const bottom = top + LID_HEIGHT + BODY_HEIGHT + 2 * MARGIN_HOTSPOT; const left = trashRect.left + SPRITE_LEFT - MARGIN_HOTSPOT; @@ -427,32 +468,34 @@ export class Trashcan extends DeleteArea implements IAutoHideable, if (this.isLidOpen === state) { return; } - if (this.lidTask_) { - clearTimeout(this.lidTask_); + if (this.lidTask) { + clearTimeout(this.lidTask); } this.isLidOpen = state; - this.animateLid_(); + this.animateLid(); } /** Rotate the lid open or closed by one step. Then wait and recurse. */ - private animateLid_() { + private animateLid() { const frames = ANIMATION_FRAMES; const delta = 1 / (frames + 1); - this.lidOpen_ += this.isLidOpen ? delta : -delta; - this.lidOpen_ = Math.min(Math.max(this.lidOpen_, this.minOpenness_), 1); + this.lidOpen += this.isLidOpen ? delta : -delta; + this.lidOpen = Math.min(Math.max(this.lidOpen, this.minOpenness), 1); - this.setLidAngle_(this.lidOpen_ * MAX_LID_ANGLE); + this.setLidAngle(this.lidOpen * MAX_LID_ANGLE); // Linear interpolation between min and max. - const opacity = OPACITY_MIN + this.lidOpen_ * (OPACITY_MAX - OPACITY_MIN); - if (this.svgGroup_) { - this.svgGroup_.style.opacity = `${opacity}`; + const opacity = OPACITY_MIN + this.lidOpen * (OPACITY_MAX - OPACITY_MIN); + if (this.svgGroup) { + this.svgGroup.style.opacity = `${opacity}`; } - if (this.lidOpen_ > this.minOpenness_ && this.lidOpen_ < 1) { - this.lidTask_ = - setTimeout(this.animateLid_.bind(this), ANIMATION_LENGTH / frames); + if (this.lidOpen > this.minOpenness && this.lidOpen < 1) { + this.lidTask = setTimeout( + this.animateLid.bind(this), + ANIMATION_LENGTH / frames + ); } } @@ -461,14 +504,20 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * * @param lidAngle The angle at which to set the lid. */ - private setLidAngle_(lidAngle: number) { + private setLidAngle(lidAngle: number) { const openAtRight = - this.workspace.toolboxPosition === toolbox.Position.RIGHT || - this.workspace.horizontalLayout && this.workspace.RTL; - this.svgLid_?.setAttribute( - 'transform', - 'rotate(' + (openAtRight ? -lidAngle : lidAngle) + ',' + - (openAtRight ? 4 : WIDTH - 4) + ',' + (LID_HEIGHT - 2) + ')'); + this.workspace.toolboxPosition === toolbox.Position.RIGHT || + (this.workspace.horizontalLayout && this.workspace.RTL); + this.svgLid?.setAttribute( + 'transform', + 'rotate(' + + (openAtRight ? -lidAngle : lidAngle) + + ',' + + (openAtRight ? 4 : WIDTH - 4) + + ',' + + (LID_HEIGHT - 2) + + ')' + ); } /** @@ -478,10 +527,10 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * @param newMin The new minimum openness of the lid. Should be between 0 * and 1. */ - private setMinOpenness_(newMin: number) { - this.minOpenness_ = newMin; + private setMinOpenness(newMin: number) { + this.minOpenness = newMin; if (!this.isLidOpen) { - this.setLidAngle_(newMin * MAX_LID_ANGLE); + this.setLidAngle(newMin * MAX_LID_ANGLE); } } @@ -495,7 +544,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, /** Inspect the contents of the trash. */ click() { - if (!this.hasContents_()) { + if (!this.hasContents()) { return; } this.openFlyout(); @@ -506,9 +555,11 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * * @param trashcanOpen Whether the flyout is opening. */ - private fireUiEvent_(trashcanOpen: boolean) { + private fireUiEvent(trashcanOpen: boolean) { const uiEvent = new (eventUtils.get(eventUtils.TRASHCAN_OPEN))( - trashcanOpen, this.workspace.id); + trashcanOpen, + this.workspace.id + ); eventUtils.fire(uiEvent); } @@ -517,8 +568,8 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * * @param e A mouse down event. */ - private blockMouseDownWhenOpenable_(e: PointerEvent) { - if (!this.contentsIsOpen() && this.hasContents_()) { + private blockMouseDownWhenOpenable(e: PointerEvent) { + if (!this.contentsIsOpen() && this.hasContents()) { // Don't start a workspace scroll. e.stopPropagation(); } @@ -527,8 +578,8 @@ export class Trashcan extends DeleteArea implements IAutoHideable, /** * Indicate that the trashcan can be clicked (by opening it) if it has blocks. */ - private mouseOver_() { - if (this.hasContents_()) { + private mouseOver() { + if (this.hasContents()) { this.setLidOpen(true); } } @@ -537,7 +588,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * Close the lid of the trashcan if it was open (Vis. it was indicating it had * blocks). */ - private mouseOut_() { + private mouseOut() { // No need to do a .hasBlocks check here because if it doesn't the trashcan // won't be open in the first place, and setOpen won't run. this.setLidOpen(false); @@ -549,9 +600,11 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * * @param event Workspace event. */ - private onDelete_(event: Abstract) { - if (this.workspace.options.maxTrashcanContents <= 0 || - event.type !== eventUtils.BLOCK_DELETE) { + private onDelete(event: Abstract) { + if ( + this.workspace.options.maxTrashcanContents <= 0 || + event.type !== eventUtils.BLOCK_DELETE + ) { return; } const deleteEvent = event as BlockDelete; @@ -559,18 +612,20 @@ export class Trashcan extends DeleteArea implements IAutoHideable, if (!deleteEvent.oldJson) { throw new Error('Encountered a delete event without proper oldJson'); } - const cleanedJson = - JSON.stringify(this.cleanBlockJson_(deleteEvent.oldJson)); - if (this.contents_.indexOf(cleanedJson) !== -1) { + const cleanedJson = JSON.stringify( + this.cleanBlockJson(deleteEvent.oldJson) + ); + if (this.contents.indexOf(cleanedJson) !== -1) { return; } - this.contents_.unshift(cleanedJson); - while (this.contents_.length > - this.workspace.options.maxTrashcanContents) { - this.contents_.pop(); + this.contents.unshift(cleanedJson); + while ( + this.contents.length > this.workspace.options.maxTrashcanContents + ) { + this.contents.pop(); } - this.setMinOpenness_(HAS_BLOCKS_LID_ANGLE); + this.setMinOpenness(HAS_BLOCKS_LID_ANGLE); } } @@ -582,7 +637,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable, * @returns A BlockInfo object corresponding to the JSON, cleaned of all * unnecessary attributes. */ - private cleanBlockJson_(json: blocks.State): BlockInfo { + private cleanBlockJson(json: blocks.State): BlockInfo { // Create a deep copy. json = JSON.parse(JSON.stringify(json)) as blocks.State; @@ -643,7 +698,6 @@ export class Trashcan extends DeleteArea implements IAutoHideable, } } - /** Width of both the trash can and lid images. */ const WIDTH = 47; diff --git a/core/utils.ts b/core/utils.ts index cb03341f0..02f27e11f 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -7,9 +7,7 @@ import * as goog from '../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils'); -import type {Block} from './block.js'; import * as browserEvents from './browser_events.js'; -import * as common from './common.js'; import * as extensions from './extensions.js'; import * as aria from './utils/aria.js'; import * as arrayUtils from './utils/array.js'; @@ -33,8 +31,6 @@ import * as svgPaths from './utils/svg_paths.js'; import * as toolbox from './utils/toolbox.js'; import * as userAgent from './utils/useragent.js'; import * as xml from './utils/xml.js'; -import type {WorkspaceSvg} from './workspace_svg.js'; - export { aria, @@ -62,214 +58,3 @@ export { userAgent, xml, }; - -/** - * Return the coordinates of the top-left corner of this element relative to - * its parent. Only for SVG elements and children (e.g. rect, g, path). - * - * @param element SVG element to find the coordinates of. - * @returns Object with .x and .y properties. - * @deprecated Use **Blockly.utils.svgMath.getRelativeXY** instead. - */ -export function getRelativeXY(element: Element): Coordinate { - deprecation.warn( - 'Blockly.utils.getRelativeXY', 'December 2021', 'December 2022', - 'Blockly.utils.svgMath.getRelativeXY'); - return svgMath.getRelativeXY(element); -} - -/** - * Return the coordinates of the top-left corner of this element relative to - * the div Blockly was injected into. - * - * @param element SVG element to find the coordinates of. If this is not a child - * of the div Blockly was injected into, the behaviour is undefined. - * @returns Object with .x and .y properties. - * @deprecated Use **Blockly.utils.svgMath.getInjectionDivXY** instead. - */ -function getInjectionDivXY(element: Element): Coordinate { - deprecation.warn( - 'Blockly.utils.getInjectionDivXY_', 'December 2021', 'December 2022', - 'Blockly.utils.svgMath.getInjectionDivXY'); - return svgMath.getInjectionDivXY(element); -} -export const getInjectionDivXY_ = getInjectionDivXY; - -/** - * Parse a string with any number of interpolation tokens (%1, %2, ...). - * It will also replace string table references (e.g., %{bky_my_msg} and - * %{BKY_MY_MSG} will both be replaced with the value in - * Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped - * (e.g., '%%'). - * - * @param message Text which might contain string table references and - * interpolation tokens. - * @returns Array of strings and numbers. - * @deprecated Use **Blockly.utils.parsing.tokenizeInterpolation** instead. - */ -export function tokenizeInterpolation(message: string): Array { - deprecation.warn( - 'Blockly.utils.tokenizeInterpolation', 'December 2021', 'December 2022', - 'Blockly.utils.parsing.tokenizeInterpolation'); - return parsing.tokenizeInterpolation(message); -} - -/** - * Replaces string table references in a message, if the message is a string. - * For example, "%{bky_my_msg}" and "%{BKY_MY_MSG}" will both be replaced with - * the value in Msg['MY_MSG']. - * - * @param message Message, which may be a string that contains string table - * references. - * @returns String with message references replaced. - * @deprecated Use **Blockly.utils.parsing.replaceMessageReferences** instead. - */ -export function replaceMessageReferences(message: string|any): string { - deprecation.warn( - 'Blockly.utils.replaceMessageReferences', 'December 2021', - 'December 2022', 'Blockly.utils.parsing.replaceMessageReferences'); - return parsing.replaceMessageReferences(message); -} - -/** - * Validates that any %{MSG_KEY} references in the message refer to keys of - * the Msg string table. - * - * @param message Text which might contain string table references. - * @returns True if all message references have matching values. - * Otherwise, false. - * @deprecated Use **Blockly.utils.parsing.checkMessageReferences** instead. - */ -export function checkMessageReferences(message: string): boolean { - deprecation.warn( - 'Blockly.utils.checkMessageReferences', 'December 2021', 'December 2022', - 'Blockly.utils.parsing.checkMessageReferences'); - return parsing.checkMessageReferences(message); -} - -/** - * Check if 3D transforms are supported by adding an element - * and attempting to set the property. - * - * @returns True if 3D transforms are supported. - * @deprecated Use **Blockly.utils.svgMath.is3dSupported** instead. - */ -export function is3dSupported(): boolean { - deprecation.warn( - 'Blockly.utils.is3dSupported', 'December 2021', 'December 2022', - 'Blockly.utils.svgMath.is3dSupported'); - return svgMath.is3dSupported(); -} - -/** - * Get the position of the current viewport in window coordinates. This takes - * scroll into account. - * - * @returns An object containing window width, height, and scroll position in - * window coordinates. - * @deprecated Use **Blockly.utils.svgMath.getViewportBBox** instead. - * @internal - */ -export function getViewportBBox(): Rect { - deprecation.warn( - 'Blockly.utils.getViewportBBox', 'December 2021', 'December 2022', - 'Blockly.utils.svgMath.getViewportBBox'); - return svgMath.getViewportBBox(); -} - -/** - * Removes the first occurrence of a particular value from an array. - * - * @param arr Array from which to remove value. - * @param value Value to remove. - * @returns True if an element was removed. - * @deprecated Use **Blockly.array.removeElem** instead. - * @internal - */ -export function arrayRemove(arr: Array, value: T): boolean { - deprecation.warn( - 'Blockly.utils.arrayRemove', 'December 2021', 'December 2022', - 'Blockly.array.removeElem'); - return arrayUtils.removeElem(arr, value); -} - -/** - * Gets the document scroll distance as a coordinate object. - * Copied from Closure's goog.dom.getDocumentScroll. - * - * @returns Object with values 'x' and 'y'. - * @deprecated Use **Blockly.utils.svgMath.getDocumentScroll** instead. - */ -export function getDocumentScroll(): Coordinate { - deprecation.warn( - 'Blockly.utils.getDocumentScroll', 'December 2021', 'December 2022', - 'Blockly.utils.svgMath.getDocumentScroll'); - return svgMath.getDocumentScroll(); -} - -/** - * Get a map of all the block's descendants mapping their type to the number of - * children with that type. - * - * @param block The block to map. - * @param opt_stripFollowing Optionally ignore all following statements (blocks - * that are not inside a value or statement input of the block). - * @returns Map of types to type counts for descendants of the bock. - * @deprecated Use **Blockly.common.getBlockTypeCounts** instead. - */ -export function getBlockTypeCounts( - block: Block, opt_stripFollowing?: boolean): {[key: string]: number} { - deprecation.warn( - 'Blockly.utils.getBlockTypeCounts', 'December 2021', 'December 2022', - 'Blockly.common.getBlockTypeCounts'); - return common.getBlockTypeCounts(block, opt_stripFollowing); -} - -/** - * Converts screen coordinates to workspace coordinates. - * - * @param ws The workspace to find the coordinates on. - * @param screenCoordinates The screen coordinates to be converted to workspace - * coordinates - * @deprecated Use **Blockly.utils.svgMath.screenToWsCoordinates** instead. - * @returns The workspace coordinates. - */ -export function screenToWsCoordinates( - ws: WorkspaceSvg, screenCoordinates: Coordinate): Coordinate { - deprecation.warn( - 'Blockly.utils.screenToWsCoordinates', 'December 2021', 'December 2022', - 'Blockly.utils.svgMath.screenToWsCoordinates'); - return svgMath.screenToWsCoordinates(ws, screenCoordinates); -} - -/** - * Parse a block colour from a number or string, as provided in a block - * definition. - * - * @param colour HSV hue value (0 to 360), #RRGGBB string, or a message - * reference string pointing to one of those two values. - * @returns An object containing the colour as a #RRGGBB string, and the hue if - * the input was an HSV hue value. - * @throws {Error} If the colour cannot be parsed. - * @deprecated Use **Blockly.utils.parsing.parseBlockColour** instead. - */ -export function parseBlockColour(colour: number| - string): {hue: number|null, hex: string} { - deprecation.warn( - 'Blockly.utils.parseBlockColour', 'December 2021', 'December 2022', - 'Blockly.utils.parsing.parseBlockColour'); - return parsing.parseBlockColour(colour); -} - -/** - * Calls a function after the page has loaded, possibly immediately. - * - * @param fn Function to run. - * @throws Error Will throw if no global document can be found (e.g., Node.js). - * @deprecated No longer provided by Blockly. - */ -export function runAfterPageLoad(fn: () => void) { - deprecation.warn( - 'Blockly.utils.runAfterPageLoad', 'December 2021', 'December 2022'); - extensions.runAfterPageLoad(fn); -} diff --git a/core/utils/aria.ts b/core/utils/aria.ts index e43190709..e71194fa7 100644 --- a/core/utils/aria.ts +++ b/core/utils/aria.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.aria'); - /** ARIA states/properties prefix. */ const ARIA_PREFIX = 'aria-'; @@ -49,7 +48,7 @@ export enum Role { TREE = 'tree', // ARIA role for a tree item that sometimes may be expanded or collapsed. - TREEITEM = 'treeitem' + TREEITEM = 'treeitem', } /** @@ -108,7 +107,7 @@ export enum State { VALUEMAX = 'valuemax', // ARIA property for slider minimum value. Value: number. - VALUEMIN = 'valuemin' + VALUEMIN = 'valuemin', } /** @@ -134,7 +133,10 @@ export function setRole(element: Element, roleName: Role) { * @param value Value for the state attribute. */ export function setState( - element: Element, stateName: State, value: string|boolean|number|string[]) { + element: Element, + stateName: State, + value: string | boolean | number | string[] +) { if (Array.isArray(value)) { value = value.join(' '); } diff --git a/core/utils/array.ts b/core/utils/array.ts index faee551e4..f0d451232 100644 --- a/core/utils/array.ts +++ b/core/utils/array.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.array'); - /** * Removes the first occurrence of a particular value from an array. * diff --git a/core/utils/colour.ts b/core/utils/colour.ts index 0e9d91017..97f195858 100644 --- a/core/utils/colour.ts +++ b/core/utils/colour.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.colour'); - /** * The richness of block colours, regardless of the hue. * Must be in the range of 0 (inclusive) to 1 (exclusive). @@ -74,7 +73,7 @@ export function setHsvValue(newValue: number) { * @returns A string containing a hex representation of the colour, or null if * can't be parsed. */ -export function parse(str: string|number): string|null { +export function parse(str: string | number): string | null { str = `${str}`.toLowerCase().trim(); let hex = names[str]; if (hex) { @@ -113,7 +112,7 @@ export function parse(str: string|number): string|null { * @returns Hex representation of the colour. */ export function rgbToHex(r: number, g: number, b: number): string { - const rgb = r << 16 | g << 8 | b; + const rgb = (r << 16) | (g << 8) | b; if (r < 0x10) { return '#' + (0x1000000 | rgb).toString(16).substr(1); } @@ -135,7 +134,7 @@ export function hexToRgb(colour: string): number[] { const rgb = parseInt(hex.substr(1), 16); const r = rgb >> 16; - const g = rgb >> 8 & 255; + const g = (rgb >> 8) & 255; const b = rgb & 255; return [r, g, b]; @@ -210,8 +209,11 @@ export function hsvToHex(h: number, s: number, v: number): string { * Values should be in the range [0, 1]. * @returns Combined colour represented in hex. */ -export function blend(colour1: string, colour2: string, factor: number): string| - null { +export function blend( + colour1: string, + colour2: string, + factor: number +): string | null { const hex1 = parse(colour1); if (!hex1) { return null; diff --git a/core/utils/coordinate.ts b/core/utils/coordinate.ts index 12d422b70..4044a89ca 100644 --- a/core/utils/coordinate.ts +++ b/core/utils/coordinate.ts @@ -14,7 +14,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.Coordinate'); - /** * Class for representing coordinates and positions. */ @@ -67,7 +66,7 @@ export class Coordinate { * @param b A Coordinate. * @returns True iff the coordinates are equal, or if both are null. */ - static equals(a?: Coordinate|null, b?: Coordinate|null): boolean { + static equals(a?: Coordinate | null, b?: Coordinate | null): boolean { if (a === b) { return true; } @@ -108,8 +107,10 @@ export class Coordinate { * @param b An x/y coordinate. * @returns A Coordinate representing the difference between `a` and `b`. */ - static difference(a: Coordinate|SVGPoint, b: Coordinate|SVGPoint): - Coordinate { + static difference( + a: Coordinate | SVGPoint, + b: Coordinate | SVGPoint + ): Coordinate { return new Coordinate(a.x - b.x, a.y - b.y); } @@ -120,7 +121,7 @@ export class Coordinate { * @param b An x/y coordinate. * @returns A Coordinate representing the sum of the two coordinates. */ - static sum(a: Coordinate|SVGPoint, b: Coordinate|SVGPoint): Coordinate { + static sum(a: Coordinate | SVGPoint, b: Coordinate | SVGPoint): Coordinate { return new Coordinate(a.x + b.x, a.y + b.y); } } diff --git a/core/utils/deprecation.ts b/core/utils/deprecation.ts index b09e5bc6a..6c800d68e 100644 --- a/core/utils/deprecation.ts +++ b/core/utils/deprecation.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.deprecation'); - /** * Warn developers that a function or property is deprecated. * @@ -20,10 +19,18 @@ goog.declareModuleId('Blockly.utils.deprecation'); * @internal */ export function warn( - name: string, deprecationDate: string, deletionDate: string, - opt_use?: string) { - let msg = name + ' was deprecated in ' + deprecationDate + - ' and will be deleted in ' + deletionDate + '.'; + name: string, + deprecationDate: string, + deletionDate: string, + opt_use?: string +) { + let msg = + name + + ' was deprecated in ' + + deprecationDate + + ' and will be deleted in ' + + deletionDate + + '.'; if (opt_use) { msg += '\nUse ' + opt_use + ' instead.'; } diff --git a/core/utils/dom.ts b/core/utils/dom.ts index 363000480..22f490189 100644 --- a/core/utils/dom.ts +++ b/core/utils/dom.ts @@ -7,9 +7,9 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.dom'); +import * as deprecation from './deprecation.js'; import type {Svg} from './svg.js'; - /** * Required name space for SVG elements. */ @@ -33,17 +33,16 @@ export enum NodeType { ELEMENT_NODE = 1, TEXT_NODE = 3, COMMENT_NODE = 8, - DOCUMENT_POSITION_CONTAINED_BY = 16 } /** Temporary cache of text widths. */ -let cacheWidths: {[key: string]: number}|null = null; +let cacheWidths: {[key: string]: number} | null = null; /** Number of current references to cache. */ let cacheReference = 0; /** A HTML canvas context used for computing text width. */ -let canvasContext: CanvasRenderingContext2D|null = null; +let canvasContext: CanvasRenderingContext2D | null = null; /** * Helper method for creating SVG elements. @@ -54,8 +53,10 @@ let canvasContext: CanvasRenderingContext2D|null = null; * @returns if name is a string or a more specific type if it a member of Svg. */ export function createSvgElement( - name: string|Svg, attrs: {[key: string]: string|number}, - opt_parent?: Element|null): T { + name: string | Svg, + attrs: {[key: string]: string | number}, + opt_parent?: Element | null +): T { const e = document.createElementNS(SVG_NS, `${name}`) as T; for (const key in attrs) { e.setAttribute(key, `${attrs[key]}`); @@ -130,7 +131,7 @@ export function hasClass(element: Element, className: string): boolean { * @returns The node removed if removed; else, null. */ // Copied from Closure goog.dom.removeNode -export function removeNode(node: Node|null): Node|null { +export function removeNode(node: Node | null): Node | null { return node && node.parentNode ? node.parentNode.removeChild(node) : null; } @@ -160,11 +161,16 @@ export function insertAfter(newNode: Element, refNode: Element) { * @param parent The node that should contain the other node. * @param descendant The node to test presence of. * @returns Whether the parent node contains the descendant node. + * @deprecated Use native 'contains' DOM method. */ export function containsNode(parent: Node, descendant: Node): boolean { - return !!( - parent.compareDocumentPosition(descendant) & - NodeType.DOCUMENT_POSITION_CONTAINED_BY); + deprecation.warn( + 'Blockly.utils.dom.containsNode', + 'version 10', + 'version 11', + 'Use native "contains" DOM method' + ); + return parent.contains(descendant); } /** @@ -176,7 +182,9 @@ export function containsNode(parent: Node, descendant: Node): boolean { * @param transform The value of the CSS `transform` property. */ export function setCssTransform( - element: HTMLElement|SVGElement, transform: string) { + element: HTMLElement | SVGElement, + transform: string +) { element.style['transform'] = transform; element.style['-webkit-transform' as any] = transform; } @@ -250,10 +258,17 @@ export function getTextWidth(textElement: SVGTextElement): number { * @returns Width of element. */ export function getFastTextWidth( - textElement: SVGTextElement, fontSize: number, fontWeight: string, - fontFamily: string): number { + textElement: SVGTextElement, + fontSize: number, + fontWeight: string, + fontFamily: string +): number { return getFastTextWidthWithSizeString( - textElement, fontSize + 'pt', fontWeight, fontFamily); + textElement, + fontSize + 'pt', + fontWeight, + fontFamily + ); } /** @@ -270,8 +285,11 @@ export function getFastTextWidth( * @returns Width of element. */ export function getFastTextWidthWithSizeString( - textElement: SVGTextElement, fontSize: string, fontWeight: string, - fontFamily: string): number { + textElement: SVGTextElement, + fontSize: string, + fontWeight: string, + fontFamily: string +): number { const text = textElement.textContent; const key = text + '\n' + textElement.className.baseVal; let width; @@ -286,7 +304,7 @@ export function getFastTextWidthWithSizeString( if (!canvasContext) { // Inject the canvas element used for computing text widths. - const computeCanvas = (document.createElement('canvas')); + const computeCanvas = document.createElement('canvas'); computeCanvas.className = 'blocklyComputeCanvas'; document.body.appendChild(computeCanvas); @@ -322,17 +340,20 @@ export function getFastTextWidthWithSizeString( * @returns Font measurements. */ export function measureFontMetrics( - text: string, fontSize: string, fontWeight: string, - fontFamily: string): {height: number, baseline: number} { - const span = (document.createElement('span')); + text: string, + fontSize: string, + fontWeight: string, + fontFamily: string +): {height: number; baseline: number} { + const span = document.createElement('span'); span.style.font = fontWeight + ' ' + fontSize + ' ' + fontFamily; span.textContent = text; - const block = (document.createElement('div')); + const block = document.createElement('div'); block.style.width = '1px'; block.style.height = '0'; - const div = (document.createElement('div')); + const div = document.createElement('div'); div.setAttribute('style', 'position: fixed; top: 0; left: 0; display: flex;'); div.appendChild(span); div.appendChild(block); diff --git a/core/utils/idgenerator.ts b/core/utils/idgenerator.ts index 3f7d66f5a..e928b30af 100644 --- a/core/utils/idgenerator.ts +++ b/core/utils/idgenerator.ts @@ -14,8 +14,9 @@ goog.declareModuleId('Blockly.utils.idGenerator'); * soup will be denied. That's your failure to properly escape in * your own environment. Issues #251, #625, #682, #1304. */ -const soup = '!#$%()*+,-./:;=?@[]^_`{|}~' + - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +const soup = + '!#$%()*+,-./:;=?@[]^_`{|}~' + + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; /** * Namespace object for internal implementations we want to be able to diff --git a/core/utils/keycodes.ts b/core/utils/keycodes.ts index b6e4baf63..1feef692f 100644 --- a/core/utils/keycodes.ts +++ b/core/utils/keycodes.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.KeyCodes'); - /** * Key codes for common characters. * @@ -21,7 +20,7 @@ export enum KeyCodes { MAC_ENTER = 3, BACKSPACE = 8, TAB = 9, - NUM_CENTER = 12, // NUMLOCK on FF/Safari Mac + NUM_CENTER = 12, // NUMLOCK on FF/Safari Mac ENTER = 13, SHIFT = 16, CTRL = 17, @@ -30,18 +29,18 @@ export enum KeyCodes { CAPS_LOCK = 20, ESC = 27, SPACE = 32, - PAGE_UP = 33, // also NUM_NORTH_EAST - PAGE_DOWN = 34, // also NUM_SOUTH_EAST - END = 35, // also NUM_SOUTH_WEST - HOME = 36, // also NUM_NORTH_WEST - LEFT = 37, // also NUM_WEST - UP = 38, // also NUM_NORTH - RIGHT = 39, // also NUM_EAST - DOWN = 40, // also NUM_SOUTH - PLUS_SIGN = 43, // NOT numpad plus + PAGE_UP = 33, // also NUM_NORTH_EAST + PAGE_DOWN = 34, // also NUM_SOUTH_EAST + END = 35, // also NUM_SOUTH_WEST + HOME = 36, // also NUM_NORTH_WEST + LEFT = 37, // also NUM_WEST + UP = 38, // also NUM_NORTH + RIGHT = 39, // also NUM_EAST + DOWN = 40, // also NUM_SOUTH + PLUS_SIGN = 43, // NOT numpad plus PRINT_SCREEN = 44, - INSERT = 45, // also NUM_INSERT - DELETE = 46, // also NUM_DELETE + INSERT = 45, // also NUM_INSERT + DELETE = 46, // also NUM_DELETE ZERO = 48, ONE = 49, TWO = 50, @@ -52,13 +51,13 @@ export enum KeyCodes { SEVEN = 55, EIGHT = 56, NINE = 57, - FF_SEMICOLON = 59, // Firefox (Gecko) fires this for semicolon instead of 186 - FF_EQUALS = 61, // Firefox (Gecko) fires this for equals instead of 187 - FF_DASH = 173, // Firefox (Gecko) fires this for dash instead of 189 + FF_SEMICOLON = 59, // Firefox (Gecko) fires this for semicolon instead of 186 + FF_EQUALS = 61, // Firefox (Gecko) fires this for equals instead of 187 + FF_DASH = 173, // Firefox (Gecko) fires this for dash instead of 189 // Firefox (Gecko) fires this for # on UK keyboards, rather than // Shift+SINGLE_QUOTE. FF_HASH = 163, - QUESTION_MARK = 63, // needs localization + QUESTION_MARK = 63, // needs localization AT_SIGN = 64, A = 65, B = 66, @@ -86,7 +85,7 @@ export enum KeyCodes { X = 88, Y = 89, Z = 90, - META = 91, // WIN_KEY_LEFT + META = 91, // WIN_KEY_LEFT WIN_KEY_RIGHT = 92, CONTEXT_MENU = 93, NUM_ZERO = 96, @@ -123,23 +122,22 @@ export enum KeyCodes { FIRST_MEDIA_KEY = 166, LAST_MEDIA_KEY = 183, - SEMICOLON = 186, // needs localization - DASH = 189, // needs localization - EQUALS = 187, // needs localization - COMMA = 188, // needs localization - PERIOD = 190, // needs localization - SLASH = 191, // needs localization - APOSTROPHE = 192, // needs localization - TILDE = 192, // needs localization - SINGLE_QUOTE = 222, // needs localization - OPEN_SQUARE_BRACKET = 219, // needs localization - BACKSLASH = 220, // needs localization - CLOSE_SQUARE_BRACKET = 221, // needs localization + SEMICOLON = 186, // needs localization + DASH = 189, // needs localization + EQUALS = 187, // needs localization + COMMA = 188, // needs localization + PERIOD = 190, // needs localization + SLASH = 191, // needs localization + APOSTROPHE = 192, // needs localization + TILDE = 192, // needs localization + SINGLE_QUOTE = 222, // needs localization + OPEN_SQUARE_BRACKET = 219, // needs localization + BACKSLASH = 220, // needs localization + CLOSE_SQUARE_BRACKET = 221, // needs localization WIN_KEY = 224, - MAC_FF_META = - 224, // Firefox (Gecko) fires this for the meta key instead of 91 - MAC_WK_CMD_LEFT = 91, // WebKit Left Command key fired, same as META - MAC_WK_CMD_RIGHT = 93, // WebKit Right Command key fired, different from META + MAC_FF_META = 224, // Firefox (Gecko) fires this for the meta key instead of 91 + MAC_WK_CMD_LEFT = 91, // WebKit Left Command key fired, same as META + MAC_WK_CMD_RIGHT = 93, // WebKit Right Command key fired, different from META WIN_IME = 229, // "Reserved for future use". Some programs (e.g. the SlingPlayer 2.4 ActiveX diff --git a/core/utils/math.ts b/core/utils/math.ts index 1704c3ae3..da7628e18 100644 --- a/core/utils/math.ts +++ b/core/utils/math.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.math'); - /** * Converts degrees to radians. * Copied from Closure's goog.math.toRadians. @@ -16,7 +15,7 @@ goog.declareModuleId('Blockly.utils.math'); * @returns Angle in radians. */ export function toRadians(angleDegrees: number): number { - return angleDegrees * Math.PI / 180; + return (angleDegrees * Math.PI) / 180; } /** @@ -27,7 +26,7 @@ export function toRadians(angleDegrees: number): number { * @returns Angle in degrees. */ export function toDegrees(angleRadians: number): number { - return angleRadians * 180 / Math.PI; + return (angleRadians * 180) / Math.PI; } /** @@ -39,7 +38,10 @@ export function toDegrees(angleRadians: number): number { * @returns The clamped number. */ export function clamp( - lowerBound: number, number: number, upperBound: number): number { + lowerBound: number, + number: number, + upperBound: number +): number { if (upperBound < lowerBound) { const temp = upperBound; upperBound = lowerBound; diff --git a/core/utils/metrics.ts b/core/utils/metrics.ts index d83e2b8a0..5f73aeec1 100644 --- a/core/utils/metrics.ts +++ b/core/utils/metrics.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.Metrics'); - export interface Metrics { /** Height of the visible portion of the workspace. */ viewHeight: number; diff --git a/core/utils/object.ts b/core/utils/object.ts index a36ea18c2..2fc6330c8 100644 --- a/core/utils/object.ts +++ b/core/utils/object.ts @@ -7,52 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.object'); -import * as deprecation from './deprecation.js'; - - -/** - * Inherit the prototype methods from one constructor into another. - * - * @param childCtor Child class. - * @param parentCtor Parent class. - * @suppress {strictMissingProperties} superClass_ is not defined on Function. - * @deprecated No longer provided by Blockly. - */ -export function inherits(childCtor: Function, parentCtor: Function) { - deprecation.warn('Blockly.utils.object.inherits', 'version 9', 'version 10'); - // Set a .superClass_ property so that methods can call parent methods - // without hard-coding the parent class name. - // Could be replaced by ES6's super(). - // AnyDuringMigration because: Property 'superClass_' does not exist on type - // 'Function'. - (childCtor as AnyDuringMigration).superClass_ = parentCtor.prototype; - - // Link the child class to the parent class so that static methods inherit. - Object.setPrototypeOf(childCtor, parentCtor); - - // Replace the child constructor's prototype object with an instance - // of the parent class. - childCtor.prototype = Object.create(parentCtor.prototype); - childCtor.prototype.constructor = childCtor; -} -// Alternatively, one could use this instead: -// Object.setPrototypeOf(childCtor.prototype, parentCtor.prototype); - -/** - * Copies all the members of a source object to a target object. - * - * @param target Target. - * @param source Source. - * @deprecated Use the built-in **Object.assign** instead. - */ -export function mixin(target: AnyDuringMigration, source: AnyDuringMigration) { - deprecation.warn( - 'Blockly.utils.object.mixin', 'May 2022', 'May 2023', 'Object.assign'); - for (const x in source) { - target[x] = source[x]; - } -} - /** * Complete a deep merge of all members of a source object with a target object. * @@ -61,8 +15,9 @@ export function mixin(target: AnyDuringMigration, source: AnyDuringMigration) { * @returns The resulting object. */ export function deepMerge( - target: AnyDuringMigration, - source: AnyDuringMigration): AnyDuringMigration { + target: AnyDuringMigration, + source: AnyDuringMigration +): AnyDuringMigration { for (const x in source) { if (source[x] !== null && typeof source[x] === 'object') { target[x] = deepMerge(target[x] || Object.create(null), source[x]); @@ -72,17 +27,3 @@ export function deepMerge( } return target; } - -/** - * Returns an array of a given object's own enumerable property values. - * - * @param obj Object containing values. - * @returns Array of values. - * @deprecated Use the built-in **Object.values** instead. - */ -export function values(obj: AnyDuringMigration): AnyDuringMigration[] { - deprecation.warn( - 'Blockly.utils.object.values', 'version 9', 'version 10', - 'Object.values'); - return Object.values(obj); -} diff --git a/core/utils/parsing.ts b/core/utils/parsing.ts index bd5160cf3..9f41a14df 100644 --- a/core/utils/parsing.ts +++ b/core/utils/parsing.ts @@ -11,7 +11,6 @@ import {Msg} from '../msg.js'; import * as colourUtils from './colour.js'; - /** * Internal implementation of the message reference and interpolation token * parsing used by tokenizeInterpolation() and replaceMessageReferences(). @@ -23,10 +22,12 @@ import * as colourUtils from './colour.js'; * @returns Array of strings and numbers. */ function tokenizeInterpolationInternal( - message: string, parseInterpolationTokens: boolean): (string|number)[] { + message: string, + parseInterpolationTokens: boolean +): (string | number)[] { const tokens = []; const chars = message.split(''); - chars.push(''); // End marker. + chars.push(''); // End marker. // Parse the message with a finite state machine. // 0 - Base case. // 1 - % found. @@ -37,7 +38,8 @@ function tokenizeInterpolationInternal( let number = null; for (let i = 0; i < chars.length; i++) { const c = chars[i]; - if (state === 0) { // Start escape. + if (state === 0) { + // Start escape. if (c === '%') { const text = buffer.join(''); if (text) { @@ -46,11 +48,11 @@ function tokenizeInterpolationInternal( buffer.length = 0; state = 1; } else { - buffer.push(c); // Regular char. + buffer.push(c); // Regular char. } } else if (state === 1) { if (c === '%') { - buffer.push(c); // Escaped %: %% + buffer.push(c); // Escaped %: %% state = 0; } else if (parseInterpolationTokens && '0' <= c && c <= '9') { state = 2; @@ -63,45 +65,51 @@ function tokenizeInterpolationInternal( } else if (c === '{') { state = 3; } else { - buffer.push('%', c); // Not recognized. Return as literal. + buffer.push('%', c); // Not recognized. Return as literal. state = 0; } } else if (state === 2) { if ('0' <= c && c <= '9') { - number += c; // Multi-digit number. + number += c; // Multi-digit number. } else { tokens.push(parseInt(number ?? '', 10)); - i--; // Parse this char again. + i--; // Parse this char again. state = 0; } - } else if (state === 3) { // String table reference + } else if (state === 3) { + // String table reference if (c === '') { // Premature end before closing '}' - buffer.splice(0, 0, '%{'); // Re-insert leading delimiter - i--; // Parse this char again. - state = 0; // and parse as string literal. + buffer.splice(0, 0, '%{'); // Re-insert leading delimiter + i--; // Parse this char again. + state = 0; // and parse as string literal. } else if (c !== '}') { buffer.push(c); } else { const rawKey = buffer.join(''); - if (/[A-Z]\w*/i.test(rawKey)) { // Strict matching + if (/[A-Z]\w*/i.test(rawKey)) { + // Strict matching // Found a valid string key. Attempt case insensitive match. const keyUpper = rawKey.toUpperCase(); // BKY_ is the prefix used to namespace the strings used in // Blockly core files and the predefined blocks in ../blocks/. // These strings are defined in ../msgs/ files. - const bklyKey = - keyUpper.startsWith('BKY_') ? keyUpper.substring(4) : null; + const bklyKey = keyUpper.startsWith('BKY_') + ? keyUpper.substring(4) + : null; if (bklyKey && bklyKey in Msg) { const rawValue = Msg[bklyKey]; if (typeof rawValue === 'string') { // Attempt to dereference substrings, too, appending to the // end. Array.prototype.push.apply( - tokens, - tokenizeInterpolationInternal( - rawValue, parseInterpolationTokens)); + tokens, + tokenizeInterpolationInternal( + rawValue, + parseInterpolationTokens + ) + ); } else if (parseInterpolationTokens) { // When parsing interpolation tokens, numbers are special // placeholders (%1, %2, etc). Make sure all other values are @@ -114,12 +122,12 @@ function tokenizeInterpolationInternal( // No entry found in the string table. Pass reference as string. tokens.push('%{' + rawKey + '}'); } - buffer.length = 0; // Clear the array + buffer.length = 0; // Clear the array state = 0; } else { tokens.push('%{' + rawKey + '}'); buffer.length = 0; - state = 0; // and parse as string literal. + state = 0; // and parse as string literal. } } } @@ -164,7 +172,7 @@ function tokenizeInterpolationInternal( * interpolation tokens. * @returns Array of strings and numbers. */ -export function tokenizeInterpolation(message: string): (string|number)[] { +export function tokenizeInterpolation(message: string): (string | number)[] { return tokenizeInterpolationInternal(message, true); } @@ -177,7 +185,7 @@ export function tokenizeInterpolation(message: string): (string|number)[] { * string table references. * @returns String with message references replaced. */ -export function replaceMessageReferences(message: string|any): string { +export function replaceMessageReferences(message: string | any): string { if (typeof message !== 'string') { return message; } @@ -201,13 +209,13 @@ export function checkMessageReferences(message: string): boolean { const msgTable = Msg; // TODO (#1169): Implement support for other string tables, // prefixes other than BKY_. - const m = message.match(/%{BKY_[A-Z]\w*}/ig); + const m = message.match(/%{BKY_[A-Z]\w*}/gi); if (m) { for (let i = 0; i < m.length; i++) { const msgKey = m[i].toUpperCase(); if (msgTable[msgKey.slice(6, -1)] === undefined) { console.warn('No message string for ' + m[i] + ' in ' + message); - validSoFar = false; // Continue to report other errors. + validSoFar = false; // Continue to report other errors. } } } @@ -225,17 +233,22 @@ export function checkMessageReferences(message: string): boolean { * a #RRGGBB string, and the hue if the input was an HSV hue value. * @throws {Error} If the colour cannot be parsed. */ -export function parseBlockColour(colour: number| - string): {hue: number|null, hex: string} { +export function parseBlockColour(colour: number | string): { + hue: number | null; + hex: string; +} { const dereferenced = - typeof colour === 'string' ? replaceMessageReferences(colour) : colour; + typeof colour === 'string' ? replaceMessageReferences(colour) : colour; const hue = Number(dereferenced); if (!isNaN(hue) && 0 <= hue && hue <= 360) { return { hue: hue, hex: colourUtils.hsvToHex( - hue, colourUtils.getHsvSaturation(), colourUtils.getHsvValue() * 255), + hue, + colourUtils.getHsvSaturation(), + colourUtils.getHsvValue() * 255 + ), }; } else { const hex = colourUtils.parse(dereferenced); diff --git a/core/utils/rect.ts b/core/utils/rect.ts index a0ed1ca37..32d8bc692 100644 --- a/core/utils/rect.ts +++ b/core/utils/rect.ts @@ -14,7 +14,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.Rect'); - /** * Class for representing rectangular regions. */ @@ -26,8 +25,19 @@ export class Rect { * @param right Right. */ constructor( - public top: number, public bottom: number, public left: number, - public right: number) {} + public top: number, + public bottom: number, + public left: number, + public right: number + ) {} + + getHeight(): number { + return this.bottom - this.top; + } + + getWidth(): number { + return this.right - this.left; + } /** * Tests whether this rectangle contains a x/y coordinate. @@ -37,8 +47,9 @@ export class Rect { * @returns Whether this rectangle contains given coordinate. */ contains(x: number, y: number): boolean { - return x >= this.left && x <= this.right && y >= this.top && - y <= this.bottom; + return ( + x >= this.left && x <= this.right && y >= this.top && y <= this.bottom + ); } /** @@ -50,7 +61,10 @@ export class Rect { */ intersects(other: Rect): boolean { return !( - this.left > other.right || this.right < other.left || - this.top > other.bottom || this.bottom < other.top); + this.left > other.right || + this.right < other.left || + this.top > other.bottom || + this.bottom < other.top + ); } } diff --git a/core/utils/size.ts b/core/utils/size.ts index 504b8182f..0b71b7b04 100644 --- a/core/utils/size.ts +++ b/core/utils/size.ts @@ -14,7 +14,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.Size'); - /** * Class for representing sizes consisting of a width and height. */ @@ -33,7 +32,7 @@ export class Size { * @returns True iff the sizes have equal widths and equal heights, or if both * are null. */ - static equals(a: Size|null, b: Size|null): boolean { + static equals(a: Size | null, b: Size | null): boolean { if (a === b) { return true; } diff --git a/core/utils/string.ts b/core/utils/string.ts index f7d8e8540..343e0e27e 100644 --- a/core/utils/string.ts +++ b/core/utils/string.ts @@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.utils.string'); import * as deprecation from './deprecation.js'; - /** * Fast prefix-checker. * Copied from Closure's goog.string.startsWith. @@ -21,8 +20,11 @@ import * as deprecation from './deprecation.js'; */ export function startsWith(str: string, prefix: string): boolean { deprecation.warn( - 'Blockly.utils.string.startsWith()', 'April 2022', 'April 2023', - 'Use built-in string.startsWith'); + 'Blockly.utils.string.startsWith()', + 'April 2022', + 'April 2023', + 'Use built-in string.startsWith' + ); return str.startsWith(prefix); } @@ -36,11 +38,9 @@ export function shortestStringLength(array: string[]): number { if (!array.length) { return 0; } - return array - .reduce(function(a, b) { - return a.length < b.length ? a : b; - }) - .length; + return array.reduce(function (a, b) { + return a.length < b.length ? a : b; + }).length; } /** @@ -52,7 +52,9 @@ export function shortestStringLength(array: string[]): number { * @returns Length of common prefix. */ export function commonWordPrefix( - array: string[], opt_shortest?: number): number { + array: string[], + opt_shortest?: number +): number { if (!array.length) { return 0; } else if (array.length === 1) { @@ -90,7 +92,9 @@ export function commonWordPrefix( * @returns Length of common suffix. */ export function commonWordSuffix( - array: string[], opt_shortest?: number): number { + array: string[], + opt_shortest?: number +): number { if (!array.length) { return 0; } else if (array.length === 1) { @@ -193,7 +197,10 @@ function wrapLine(text: string, limit: number): string { * @returns Larger the better. */ function wrapScore( - words: string[], wordBreaks: boolean[], limit: number): number { + words: string[], + wordBreaks: boolean[], + limit: number +): number { // If this function becomes a performance liability, add caching. // Compute the length of each line. const lineLengths = [0]; @@ -229,9 +236,10 @@ function wrapScore( // previous line. For example, this looks wrong: // aaa bbb // ccc ddd eee - if (lineLengths.length > 1 && - lineLengths[lineLengths.length - 1] <= - lineLengths[lineLengths.length - 2]) { + if ( + lineLengths.length > 1 && + lineLengths[lineLengths.length - 1] <= lineLengths[lineLengths.length - 2] + ) { score += 0.5; } return score; @@ -246,7 +254,10 @@ function wrapScore( * @returns New array of optimal line breaks. */ function wrapMutate( - words: string[], wordBreaks: boolean[], limit: number): boolean[] { + words: string[], + wordBreaks: boolean[], + limit: number +): boolean[] { let bestScore = wrapScore(words, wordBreaks, limit); let bestBreaks; // Try shifting every line break forward or backward. @@ -254,7 +265,7 @@ function wrapMutate( if (wordBreaks[i] === wordBreaks[i + 1]) { continue; } - const mutatedWordBreaks = (new Array()).concat(wordBreaks); + const mutatedWordBreaks = new Array().concat(wordBreaks); mutatedWordBreaks[i] = !mutatedWordBreaks[i]; mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1]; const mutatedScore = wrapScore(words, mutatedWordBreaks, limit); diff --git a/core/utils/style.ts b/core/utils/style.ts index 7c53079bd..9adeadf1f 100644 --- a/core/utils/style.ts +++ b/core/utils/style.ts @@ -7,12 +7,10 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.style'); -import * as deprecation from './deprecation.js'; import {Coordinate} from './coordinate.js'; import {Rect} from './rect.js'; import {Size} from './size.js'; - /** * Gets the height and width of an element. * Similar to Closure's goog.style.getSize @@ -81,30 +79,10 @@ export function getComputedStyle(element: Element, property: string): string { const styles = window.getComputedStyle(element); // element.style[..] is undefined for browser specific styles // as 'filter'. - return (styles as AnyDuringMigration)[property] || - styles.getPropertyValue(property); -} - -/** - * Gets the cascaded style value of a node, or null if the value cannot be - * computed (only Internet Explorer can do this). - * - * Copied from Closure's goog.style.getCascadedStyle - * - * @param element Element to get style of. - * @param style Property to get (camel-case). - * @returns Style value. - * @deprecated No longer provided by Blockly. - */ -export function getCascadedStyle(element: Element, style: string): string { - deprecation.warn( - 'Blockly.utils.style.getCascadedStyle', 'version 9', 'version 10'); - // AnyDuringMigration because: Property 'currentStyle' does not exist on type - // 'Element'. AnyDuringMigration because: Property 'currentStyle' does not - // exist on type 'Element'. - return (element as AnyDuringMigration).currentStyle ? - (element as AnyDuringMigration).currentStyle[style] : - '' as string; + return ( + (styles as AnyDuringMigration)[property] || + styles.getPropertyValue(property) + ); } /** @@ -122,8 +100,9 @@ export function getPageOffset(el: Element): Coordinate { // of element since getBoundingClientRect returns relative coordinates to // the viewport. const scrollCoord = new Coordinate( - window.pageXOffset || documentElement.scrollLeft, - window.pageYOffset || documentElement.scrollTop); + window.pageXOffset || documentElement.scrollLeft, + window.pageYOffset || documentElement.scrollTop + ); pos.x = box.left + scrollCoord.x; pos.y = box.top + scrollCoord.y; @@ -174,7 +153,10 @@ export function getBorderBox(element: Element): Rect { * Defaults to false. */ export function scrollIntoContainerView( - element: Element, container: Element, opt_center?: boolean) { + element: Element, + container: Element, + opt_center?: boolean +) { const offset = getContainerOffsetToScrollInto(element, container, opt_center); container.scrollLeft = offset.x; container.scrollTop = offset.y; @@ -195,7 +177,10 @@ export function scrollIntoContainerView( * @returns The new scroll position of the container. */ export function getContainerOffsetToScrollInto( - element: Element, container: Element, opt_center?: boolean): Coordinate { + element: Element, + container: Element, + opt_center?: boolean +): Coordinate { // Absolute position of the element's border's top left corner. const elementPos = getPageOffset(element); // Absolute position of the container's border's top left corner. diff --git a/core/utils/svg.ts b/core/utils/svg.ts index 04904fcb9..21a1b7968 100644 --- a/core/utils/svg.ts +++ b/core/utils/svg.ts @@ -13,7 +13,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.Svg'); - /** * A name with the type of the SVG element stored in the generic. */ @@ -29,8 +28,9 @@ export class Svg<_T> { /** @internal */ static FECOMPOSITE = new Svg('feComposite'); /** @internal */ - static FECOMPONENTTRANSFER = - new Svg('feComponentTransfer'); + static FECOMPONENTTRANSFER = new Svg( + 'feComponentTransfer' + ); /** @internal */ static FEFLOOD = new Svg('feFlood'); /** @internal */ @@ -40,8 +40,9 @@ export class Svg<_T> { /** @internal */ static FEPOINTLIGHT = new Svg('fePointLight'); /** @internal */ - static FESPECULARLIGHTING = - new Svg('feSpecularLighting'); + static FESPECULARLIGHTING = new Svg( + 'feSpecularLighting' + ); /** @internal */ static FILTER = new Svg('filter'); /** @internal */ diff --git a/core/utils/svg_math.ts b/core/utils/svg_math.ts index 2ab1dca63..4c583a49f 100644 --- a/core/utils/svg_math.ts +++ b/core/utils/svg_math.ts @@ -10,11 +10,9 @@ goog.declareModuleId('Blockly.utils.svgMath'); import type {WorkspaceSvg} from '../workspace_svg.js'; import {Coordinate} from './coordinate.js'; -import * as deprecation from './deprecation.js'; import {Rect} from './rect.js'; import * as style from './style.js'; - /** * Static regex to pull the x,y values out of an SVG translate() directive. * Note that Firefox and IE (9,10) return 'translate(12)' instead of @@ -30,7 +28,7 @@ const XY_REGEX = /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*)?/; * Accounts for same exceptions as XY_REGEX. */ const XY_STYLE_REGEX = - /transform:\s*translate(?:3d)?\(\s*([-+\d.e]+)\s*px([ ,]\s*([-+\d.e]+)\s*px)?/; + /transform:\s*translate(?:3d)?\(\s*([-+\d.e]+)\s*px([ ,]\s*([-+\d.e]+)\s*px)?/; /** * Return the coordinates of the top-left corner of this element relative to @@ -101,20 +99,6 @@ export function getInjectionDivXY(element: Element): Coordinate { return new Coordinate(x, y); } -/** - * Check if 3D transforms are supported by adding an element - * and attempting to set the property. - * - * @returns True if 3D transforms are supported. - * @deprecated No longer provided by Blockly. - */ -export function is3dSupported(): boolean { - // All browsers support translate3d in 2022. - deprecation.warn( - 'Blockly.utils.svgMath.is3dSupported', 'version 9', 'version 10'); - return true; -} - /** * Get the position of the current viewport in window coordinates. This takes * scroll into account. @@ -127,8 +111,11 @@ export function getViewportBBox(): Rect { // Pixels, in window coordinates. const scrollOffset = style.getViewportPageOffset(); return new Rect( - scrollOffset.y, document.documentElement.clientHeight + scrollOffset.y, - scrollOffset.x, document.documentElement.clientWidth + scrollOffset.x); + scrollOffset.y, + document.documentElement.clientHeight + scrollOffset.y, + scrollOffset.x, + document.documentElement.clientWidth + scrollOffset.x + ); } /** @@ -141,7 +128,9 @@ export function getDocumentScroll(): Coordinate { const el = document.documentElement; const win = window; return new Coordinate( - win.pageXOffset || el.scrollLeft, win.pageYOffset || el.scrollTop); + win.pageXOffset || el.scrollLeft, + win.pageYOffset || el.scrollTop + ); } /** @@ -153,7 +142,9 @@ export function getDocumentScroll(): Coordinate { * @returns The workspace coordinates. */ export function screenToWsCoordinates( - ws: WorkspaceSvg, screenCoordinates: Coordinate): Coordinate { + ws: WorkspaceSvg, + screenCoordinates: Coordinate +): Coordinate { const screenX = screenCoordinates.x; const screenY = screenCoordinates.y; @@ -164,8 +155,10 @@ export function screenToWsCoordinates( const boundingRect = injectionDiv.getBoundingClientRect(); // The client coordinates offset by the injection div's upper left corner. - const clientOffsetPixels = - new Coordinate(screenX - boundingRect.left, screenY - boundingRect.top); + const clientOffsetPixels = new Coordinate( + screenX - boundingRect.left, + screenY - boundingRect.top + ); // The offset in pixels between the main workspace's origin and the upper // left corner of the injection div. @@ -173,8 +166,10 @@ export function screenToWsCoordinates( // The position of the new comment in pixels relative to the origin of the // main workspace. - const finalOffsetPixels = - Coordinate.difference(clientOffsetPixels, mainOffsetPixels); + const finalOffsetPixels = Coordinate.difference( + clientOffsetPixels, + mainOffsetPixels + ); // The position in main workspace coordinates. const finalOffsetMainWs = finalOffsetPixels.scale(1 / ws.scale); return finalOffsetMainWs; diff --git a/core/utils/svg_paths.ts b/core/utils/svg_paths.ts index ee7dd8d9c..891ba2151 100644 --- a/core/utils/svg_paths.ts +++ b/core/utils/svg_paths.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.svgPaths'); - /** * Create a string representing the given x, y pair. It does not matter whether * the coordinate is relative or absolute. The result has leading @@ -126,6 +125,10 @@ export function lineOnAxis(command: string, val: number): string { * @returns A string of the format 'command radius radius flags point' */ export function arc( - command: string, flags: string, radius: number, point: string): string { + command: string, + flags: string, + radius: number, + point: string +): string { return command + ' ' + radius + ' ' + radius + ' ' + flags + point; } diff --git a/core/utils/toolbox.ts b/core/utils/toolbox.ts index 0f6ecddac..569731591 100644 --- a/core/utils/toolbox.ts +++ b/core/utils/toolbox.ts @@ -12,17 +12,16 @@ import type {CssConfig as CategoryCssConfig} from '../toolbox/category.js'; import type {CssConfig as SeparatorCssConfig} from '../toolbox/separator.js'; import * as utilsXml from './xml.js'; - /** * The information needed to create a block in the toolbox. * Note that disabled has a different type for backwards compatibility. */ export interface BlockInfo { kind: string; - blockxml?: string|Node; + blockxml?: string | Node; type?: string; - gap?: string|number; - disabled?: string|boolean; + gap?: string | number; + disabled?: string | boolean; enabled?: boolean; id?: string; x?: number; @@ -42,9 +41,9 @@ export interface BlockInfo { */ export interface SeparatorInfo { kind: string; - id: string|undefined; - gap: number|undefined; - cssconfig: SeparatorCssConfig|undefined; + id: string | undefined; + gap: number | undefined; + cssconfig: SeparatorCssConfig | undefined; } /** @@ -62,13 +61,13 @@ export interface ButtonInfo { export interface LabelInfo { kind: string; text: string; - id: string|undefined; + id: string | undefined; } /** * The information needed to create either a button or a label in the flyout. */ -export type ButtonOrLabelInfo = ButtonInfo|LabelInfo; +export type ButtonOrLabelInfo = ButtonInfo | LabelInfo; /** * The information needed to create a category in the toolbox. @@ -77,12 +76,12 @@ export interface StaticCategoryInfo { kind: string; name: string; contents: ToolboxItemInfo[]; - id: string|undefined; - categorystyle: string|undefined; - colour: string|undefined; - cssconfig: CategoryCssConfig|undefined; - hidden: string|undefined; - expanded?: string|boolean; + id: string | undefined; + categorystyle: string | undefined; + colour: string | undefined; + cssconfig: CategoryCssConfig | undefined; + hidden: string | undefined; + expanded?: string | boolean; } /** @@ -91,29 +90,33 @@ export interface StaticCategoryInfo { export interface DynamicCategoryInfo { kind: string; custom: string; - id: string|undefined; - categorystyle: string|undefined; - colour: string|undefined; - cssconfig: CategoryCssConfig|undefined; - hidden: string|undefined; - expanded?: string|boolean; + id: string | undefined; + categorystyle: string | undefined; + colour: string | undefined; + cssconfig: CategoryCssConfig | undefined; + hidden: string | undefined; + expanded?: string | boolean; } /** * The information needed to create either a dynamic or static category. */ -export type CategoryInfo = StaticCategoryInfo|DynamicCategoryInfo; +export type CategoryInfo = StaticCategoryInfo | DynamicCategoryInfo; /** * Any information that can be used to create an item in the toolbox. */ -export type ToolboxItemInfo = FlyoutItemInfo|StaticCategoryInfo; +export type ToolboxItemInfo = FlyoutItemInfo | StaticCategoryInfo; /** * All the different types that can be displayed in a flyout. */ export type FlyoutItemInfo = - BlockInfo|SeparatorInfo|ButtonInfo|LabelInfo|DynamicCategoryInfo; + | BlockInfo + | SeparatorInfo + | ButtonInfo + | LabelInfo + | DynamicCategoryInfo; /** * The JSON definition of a toolbox. @@ -131,12 +134,16 @@ export type FlyoutItemInfoArray = FlyoutItemInfo[]; /** * All of the different types that can create a toolbox. */ -export type ToolboxDefinition = Node|ToolboxInfo|string; +export type ToolboxDefinition = Node | ToolboxInfo | string; /** * All of the different types that can be used to show items in a flyout. */ -export type FlyoutDefinition = FlyoutItemInfoArray|NodeList|ToolboxInfo|Node[]; +export type FlyoutDefinition = + | FlyoutItemInfoArray + | NodeList + | ToolboxInfo + | Node[]; /** * The name used to identify a toolbox that has category like items. @@ -159,7 +166,7 @@ export enum Position { TOP, BOTTOM, LEFT, - RIGHT + RIGHT, } /** @@ -169,8 +176,9 @@ export enum Position { * @returns Object holding information for creating a toolbox. * @internal */ -export function convertToolboxDefToJson(toolboxDef: ToolboxDefinition| - null): ToolboxInfo|null { +export function convertToolboxDefToJson( + toolboxDef: ToolboxDefinition | null +): ToolboxInfo | null { if (!toolboxDef) { return null; } @@ -198,12 +206,19 @@ function validateToolbox(toolboxJson: ToolboxInfo) { const toolboxContents = toolboxJson['contents']; if (toolboxKind) { - if (toolboxKind !== FLYOUT_TOOLBOX_KIND && - toolboxKind !== CATEGORY_TOOLBOX_KIND) { + if ( + toolboxKind !== FLYOUT_TOOLBOX_KIND && + toolboxKind !== CATEGORY_TOOLBOX_KIND + ) { throw Error( - 'Invalid toolbox kind ' + toolboxKind + '.' + - ' Please supply either ' + FLYOUT_TOOLBOX_KIND + ' or ' + - CATEGORY_TOOLBOX_KIND); + 'Invalid toolbox kind ' + + toolboxKind + + '.' + + ' Please supply either ' + + FLYOUT_TOOLBOX_KIND + + ' or ' + + CATEGORY_TOOLBOX_KIND + ); } } if (!toolboxContents) { @@ -218,8 +233,9 @@ function validateToolbox(toolboxJson: ToolboxInfo) { * @returns A list of flyout items. * @internal */ -export function convertFlyoutDefToJsonArray(flyoutDef: FlyoutDefinition| - null): FlyoutItemInfoArray { +export function convertFlyoutDefToJsonArray( + flyoutDef: FlyoutDefinition | null +): FlyoutItemInfoArray { if (!flyoutDef) { return []; } @@ -230,8 +246,11 @@ export function convertFlyoutDefToJsonArray(flyoutDef: FlyoutDefinition| // If it is already in the correct format return the flyoutDef. // AnyDuringMigration because: Property 'nodeType' does not exist on type // 'Node | FlyoutItemInfo'. - if (Array.isArray(flyoutDef) && flyoutDef.length > 0 && - !((flyoutDef[0]) as AnyDuringMigration).nodeType) { + if ( + Array.isArray(flyoutDef) && + flyoutDef.length > 0 && + !(flyoutDef[0] as AnyDuringMigration).nodeType + ) { // AnyDuringMigration because: Type 'FlyoutItemInfoArray | Node[]' is not // assignable to type 'FlyoutItemInfoArray'. return flyoutDef as AnyDuringMigration; @@ -249,14 +268,14 @@ export function convertFlyoutDefToJsonArray(flyoutDef: FlyoutDefinition| * @returns True if the toolbox has categories. * @internal */ -export function hasCategories(toolboxJson: ToolboxInfo|null): boolean { +export function hasCategories(toolboxJson: ToolboxInfo | null): boolean { return TEST_ONLY.hasCategoriesInternal(toolboxJson); } /** * Private version of hasCategories for stubbing in tests. */ -function hasCategoriesInternal(toolboxJson: ToolboxInfo|null): boolean { +function hasCategoriesInternal(toolboxJson: ToolboxInfo | null): boolean { if (!toolboxJson) { return false; } @@ -266,7 +285,7 @@ function hasCategoriesInternal(toolboxJson: ToolboxInfo|null): boolean { return toolboxKind === CATEGORY_TOOLBOX_KIND; } - const categories = toolboxJson['contents'].filter(function(item) { + const categories = toolboxJson['contents'].filter(function (item) { return item['kind'].toUpperCase() === 'CATEGORY'; }); return !!categories.length; @@ -284,11 +303,11 @@ export function isCategoryCollapsible(categoryInfo: CategoryInfo): boolean { return false; } - const categories = - (categoryInfo as AnyDuringMigration)['contents'].filter(function( - item: AnyDuringMigration) { - return item['kind'].toUpperCase() === 'CATEGORY'; - }); + const categories = (categoryInfo as AnyDuringMigration)['contents'].filter( + function (item: AnyDuringMigration) { + return item['kind'].toUpperCase() === 'CATEGORY'; + } + ); return !!categories.length; } @@ -313,8 +332,9 @@ function convertToToolboxJson(toolboxDef: Node): ToolboxInfo { * @param toolboxDef The definition of the toolbox in one of its many forms. * @returns A list of objects in the toolbox. */ -function xmlToJsonArray(toolboxDef: Node|Node[]|NodeList): FlyoutItemInfoArray| - ToolboxItemInfo[] { +function xmlToJsonArray( + toolboxDef: Node | Node[] | NodeList +): FlyoutItemInfoArray | ToolboxItemInfo[] { const arr = []; // If it is a node it will have children. // AnyDuringMigration because: Property 'childNodes' does not exist on type @@ -324,7 +344,7 @@ function xmlToJsonArray(toolboxDef: Node|Node[]|NodeList): FlyoutItemInfoArray| // Otherwise the toolboxDef is an array or collection. childNodes = toolboxDef; } - for (let i = 0, child; child = childNodes[i]; i++) { + for (let i = 0, child; (child = childNodes[i]); i++) { if (!child.tagName) { continue; } @@ -377,9 +397,10 @@ function addAttributes(node: Node, obj: AnyDuringMigration) { * @param toolboxDef DOM tree of blocks, or text representation of same. * @returns DOM tree of blocks, or null. */ -export function parseToolboxTree(toolboxDef: Element|null|string): Element| - null { - let parsedToolboxDef: Element|null = null; +export function parseToolboxTree( + toolboxDef: Element | null | string +): Element | null { + let parsedToolboxDef: Element | null = null; if (toolboxDef) { if (typeof toolboxDef === 'string') { parsedToolboxDef = utilsXml.textToDom(toolboxDef); diff --git a/core/utils/useragent.ts b/core/utils/useragent.ts index 386c99965..49ab6d95d 100644 --- a/core/utils/useragent.ts +++ b/core/utils/useragent.ts @@ -7,7 +7,6 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.userAgent'); - /** The raw useragent string. */ let rawUserAgent: string; @@ -29,47 +28,47 @@ let isTablet: boolean; let isMobile: boolean; -(function(raw) { -rawUserAgent = raw; -const rawUpper = rawUserAgent.toUpperCase(); -/** - * Case-insensitive test of whether name is in the useragent string. - * - * @param name Name to test. - * @returns True if name is present. - */ -function has(name: string): boolean { - return rawUpper.indexOf(name.toUpperCase()) !== -1; -} +(function (raw) { + rawUserAgent = raw; + const rawUpper = rawUserAgent.toUpperCase(); + /** + * Case-insensitive test of whether name is in the useragent string. + * + * @param name Name to test. + * @returns True if name is present. + */ + function has(name: string): boolean { + return rawUpper.indexOf(name.toUpperCase()) !== -1; + } -// Browsers. Logic from: -// https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/browser.js -// Useragent for JavaFX: -// Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44 -// (KHTML, like Gecko) JavaFX/8.0 Safari/537.44 -isJavaFx = has('JavaFX'); + // Browsers. Logic from: + // https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/browser.js + // Useragent for JavaFX: + // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44 + // (KHTML, like Gecko) JavaFX/8.0 Safari/537.44 + isJavaFx = has('JavaFX'); -// Engines. Logic from: -// https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/engine.js -isWebKit = has('WebKit'); -isGecko = has('Gecko') && !isWebKit; + // Engines. Logic from: + // https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/engine.js + isWebKit = has('WebKit'); + isGecko = has('Gecko') && !isWebKit; -// Platforms. Logic from: -// https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/platform.js -// and -// https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/extra.js -isAndroid = has('Android'); -const maxTouchPoints = + // Platforms. Logic from: + // https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/platform.js + // and + // https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/extra.js + isAndroid = has('Android'); + const maxTouchPoints = globalThis['navigator'] && globalThis['navigator']['maxTouchPoints']; -isIPad = has('iPad') || has('Macintosh') && maxTouchPoints > 0; -isIPhone = has('iPhone') && !isIPad; -isMac = has('Macintosh'); + isIPad = has('iPad') || (has('Macintosh') && maxTouchPoints > 0); + isIPhone = has('iPhone') && !isIPad; + isMac = has('Macintosh'); -// Devices. Logic from: -// https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/device.js -isTablet = isIPad || isAndroid && !has('Mobile') || has('Silk'); -isMobile = !isTablet && (isIPhone || isAndroid); -})(globalThis['navigator'] && globalThis['navigator']['userAgent'] || ''); + // Devices. Logic from: + // https://github.com/google/closure-library/blob/master/closure/goog/labs/useragent/device.js + isTablet = isIPad || (isAndroid && !has('Mobile')) || has('Silk'); + isMobile = !isTablet && (isIPhone || isAndroid); +})((globalThis['navigator'] && globalThis['navigator']['userAgent']) || ''); export const raw: string = rawUserAgent; diff --git a/core/utils/xml.ts b/core/utils/xml.ts index a02f1a8d6..75a4d70c8 100644 --- a/core/utils/xml.ts +++ b/core/utils/xml.ts @@ -7,8 +7,23 @@ import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.utils.xml'); -import * as deprecation from './deprecation.js'; +let domParser: DOMParser = { + parseFromString: function () { + throw new Error( + 'DOMParser was not found in the global scope and was not properly ' + + 'injected using injectDependencies' + ); + }, +}; +let xmlSerializer: XMLSerializer = { + serializeToString: function () { + throw new Error( + 'XMLSerializer was not foundin the global scope and was not properly ' + + 'injected using injectDependencies' + ); + }, +}; /** * Injected dependencies. By default these are just (and have the @@ -18,6 +33,8 @@ import * as deprecation from './deprecation.js'; * package instead. */ let {document, DOMParser, XMLSerializer} = globalThis; +if (DOMParser) domParser = new DOMParser(); +if (XMLSerializer) xmlSerializer = new XMLSerializer(); /** * Inject implementations of document, DOMParser and/or XMLSerializer @@ -40,9 +57,9 @@ let {document, DOMParser, XMLSerializer} = globalThis; * @param dependencies Options object containing dependencies to set. */ export function injectDependencies(dependencies: { - document?: Document, - DOMParser?: typeof DOMParser, - XMLSerializer?: typeof XMLSerializer, + document?: Document; + DOMParser?: typeof DOMParser; + XMLSerializer?: typeof XMLSerializer; }) { ({ // Default to existing value if option not supplied. @@ -50,6 +67,9 @@ export function injectDependencies(dependencies: { DOMParser = DOMParser, XMLSerializer = XMLSerializer, } = dependencies); + + domParser = new DOMParser(); + xmlSerializer = new XMLSerializer(); } /** @@ -57,27 +77,8 @@ export function injectDependencies(dependencies: { */ export const NAME_SPACE = 'https://developers.google.com/blockly/xml'; -/** - * Get the document object to use for XML serialization. - * - * @returns The document object. - * @deprecated No longer provided by Blockly. - */ -export function getDocument(): Document { - deprecation.warn('Blockly.utils.xml.getDocument', 'version 9', 'version 10'); - return document; -} - -/** - * Get the document object to use for XML serialization. - * - * @param xmlDocument The document object to use. - * @deprecated No longer provided by Blockly. - */ -export function setDocument(xmlDocument: Document) { - deprecation.warn('Blockly.utils.xml.setDocument', 'version 9', 'version 10'); - document = xmlDocument; -} +// eslint-disable-next-line no-control-regex +const INVALID_CONTROL_CHARS = /[\x00-\x09\x0B\x0C\x0E-\x1F]/g; /** * Create DOM element for XML. @@ -102,40 +103,64 @@ export function createTextNode(text: string): Text { /** * Converts an XML string into a DOM structure. * + * Control characters should be escaped. (But we will try to best-effort parse + * unescaped characters.) + * + * Note that even when escaped, U+0000 will be parsed as U+FFFD (the + * "replacement character") because U+0000 is never a valid XML character + * (even in XML 1.1). + * https://www.w3.org/TR/xml11/#charsets + * * @param text An XML string. * @returns A DOM object representing the singular child of the document * element. * @throws if the text doesn't parse. */ export function textToDom(text: string): Element { - const doc = textToDomDocument(text); - if (!doc || !doc.documentElement || - doc.getElementsByTagName('parsererror').length) { - throw Error('textToDom was unable to parse: ' + text); + let doc = domParser.parseFromString(text, 'text/xml'); + if ( + doc && + doc.documentElement && + !doc.getElementsByTagName('parsererror').length + ) { + return doc.documentElement; } - return doc.documentElement; -} -/** - * Converts an XML string into a DOM tree. - * - * @param text XML string. - * @returns The DOM document. - * @throws if XML doesn't parse. - */ -export function textToDomDocument(text: string): Document { - const oParser = new DOMParser(); - return oParser.parseFromString(text, 'text/xml'); + // Attempt to parse as HTML to deserialize control characters that were + // serialized before the serializer did proper escaping. + doc = domParser.parseFromString(text, 'text/html'); + if ( + doc && + doc.body.firstChild && + doc.body.firstChild.nodeName.toLowerCase() === 'xml' + ) { + return doc.body.firstChild as Element; + } + + throw new Error(`DOMParser was unable to parse: ${text}`); } /** * Converts a DOM structure into plain text. * Currently the text format is fairly ugly: all one line with no whitespace. * + * Control characters are escaped using their decimal encodings. This includes + * U+0000 even though it is technically never a valid XML character (even in + * XML 1.1). + * https://www.w3.org/TR/xml11/#charsets + * + * When decoded U+0000 will be parsed as U+FFFD (the "replacement character"). + * * @param dom A tree of XML nodes. * @returns Text representation. */ export function domToText(dom: Node): string { - const oSerializer = new XMLSerializer(); - return oSerializer.serializeToString(dom); + return sanitizeText(xmlSerializer.serializeToString(dom)); +} + +function sanitizeText(text: string) { + return text.replace( + INVALID_CONTROL_CHARS, + (match) => `&#${match.charCodeAt(0)};` + ); } diff --git a/core/variable_map.ts b/core/variable_map.ts index 722c51b12..b8db8a54e 100644 --- a/core/variable_map.ts +++ b/core/variable_map.ts @@ -27,7 +27,6 @@ import * as idGenerator from './utils/idgenerator.js'; import {VariableModel} from './variable_model.js'; import type {Workspace} from './workspace.js'; - /** * Class for a variable map. This contains a dictionary data structure with * variable types as keys and lists of variables as values. The list of @@ -80,7 +79,11 @@ export class VariableMap { this.renameVariableAndUses_(variable, newName, blocks); } else { this.renameVariableWithConflict_( - variable, newName, conflictVar, blocks); + variable, + newName, + conflictVar, + blocks + ); } } finally { eventUtils.setGroup(existingGroup); @@ -97,7 +100,7 @@ export class VariableMap { renameVariableById(id: string, newName: string) { const variable = this.getVariableById(id); if (!variable) { - throw Error('Tried to rename a variable that didn\'t exist. ID: ' + id); + throw Error("Tried to rename a variable that didn't exist. ID: " + id); } this.renameVariable(variable, newName); @@ -112,9 +115,13 @@ export class VariableMap { * @param blocks The list of all blocks in the workspace. */ private renameVariableAndUses_( - variable: VariableModel, newName: string, blocks: Block[]) { + variable: VariableModel, + newName: string, + blocks: Block[] + ) { eventUtils.fire( - new (eventUtils.get(eventUtils.VAR_RENAME))(variable, newName)); + new (eventUtils.get(eventUtils.VAR_RENAME))(variable, newName) + ); variable.name = newName; for (let i = 0; i < blocks.length; i++) { blocks[i].updateVarName(variable); @@ -133,8 +140,11 @@ export class VariableMap { * @param blocks The list of all blocks in the workspace. */ private renameVariableWithConflict_( - variable: VariableModel, newName: string, conflictVar: VariableModel, - blocks: Block[]) { + variable: VariableModel, + newName: string, + conflictVar: VariableModel, + blocks: Block[] + ) { const type = variable.type; const oldCase = conflictVar.name; @@ -166,15 +176,24 @@ export class VariableMap { * @param opt_id The unique ID of the variable. This will default to a UUID. * @returns The newly created variable. */ - createVariable(name: string, opt_type?: string|null, opt_id?: string|null): - VariableModel { + createVariable( + name: string, + opt_type?: string | null, + opt_id?: string | null + ): VariableModel { let variable = this.getVariable(name, opt_type); if (variable) { if (opt_id && variable.getId() !== opt_id) { throw Error( - 'Variable "' + name + '" is already in use and its id is "' + - variable.getId() + '" which conflicts with the passed in ' + - 'id, "' + opt_id + '".'); + 'Variable "' + + name + + '" is already in use and its id is "' + + variable.getId() + + '" which conflicts with the passed in ' + + 'id, "' + + opt_id + + '".' + ); } // The variable already exists and has the same ID. return variable; @@ -215,7 +234,8 @@ export class VariableMap { if (tempVar.getId() === variableId) { variableList.splice(i, 1); eventUtils.fire( - new (eventUtils.get(eventUtils.VAR_DELETE))(variable)); + new (eventUtils.get(eventUtils.VAR_DELETE))(variable) + ); if (variableList.length === 0) { this.variableMap.delete(variable.type); } @@ -237,13 +257,15 @@ export class VariableMap { // Check whether this variable is a function parameter before deleting. const variableName = variable.name; const uses = this.getVariableUsesById(id); - for (let i = 0, block; block = uses[i]; i++) { - if (block.type === 'procedures_defnoreturn' || - block.type === 'procedures_defreturn') { + for (let i = 0, block; (block = uses[i]); i++) { + if ( + block.type === 'procedures_defnoreturn' || + block.type === 'procedures_defreturn' + ) { const procedureName = String(block.getFieldValue('NAME')); const deleteText = Msg['CANNOT_DELETE_VARIABLE_PROCEDURE'] - .replace('%1', variableName) - .replace('%2', procedureName); + .replace('%1', variableName) + .replace('%2', procedureName); dialog.alert(deleteText); return; } @@ -252,8 +274,8 @@ export class VariableMap { if (uses.length > 1) { // Confirm before deleting multiple blocks. const confirmText = Msg['DELETE_VARIABLE_CONFIRMATION'] - .replace('%1', String(uses.length)) - .replace('%2', variableName); + .replace('%1', String(uses.length)) + .replace('%2', variableName); dialog.confirm(confirmText, (ok) => { if (ok && variable) { this.deleteVariableInternal(variable, uses); @@ -264,7 +286,7 @@ export class VariableMap { this.deleteVariableInternal(variable, uses); } } else { - console.warn('Can\'t delete non-existent variable: ' + id); + console.warn("Can't delete non-existent variable: " + id); } } @@ -300,11 +322,11 @@ export class VariableMap { * the empty string, which is a specific type. * @returns The variable with the given name, or null if it was not found. */ - getVariable(name: string, opt_type?: string|null): VariableModel|null { + getVariable(name: string, opt_type?: string | null): VariableModel | null { const type = opt_type || ''; const list = this.variableMap.get(type); if (list) { - for (let j = 0, variable; variable = list[j]; j++) { + for (let j = 0, variable; (variable = list[j]); j++) { if (Names.equals(variable.name, name)) { return variable; } @@ -319,7 +341,7 @@ export class VariableMap { * @param id The ID to check for. * @returns The variable with the given ID. */ - getVariableById(id: string): VariableModel|null { + getVariableById(id: string): VariableModel | null { for (const variables of this.variableMap.values()) { for (const variable of variables) { if (variable.getId() === id) { @@ -338,7 +360,7 @@ export class VariableMap { * @returns The sought after variables of the passed in type. An empty array * if none are found. */ - getVariablesOfType(type: string|null): VariableModel[] { + getVariablesOfType(type: string | null): VariableModel[] { type = type || ''; const variableList = this.variableMap.get(type); if (variableList) { @@ -357,7 +379,7 @@ export class VariableMap { * @returns List of variable types. * @internal */ - getVariableTypes(ws: Workspace|null): string[] { + getVariableTypes(ws: Workspace | null): string[] { const variableTypes = new Set(this.variableMap.keys()); if (ws && ws.getPotentialVariableMap()) { for (const key of ws.getPotentialVariableMap()!.variableMap.keys()) { @@ -390,8 +412,8 @@ export class VariableMap { */ getAllVariableNames(): string[] { return Array.from(this.variableMap.values()) - .flat() - .map((variable) => variable.name); + .flat() + .map((variable) => variable.name); } /** diff --git a/core/variable_model.ts b/core/variable_model.ts index 11c901474..1fe58772f 100644 --- a/core/variable_model.ts +++ b/core/variable_model.ts @@ -18,7 +18,6 @@ import './events/events_var_create.js'; import * as idGenerator from './utils/idgenerator.js'; import type {Workspace} from './workspace.js'; - /** * Class for a variable model. * Holds information for the variable including name, ID, and type. @@ -39,8 +38,11 @@ export class VariableModel { * @param opt_id The unique ID of the variable. This will default to a UUID. */ constructor( - public workspace: Workspace, public name: string, opt_type?: string, - opt_id?: string) { + public workspace: Workspace, + public name: string, + opt_type?: string, + opt_id?: string + ) { /** * The type of the variable, such as 'int' or 'sound_effect'. This may be * used to build a list of variables of a specific type. By default this is diff --git a/core/variables.ts b/core/variables.ts index 901572bb0..1a871bc3f 100644 --- a/core/variables.ts +++ b/core/variables.ts @@ -17,7 +17,6 @@ import {VariableModel} from './variable_model.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * String for use in the "custom" attribute of a category in toolbox XML. * This string indicates that the category should be dynamically populated with @@ -70,7 +69,7 @@ export function allUsedVarModels(ws: Workspace): VariableModel[] { export function allDeveloperVariables(workspace: Workspace): string[] { const blocks = workspace.getAllBlocks(false); const variables = new Set(); - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { const getDeveloperVariables = block.getDeveloperVariables; if (getDeveloperVariables) { const devVars = getDeveloperVariables(); @@ -96,7 +95,7 @@ export function flyoutCategory(workspace: WorkspaceSvg): Element[] { button.setAttribute('text', '%{BKY_NEW_VARIABLE}'); button.setAttribute('callbackKey', 'CREATE_VARIABLE'); - workspace.registerButtonCallback('CREATE_VARIABLE', function(button) { + workspace.registerButtonCallback('CREATE_VARIABLE', function (button) { createVariableButtonHandler(button.getTargetWorkspace()); }); @@ -133,18 +132,19 @@ export function flyoutCategoryBlocks(workspace: Workspace): Element[] { block.setAttribute('gap', Blocks['variables_get'] ? '20' : '8'); block.appendChild(generateVariableFieldDom(mostRecentVariable)); const value = utilsXml.textToDom( - '' + + '' + '' + '1' + '' + - ''); + '' + ); block.appendChild(value); xmlList.push(block); } if (Blocks['variables_get']) { variableModelList.sort(VariableModel.compareByName); - for (let i = 0, variable; variable = variableModelList[i]; i++) { + for (let i = 0, variable; (variable = variableModelList[i]); i++) { const block = utilsXml.createElement('block'); block.setAttribute('type', 'variables_get'); block.setAttribute('gap', '8'); @@ -176,7 +176,9 @@ export function generateUniqueName(workspace: Workspace): string { */ function generateUniqueNameInternal(workspace: Workspace): string { return generateUniqueNameFromOptions( - VAR_LETTER_OPTIONS.charAt(0), workspace.getAllVariableNames()); + VAR_LETTER_OPTIONS.charAt(0), + workspace.getAllVariableNames() + ); } /** @@ -189,7 +191,9 @@ function generateUniqueNameInternal(workspace: Workspace): string { * @returns A unique name that is not present in the usedNames array. */ export function generateUniqueNameFromOptions( - startChar: string, usedNames: string[]): string { + startChar: string, + usedNames: string[] +): string { if (!usedNames.length) { return startChar; } @@ -240,19 +244,23 @@ export function generateUniqueNameFromOptions( * will default to '', which is a specific type. */ export function createVariableButtonHandler( - workspace: Workspace, opt_callback?: (p1?: string|null) => void, - opt_type?: string) { + workspace: Workspace, + opt_callback?: (p1?: string | null) => void, + opt_type?: string +) { const type = opt_type || ''; // This function needs to be named so it can be called recursively. function promptAndCheckWithAlert(defaultName: string) { - promptName(Msg['NEW_VARIABLE_TITLE'], defaultName, function(text) { - if (!text) { // User canceled prompt. + promptName(Msg['NEW_VARIABLE_TITLE'], defaultName, function (text) { + if (!text) { + // User canceled prompt. if (opt_callback) opt_callback(null); return; } const existing = nameUsedWithAnyType(text, workspace); - if (!existing) { // No conflict + if (!existing) { + // No conflict workspace.createVariable(text, type); if (opt_callback) opt_callback(text); return; @@ -265,7 +273,7 @@ export function createVariableButtonHandler( msg = Msg['VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE']; msg = msg.replace('%1', existing.name).replace('%2', existing.type); } - dialog.alert(msg, function() { + dialog.alert(msg, function () { promptAndCheckWithAlert(text); }); }); @@ -285,22 +293,31 @@ export function createVariableButtonHandler( * an existing variable was chosen. */ export function renameVariable( - workspace: Workspace, variable: VariableModel, - opt_callback?: (p1?: string|null) => void) { + workspace: Workspace, + variable: VariableModel, + opt_callback?: (p1?: string | null) => void +) { // This function needs to be named so it can be called recursively. function promptAndCheckWithAlert(defaultName: string) { - const promptText = - Msg['RENAME_VARIABLE_TITLE'].replace('%1', variable.name); - promptName(promptText, defaultName, function(newName) { - if (!newName) { // User canceled prompt. + const promptText = Msg['RENAME_VARIABLE_TITLE'].replace( + '%1', + variable.name + ); + promptName(promptText, defaultName, function (newName) { + if (!newName) { + // User canceled prompt. if (opt_callback) opt_callback(null); return; } const existing = nameUsedWithOtherType(newName, variable.type, workspace); - const procedure = - nameUsedWithConflictingParam(variable.name, newName, workspace); - if (!existing && !procedure) { // No conflict. + const procedure = nameUsedWithConflictingParam( + variable.name, + newName, + workspace + ); + if (!existing && !procedure) { + // No conflict. workspace.renameVariableById(variable.getId(), newName); if (opt_callback) opt_callback(newName); return; @@ -309,14 +326,14 @@ export function renameVariable( let msg = ''; if (existing) { msg = Msg['VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE'] - .replace('%1', existing.name) - .replace('%2', existing.type); + .replace('%1', existing.name) + .replace('%2', existing.type); } else if (procedure) { msg = Msg['VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER'] - .replace('%1', newName) - .replace('%2', procedure); + .replace('%1', newName) + .replace('%2', procedure); } - dialog.alert(msg, function() { + dialog.alert(msg, function () { promptAndCheckWithAlert(newName); }); }); @@ -333,9 +350,11 @@ export function renameVariable( * if the user picked something illegal. */ export function promptName( - promptText: string, defaultText: string, - callback: (p1: string|null) => void) { - dialog.prompt(promptText, defaultText, function(newVar) { + promptText: string, + defaultText: string, + callback: (p1: string | null) => void +) { + dialog.prompt(promptText, defaultText, function (newVar) { // Merge runs of whitespace. Strip leading and trailing whitespace. // Beyond this, all names are legal. if (newVar) { @@ -359,11 +378,14 @@ export function promptName( * none was found. */ function nameUsedWithOtherType( - name: string, type: string, workspace: Workspace): VariableModel|null { + name: string, + type: string, + workspace: Workspace +): VariableModel | null { const allVariables = workspace.getVariableMap().getAllVariables(); name = name.toLowerCase(); - for (let i = 0, variable; variable = allVariables[i]; i++) { + for (let i = 0, variable; (variable = allVariables[i]); i++) { if (variable.name.toLowerCase() === name && variable.type !== type) { return variable; } @@ -379,11 +401,13 @@ function nameUsedWithOtherType( * @returns The variable with the given name, or null if none was found. */ export function nameUsedWithAnyType( - name: string, workspace: Workspace): VariableModel|null { + name: string, + workspace: Workspace +): VariableModel | null { const allVariables = workspace.getVariableMap().getAllVariables(); name = name.toLowerCase(); - for (let i = 0, variable; variable = allVariables[i]; i++) { + for (let i = 0, variable; (variable = allVariables[i]); i++) { if (variable.name.toLowerCase() === name) { return variable; } @@ -404,10 +428,13 @@ export function nameUsedWithAnyType( * @internal */ export function nameUsedWithConflictingParam( - oldName: string, newName: string, workspace: Workspace): string|null { - return workspace.getProcedureMap().getProcedures().length ? - checkForConflictingParamWithProcedureModels(oldName, newName, workspace) : - checkForConflictingParamWithLegacyProcedures(oldName, newName, workspace); + oldName: string, + newName: string, + workspace: Workspace +): string | null { + return workspace.getProcedureMap().getProcedures().length + ? checkForConflictingParamWithProcedureModels(oldName, newName, workspace) + : checkForConflictingParamWithLegacyProcedures(oldName, newName, workspace); } /** @@ -415,15 +442,19 @@ export function nameUsedWithConflictingParam( * null if one does not exist. */ function checkForConflictingParamWithProcedureModels( - oldName: string, newName: string, workspace: Workspace): string|null { + oldName: string, + newName: string, + workspace: Workspace +): string | null { oldName = oldName.toLowerCase(); newName = newName.toLowerCase(); const procedures = workspace.getProcedureMap().getProcedures(); for (const procedure of procedures) { - const params = procedure.getParameters() - .filter(isVariableBackedParameterModel) - .map((param) => param.getVariableModel().name); + const params = procedure + .getParameters() + .filter(isVariableBackedParameterModel) + .map((param) => param.getVariableModel().name); if (!params) continue; const procHasOld = params.some((param) => param.toLowerCase() === oldName); const procHasNew = params.some((param) => param.toLowerCase() === newName); @@ -437,7 +468,10 @@ function checkForConflictingParamWithProcedureModels( * null if one does not exist. */ function checkForConflictingParamWithLegacyProcedures( - oldName: string, newName: string, workspace: Workspace): string|null { + oldName: string, + newName: string, + workspace: Workspace +): string | null { oldName = oldName.toLowerCase(); newName = newName.toLowerCase(); @@ -459,8 +493,9 @@ function checkForConflictingParamWithLegacyProcedures( * @param variableModel The variable model to represent. * @returns The generated DOM. */ -export function generateVariableFieldDom(variableModel: VariableModel): - Element { +export function generateVariableFieldDom( + variableModel: VariableModel +): Element { /* Generates the following XML: * foo */ @@ -486,8 +521,11 @@ export function generateVariableFieldDom(variableModel: VariableModel): * combination. */ export function getOrCreateVariablePackage( - workspace: Workspace, id: string|null, opt_name?: string, - opt_type?: string): VariableModel { + workspace: Workspace, + id: string | null, + opt_name?: string, + opt_type?: string +): VariableModel { let variable = getVariable(workspace, id, opt_name, opt_type); if (!variable) { variable = createVariable(workspace, id, opt_name, opt_type); @@ -511,8 +549,11 @@ export function getOrCreateVariablePackage( * combination, or null if not found. */ export function getVariable( - workspace: Workspace, id: string|null, opt_name?: string, - opt_type?: string): VariableModel|null { + workspace: Workspace, + id: string | null, + opt_name?: string, + opt_type?: string +): VariableModel | null { const potentialVariableMap = workspace.getPotentialVariableMap(); let variable = null; // Try to just get the variable, by ID if possible. @@ -553,14 +594,17 @@ export function getVariable( * combination. */ function createVariable( - workspace: Workspace, id: string|null, opt_name?: string, - opt_type?: string): VariableModel { + workspace: Workspace, + id: string | null, + opt_name?: string, + opt_type?: string +): VariableModel { const potentialVariableMap = workspace.getPotentialVariableMap(); // Variables without names get uniquely named for this workspace. if (!opt_name) { - const ws = - (workspace.isFlyout ? (workspace as WorkspaceSvg).targetWorkspace : - workspace); + const ws = workspace.isFlyout + ? (workspace as WorkspaceSvg).targetWorkspace + : workspace; opt_name = generateUniqueName(ws!); } @@ -589,7 +633,9 @@ function createVariable( * @internal */ export function getAddedVariables( - workspace: Workspace, originalVariables: VariableModel[]): VariableModel[] { + workspace: Workspace, + originalVariables: VariableModel[] +): VariableModel[] { const allCurrentVariables = workspace.getAllVariables(); const addedVariables = []; if (originalVariables.length !== allCurrentVariables.length) { diff --git a/core/variables_dynamic.ts b/core/variables_dynamic.ts index da3b797aa..a143487b8 100644 --- a/core/variables_dynamic.ts +++ b/core/variables_dynamic.ts @@ -16,7 +16,6 @@ import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; import type {FlyoutButton} from './flyout_button.js'; - /** * String for use in the "custom" attribute of a category in toolbox XML. * This string indicates that the category should be dynamically populated with @@ -33,7 +32,10 @@ export const CATEGORY_NAME = 'VARIABLE_DYNAMIC'; */ function stringButtonClickHandler(button: FlyoutButton) { Variables.createVariableButtonHandler( - button.getTargetWorkspace(), undefined, 'String'); + button.getTargetWorkspace(), + undefined, + 'String' + ); } // eslint-disable-next-line camelcase export const onCreateVariableButtonClick_String = stringButtonClickHandler; @@ -45,7 +47,10 @@ export const onCreateVariableButtonClick_String = stringButtonClickHandler; */ function numberButtonClickHandler(button: FlyoutButton) { Variables.createVariableButtonHandler( - button.getTargetWorkspace(), undefined, 'Number'); + button.getTargetWorkspace(), + undefined, + 'Number' + ); } // eslint-disable-next-line camelcase export const onCreateVariableButtonClick_Number = numberButtonClickHandler; @@ -57,7 +62,10 @@ export const onCreateVariableButtonClick_Number = numberButtonClickHandler; */ function colourButtonClickHandler(button: FlyoutButton) { Variables.createVariableButtonHandler( - button.getTargetWorkspace(), undefined, 'Colour'); + button.getTargetWorkspace(), + undefined, + 'Colour' + ); } // eslint-disable-next-line camelcase export const onCreateVariableButtonClick_Colour = colourButtonClickHandler; @@ -85,11 +93,17 @@ export function flyoutCategory(workspace: WorkspaceSvg): Element[] { xmlList.push(button); workspace.registerButtonCallback( - 'CREATE_VARIABLE_STRING', stringButtonClickHandler); + 'CREATE_VARIABLE_STRING', + stringButtonClickHandler + ); workspace.registerButtonCallback( - 'CREATE_VARIABLE_NUMBER', numberButtonClickHandler); + 'CREATE_VARIABLE_NUMBER', + numberButtonClickHandler + ); workspace.registerButtonCallback( - 'CREATE_VARIABLE_COLOUR', colourButtonClickHandler); + 'CREATE_VARIABLE_COLOUR', + colourButtonClickHandler + ); const blockList = flyoutCategoryBlocks(workspace); xmlList = xmlList.concat(blockList); @@ -117,7 +131,7 @@ export function flyoutCategoryBlocks(workspace: Workspace): Element[] { } if (Blocks['variables_get_dynamic']) { variableModelList.sort(VariableModel.compareByName); - for (let i = 0, variable; variable = variableModelList[i]; i++) { + for (let i = 0, variable; (variable = variableModelList[i]); i++) { const block = xml.createElement('block'); block.setAttribute('type', 'variables_get_dynamic'); block.setAttribute('gap', '8'); diff --git a/core/warning.ts b/core/warning.ts deleted file mode 100644 index 8e80f9234..000000000 --- a/core/warning.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing a warning. - * - * @class - */ -import * as goog from '../closure/goog/goog.js'; -goog.declareModuleId('Blockly.Warning'); - -// Unused import preserved for side-effects. Remove if unneeded. -import './events/events_bubble_open.js'; - -import type {BlockSvg} from './block_svg.js'; -import {Bubble} from './bubble.js'; -import * as eventUtils from './events/utils.js'; -import {Icon} from './icon.js'; -import type {Coordinate} from './utils/coordinate.js'; -import * as dom from './utils/dom.js'; -import {Svg} from './utils/svg.js'; - - -/** - * Class for a warning. - */ -export class Warning extends Icon { - private text: {[key: string]: string}; - - /** The top-level node of the warning text, or null if not created. */ - private paragraphElement: SVGTextElement|null = null; - - /** Does this icon get hidden when the block is collapsed? */ - override collapseHidden = false; - - /** @param block The block associated with this warning. */ - constructor(block: BlockSvg) { - super(block); - this.createIcon(); - // The text object can contain multiple warnings. - this.text = Object.create(null); - } - - /** - * Draw the warning icon. - * - * @param group The icon group. - */ - protected override drawIcon_(group: Element) { - // Triangle with rounded corners. - dom.createSvgElement( - Svg.PATH, { - 'class': 'blocklyIconShape', - 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z', - }, - group); - // Can't use a real '!' text character since different browsers and - // operating systems render it differently. Body of exclamation point. - dom.createSvgElement( - Svg.PATH, { - 'class': 'blocklyIconSymbol', - 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z', - }, - group); - // Dot of exclamation point. - dom.createSvgElement( - Svg.RECT, { - 'class': 'blocklyIconSymbol', - 'x': '7', - 'y': '11', - 'height': '2', - 'width': '2', - }, - group); - } - - /** - * Show or hide the warning bubble. - * - * @param visible True if the bubble should be visible. - */ - override setVisible(visible: boolean) { - if (visible === this.isVisible()) { - return; - } - eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))( - this.getBlock(), visible, 'warning')); - if (visible) { - this.createBubble(); - } else { - this.disposeBubble(); - } - } - - /** Show the bubble. */ - private createBubble() { - this.paragraphElement = Bubble.textToDom(this.getText()); - this.bubble_ = Bubble.createNonEditableBubble( - this.paragraphElement, this.getBlock(), this.iconXY_ as Coordinate); - this.applyColour(); - } - - /** Dispose of the bubble and references to it. */ - private disposeBubble() { - if (this.bubble_) { - this.bubble_.dispose(); - this.bubble_ = null; - } - this.paragraphElement = null; - } - - /** - * Set this warning's text. - * - * @param text Warning text (or '' to delete). This supports linebreaks. - * @param id An ID for this text entry to be able to maintain multiple - * warnings. - */ - setText(text: string, id: string) { - if (this.text[id] === text) { - return; - } - if (text) { - this.text[id] = text; - } else { - delete this.text[id]; - } - if (this.isVisible()) { - this.setVisible(false); - this.setVisible(true); - } - } - - /** - * Get this warning's texts. - * - * @returns All texts concatenated into one string. - */ - getText(): string { - const allWarnings = []; - for (const id in this.text) { - allWarnings.push(this.text[id]); - } - return allWarnings.join('\n'); - } - - /** Dispose of this warning. */ - override dispose() { - this.getBlock().warning = null; - super.dispose(); - } -} diff --git a/core/widgetdiv.ts b/core/widgetdiv.ts index 0c5f1615d..a92dd7fbe 100644 --- a/core/widgetdiv.ts +++ b/core/widgetdiv.ts @@ -9,16 +9,16 @@ goog.declareModuleId('Blockly.WidgetDiv'); import * as common from './common.js'; import * as dom from './utils/dom.js'; +import type {Field} from './field.js'; import type {Rect} from './utils/rect.js'; import type {Size} from './utils/size.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** The object currently using this container. */ let owner: unknown = null; /** Optional cleanup function set by whichever object uses the widget. */ -let dispose: (() => void)|null = null; +let dispose: (() => void) | null = null; /** A class name representing the current owner's workspace renderer. */ let rendererClassName = ''; @@ -27,14 +27,14 @@ let rendererClassName = ''; let themeClassName = ''; /** The HTML container for popup overlays (e.g. editor widgets). */ -let containerDiv: HTMLDivElement|null; +let containerDiv: HTMLDivElement | null; /** * Returns the HTML container for editor widgets. * * @returns The editor widget container. */ -export function getDiv(): HTMLDivElement|null { +export function getDiv(): HTMLDivElement | null { return containerDiv; } @@ -44,7 +44,7 @@ export function getDiv(): HTMLDivElement|null { * @param newDiv The new value for the DIV field. * @internal */ -export function testOnly_setDiv(newDiv: HTMLDivElement|null) { +export function testOnly_setDiv(newDiv: HTMLDivElement | null) { containerDiv = newDiv; } @@ -53,7 +53,7 @@ export function testOnly_setDiv(newDiv: HTMLDivElement|null) { */ export function createDom() { if (containerDiv) { - return; // Already created. + return; // Already created. } containerDiv = document.createElement('div') as HTMLDivElement; @@ -169,7 +169,11 @@ function positionInternal(x: number, y: number, height: number) { * @internal */ export function positionWithAnchor( - viewportBBox: Rect, anchorBBox: Rect, widgetSize: Size, rtl: boolean) { + viewportBBox: Rect, + anchorBBox: Rect, + widgetSize: Size, + rtl: boolean +) { const y = calculateY(viewportBBox, anchorBBox, widgetSize); const x = calculateX(viewportBBox, anchorBBox, widgetSize, rtl); @@ -194,8 +198,11 @@ export function positionWithAnchor( * window coordinates. */ function calculateX( - viewportBBox: Rect, anchorBBox: Rect, widgetSize: Size, - rtl: boolean): number { + viewportBBox: Rect, + anchorBBox: Rect, + widgetSize: Size, + rtl: boolean +): number { if (rtl) { // Try to align the right side of the field and the right side of widget. const widgetLeft = anchorBBox.right - widgetSize.width; @@ -225,7 +232,10 @@ function calculateX( * window coordinates. */ function calculateY( - viewportBBox: Rect, anchorBBox: Rect, widgetSize: Size): number { + viewportBBox: Rect, + anchorBBox: Rect, + widgetSize: Size +): number { // Flip the widget vertically if off the bottom. // The widget could go off the top of the window, but it would also go off // the bottom. The window is just too small. @@ -237,3 +247,25 @@ function calculateY( return anchorBBox.bottom; } } + +/** + * Determine if the owner is a field for purposes of repositioning. + * We can't simply check `instanceof Field` as that would introduce a circular + * dependency. + */ +function isRepositionable(item: any): item is Field { + return !!item?.repositionForWindowResize; +} + +/** + * Reposition the widget div if the owner of it says to. + * If the owner isn't a field, just give up and hide it. + */ +export function repositionForWindowResize(): void { + if (!isRepositionable(owner) || !owner.repositionForWindowResize()) { + // If the owner is not a Field, or if the owner returns false from the + // reposition method, we should hide the widget div. Otherwise, we'll assume + // the owner handled any needed resize. + hide(); + } +} diff --git a/core/workspace.ts b/core/workspace.ts index 04a03eb91..f184ce6a8 100644 --- a/core/workspace.ts +++ b/core/workspace.ts @@ -35,7 +35,6 @@ import type {WorkspaceComment} from './workspace_comment.js'; import {IProcedureMap} from './interfaces/i_procedure_map.js'; import {ObservableProcedureMap} from './observable_procedure_map.js'; - /** * Class for a workspace. This is a data structure that contains blocks. * There is no UI, and can be created headlessly. @@ -120,19 +119,22 @@ export class Workspace implements IASTNodeLocation { * these by tracking "potential" variables in the flyout. These variables * become real when references to them are dragged into the main workspace. */ - private potentialVariableMap: VariableMap|null = null; + private potentialVariableMap: VariableMap | null = null; /** @param opt_options Dictionary of options. */ constructor(opt_options?: Options) { this.id = idGenerator.genUid(); common.registerWorkspace(this); - this.options = opt_options || new Options(({} as BlocklyOptions)); + this.options = opt_options || new Options({} as BlocklyOptions); this.RTL = !!this.options.RTL; this.horizontalLayout = !!this.options.horizontalLayout; this.toolboxPosition = this.options.toolboxPosition; const connectionCheckerClass = registry.getClassFromOptions( - registry.Type.CONNECTION_CHECKER, this.options, true); + registry.Type.CONNECTION_CHECKER, + this.options, + true + ); /** * An object that encapsulates logic for safety, type, and dragging checks. */ @@ -149,8 +151,6 @@ export class Workspace implements IASTNodeLocation { /** * Dispose of this workspace. * Unlink from all DOM elements to prevent memory leaks. - * - * @suppress {checkTypes} */ dispose() { this.listeners.length = 0; @@ -168,10 +168,12 @@ export class Workspace implements IASTNodeLocation { * @returns The comparison value. This tells Array.sort() how to change object * a's index. */ - private sortObjects_(a: Block|WorkspaceComment, b: Block|WorkspaceComment): - number { + private sortObjects_( + a: Block | WorkspaceComment, + b: Block | WorkspaceComment + ): number { const offset = - Math.sin(math.toRadians(Workspace.SCAN_ANGLE)) * (this.RTL ? -1 : 1); + Math.sin(math.toRadians(Workspace.SCAN_ANGLE)) * (this.RTL ? -1 : 1); const aXY = a.getRelativeToSurfaceXY(); const bXY = b.getRelativeToSurfaceXY(); return aXY.y + offset * aXY.x - (bXY.y + offset * bXY.x); @@ -193,7 +195,7 @@ export class Workspace implements IASTNodeLocation { */ removeTopBlock(block: Block) { if (!arrayUtils.removeElem(this.topBlocks, block)) { - throw Error('Block not present in workspace\'s list of top-most blocks.'); + throw Error("Block not present in workspace's list of top-most blocks."); } } @@ -206,7 +208,7 @@ export class Workspace implements IASTNodeLocation { */ getTopBlocks(ordered: boolean): Block[] { // Copy the topBlocks list. - const blocks = (new Array()).concat(this.topBlocks); + const blocks = new Array().concat(this.topBlocks); if (ordered && blocks.length > 1) { blocks.sort(this.sortObjects_.bind(this)); } @@ -254,7 +256,7 @@ export class Workspace implements IASTNodeLocation { blocks.sort(this.sortObjects_.bind(this)); } - return blocks.filter(function(block: Block) { + return blocks.filter(function (block: Block) { return !block.isInsertionMarker(); }); } @@ -272,8 +274,10 @@ export class Workspace implements IASTNodeLocation { // need to move to a separate function. if (this.commentDB.has(comment.id)) { console.warn( - 'Overriding an existing comment on this workspace, with id "' + - comment.id + '"'); + 'Overriding an existing comment on this workspace, with id "' + + comment.id + + '"' + ); } this.commentDB.set(comment.id, comment); } @@ -287,8 +291,8 @@ export class Workspace implements IASTNodeLocation { removeTopComment(comment: WorkspaceComment) { if (!arrayUtils.removeElem(this.topComments, comment)) { throw Error( - 'Comment not present in workspace\'s list of top-most ' + - 'comments.'); + "Comment not present in workspace's list of top-most " + 'comments.' + ); } // Note: If the comment database starts to hold block comments, this may // need to move to a separate function. @@ -305,7 +309,7 @@ export class Workspace implements IASTNodeLocation { */ getTopComments(ordered: boolean): WorkspaceComment[] { // Copy the topComments list. - const comments = (new Array()).concat(this.topComments); + const comments = new Array().concat(this.topComments); if (ordered && comments.length > 1) { comments.sort(this.sortObjects_.bind(this)); } @@ -338,7 +342,7 @@ export class Workspace implements IASTNodeLocation { // Insertion markers exist on the workspace for rendering reasons, but // aren't "real" blocks from a developer perspective. - const filtered = blocks.filter(function(block) { + const filtered = blocks.filter(function (block) { return !block.isInsertionMarker(); }); @@ -392,8 +396,11 @@ export class Workspace implements IASTNodeLocation { * @param opt_id The unique ID of the variable. This will default to a UUID. * @returns The newly created variable. */ - createVariable(name: string, opt_type?: string|null, opt_id?: string|null): - VariableModel { + createVariable( + name: string, + opt_type?: string | null, + opt_id?: string | null + ): VariableModel { return this.variableMap.createVariable(name, opt_type, opt_id); } @@ -426,7 +433,7 @@ export class Workspace implements IASTNodeLocation { * the empty string, which is a specific type. * @returns The variable with the given name. */ - getVariable(name: string, opt_type?: string): VariableModel|null { + getVariable(name: string, opt_type?: string): VariableModel | null { // TODO (#1559): Possibly delete this function after resolving #1559. return this.variableMap.getVariable(name, opt_type); } @@ -437,7 +444,7 @@ export class Workspace implements IASTNodeLocation { * @param id The ID to check for. * @returns The variable with the given ID. */ - getVariableById(id: string): VariableModel|null { + getVariableById(id: string): VariableModel | null { return this.variableMap.getVariableById(id); } @@ -449,7 +456,7 @@ export class Workspace implements IASTNodeLocation { * @returns The sought after variables of the passed in type. An empty array * if none are found. */ - getVariablesOfType(type: string|null): VariableModel[] { + getVariablesOfType(type: string | null): VariableModel[] { return this.variableMap.getVariablesOfType(type); } @@ -505,8 +512,9 @@ export class Workspace implements IASTNodeLocation { */ newBlock(prototypeName: string, opt_id?: string): Block { throw new Error( - 'The implementation of newBlock should be ' + - 'monkey-patched in by blockly.ts'); + 'The implementation of newBlock should be ' + + 'monkey-patched in by blockly.ts' + ); } /* eslint-enable */ @@ -536,9 +544,10 @@ export class Workspace implements IASTNodeLocation { return Infinity; } - const maxInstanceOfType = this.options.maxInstances[type] !== undefined ? - this.options.maxInstances[type] : - Infinity; + const maxInstanceOfType = + this.options.maxInstances[type] !== undefined + ? this.options.maxInstances[type] + : Infinity; return maxInstanceOfType - this.getBlocksByType(type, false).length; } @@ -614,8 +623,11 @@ export class Workspace implements IASTNodeLocation { } let events = [inputEvent]; // Do another undo/redo if the next one is of the same group. - while (inputStack.length && inputEvent.group && - inputEvent.group === inputStack[inputStack.length - 1].group) { + while ( + inputStack.length && + inputEvent.group && + inputEvent.group === inputStack[inputStack.length - 1].group + ) { const event = inputStack.pop(); if (!event) continue; events.push(event); @@ -654,7 +666,7 @@ export class Workspace implements IASTNodeLocation { * @param func Function to call. * @returns Obsolete return value, ignore. */ - addChangeListener(func: Function): Function { + addChangeListener(func: (e: Abstract) => void): Function { this.listeners.push(func); return func; } @@ -693,7 +705,7 @@ export class Workspace implements IASTNodeLocation { * @param id ID of block to find. * @returns The sought after block, or null if not found. */ - getBlockById(id: string): Block|null { + getBlockById(id: string): Block | null { return this.blockDB.get(id) || null; } @@ -725,7 +737,7 @@ export class Workspace implements IASTNodeLocation { * @returns The sought after comment, or null if not found. * @internal */ - getCommentById(id: string): WorkspaceComment|null { + getCommentById(id: string): WorkspaceComment | null { return this.commentDB.get(id) ?? null; } @@ -755,7 +767,7 @@ export class Workspace implements IASTNodeLocation { * @returns The potential variable map. * @internal */ - getPotentialVariableMap(): VariableMap|null { + getPotentialVariableMap(): VariableMap | null { return this.potentialVariableMap; } @@ -792,13 +804,35 @@ export class Workspace implements IASTNodeLocation { return this.procedureMap; } + /** + * Returns the root workspace of this workspace if the workspace has + * parent(s). + * + * E.g. workspaces in flyouts and mini workspace bubbles have parent + * workspaces. + */ + getRootWorkspace(): Workspace | null { + let outerWs = null; + const parent = this.options.parentWorkspace; + // If we were in a flyout in a mutator, need to go up two levels to find + // the actual parent. + if (this.isFlyout) { + if (parent && parent.options) { + outerWs = parent.options.parentWorkspace; + } + } else if (parent) { + outerWs = parent; + } + return outerWs; + } + /** * Find the workspace with the specified ID. * * @param id ID of workspace to find. * @returns The sought after workspace or null if not found. */ - static getById(id: string): Workspace|null { + static getById(id: string): Workspace | null { return common.getWorkspaceById(id); } diff --git a/core/workspace_audio.ts b/core/workspace_audio.ts index 7874161f8..d045ab4db 100644 --- a/core/workspace_audio.ts +++ b/core/workspace_audio.ts @@ -16,7 +16,6 @@ goog.declareModuleId('Blockly.WorkspaceAudio'); import * as userAgent from './utils/useragent.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Prevent a sound from playing if another sound preceded it within this many * milliseconds. @@ -31,7 +30,7 @@ export class WorkspaceAudio { private sounds = new Map(); /** Time that the last sound was played. */ - private lastSound_: Date|null = null; + private lastSound_: Date | null = null; /** * @param parentWorkspace The parent of the workspace this audio object @@ -98,10 +97,10 @@ export class WorkspaceAudio { // pause() we will get an exception: (DOMException: The play() request // was interrupted) See more: // https://developers.google.com/web/updates/2017/06/play-request-was-interrupted - playPromise.then(sound.pause) - .catch( - // Play without user interaction was prevented. - function() {}); + playPromise.then(sound.pause).catch( + // Play without user interaction was prevented. + function () {} + ); } else { sound.pause(); } @@ -127,8 +126,10 @@ export class WorkspaceAudio { if (sound) { // Don't play one sound on top of another. const now = new Date(); - if (this.lastSound_ !== null && - now.getTime() - this.lastSound_.getTime() < SOUND_LIMIT) { + if ( + this.lastSound_ !== null && + now.getTime() - this.lastSound_.getTime() < SOUND_LIMIT + ) { return; } this.lastSound_ = now; diff --git a/core/workspace_comment.ts b/core/workspace_comment.ts index 88418da82..ad900d23b 100644 --- a/core/workspace_comment.ts +++ b/core/workspace_comment.ts @@ -19,7 +19,6 @@ import * as idGenerator from './utils/idgenerator.js'; import * as xml from './utils/xml.js'; import type {Workspace} from './workspace.js'; - /** * Class for a workspace comment. */ @@ -30,11 +29,11 @@ export class WorkspaceComment { protected width_: number; protected RTL: boolean; - private deletable_ = true; + private deletable = true; - private movable_ = true; + private movable = true; - private editable_ = true; + private editable = true; protected content_: string; /** Whether this comment has been disposed. */ @@ -51,11 +50,16 @@ export class WorkspaceComment { * ID. */ constructor( - public workspace: Workspace, content: string, height: number, - width: number, opt_id?: string) { - this.id = opt_id && !workspace.getCommentById(opt_id) ? - opt_id : - idGenerator.genUid(); + public workspace: Workspace, + content: string, + height: number, + width: number, + opt_id?: string + ) { + this.id = + opt_id && !workspace.getCommentById(opt_id) + ? opt_id + : idGenerator.genUid(); workspace.addTopComment(this); @@ -164,8 +168,9 @@ export class WorkspaceComment { * @internal */ moveBy(dx: number, dy: number) { - const event = - new (eventUtils.get(eventUtils.COMMENT_MOVE))(this) as CommentMove; + const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( + this + ) as CommentMove; this.xy_.translate(dx, dy); event.recordNew(); eventUtils.fire(event); @@ -178,8 +183,9 @@ export class WorkspaceComment { * @internal */ isDeletable(): boolean { - return this.deletable_ && - !(this.workspace && this.workspace.options.readOnly); + return ( + this.deletable && !(this.workspace && this.workspace.options.readOnly) + ); } /** @@ -189,7 +195,7 @@ export class WorkspaceComment { * @internal */ setDeletable(deletable: boolean) { - this.deletable_ = deletable; + this.deletable = deletable; } /** @@ -199,8 +205,7 @@ export class WorkspaceComment { * @internal */ isMovable(): boolean { - return this.movable_ && - !(this.workspace && this.workspace.options.readOnly); + return this.movable && !(this.workspace && this.workspace.options.readOnly); } /** @@ -210,7 +215,7 @@ export class WorkspaceComment { * @internal */ setMovable(movable: boolean) { - this.movable_ = movable; + this.movable = movable; } /** @@ -219,8 +224,9 @@ export class WorkspaceComment { * @returns True if editable. */ isEditable(): boolean { - return this.editable_ && - !(this.workspace && this.workspace.options.readOnly); + return ( + this.editable && !(this.workspace && this.workspace.options.readOnly) + ); } /** @@ -229,7 +235,7 @@ export class WorkspaceComment { * @param editable True if editable. */ setEditable(editable: boolean) { - this.editable_ = editable; + this.editable = editable; } /** @@ -250,8 +256,13 @@ export class WorkspaceComment { */ setContent(content: string) { if (this.content_ !== content) { - eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_CHANGE))( - this, this.content_, content)); + eventUtils.fire( + new (eventUtils.get(eventUtils.COMMENT_CHANGE))( + this, + this.content_, + content + ) + ); this.content_ = content; } } @@ -305,7 +316,8 @@ export class WorkspaceComment { } try { eventUtils.fire( - new (eventUtils.get(eventUtils.COMMENT_CREATE))(comment)); + new (eventUtils.get(eventUtils.COMMENT_CREATE))(comment) + ); } finally { eventUtils.setGroup(existingGroup); } @@ -323,8 +335,13 @@ export class WorkspaceComment { static fromXml(xmlComment: Element, workspace: Workspace): WorkspaceComment { const info = WorkspaceComment.parseAttributes(xmlComment); - const comment = - new WorkspaceComment(workspace, info.content, info.h, info.w, info.id); + const comment = new WorkspaceComment( + workspace, + info.content, + info.h, + info.w, + info.id + ); const xmlX = xmlComment.getAttribute('x'); const xmlY = xmlComment.getAttribute('y'); @@ -346,12 +363,12 @@ export class WorkspaceComment { * @internal */ static parseAttributes(xml: Element): { - id: string, - w: number, - h: number, - x: number, - y: number, - content: string + id: string; + w: number; + h: number; + x: number; + y: number; + content: string; } { const xmlH = xml.getAttribute('h'); const xmlW = xml.getAttribute('w'); diff --git a/core/workspace_comment_svg.ts b/core/workspace_comment_svg.ts index b440b0679..98127cb8e 100644 --- a/core/workspace_comment_svg.ts +++ b/core/workspace_comment_svg.ts @@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.WorkspaceCommentSvg'); // Unused import preserved for side-effects. Remove if unneeded. import './events/events_selected.js'; -import type {BlockDragSurfaceSvg} from './block_drag_surface.js'; import * as browserEvents from './browser_events.js'; import * as common from './common.js'; // import * as ContextMenu from './contextmenu.js'; @@ -34,7 +33,6 @@ import * as svgMath from './utils/svg_math.js'; import {WorkspaceComment} from './workspace_comment.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** Size of the resize icon. */ const RESIZE_SIZE = 8; @@ -47,8 +45,10 @@ const TEXTAREA_OFFSET = 2; /** * Class for a workspace comment's SVG representation. */ -export class WorkspaceCommentSvg extends WorkspaceComment implements - IBoundedElement, IBubble, ICopyable { +export class WorkspaceCommentSvg + extends WorkspaceComment + implements IBoundedElement, IBubble, ICopyable +{ /** * The width and height to use to size a workspace comment when it is first * added, before it has been edited by the user. @@ -62,36 +62,36 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements override workspace: WorkspaceSvg; /** Mouse up event data. */ - private onMouseUpWrapper_: browserEvents.Data|null = null; + private onMouseUpWrapper: browserEvents.Data | null = null; /** Mouse move event data. */ - private onMouseMoveWrapper_: browserEvents.Data|null = null; + private onMouseMoveWrapper: browserEvents.Data | null = null; /** Whether event handlers have been initialized. */ - private eventsInit_ = false; - private textarea_: HTMLTextAreaElement|null = null; + private eventsInit = false; + private textarea: HTMLTextAreaElement | null = null; - private svgRectTarget_: SVGRectElement|null = null; + private svgRectTarget: SVGRectElement | null = null; - private svgHandleTarget_: SVGRectElement|null = null; + private svgHandleTarget: SVGRectElement | null = null; - private foreignObject_: SVGForeignObjectElement|null = null; + private foreignObject: SVGForeignObjectElement | null = null; - private resizeGroup_: SVGGElement|null = null; + private resizeGroup: SVGGElement | null = null; - private deleteGroup_: SVGGElement|null = null; + private deleteGroup: SVGGElement | null = null; - private deleteIconBorder_: SVGCircleElement|null = null; + private deleteIconBorder: SVGCircleElement | null = null; + + private focused = false; + private autoLayout = false; - private focused_ = false; - private autoLayout_ = false; // Create core elements for the block. - private readonly svgGroup_: SVGElement; + private readonly svgGroup: SVGElement; svgRect_: SVGRectElement; /** Whether the comment is rendered onscreen and is a part of the DOM. */ - private rendered_ = false; - private readonly useDragSurface_: boolean; + private rendered = false; /** * @param workspace The block's workspace. @@ -102,10 +102,14 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * ID. */ constructor( - workspace: WorkspaceSvg, content: string, height: number, width: number, - opt_id?: string) { + workspace: WorkspaceSvg, + content: string, + height: number, + width: number, + opt_id?: string + ) { super(workspace, content, height, width, opt_id); - this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': 'blocklyComment'}); + this.svgGroup = dom.createSvgElement(Svg.G, {'class': 'blocklyComment'}); this.workspace = workspace; this.svgRect_ = dom.createSvgElement(Svg.RECT, { @@ -115,13 +119,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements 'rx': BORDER_RADIUS, 'ry': BORDER_RADIUS, }); - this.svgGroup_.appendChild(this.svgRect_); - - /** - * Whether to move the comment to the drag surface when it is dragged. - * True if it should move, false if it should be translated directly. - */ - this.useDragSurface_ = !!workspace.getBlockDragSurface(); + this.svgGroup.appendChild(this.svgRect_); this.render(); } @@ -145,9 +143,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_DELETE))(this)); } - dom.removeNode(this.svgGroup_); + dom.removeNode(this.svgGroup); // Dispose of any rendered components - this.disposeInternal_(); + this.disposeInternal(); eventUtils.disable(); super.dispose(); @@ -165,23 +163,29 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements if (!this.workspace.rendered) { throw TypeError('Workspace is headless.'); } - if (!this.workspace.options.readOnly && !this.eventsInit_) { + if (!this.workspace.options.readOnly && !this.eventsInit) { browserEvents.conditionalBind( - this.svgRectTarget_ as SVGRectElement, 'pointerdown', this, - this.pathMouseDown_); + this.svgRectTarget as SVGRectElement, + 'pointerdown', + this, + this.pathMouseDown + ); browserEvents.conditionalBind( - this.svgHandleTarget_ as SVGRectElement, 'pointerdown', this, - this.pathMouseDown_); + this.svgHandleTarget as SVGRectElement, + 'pointerdown', + this, + this.pathMouseDown + ); } - this.eventsInit_ = true; + this.eventsInit = true; this.updateMovable(); if (!this.getSvgRoot().parentNode) { this.workspace.getBubbleCanvas().appendChild(this.getSvgRoot()); } - if (!opt_noSelect && this.textarea_) { - this.textarea_.select(); + if (!opt_noSelect && this.textarea) { + this.textarea.select(); } } @@ -190,7 +194,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param e Pointer down event. */ - private pathMouseDown_(e: PointerEvent) { + private pathMouseDown(e: PointerEvent) { const gesture = this.workspace.getGesture(e); if (gesture) { gesture.handleBubbleStart(e, this); @@ -206,8 +210,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements // eslint-disable-next-line @typescript-eslint/no-unused-vars showContextMenu(e: PointerEvent) { throw new Error( - 'The implementation of showContextMenu should be ' + - 'monkey-patched in by blockly.ts'); + 'The implementation of showContextMenu should be ' + + 'monkey-patched in by blockly.ts' + ); } /** @@ -231,7 +236,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements } } const event = new (eventUtils.get(eventUtils.SELECTED))( - oldId, this.id, this.workspace.id); + oldId, + this.id, + this.workspace.id + ); eventUtils.fire(event); common.setSelected(this); this.addSelect(); @@ -247,7 +255,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements return; } const event = new (eventUtils.get(eventUtils.SELECTED))( - this.id, null, this.workspace.id); + this.id, + null, + this.workspace.id + ); eventUtils.fire(event); common.setSelected(null); this.removeSelect(); @@ -260,7 +271,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ addSelect() { - dom.addClass(this.svgGroup_, 'blocklySelected'); + dom.addClass(this.svgGroup, 'blocklySelected'); this.setFocus(); } @@ -270,7 +281,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ removeSelect() { - dom.addClass(this.svgGroup_, 'blocklySelected'); + dom.addClass(this.svgGroup, 'blocklySelected'); this.blurFocus(); } @@ -280,7 +291,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ addFocus() { - dom.addClass(this.svgGroup_, 'blocklyFocused'); + dom.addClass(this.svgGroup, 'blocklyFocused'); } /** @@ -289,7 +300,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ removeFocus() { - dom.removeClass(this.svgGroup_, 'blocklyFocused'); + dom.removeClass(this.svgGroup, 'blocklyFocused'); } /** @@ -306,31 +317,19 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements let x = 0; let y = 0; - const dragSurfaceGroup = this.useDragSurface_ ? - this.workspace.getBlockDragSurface()!.getGroup() : - null; - - let element: Node|null = this.getSvgRoot(); + let element: Node | null = this.getSvgRoot(); if (element) { do { // Loop through this comment and every parent. const xy = svgMath.getRelativeXY(element as Element); x += xy.x; y += xy.y; - // If this element is the current element on the drag surface, include - // the translation of the drag surface itself. - if (this.useDragSurface_ && - this.workspace.getBlockDragSurface()!.getCurrentBlock() === - element) { - const surfaceTranslation = - this.workspace.getBlockDragSurface()!.getSurfaceTranslation(); - x += surfaceTranslation.x; - y += surfaceTranslation.y; - } - element = element.parentNode; - } while (element && element !== this.workspace.getBubbleCanvas() && - element !== dragSurfaceGroup); + } while ( + element && + element !== this.workspace.getBubbleCanvas() && + element !== null + ); } this.xy_ = new Coordinate(x, y); return this.xy_; @@ -344,8 +343,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ override moveBy(dx: number, dy: number) { - const event = - new (eventUtils.get(eventUtils.COMMENT_MOVE))(this) as CommentMove; + const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( + this + ) as CommentMove; // TODO: Do I need to look up the relative to surface XY position here? const xy = this.getRelativeToSurfaceXY(); this.translate(xy.x + dx, xy.y + dy); @@ -366,47 +366,20 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements translate(x: number, y: number) { this.xy_ = new Coordinate(x, y); this.getSvgRoot().setAttribute( - 'transform', 'translate(' + x + ',' + y + ')'); + 'transform', + 'translate(' + x + ',' + y + ')' + ); } /** - * Move this comment to its workspace's drag surface, accounting for - * positioning. Generally should be called at the same time as - * setDragging(true). Does nothing if useDragSurface_ is false. + * Move this comment during a drag. * - * @internal - */ - moveToDragSurface() { - if (!this.useDragSurface_) { - return; - } - // The translation for drag surface blocks, - // is equal to the current relative-to-surface position, - // to keep the position in sync as it move on/off the surface. - // This is in workspace coordinates. - const xy = this.getRelativeToSurfaceXY(); - this.clearTransformAttributes_(); - this.workspace.getBlockDragSurface()!.translateSurface(xy.x, xy.y); - // Execute the move on the top-level SVG component - this.workspace.getBlockDragSurface()!.setBlocksAndShow(this.getSvgRoot()); - } - - /** - * Move this comment during a drag, taking into account whether we are using a - * drag surface to translate blocks. - * - * @param dragSurface The surface that carries rendered items during a drag, - * or null if no drag surface is in use. * @param newLoc The location to translate to, in workspace coordinates. * @internal */ - moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate) { - if (dragSurface) { - dragSurface.translateSurface(newLoc.x, newLoc.y); - } else { - const translation = `translate(${newLoc.x}, ${newLoc.y})`; - this.getSvgRoot().setAttribute('transform', translation); - } + moveDuringDrag(newLoc: Coordinate) { + const translation = `translate(${newLoc.x}, ${newLoc.y})`; + this.getSvgRoot().setAttribute('transform', translation); } /** @@ -424,7 +397,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * Clear the comment of transform="..." attributes. * Used when the comment is switching from 3d to 2d transform or vice versa. */ - private clearTransformAttributes_() { + private clearTransformAttributes() { this.getSvgRoot().removeAttribute('transform'); } @@ -464,9 +437,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements */ updateMovable() { if (this.isMovable()) { - dom.addClass(this.svgGroup_, 'blocklyDraggable'); + dom.addClass(this.svgGroup, 'blocklyDraggable'); } else { - dom.removeClass(this.svgGroup_, 'blocklyDraggable'); + dom.removeClass(this.svgGroup, 'blocklyDraggable'); } } @@ -488,8 +461,8 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements */ override setEditable(editable: boolean) { super.setEditable(editable); - if (this.textarea_) { - this.textarea_.readOnly = !editable; + if (this.textarea) { + this.textarea.readOnly = !editable; } } @@ -515,7 +488,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ getSvgRoot(): SVGElement { - return this.svgGroup_; + return this.svgGroup; } /** @@ -525,7 +498,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ override getContent(): string { - return this.textarea_ ? this.textarea_.value : this.content_; + return this.textarea ? this.textarea.value : this.content_; } /** @@ -536,8 +509,8 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements */ override setContent(content: string) { super.setContent(content); - if (this.textarea_) { - this.textarea_.value = content; + if (this.textarea) { + this.textarea.value = content; } } @@ -549,9 +522,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements */ setDeleteStyle(enable: boolean) { if (enable) { - dom.addClass(this.svgGroup_, 'blocklyDraggingDelete'); + dom.addClass(this.svgGroup, 'blocklyDraggingDelete'); } else { - dom.removeClass(this.svgGroup_, 'blocklyDraggingDelete'); + dom.removeClass(this.svgGroup, 'blocklyDraggingDelete'); } } @@ -574,7 +547,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ override toXmlWithXY(opt_noId?: boolean): Element { - let width = 0; // Not used in LTR. + let width = 0; // Not used in LTR. if (this.workspace.RTL) { // Here be performance dragons: This calls getMetrics(). width = this.workspace.getWidth(); @@ -582,7 +555,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements const element = this.toXml(opt_noId); const xy = this.getRelativeToSurfaceXY(); element.setAttribute( - 'x', String(Math.round(this.workspace.RTL ? width - xy.x : xy.x))); + 'x', + String(Math.round(this.workspace.RTL ? width - xy.x : xy.x)) + ); element.setAttribute('y', String(Math.round(xy.y))); element.setAttribute('h', String(this.getHeight())); element.setAttribute('w', String(this.getWidth())); @@ -609,7 +584,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @returns Object with height and width properties in workspace units. * @internal */ - getHeightWidth(): {height: number, width: number} { + getHeightWidth(): {height: number; width: number} { return {width: this.getWidth(), height: this.getHeight()}; } @@ -619,57 +594,73 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ render() { - if (this.rendered_) { + if (this.rendered) { return; } const size = this.getHeightWidth(); // Add text area - const foreignObject = this.createEditor_(); - this.svgGroup_.appendChild(foreignObject); + const foreignObject = this.createEditor(); + this.svgGroup.appendChild(foreignObject); - this.svgHandleTarget_ = dom.createSvgElement( - Svg.RECT, {'class': 'blocklyCommentHandleTarget', 'x': 0, 'y': 0}); - this.svgGroup_.appendChild(this.svgHandleTarget_); - this.svgRectTarget_ = dom.createSvgElement(Svg.RECT, { + this.svgHandleTarget = dom.createSvgElement(Svg.RECT, { + 'class': 'blocklyCommentHandleTarget', + 'x': 0, + 'y': 0, + }); + this.svgGroup.appendChild(this.svgHandleTarget); + this.svgRectTarget = dom.createSvgElement(Svg.RECT, { 'class': 'blocklyCommentTarget', 'x': 0, 'y': 0, 'rx': BORDER_RADIUS, 'ry': BORDER_RADIUS, }); - this.svgGroup_.appendChild(this.svgRectTarget_); + this.svgGroup.appendChild(this.svgRectTarget); // Add the resize icon - this.addResizeDom_(); + this.addResizeDom(); if (this.isDeletable()) { // Add the delete icon - this.addDeleteDom_(); + this.addDeleteDom(); } - this.setSize_(size.width, size.height); + this.setSize(size.width, size.height); // Set the content - this.textarea_!.value = this.content_; + this.textarea!.value = this.content_; - this.rendered_ = true; + this.rendered = true; - if (this.resizeGroup_) { + if (this.resizeGroup) { browserEvents.conditionalBind( - (this.resizeGroup_), 'pointerdown', this, this.resizeMouseDown_); + this.resizeGroup, + 'pointerdown', + this, + this.resizeMouseDown + ); } if (this.isDeletable()) { browserEvents.conditionalBind( - this.deleteGroup_ as SVGGElement, 'pointerdown', this, - this.deleteMouseDown_); + this.deleteGroup as SVGGElement, + 'pointerdown', + this, + this.deleteMouseDown + ); browserEvents.conditionalBind( - this.deleteGroup_ as SVGGElement, 'pointerout', this, - this.deleteMouseOut_); + this.deleteGroup as SVGGElement, + 'pointerout', + this, + this.deleteMouseOut + ); browserEvents.conditionalBind( - this.deleteGroup_ as SVGGElement, 'pointerup', this, - this.deleteMouseUp_); + this.deleteGroup as SVGGElement, + 'pointerup', + this, + this.deleteMouseUp + ); } } @@ -678,7 +669,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @returns The top-level node of the editor. */ - private createEditor_(): Element { + private createEditor(): Element { /* Create the editor. Here's the markup that will be generated: */ - this.foreignObject_ = dom.createSvgElement(Svg.FOREIGNOBJECT, { + this.foreignObject = dom.createSvgElement(Svg.FOREIGNOBJECT, { 'x': 0, 'y': WorkspaceCommentSvg.TOP_OFFSET, 'class': 'blocklyCommentForeignObject', @@ -696,87 +687,111 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements const body = document.createElementNS(dom.HTML_NS, 'body'); body.setAttribute('xmlns', dom.HTML_NS); body.className = 'blocklyMinimalBody'; - const textarea = document.createElementNS(dom.HTML_NS, 'textarea') as - HTMLTextAreaElement; + const textarea = document.createElementNS( + dom.HTML_NS, + 'textarea' + ) as HTMLTextAreaElement; textarea.className = 'blocklyCommentTextarea'; textarea.setAttribute('dir', this.RTL ? 'RTL' : 'LTR'); textarea.readOnly = !this.isEditable(); body.appendChild(textarea); - this.textarea_ = textarea; - this.foreignObject_.appendChild(body); + this.textarea = textarea; + this.foreignObject.appendChild(body); // Don't zoom with mousewheel. browserEvents.conditionalBind( - textarea, 'wheel', this, function(e: WheelEvent) { - e.stopPropagation(); - }); + textarea, + 'wheel', + this, + function (e: WheelEvent) { + e.stopPropagation(); + } + ); browserEvents.conditionalBind( - textarea, 'change', this, - function(this: WorkspaceCommentSvg, _e: Event) { - this.setContent(textarea.value); - }); - return this.foreignObject_; + textarea, + 'change', + this, + function (this: WorkspaceCommentSvg, _e: Event) { + this.setContent(textarea.value); + } + ); + return this.foreignObject; } /** Add the resize icon to the DOM */ - private addResizeDom_() { - this.resizeGroup_ = dom.createSvgElement( - Svg.G, {'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'}, - this.svgGroup_); + private addResizeDom() { + this.resizeGroup = dom.createSvgElement( + Svg.G, + {'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'}, + this.svgGroup + ); dom.createSvgElement( - Svg.POLYGON, { - 'points': - `0,${RESIZE_SIZE} ${RESIZE_SIZE},${RESIZE_SIZE} ${RESIZE_SIZE},0`, - }, - this.resizeGroup_); + Svg.POLYGON, + { + 'points': `0,${RESIZE_SIZE} ${RESIZE_SIZE},${RESIZE_SIZE} ${RESIZE_SIZE},0`, + }, + this.resizeGroup + ); dom.createSvgElement( - Svg.LINE, { - 'class': 'blocklyResizeLine', - 'x1': RESIZE_SIZE / 3, - 'y1': RESIZE_SIZE - 1, - 'x2': RESIZE_SIZE - 1, - 'y2': RESIZE_SIZE / 3, - }, - this.resizeGroup_); + Svg.LINE, + { + 'class': 'blocklyResizeLine', + 'x1': RESIZE_SIZE / 3, + 'y1': RESIZE_SIZE - 1, + 'x2': RESIZE_SIZE - 1, + 'y2': RESIZE_SIZE / 3, + }, + this.resizeGroup + ); dom.createSvgElement( - Svg.LINE, { - 'class': 'blocklyResizeLine', - 'x1': RESIZE_SIZE * 2 / 3, - 'y1': RESIZE_SIZE - 1, - 'x2': RESIZE_SIZE - 1, - 'y2': RESIZE_SIZE * 2 / 3, - }, - this.resizeGroup_); + Svg.LINE, + { + 'class': 'blocklyResizeLine', + 'x1': (RESIZE_SIZE * 2) / 3, + 'y1': RESIZE_SIZE - 1, + 'x2': RESIZE_SIZE - 1, + 'y2': (RESIZE_SIZE * 2) / 3, + }, + this.resizeGroup + ); } /** Add the delete icon to the DOM */ - private addDeleteDom_() { - this.deleteGroup_ = dom.createSvgElement( - Svg.G, {'class': 'blocklyCommentDeleteIcon'}, this.svgGroup_); - this.deleteIconBorder_ = dom.createSvgElement( - Svg.CIRCLE, - {'class': 'blocklyDeleteIconShape', 'r': '7', 'cx': '7.5', 'cy': '7.5'}, - this.deleteGroup_); + private addDeleteDom() { + this.deleteGroup = dom.createSvgElement( + Svg.G, + {'class': 'blocklyCommentDeleteIcon'}, + this.svgGroup + ); + this.deleteIconBorder = dom.createSvgElement( + Svg.CIRCLE, + {'class': 'blocklyDeleteIconShape', 'r': '7', 'cx': '7.5', 'cy': '7.5'}, + this.deleteGroup + ); // x icon. dom.createSvgElement( - Svg.LINE, { - 'x1': '5', - 'y1': '10', - 'x2': '10', - 'y2': '5', - 'stroke': '#fff', - 'stroke-width': '2', - }, - this.deleteGroup_); + Svg.LINE, + { + 'x1': '5', + 'y1': '10', + 'x2': '10', + 'y2': '5', + 'stroke': '#fff', + 'stroke-width': '2', + }, + this.deleteGroup + ); dom.createSvgElement( - Svg.LINE, { - 'x1': '5', - 'y1': '5', - 'x2': '10', - 'y2': '10', - 'stroke': '#fff', - 'stroke-width': '2', - }, - this.deleteGroup_); + Svg.LINE, + { + 'x1': '5', + 'y1': '5', + 'x2': '10', + 'y2': '10', + 'stroke': '#fff', + 'stroke-width': '2', + }, + this.deleteGroup + ); } /** @@ -784,8 +799,8 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param e Pointer down event. */ - private resizeMouseDown_(e: PointerEvent) { - this.unbindDragEvents_(); + private resizeMouseDown(e: PointerEvent) { + this.unbindDragEvents(); if (browserEvents.isRightButton(e)) { // No right-click. e.stopPropagation(); @@ -793,14 +808,25 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements } // Left-click (or middle click) this.workspace.startDrag( - e, - new Coordinate( - this.workspace.RTL ? -this.width_ : this.width_, this.height_)); + e, + new Coordinate( + this.workspace.RTL ? -this.width_ : this.width_, + this.height_ + ) + ); - this.onMouseUpWrapper_ = browserEvents.conditionalBind( - document, 'pointerup', this, this.resizeMouseUp_); - this.onMouseMoveWrapper_ = browserEvents.conditionalBind( - document, 'pointermove', this, this.resizeMouseMove_); + this.onMouseUpWrapper = browserEvents.conditionalBind( + document, + 'pointerup', + this, + this.resizeMouseUp + ); + this.onMouseMoveWrapper = browserEvents.conditionalBind( + document, + 'pointermove', + this, + this.resizeMouseMove + ); this.workspace.hideChaff(); // This event has been handled. No need to bubble up to the document. e.stopPropagation(); @@ -811,10 +837,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param e Pointer down event. */ - private deleteMouseDown_(e: PointerEvent) { + private deleteMouseDown(e: PointerEvent) { // Highlight the delete icon. - if (this.deleteIconBorder_) { - dom.addClass(this.deleteIconBorder_, 'blocklyDeleteIconHighlighted'); + if (this.deleteIconBorder) { + dom.addClass(this.deleteIconBorder, 'blocklyDeleteIconHighlighted'); } // This event has been handled. No need to bubble up to the document. e.stopPropagation(); @@ -825,10 +851,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param _e Pointer out event. */ - private deleteMouseOut_(_e: PointerEvent) { + private deleteMouseOut(_e: PointerEvent) { // Restore highlight on the delete icon. - if (this.deleteIconBorder_) { - dom.removeClass(this.deleteIconBorder_, 'blocklyDeleteIconHighlighted'); + if (this.deleteIconBorder) { + dom.removeClass(this.deleteIconBorder, 'blocklyDeleteIconHighlighted'); } } @@ -837,7 +863,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param e Pointer up event. */ - private deleteMouseUp_(e: PointerEvent) { + private deleteMouseUp(e: PointerEvent) { // Delete this comment. this.dispose(); // This event has been handled. No need to bubble up to the document. @@ -845,14 +871,14 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements } /** Stop binding to the global pointerup and pointermove events. */ - private unbindDragEvents_() { - if (this.onMouseUpWrapper_) { - browserEvents.unbind(this.onMouseUpWrapper_); - this.onMouseUpWrapper_ = null; + private unbindDragEvents() { + if (this.onMouseUpWrapper) { + browserEvents.unbind(this.onMouseUpWrapper); + this.onMouseUpWrapper = null; } - if (this.onMouseMoveWrapper_) { - browserEvents.unbind(this.onMouseMoveWrapper_); - this.onMouseMoveWrapper_ = null; + if (this.onMouseMoveWrapper) { + browserEvents.unbind(this.onMouseMoveWrapper); + this.onMouseMoveWrapper = null; } } @@ -862,9 +888,9 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param _e Pointer up event. */ - private resizeMouseUp_(_e: PointerEvent) { + private resizeMouseUp(_e: PointerEvent) { Touch.clearTouchIdentifier(); - this.unbindDragEvents_(); + this.unbindDragEvents(); } /** @@ -872,31 +898,30 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * * @param e Pointer move event. */ - private resizeMouseMove_(e: PointerEvent) { - this.autoLayout_ = false; + private resizeMouseMove(e: PointerEvent) { + this.autoLayout = false; const newXY = this.workspace.moveDrag(e); - this.setSize_(this.RTL ? -newXY.x : newXY.x, newXY.y); + this.setSize(this.RTL ? -newXY.x : newXY.x, newXY.y); } /** * Callback function triggered when the comment has resized. * Resize the text area accordingly. */ - private resizeComment_() { + private resizeComment() { const size = this.getHeightWidth(); const topOffset = WorkspaceCommentSvg.TOP_OFFSET; const textOffset = TEXTAREA_OFFSET * 2; - this.foreignObject_?.setAttribute('width', String(size.width)); - this.foreignObject_?.setAttribute( - 'height', String(size.height - topOffset)); + this.foreignObject?.setAttribute('width', String(size.width)); + this.foreignObject?.setAttribute('height', String(size.height - topOffset)); if (this.RTL) { - this.foreignObject_?.setAttribute('x', String(-size.width)); + this.foreignObject?.setAttribute('x', String(-size.width)); } - if (!this.textarea_) return; - this.textarea_.style.width = size.width - textOffset + 'px'; - this.textarea_.style.height = size.height - textOffset - topOffset + 'px'; + if (!this.textarea) return; + this.textarea.style.width = size.width - textOffset + 'px'; + this.textarea.style.height = size.height - textOffset - topOffset + 'px'; } /** @@ -905,7 +930,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @param width width of the container * @param height height of the container */ - private setSize_(width: number, height: number) { + private setSize(width: number, height: number) { // Minimum size of a comment. width = Math.max(width, 45); height = Math.max(height, 20 + WorkspaceCommentSvg.TOP_OFFSET); @@ -913,48 +938,59 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements this.height_ = height; this.svgRect_.setAttribute('width', `${width}`); this.svgRect_.setAttribute('height', `${height}`); - this.svgRectTarget_?.setAttribute('width', `${width}`); - this.svgRectTarget_?.setAttribute('height', `${height}`); - this.svgHandleTarget_?.setAttribute('width', `${width}`); - this.svgHandleTarget_?.setAttribute( - 'height', String(WorkspaceCommentSvg.TOP_OFFSET)); + this.svgRectTarget?.setAttribute('width', `${width}`); + this.svgRectTarget?.setAttribute('height', `${height}`); + this.svgHandleTarget?.setAttribute('width', `${width}`); + this.svgHandleTarget?.setAttribute( + 'height', + String(WorkspaceCommentSvg.TOP_OFFSET) + ); if (this.RTL) { this.svgRect_.setAttribute('transform', 'scale(-1 1)'); - this.svgRectTarget_?.setAttribute('transform', 'scale(-1 1)'); + this.svgRectTarget?.setAttribute('transform', 'scale(-1 1)'); } - if (this.resizeGroup_) { + if (this.resizeGroup) { if (this.RTL) { // Mirror the resize group. - this.resizeGroup_.setAttribute( - 'transform', - 'translate(' + (-width + RESIZE_SIZE) + ',' + - (height - RESIZE_SIZE) + ') scale(-1 1)'); - this.deleteGroup_?.setAttribute( - 'transform', - 'translate(' + (-width + RESIZE_SIZE) + ',' + -RESIZE_SIZE + - ') scale(-1 1)'); + this.resizeGroup.setAttribute( + 'transform', + 'translate(' + + (-width + RESIZE_SIZE) + + ',' + + (height - RESIZE_SIZE) + + ') scale(-1 1)' + ); + this.deleteGroup?.setAttribute( + 'transform', + 'translate(' + + (-width + RESIZE_SIZE) + + ',' + + -RESIZE_SIZE + + ') scale(-1 1)' + ); } else { - this.resizeGroup_.setAttribute( - 'transform', - 'translate(' + (width - RESIZE_SIZE) + ',' + - (height - RESIZE_SIZE) + ')'); - this.deleteGroup_?.setAttribute( - 'transform', - 'translate(' + (width - RESIZE_SIZE) + ',' + -RESIZE_SIZE + ')'); + this.resizeGroup.setAttribute( + 'transform', + 'translate(' + + (width - RESIZE_SIZE) + + ',' + + (height - RESIZE_SIZE) + + ')' + ); + this.deleteGroup?.setAttribute( + 'transform', + 'translate(' + (width - RESIZE_SIZE) + ',' + -RESIZE_SIZE + ')' + ); } } // Allow the contents to resize. - this.resizeComment_(); + this.resizeComment(); } /** Dispose of any rendered comment components. */ - private disposeInternal_() { - this.textarea_ = null; - this.foreignObject_ = null; - this.svgRectTarget_ = null; - this.svgHandleTarget_ = null; + private disposeInternal() { this.disposed_ = true; } @@ -964,20 +1000,19 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ setFocus() { - this.focused_ = true; + this.focused = true; // Defer CSS changes. setTimeout(() => { if (this.disposed_) { return; } - this.textarea_!.focus(); + this.textarea!.focus(); this.addFocus(); - if (this.svgRectTarget_) { - dom.addClass(this.svgRectTarget_, 'blocklyCommentTargetFocused'); + if (this.svgRectTarget) { + dom.addClass(this.svgRectTarget, 'blocklyCommentTargetFocused'); } - if (this.svgHandleTarget_) { - dom.addClass( - this.svgHandleTarget_, 'blocklyCommentHandleTargetFocused'); + if (this.svgHandleTarget) { + dom.addClass(this.svgHandleTarget, 'blocklyCommentHandleTargetFocused'); } }, 0); } @@ -988,21 +1023,23 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ blurFocus() { - this.focused_ = false; + this.focused = false; // Defer CSS changes. setTimeout(() => { if (this.disposed_) { return; } - this.textarea_!.blur(); + this.textarea!.blur(); this.removeFocus(); - if (this.svgRectTarget_) { - dom.removeClass(this.svgRectTarget_, 'blocklyCommentTargetFocused'); + if (this.svgRectTarget) { + dom.removeClass(this.svgRectTarget, 'blocklyCommentTargetFocused'); } - if (this.svgHandleTarget_) { + if (this.svgHandleTarget) { dom.removeClass( - this.svgHandleTarget_, 'blocklyCommentHandleTargetFocused'); + this.svgHandleTarget, + 'blocklyCommentHandleTargetFocused' + ); } }, 0); } @@ -1018,15 +1055,22 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements * @internal */ static fromXmlRendered( - xmlComment: Element, workspace: WorkspaceSvg, - opt_wsWidth?: number): WorkspaceCommentSvg { + xmlComment: Element, + workspace: WorkspaceSvg, + opt_wsWidth?: number + ): WorkspaceCommentSvg { eventUtils.disable(); let comment; try { const info = WorkspaceComment.parseAttributes(xmlComment); comment = new WorkspaceCommentSvg( - workspace, info.content, info.h, info.w, info.id); + workspace, + info.content, + info.h, + info.w, + info.id + ); if (workspace.rendered) { comment.initSvg(true); comment.render(); diff --git a/core/workspace_drag_surface_svg.ts b/core/workspace_drag_surface_svg.ts index c20678609..08e19b656 100644 --- a/core/workspace_drag_surface_svg.ts +++ b/core/workspace_drag_surface_svg.ts @@ -20,7 +20,6 @@ import * as dom from './utils/dom.js'; import {Svg} from './utils/svg.js'; import * as svgMath from './utils/svg_math.js'; - /** * Blocks are moved into this SVG during a drag, improving performance. * The entire SVG is translated using CSS transforms instead of SVG so the @@ -38,7 +37,7 @@ export class WorkspaceDragSurfaceSvg { * The element to insert the block canvas and bubble canvas after when it * goes back in the DOM at the end of a drag. */ - private previousSibling: Element|null = null; + private previousSibling: Element | null = null; /** @param container Containing element. */ constructor(private readonly container: Element) { @@ -48,7 +47,7 @@ export class WorkspaceDragSurfaceSvg { /** Create the drag surface and inject it into the container. */ createDom() { if (this.SVG) { - return; // Already created. + return; // Already created. } /** * Dom structure when the workspace is being dragged. If there is no drag in @@ -85,7 +84,9 @@ export class WorkspaceDragSurfaceSvg { this.SVG.style.display = 'block'; dom.setCssTransform( - this.SVG, 'translate3d(' + fixedX + 'px, ' + fixedY + 'px, 0)'); + this.SVG, + 'translate3d(' + fixedX + 'px, ' + fixedY + 'px, 0)' + ); } /** @@ -96,7 +97,7 @@ export class WorkspaceDragSurfaceSvg { * @internal */ getSurfaceTranslation(): Coordinate { - return svgMath.getRelativeXY((this.SVG)); + return svgMath.getRelativeXY(this.SVG); } /** @@ -109,15 +110,22 @@ export class WorkspaceDragSurfaceSvg { clearAndHide(newSurface: SVGElement) { if (!newSurface) { throw Error( - 'Couldn\'t clear and hide the drag surface: missing new surface.'); + "Couldn't clear and hide the drag surface: missing new surface." + ); } const blockCanvas = this.SVG.childNodes[0] as Element; const bubbleCanvas = this.SVG.childNodes[1] as Element; - if (!blockCanvas || !bubbleCanvas || - !(blockCanvas.classList.contains('blocklyBlockCanvas') || - !bubbleCanvas.classList.contains('blocklyBubbleCanvas'))) { + if ( + !blockCanvas || + !bubbleCanvas || + !( + blockCanvas.classList.contains('blocklyBlockCanvas') || + !bubbleCanvas.classList.contains('blocklyBubbleCanvas') + ) + ) { throw Error( - 'Couldn\'t clear and hide the drag surface. A node was missing.'); + "Couldn't clear and hide the drag surface. A node was missing." + ); } // If there is a previous sibling, put the blockCanvas back right @@ -156,17 +164,26 @@ export class WorkspaceDragSurfaceSvg { * @internal */ setContentsAndShow( - blockCanvas: SVGElement, bubbleCanvas: SVGElement, - previousSibling: Element, width: number, height: number, scale: number) { + blockCanvas: SVGElement, + bubbleCanvas: SVGElement, + previousSibling: Element, + width: number, + height: number, + scale: number + ) { if (this.SVG.childNodes.length) { throw Error('Already dragging a block.'); } this.previousSibling = previousSibling; // Make sure the blocks and bubble canvas are scaled appropriately. blockCanvas.setAttribute( - 'transform', 'translate(0, 0) scale(' + scale + ')'); + 'transform', + 'translate(0, 0) scale(' + scale + ')' + ); bubbleCanvas.setAttribute( - 'transform', 'translate(0, 0) scale(' + scale + ')'); + 'transform', + 'translate(0, 0) scale(' + scale + ')' + ); this.SVG.setAttribute('width', String(width)); this.SVG.setAttribute('height', String(height)); this.SVG.appendChild(blockCanvas); diff --git a/core/workspace_dragger.ts b/core/workspace_dragger.ts index d3a50adc5..af92a3388 100644 --- a/core/workspace_dragger.ts +++ b/core/workspace_dragger.ts @@ -16,13 +16,10 @@ import * as common from './common.js'; import {Coordinate} from './utils/coordinate.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for a workspace dragger. It moves the workspace around when it is * being dragged by a mouse or touch. - * Note that the workspace itself manages whether or not it has a drag surface - * and how to do translations based on that. This simply passes the right - * commands based on events. + * */ export class WorkspaceDragger { private readonly horizontalScrollEnabled_: boolean; @@ -47,7 +44,6 @@ export class WorkspaceDragger { /** * Sever all links from this object. * - * @suppress {checkTypes} * @internal */ dispose() { @@ -65,7 +61,6 @@ export class WorkspaceDragger { if (common.getSelected()) { common.getSelected()!.unselect(); } - this.workspace.setupDragSurface(); } /** @@ -78,7 +73,6 @@ export class WorkspaceDragger { endDrag(currentDragDeltaXY: Coordinate) { // Make sure everything is up to date. this.drag(currentDragDeltaXY); - this.workspace.resetDragSurface(); } /** diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 7ac747c14..443249c8f 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -20,7 +20,6 @@ import './events/events_theme_change.js'; import './events/events_viewport.js'; import type {Block} from './block.js'; -import type {BlockDragSurfaceSvg} from './block_drag_surface.js'; import type {BlockSvg} from './block_svg.js'; import type {BlocklyOptions} from './blockly_options.js'; import * as browserEvents from './browser_events.js'; @@ -75,12 +74,10 @@ import {Workspace} from './workspace.js'; import {WorkspaceAudio} from './workspace_audio.js'; import {WorkspaceComment} from './workspace_comment.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; -import type {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js'; import * as Xml from './xml.js'; import {ZoomControls} from './zoom_controls.js'; import {ContextMenuOption} from './contextmenu_registry.js'; - /** Margin around the top/bottom/left/right after a zoomToFit call. */ const ZOOM_TO_FIT_MARGIN = 20; @@ -93,7 +90,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * A wrapper function called when a resize event occurs. * You can pass the result to `eventHandling.unbind`. */ - private resizeHandlerWrapper: browserEvents.Data|null = null; + private resizeHandlerWrapper: browserEvents.Data | null = null; /** * The render status of an SVG workspace. @@ -185,7 +182,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { startScrollY = 0; /** Distance from mouse to object being dragged. */ - private dragDeltaXY: Coordinate|null = null; + private dragDeltaXY: Coordinate | null = null; /** Current scale. */ scale = 1; @@ -200,61 +197,41 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { private oldLeft = 0; /** The workspace's trashcan (if any). */ - trashcan: Trashcan|null = null; + trashcan: Trashcan | null = null; /** This workspace's scrollbars, if they exist. */ - scrollbar: ScrollbarPair|null = null; + scrollbar: ScrollbarPair | null = null; /** * Fixed flyout providing blocks which may be dragged into this workspace. */ - private flyout: IFlyout|null = null; + private flyout: IFlyout | null = null; /** * Category-based toolbox providing blocks which may be dragged into this * workspace. */ - private toolbox_: IToolbox|null = null; + private toolbox_: IToolbox | null = null; /** * The current gesture in progress on this workspace, if any. * * @internal */ - currentGesture_: Gesture|null = null; - - /** This workspace's surface for dragging blocks, if it exists. */ - private readonly blockDragSurface: BlockDragSurfaceSvg|null = null; - - /** This workspace's drag surface, if it exists. */ - private readonly workspaceDragSurface: WorkspaceDragSurfaceSvg|null = null; - - /** - * Whether to move workspace to the drag surface when it is dragged. - * True if it should move, false if it should be translated directly. - */ - private readonly useWorkspaceDragSurface; - - /** - * Whether the drag surface is actively in use. When true, calls to - * translate will translate the drag surface instead of the translating the - * workspace directly. - * This is set to true in setupDragSurface and to false in resetDragSurface. - */ - private isDragSurfaceActive = false; + currentGesture_: Gesture | null = null; /** * The first parent div with 'injectionDiv' in the name, or null if not set. * Access this with getInjectionDiv. */ - private injectionDiv: Element|null = null; + private injectionDiv: Element | null = null; /** * Last known position of the page scroll. * This is used to determine whether we have recalculated screen coordinate * stuff since the page scrolled. */ - private lastRecordedPageScroll: Coordinate|null = null; + private lastRecordedPageScroll: Coordinate | null = null; /** * Developers may define this function to add custom menu options to the @@ -265,7 +242,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param e The right-click event that triggered the context menu. */ configureContextMenu: - ((menuOptions: ContextMenuOption[], e: Event) => void)|null = null; + | ((menuOptions: ContextMenuOption[], e: Event) => void) + | null = null; /** * In a flyout, the target workspace where blocks should be placed after a @@ -273,10 +251,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @internal */ - targetWorkspace: WorkspaceSvg|null = null; + targetWorkspace: WorkspaceSvg | null = null; /** Inverted screen CTM, for use in mouseToSvg. */ - private inverseScreenCTM: SVGMatrix|null = null; + private inverseScreenCTM: SVGMatrix | null = null; /** Inverted screen CTM is dirty, recalculate it. */ private inverseScreenCTMDirty = true; @@ -284,7 +262,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { /** @internal */ getMetrics: () => Metrics; /** @internal */ - setMetrics: (p1: {x?: number, y?: number}) => void; + setMetrics: (p1: {x?: number; y?: number}) => void; private readonly componentManager: ComponentManager; /** @@ -293,15 +271,17 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ private readonly highlightedBlocks: BlockSvg[] = []; private audioManager: WorkspaceAudio; - private grid: Grid|null; + private grid: Grid | null; private markerManager: MarkerManager; /** * Map from function names to callbacks, for deciding what to do when a * custom toolbox category is opened. */ - private toolboxCategoryCallbacks = - new Map toolbox.FlyoutDefinition>(); + private toolboxCategoryCallbacks = new Map< + string, + (p1: WorkspaceSvg) => toolbox.FlyoutDefinition + >(); /** * Map from function names to callbacks, for deciding what to do when a @@ -312,7 +292,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { private readonly renderer: Renderer; /** Cached parent SVG. */ - private cachedParentSvg: SVGElement|null = null; + private cachedParentSvg: SVGElement | null = null; /** True if keyboard accessibility mode is on, false otherwise. */ keyboardAccessibilityMode = false; @@ -321,8 +301,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { private topBoundedElements: IBoundedElement[] = []; /** The recorded drag targets. */ - private dragTargetAreas: Array<{component: IDragTarget, clientRect: Rect}> = - []; + private dragTargetAreas: Array<{component: IDragTarget; clientRect: Rect}> = + []; private readonly cachedParentSvgSize: Size; // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. svgGroup_!: SVGElement; @@ -332,85 +312,84 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { svgBlockCanvas_!: SVGElement; // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. svgBubbleCanvas_!: SVGElement; - zoomControls_: ZoomControls|null = null; + zoomControls_: ZoomControls | null = null; /** * @param options Dictionary of options. - * @param opt_blockDragSurface Drag surface for blocks. - * @param opt_wsDragSurface Drag surface for the workspace. */ - constructor( - options: Options, opt_blockDragSurface?: BlockDragSurfaceSvg, - opt_wsDragSurface?: WorkspaceDragSurfaceSvg) { + constructor(options: Options) { super(options); const MetricsManagerClass = registry.getClassFromOptions( - registry.Type.METRICS_MANAGER, options, true); + registry.Type.METRICS_MANAGER, + options, + true + ); /** Object in charge of calculating metrics for the workspace. */ this.metricsManager = new MetricsManagerClass!(this); /** Method to get all the metrics that have to do with a workspace. */ - this.getMetrics = options.getMetrics || - this.metricsManager.getMetrics.bind(this.metricsManager); + this.getMetrics = + options.getMetrics || + this.metricsManager.getMetrics.bind(this.metricsManager); /** Translates the workspace. */ this.setMetrics = - options.setMetrics || WorkspaceSvg.setTopLevelWorkspaceMetrics_; + options.setMetrics || WorkspaceSvg.setTopLevelWorkspaceMetrics_; this.componentManager = new ComponentManager(); this.connectionDBList = ConnectionDB.init(this.connectionChecker); - if (opt_blockDragSurface) { - this.blockDragSurface = opt_blockDragSurface; - } - - if (opt_wsDragSurface) { - this.workspaceDragSurface = opt_wsDragSurface; - } - - this.useWorkspaceDragSurface = !!this.workspaceDragSurface; - /** * Object in charge of loading, storing, and playing audio for a workspace. */ - this.audioManager = - new WorkspaceAudio((options.parentWorkspace as WorkspaceSvg)); + this.audioManager = new WorkspaceAudio( + options.parentWorkspace as WorkspaceSvg + ); /** This workspace's grid object or null. */ - this.grid = this.options.gridPattern ? - new Grid(this.options.gridPattern, options.gridOptions) : - null; + this.grid = this.options.gridPattern + ? new Grid(this.options.gridPattern, options.gridOptions) + : null; /** Manager in charge of markers and cursors. */ this.markerManager = new MarkerManager(this); if (Variables && Variables.flyoutCategory) { this.registerToolboxCategoryCallback( - Variables.CATEGORY_NAME, Variables.flyoutCategory); + Variables.CATEGORY_NAME, + Variables.flyoutCategory + ); } if (VariablesDynamic && VariablesDynamic.flyoutCategory) { this.registerToolboxCategoryCallback( - VariablesDynamic.CATEGORY_NAME, VariablesDynamic.flyoutCategory); + VariablesDynamic.CATEGORY_NAME, + VariablesDynamic.flyoutCategory + ); } if (Procedures && Procedures.flyoutCategory) { this.registerToolboxCategoryCallback( - Procedures.CATEGORY_NAME, Procedures.flyoutCategory); + Procedures.CATEGORY_NAME, + Procedures.flyoutCategory + ); this.addChangeListener(Procedures.mutatorOpenListener); } /** Object in charge of storing and updating the workspace theme. */ - this.themeManager_ = this.options.parentWorkspace ? - this.options.parentWorkspace.getThemeManager() : - new ThemeManager(this, this.options.theme || Classic); + this.themeManager_ = this.options.parentWorkspace + ? this.options.parentWorkspace.getThemeManager() + : new ThemeManager(this, this.options.theme || Classic); this.themeManager_.subscribeWorkspace(this); /** The block renderer used for rendering blocks on this workspace. */ this.renderer = blockRendering.init( - this.options.renderer || 'geras', this.getTheme(), - this.options.rendererOverrides ?? undefined); + this.options.renderer || 'geras', + this.getTheme(), + this.options.rendererOverrides ?? undefined + ); /** * The cached size of the parent svg element. @@ -487,7 +466,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * ID exists. * @internal */ - getMarker(id: string): Marker|null { + getMarker(id: string): Marker | null { if (this.markerManager) { return this.markerManager.getMarker(id); } @@ -499,7 +478,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @returns The cursor for the workspace. */ - getCursor(): Cursor|null { + getCursor(): Cursor | null { if (this.markerManager) { return this.markerManager.getCursor(); } @@ -556,9 +535,11 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { } // Update all blocks in workspace that have a style name. - this.updateBlockStyles_(this.getAllBlocks(false).filter(function(block) { - return !!block.getStyleName(); - })); + this.updateBlockStyles_( + this.getAllBlocks(false).filter(function (block) { + return !!block.getStyleName(); + }) + ); // Update current toolbox selection. this.refreshToolboxSelection(); @@ -572,7 +553,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { } const event = new (eventUtils.get(eventUtils.THEME_CHANGE))( - this.getTheme().name, this.id); + this.getTheme().name, + this.id + ); eventUtils.fire(event); } @@ -582,14 +565,11 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param blocks List of blocks to update the style on. */ private updateBlockStyles_(blocks: Block[]) { - for (let i = 0, block; block = blocks[i]; i++) { + for (let i = 0, block; (block = blocks[i]); i++) { const blockStyleName = block.getStyleName(); if (blockStyleName) { const blockSvg = block as BlockSvg; blockSvg.setStyle(blockStyleName); - if (blockSvg.mutator) { - blockSvg.mutator.updateBlockStyle(); - } } } } @@ -599,13 +579,13 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @returns The matrix to use in mouseToSvg */ - getInverseScreenCTM(): SVGMatrix|null { + getInverseScreenCTM(): SVGMatrix | null { // Defer getting the screen CTM until we actually need it, this should // avoid forced reflows from any calls to updateInverseScreenCTM. if (this.inverseScreenCTMDirty) { const ctm = this.getParentSvg().getScreenCTM(); if (ctm) { - this.inverseScreenCTM = (ctm).inverse(); + this.inverseScreenCTM = ctm.inverse(); this.inverseScreenCTMDirty = false; } } @@ -641,8 +621,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { let x = 0; let y = 0; let scale = 1; - if (dom.containsNode(this.getCanvas(), element) || - dom.containsNode(this.getBubbleCanvas(), element)) { + if ( + this.getCanvas().contains(element) || + this.getBubbleCanvas().contains(element) + ) { // Before the SVG canvas, scale the coordinates. scale = this.scale; } @@ -716,7 +698,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The SVG group for the workspace. * @internal */ - getBlockCanvas(): SVGElement|null { + getBlockCanvas(): SVGElement | null { return this.svgBlockCanvas_; } @@ -752,47 +734,71 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // flyout, the workspace will not receive mouse events. if (opt_backgroundClass) { this.svgBackground_ = dom.createSvgElement( - Svg.RECT, - {'height': '100%', 'width': '100%', 'class': opt_backgroundClass}, - this.svgGroup_); + Svg.RECT, + {'height': '100%', 'width': '100%', 'class': opt_backgroundClass}, + this.svgGroup_ + ); if (opt_backgroundClass === 'blocklyMainBackground' && this.grid) { this.svgBackground_.style.fill = - 'url(#' + this.grid.getPatternId() + ')'; + 'url(#' + this.grid.getPatternId() + ')'; } else { this.themeManager_.subscribe( - this.svgBackground_, 'workspaceBackgroundColour', 'fill'); + this.svgBackground_, + 'workspaceBackgroundColour', + 'fill' + ); } } this.svgBlockCanvas_ = dom.createSvgElement( - Svg.G, {'class': 'blocklyBlockCanvas'}, this.svgGroup_); + Svg.G, + {'class': 'blocklyBlockCanvas'}, + this.svgGroup_ + ); this.svgBubbleCanvas_ = dom.createSvgElement( - Svg.G, {'class': 'blocklyBubbleCanvas'}, this.svgGroup_); + Svg.G, + {'class': 'blocklyBubbleCanvas'}, + this.svgGroup_ + ); if (!this.isFlyout) { browserEvents.conditionalBind( - this.svgGroup_, 'pointerdown', this, this.onMouseDown_, false); + this.svgGroup_, + 'pointerdown', + this, + this.onMouseDown_, + false + ); // This no-op works around https://bugs.webkit.org/show_bug.cgi?id=226683, // which otherwise prevents zoom/scroll events from being observed in // Safari. Once that bug is fixed it should be removed. - document.body.addEventListener('wheel', function() {}); + document.body.addEventListener('wheel', function () {}); browserEvents.conditionalBind( - this.svgGroup_, 'wheel', this, this.onMouseWheel_); + this.svgGroup_, + 'wheel', + this, + this.onMouseWheel_ + ); } // Determine if there needs to be a category tree, or a simple list of // blocks. This cannot be changed later, since the UI is very different. if (this.options.hasCategories) { const ToolboxClass = registry.getClassFromOptions( - registry.Type.TOOLBOX, this.options, true); + registry.Type.TOOLBOX, + this.options, + true + ); this.toolbox_ = new ToolboxClass!(this); } if (this.grid) { this.grid.update(this.scale); } this.recordDragTargets(); - const CursorClass = - registry.getClassFromOptions(registry.Type.CURSOR, this.options); + const CursorClass = registry.getClassFromOptions( + registry.Type.CURSOR, + this.options + ); CursorClass && this.markerManager.setCursor(new CursorClass()); @@ -803,8 +809,6 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { /** * Dispose of this workspace. * Unlink from all DOM elements to prevent memory leaks. - * - * @suppress {checkTypes} */ override dispose() { // Stop rerendering. @@ -896,8 +900,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ static newTrashcan(_workspace: WorkspaceSvg): Trashcan { throw new Error( - 'The implementation of newTrashcan should be ' + - 'monkey-patched in by blockly.ts'); + 'The implementation of newTrashcan should be ' + + 'monkey-patched in by blockly.ts' + ); } /** @@ -918,8 +923,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The element containing the flyout DOM. * @internal */ - addFlyout(tagName: string|Svg|Svg): Element { - const workspaceOptions = new Options(({ + addFlyout(tagName: string | Svg | Svg): Element { + const workspaceOptions = new Options({ 'parentWorkspace': this, 'rtl': this.RTL, 'oneBasedIndex': this.options.oneBasedIndex, @@ -929,15 +934,21 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { 'move': { 'scrollbars': true, }, - } as BlocklyOptions)); + } as BlocklyOptions); workspaceOptions.toolboxPosition = this.options.toolboxPosition; if (this.horizontalLayout) { const HorizontalFlyout = registry.getClassFromOptions( - registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, this.options, true); + registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, + this.options, + true + ); this.flyout = new HorizontalFlyout!(workspaceOptions); } else { const VerticalFlyout = registry.getClassFromOptions( - registry.Type.FLYOUTS_VERTICAL_TOOLBOX, this.options, true); + registry.Type.FLYOUTS_VERTICAL_TOOLBOX, + this.options, + true + ); this.flyout = new VerticalFlyout!(workspaceOptions); } this.flyout.autoClose = false; @@ -957,7 +968,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param opt_own Whether to only return the workspace's own flyout. * @returns The flyout on this workspace. */ - getFlyout(opt_own?: boolean): IFlyout|null { + getFlyout(opt_own?: boolean): IFlyout | null { if (this.flyout || opt_own) { return this.flyout; } @@ -972,7 +983,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @returns The toolbox on this workspace. */ - getToolbox(): IToolbox|null { + getToolbox(): IToolbox | null { return this.toolbox_; } @@ -1018,10 +1029,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { } const positionables = this.componentManager.getComponents( - ComponentManager.Capability.POSITIONABLE, true); + ComponentManager.Capability.POSITIONABLE, + true + ); const metrics = this.getMetricsManager().getUiMetrics(); const savedPositions = []; - for (let i = 0, positionable; positionable = positionables[i]; i++) { + for (let i = 0, positionable; (positionable = positionables[i]); i++) { positionable.position(metrics, savedPositions); const boundingRect = positionable.getBoundingRectangle(); if (boundingRect) { @@ -1068,7 +1081,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param height The height of the parent SVG element * @internal */ - setCachedParentSvgSize(width: number|null, height: number|null) { + setCachedParentSvgSize(width: number | null, height: number | null) { const svg = this.getParentSvg(); if (width != null) { this.cachedParentSvgSize.width = width; @@ -1127,14 +1140,22 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { const scale = this.scale; const top = -this.scrollY; const left = -this.scrollX; - if (scale === this.oldScale && Math.abs(top - this.oldTop) < 1 && - Math.abs(left - this.oldLeft) < 1) { + if ( + scale === this.oldScale && + Math.abs(top - this.oldTop) < 1 && + Math.abs(left - this.oldLeft) < 1 + ) { // Ignore sub-pixel changes in top and left. Due to #4192 there are a lot // of negligible changes in viewport top/left. return; } const event = new (eventUtils.get(eventUtils.VIEWPORT_CHANGE))( - top, left, scale, this.id, this.oldScale); + top, + left, + scale, + this.id, + this.oldScale + ); this.oldScale = scale; this.oldTop = top; this.oldLeft = left; @@ -1150,18 +1171,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * the Blockly div. */ translate(x: number, y: number) { - if (this.useWorkspaceDragSurface && this.isDragSurfaceActive) { - this.workspaceDragSurface?.translateSurface(x, y); - } else { - const translation = 'translate(' + x + ',' + y + ') ' + - 'scale(' + this.scale + ')'; - this.svgBlockCanvas_.setAttribute('transform', translation); - this.svgBubbleCanvas_.setAttribute('transform', translation); - } - // Now update the block drag surface if we're using one. - if (this.blockDragSurface) { - this.blockDragSurface.translateAndScaleGroup(x, y, this.scale); - } + const translation = + 'translate(' + x + ',' + y + ') ' + 'scale(' + this.scale + ')'; + this.svgBlockCanvas_.setAttribute('transform', translation); + this.svgBubbleCanvas_.setAttribute('transform', translation); // And update the grid if we're using one. if (this.grid) { this.grid.moveTo(x, y); @@ -1170,75 +1183,6 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { this.maybeFireViewportChangeEvent(); } - /** - * Called at the end of a workspace drag to take the contents - * out of the drag surface and put them back into the workspace SVG. - * Does nothing if the workspace drag surface is not enabled. - * - * @internal - */ - resetDragSurface() { - // Don't do anything if we aren't using a drag surface. - if (!this.useWorkspaceDragSurface) { - return; - } - - this.isDragSurfaceActive = false; - - const trans = this.workspaceDragSurface!.getSurfaceTranslation(); - this.workspaceDragSurface!.clearAndHide(this.svgGroup_); - const translation = 'translate(' + trans.x + ',' + trans.y + ') ' + - 'scale(' + this.scale + ')'; - this.svgBlockCanvas_.setAttribute('transform', translation); - this.svgBubbleCanvas_.setAttribute('transform', translation); - } - - /** - * Called at the beginning of a workspace drag to move contents of - * the workspace to the drag surface. - * Does nothing if the drag surface is not enabled. - * - * @internal - */ - setupDragSurface() { - // Don't do anything if we aren't using a drag surface. - if (!this.useWorkspaceDragSurface) { - return; - } - - // This can happen if the user starts a drag, mouses up outside of the - // document where the mouseup listener is registered (e.g. outside of an - // iframe) and then moves the mouse back in the workspace. On mobile and - // ff, we get the mouseup outside the frame. On chrome and safari desktop we - // do not. - if (this.isDragSurfaceActive) { - return; - } - - this.isDragSurfaceActive = true; - - // Figure out where we want to put the canvas back. The order - // in the is important because things are layered. - const previousElement = this.svgBlockCanvas_.previousSibling as Element; - const width = parseInt(this.getParentSvg().getAttribute('width') ?? '0'); - const height = parseInt(this.getParentSvg().getAttribute('height') ?? '0'); - const coord = svgMath.getRelativeXY(this.getCanvas()); - this.workspaceDragSurface!.setContentsAndShow( - this.getCanvas(), this.getBubbleCanvas(), previousElement, width, - height, this.scale); - this.workspaceDragSurface!.translateSurface(coord.x, coord.y); - } - - /** - * Gets the drag surface blocks are moved to when a drag is started. - * - * @returns This workspace's block drag surface, if one is in use. - * @internal - */ - getBlockDragSurface(): BlockDragSurfaceSvg|null { - return this.blockDragSurface; - } - /** * Returns the horizontal offset of the workspace. * Intended for LTR/RTL compatibility in XML. @@ -1301,7 +1245,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { const blocks = this.getAllBlocks(false); // Render each block. for (let i = blocks.length - 1; i >= 0; i--) { - blocks[i].render(false); + blocks[i].queueRender(); } if (this.currentGesture_) { @@ -1324,10 +1268,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * unhighlight all others. If true or false, manually * highlight/unhighlight the specified block. */ - highlightBlock(id: string|null, opt_state?: boolean) { + highlightBlock(id: string | null, opt_state?: boolean) { if (opt_state === undefined) { // Unhighlight all blocks. - for (let i = 0, block; block = this.highlightedBlocks[i]; i++) { + for (let i = 0, block; (block = this.highlightedBlocks[i]); i++) { block.setHighlighted(false); } this.highlightedBlocks.length = 0; @@ -1354,11 +1298,14 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param state The representation of the thing to paste. * @returns The pasted thing, or null if the paste was not successful. */ - paste(state: AnyDuringMigration|Element|DocumentFragment): ICopyable|null { - if (!this.rendered || !state['type'] && !state['tagName']) { + paste( + state: AnyDuringMigration | Element | DocumentFragment + ): ICopyable | null { + if (!this.rendered || (!state['type'] && !state['tagName'])) { return null; } - if (this.currentGesture_) { // Dragging while pasting? No. + if (this.currentGesture_) { + // Dragging while pasting? No. this.currentGesture_.cancel(); } @@ -1392,8 +1339,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param jsonBlock JSON block representation. * @returns The pasted block. */ - private pasteBlock_(xmlBlock: Element|null, jsonBlock: blocks.State|null): - BlockSvg { + private pasteBlock_( + xmlBlock: Element | null, + jsonBlock: blocks.State | null + ): BlockSvg { eventUtils.disable(); let block: BlockSvg; try { @@ -1423,10 +1372,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { do { collide = false; const allBlocks = this.getAllBlocks(false); - for (let i = 0, otherBlock; otherBlock = allBlocks[i]; i++) { + for (let i = 0, otherBlock; (otherBlock = allBlocks[i]); i++) { const otherXY = otherBlock.getRelativeToSurfaceXY(); - if (Math.abs(blockX - otherXY.x) <= 1 && - Math.abs(blockY - otherXY.y) <= 1) { + if ( + Math.abs(blockX - otherXY.x) <= 1 && + Math.abs(blockY - otherXY.y) <= 1 + ) { collide = true; break; } @@ -1434,11 +1385,11 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { if (!collide) { // Check for blocks in snap range to any of its connections. const connections = block!.getConnections_(false); - for (let i = 0, connection; connection = connections[i]; i++) { - const neighbour = - (connection) - .closest( - config.snapRadius, new Coordinate(blockX, blockY)); + for (let i = 0, connection; (connection = connections[i]); i++) { + const neighbour = connection.closest( + config.snapRadius, + new Coordinate(blockX, blockY) + ); if (neighbour.connection) { collide = true; break; @@ -1454,6 +1405,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { blockY += config.snapRadius * 2; } } while (collide); + // No 'reason' provided since events are disabled. block!.moveTo(new Coordinate(blockX, blockY)); } } finally { @@ -1471,8 +1423,6 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @param xmlComment XML workspace comment element. * @returns The pasted workspace comment. - * @suppress {checkTypes} Suppress checks while workspace comments are not - * bundled in. */ private pasteWorkspaceComment_(xmlComment: Element): WorkspaceCommentSvg { eventUtils.disable(); @@ -1550,8 +1500,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The newly created variable. */ override createVariable( - name: string, opt_type?: string|null, - opt_id?: string|null): VariableModel { + name: string, + opt_type?: string | null, + opt_id?: string | null + ): VariableModel { const newVar = super.createVariable(name, opt_type, opt_id); this.refreshToolboxSelection(); return newVar; @@ -1560,10 +1512,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { /** Make a list of all the delete areas for this workspace. */ recordDragTargets() { const dragTargets = this.componentManager.getComponents( - ComponentManager.Capability.DRAG_TARGET, true); + ComponentManager.Capability.DRAG_TARGET, + true + ); this.dragTargetAreas = []; - for (let i = 0, targetArea; targetArea = dragTargets[i]; i++) { + for (let i = 0, targetArea; (targetArea = dragTargets[i]); i++) { const rect = targetArea.getClientRect(); if (rect) { this.dragTargetAreas.push({ @@ -1586,8 +1540,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ override newBlock(prototypeName: string, opt_id?: string): BlockSvg { throw new Error( - 'The implementation of newBlock should be ' + - 'monkey-patched in by blockly.ts'); + 'The implementation of newBlock should be ' + + 'monkey-patched in by blockly.ts' + ); } /* eslint-enable */ @@ -1598,8 +1553,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns Null if not over a drag target, or the drag target the event is * over. */ - getDragTarget(e: PointerEvent): IDragTarget|null { - for (let i = 0, targetArea; targetArea = this.dragTargetAreas[i]; i++) { + getDragTarget(e: PointerEvent): IDragTarget | null { + for (let i = 0, targetArea; (targetArea = this.dragTargetAreas[i]); i++) { if (targetArea.clientRect.contains(e.clientX, e.clientY)) { return targetArea.component; } @@ -1628,7 +1583,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { startDrag(e: PointerEvent, xy: Coordinate) { // Record the starting offset between the bubble's location and the mouse. const point = browserEvents.mouseToSvg( - e, this.getParentSvg(), this.getInverseScreenCTM()); + e, + this.getParentSvg(), + this.getInverseScreenCTM() + ); // Fix scale of mouse event. point.x /= this.scale; point.y /= this.scale; @@ -1643,11 +1601,14 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ moveDrag(e: PointerEvent): Coordinate { const point = browserEvents.mouseToSvg( - e, this.getParentSvg(), this.getInverseScreenCTM()); + e, + this.getParentSvg(), + this.getInverseScreenCTM() + ); // Fix scale of mouse event. point.x /= this.scale; point.y /= this.scale; - return Coordinate.sum((this.dragDeltaXY!), point); + return Coordinate.sum(this.dragDeltaXY!, point); } /** @@ -1680,11 +1641,13 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns True if the workspace is movable, false otherwise. */ isMovable(): boolean { - return this.options.moveOptions && !!this.options.moveOptions.scrollbars || - this.options.moveOptions && this.options.moveOptions.wheel || - this.options.moveOptions && this.options.moveOptions.drag || - this.options.zoomOptions && this.options.zoomOptions.wheel || - this.options.zoomOptions && this.options.zoomOptions.pinch; + return ( + (this.options.moveOptions && !!this.options.moveOptions.scrollbars) || + (this.options.moveOptions && this.options.moveOptions.wheel) || + (this.options.moveOptions && this.options.moveOptions.drag) || + (this.options.zoomOptions && this.options.zoomOptions.wheel) || + (this.options.zoomOptions && this.options.zoomOptions.pinch) + ); } /** @@ -1694,9 +1657,11 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ isMovableHorizontally(): boolean { const hasScrollbars = !!this.scrollbar; - return this.isMovable() && - (!hasScrollbars || - hasScrollbars && this.scrollbar!.canScrollHorizontally()); + return ( + this.isMovable() && + (!hasScrollbars || + (hasScrollbars && this.scrollbar!.canScrollHorizontally())) + ); } /** @@ -1706,9 +1671,11 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { */ isMovableVertically(): boolean { const hasScrollbars = !!this.scrollbar; - return this.isMovable() && - (!hasScrollbars || - hasScrollbars && this.scrollbar!.canScrollVertically()); + return ( + this.isMovable() && + (!hasScrollbars || + (hasScrollbars && this.scrollbar!.canScrollVertically())) + ); } /** @@ -1724,9 +1691,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { return; } const canWheelZoom = - this.options.zoomOptions && this.options.zoomOptions.wheel; + this.options.zoomOptions && this.options.zoomOptions.wheel; const canWheelMove = - this.options.moveOptions && this.options.moveOptions.wheel; + this.options.moveOptions && this.options.moveOptions.wheel; if (!canWheelZoom && !canWheelMove) { return; } @@ -1747,7 +1714,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { const PIXELS_PER_ZOOM_STEP = 50; const delta = -scrollDelta.y / PIXELS_PER_ZOOM_STEP; const position = browserEvents.mouseToSvg( - e, this.getParentSvg(), this.getInverseScreenCTM()); + e, + this.getParentSvg(), + this.getInverseScreenCTM() + ); this.zoom(position.x, position.y, delta); } else { // Scroll. @@ -1759,7 +1729,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // This is needed as for some browser/system combinations which do not // set deltaX. x = this.scrollX - scrollDelta.y; - y = this.scrollY; // Don't scroll vertically. + y = this.scrollY; // Don't scroll vertically. } this.scroll(x, y); } @@ -1786,8 +1756,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // Start at 1 since the 0th block was used for initialization. for (let i = 1; i < topElements.length; i++) { const topElement = topElements[i]; - if ((topElement as any).isInsertionMarker && - (topElement as any).isInsertionMarker()) { + if ( + (topElement as any).isInsertionMarker && + (topElement as any).isInsertionMarker() + ) { continue; } const blockBoundary = topElement.getBoundingRectangle(); @@ -1813,16 +1785,17 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { eventUtils.setGroup(true); const topBlocks = this.getTopBlocks(true); let cursorY = 0; - for (let i = 0, block; block = topBlocks[i]; i++) { + for (let i = 0, block; (block = topBlocks[i]); i++) { if (!block.isMovable()) { continue; } const xy = block.getRelativeToSurfaceXY(); - block.moveBy(-xy.x, cursorY - xy.y); + block.moveBy(-xy.x, cursorY - xy.y, ['cleanup']); block.snapToGrid(); - cursorY = block.getRelativeToSurfaceXY().y + - block.getHeightWidth().height + - this.renderer.getConstants().MIN_BLOCK_HEIGHT; + cursorY = + block.getRelativeToSurfaceXY().y + + block.getHeightWidth().height + + this.renderer.getConstants().MIN_BLOCK_HEIGHT; } eventUtils.setGroup(false); this.setResizesEnabled(true); @@ -1839,7 +1812,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { return; } const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions( - ContextMenuRegistry.ScopeType.WORKSPACE, {workspace: this}); + ContextMenuRegistry.ScopeType.WORKSPACE, + {workspace: this} + ); // Allow the developer to add or modify menuOptions. if (this.configureContextMenu) { @@ -1855,28 +1830,28 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param toolboxDef DOM tree of toolbox contents, string of toolbox contents, * or JSON representing toolbox definition. */ - updateToolbox(toolboxDef: toolbox.ToolboxDefinition|null) { + updateToolbox(toolboxDef: toolbox.ToolboxDefinition | null) { const parsedToolboxDef = toolbox.convertToolboxDefToJson(toolboxDef); if (!parsedToolboxDef) { if (this.options.languageTree) { - throw Error('Can\'t nullify an existing toolbox.'); + throw Error("Can't nullify an existing toolbox."); } - return; // No change (null to null). + return; // No change (null to null). } if (!this.options.languageTree) { - throw Error('Existing toolbox is null. Can\'t create new toolbox.'); + throw Error("Existing toolbox is null. Can't create new toolbox."); } if (toolbox.hasCategories(parsedToolboxDef)) { if (!this.toolbox_) { - throw Error('Existing toolbox has no categories. Can\'t change mode.'); + throw Error("Existing toolbox has no categories. Can't change mode."); } this.options.languageTree = parsedToolboxDef; this.toolbox_.render(parsedToolboxDef); } else { if (!this.flyout) { - throw Error('Existing toolbox has categories. Can\'t change mode.'); + throw Error("Existing toolbox has categories. Can't change mode."); } this.options.languageTree = parsedToolboxDef; this.flyout.show(parsedToolboxDef); @@ -1915,7 +1890,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { let scaleChange = Math.pow(speed, amount); const newScale = this.scale * scaleChange; if (this.scale === newScale) { - return; // No change in zoom. + return; // No change in zoom. } // Clamp scale within valid range. @@ -1929,7 +1904,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // canvas' space, so that they are in workspace units relative to the top // left of the visible portion of the workspace. let matrix = this.getCanvas().getCTM(); - let center = (this.getParentSvg()).createSVGPoint(); + let center = this.getParentSvg().createSVGPoint(); center.x = x; center.y = y; center = center.matrixTransform(matrix!.inverse()); @@ -1939,8 +1914,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // Find the new scrollX/scrollY so that the center remains in the same // position (relative to the center) after we zoom. // newScale and matrix.a should be identical (within a rounding error). - matrix = matrix!.translate(x * (1 - scaleChange), y * (1 - scaleChange)) - .scale(scaleChange); + matrix = matrix! + .translate(x * (1 - scaleChange), y * (1 - scaleChange)) + .scale(scaleChange); // scrollX and scrollY are in pixels. // The scrollX and scrollY still need to have absoluteLeft and absoluteTop // subtracted from them, but we'll leave that for setScale so that they're @@ -1978,8 +1954,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { zoomToFit() { if (!this.isMovable()) { console.warn( - 'Tried to move a non-movable workspace. This could result' + - ' in blocks becoming inaccessible.'); + 'Tried to move a non-movable workspace. This could result' + + ' in blocks becoming inaccessible.' + ); return; } @@ -1991,7 +1968,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { let blocksWidth = blocksBox.right - blocksBox.left + doubleMargin; let blocksHeight = blocksBox.bottom - blocksBox.top + doubleMargin; if (!blocksWidth) { - return; // Prevents zooming to infinity. + return; // Prevents zooming to infinity. } if (this.flyout) { // We have to add the flyout size to both the workspace size and the @@ -2047,8 +2024,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { scrollCenter() { if (!this.isMovable()) { console.warn( - 'Tried to move a non-movable workspace. This could result' + - ' in blocks becoming inaccessible.'); + 'Tried to move a non-movable workspace. This could result' + + ' in blocks becoming inaccessible.' + ); return; } @@ -2070,11 +2048,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param id ID of block center on. * @param blockOnly True to center only on the block itself, not its stack. */ - centerOnBlock(id: string|null, blockOnly?: boolean) { + centerOnBlock(id: string | null, blockOnly?: boolean) { if (!this.isMovable()) { console.warn( - 'Tried to move a non-movable workspace. This could result' + - ' in blocks becoming inaccessible.'); + 'Tried to move a non-movable workspace. This could result' + + ' in blocks becoming inaccessible.' + ); return; } @@ -2086,15 +2065,16 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // XY is in workspace coordinates. const xy = block.getRelativeToSurfaceXY(); // Height/width is in workspace units. - const heightWidth = blockOnly ? {height: block.height, width: block.width} : - block.getHeightWidth(); + const heightWidth = blockOnly + ? {height: block.height, width: block.width} + : block.getHeightWidth(); // Find the enter of the block in workspace units. const blockCenterY = xy.y + heightWidth.height / 2; // In RTL the block's position is the top right of the block, not top left. const multiplier = this.RTL ? -1 : 1; - const blockCenterX = xy.x + multiplier * heightWidth.width / 2; + const blockCenterX = xy.x + (multiplier * heightWidth.width) / 2; // Workspace scale, used to convert from workspace coordinates to pixels. const scale = this.scale; @@ -2128,12 +2108,15 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param newScale Zoom factor. Units: (pixels / workspaceUnit). */ setScale(newScale: number) { - if (this.options.zoomOptions.maxScale && - newScale > this.options.zoomOptions.maxScale) { + if ( + this.options.zoomOptions.maxScale && + newScale > this.options.zoomOptions.maxScale + ) { newScale = this.options.zoomOptions.maxScale; } else if ( - this.options.zoomOptions.minScale && - newScale < this.options.zoomOptions.minScale) { + this.options.zoomOptions.minScale && + newScale < this.options.zoomOptions.minScale + ) { newScale = this.options.zoomOptions.minScale; } this.scale = newScale; @@ -2194,8 +2177,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @internal */ scroll(x: number, y: number) { - this.hideChaff(/* opt_onlyClosePopups= */ - true); + this.hideChaff(/* opt_onlyClosePopups= */ true); // Keep scrolling within the bounds of the content. const metrics = this.getMetrics(); @@ -2203,11 +2185,15 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // to workspace coordinates so we have to inverse them. x = Math.min(x, -metrics.scrollLeft); y = Math.min(y, -metrics.scrollTop); - const maxXDisplacement = - Math.max(0, metrics.scrollWidth - metrics.viewWidth); + const maxXDisplacement = Math.max( + 0, + metrics.scrollWidth - metrics.viewWidth + ); const maxXScroll = metrics.scrollLeft + maxXDisplacement; - const maxYDisplacement = - Math.max(0, metrics.scrollHeight - metrics.viewHeight); + const maxYDisplacement = Math.max( + 0, + metrics.scrollHeight - metrics.viewHeight + ); const maxYScroll = metrics.scrollTop + maxYDisplacement; x = Math.max(x, -maxXScroll); y = Math.max(y, -maxYScroll); @@ -2222,7 +2208,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { // the content's top-left to the view's top-left, matching the // directionality of the scrollbars. this.scrollbar.set( - -(x + metrics.scrollLeft), -(y + metrics.scrollTop), false); + -(x + metrics.scrollLeft), + -(y + metrics.scrollTop), + false + ); } // We have to shift the translation so that when the canvas is at 0, 0 the // workspace origin is not underneath the toolbox. @@ -2237,7 +2226,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param id ID of block to find. * @returns The sought after block, or null if not found. */ - override getBlockById(id: string): BlockSvg|null { + override getBlockById(id: string): BlockSvg | null { return super.getBlockById(id) as BlockSvg; } @@ -2303,6 +2292,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { super.removeTopComment(comment); } + override getRootWorkspace(): WorkspaceSvg | null { + return super.getRootWorkspace() as WorkspaceSvg | null; + } + /** * Adds a bounded element to the list of top bounded elements. * @@ -2327,7 +2320,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The top-level bounded elements. */ getTopBoundedElements(): IBoundedElement[] { - return (new Array()).concat(this.topBoundedElements); + return new Array().concat(this.topBoundedElements); } /** @@ -2384,7 +2377,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The function corresponding to the given key for this workspace; * null if no callback is registered. */ - getButtonCallback(key: string): ((p1: FlyoutButton) => void)|null { + getButtonCallback(key: string): ((p1: FlyoutButton) => void) | null { return this.flyoutButtonCallbacks.get(key) ?? null; } @@ -2406,7 +2399,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @param func The function to call when the given toolbox category is opened. */ registerToolboxCategoryCallback( - key: string, func: (p1: WorkspaceSvg) => toolbox.FlyoutDefinition) { + key: string, + func: (p1: WorkspaceSvg) => toolbox.FlyoutDefinition + ) { if (typeof func !== 'function') { throw TypeError('Toolbox category callbacks must be functions.'); } @@ -2421,8 +2416,9 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The function corresponding to the given key for this workspace, or * null if no function is registered. */ - getToolboxCategoryCallback(key: string): - ((p1: WorkspaceSvg) => toolbox.FlyoutDefinition)|null { + getToolboxCategoryCallback( + key: string + ): ((p1: WorkspaceSvg) => toolbox.FlyoutDefinition) | null { return this.toolboxCategoryCallbacks.get(key) || null; } @@ -2444,7 +2440,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * valid gesture exists. * @internal */ - getGesture(e: PointerEvent): Gesture|null { + getGesture(e: PointerEvent): Gesture | null { const isStart = e.type === 'pointerdown'; const gesture = this.currentGesture_; @@ -2504,25 +2500,39 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * @returns The grid object for this workspace. * @internal */ - getGrid(): Grid|null { + getGrid(): Grid | null { return this.grid; } /** * Close tooltips, context menus, dropdown selections, etc. * - * @param opt_onlyClosePopups Whether only popups should be closed. + * @param onlyClosePopups Whether only popups should be closed. Defaults to + * false. */ - hideChaff(opt_onlyClosePopups?: boolean) { + hideChaff(onlyClosePopups = false) { Tooltip.hide(); WidgetDiv.hide(); dropDownDiv.hideWithoutAnimation(); - const onlyClosePopups = !!opt_onlyClosePopups; + this.hideComponents(onlyClosePopups); + } + + /** + * Hide any autohideable components (like flyout, trashcan, and any + * user-registered components). + * + * @param onlyClosePopups Whether only popups should be closed. Defaults to + * false. + */ + hideComponents(onlyClosePopups = false) { const autoHideables = this.getComponentManager().getComponents( - ComponentManager.Capability.AUTOHIDEABLE, true); - autoHideables.forEach( - (autoHideable) => autoHideable.autoHide(onlyClosePopups)); + ComponentManager.Capability.AUTOHIDEABLE, + true + ); + autoHideables.forEach((autoHideable) => + autoHideable.autoHide(onlyClosePopups) + ); } /** @@ -2532,18 +2542,22 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * and 1 specifying the degree of scrolling. */ private static setTopLevelWorkspaceMetrics_( - this: WorkspaceSvg, xyRatio: {x?: number, y?: number}) { + this: WorkspaceSvg, + xyRatio: {x?: number; y?: number} + ) { const metrics = this.getMetrics(); if (typeof xyRatio.x === 'number') { - this.scrollX = - -(metrics.scrollLeft + - (metrics.scrollWidth - metrics.viewWidth) * xyRatio.x); + this.scrollX = -( + metrics.scrollLeft + + (metrics.scrollWidth - metrics.viewWidth) * xyRatio.x + ); } if (typeof xyRatio.y === 'number') { - this.scrollY = - -(metrics.scrollTop + - (metrics.scrollHeight - metrics.viewHeight) * xyRatio.y); + this.scrollY = -( + metrics.scrollTop + + (metrics.scrollHeight - metrics.viewHeight) * xyRatio.y + ); } // We have to shift the translation so that when the canvas is at 0, 0 the // workspace origin is not underneath the toolbox. diff --git a/core/xml.ts b/core/xml.ts index ffc2f2bfc..0367e550a 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -10,10 +10,10 @@ goog.declareModuleId('Blockly.Xml'); import type {Block} from './block.js'; import type {BlockSvg} from './block_svg.js'; import type {Connection} from './connection.js'; -import * as deprecation from './utils/deprecation.js'; import * as eventUtils from './events/utils.js'; import type {Field} from './field.js'; -import {inputTypes} from './input_types.js'; +import {IconType} from './icons/icon_types.js'; +import {inputTypes} from './inputs/input_types.js'; import * as dom from './utils/dom.js'; import {Size} from './utils/size.js'; import * as utilsXml from './utils/xml.js'; @@ -24,7 +24,6 @@ import {WorkspaceComment} from './workspace_comment.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Encode a block tree as XML. * @@ -33,10 +32,13 @@ import type {WorkspaceSvg} from './workspace_svg.js'; * @returns XML DOM element. */ export function workspaceToDom( - workspace: Workspace, opt_noId?: boolean): Element { + workspace: Workspace, + opt_noId?: boolean +): Element { const treeXml = utilsXml.createElement('xml'); - const variablesElement = - variablesToDom(Variables.allUsedVarModels(workspace)); + const variablesElement = variablesToDom( + Variables.allUsedVarModels(workspace) + ); if (variablesElement.hasChildNodes()) { treeXml.appendChild(variablesElement); } @@ -82,9 +84,12 @@ export function variablesToDom(variableList: VariableModel[]): Element { * @returns Tree of XML elements or an empty document fragment if the block was * an insertion marker. */ -export function blockToDomWithXY(block: Block, opt_noId?: boolean): Element| - DocumentFragment { - if (block.isInsertionMarker()) { // Skip over insertion markers. +export function blockToDomWithXY( + block: Block, + opt_noId?: boolean +): Element | DocumentFragment { + if (block.isInsertionMarker()) { + // Skip over insertion markers. block = block.getChildren(false)[0]; if (!block) { // Disappears when appended. @@ -92,7 +97,7 @@ export function blockToDomWithXY(block: Block, opt_noId?: boolean): Element| } } - let width = 0; // Not used in LTR. + let width = 0; // Not used in LTR. if (block.workspace.RTL) { width = block.workspace.getWidth(); } @@ -101,7 +106,9 @@ export function blockToDomWithXY(block: Block, opt_noId?: boolean): Element| if (isElement(element)) { const xy = block.getRelativeToSurfaceXY(); element.setAttribute( - 'x', String(Math.round(block.workspace.RTL ? width - xy.x : xy.x))); + 'x', + String(Math.round(block.workspace.RTL ? width - xy.x : xy.x)) + ); element.setAttribute('y', String(Math.round(xy.y))); } return element; @@ -113,7 +120,7 @@ export function blockToDomWithXY(block: Block, opt_noId?: boolean): Element| * @param field The field to encode. * @returns XML element, or null if the field did not need to be serialized. */ -function fieldToDom(field: Field): Element|null { +function fieldToDom(field: Field): Element | null { if (field.isSerializable()) { const container = utilsXml.createElement('field'); container.setAttribute('name', field.name || ''); @@ -150,8 +157,10 @@ function allFieldsToDom(block: Block, element: Element) { * @returns Tree of XML elements or an empty document fragment if the block was * an insertion marker. */ -export function blockToDom(block: Block, opt_noId?: boolean): Element| - DocumentFragment { +export function blockToDom( + block: Block, + opt_noId?: boolean +): Element | DocumentFragment { // Skip over insertion markers. if (block.isInsertionMarker()) { const child = block.getChildren(false)[0]; @@ -180,8 +189,9 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element| const commentText = block.getCommentText(); if (commentText) { - const size = block.commentModel.size; - const pinned = block.commentModel.pinned; + const comment = block.getIcon(IconType.COMMENT)!; + const size = comment.getBubbleSize(); + const pinned = comment.bubbleIsVisible(); const commentElement = utilsXml.createElement('comment'); commentElement.appendChild(utilsXml.createTextNode(commentText)); @@ -228,8 +238,10 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element| element.appendChild(container!); } } - if (block.inputsInline !== undefined && - block.inputsInline !== block.inputsInlineDefault) { + if ( + block.inputsInline !== undefined && + block.inputsInline !== block.inputsInlineDefault + ) { element.setAttribute('inline', String(block.inputsInline)); } if (block.isCollapsed()) { @@ -259,7 +271,7 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element| } } const nextShadow = - block.nextConnection && block.nextConnection.getShadowDom(); + block.nextConnection && block.nextConnection.getShadowDom(); if (nextShadow && (!nextBlock || !nextBlock.isShadow())) { container!.appendChild(cloneShadow(nextShadow, opt_noId)); } @@ -277,7 +289,7 @@ export function blockToDom(block: Block, opt_noId?: boolean): Element| function cloneShadow(shadow: Element, opt_noId?: boolean): Element { shadow = shadow.cloneNode(true) as Element; // Walk the tree looking for whitespace. Don't prune whitespace in a tag. - let node: Node|null = shadow; + let node: Node | null = shadow; let textNode; while (node) { if (opt_noId && node.nodeName === 'shadow') { @@ -291,9 +303,11 @@ function cloneShadow(shadow: Element, opt_noId?: boolean): Element { while (node && !node.nextSibling) { textNode = node; node = node.parentNode; - if (textNode.nodeType === dom.NodeType.TEXT_NODE && - (textNode as Text).data.trim() === '' && - node?.firstChild !== textNode) { + if ( + textNode.nodeType === dom.NodeType.TEXT_NODE && + (textNode as Text).data.trim() === '' && + node?.firstChild !== textNode + ) { // Prune whitespace after a tag. dom.removeNode(textNode); } @@ -301,8 +315,10 @@ function cloneShadow(shadow: Element, opt_noId?: boolean): Element { if (node) { textNode = node; node = node.nextSibling; - if (textNode.nodeType === dom.NodeType.TEXT_NODE && - (textNode as Text).data.trim() === '') { + if ( + textNode.nodeType === dom.NodeType.TEXT_NODE && + (textNode as Text).data.trim() === '' + ) { // Prune whitespace before a tag. dom.removeNode(textNode); } @@ -359,22 +375,6 @@ export function domToPrettyText(dom: Node): string { return text.replace(/^\n/, ''); } -/** - * Converts an XML string into a DOM structure. - * - * @param text An XML string. - * @returns A DOM object representing the singular child of the document - * element. - * @throws if the text doesn't parse. - * @deprecated Moved to core/utils/xml.js. - */ -export function textToDom(text: string): Element { - deprecation.warn( - 'Blockly.Xml.textToDom', 'version 9', 'version 10', - 'Use Blockly.utils.xml.textToDom instead'); - return utilsXml.textToDom(text); -} - /** * Clear the given workspace then decode an XML DOM and * create blocks on the workspace. @@ -384,7 +384,9 @@ export function textToDom(text: string): Element { * @returns An array containing new block IDs. */ export function clearWorkspaceAndLoadFromXml( - xml: Element, workspace: WorkspaceSvg): string[] { + xml: Element, + workspace: WorkspaceSvg +): string[] { workspace.setResizesEnabled(false); workspace.clear(); const blockIds = domToWorkspace(xml, workspace); @@ -398,15 +400,13 @@ export function clearWorkspaceAndLoadFromXml( * @param xml XML DOM. * @param workspace The workspace. * @returns An array containing new block IDs. - * @suppress {strictModuleDepCheck} Suppress module check while workspace - * comments are not bundled in. */ export function domToWorkspace(xml: Element, workspace: Workspace): string[] { - let width = 0; // Not used in LTR. + let width = 0; // Not used in LTR. if (workspace.RTL) { width = workspace.getWidth(); } - const newBlockIds = []; // A list of block IDs added by this call. + const newBlockIds = []; // A list of block IDs added by this call. dom.startTextWidthCache(); const existingGroup = eventUtils.getGroup(); if (!existingGroup) { @@ -420,11 +420,13 @@ export function domToWorkspace(xml: Element, workspace: Workspace): string[] { } let variablesFirst = true; try { - for (let i = 0, xmlChild; xmlChild = xml.childNodes[i]; i++) { + for (let i = 0, xmlChild; (xmlChild = xml.childNodes[i]); i++) { const name = xmlChild.nodeName.toLowerCase(); const xmlChildElement = xmlChild as Element; - if (name === 'block' || - name === 'shadow' && !eventUtils.getRecordUndo()) { + if ( + name === 'block' || + (name === 'shadow' && !eventUtils.getRecordUndo()) + ) { // Allow top-level shadow blocks if recordUndo is disabled since // that means an undo is in progress. Such a block is expected // to be moved to a nested destination in the next operation. @@ -433,7 +435,9 @@ export function domToWorkspace(xml: Element, workspace: Workspace): string[] { const blockX = parseInt(xmlChildElement.getAttribute('x') ?? '10', 10); const blockY = parseInt(xmlChildElement.getAttribute('y') ?? '10', 10); if (!isNaN(blockX) && !isNaN(blockY)) { - block.moveBy(workspace.RTL ? width - blockX : blockX, blockY); + block.moveBy(workspace.RTL ? width - blockX : blockX, blockY, [ + 'create', + ]); } variablesFirst = false; } else if (name === 'shadow') { @@ -441,7 +445,10 @@ export function domToWorkspace(xml: Element, workspace: Workspace): string[] { } else if (name === 'comment') { if (workspace.rendered) { WorkspaceCommentSvg.fromXmlRendered( - xmlChildElement, workspace as WorkspaceSvg, width); + xmlChildElement, + workspace as WorkspaceSvg, + width + ); } else { WorkspaceComment.fromXml(xmlChildElement, workspace); } @@ -450,9 +457,10 @@ export function domToWorkspace(xml: Element, workspace: Workspace): string[] { domToVariables(xmlChildElement, workspace); } else { throw Error( - '\'variables\' tag must exist once before block and ' + + "'variables' tag must exist once before block and " + 'shadow tag elements in the workspace XML, but it was found in ' + - 'another location.'); + 'another location.' + ); } variablesFirst = false; } @@ -478,7 +486,9 @@ export function domToWorkspace(xml: Element, workspace: Workspace): string[] { * @returns An array containing new block IDs. */ export function appendDomToWorkspace( - xml: Element, workspace: WorkspaceSvg): string[] { + xml: Element, + workspace: WorkspaceSvg +): string[] { // First check if we have a WorkspaceSvg, otherwise the blocks have no shape // and the position does not matter. // Assume it is rendered so we can check. @@ -489,26 +499,30 @@ export function appendDomToWorkspace( const bbox = (workspace as WorkspaceSvg).getBlocksBoundingBox(); // Load the new blocks into the workspace and get the IDs of the new blocks. const newBlockIds = domToWorkspace(xml, workspace); - if (bbox && bbox.top !== bbox.bottom) { // Check if any previous block. - let offsetY = 0; // Offset to add to y of the new block. + if (bbox && bbox.top !== bbox.bottom) { + // Check if any previous block. + let offsetY = 0; // Offset to add to y of the new block. let offsetX = 0; - const farY = bbox.bottom; // Bottom position. - const topX = workspace.RTL ? bbox.right : bbox.left; // X of bounding box. + const farY = bbox.bottom; // Bottom position. + const topX = workspace.RTL ? bbox.right : bbox.left; // X of bounding box. // Check position of the new blocks. - let newLeftX = Infinity; // X of top left corner. - let newRightX = -Infinity; // X of top right corner. - let newY = Infinity; // Y of top corner. + let newLeftX = Infinity; // X of top left corner. + let newRightX = -Infinity; // X of top right corner. + let newY = Infinity; // Y of top corner. const ySeparation = 10; for (let i = 0; i < newBlockIds.length; i++) { - const blockXY = - workspace.getBlockById(newBlockIds[i])!.getRelativeToSurfaceXY(); + const blockXY = workspace + .getBlockById(newBlockIds[i])! + .getRelativeToSurfaceXY(); if (blockXY.y < newY) { newY = blockXY.y; } - if (blockXY.x < newLeftX) { // if we left align also on x + if (blockXY.x < newLeftX) { + // if we left align also on x newLeftX = blockXY.x; } - if (blockXY.x > newRightX) { // if we right align also on x + if (blockXY.x > newRightX) { + // if we right align also on x newRightX = blockXY.x; } } @@ -516,7 +530,7 @@ export function appendDomToWorkspace( offsetX = workspace.RTL ? topX - newRightX : topX - newLeftX; for (let i = 0; i < newBlockIds.length; i++) { const block = workspace.getBlockById(newBlockIds[i]); - block!.moveBy(offsetX, offsetY); + block!.moveBy(offsetX, offsetY, ['create']); } } return newBlockIds; @@ -551,7 +565,7 @@ export function domToBlock(xmlBlock: Element, workspace: Workspace): Block { } // Populating the connection database may be deferred until after the // blocks have rendered. - setTimeout(function() { + setTimeout(function () { if (!topBlockSvg.disposed) { topBlockSvg.setConnectionTracking(true); } @@ -570,13 +584,16 @@ export function domToBlock(xmlBlock: Element, workspace: Workspace): Block { eventUtils.enable(); } if (eventUtils.isEnabled()) { - const newVariables = - Variables.getAddedVariables(workspace, variablesBeforeCreation); + const newVariables = Variables.getAddedVariables( + workspace, + variablesBeforeCreation + ); // Fire a VarCreate event for each (if any) new variable created. for (let i = 0; i < newVariables.length; i++) { const thisVariable = newVariables[i]; eventUtils.fire( - new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable)); + new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable) + ); } // Block events come after var events, in case they refer to newly created // variables. @@ -705,17 +722,14 @@ function applyCommentTagNodes(xmlChildren: Element[], block: Block) { const height = parseInt(xmlChild.getAttribute('h') ?? '50', 10); block.setCommentText(text); - block.commentModel.pinned = pinned; + const comment = block.getIcon(IconType.COMMENT)!; if (!isNaN(width) && !isNaN(height)) { - block.commentModel.size = new Size(width, height); - } - - if (pinned && (block as BlockSvg).getCommentIcon && !block.isInFlyout) { - const blockSvg = block as BlockSvg; - setTimeout(function() { - blockSvg.getCommentIcon()!.setVisible(true); - }, 1); + comment.setBubbleSize(new Size(width, height)); } + // Set the pinned state of the bubble. + comment.setBubbleVisible(pinned); + // Actually show the bubble after the block has been rendered. + setTimeout(() => comment.setBubbleVisible(pinned), 1); } } @@ -756,10 +770,12 @@ function applyFieldTagNodes(xmlChildren: Element[], block: Block) { * @param xmlNode The XML node to extract child block info from. * @returns Any found child block. */ -function findChildBlocks(xmlNode: Element): - {childBlockElement: Element|null, childShadowElement: Element|null} { - let childBlockElement: Element|null = null; - let childShadowElement: Element|null = null; +function findChildBlocks(xmlNode: Element): { + childBlockElement: Element | null; + childShadowElement: Element | null; +} { + let childBlockElement: Element | null = null; + let childShadowElement: Element | null = null; for (let i = 0; i < xmlNode.childNodes.length; i++) { const xmlChild = xmlNode.childNodes[i]; if (isElement(xmlChild)) { @@ -781,16 +797,19 @@ function findChildBlocks(xmlNode: Element): * @param prototypeName The prototype name of the block. */ function applyInputTagNodes( - xmlChildren: Element[], workspace: Workspace, block: Block, - prototypeName: string) { + xmlChildren: Element[], + workspace: Workspace, + block: Block, + prototypeName: string +) { for (let i = 0; i < xmlChildren.length; i++) { const xmlChild = xmlChildren[i]; const nodeName = xmlChild.getAttribute('name'); const input = nodeName ? block.getInput(nodeName) : null; if (!input) { console.warn( - 'Ignoring non-existent input ' + nodeName + ' in block ' + - prototypeName); + 'Ignoring non-existent input ' + nodeName + ' in block ' + prototypeName + ); break; } const childBlockInfo = findChildBlocks(xmlChild); @@ -799,7 +818,11 @@ function applyInputTagNodes( throw TypeError('Input connection does not exist.'); } domToBlockHeadless( - childBlockInfo.childBlockElement, workspace, input.connection, false); + childBlockInfo.childBlockElement, + workspace, + input.connection, + false + ); } // Set shadow after so we don't create a shadow we delete immediately. if (childBlockInfo.childShadowElement) { @@ -816,7 +839,10 @@ function applyInputTagNodes( * @param block The block to apply the child nodes on. */ function applyNextTagNodes( - xmlChildren: Element[], workspace: Workspace, block: Block) { + xmlChildren: Element[], + workspace: Workspace, + block: Block +) { for (let i = 0; i < xmlChildren.length; i++) { const xmlChild = xmlChildren[i]; const childBlockInfo = findChildBlocks(xmlChild); @@ -830,8 +856,11 @@ function applyNextTagNodes( } // Create child block. domToBlockHeadless( - childBlockInfo.childBlockElement, workspace, block.nextConnection, - true); + childBlockInfo.childBlockElement, + workspace, + block.nextConnection, + true + ); } // Set shadow after so we don't create a shadow we delete immediately. if (childBlockInfo.childShadowElement && block.nextConnection) { @@ -853,8 +882,11 @@ function applyNextTagNodes( * @returns The root block created. */ function domToBlockHeadless( - xmlBlock: Element, workspace: Workspace, parentConnection?: Connection, - connectedToParentNext?: boolean): Block { + xmlBlock: Element, + workspace: Workspace, + parentConnection?: Connection, + connectedToParentNext?: boolean +): Block { let block = null; const prototypeName = xmlBlock.getAttribute('type'); if (!prototypeName) { @@ -866,8 +898,10 @@ function domToBlockHeadless( // Preprocess childNodes so tags can be processed in a consistent order. const xmlChildNameMap = mapSupportedXmlTags(xmlBlock); - const shouldCallInitSvg = - applyMutationTagNodes(xmlChildNameMap.mutation, block); + const shouldCallInitSvg = applyMutationTagNodes( + xmlChildNameMap.mutation, + block + ); applyCommentTagNodes(xmlChildNameMap.comment, block); applyDataTagNodes(xmlChildNameMap.data, block); @@ -886,7 +920,8 @@ function domToBlockHeadless( parentConnection.connect(block.previousConnection); } else { throw TypeError( - 'Child block does not have output or previous statement.'); + 'Child block does not have output or previous statement.' + ); } } } @@ -956,7 +991,8 @@ function domToField(block: Block, fieldName: string, xml: Element) { const field = block.getField(fieldName); if (!field) { console.warn( - 'Ignoring non-existent field ' + fieldName + ' in block ' + block.type); + 'Ignoring non-existent field ' + fieldName + ' in block ' + block.type + ); return; } field.fromXml(xml); @@ -968,7 +1004,7 @@ function domToField(block: Block, fieldName: string, xml: Element) { * @param xmlBlock XML block element or an empty DocumentFragment if the block * was an insertion marker. */ -export function deleteNext(xmlBlock: Element|DocumentFragment) { +export function deleteNext(xmlBlock: Element | DocumentFragment) { for (let i = 0; i < xmlBlock.childNodes.length; i++) { const child = xmlBlock.childNodes[i]; if (child.nodeName.toLowerCase() === 'next') { diff --git a/core/zoom_controls.ts b/core/zoom_controls.ts index 5ae63f8a5..093121b10 100644 --- a/core/zoom_controls.ts +++ b/core/zoom_controls.ts @@ -30,7 +30,6 @@ import {Size} from './utils/size.js'; import {Svg} from './utils/svg.js'; import type {WorkspaceSvg} from './workspace_svg.js'; - /** * Class for a zoom controls. */ @@ -49,13 +48,13 @@ export class ZoomControls implements IPositionable { private boundEvents: browserEvents.Data[] = []; /** The zoom in svg element. */ - private zoomInGroup: SVGGElement|null = null; + private zoomInGroup: SVGGElement | null = null; /** The zoom out svg element. */ - private zoomOutGroup: SVGGElement|null = null; + private zoomOutGroup: SVGGElement | null = null; /** The zoom reset svg element. */ - private zoomResetGroup: SVGGElement|null = null; + private zoomResetGroup: SVGGElement | null = null; /** Width of the zoom controls. */ private readonly WIDTH = 32; @@ -78,7 +77,7 @@ export class ZoomControls implements IPositionable { private readonly MARGIN_HORIZONTAL = 20; /** The SVG group containing the zoom controls. */ - private svgGroup: SVGElement|null = null; + private svgGroup: SVGElement | null = null; /** Left coordinate of the zoom controls. */ private left = 0; @@ -146,7 +145,7 @@ export class ZoomControls implements IPositionable { * @returns The UI elements's bounding box. Null if bounding box should be * ignored by other UI elements. */ - getBoundingRectangle(): Rect|null { + getBoundingRectangle(): Rect | null { let height = this.SMALL_SPACING + 2 * this.HEIGHT; if (this.zoomResetGroup) { height += this.LARGE_SPACING + this.HEIGHT; @@ -170,48 +169,71 @@ export class ZoomControls implements IPositionable { return; } - const cornerPosition = - uiPosition.getCornerOppositeToolbox(this.workspace, metrics); + const cornerPosition = uiPosition.getCornerOppositeToolbox( + this.workspace, + metrics + ); let height = this.SMALL_SPACING + 2 * this.HEIGHT; if (this.zoomResetGroup) { height += this.LARGE_SPACING + this.HEIGHT; } const startRect = uiPosition.getStartPositionRect( - cornerPosition, new Size(this.WIDTH, height), this.MARGIN_HORIZONTAL, - this.MARGIN_VERTICAL, metrics, this.workspace); + cornerPosition, + new Size(this.WIDTH, height), + this.MARGIN_HORIZONTAL, + this.MARGIN_VERTICAL, + metrics, + this.workspace + ); const verticalPosition = cornerPosition.vertical; - const bumpDirection = verticalPosition === uiPosition.verticalPosition.TOP ? - uiPosition.bumpDirection.DOWN : - uiPosition.bumpDirection.UP; + const bumpDirection = + verticalPosition === uiPosition.verticalPosition.TOP + ? uiPosition.bumpDirection.DOWN + : uiPosition.bumpDirection.UP; const positionRect = uiPosition.bumpPositionRect( - startRect, this.MARGIN_VERTICAL, bumpDirection, savedPositions); + startRect, + this.MARGIN_VERTICAL, + bumpDirection, + savedPositions + ); if (verticalPosition === uiPosition.verticalPosition.TOP) { const zoomInTranslateY = this.SMALL_SPACING + this.HEIGHT; this.zoomInGroup?.setAttribute( - 'transform', 'translate(0, ' + zoomInTranslateY + ')'); + 'transform', + 'translate(0, ' + zoomInTranslateY + ')' + ); if (this.zoomResetGroup) { const zoomResetTranslateY = - zoomInTranslateY + this.LARGE_SPACING + this.HEIGHT; + zoomInTranslateY + this.LARGE_SPACING + this.HEIGHT; this.zoomResetGroup.setAttribute( - 'transform', 'translate(0, ' + zoomResetTranslateY + ')'); + 'transform', + 'translate(0, ' + zoomResetTranslateY + ')' + ); } } else { - const zoomInTranslateY = - this.zoomResetGroup ? this.LARGE_SPACING + this.HEIGHT : 0; + const zoomInTranslateY = this.zoomResetGroup + ? this.LARGE_SPACING + this.HEIGHT + : 0; this.zoomInGroup?.setAttribute( - 'transform', 'translate(0, ' + zoomInTranslateY + ')'); + 'transform', + 'translate(0, ' + zoomInTranslateY + ')' + ); const zoomOutTranslateY = - zoomInTranslateY + this.SMALL_SPACING + this.HEIGHT; + zoomInTranslateY + this.SMALL_SPACING + this.HEIGHT; this.zoomOutGroup?.setAttribute( - 'transform', 'translate(0, ' + zoomOutTranslateY + ')'); + 'transform', + 'translate(0, ' + zoomOutTranslateY + ')' + ); } this.top = positionRect.top; this.left = positionRect.left; this.svgGroup?.setAttribute( - 'transform', 'translate(' + this.left + ',' + this.top + ')'); + 'transform', + 'translate(' + this.left + ',' + this.top + ')' + ); } /** @@ -232,33 +254,50 @@ export class ZoomControls implements IPositionable { clip-path="url(#blocklyZoomoutClipPath837493)"> */ - this.zoomOutGroup = - dom.createSvgElement(Svg.G, {'class': 'blocklyZoom'}, this.svgGroup); + this.zoomOutGroup = dom.createSvgElement( + Svg.G, + {'class': 'blocklyZoom'}, + this.svgGroup + ); const clip = dom.createSvgElement( - Svg.CLIPPATH, {'id': 'blocklyZoomoutClipPath' + rnd}, - this.zoomOutGroup); + Svg.CLIPPATH, + {'id': 'blocklyZoomoutClipPath' + rnd}, + this.zoomOutGroup + ); dom.createSvgElement( - Svg.RECT, { - 'width': 32, - 'height': 32, - }, - clip); + Svg.RECT, + { + 'width': 32, + 'height': 32, + }, + clip + ); const zoomoutSvg = dom.createSvgElement( - Svg.IMAGE, { - 'width': SPRITE.width, - 'height': SPRITE.height, - 'x': -64, - 'y': -92, - 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')', - }, - this.zoomOutGroup); + Svg.IMAGE, + { + 'width': SPRITE.width, + 'height': SPRITE.height, + 'x': -64, + 'y': -92, + 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')', + }, + this.zoomOutGroup + ); zoomoutSvg.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.workspace.options.pathToMedia + SPRITE.url); + dom.XLINK_NS, + 'xlink:href', + this.workspace.options.pathToMedia + SPRITE.url + ); // Attach listener. - this.boundEvents.push(browserEvents.conditionalBind( - this.zoomOutGroup, 'pointerdown', null, this.zoom.bind(this, -1))); + this.boundEvents.push( + browserEvents.conditionalBind( + this.zoomOutGroup, + 'pointerdown', + null, + this.zoom.bind(this, -1) + ) + ); } /** @@ -279,32 +318,50 @@ export class ZoomControls implements IPositionable { clip-path="url(#blocklyZoominClipPath837493)"> */ - this.zoomInGroup = - dom.createSvgElement(Svg.G, {'class': 'blocklyZoom'}, this.svgGroup); + this.zoomInGroup = dom.createSvgElement( + Svg.G, + {'class': 'blocklyZoom'}, + this.svgGroup + ); const clip = dom.createSvgElement( - Svg.CLIPPATH, {'id': 'blocklyZoominClipPath' + rnd}, this.zoomInGroup); + Svg.CLIPPATH, + {'id': 'blocklyZoominClipPath' + rnd}, + this.zoomInGroup + ); dom.createSvgElement( - Svg.RECT, { - 'width': 32, - 'height': 32, - }, - clip); + Svg.RECT, + { + 'width': 32, + 'height': 32, + }, + clip + ); const zoominSvg = dom.createSvgElement( - Svg.IMAGE, { - 'width': SPRITE.width, - 'height': SPRITE.height, - 'x': -32, - 'y': -92, - 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')', - }, - this.zoomInGroup); + Svg.IMAGE, + { + 'width': SPRITE.width, + 'height': SPRITE.height, + 'x': -32, + 'y': -92, + 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')', + }, + this.zoomInGroup + ); zoominSvg.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.workspace.options.pathToMedia + SPRITE.url); + dom.XLINK_NS, + 'xlink:href', + this.workspace.options.pathToMedia + SPRITE.url + ); // Attach listener. - this.boundEvents.push(browserEvents.conditionalBind( - this.zoomInGroup, 'pointerdown', null, this.zoom.bind(this, 1))); + this.boundEvents.push( + browserEvents.conditionalBind( + this.zoomInGroup, + 'pointerdown', + null, + this.zoom.bind(this, 1) + ) + ); } /** @@ -319,9 +376,9 @@ export class ZoomControls implements IPositionable { this.workspace.markFocused(); this.workspace.zoomCenter(amount); this.fireZoomEvent(); - Touch.clearTouchIdentifier(); // Don't block future drags. - e.stopPropagation(); // Don't start a workspace scroll. - e.preventDefault(); // Stop double-clicking from selecting text. + Touch.clearTouchIdentifier(); // Don't block future drags. + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. } /** @@ -342,27 +399,42 @@ export class ZoomControls implements IPositionable { clip-path="url(#blocklyZoomresetClipPath837493)"> */ - this.zoomResetGroup = - dom.createSvgElement(Svg.G, {'class': 'blocklyZoom'}, this.svgGroup); + this.zoomResetGroup = dom.createSvgElement( + Svg.G, + {'class': 'blocklyZoom'}, + this.svgGroup + ); const clip = dom.createSvgElement( - Svg.CLIPPATH, {'id': 'blocklyZoomresetClipPath' + rnd}, - this.zoomResetGroup); + Svg.CLIPPATH, + {'id': 'blocklyZoomresetClipPath' + rnd}, + this.zoomResetGroup + ); dom.createSvgElement(Svg.RECT, {'width': 32, 'height': 32}, clip); const zoomresetSvg = dom.createSvgElement( - Svg.IMAGE, { - 'width': SPRITE.width, - 'height': SPRITE.height, - 'y': -92, - 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')', - }, - this.zoomResetGroup); + Svg.IMAGE, + { + 'width': SPRITE.width, + 'height': SPRITE.height, + 'y': -92, + 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')', + }, + this.zoomResetGroup + ); zoomresetSvg.setAttributeNS( - dom.XLINK_NS, 'xlink:href', - this.workspace.options.pathToMedia + SPRITE.url); + dom.XLINK_NS, + 'xlink:href', + this.workspace.options.pathToMedia + SPRITE.url + ); // Attach event listeners. - this.boundEvents.push(browserEvents.conditionalBind( - this.zoomResetGroup, 'pointerdown', null, this.resetZoom.bind(this))); + this.boundEvents.push( + browserEvents.conditionalBind( + this.zoomResetGroup, + 'pointerdown', + null, + this.resetZoom.bind(this) + ) + ); } /** @@ -389,15 +461,18 @@ export class ZoomControls implements IPositionable { setTimeout(this.workspace.endCanvasTransition.bind(this.workspace), 500); this.fireZoomEvent(); - Touch.clearTouchIdentifier(); // Don't block future drags. - e.stopPropagation(); // Don't start a workspace scroll. - e.preventDefault(); // Stop double-clicking from selecting text. + Touch.clearTouchIdentifier(); // Don't block future drags. + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. } /** Fires a zoom control UI event. */ private fireZoomEvent() { const uiEvent = new (eventUtils.get(eventUtils.CLICK))( - null, this.workspace.id, 'zoom_controls'); + null, + this.workspace.id, + 'zoom_controls' + ); eventUtils.fire(uiEvent); } } diff --git a/demos/blockfactory/blocks.js b/demos/blockfactory/blocks.js index e05d55602..8927e6453 100644 --- a/demos/blockfactory/blocks.js +++ b/demos/blockfactory/blocks.js @@ -336,8 +336,8 @@ Blockly.Blocks['field_dropdown'] = { this.updateShape_(); this.setPreviousStatement(true, 'Field'); this.setNextStatement(true, 'Field'); - this.setMutator(new Blockly.Mutator(['field_dropdown_option_text', - 'field_dropdown_option_image'])); + this.setMutator(new Blockly.icons.MutatorIcon( + ['field_dropdown_option_text', 'field_dropdown_option_image'], this)); this.setColour(160); this.setTooltip('Dropdown menu with a list of options.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=386'); @@ -611,7 +611,7 @@ Blockly.Blocks['type_group'] = { this.typeCount_ = 2; this.updateShape_(); this.setOutput(true, 'Type'); - this.setMutator(new Blockly.Mutator(['type_group_item'])); + this.setMutator(new Blockly.icons.MutatorIcon(['type_group_item'], this)); this.setColour(230); this.setTooltip('Allows more than one type to be accepted.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=677'); @@ -671,7 +671,7 @@ Blockly.Blocks['type_group'] = { this.updateShape_(); // Reconnect any child blocks. for (var i = 0; i < this.typeCount_; i++) { - Blockly.Mutator.reconnect(connections[i], this, 'TYPE' + i); + connections[i]?.reconnect(this, 'TYPE' + i); } }, saveConnections: function(containerBlock) { diff --git a/demos/blockfactory/factory_utils.js b/demos/blockfactory/factory_utils.js index 61e53e0a2..0caf4b5aa 100644 --- a/demos/blockfactory/factory_utils.js +++ b/demos/blockfactory/factory_utils.js @@ -80,10 +80,10 @@ FactoryUtils.getGeneratorStub = function(block, generatorLanguage) { return ' var ' + root + '_' + name; } // The makevar function lives in the original update generator. - var language = generatorLanguage; + var language = generatorLanguage.toLowerCase(); var code = []; - code.push("Blockly." + language + "['" + block.type + - "'] = function(block) {"); + code.push(`${language}.${language}Generator.forBlock['${block.type}'] = ` + + 'function(block, generator) {'); // Generate getters for any fields or inputs. for (var i = 0, input; input = block.inputList[i]; i++) { @@ -93,42 +93,34 @@ FactoryUtils.getGeneratorStub = function(block, generatorLanguage) { continue; } if (field instanceof Blockly.FieldVariable) { - // Subclass of Blockly.FieldDropdown, must test first. - code.push(makeVar('variable', name) + - " = Blockly." + language + - ".nameDB_.getName(block.getFieldValue('" + name + - "'), Blockly.Variables.NAME_TYPE);"); - } else if (field instanceof Blockly.FieldAngle) { - // Subclass of Blockly.FieldTextInput, must test first. - code.push(makeVar('angle', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldColour) { - code.push(makeVar('colour', name) + - " = block.getFieldValue('" + name + "');"); + // FieldVariable is subclass of FieldDropdown; must test first. + code.push(`${makeVar('variable', name)} = ` + + `generator.nameDB_.getName(block.getFieldValue('${name}'), ` + + `Blockly.Variables.NAME_TYPE);`); } else if (field instanceof Blockly.FieldCheckbox) { - code.push(makeVar('checkbox', name) + - " = block.getFieldValue('" + name + "') === 'TRUE';"); - } else if (field instanceof Blockly.FieldDropdown) { - code.push(makeVar('dropdown', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldNumber) { - code.push(makeVar('number', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldTextInput) { - code.push(makeVar('text', name) + - " = block.getFieldValue('" + name + "');"); + code.push(`${makeVar('checkbox', name)} = ` + + `block.getFieldValue('${name}') === 'TRUE';`); + } else { + let prefix = + // Angle is subclass of FieldTextInput; must test first. + field instanceof Blockly.FieldAngle ? 'angle' : + field instanceof Blockly.FieldColour ? 'colour' : + field instanceof Blockly.FieldDropdown ? 'dropdown' : + field instanceof Blockly.FieldNumber ? 'number' : + field instanceof Blockly.FieldTextInput ? 'text' : + 'field'; // Default if subclass not found. + code.push(`${makeVar(prefix, name)} = block.getFieldValue('${name}');`); } } var name = input.name; if (name) { if (input.type === Blockly.INPUT_VALUE) { - code.push(makeVar('value', name) + - " = Blockly." + language + ".valueToCode(block, '" + name + - "', Blockly." + language + ".ORDER_ATOMIC);"); + code.push(`${makeVar('value', name)} = ` + + `generator.valueToCode(block, '${name}', ` + + `${language}.Order.ATOMIC);`); } else if (input.type === Blockly.NEXT_STATEMENT) { - code.push(makeVar('statements', name) + - " = Blockly." + language + ".statementToCode(block, '" + - name + "');"); + code.push(`${makeVar('statements', name)} = ` + + `generator.statementToCode(block, '${name}');`); } } } diff --git a/demos/blockfactory_old/blocks.js b/demos/blockfactory_old/blocks.js index 11bc01112..9d6adc9e8 100644 --- a/demos/blockfactory_old/blocks.js +++ b/demos/blockfactory_old/blocks.js @@ -308,7 +308,7 @@ Blockly.Blocks['field_dropdown'] = { this.updateShape_(); this.setPreviousStatement(true, 'Field'); this.setNextStatement(true, 'Field'); - this.setMutator(new Blockly.Mutator(['field_dropdown_option'])); + this.setMutator(new Blockly.icons.MutatorIcon(['field_dropdown_option'])); this.setColour(160); this.setTooltip('Dropdown menu with a list of options.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=386'); @@ -508,7 +508,7 @@ Blockly.Blocks['type_group'] = { this.typeCount_ = 2; this.updateShape_(); this.setOutput(true, 'Type'); - this.setMutator(new Blockly.Mutator(['type_group_item'])); + this.setMutator(new Blockly.icons.MutatorIcon(['type_group_item'])); this.setColour(230); this.setTooltip('Allows more than one type to be accepted.'); this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=677'); @@ -568,7 +568,7 @@ Blockly.Blocks['type_group'] = { this.updateShape_(); // Reconnect any child blocks. for (var i = 0; i < this.typeCount_; i++) { - Blockly.Mutator.reconnect(connections[i], this, 'TYPE' + i); + connections[i]?.reconnect(this, 'TYPE' + i); } }, saveConnections: function(containerBlock) { diff --git a/demos/code/code.js b/demos/code/code.js index 7752e6763..63b776aab 100644 --- a/demos/code/code.js +++ b/demos/code/code.js @@ -356,15 +356,15 @@ Code.renderContent = function() { Blockly.serialization.workspaces.save(Code.workspace), null, 2); jsonTextarea.focus(); } else if (content.id === 'content_javascript') { - Code.attemptCodeGeneration(Blockly.JavaScript); + Code.attemptCodeGeneration(javascript.javascriptGenerator); } else if (content.id === 'content_python') { - Code.attemptCodeGeneration(Blockly.Python); + Code.attemptCodeGeneration(python.pythonGenerator); } else if (content.id === 'content_php') { - Code.attemptCodeGeneration(Blockly.PHP); + Code.attemptCodeGeneration(php.phpGenerator); } else if (content.id === 'content_dart') { - Code.attemptCodeGeneration(Blockly.Dart); + Code.attemptCodeGeneration(dart.dartGenerator); } else if (content.id === 'content_lua') { - Code.attemptCodeGeneration(Blockly.Lua); + Code.attemptCodeGeneration(lua.luaGenerator); } if (typeof PR === 'object') { PR.prettyPrint(); @@ -395,7 +395,7 @@ Code.checkAllGeneratorFunctionsDefined = function(generator) { var missingBlockGenerators = []; for (var i = 0; i < blocks.length; i++) { var blockType = blocks[i].type; - if (!generator[blockType]) { + if (!generator.forBlock[blockType]) { if (missingBlockGenerators.indexOf(blockType) === -1) { missingBlockGenerators.push(blockType); } @@ -477,7 +477,7 @@ Code.init = function() { // Add to reserved word list: Local variables in execution environment (runJS) // and the infinite loop detection function. - Blockly.JavaScript.addReservedWords('code,timeouts,checkTimeout'); + javascript.javascriptGenerator.addReservedWords('code,timeouts,checkTimeout'); Code.loadBlocks(''); @@ -588,15 +588,15 @@ Code.runJS = function(event) { event.preventDefault(); } - Blockly.JavaScript.INFINITE_LOOP_TRAP = 'checkTimeout();\n'; + javascript.javascriptGenerator.INFINITE_LOOP_TRAP = 'checkTimeout();\n'; var timeouts = 0; var checkTimeout = function() { if (timeouts++ > 1000000) { throw MSG['timeout']; } }; - var code = Blockly.JavaScript.workspaceToCode(Code.workspace); - Blockly.JavaScript.INFINITE_LOOP_TRAP = null; + var code = javascript.javascriptGenerator.workspaceToCode(Code.workspace); + javascript.javascriptGenerator.INFINITE_LOOP_TRAP = null; try { eval(code); } catch (e) { diff --git a/generators/dart.js b/generators/dart.js index 964d9ae80..4d2943ce7 100644 --- a/generators/dart.js +++ b/generators/dart.js @@ -1,303 +1,44 @@ /** * @license - * Copyright 2014 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating Dart for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating Dart for + * blocks. This is the entrypoint for dart_compressed.js. + * @suppress {extraRequire} */ -'use strict'; -goog.module('Blockly.Dart'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.all'); -const Variables = goog.require('Blockly.Variables'); -const stringUtils = goog.require('Blockly.utils.string'); -const {Block} = goog.requireType('Blockly.Block'); -const {CodeGenerator} = goog.require('Blockly.CodeGenerator'); -const {Names, NameType} = goog.require('Blockly.Names'); -const {Workspace} = goog.requireType('Blockly.Workspace'); -const {inputTypes} = goog.require('Blockly.inputTypes'); +import {DartGenerator} from './dart/dart_generator.js'; +import * as colour from './dart/colour.js'; +import * as lists from './dart/lists.js'; +import * as logic from './dart/logic.js'; +import * as loops from './dart/loops.js'; +import * as math from './dart/math.js'; +import * as procedures from './dart/procedures.js'; +import * as text from './dart/text.js'; +import * as variables from './dart/variables.js'; +import * as variablesDynamic from './dart/variables_dynamic.js'; +export * from './dart/dart_generator.js'; /** - * Dart code generator. - * @type {!CodeGenerator} + * Dart code generator instance. + * @type {!DartGenerator} */ -const Dart = new CodeGenerator('Dart'); +export const dartGenerator = new DartGenerator(); -/** - * List of illegal variable names. - * This is not intended to be a security feature. Blockly is 100% client-side, - * so bypassing this list is trivial. This is intended to prevent users from - * accidentally clobbering a built-in object or function. - */ -Dart.addReservedWords( - // https://www.dartlang.org/docs/spec/latest/dart-language-specification.pdf - // Section 16.1.1 - 'assert,break,case,catch,class,const,continue,default,do,else,enum,' + - 'extends,false,final,finally,for,if,in,is,new,null,rethrow,return,super,' + - 'switch,this,throw,true,try,var,void,while,with,' + - // https://api.dartlang.org/dart_core.html - 'print,identityHashCode,identical,BidirectionalIterator,Comparable,' + - 'double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,' + - 'Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,' + - 'Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,' + - 'StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,' + - 'ArgumentError,AssertionError,CastError,ConcurrentModificationError,' + - 'CyclicInitializationError,Error,Exception,FallThroughError,' + - 'FormatException,IntegerDivisionByZeroException,NoSuchMethodError,' + - 'NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,' + - 'StateError,TypeError,UnimplementedError,UnsupportedError' +// Add reserved words. This list should include all words mentioned +// in RESERVED WORDS: comments in the imports above. +dartGenerator.addReservedWords('Html,Math'); + +// Install per-block-type generator functions: +Object.assign( + dartGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic ); - -/** - * Order of operation ENUMs. - * https://dart.dev/guides/language/language-tour#operators - */ -Dart.ORDER_ATOMIC = 0; // 0 "" ... -Dart.ORDER_UNARY_POSTFIX = 1; // expr++ expr-- () [] . ?. -Dart.ORDER_UNARY_PREFIX = 2; // -expr !expr ~expr ++expr --expr -Dart.ORDER_MULTIPLICATIVE = 3; // * / % ~/ -Dart.ORDER_ADDITIVE = 4; // + - -Dart.ORDER_SHIFT = 5; // << >> -Dart.ORDER_BITWISE_AND = 6; // & -Dart.ORDER_BITWISE_XOR = 7; // ^ -Dart.ORDER_BITWISE_OR = 8; // | -Dart.ORDER_RELATIONAL = 9; // >= > <= < as is is! -Dart.ORDER_EQUALITY = 10; // == != -Dart.ORDER_LOGICAL_AND = 11; // && -Dart.ORDER_LOGICAL_OR = 12; // || -Dart.ORDER_IF_NULL = 13; // ?? -Dart.ORDER_CONDITIONAL = 14; // expr ? expr : expr -Dart.ORDER_CASCADE = 15; // .. -Dart.ORDER_ASSIGNMENT = 16; // = *= /= ~/= %= += -= <<= >>= &= ^= |= -Dart.ORDER_NONE = 99; // (...) - -/** - * Whether the init method has been called. - * @type {?boolean} - */ -Dart.isInitialized = false; - -/** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ -Dart.init = function(workspace) { - // Call Blockly.CodeGenerator's init. - Object.getPrototypeOf(this).init.call(this); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - const defvars = []; - // Add developer variables (not created or named by the user). - const devVarList = Variables.allDeveloperVariables(workspace); - for (let i = 0; i < devVarList.length; i++) { - defvars.push(this.nameDB_.getName(devVarList[i], - NameType.DEVELOPER_VARIABLE)); - } - - // Add user variables, but only ones that are being used. - const variables = Variables.allUsedVarModels(workspace); - for (let i = 0; i < variables.length; i++) { - defvars.push(this.nameDB_.getName(variables[i].getId(), - NameType.VARIABLE)); - } - - // Declare all of the variables. - if (defvars.length) { - this.definitions_['variables'] = - 'var ' + defvars.join(', ') + ';'; - } - this.isInitialized = true; -}; - -/** - * Prepend the generated code with import statements and variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ -Dart.finish = function(code) { - // Indent every line. - if (code) { - code = this.prefixLines(code, this.INDENT); - } - code = 'main() {\n' + code + '}'; - - // Convert the definitions dictionary into a list. - const imports = []; - const definitions = []; - for (let name in this.definitions_) { - const def = this.definitions_[name]; - if (def.match(/^import\s/)) { - imports.push(def); - } else { - definitions.push(def); - } - } - // Call Blockly.CodeGenerator's finish. - code = Object.getPrototypeOf(this).finish.call(this, code); - this.isInitialized = false; - - this.nameDB_.reset(); - const allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n'); - return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code; -}; - -/** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ -Dart.scrubNakedValue = function(line) { - return line + ';\n'; -}; - -/** - * Encode a string as a properly escaped Dart string, complete with quotes. - * @param {string} string Text to encode. - * @return {string} Dart string. - * @protected - */ -Dart.quote_ = function(string) { - // Can't use goog.string.quote since $ must also be escaped. - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/\$/g, '\\$') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; -}; - -/** - * Encode a string as a properly escaped multiline Dart string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} Dart string. - * @protected - */ -Dart.multiline_quote_ = function (string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // + '\n' + - return lines.join(' + \'\\n\' + \n'); -}; - -/** - * Common tasks for generating Dart from blocks. - * Handles comments for the specified block and any connected value blocks. - * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The Dart code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} Dart code with comments and subsequent blocks added. - * @protected - */ -Dart.scrub_ = function(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - if (block.getProcedureDef) { - // Use documentation comment for function comments. - commentCode += this.prefixLines(comment + '\n', '/// '); - } else { - commentCode += this.prefixLines(comment + '\n', '// '); - } - } - // Collect comments for all value arguments. - // Don't collect comments for nested statements. - for (let i = 0; i < block.inputList.length; i++) { - if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '// '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; -}; - -/** - * Gets a property and adjusts the value while taking into account indexing. - * @param {!Block} block The block. - * @param {string} atId The property ID of the element to get. - * @param {number=} opt_delta Value to add. - * @param {boolean=} opt_negate Whether to negate the value. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} - */ -Dart.getAdjusted = function(block, atId, opt_delta, opt_negate, - opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - - /** @type {number} */ - let outerOrder; - let innerOrder; - if (delta) { - outerOrder = this.ORDER_ADDITIVE; - innerOrder = this.ORDER_ADDITIVE; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_PREFIX; - innerOrder = this.ORDER_UNARY_PREFIX; - } else { - outerOrder = order; - } - - /** @type {string|number} */ - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = parseInt(at, 10) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (opt_negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } - } - return at; -}; - -exports.dartGenerator = Dart; diff --git a/generators/dart/all.js b/generators/dart/all.js deleted file mode 100644 index ffd726a26..000000000 --- a/generators/dart/all.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating Dart for - * blocks. This is the entrypoint for dart_compressed.js. - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.Dart.all'); - -const moduleExports = goog.require('Blockly.Dart'); -goog.require('Blockly.Dart.colour'); -goog.require('Blockly.Dart.lists'); -goog.require('Blockly.Dart.logic'); -goog.require('Blockly.Dart.loops'); -goog.require('Blockly.Dart.math'); -goog.require('Blockly.Dart.procedures'); -goog.require('Blockly.Dart.texts'); -goog.require('Blockly.Dart.variables'); -goog.require('Blockly.Dart.variablesDynamic'); - -exports = moduleExports; diff --git a/generators/dart/colour.js b/generators/dart/colour.js index 324fe65c4..e415d589a 100644 --- a/generators/dart/colour.js +++ b/generators/dart/colour.js @@ -7,26 +7,27 @@ /** * @fileoverview Generating Dart for colour blocks. */ -'use strict'; -goog.module('Blockly.Dart.colour'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.colour'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {Order} from './dart_generator.js'; -Dart.addReservedWords('Math'); +// RESERVED WORDS: 'Math' -Dart['colour_picker'] = function(block) { +export function colour_picker(block, generator) { // Colour picker. - const code = Dart.quote_(block.getFieldValue('COLOUR')); - return [code, Dart.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('COLOUR')); + return [code, Order.ATOMIC]; }; -Dart['colour_random'] = function(block) { +export function colour_random(block, generator) { // Generate a random colour. - Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; - const functionName = Dart.provideFunction_('colour_random', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}() { + generator.definitions_['import_dart_math'] = + "import 'dart:math' as Math;"; + const functionName = generator.provideFunction_('colour_random', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}() { String hex = '0123456789abcdef'; var rnd = new Math.Random(); return '#\${hex[rnd.nextInt(16)]}\${hex[rnd.nextInt(16)]}' @@ -35,21 +36,19 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}() { } `); const code = functionName + '()'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['colour_rgb'] = function(block) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. - const red = Dart.valueToCode(block, 'RED', - Dart.ORDER_NONE) || 0; - const green = Dart.valueToCode(block, 'GREEN', - Dart.ORDER_NONE) || 0; - const blue = Dart.valueToCode(block, 'BLUE', - Dart.ORDER_NONE) || 0; + const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; + const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; + const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; - const functionName = Dart.provideFunction_('colour_rgb', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num r, num g, num b) { + generator.definitions_['import_dart_math'] = + "import 'dart:math' as Math;"; + const functionName = generator.provideFunction_('colour_rgb', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}(num r, num g, num b) { num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round(); String rs = rn.toInt().toRadixString(16); rs = '0$rs'; @@ -66,18 +65,22 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num r, num g, num b) { } `); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['colour_blend'] = function(block) { +export function colour_blend(block, generator) { // Blend two colours together. - const c1 = Dart.valueToCode(block, 'COLOUR1', Dart.ORDER_NONE) || "'#000000'"; - const c2 = Dart.valueToCode(block, 'COLOUR2', Dart.ORDER_NONE) || "'#000000'"; - const ratio = Dart.valueToCode(block, 'RATIO', Dart.ORDER_NONE) || 0.5; + const c1 = + generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; + const c2 = + generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; + const ratio = + generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; - Dart.definitions_['import_dart_math'] = "import 'dart:math' as Math;"; - const functionName = Dart.provideFunction_('colour_blend', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String c1, String c2, num ratio) { + generator.definitions_['import_dart_math'] = + "import 'dart:math' as Math;"; + const functionName = generator.provideFunction_('colour_blend', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String c1, String c2, num ratio) { ratio = Math.max(Math.min(ratio, 1), 0); int r1 = int.parse('0x\${c1.substring(1, 3)}'); int g1 = int.parse('0x\${c1.substring(3, 5)}'); @@ -101,5 +104,5 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String c1, String c2, num ratio) { } `); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; diff --git a/generators/dart/dart_generator.js b/generators/dart/dart_generator.js new file mode 100644 index 000000000..5f64e919f --- /dev/null +++ b/generators/dart/dart_generator.js @@ -0,0 +1,311 @@ +/** + * @license + * Copyright 2014 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating Dart for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart'); + +import * as Variables from '../../core/variables.js'; +import * as stringUtils from '../../core/utils/string.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names, NameType} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * https://dart.dev/guides/language/language-tour#operators + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + UNARY_POSTFIX: 1, // expr++ expr-- () [] . ?. + UNARY_PREFIX: 2, // -expr !expr ~expr ++expr --expr + MULTIPLICATIVE: 3, // * / % ~/ + ADDITIVE: 4, // + - + SHIFT: 5, // << >> + BITWISE_AND: 6, // & + BITWISE_XOR: 7, // ^ + BITWISE_OR: 8, // | + RELATIONAL: 9, // >= > <= < as is is! + EQUALITY: 10, // == != + LOGICAL_AND: 11, // && + LOGICAL_OR: 12, // || + IF_NULL: 13, // ?? + CONDITIONAL: 14, // expr ? expr : expr + CASCADE: 15, // .. + ASSIGNMENT: 16, // = *= /= ~/= %= += -= <<= >>= &= ^= |= + NONE: 99, // (...) +}; + +/** + * Dart code generator class. + */ +export class DartGenerator extends CodeGenerator { + constructor(name) { + super(name ?? 'Dart'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // https://www.dartlang.org/docs/spec/latest/dart-language-specification.pdf + // Section 16.1.1 + 'assert,break,case,catch,class,const,continue,default,do,else,enum,' + + 'extends,false,final,finally,for,if,in,is,new,null,rethrow,return,' + + 'super,switch,this,throw,true,try,var,void,while,with,' + + // https://api.dartlang.org/dart_core.html + 'print,identityHashCode,identical,BidirectionalIterator,Comparable,' + + 'double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,' + + 'Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,' + + 'Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,' + + 'StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,' + + 'ArgumentError,AssertionError,CastError,ConcurrentModificationError,' + + 'CyclicInitializationError,Error,Exception,FallThroughError,' + + 'FormatException,IntegerDivisionByZeroException,NoSuchMethodError,' + + 'NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,' + + 'StateError,TypeError,UnimplementedError,UnsupportedError' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + super.init(); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + const defvars = []; + // Add developer variables (not created or named by the user). + const devVarList = Variables.allDeveloperVariables(workspace); + for (let i = 0; i < devVarList.length; i++) { + defvars.push(this.nameDB_.getName(devVarList[i], + NameType.DEVELOPER_VARIABLE)); + } + + // Add user variables, but only ones that are being used. + const variables = Variables.allUsedVarModels(workspace); + for (let i = 0; i < variables.length; i++) { + defvars.push(this.nameDB_.getName(variables[i].getId(), + NameType.VARIABLE)); + } + + // Declare all of the variables. + if (defvars.length) { + this.definitions_['variables'] = + 'var ' + defvars.join(', ') + ';'; + } + this.isInitialized = true; + } + + /** + * Prepend the generated code with import statements and variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Indent every line. + if (code) { + code = this.prefixLines(code, this.INDENT); + } + code = 'main() {\n' + code + '}'; + + // Convert the definitions dictionary into a list. + const imports = []; + const definitions = []; + for (let name in this.definitions_) { + const def = this.definitions_[name]; + if (def.match(/^import\s/)) { + imports.push(def); + } else { + definitions.push(def); + } + } + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + const allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n'); + return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code; + } + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. A trailing semicolon is needed to make this legal. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + ';\n'; + } + + /** + * Encode a string as a properly escaped Dart string, complete with quotes. + * @param {string} string Text to encode. + * @return {string} Dart string. + * @protected + */ + quote_(string) { + // Can't use goog.string.quote since $ must also be escaped. + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/\$/g, '\\$') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + } + + /** + * Encode a string as a properly escaped multiline Dart string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} Dart string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // + '\n' + + return lines.join(' + \'\\n\' + \n'); + } + + /** + * Common tasks for generating Dart from blocks. + * Handles comments for the specified block and any connected value blocks. + * Calls any statements following this block. + * @param {!Block} block The current block. + * @param {string} code The Dart code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string} Dart code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + if (block.getProcedureDef) { + // Use documentation comment for function comments. + commentCode += this.prefixLines(comment + '\n', '/// '); + } else { + commentCode += this.prefixLines(comment + '\n', '// '); + } + } + // Collect comments for all value arguments. + // Don't collect comments for nested statements. + for (let i = 0; i < block.inputList.length; i++) { + if (block.inputList[i].type === inputTypes.VALUE) { + const childBlock = block.inputList[i].connection.targetBlock(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '// '); + } + } + } + } + } + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + } + + /** + * Gets a property and adjusts the value while taking into account indexing. + * @param {!Block} block The block. + * @param {string} atId The property ID of the element to get. + * @param {number=} opt_delta Value to add. + * @param {boolean=} opt_negate Whether to negate the value. + * @param {number=} opt_order The highest order acting on this value. + * @return {string|number} + */ + getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { + let delta = opt_delta || 0; + let order = opt_order || this.ORDER_NONE; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + + /** @type {number} */ + let outerOrder; + let innerOrder; + if (delta) { + outerOrder = this.ORDER_ADDITIVE; + innerOrder = this.ORDER_ADDITIVE; + } else if (opt_negate) { + outerOrder = this.ORDER_UNARY_PREFIX; + innerOrder = this.ORDER_UNARY_PREFIX; + } else { + outerOrder = order; + } + + /** @type {string|number} */ + let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = parseInt(at, 10) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = at + ' + ' + delta; + } else if (delta < 0) { + at = at + ' - ' + -delta; + } + if (opt_negate) { + if (delta) { + at = '-(' + at + ')'; + } else { + at = '-' + at; + } + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (innerOrder && order >= innerOrder) { + at = '(' + at + ')'; + } + } + return at; + } +} diff --git a/generators/dart/lists.js b/generators/dart/lists.js index 28fc9bd89..0f7a24d64 100644 --- a/generators/dart/lists.js +++ b/generators/dart/lists.js @@ -7,80 +7,84 @@ /** * @fileoverview Generating Dart for list blocks. */ -'use strict'; -goog.module('Blockly.Dart.lists'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.lists'); -const {NameType} = goog.require('Blockly.Names'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {NameType} from '../../core/names.js'; +import {Order} from './dart_generator.js'; -Dart.addReservedWords('Math'); +// RESERVED WORDS: 'Math' -Dart['lists_create_empty'] = function(block) { +export function lists_create_empty(block, generator) { // Create an empty list. - return ['[]', Dart.ORDER_ATOMIC]; + return ['[]', Order.ATOMIC]; }; -Dart['lists_create_with'] = function(block) { +export function lists_create_with(block, generator) { // Create a list with any number of elements of any type. const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { - elements[i] = Dart.valueToCode(block, 'ADD' + i, Dart.ORDER_NONE) || 'null'; + elements[i] = + generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null'; } const code = '[' + elements.join(', ') + ']'; - return [code, Dart.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -Dart['lists_repeat'] = function(block) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. - const element = Dart.valueToCode(block, 'ITEM', Dart.ORDER_NONE) || 'null'; - const repeatCount = Dart.valueToCode(block, 'NUM', Dart.ORDER_NONE) || '0'; + const element = + generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; + const repeatCount = + generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const code = 'new List.filled(' + repeatCount + ', ' + element + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['lists_length'] = function(block) { +export function lists_length(block, generator) { // String or array length. const list = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '[]'; - return [list + '.length', Dart.ORDER_UNARY_POSTFIX]; + generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || '[]'; + return [list + '.length', Order.UNARY_POSTFIX]; }; -Dart['lists_isEmpty'] = function(block) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const list = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '[]'; - return [list + '.isEmpty', Dart.ORDER_UNARY_POSTFIX]; + generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || '[]'; + return [list + '.isEmpty', Order.UNARY_POSTFIX]; }; -Dart['lists_indexOf'] = function(block) { +export function lists_indexOf(block, generator) { // Find an item in the list. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; - const item = Dart.valueToCode(block, 'FIND', Dart.ORDER_NONE) || "''"; + const item = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const list = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || '[]'; + generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || '[]'; const code = list + '.' + operator + '(' + item + ')'; if (block.workspace.options.oneBasedIndex) { - return [code + ' + 1', Dart.ORDER_ADDITIVE]; + return [code + ' + 1', Order.ADDITIVE]; } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['lists_getIndex'] = function(block) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; const listOrder = (where === 'RANDOM' || where === 'FROM_END') ? - Dart.ORDER_NONE : - Dart.ORDER_UNARY_POSTFIX; - let list = Dart.valueToCode(block, 'VALUE', listOrder) || '[]'; + Order.NONE : + Order.UNARY_POSTFIX; + let list = generator.valueToCode(block, 'VALUE', listOrder) || '[]'; // Cache non-trivial values to variables to prevent repeated look-ups. // Closure, which accesses and modifies 'list'. function cacheList() { - const listVar = Dart.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + const listVar = + generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); const code = 'List ' + listVar + ' = ' + list + ';\n'; list = listVar; return code; @@ -92,10 +96,12 @@ Dart['lists_getIndex'] = function(block) { !list.match(/^\w+$/)) { // `list` is an expression, so we may not evaluate it more than once. if (where === 'RANDOM') { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; // We can use multiple statements. let code = cacheList(); - const xVar = Dart.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); + const xVar = + generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); code += 'int ' + xVar + ' = new Math.Random().nextInt(' + list + '.length);\n'; code += list + '.removeAt(' + xVar + ');\n'; @@ -103,34 +109,36 @@ Dart['lists_getIndex'] = function(block) { } else { // where === 'FROM_END' if (mode === 'REMOVE') { // We can use multiple statements. - const at = Dart.getAdjusted(block, 'AT', 1, false, Dart.ORDER_ADDITIVE); + const at = + generator.getAdjusted(block, 'AT', 1, false, Order.ADDITIVE); let code = cacheList(); code += list + '.removeAt(' + list + '.length' + ' - ' + at + ');\n'; return code; } else if (mode === 'GET') { - const at = Dart.getAdjusted(block, 'AT', 1); + const at = generator.getAdjusted(block, 'AT', 1); // We need to create a procedure to avoid reevaluating values. - const functionName = Dart.provideFunction_('lists_get_from_end', ` -dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { + const functionName = generator.provideFunction_('lists_get_from_end', ` +dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { x = my_list.length - x; return my_list[x]; } `); const code = functionName + '(' + list + ', ' + at + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { - const at = Dart.getAdjusted(block, 'AT', 1); + const at = generator.getAdjusted(block, 'AT', 1); // We need to create a procedure to avoid reevaluating values. - const functionName = Dart.provideFunction_('lists_remove_from_end', ` -dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { + const functionName = + generator.provideFunction_('lists_remove_from_end', ` +dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { x = my_list.length - x; return my_list.removeAt(x); } `); const code = functionName + '(' + list + ', ' + at + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } } } else { @@ -140,10 +148,10 @@ dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { case 'FIRST': if (mode === 'GET') { const code = list + '.first'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { const code = list + '.removeAt(0)'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'REMOVE') { return list + '.removeAt(0);\n'; } @@ -151,36 +159,37 @@ dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { case 'LAST': if (mode === 'GET') { const code = list + '.last'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { const code = list + '.removeLast()'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'REMOVE') { return list + '.removeLast();\n'; } break; case 'FROM_START': { - const at = Dart.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); if (mode === 'GET') { const code = list + '[' + at + ']'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { const code = list + '.removeAt(' + at + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'REMOVE') { return list + '.removeAt(' + at + ');\n'; } break; } case 'FROM_END': { - const at = Dart.getAdjusted(block, 'AT', 1, false, Dart.ORDER_ADDITIVE); + const at = + generator.getAdjusted(block, 'AT', 1, false, Order.ADDITIVE); if (mode === 'GET') { const code = list + '[' + list + '.length - ' + at + ']'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE' || mode === 'REMOVE') { const code = list + '.removeAt(' + list + '.length - ' + at + ')'; if (mode === 'GET_REMOVE') { - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'REMOVE') { return code + ';\n'; } @@ -188,33 +197,36 @@ dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list, num x) { break; } case 'RANDOM': - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; if (mode === 'REMOVE') { // We can use multiple statements. - const xVar = Dart.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); + const xVar = + generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); let code = 'int ' + xVar + ' = new Math.Random().nextInt(' + list + '.length);\n'; code += list + '.removeAt(' + xVar + ');\n'; return code; } else if (mode === 'GET') { - const functionName = Dart.provideFunction_('lists_get_random_item', ` -dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { + const functionName = + generator.provideFunction_('lists_get_random_item', ` +dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { int x = new Math.Random().nextInt(my_list.length); return my_list[x]; } `); const code = functionName + '(' + list + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } else if (mode === 'GET_REMOVE') { const functionName = - Dart.provideFunction_('lists_remove_random_item', ` -dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { + generator.provideFunction_('lists_remove_random_item', ` +dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { int x = new Math.Random().nextInt(my_list.length); return my_list.removeAt(x); } `); const code = functionName + '(' + list + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } break; } @@ -222,20 +234,23 @@ dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List my_list) { throw Error('Unhandled combination (lists_getIndex).'); }; -Dart['lists_setIndex'] = function(block) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - let list = Dart.valueToCode(block, 'LIST', Dart.ORDER_UNARY_POSTFIX) || '[]'; - const value = Dart.valueToCode(block, 'TO', Dart.ORDER_ASSIGNMENT) || 'null'; + let list = + generator.valueToCode(block, 'LIST', Order.UNARY_POSTFIX) || '[]'; + const value = + generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null'; // Cache non-trivial values to variables to prevent repeated look-ups. // Closure, which accesses and modifies 'list'. function cacheList() { if (list.match(/^\w+$/)) { return ''; } - const listVar = Dart.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + const listVar = + generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); const code = 'List ' + listVar + ' = ' + list + ';\n'; list = listVar; return code; @@ -258,7 +273,7 @@ Dart['lists_setIndex'] = function(block) { } break; case 'FROM_START': { - const at = Dart.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); if (mode === 'SET') { return list + '[' + at + '] = ' + value + ';\n'; } else if (mode === 'INSERT') { @@ -267,7 +282,8 @@ Dart['lists_setIndex'] = function(block) { break; } case 'FROM_END': { - const at = Dart.getAdjusted(block, 'AT', 1, false, Dart.ORDER_ADDITIVE); + const at = + generator.getAdjusted(block, 'AT', 1, false, Order.ADDITIVE); let code = cacheList(); if (mode === 'SET') { code += list + '[' + list + '.length - ' + at + '] = ' + value + ';\n'; @@ -280,9 +296,11 @@ Dart['lists_setIndex'] = function(block) { break; } case 'RANDOM': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; let code = cacheList(); - const xVar = Dart.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); + const xVar = + generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); code += 'int ' + xVar + ' = new Math.Random().nextInt(' + list + '.length);\n'; if (mode === 'SET') { @@ -298,10 +316,10 @@ Dart['lists_setIndex'] = function(block) { throw Error('Unhandled combination (lists_setIndex).'); }; -Dart['lists_getSublist'] = function(block) { +export function lists_getSublist(block, generator) { // Get sublist. const list = - Dart.valueToCode(block, 'LIST', Dart.ORDER_UNARY_POSTFIX) || '[]'; + generator.valueToCode(block, 'LIST', Order.UNARY_POSTFIX) || '[]'; const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); let code; @@ -312,10 +330,10 @@ Dart['lists_getSublist'] = function(block) { let at1; switch (where1) { case 'FROM_START': - at1 = Dart.getAdjusted(block, 'AT1'); + at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = Dart.getAdjusted(block, 'AT1', 1, false, Dart.ORDER_ADDITIVE); + at1 = generator.getAdjusted(block, 'AT1', 1, false, Order.ADDITIVE); at1 = list + '.length - ' + at1; break; case 'FIRST': @@ -327,10 +345,10 @@ Dart['lists_getSublist'] = function(block) { let at2; switch (where2) { case 'FROM_START': - at2 = Dart.getAdjusted(block, 'AT2', 1); + at2 = generator.getAdjusted(block, 'AT2', 1); break; case 'FROM_END': - at2 = Dart.getAdjusted(block, 'AT2', 0, false, Dart.ORDER_ADDITIVE); + at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.ADDITIVE); at2 = list + '.length - ' + at2; break; case 'LAST': @@ -345,10 +363,10 @@ Dart['lists_getSublist'] = function(block) { code = list + '.sublist(' + at1 + ', ' + at2 + ')'; } } else { - const at1 = Dart.getAdjusted(block, 'AT1'); - const at2 = Dart.getAdjusted(block, 'AT2'); - const functionName = Dart.provideFunction_('lists_get_sublist', ` -List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String where1, num at1, String where2, num at2) { + const at1 = generator.getAdjusted(block, 'AT1'); + const at2 = generator.getAdjusted(block, 'AT2'); + const functionName = generator.provideFunction_('lists_get_sublist', ` +List ${generator.FUNCTION_NAME_PLACEHOLDER_}(List list, String where1, num at1, String where2, num at2) { int getAt(String where, num at) { if (where == 'FROM_END') { at = list.length - 1 - at; @@ -369,16 +387,16 @@ List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String where1, num at1, Strin code = functionName + '(' + list + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['lists_sort'] = function(block) { +export function lists_sort(block, generator) { // Block for sorting a list. - const list = Dart.valueToCode(block, 'LIST', Dart.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); - const sortFunctionName = Dart.provideFunction_('lists_sort', ` -List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String type, int direction) { + const sortFunctionName = generator.provideFunction_('lists_sort', ` +List ${generator.FUNCTION_NAME_PLACEHOLDER_}(List list, String type, int direction) { var compareFuncs = { 'NUMERIC': (a, b) => (direction * a.compareTo(b)).toInt(), 'TEXT': (a, b) => direction * a.toString().compareTo(b.toString()), @@ -395,14 +413,15 @@ List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List list, String type, int direction) { return [ sortFunctionName + '(' + list + ', ' + '"' + type + '", ' + direction + ')', - Dart.ORDER_UNARY_POSTFIX + Order.UNARY_POSTFIX ]; }; -Dart['lists_split'] = function(block) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. - let input = Dart.valueToCode(block, 'INPUT', Dart.ORDER_UNARY_POSTFIX); - const delimiter = Dart.valueToCode(block, 'DELIM', Dart.ORDER_NONE) || "''"; + let input = generator.valueToCode(block, 'INPUT', Order.UNARY_POSTFIX); + const delimiter = + generator.valueToCode(block, 'DELIM', Order.NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { @@ -419,13 +438,13 @@ Dart['lists_split'] = function(block) { throw Error('Unknown mode: ' + mode); } const code = input + '.' + functionName + '(' + delimiter + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['lists_reverse'] = function(block) { +export function lists_reverse(block, generator) { // Block for reversing a list. - const list = Dart.valueToCode(block, 'LIST', Dart.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; // XXX What should the operator precedence be for a `new`? const code = 'new List.from(' + list + '.reversed)'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; diff --git a/generators/dart/logic.js b/generators/dart/logic.js index 4fa117987..ab589a133 100644 --- a/generators/dart/logic.js +++ b/generators/dart/logic.js @@ -7,29 +7,30 @@ /** * @fileoverview Generating Dart for logic blocks. */ -'use strict'; -goog.module('Blockly.Dart.logic'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.logic'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {Order} from './dart_generator.js'; -Dart['controls_if'] = function(block) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = '', branchCode, conditionCode; - if (Dart.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - code += Dart.injectId(Dart.STATEMENT_PREFIX, block); + code += generator.injectId(generator.STATEMENT_PREFIX, block); } do { conditionCode = - Dart.valueToCode(block, 'IF' + n, Dart.ORDER_NONE) || 'false'; - branchCode = Dart.statementToCode(block, 'DO' + n); - if (Dart.STATEMENT_SUFFIX) { + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false'; + branchCode = generator.statementToCode(block, 'DO' + n); + if (generator.STATEMENT_SUFFIX) { branchCode = - Dart.prefixLines( - Dart.injectId(Dart.STATEMENT_SUFFIX, block), Dart.INDENT) + + generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), generator.INDENT) + branchCode; } code += (n > 0 ? 'else ' : '') + 'if (' + conditionCode + ') {\n' + @@ -37,12 +38,13 @@ Dart['controls_if'] = function(block) { n++; } while (block.getInput('IF' + n)); - if (block.getInput('ELSE') || Dart.STATEMENT_SUFFIX) { - branchCode = Dart.statementToCode(block, 'ELSE'); - if (Dart.STATEMENT_SUFFIX) { + if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { + branchCode = generator.statementToCode(block, 'ELSE'); + if (generator.STATEMENT_SUFFIX) { branchCode = - Dart.prefixLines( - Dart.injectId(Dart.STATEMENT_SUFFIX, block), Dart.INDENT) + + generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), generator.INDENT) + branchCode; } code += ' else {\n' + branchCode + '}'; @@ -50,29 +52,29 @@ Dart['controls_if'] = function(block) { return code + '\n'; }; -Dart['controls_ifelse'] = Dart['controls_if']; +export const controls_ifelse = controls_if; -Dart['logic_compare'] = function(block) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; const operator = OPERATORS[block.getFieldValue('OP')]; const order = (operator === '==' || operator === '!=') ? - Dart.ORDER_EQUALITY : - Dart.ORDER_RELATIONAL; - const argument0 = Dart.valueToCode(block, 'A', order) || '0'; - const argument1 = Dart.valueToCode(block, 'B', order) || '0'; + Order.EQUALITY : + Order.RELATIONAL; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; }; -Dart['logic_operation'] = function(block) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const order = - (operator === '&&') ? Dart.ORDER_LOGICAL_AND : Dart.ORDER_LOGICAL_OR; - let argument0 = Dart.valueToCode(block, 'A', order); - let argument1 = Dart.valueToCode(block, 'B', order); + (operator === '&&') ? Order.LOGICAL_AND : Order.LOGICAL_OR; + let argument0 = generator.valueToCode(block, 'A', order); + let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { // If there are no arguments, then the return value is false. argument0 = 'false'; @@ -91,33 +93,33 @@ Dart['logic_operation'] = function(block) { return [code, order]; }; -Dart['logic_negate'] = function(block) { +export function logic_negate(block, generator) { // Negation. - const order = Dart.ORDER_UNARY_PREFIX; - const argument0 = Dart.valueToCode(block, 'BOOL', order) || 'true'; + const order = Order.UNARY_PREFIX; + const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true'; const code = '!' + argument0; return [code, order]; }; -Dart['logic_boolean'] = function(block) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; - return [code, Dart.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -Dart['logic_null'] = function(block) { +export function logic_null(block, generator) { // Null data type. - return ['null', Dart.ORDER_ATOMIC]; + return ['null', Order.ATOMIC]; }; -Dart['logic_ternary'] = function(block) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = - Dart.valueToCode(block, 'IF', Dart.ORDER_CONDITIONAL) || 'false'; + generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; const value_then = - Dart.valueToCode(block, 'THEN', Dart.ORDER_CONDITIONAL) || 'null'; + generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null'; const value_else = - Dart.valueToCode(block, 'ELSE', Dart.ORDER_CONDITIONAL) || 'null'; + generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'null'; const code = value_if + ' ? ' + value_then + ' : ' + value_else; - return [code, Dart.ORDER_CONDITIONAL]; + return [code, Order.CONDITIONAL]; }; diff --git a/generators/dart/loops.js b/generators/dart/loops.js index ce66c2d33..b7735abf9 100644 --- a/generators/dart/loops.js +++ b/generators/dart/loops.js @@ -7,16 +7,16 @@ /** * @fileoverview Generating Dart for loop blocks. */ -'use strict'; -goog.module('Blockly.Dart.loops'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.loops'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); +import {Order} from './dart_generator.js'; +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; -Dart['controls_repeat_ext'] = function(block) { +export function controls_repeat_ext(block, generator) { let repeats; // Repeat n times. if (block.getField('TIMES')) { @@ -24,15 +24,18 @@ Dart['controls_repeat_ext'] = function(block) { repeats = String(Number(block.getFieldValue('TIMES'))); } else { // External number. - repeats = Dart.valueToCode(block, 'TIMES', Dart.ORDER_ASSIGNMENT) || '0'; + repeats = + generator.valueToCode(block, 'TIMES', Order.ASSIGNMENT) || '0'; } - let branch = Dart.statementToCode(block, 'DO'); - branch = Dart.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code = ''; - const loopVar = Dart.nameDB_.getDistinctName('count', NameType.VARIABLE); + const loopVar = + generator.nameDB_.getDistinctName('count', NameType.VARIABLE); let endVar = repeats; if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) { - endVar = Dart.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE); + endVar = + generator.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE); code += 'var ' + endVar + ' = ' + repeats + ';\n'; } code += 'for (int ' + loopVar + ' = 0; ' + loopVar + ' < ' + endVar + '; ' + @@ -40,33 +43,36 @@ Dart['controls_repeat_ext'] = function(block) { return code; }; -Dart['controls_repeat'] = Dart['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -Dart['controls_whileUntil'] = function(block) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = - Dart.valueToCode( - block, 'BOOL', until ? Dart.ORDER_UNARY_PREFIX : Dart.ORDER_NONE) || + generator.valueToCode( + block, 'BOOL', until ? Order.UNARY_PREFIX : Order.NONE) || 'false'; - let branch = Dart.statementToCode(block, 'DO'); - branch = Dart.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); if (until) { argument0 = '!' + argument0; } return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -Dart['controls_for'] = function(block) { +export function controls_for(block, generator) { // For loop. const variable0 = - Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = - Dart.valueToCode(block, 'FROM', Dart.ORDER_ASSIGNMENT) || '0'; - const argument1 = Dart.valueToCode(block, 'TO', Dart.ORDER_ASSIGNMENT) || '0'; - const increment = Dart.valueToCode(block, 'BY', Dart.ORDER_ASSIGNMENT) || '1'; - let branch = Dart.statementToCode(block, 'DO'); - branch = Dart.addLoopTrap(branch, block); + generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0'; + const argument1 = + generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0'; + const increment = + generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code; if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) && stringUtils.isNumber(increment)) { @@ -87,19 +93,22 @@ Dart['controls_for'] = function(block) { let startVar = argument0; if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) { startVar = - Dart.nameDB_.getDistinctName(variable0 + '_start', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_start', NameType.VARIABLE); code += 'var ' + startVar + ' = ' + argument0 + ';\n'; } let endVar = argument1; if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) { endVar = - Dart.nameDB_.getDistinctName(variable0 + '_end', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_end', NameType.VARIABLE); code += 'var ' + endVar + ' = ' + argument1 + ';\n'; } // Determine loop direction at start, in case one of the bounds // changes during loop execution. const incVar = - Dart.nameDB_.getDistinctName(variable0 + '_inc', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_inc', NameType.VARIABLE); code += 'num ' + incVar + ' = '; if (stringUtils.isNumber(increment)) { code += Math.abs(increment) + ';\n'; @@ -107,7 +116,7 @@ Dart['controls_for'] = function(block) { code += '(' + increment + ').abs();\n'; } code += 'if (' + startVar + ' > ' + endVar + ') {\n'; - code += Dart.INDENT + incVar + ' = -' + incVar + ';\n'; + code += generator.INDENT + incVar + ' = -' + incVar + ';\n'; code += '}\n'; code += 'for (' + variable0 + ' = ' + startVar + '; ' + incVar + ' >= 0 ? ' + variable0 + ' <= ' + endVar + ' : ' + variable0 + @@ -117,38 +126,39 @@ Dart['controls_for'] = function(block) { return code; }; -Dart['controls_forEach'] = function(block) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = - Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = - Dart.valueToCode(block, 'LIST', Dart.ORDER_ASSIGNMENT) || '[]'; - let branch = Dart.statementToCode(block, 'DO'); - branch = Dart.addLoopTrap(branch, block); + generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); const code = 'for (var ' + variable0 + ' in ' + argument0 + ') {\n' + branch + '}\n'; return code; }; -Dart['controls_flow_statements'] = function(block) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; - if (Dart.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - xfix += Dart.injectId(Dart.STATEMENT_PREFIX, block); + xfix += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (Dart.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. - xfix += Dart.injectId(Dart.STATEMENT_SUFFIX, block); + xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } - if (Dart.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { const loop = block.getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. - xfix += Dart.injectId(Dart.STATEMENT_PREFIX, loop); + xfix += generator.injectId(generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { diff --git a/generators/dart/math.js b/generators/dart/math.js index 81580c213..3d15d5a2f 100644 --- a/generators/dart/math.js +++ b/generators/dart/math.js @@ -7,81 +7,83 @@ /** * @fileoverview Generating Dart for math blocks. */ -'use strict'; -goog.module('Blockly.Dart.math'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.math'); -const {NameType} = goog.require('Blockly.Names'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {NameType} from '../../core/names.js'; +import {Order} from './dart_generator.js'; -Dart.addReservedWords('Math'); +// RESERVED WORDS: 'Math' -Dart['math_number'] = function(block) { +export function math_number(block, generator) { // Numeric value. let code = Number(block.getFieldValue('NUM')); let order; if (code === Infinity) { code = 'double.infinity'; - order = Dart.ORDER_UNARY_POSTFIX; + order = Order.UNARY_POSTFIX; } else if (code === -Infinity) { code = '-double.infinity'; - order = Dart.ORDER_UNARY_PREFIX; + order = Order.UNARY_PREFIX; } else { - // -4.abs() returns -4 in Dart due to strange order of operation choices. + // -4.abs() returns -4 in generator due to strange order of operation choices. // -4 is actually an operator and a number. Reflect this in the order. - order = code < 0 ? Dart.ORDER_UNARY_PREFIX : Dart.ORDER_ATOMIC; + order = code < 0 ? Order.UNARY_PREFIX : Order.ATOMIC; } return [code, order]; }; -Dart['math_arithmetic'] = function(block) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { - 'ADD': [' + ', Dart.ORDER_ADDITIVE], - 'MINUS': [' - ', Dart.ORDER_ADDITIVE], - 'MULTIPLY': [' * ', Dart.ORDER_MULTIPLICATIVE], - 'DIVIDE': [' / ', Dart.ORDER_MULTIPLICATIVE], - 'POWER': [null, Dart.ORDER_NONE], // Handle power separately. + 'ADD': [' + ', Order.ADDITIVE], + 'MINUS': [' - ', Order.ADDITIVE], + 'MULTIPLY': [' * ', Order.MULTIPLICATIVE], + 'DIVIDE': [' / ', Order.MULTIPLICATIVE], + 'POWER': [null, Order.NONE], // Handle power separately. }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; const order = tuple[1]; - const argument0 = Dart.valueToCode(block, 'A', order) || '0'; - const argument1 = Dart.valueToCode(block, 'B', order) || '0'; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; let code; - // Power in Dart requires a special case since it has no operator. + // Power in generator requires a special case since it has no operator. if (!operator) { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; code = 'Math.pow(' + argument0 + ', ' + argument1 + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } code = argument0 + operator + argument1; return [code, order]; }; -Dart['math_single'] = function(block) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; let arg; if (operator === 'NEG') { // Negation is a special case given its different operator precedence. - arg = Dart.valueToCode(block, 'NUM', Dart.ORDER_UNARY_PREFIX) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.UNARY_PREFIX) || '0'; if (arg[0] === '-') { - // --3 is not legal in Dart. + // --3 is not legal in generator. arg = ' ' + arg; } code = '-' + arg; - return [code, Dart.ORDER_UNARY_PREFIX]; + return [code, Order.UNARY_PREFIX]; } - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; if (operator === 'ABS' || operator.substring(0, 5) === 'ROUND') { - arg = Dart.valueToCode(block, 'NUM', Dart.ORDER_UNARY_POSTFIX) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.UNARY_POSTFIX) || '0'; } else if (operator === 'SIN' || operator === 'COS' || operator === 'TAN') { - arg = Dart.valueToCode(block, 'NUM', Dart.ORDER_MULTIPLICATIVE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; } else { - arg = Dart.valueToCode(block, 'NUM', Dart.ORDER_NONE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; } // First, handle cases which generate values that don't need parentheses // wrapping the code. @@ -121,7 +123,7 @@ Dart['math_single'] = function(block) { break; } if (code) { - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } // Second, handle cases which generate values that may need parentheses // wrapping the code. @@ -141,49 +143,50 @@ Dart['math_single'] = function(block) { default: throw Error('Unknown math operator: ' + operator); } - return [code, Dart.ORDER_MULTIPLICATIVE]; + return [code, Order.MULTIPLICATIVE]; }; -Dart['math_constant'] = function(block) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { - 'PI': ['Math.pi', Dart.ORDER_UNARY_POSTFIX], - 'E': ['Math.e', Dart.ORDER_UNARY_POSTFIX], - 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', Dart.ORDER_MULTIPLICATIVE], - 'SQRT2': ['Math.sqrt2', Dart.ORDER_UNARY_POSTFIX], - 'SQRT1_2': ['Math.sqrt1_2', Dart.ORDER_UNARY_POSTFIX], - 'INFINITY': ['double.infinity', Dart.ORDER_ATOMIC], + 'PI': ['Math.pi', Order.UNARY_POSTFIX], + 'E': ['Math.e', Order.UNARY_POSTFIX], + 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', Order.MULTIPLICATIVE], + 'SQRT2': ['Math.sqrt2', Order.UNARY_POSTFIX], + 'SQRT1_2': ['Math.sqrt1_2', Order.UNARY_POSTFIX], + 'INFINITY': ['double.infinity', Order.ATOMIC], }; const constant = block.getFieldValue('CONSTANT'); if (constant !== 'INFINITY') { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; } return CONSTANTS[constant]; }; -Dart['math_number_property'] = function(block) { +export function math_number_property(block, generator) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. const PROPERTIES = { - 'EVEN': [' % 2 == 0', Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], - 'ODD': [' % 2 == 1', Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], - 'WHOLE': [' % 1 == 0', Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], - 'POSITIVE': [' > 0', Dart.ORDER_RELATIONAL, Dart.ORDER_RELATIONAL], - 'NEGATIVE': [' < 0', Dart.ORDER_RELATIONAL, Dart.ORDER_RELATIONAL], - 'DIVISIBLE_BY': [null, Dart.ORDER_MULTIPLICATIVE, Dart.ORDER_EQUALITY], - 'PRIME': [null, Dart.ORDER_NONE, Dart.ORDER_UNARY_POSTFIX], + 'EVEN': [' % 2 == 0', Order.MULTIPLICATIVE, Order.EQUALITY], + 'ODD': [' % 2 == 1', Order.MULTIPLICATIVE, Order.EQUALITY], + 'WHOLE': [' % 1 == 0', Order.MULTIPLICATIVE, Order.EQUALITY], + 'POSITIVE': [' > 0', Order.RELATIONAL, Order.RELATIONAL], + 'NEGATIVE': [' < 0', Order.RELATIONAL, Order.RELATIONAL], + 'DIVISIBLE_BY': [null, Order.MULTIPLICATIVE, Order.EQUALITY], + 'PRIME': [null, Order.NONE, Order.UNARY_POSTFIX], }; const dropdownProperty = block.getFieldValue('PROPERTY'); const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; - const numberToCheck = Dart.valueToCode(block, 'NUMBER_TO_CHECK', + const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - Dart.definitions_['import_dart_math'] = + generator.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_isPrime', ` -bool ${Dart.FUNCTION_NAME_PLACEHOLDER_}(n) { + const functionName = generator.provideFunction_('math_isPrime', ` +bool ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { // https://en.wikipedia.org/wiki/Primality_test#Naive_methods if (n == 2 || n == 3) { return true; @@ -204,10 +207,10 @@ bool ${Dart.FUNCTION_NAME_PLACEHOLDER_}(n) { `); code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = Dart.valueToCode(block, 'DIVISOR', - Dart.ORDER_MULTIPLICATIVE) || '0'; + const divisor = generator.valueToCode(block, 'DIVISOR', + Order.MULTIPLICATIVE) || '0'; if (divisor === '0') { - return ['false', Dart.ORDER_ATOMIC]; + return ['false', Order.ATOMIC]; } code = numberToCheck + ' % ' + divisor + ' == 0'; } else { @@ -216,30 +219,31 @@ bool ${Dart.FUNCTION_NAME_PLACEHOLDER_}(n) { return [code, outputOrder]; }; -Dart['math_change'] = function(block) { +export function math_change(block, generator) { // Add to a variable in place. const argument0 = - Dart.valueToCode(block, 'DELTA', Dart.ORDER_ADDITIVE) || '0'; + generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; const varName = - Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = (' + varName + ' is num ? ' + varName + ' : 0) + ' + argument0 + ';\n'; }; // Rounding functions have a single operand. -Dart['math_round'] = Dart['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -Dart['math_trig'] = Dart['math_single']; +export const math_trig = math_single; -Dart['math_on_list'] = function(block) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); - const list = Dart.valueToCode(block, 'LIST', Dart.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; let code; switch (func) { case 'SUM': { - const functionName = Dart.provideFunction_('math_sum', ` -num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + const functionName = generator.provideFunction_('math_sum', ` +num ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { num sumVal = 0; myList.forEach((num entry) {sumVal += entry;}); return sumVal; @@ -249,9 +253,10 @@ num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { break; } case 'MIN': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_min', ` -num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const functionName = generator.provideFunction_('math_min', ` +num ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { if (myList.isEmpty) return null; num minVal = myList[0]; myList.forEach((num entry) {minVal = Math.min(minVal, entry);}); @@ -262,9 +267,10 @@ num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { break; } case 'MAX': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_max', ` -num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const functionName = generator.provideFunction_('math_max', ` +num ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { if (myList.isEmpty) return null; num maxVal = myList[0]; myList.forEach((num entry) {maxVal = Math.max(maxVal, entry);}); @@ -277,8 +283,8 @@ num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { case 'AVERAGE': { // This operation exclude null and values that are not int or float: // math_mean([null,null,"aString",1,9]) -> 5.0 - const functionName = Dart.provideFunction_('math_mean', ` -num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + const functionName = generator.provideFunction_('math_mean', ` +num ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { // First filter list for numbers only. List localList = new List.from(myList); localList.removeWhere((a) => a is! num); @@ -292,8 +298,8 @@ num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { break; } case 'MEDIAN': { - const functionName = Dart.provideFunction_('math_median', ` -num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + const functionName = generator.provideFunction_('math_median', ` +num ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { // First filter list for numbers only, then sort, then return middle value // or the average of two middle values if list has an even number of elements. List localList = new List.from(myList); @@ -312,12 +318,13 @@ num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { break; } case 'MODE': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1] - const functionName = Dart.provideFunction_('math_modes', ` -List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List values) { + const functionName = generator.provideFunction_('math_modes', ` +List ${generator.FUNCTION_NAME_PLACEHOLDER_}(List values) { List modes = []; List counts = []; int maxCount = 0; @@ -350,9 +357,11 @@ List ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List values) { break; } case 'STD_DEV': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_standard_deviation', ` -num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const functionName = + generator.provideFunction_('math_standard_deviation', ` +num ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { // First filter list for numbers only. List numbers = new List.from(myList); numbers.removeWhere((a) => a is! num); @@ -370,9 +379,10 @@ num ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { break; } case 'RANDOM': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('math_random_item', ` -dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const functionName = generator.provideFunction_('math_random_item', ` +dynamic ${generator.FUNCTION_NAME_PLACEHOLDER_}(List myList) { int x = new Math.Random().nextInt(myList.length); return myList[x]; } @@ -383,38 +393,41 @@ dynamic ${Dart.FUNCTION_NAME_PLACEHOLDER_}(List myList) { default: throw Error('Unknown operator: ' + func); } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['math_modulo'] = function(block) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = - Dart.valueToCode(block, 'DIVIDEND', Dart.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || '0'; const argument1 = - Dart.valueToCode(block, 'DIVISOR', Dart.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'DIVISOR', Order.MULTIPLICATIVE) || '0'; const code = argument0 + ' % ' + argument1; - return [code, Dart.ORDER_MULTIPLICATIVE]; + return [code, Order.MULTIPLICATIVE]; }; -Dart['math_constrain'] = function(block) { +export function math_constrain(block, generator) { // Constrain a number between two limits. - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const argument0 = Dart.valueToCode(block, 'VALUE', Dart.ORDER_NONE) || '0'; - const argument1 = Dart.valueToCode(block, 'LOW', Dart.ORDER_NONE) || '0'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const argument0 = + generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0'; const argument2 = - Dart.valueToCode(block, 'HIGH', Dart.ORDER_NONE) || 'double.infinity'; + generator.valueToCode(block, 'HIGH', Order.NONE) || 'double.infinity'; const code = 'Math.min(Math.max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['math_random_int'] = function(block) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const argument0 = Dart.valueToCode(block, 'FROM', Dart.ORDER_NONE) || '0'; - const argument1 = Dart.valueToCode(block, 'TO', Dart.ORDER_NONE) || '0'; - const functionName = Dart.provideFunction_('math_random_int', ` -int ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num a, num b) { + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; + const functionName = generator.provideFunction_('math_random_int', ` +int ${generator.FUNCTION_NAME_PLACEHOLDER_}(num a, num b) { if (a > b) { // Swap a and b to ensure a is smaller. num c = a; @@ -425,22 +438,24 @@ int ${Dart.FUNCTION_NAME_PLACEHOLDER_}(num a, num b) { } `); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['math_random_float'] = function(block) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - return ['new Math.Random().nextDouble()', Dart.ORDER_UNARY_POSTFIX]; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + return ['new Math.Random().nextDouble()', Order.UNARY_POSTFIX]; }; -Dart['math_atan2'] = function(block) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const argument0 = Dart.valueToCode(block, 'X', Dart.ORDER_NONE) || '0'; - const argument1 = Dart.valueToCode(block, 'Y', Dart.ORDER_NONE) || '0'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; return [ 'Math.atan2(' + argument1 + ', ' + argument0 + ') / Math.pi * 180', - Dart.ORDER_MULTIPLICATIVE + Order.MULTIPLICATIVE ]; }; diff --git a/generators/dart/procedures.js b/generators/dart/procedures.js index bf877277e..c8d61cc81 100644 --- a/generators/dart/procedures.js +++ b/generators/dart/procedures.js @@ -7,98 +7,104 @@ /** * @fileoverview Generating Dart for procedure blocks. */ -'use strict'; -goog.module('Blockly.Dart.procedures'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.procedures'); -const {NameType} = goog.require('Blockly.Names'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {NameType} from '../../core/names.js'; +import {Order} from './dart_generator.js'; -Dart['procedures_defreturn'] = function(block) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. const funcName = - Dart.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); let xfix1 = ''; - if (Dart.STATEMENT_PREFIX) { - xfix1 += Dart.injectId(Dart.STATEMENT_PREFIX, block); + if (generator.STATEMENT_PREFIX) { + xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (Dart.STATEMENT_SUFFIX) { - xfix1 += Dart.injectId(Dart.STATEMENT_SUFFIX, block); + if (generator.STATEMENT_SUFFIX) { + xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (xfix1) { - xfix1 = Dart.prefixLines(xfix1, Dart.INDENT); + xfix1 = generator.prefixLines(xfix1, generator.INDENT); } let loopTrap = ''; - if (Dart.INFINITE_LOOP_TRAP) { - loopTrap = Dart.prefixLines( - Dart.injectId(Dart.INFINITE_LOOP_TRAP, block), Dart.INDENT); + if (generator.INFINITE_LOOP_TRAP) { + loopTrap = generator.prefixLines( + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT); } - const branch = Dart.statementToCode(block, 'STACK'); - let returnValue = Dart.valueToCode(block, 'RETURN', Dart.ORDER_NONE) || ''; + const branch = generator.statementToCode(block, 'STACK'); + let returnValue = + generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. xfix2 = xfix1; } if (returnValue) { - returnValue = Dart.INDENT + 'return ' + returnValue + ';\n'; + returnValue = generator.INDENT + 'return ' + returnValue + ';\n'; } const returnType = returnValue ? 'dynamic' : 'void'; const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = Dart.nameDB_.getName(variables[i], NameType.VARIABLE); + args[i] = generator.nameDB_.getName(variables[i], NameType.VARIABLE); } let code = returnType + ' ' + funcName + '(' + args.join(', ') + ') {\n' + xfix1 + loopTrap + branch + xfix2 + returnValue + '}'; - code = Dart.scrub_(block, code); + code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. - Dart.definitions_['%' + funcName] = code; + generator.definitions_['%' + funcName] = code; return null; }; // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -Dart['procedures_defnoreturn'] = Dart['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -Dart['procedures_callreturn'] = function(block) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = - Dart.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'),NameType.PROCEDURE); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = Dart.valueToCode(block, 'ARG' + i, Dart.ORDER_NONE) || 'null'; + args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'null'; } let code = funcName + '(' + args.join(', ') + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['procedures_callnoreturn'] = function(block) { +export function procedures_callnoreturn(block, generator) { // Call a procedure with no return value. // Generated code is for a function call as a statement is the same as a // function call as a value, with the addition of line ending. - const tuple = Dart['procedures_callreturn'](block); + const tuple = generator.forBlock['procedures_callreturn'](block, generator); return tuple[0] + ';\n'; }; -Dart['procedures_ifreturn'] = function(block) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = - Dart.valueToCode(block, 'CONDITION', Dart.ORDER_NONE) || 'false'; + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; let code = 'if (' + condition + ') {\n'; - if (Dart.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. - code += Dart.prefixLines( - Dart.injectId(Dart.STATEMENT_SUFFIX, block), Dart.INDENT); + code += generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), generator.INDENT); } if (block.hasReturnValue_) { - const value = Dart.valueToCode(block, 'VALUE', Dart.ORDER_NONE) || 'null'; - code += Dart.INDENT + 'return ' + value + ';\n'; + const value = + generator.valueToCode(block, 'VALUE', Order.NONE) || 'null'; + code += generator.INDENT + 'return ' + value + ';\n'; } else { - code += Dart.INDENT + 'return;\n'; + code += generator.INDENT + 'return;\n'; } code += '}\n'; return code; diff --git a/generators/dart/text.js b/generators/dart/text.js index b611f0d8b..a1dcade42 100644 --- a/generators/dart/text.js +++ b/generators/dart/text.js @@ -7,158 +7,162 @@ /** * @fileoverview Generating Dart for text blocks. */ -'use strict'; -goog.module('Blockly.Dart.texts'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.texts'); -const {NameType} = goog.require('Blockly.Names'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {NameType} from '../../core/names.js'; +import {Order} from './dart_generator.js'; -Dart.addReservedWords('Html,Math'); +// RESERVED WORDS: 'Html,Math' -Dart['text'] = function(block) { +export function text(block, generator) { // Text value. - const code = Dart.quote_(block.getFieldValue('TEXT')); - return [code, Dart.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('TEXT')); + return [code, Order.ATOMIC]; }; -Dart['text_multiline'] = function(block) { +export function text_multiline(block, generator) { // Text value. - const code = Dart.multiline_quote_(block.getFieldValue('TEXT')); + const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = - code.indexOf('+') !== -1 ? Dart.ORDER_ADDITIVE : Dart.ORDER_ATOMIC; + code.indexOf('+') !== -1 ? Order.ADDITIVE : Order.ATOMIC; return [code, order]; }; -Dart['text_join'] = function(block) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: - return ["''", Dart.ORDER_ATOMIC]; + return ["''", Order.ATOMIC]; case 1: { const element = - Dart.valueToCode(block, 'ADD0', Dart.ORDER_UNARY_POSTFIX) || "''"; + generator.valueToCode(block, 'ADD0', Order.UNARY_POSTFIX) || "''"; const code = element + '.toString()'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } default: { const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { elements[i] = - Dart.valueToCode(block, 'ADD' + i, Dart.ORDER_NONE) || "''"; + generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''"; } const code = '[' + elements.join(',') + '].join()'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } } }; -Dart['text_append'] = function(block) { +export function text_append(block, generator) { // Append to a variable in place. const varName = - Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const value = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + const value = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return varName + ' = [' + varName + ', ' + value + '].join();\n'; }; -Dart['text_length'] = function(block) { +export function text_length(block, generator) { // String or array length. const text = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || "''"; - return [text + '.length', Dart.ORDER_UNARY_POSTFIX]; + generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || "''"; + return [text + '.length', Order.UNARY_POSTFIX]; }; -Dart['text_isEmpty'] = function(block) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? const text = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || "''"; - return [text + '.isEmpty', Dart.ORDER_UNARY_POSTFIX]; + generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || "''"; + return [text + '.isEmpty', Order.UNARY_POSTFIX]; }; -Dart['text_indexOf'] = function(block) { +export function text_indexOf(block, generator) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; - const substring = Dart.valueToCode(block, 'FIND', Dart.ORDER_NONE) || "''"; + const substring = + generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const text = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_UNARY_POSTFIX) || "''"; + generator.valueToCode(block, 'VALUE', Order.UNARY_POSTFIX) || "''"; const code = text + '.' + operator + '(' + substring + ')'; if (block.workspace.options.oneBasedIndex) { - return [code + ' + 1', Dart.ORDER_ADDITIVE]; + return [code + ' + 1', Order.ADDITIVE]; } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['text_charAt'] = function(block) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; const textOrder = (where === 'FIRST' || where === 'FROM_START') ? - Dart.ORDER_UNARY_POSTFIX : - Dart.ORDER_NONE; - const text = Dart.valueToCode(block, 'VALUE', textOrder) || "''"; + Order.UNARY_POSTFIX : + Order.NONE; + const text = generator.valueToCode(block, 'VALUE', textOrder) || "''"; let at; switch (where) { case 'FIRST': { const code = text + '[0]'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } case 'FROM_START': { - at = Dart.getAdjusted(block, 'AT'); + at = generator.getAdjusted(block, 'AT'); const code = text + '[' + at + ']'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } case 'LAST': at = 1; // Fall through. case 'FROM_END': { - at = Dart.getAdjusted(block, 'AT', 1); - const functionName = Dart.provideFunction_('text_get_from_end', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text, num x) { + at = generator.getAdjusted(block, 'AT', 1); + const functionName = generator.provideFunction_('text_get_from_end', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String text, num x) { return text[text.length - x]; } `); const code = functionName + '(' + text + ', ' + at + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } case 'RANDOM': { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; - const functionName = Dart.provideFunction_('text_random_letter', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text) { + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; + const functionName = + generator.provideFunction_('text_random_letter', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String text) { int x = new Math.Random().nextInt(text.length); return text[x]; } `); const code = functionName + '(' + text + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; } } throw Error('Unhandled option (text_charAt).'); }; -Dart['text_getSubstring'] = function(block) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); const requiresLengthCall = (where1 !== 'FROM_END' && where2 === 'FROM_START'); const textOrder = - requiresLengthCall ? Dart.ORDER_UNARY_POSTFIX : Dart.ORDER_NONE; - const text = Dart.valueToCode(block, 'STRING', textOrder) || "''"; + requiresLengthCall ? Order.UNARY_POSTFIX : Order.NONE; + const text = generator.valueToCode(block, 'STRING', textOrder) || "''"; let code; if (where1 === 'FIRST' && where2 === 'LAST') { code = text; - return [code, Dart.ORDER_NONE]; + return [code, Order.NONE]; } else if (text.match(/^'?\w+'?$/) || requiresLengthCall) { // If the text is a variable or literal or doesn't require a call for // length, don't generate a helper function. let at1; switch (where1) { case 'FROM_START': - at1 = Dart.getAdjusted(block, 'AT1'); + at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = Dart.getAdjusted(block, 'AT1', 1, false, Dart.ORDER_ADDITIVE); + at1 = generator.getAdjusted(block, 'AT1', 1, false, Order.ADDITIVE); at1 = text + '.length - ' + at1; break; case 'FIRST': @@ -170,10 +174,10 @@ Dart['text_getSubstring'] = function(block) { let at2; switch (where2) { case 'FROM_START': - at2 = Dart.getAdjusted(block, 'AT2', 1); + at2 = generator.getAdjusted(block, 'AT2', 1); break; case 'FROM_END': - at2 = Dart.getAdjusted(block, 'AT2', 0, false, Dart.ORDER_ADDITIVE); + at2 = generator.getAdjusted(block, 'AT2', 0, false, Order.ADDITIVE); at2 = text + '.length - ' + at2; break; case 'LAST': @@ -188,10 +192,11 @@ Dart['text_getSubstring'] = function(block) { code = text + '.substring(' + at1 + ', ' + at2 + ')'; } } else { - const at1 = Dart.getAdjusted(block, 'AT1'); - const at2 = Dart.getAdjusted(block, 'AT2'); - const functionName = Dart.provideFunction_('text_get_substring', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text, String where1, num at1, String where2, num at2) { + const at1 = generator.getAdjusted(block, 'AT1'); + const at2 = generator.getAdjusted(block, 'AT2'); + const functionName = + generator.provideFunction_('text_get_substring', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String text, String where1, num at1, String where2, num at2) { int getAt(String where, num at) { if (where == 'FROM_END') { at = text.length - 1 - at; @@ -212,10 +217,10 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String text, String where1, num at1, S code = functionName + '(' + text + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['text_changeCase'] = function(block) { +export function text_changeCase(block, generator) { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.toUpperCase()', @@ -223,16 +228,16 @@ Dart['text_changeCase'] = function(block) { 'TITLECASE': null }; const operator = OPERATORS[block.getFieldValue('CASE')]; - const textOrder = operator ? Dart.ORDER_UNARY_POSTFIX : Dart.ORDER_NONE; - const text = Dart.valueToCode(block, 'TEXT', textOrder) || "''"; + const textOrder = operator ? Order.UNARY_POSTFIX : Order.NONE; + const text = generator.valueToCode(block, 'TEXT', textOrder) || "''"; let code; if (operator) { - // Upper and lower case are functions built into Dart. + // Upper and lower case are functions built into generator. code = text + operator; } else { - // Title case is not a native Dart function. Define one. - const functionName = Dart.provideFunction_('text_toTitleCase', ` -String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String str) { + // Title case is not a native generator function. Define one. + const functionName = generator.provideFunction_('text_toTitleCase', ` +String ${generator.FUNCTION_NAME_PLACEHOLDER_}(String str) { RegExp exp = new RegExp(r'\\b'); List list = str.split(exp); final title = new StringBuffer(); @@ -249,10 +254,10 @@ String ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String str) { `); code = functionName + '(' + text + ')'; } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['text_trim'] = function(block) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = { 'LEFT': '.replaceFirst(new RegExp(r\'^\\s+\'), \'\')', @@ -261,44 +266,46 @@ Dart['text_trim'] = function(block) { }; const operator = OPERATORS[block.getFieldValue('MODE')]; const text = - Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || "''"; - return [text + operator, Dart.ORDER_UNARY_POSTFIX]; + generator.valueToCode(block, 'TEXT', Order.UNARY_POSTFIX) || "''"; + return [text + operator, Order.UNARY_POSTFIX]; }; -Dart['text_print'] = function(block) { +export function text_print(block, generator) { // Print statement. - const msg = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; + const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ');\n'; }; -Dart['text_prompt_ext'] = function(block) { +export function text_prompt_ext(block, generator) { // Prompt function. - Dart.definitions_['import_dart_html'] = 'import \'dart:html\' as Html;'; + generator.definitions_['import_dart_html'] = + 'import \'dart:html\' as Html;'; let msg; if (block.getField('TEXT')) { // Internal message. - msg = Dart.quote_(block.getFieldValue('TEXT')); + msg = generator.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; + msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; } let code = 'Html.window.prompt(' + msg + ', \'\')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; if (toNumber) { - Dart.definitions_['import_dart_math'] = 'import \'dart:math\' as Math;'; + generator.definitions_['import_dart_math'] = + 'import \'dart:math\' as Math;'; code = 'Math.parseDouble(' + code + ')'; } - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['text_prompt'] = Dart['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -Dart['text_count'] = function(block) { - const text = Dart.valueToCode(block, 'TEXT', Dart.ORDER_NONE) || "''"; - const sub = Dart.valueToCode(block, 'SUB', Dart.ORDER_NONE) || "''"; - // Substring count is not a native Dart function. Define one. - const functionName = Dart.provideFunction_('text_count', ` -int ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String haystack, String needle) { +export function text_count(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; + // Substring count is not a native generator function. Define one. + const functionName = generator.provideFunction_('text_count', ` +int ${generator.FUNCTION_NAME_PLACEHOLDER_}(String haystack, String needle) { if (needle.length == 0) { return haystack.length + 1; } @@ -315,24 +322,24 @@ int ${Dart.FUNCTION_NAME_PLACEHOLDER_}(String haystack, String needle) { } `); const code = functionName + '(' + text + ', ' + sub + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['text_replace'] = function(block) { +export function text_replace(block, generator) { const text = - Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || "''"; - const from = Dart.valueToCode(block, 'FROM', Dart.ORDER_NONE) || "''"; - const to = Dart.valueToCode(block, 'TO', Dart.ORDER_NONE) || "''"; + generator.valueToCode(block, 'TEXT', Order.UNARY_POSTFIX) || "''"; + const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; + const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; const code = text + '.replaceAll(' + from + ', ' + to + ')'; - return [code, Dart.ORDER_UNARY_POSTFIX]; + return [code, Order.UNARY_POSTFIX]; }; -Dart['text_reverse'] = function(block) { - // There isn't a sensible way to do this in Dart. See: +export function text_reverse(block, generator) { + // There isn't a sensible way to do this in generator. See: // http://stackoverflow.com/a/21613700/3529104 // Implementing something is possibly better than not implementing anything? const text = - Dart.valueToCode(block, 'TEXT', Dart.ORDER_UNARY_POSTFIX) || "''"; + generator.valueToCode(block, 'TEXT', Order.UNARY_POSTFIX) || "''"; const code = 'new String.fromCharCodes(' + text + '.runes.toList().reversed)'; - return [code, Dart.ORDER_UNARY_PREFIX]; + return [code, Order.UNARY_PREFIX]; }; diff --git a/generators/dart/variables.js b/generators/dart/variables.js index 33e314764..2f628e39c 100644 --- a/generators/dart/variables.js +++ b/generators/dart/variables.js @@ -7,26 +7,28 @@ /** * @fileoverview Generating Dart for variable blocks. */ -'use strict'; -goog.module('Blockly.Dart.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.variables'); -const {NameType} = goog.require('Blockly.Names'); -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); +import {NameType} from '../../core/names.js'; +import {Order} from './dart_generator.js'; -Dart['variables_get'] = function(block) { +export function variables_get(block, generator) { // Variable getter. const code = - Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - return [code, Dart.ORDER_ATOMIC]; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + return [code, Order.ATOMIC]; }; -Dart['variables_set'] = function(block) { +export function variables_set(block, generator) { // Variable setter. const argument0 = - Dart.valueToCode(block, 'VALUE', Dart.ORDER_ASSIGNMENT) || '0'; + generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0'; const varName = - Dart.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = ' + argument0 + ';\n'; }; diff --git a/generators/dart/variables_dynamic.js b/generators/dart/variables_dynamic.js index 50bfdb620..00c2c3cd7 100644 --- a/generators/dart/variables_dynamic.js +++ b/generators/dart/variables_dynamic.js @@ -7,15 +7,13 @@ /** * @fileoverview Generating Dart for dynamic variable blocks. */ -'use strict'; -goog.module('Blockly.Dart.variablesDynamic'); - -const {dartGenerator: Dart} = goog.require('Blockly.Dart'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Dart.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Dart.variablesDynamic'); -// Dart is dynamically typed. -Dart['variables_get_dynamic'] = Dart['variables_get']; -Dart['variables_set_dynamic'] = Dart['variables_set']; +// generator is dynamically typed. +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/javascript.js b/generators/javascript.js index c3e6aa90e..ec877c364 100644 --- a/generators/javascript.js +++ b/generators/javascript.js @@ -1,321 +1,40 @@ /** * @license - * Copyright 2012 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating JavaScript for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating JavaScript for + * blocks. This is the entrypoint for javascript_compressed.js. + * @suppress {extraRequire} */ -'use strict'; -goog.module('Blockly.JavaScript'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.all'); -const Variables = goog.require('Blockly.Variables'); -const stringUtils = goog.require('Blockly.utils.string'); -const {Block} = goog.requireType('Blockly.Block'); -const {CodeGenerator} = goog.require('Blockly.CodeGenerator'); -const {inputTypes} = goog.require('Blockly.inputTypes'); -const {Names, NameType} = goog.require('Blockly.Names'); -const {Workspace} = goog.requireType('Blockly.Workspace'); +import {JavascriptGenerator} from './javascript/javascript_generator.js'; +import * as colour from './javascript/colour.js'; +import * as lists from './javascript/lists.js'; +import * as logic from './javascript/logic.js'; +import * as loops from './javascript/loops.js'; +import * as math from './javascript/math.js'; +import * as procedures from './javascript/procedures.js'; +import * as text from './javascript/text.js'; +import * as variables from './javascript/variables.js'; +import * as variablesDynamic from './javascript/variables_dynamic.js'; +export * from './javascript/javascript_generator.js'; /** - * JavaScript code generator. - * @type {!CodeGenerator} + * JavaScript code generator instance. + * @type {!JavascriptGenerator} */ -const JavaScript = new CodeGenerator('JavaScript'); +export const javascriptGenerator = new JavascriptGenerator(); -/** - * List of illegal variable names. - * This is not intended to be a security feature. Blockly is 100% client-side, - * so bypassing this list is trivial. This is intended to prevent users from - * accidentally clobbering a built-in object or function. - */ -JavaScript.addReservedWords( - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords - 'break,case,catch,class,const,continue,debugger,default,delete,do,else,export,extends,finally,for,function,if,import,in,instanceof,new,return,super,switch,this,throw,try,typeof,var,void,while,with,yield,' + - 'enum,' + - 'implements,interface,let,package,private,protected,public,static,' + - 'await,' + - 'null,true,false,' + - // Magic variable. - 'arguments,' + - // Everything in the current environment (835 items in Chrome, 104 in Node). - Object.getOwnPropertyNames(globalThis).join(',')); - -/** - * Order of operation ENUMs. - * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence - */ -JavaScript.ORDER_ATOMIC = 0; // 0 "" ... -JavaScript.ORDER_NEW = 1.1; // new -JavaScript.ORDER_MEMBER = 1.2; // . [] -JavaScript.ORDER_FUNCTION_CALL = 2; // () -JavaScript.ORDER_INCREMENT = 3; // ++ -JavaScript.ORDER_DECREMENT = 3; // -- -JavaScript.ORDER_BITWISE_NOT = 4.1; // ~ -JavaScript.ORDER_UNARY_PLUS = 4.2; // + -JavaScript.ORDER_UNARY_NEGATION = 4.3; // - -JavaScript.ORDER_LOGICAL_NOT = 4.4; // ! -JavaScript.ORDER_TYPEOF = 4.5; // typeof -JavaScript.ORDER_VOID = 4.6; // void -JavaScript.ORDER_DELETE = 4.7; // delete -JavaScript.ORDER_AWAIT = 4.8; // await -JavaScript.ORDER_EXPONENTIATION = 5.0; // ** -JavaScript.ORDER_MULTIPLICATION = 5.1; // * -JavaScript.ORDER_DIVISION = 5.2; // / -JavaScript.ORDER_MODULUS = 5.3; // % -JavaScript.ORDER_SUBTRACTION = 6.1; // - -JavaScript.ORDER_ADDITION = 6.2; // + -JavaScript.ORDER_BITWISE_SHIFT = 7; // << >> >>> -JavaScript.ORDER_RELATIONAL = 8; // < <= > >= -JavaScript.ORDER_IN = 8; // in -JavaScript.ORDER_INSTANCEOF = 8; // instanceof -JavaScript.ORDER_EQUALITY = 9; // == != === !== -JavaScript.ORDER_BITWISE_AND = 10; // & -JavaScript.ORDER_BITWISE_XOR = 11; // ^ -JavaScript.ORDER_BITWISE_OR = 12; // | -JavaScript.ORDER_LOGICAL_AND = 13; // && -JavaScript.ORDER_LOGICAL_OR = 14; // || -JavaScript.ORDER_CONDITIONAL = 15; // ?: -JavaScript.ORDER_ASSIGNMENT = 16; // = += -= **= *= /= %= <<= >>= ... -JavaScript.ORDER_YIELD = 17; // yield -JavaScript.ORDER_COMMA = 18; // , -JavaScript.ORDER_NONE = 99; // (...) - -/** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ -JavaScript.ORDER_OVERRIDES = [ - // (foo()).bar -> foo().bar - // (foo())[0] -> foo()[0] - [JavaScript.ORDER_FUNCTION_CALL, JavaScript.ORDER_MEMBER], - // (foo())() -> foo()() - [JavaScript.ORDER_FUNCTION_CALL, JavaScript.ORDER_FUNCTION_CALL], - // (foo.bar).baz -> foo.bar.baz - // (foo.bar)[0] -> foo.bar[0] - // (foo[0]).bar -> foo[0].bar - // (foo[0])[1] -> foo[0][1] - [JavaScript.ORDER_MEMBER, JavaScript.ORDER_MEMBER], - // (foo.bar)() -> foo.bar() - // (foo[0])() -> foo[0]() - [JavaScript.ORDER_MEMBER, JavaScript.ORDER_FUNCTION_CALL], - - // !(!foo) -> !!foo - [JavaScript.ORDER_LOGICAL_NOT, JavaScript.ORDER_LOGICAL_NOT], - // a * (b * c) -> a * b * c - [JavaScript.ORDER_MULTIPLICATION, JavaScript.ORDER_MULTIPLICATION], - // a + (b + c) -> a + b + c - [JavaScript.ORDER_ADDITION, JavaScript.ORDER_ADDITION], - // a && (b && c) -> a && b && c - [JavaScript.ORDER_LOGICAL_AND, JavaScript.ORDER_LOGICAL_AND], - // a || (b || c) -> a || b || c - [JavaScript.ORDER_LOGICAL_OR, JavaScript.ORDER_LOGICAL_OR] -]; - -/** - * Whether the init method has been called. - * @type {?boolean} - */ -JavaScript.isInitialized = false; - -/** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ -JavaScript.init = function(workspace) { - // Call Blockly.CodeGenerator's init. - Object.getPrototypeOf(this).init.call(this); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - const defvars = []; - // Add developer variables (not created or named by the user). - const devVarList = Variables.allDeveloperVariables(workspace); - for (let i = 0; i < devVarList.length; i++) { - defvars.push( - this.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); - } - - // Add user variables, but only ones that are being used. - const variables = Variables.allUsedVarModels(workspace); - for (let i = 0; i < variables.length; i++) { - defvars.push(this.nameDB_.getName(variables[i].getId(), NameType.VARIABLE)); - } - - // Declare all of the variables. - if (defvars.length) { - this.definitions_['variables'] = 'var ' + defvars.join(', ') + ';'; - } - this.isInitialized = true; -}; - -/** - * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ -JavaScript.finish = function(code) { - // Convert the definitions dictionary into a list. - const definitions = Object.values(this.definitions_); - // Call Blockly.CodeGenerator's finish. - code = Object.getPrototypeOf(this).finish.call(this, code); - this.isInitialized = false; - - this.nameDB_.reset(); - return definitions.join('\n\n') + '\n\n\n' + code; -}; - -/** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ -JavaScript.scrubNakedValue = function(line) { - return line + ';\n'; -}; - -/** - * Encode a string as a properly escaped JavaScript string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} JavaScript string. - * @protected - */ -JavaScript.quote_ = function(string) { - // Can't use goog.string.quote since Google's style guide recommends - // JS string literals use single quotes. - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; -}; - -/** - * Encode a string as a properly escaped multiline JavaScript string, complete - * with quotes. - * @param {string} string Text to encode. - * @return {string} JavaScript string. - * @protected - */ -JavaScript.multiline_quote_ = function(string) { - // Can't use goog.string.quote since Google's style guide recommends - // JS string literals use single quotes. - const lines = string.split(/\n/g).map(this.quote_); - return lines.join(' + \'\\n\' +\n'); -}; - -/** - * Common tasks for generating JavaScript from blocks. - * Handles comments for the specified block and any connected value blocks. - * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The JavaScript code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} JavaScript code with comments and subsequent blocks added. - * @protected - */ -JavaScript.scrub_ = function(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment + '\n', '// '); - } - // Collect comments for all value arguments. - // Don't collect comments for nested statements. - for (let i = 0; i < block.inputList.length; i++) { - if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '// '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; -}; - -/** - * Gets a property and adjusts the value while taking into account indexing. - * @param {!Block} block The block. - * @param {string} atId The property ID of the element to get. - * @param {number=} opt_delta Value to add. - * @param {boolean=} opt_negate Whether to negate the value. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} - */ -JavaScript.getAdjusted = function( - block, atId, opt_delta, opt_negate, opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - - let innerOrder; - let outerOrder = order; - if (delta > 0) { - outerOrder = this.ORDER_ADDITION; - innerOrder = this.ORDER_ADDITION; - } else if (delta < 0) { - outerOrder = this.ORDER_SUBTRACTION; - innerOrder = this.ORDER_SUBTRACTION; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_NEGATION; - innerOrder = this.ORDER_UNARY_NEGATION; - } - - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = Number(at) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (opt_negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } - } - return at; -}; - -exports.javascriptGenerator = JavaScript; +// Install per-block-type generator functions: +Object.assign( + javascriptGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/javascript/all.js b/generators/javascript/all.js deleted file mode 100644 index 1ffbd3de8..000000000 --- a/generators/javascript/all.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating JavaScript for - * blocks. This is the entrypoint for javascript_compressed.js. - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.JavaScript.all'); - -const moduleExports = goog.require('Blockly.JavaScript'); -goog.require('Blockly.JavaScript.colour'); -goog.require('Blockly.JavaScript.lists'); -goog.require('Blockly.JavaScript.logic'); -goog.require('Blockly.JavaScript.loops'); -goog.require('Blockly.JavaScript.math'); -goog.require('Blockly.JavaScript.procedures'); -goog.require('Blockly.JavaScript.texts'); -goog.require('Blockly.JavaScript.variables'); -goog.require('Blockly.JavaScript.variablesDynamic'); - -exports = moduleExports; diff --git a/generators/javascript/colour.js b/generators/javascript/colour.js index 944a4bb1a..d8976d3e9 100644 --- a/generators/javascript/colour.js +++ b/generators/javascript/colour.js @@ -7,40 +7,40 @@ /** * @fileoverview Generating JavaScript for colour blocks. */ -'use strict'; -goog.module('Blockly.JavaScript.colour'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.colour'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import {Order} from './javascript_generator.js'; -JavaScript['colour_picker'] = function(block) { +export function colour_picker(block, generator) { // Colour picker. - const code = JavaScript.quote_(block.getFieldValue('COLOUR')); - return [code, JavaScript.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('COLOUR')); + return [code, Order.ATOMIC]; }; -JavaScript['colour_random'] = function(block) { +export function colour_random(block, generator) { // Generate a random colour. - const functionName = JavaScript.provideFunction_('colourRandom', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}() { + const functionName = generator.provideFunction_('colourRandom', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { var num = Math.floor(Math.random() * Math.pow(2, 24)); return '#' + ('00000' + num.toString(16)).substr(-6); } `); const code = functionName + '()'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['colour_rgb'] = function(block) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. - const red = JavaScript.valueToCode(block, 'RED', JavaScript.ORDER_NONE) || 0; + const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; const green = - JavaScript.valueToCode(block, 'GREEN', JavaScript.ORDER_NONE) || 0; + generator.valueToCode(block, 'GREEN', Order.NONE) || 0; const blue = - JavaScript.valueToCode(block, 'BLUE', JavaScript.ORDER_NONE) || 0; - const functionName = JavaScript.provideFunction_('colourRgb', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { + generator.valueToCode(block, 'BLUE', Order.NONE) || 0; + const functionName = generator.provideFunction_('colourRgb', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { r = Math.max(Math.min(Number(r), 100), 0) * 2.55; g = Math.max(Math.min(Number(g), 100), 0) * 2.55; b = Math.max(Math.min(Number(b), 100), 0) * 2.55; @@ -51,19 +51,19 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) { } `); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['colour_blend'] = function(block) { +export function colour_blend(block, generator) { // Blend two colours together. - const c1 = JavaScript.valueToCode(block, 'COLOUR1', JavaScript.ORDER_NONE) || + const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; - const c2 = JavaScript.valueToCode(block, 'COLOUR2', JavaScript.ORDER_NONE) || + const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; const ratio = - JavaScript.valueToCode(block, 'RATIO', JavaScript.ORDER_NONE) || 0.5; - const functionName = JavaScript.provideFunction_('colourBlend', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) { + generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; + const functionName = generator.provideFunction_('colourBlend', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) { ratio = Math.max(Math.min(Number(ratio), 1), 0); var r1 = parseInt(c1.substring(1, 3), 16); var g1 = parseInt(c1.substring(3, 5), 16); @@ -81,5 +81,5 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(c1, c2, ratio) { } `); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/javascript/javascript_generator.js b/generators/javascript/javascript_generator.js new file mode 100644 index 000000000..18811011c --- /dev/null +++ b/generators/javascript/javascript_generator.js @@ -0,0 +1,335 @@ +/** + * @license + * Copyright 2012 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating JavaScript for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript'); + +import * as Variables from '../../core/variables.js'; +import * as stringUtils from '../../core/utils/string.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names, NameType} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + NEW: 1.1, // new + MEMBER: 1.2, // . [] + FUNCTION_CALL: 2, // () + INCREMENT: 3, // ++ + DECREMENT: 3, // -- + BITWISE_NOT: 4.1, // ~ + UNARY_PLUS: 4.2, // + + UNARY_NEGATION: 4.3, // - + LOGICAL_NOT: 4.4, // ! + TYPEOF: 4.5, // typeof + VOID: 4.6, // void + DELETE: 4.7, // delete + AWAIT: 4.8, // await + EXPONENTIATION: 5.0, // ** + MULTIPLICATION: 5.1, // * + DIVISION: 5.2, // / + MODULUS: 5.3, // % + SUBTRACTION: 6.1, // - + ADDITION: 6.2, // + + BITWISE_SHIFT: 7, // << >> >>> + RELATIONAL: 8, // < <= > >= + IN: 8, // in + INSTANCEOF: 8, // instanceof + EQUALITY: 9, // == != === !== + BITWISE_AND: 10, // & + BITWISE_XOR: 11, // ^ + BITWISE_OR: 12, // | + LOGICAL_AND: 13, // && + LOGICAL_OR: 14, // || + CONDITIONAL: 15, // ?: + ASSIGNMENT: 16, //: += -= **= *= /= %= <<= >>= ... + YIELD: 17, // yield + COMMA: 18, // , + NONE: 99, // (...) +}; + +/** + * JavaScript code generator class. + */ +export class JavascriptGenerator extends CodeGenerator { + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + ORDER_OVERRIDES = [ + // (foo()).bar -> foo().bar + // (foo())[0] -> foo()[0] + [Order.FUNCTION_CALL, Order.MEMBER], + // (foo())() -> foo()() + [Order.FUNCTION_CALL, Order.FUNCTION_CALL], + // (foo.bar).baz -> foo.bar.baz + // (foo.bar)[0] -> foo.bar[0] + // (foo[0]).bar -> foo[0].bar + // (foo[0])[1] -> foo[0][1] + [Order.MEMBER, Order.MEMBER], + // (foo.bar)() -> foo.bar() + // (foo[0])() -> foo[0]() + [Order.MEMBER, Order.FUNCTION_CALL], + + // !(!foo) -> !!foo + [Order.LOGICAL_NOT, Order.LOGICAL_NOT], + // a * (b * c) -> a * b * c + [Order.MULTIPLICATION, Order.MULTIPLICATION], + // a + (b + c) -> a + b + c + [Order.ADDITION, Order.ADDITION], + // a && (b && c) -> a && b && c + [Order.LOGICAL_AND, Order.LOGICAL_AND], + // a || (b || c) -> a || b || c + [Order.LOGICAL_OR, Order.LOGICAL_OR] + ]; + + constructor(name) { + super(name ?? 'JavaScript'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords + 'break,case,catch,class,const,continue,debugger,default,delete,do,' + + 'else,export,extends,finally,for,function,if,import,in,instanceof,' + + 'new,return,super,switch,this,throw,try,typeof,var,void,' + + 'while,with,yield,' + + 'enum,' + + 'implements,interface,let,package,private,protected,public,static,' + + 'await,' + + 'null,true,false,' + + // Magic variable. + 'arguments,' + + // Everything in the current environment (835 items in Chrome, + // 104 in Node). + Object.getOwnPropertyNames(globalThis).join(',') + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + super.init(workspace); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + const defvars = []; + // Add developer variables (not created or named by the user). + const devVarList = Variables.allDeveloperVariables(workspace); + for (let i = 0; i < devVarList.length; i++) { + defvars.push( + this.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); + } + + // Add user variables, but only ones that are being used. + const variables = Variables.allUsedVarModels(workspace); + for (let i = 0; i < variables.length; i++) { + defvars.push( + this.nameDB_.getName(variables[i].getId(), NameType.VARIABLE)); + } + + // Declare all of the variables. + if (defvars.length) { + this.definitions_['variables'] = 'var ' + defvars.join(', ') + ';'; + } + this.isInitialized = true; + } + + /** + * Prepend the generated code with the variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const definitions = Object.values(this.definitions_); + // Call Blockly.CodeGenerator's finish. + super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + return definitions.join('\n\n') + '\n\n\n' + code; + } + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. A trailing semicolon is needed to make this legal. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + ';\n'; + } + + /** + * Encode a string as a properly escaped JavaScript string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} JavaScript string. + * @protected + */ + quote_(string) { + // Can't use goog.string.quote since Google's style guide recommends + // JS string literals use single quotes. + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + } + + /** + * Encode a string as a properly escaped multiline JavaScript string, complete + * with quotes. + * @param {string} string Text to encode. + * @return {string} JavaScript string. + * @protected + */ + multiline_quote_(string) { + // Can't use goog.string.quote since Google's style guide recommends + // JS string literals use single quotes. + const lines = string.split(/\n/g).map(this.quote_); + return lines.join(' + \'\\n\' +\n'); + } + + /** + * Common tasks for generating JavaScript from blocks. + * Handles comments for the specified block and any connected value blocks. + * Calls any statements following this block. + * @param {!Block} block The current block. + * @param {string} code The JavaScript code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string} JavaScript code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment + '\n', '// '); + } + // Collect comments for all value arguments. + // Don't collect comments for nested statements. + for (let i = 0; i < block.inputList.length; i++) { + if (block.inputList[i].type === inputTypes.VALUE) { + const childBlock = block.inputList[i].connection.targetBlock(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '// '); + } + } + } + } + } + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + } + + /** + * Gets a property and adjusts the value while taking into account indexing. + * @param {!Block} block The block. + * @param {string} atId The property ID of the element to get. + * @param {number=} opt_delta Value to add. + * @param {boolean=} opt_negate Whether to negate the value. + * @param {number=} opt_order The highest order acting on this value. + * @return {string|number} + */ + getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { + let delta = opt_delta || 0; + let order = opt_order || this.ORDER_NONE; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + + let innerOrder; + let outerOrder = order; + if (delta > 0) { + outerOrder = this.ORDER_ADDITION; + innerOrder = this.ORDER_ADDITION; + } else if (delta < 0) { + outerOrder = this.ORDER_SUBTRACTION; + innerOrder = this.ORDER_SUBTRACTION; + } else if (opt_negate) { + outerOrder = this.ORDER_UNARY_NEGATION; + innerOrder = this.ORDER_UNARY_NEGATION; + } + + let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = Number(at) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = at + ' + ' + delta; + } else if (delta < 0) { + at = at + ' - ' + -delta; + } + if (opt_negate) { + if (delta) { + at = '-(' + at + ')'; + } else { + at = '-' + at; + } + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (innerOrder && order >= innerOrder) { + at = '(' + at + ')'; + } + } + return at; + } +} diff --git a/generators/javascript/lists.js b/generators/javascript/lists.js index c2845d439..3106906b0 100644 --- a/generators/javascript/lists.js +++ b/generators/javascript/lists.js @@ -8,35 +8,35 @@ * @fileoverview Generating JavaScript for list blocks. * @suppress {missingRequire} */ -'use strict'; -goog.module('Blockly.JavaScript.lists'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.lists'); -const {NameType} = goog.require('Blockly.Names'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import {NameType} from '../../core/names.js'; +import {Order} from './javascript_generator.js'; -JavaScript['lists_create_empty'] = function(block) { +export function lists_create_empty(block, generator) { // Create an empty list. - return ['[]', JavaScript.ORDER_ATOMIC]; + return ['[]', Order.ATOMIC]; }; -JavaScript['lists_create_with'] = function(block) { +export function lists_create_with(block, generator) { // Create a list with any number of elements of any type. const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { elements[i] = - JavaScript.valueToCode(block, 'ADD' + i, JavaScript.ORDER_NONE) || + generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null'; } const code = '[' + elements.join(', ') + ']'; - return [code, JavaScript.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -JavaScript['lists_repeat'] = function(block) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. - const functionName = JavaScript.provideFunction_('listsRepeat', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(value, n) { + const functionName = generator.provideFunction_('listsRepeat', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(value, n) { var array = []; for (var i = 0; i < n; i++) { array[i] = value; @@ -45,59 +45,60 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(value, n) { } `); const element = - JavaScript.valueToCode(block, 'ITEM', JavaScript.ORDER_NONE) || 'null'; + generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; const repeatCount = - JavaScript.valueToCode(block, 'NUM', JavaScript.ORDER_NONE) || '0'; + generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const code = functionName + '(' + element + ', ' + repeatCount + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['lists_length'] = function(block) { +export function lists_length(block, generator) { // String or array length. const list = - JavaScript.valueToCode(block, 'VALUE', JavaScript.ORDER_MEMBER) || '[]'; - return [list + '.length', JavaScript.ORDER_MEMBER]; + generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; + return [list + '.length', Order.MEMBER]; }; -JavaScript['lists_isEmpty'] = function(block) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const list = - JavaScript.valueToCode(block, 'VALUE', JavaScript.ORDER_MEMBER) || '[]'; - return ['!' + list + '.length', JavaScript.ORDER_LOGICAL_NOT]; + generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; + return ['!' + list + '.length', Order.LOGICAL_NOT]; }; -JavaScript['lists_indexOf'] = function(block) { +export function lists_indexOf(block, generator) { // Find an item in the list. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; const item = - JavaScript.valueToCode(block, 'FIND', JavaScript.ORDER_NONE) || "''"; + generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const list = - JavaScript.valueToCode(block, 'VALUE', JavaScript.ORDER_MEMBER) || '[]'; + generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; const code = list + '.' + operator + '(' + item + ')'; if (block.workspace.options.oneBasedIndex) { - return [code + ' + 1', JavaScript.ORDER_ADDITION]; + return [code + ' + 1', Order.ADDITION]; } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['lists_getIndex'] = function(block) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; const listOrder = - (where === 'RANDOM') ? JavaScript.ORDER_NONE : JavaScript.ORDER_MEMBER; - const list = JavaScript.valueToCode(block, 'VALUE', listOrder) || '[]'; + (where === 'RANDOM') ? Order.NONE : Order.MEMBER; + const list = + generator.valueToCode(block, 'VALUE', listOrder) || '[]'; switch (where) { case ('FIRST'): if (mode === 'GET') { const code = list + '[0]'; - return [code, JavaScript.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.shift()'; - return [code, JavaScript.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'REMOVE') { return list + '.shift();\n'; } @@ -105,43 +106,44 @@ JavaScript['lists_getIndex'] = function(block) { case ('LAST'): if (mode === 'GET') { const code = list + '.slice(-1)[0]'; - return [code, JavaScript.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.pop()'; - return [code, JavaScript.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'REMOVE') { return list + '.pop();\n'; } break; case ('FROM_START'): { - const at = JavaScript.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); if (mode === 'GET') { const code = list + '[' + at + ']'; - return [code, JavaScript.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.splice(' + at + ', 1)[0]'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return list + '.splice(' + at + ', 1);\n'; } break; } case ('FROM_END'): { - const at = JavaScript.getAdjusted(block, 'AT', 1, true); + const at = generator.getAdjusted(block, 'AT', 1, true); if (mode === 'GET') { const code = list + '.slice(' + at + ')[0]'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'GET_REMOVE') { const code = list + '.splice(' + at + ', 1)[0]'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return list + '.splice(' + at + ', 1);'; } break; } case ('RANDOM'): { - const functionName = JavaScript.provideFunction_('listsGetRandomItem', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { + const functionName = + generator.provideFunction_('listsGetRandomItem', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { var x = Math.floor(Math.random() * list.length); if (remove) { return list.splice(x, 1)[0]; @@ -152,7 +154,7 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { `); const code = functionName + '(' + list + ', ' + (mode !== 'GET') + ')'; if (mode === 'GET' || mode === 'GET_REMOVE') { - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return code + ';\n'; } @@ -162,15 +164,15 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(list, remove) { throw Error('Unhandled combination (lists_getIndex).'); }; -JavaScript['lists_setIndex'] = function(block) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. let list = - JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_MEMBER) || '[]'; + generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; const value = - JavaScript.valueToCode(block, 'TO', JavaScript.ORDER_ASSIGNMENT) || + generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null'; // Cache non-trivial values to variables to prevent repeated look-ups. // Closure, which accesses and modifies 'list'. @@ -179,7 +181,8 @@ JavaScript['lists_setIndex'] = function(block) { return ''; } const listVar = - JavaScript.nameDB_.getDistinctName('tmpList', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + 'tmpList', NameType.VARIABLE); const code = 'var ' + listVar + ' = ' + list + ';\n'; list = listVar; return code; @@ -202,7 +205,7 @@ JavaScript['lists_setIndex'] = function(block) { } break; case ('FROM_START'): { - const at = JavaScript.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); if (mode === 'SET') { return list + '[' + at + '] = ' + value + ';\n'; } else if (mode === 'INSERT') { @@ -211,8 +214,8 @@ JavaScript['lists_setIndex'] = function(block) { break; } case ('FROM_END'): { - const at = JavaScript.getAdjusted( - block, 'AT', 1, false, JavaScript.ORDER_SUBTRACTION); + const at = generator.getAdjusted( + block, 'AT', 1, false, Order.SUBTRACTION); let code = cacheList(); if (mode === 'SET') { code += list + '[' + list + '.length - ' + at + '] = ' + value + ';\n'; @@ -227,7 +230,8 @@ JavaScript['lists_setIndex'] = function(block) { case ('RANDOM'): { let code = cacheList(); const xVar = - JavaScript.nameDB_.getDistinctName('tmpX', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + 'tmpX', NameType.VARIABLE); code += 'var ' + xVar + ' = Math.floor(Math.random() * ' + list + '.length);\n'; if (mode === 'SET') { @@ -262,10 +266,10 @@ const getSubstringIndex = function(listName, where, opt_at) { } }; -JavaScript['lists_getSublist'] = function(block) { +export function lists_getSublist(block, generator) { // Get sublist. const list = - JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_MEMBER) || '[]'; + generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); let code; @@ -279,11 +283,11 @@ JavaScript['lists_getSublist'] = function(block) { let at1; switch (where1) { case 'FROM_START': - at1 = JavaScript.getAdjusted(block, 'AT1'); + at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = JavaScript.getAdjusted( - block, 'AT1', 1, false, JavaScript.ORDER_SUBTRACTION); + at1 = generator.getAdjusted( + block, 'AT1', 1, false, Order.SUBTRACTION); at1 = list + '.length - ' + at1; break; case 'FIRST': @@ -295,11 +299,11 @@ JavaScript['lists_getSublist'] = function(block) { let at2; switch (where2) { case 'FROM_START': - at2 = JavaScript.getAdjusted(block, 'AT2', 1); + at2 = generator.getAdjusted(block, 'AT2', 1); break; case 'FROM_END': - at2 = JavaScript.getAdjusted( - block, 'AT2', 0, false, JavaScript.ORDER_SUBTRACTION); + at2 = generator.getAdjusted( + block, 'AT2', 0, false, Order.SUBTRACTION); at2 = list + '.length - ' + at2; break; case 'LAST': @@ -310,8 +314,8 @@ JavaScript['lists_getSublist'] = function(block) { } code = list + '.slice(' + at1 + ', ' + at2 + ')'; } else { - const at1 = JavaScript.getAdjusted(block, 'AT1'); - const at2 = JavaScript.getAdjusted(block, 'AT2'); + const at1 = generator.getAdjusted(block, 'AT1'); + const at2 = generator.getAdjusted(block, 'AT2'); const wherePascalCase = { 'FIRST': 'First', 'LAST': 'Last', @@ -324,9 +328,9 @@ JavaScript['lists_getSublist'] = function(block) { (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; const at2Param = (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; - const functionName = JavaScript.provideFunction_( + const functionName = generator.provideFunction_( 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { var start = ${getSubstringIndex('sequence', where1, 'at1')}; var end = ${getSubstringIndex('sequence', where2, 'at2')} + 1; return sequence.slice(start, end); @@ -339,19 +343,19 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param} ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', ' + at2 : '') + ')'; } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['lists_sort'] = function(block) { +export function lists_sort(block, generator) { // Block for sorting a list. const list = - JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_FUNCTION_CALL) || + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || '[]'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); const getCompareFunctionName = - JavaScript.provideFunction_('listsGetSortCompare', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { + generator.provideFunction_('listsGetSortCompare', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { var compareFuncs = { 'NUMERIC': function(a, b) { return Number(a) - Number(b); }, @@ -367,15 +371,15 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(type, direction) { return [ list + '.slice().sort(' + getCompareFunctionName + '("' + type + '", ' + direction + '))', - JavaScript.ORDER_FUNCTION_CALL + Order.FUNCTION_CALL ]; }; -JavaScript['lists_split'] = function(block) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. - let input = JavaScript.valueToCode(block, 'INPUT', JavaScript.ORDER_MEMBER); + let input = generator.valueToCode(block, 'INPUT', Order.MEMBER); const delimiter = - JavaScript.valueToCode(block, 'DELIM', JavaScript.ORDER_NONE) || "''"; + generator.valueToCode(block, 'DELIM', Order.NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { @@ -392,14 +396,14 @@ JavaScript['lists_split'] = function(block) { throw Error('Unknown mode: ' + mode); } const code = input + '.' + functionName + '(' + delimiter + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['lists_reverse'] = function(block) { +export function lists_reverse(block, generator) { // Block for reversing a list. const list = - JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_FUNCTION_CALL) || + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) || '[]'; const code = list + '.slice().reverse()'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/javascript/logic.js b/generators/javascript/logic.js index 0933b0d7c..8cbdc1b5e 100644 --- a/generators/javascript/logic.js +++ b/generators/javascript/logic.js @@ -7,30 +7,32 @@ /** * @fileoverview Generating JavaScript for logic blocks. */ -'use strict'; -goog.module('Blockly.JavaScript.logic'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.logic'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import {Order} from './javascript_generator.js'; -JavaScript['controls_if'] = function(block) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = ''; - if (JavaScript.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - code += JavaScript.injectId(JavaScript.STATEMENT_PREFIX, block); + code += generator.injectId( + generator.STATEMENT_PREFIX, block); } do { const conditionCode = - JavaScript.valueToCode(block, 'IF' + n, JavaScript.ORDER_NONE) || + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false'; - let branchCode = JavaScript.statementToCode(block, 'DO' + n); - if (JavaScript.STATEMENT_SUFFIX) { - branchCode = JavaScript.prefixLines( - JavaScript.injectId(JavaScript.STATEMENT_SUFFIX, block), - JavaScript.INDENT) + + let branchCode = generator.statementToCode(block, 'DO' + n); + if (generator.STATEMENT_SUFFIX) { + branchCode = generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += (n > 0 ? ' else ' : '') + 'if (' + conditionCode + ') {\n' + @@ -38,12 +40,13 @@ JavaScript['controls_if'] = function(block) { n++; } while (block.getInput('IF' + n)); - if (block.getInput('ELSE') || JavaScript.STATEMENT_SUFFIX) { - let branchCode = JavaScript.statementToCode(block, 'ELSE'); - if (JavaScript.STATEMENT_SUFFIX) { - branchCode = JavaScript.prefixLines( - JavaScript.injectId(JavaScript.STATEMENT_SUFFIX, block), - JavaScript.INDENT) + + if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { + let branchCode = generator.statementToCode(block, 'ELSE'); + if (generator.STATEMENT_SUFFIX) { + branchCode = generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += ' else {\n' + branchCode + '}'; @@ -51,29 +54,29 @@ JavaScript['controls_if'] = function(block) { return code + '\n'; }; -JavaScript['controls_ifelse'] = JavaScript['controls_if']; +export const controls_ifelse = controls_if; -JavaScript['logic_compare'] = function(block) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; const operator = OPERATORS[block.getFieldValue('OP')]; const order = (operator === '==' || operator === '!=') ? - JavaScript.ORDER_EQUALITY : - JavaScript.ORDER_RELATIONAL; - const argument0 = JavaScript.valueToCode(block, 'A', order) || '0'; - const argument1 = JavaScript.valueToCode(block, 'B', order) || '0'; + Order.EQUALITY : + Order.RELATIONAL; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; }; -JavaScript['logic_operation'] = function(block) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; - const order = (operator === '&&') ? JavaScript.ORDER_LOGICAL_AND : - JavaScript.ORDER_LOGICAL_OR; - let argument0 = JavaScript.valueToCode(block, 'A', order); - let argument1 = JavaScript.valueToCode(block, 'B', order); + const order = (operator === '&&') ? Order.LOGICAL_AND : + Order.LOGICAL_OR; + let argument0 = generator.valueToCode(block, 'A', order); + let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { // If there are no arguments, then the return value is false. argument0 = 'false'; @@ -92,36 +95,37 @@ JavaScript['logic_operation'] = function(block) { return [code, order]; }; -JavaScript['logic_negate'] = function(block) { +export function logic_negate(block, generator) { // Negation. - const order = JavaScript.ORDER_LOGICAL_NOT; - const argument0 = JavaScript.valueToCode(block, 'BOOL', order) || 'true'; + const order = Order.LOGICAL_NOT; + const argument0 = + generator.valueToCode(block, 'BOOL', order) || 'true'; const code = '!' + argument0; return [code, order]; }; -JavaScript['logic_boolean'] = function(block) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; - return [code, JavaScript.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -JavaScript['logic_null'] = function(block) { +export function logic_null(block, generator) { // Null data type. - return ['null', JavaScript.ORDER_ATOMIC]; + return ['null', Order.ATOMIC]; }; -JavaScript['logic_ternary'] = function(block) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = - JavaScript.valueToCode(block, 'IF', JavaScript.ORDER_CONDITIONAL) || + generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; const value_then = - JavaScript.valueToCode(block, 'THEN', JavaScript.ORDER_CONDITIONAL) || + generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null'; const value_else = - JavaScript.valueToCode(block, 'ELSE', JavaScript.ORDER_CONDITIONAL) || + generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'null'; const code = value_if + ' ? ' + value_then + ' : ' + value_else; - return [code, JavaScript.ORDER_CONDITIONAL]; + return [code, Order.CONDITIONAL]; }; diff --git a/generators/javascript/loops.js b/generators/javascript/loops.js index dd710cbeb..c93438b56 100644 --- a/generators/javascript/loops.js +++ b/generators/javascript/loops.js @@ -7,16 +7,16 @@ /** * @fileoverview Generating JavaScript for loop blocks. */ -'use strict'; -goog.module('Blockly.JavaScript.loops'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.loops'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './javascript_generator.js'; -JavaScript['controls_repeat_ext'] = function(block) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -25,18 +25,19 @@ JavaScript['controls_repeat_ext'] = function(block) { } else { // External number. repeats = - JavaScript.valueToCode(block, 'TIMES', JavaScript.ORDER_ASSIGNMENT) || + generator.valueToCode(block, 'TIMES', Order.ASSIGNMENT) || '0'; } - let branch = JavaScript.statementToCode(block, 'DO'); - branch = JavaScript.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code = ''; const loopVar = - JavaScript.nameDB_.getDistinctName('count', NameType.VARIABLE); + generator.nameDB_.getDistinctName('count', NameType.VARIABLE); let endVar = repeats; if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) { endVar = - JavaScript.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + 'repeat_end', NameType.VARIABLE); code += 'var ' + endVar + ' = ' + repeats + ';\n'; } code += 'for (var ' + loopVar + ' = 0; ' + loopVar + ' < ' + endVar + '; ' + @@ -44,36 +45,37 @@ JavaScript['controls_repeat_ext'] = function(block) { return code; }; -JavaScript['controls_repeat'] = JavaScript['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -JavaScript['controls_whileUntil'] = function(block) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = - JavaScript.valueToCode( + generator.valueToCode( block, 'BOOL', - until ? JavaScript.ORDER_LOGICAL_NOT : JavaScript.ORDER_NONE) || + until ? Order.LOGICAL_NOT : Order.NONE) || 'false'; - let branch = JavaScript.statementToCode(block, 'DO'); - branch = JavaScript.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); if (until) { argument0 = '!' + argument0; } return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -JavaScript['controls_for'] = function(block) { +export function controls_for(block, generator) { // For loop. const variable0 = - JavaScript.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = - JavaScript.valueToCode(block, 'FROM', JavaScript.ORDER_ASSIGNMENT) || '0'; + generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0'; const argument1 = - JavaScript.valueToCode(block, 'TO', JavaScript.ORDER_ASSIGNMENT) || '0'; + generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0'; const increment = - JavaScript.valueToCode(block, 'BY', JavaScript.ORDER_ASSIGNMENT) || '1'; - let branch = JavaScript.statementToCode(block, 'DO'); - branch = JavaScript.addLoopTrap(branch, block); + generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code; if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) && stringUtils.isNumber(increment)) { @@ -93,19 +95,19 @@ JavaScript['controls_for'] = function(block) { // Cache non-trivial values to variables to prevent repeated look-ups. let startVar = argument0; if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) { - startVar = JavaScript.nameDB_.getDistinctName( + startVar = generator.nameDB_.getDistinctName( variable0 + '_start', NameType.VARIABLE); code += 'var ' + startVar + ' = ' + argument0 + ';\n'; } let endVar = argument1; if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) { - endVar = JavaScript.nameDB_.getDistinctName( + endVar = generator.nameDB_.getDistinctName( variable0 + '_end', NameType.VARIABLE); code += 'var ' + endVar + ' = ' + argument1 + ';\n'; } // Determine loop direction at start, in case one of the bounds // changes during loop execution. - const incVar = JavaScript.nameDB_.getDistinctName( + const incVar = generator.nameDB_.getDistinctName( variable0 + '_inc', NameType.VARIABLE); code += 'var ' + incVar + ' = '; if (stringUtils.isNumber(increment)) { @@ -114,7 +116,7 @@ JavaScript['controls_for'] = function(block) { code += 'Math.abs(' + increment + ');\n'; } code += 'if (' + startVar + ' > ' + endVar + ') {\n'; - code += JavaScript.INDENT + incVar + ' = -' + incVar + ';\n'; + code += generator.INDENT + incVar + ' = -' + incVar + ';\n'; code += '}\n'; code += 'for (' + variable0 + ' = ' + startVar + '; ' + incVar + ' >= 0 ? ' + variable0 + ' <= ' + endVar + ' : ' + variable0 + @@ -124,50 +126,54 @@ JavaScript['controls_for'] = function(block) { return code; }; -JavaScript['controls_forEach'] = function(block) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = - JavaScript.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = - JavaScript.valueToCode(block, 'LIST', JavaScript.ORDER_ASSIGNMENT) || + generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]'; - let branch = JavaScript.statementToCode(block, 'DO'); - branch = JavaScript.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code = ''; // Cache non-trivial values to variables to prevent repeated look-ups. let listVar = argument0; if (!argument0.match(/^\w+$/)) { - listVar = JavaScript.nameDB_.getDistinctName( + listVar = generator.nameDB_.getDistinctName( variable0 + '_list', NameType.VARIABLE); code += 'var ' + listVar + ' = ' + argument0 + ';\n'; } - const indexVar = JavaScript.nameDB_.getDistinctName( + const indexVar = generator.nameDB_.getDistinctName( variable0 + '_index', NameType.VARIABLE); - branch = JavaScript.INDENT + variable0 + ' = ' + listVar + '[' + indexVar + - '];\n' + branch; + branch = generator.INDENT + variable0 + ' = ' + listVar + + '[' + indexVar + '];\n' + branch; code += 'for (var ' + indexVar + ' in ' + listVar + ') {\n' + branch + '}\n'; return code; }; -JavaScript['controls_flow_statements'] = function(block) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; - if (JavaScript.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - xfix += JavaScript.injectId(JavaScript.STATEMENT_PREFIX, block); + xfix += generator.injectId( + generator.STATEMENT_PREFIX, block); } - if (JavaScript.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. - xfix += JavaScript.injectId(JavaScript.STATEMENT_SUFFIX, block); + xfix += generator.injectId( + generator.STATEMENT_SUFFIX, block); } - if (JavaScript.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { const loop = block.getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. - xfix += JavaScript.injectId(JavaScript.STATEMENT_PREFIX, loop); + xfix += generator.injectId( + generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { diff --git a/generators/javascript/math.js b/generators/javascript/math.js index ba4a1a129..ecba63523 100644 --- a/generators/javascript/math.js +++ b/generators/javascript/math.js @@ -8,68 +8,68 @@ * @fileoverview Generating JavaScript for math blocks. * @suppress {missingRequire} */ -'use strict'; -goog.module('Blockly.JavaScript.math'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.math'); -const {NameType} = goog.require('Blockly.Names'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import {NameType} from '../../core/names.js'; +import {Order} from './javascript_generator.js'; -JavaScript['math_number'] = function(block) { +export function math_number(block, generator) { // Numeric value. const code = Number(block.getFieldValue('NUM')); - const order = code >= 0 ? JavaScript.ORDER_ATOMIC : - JavaScript.ORDER_UNARY_NEGATION; + const order = code >= 0 ? Order.ATOMIC : + Order.UNARY_NEGATION; return [code, order]; }; -JavaScript['math_arithmetic'] = function(block) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { - 'ADD': [' + ', JavaScript.ORDER_ADDITION], - 'MINUS': [' - ', JavaScript.ORDER_SUBTRACTION], - 'MULTIPLY': [' * ', JavaScript.ORDER_MULTIPLICATION], - 'DIVIDE': [' / ', JavaScript.ORDER_DIVISION], - 'POWER': [null, JavaScript.ORDER_NONE], // Handle power separately. + 'ADD': [' + ', Order.ADDITION], + 'MINUS': [' - ', Order.SUBTRACTION], + 'MULTIPLY': [' * ', Order.MULTIPLICATION], + 'DIVIDE': [' / ', Order.DIVISION], + 'POWER': [null, Order.NONE], // Handle power separately. }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; const order = tuple[1]; - const argument0 = JavaScript.valueToCode(block, 'A', order) || '0'; - const argument1 = JavaScript.valueToCode(block, 'B', order) || '0'; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; let code; // Power in JavaScript requires a special case since it has no operator. if (!operator) { code = 'Math.pow(' + argument0 + ', ' + argument1 + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } code = argument0 + operator + argument1; return [code, order]; }; -JavaScript['math_single'] = function(block) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; let arg; if (operator === 'NEG') { // Negation is a special case given its different operator precedence. - arg = JavaScript.valueToCode(block, 'NUM', - JavaScript.ORDER_UNARY_NEGATION) || '0'; + arg = generator.valueToCode(block, 'NUM', + Order.UNARY_NEGATION) || '0'; if (arg[0] === '-') { // --3 is not legal in JS. arg = ' ' + arg; } code = '-' + arg; - return [code, JavaScript.ORDER_UNARY_NEGATION]; + return [code, Order.UNARY_NEGATION]; } if (operator === 'SIN' || operator === 'COS' || operator === 'TAN') { - arg = JavaScript.valueToCode(block, 'NUM', - JavaScript.ORDER_DIVISION) || '0'; + arg = generator.valueToCode(block, 'NUM', + Order.DIVISION) || '0'; } else { - arg = JavaScript.valueToCode(block, 'NUM', - JavaScript.ORDER_NONE) || '0'; + arg = generator.valueToCode(block, 'NUM', + Order.NONE) || '0'; } // First, handle cases which generate values that don't need parentheses // wrapping the code. @@ -109,7 +109,7 @@ JavaScript['math_single'] = function(block) { break; } if (code) { - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } // Second, handle cases which generate values that may need parentheses // wrapping the code. @@ -129,46 +129,47 @@ JavaScript['math_single'] = function(block) { default: throw Error('Unknown math operator: ' + operator); } - return [code, JavaScript.ORDER_DIVISION]; + return [code, Order.DIVISION]; }; -JavaScript['math_constant'] = function(block) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { - 'PI': ['Math.PI', JavaScript.ORDER_MEMBER], - 'E': ['Math.E', JavaScript.ORDER_MEMBER], - 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', JavaScript.ORDER_DIVISION], - 'SQRT2': ['Math.SQRT2', JavaScript.ORDER_MEMBER], - 'SQRT1_2': ['Math.SQRT1_2', JavaScript.ORDER_MEMBER], - 'INFINITY': ['Infinity', JavaScript.ORDER_ATOMIC], + 'PI': ['Math.PI', Order.MEMBER], + 'E': ['Math.E', Order.MEMBER], + 'GOLDEN_RATIO': ['(1 + Math.sqrt(5)) / 2', Order.DIVISION], + 'SQRT2': ['Math.SQRT2', Order.MEMBER], + 'SQRT1_2': ['Math.SQRT1_2', Order.MEMBER], + 'INFINITY': ['Infinity', Order.ATOMIC], }; return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -JavaScript['math_number_property'] = function(block) { +export function math_number_property(block, generator) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. const PROPERTIES = { - 'EVEN': [' % 2 === 0', JavaScript.ORDER_MODULUS, JavaScript.ORDER_EQUALITY], - 'ODD': [' % 2 === 1', JavaScript.ORDER_MODULUS, JavaScript.ORDER_EQUALITY], - 'WHOLE': [' % 1 === 0', JavaScript.ORDER_MODULUS, - JavaScript.ORDER_EQUALITY], - 'POSITIVE': [' > 0', JavaScript.ORDER_RELATIONAL, - JavaScript.ORDER_RELATIONAL], - 'NEGATIVE': [' < 0', JavaScript.ORDER_RELATIONAL, - JavaScript.ORDER_RELATIONAL], - 'DIVISIBLE_BY': [null, JavaScript.ORDER_MODULUS, JavaScript.ORDER_EQUALITY], - 'PRIME': [null, JavaScript.ORDER_NONE, JavaScript.ORDER_FUNCTION_CALL], + 'EVEN': [' % 2 === 0', Order.MODULUS, Order.EQUALITY], + 'ODD': [' % 2 === 1', Order.MODULUS, Order.EQUALITY], + 'WHOLE': [' % 1 === 0', Order.MODULUS, + Order.EQUALITY], + 'POSITIVE': [' > 0', Order.RELATIONAL, + Order.RELATIONAL], + 'NEGATIVE': [' < 0', Order.RELATIONAL, + Order.RELATIONAL], + 'DIVISIBLE_BY': [null, Order.MODULUS, Order.EQUALITY], + 'PRIME': [null, Order.NONE, Order.FUNCTION_CALL], }; const dropdownProperty = block.getFieldValue('PROPERTY'); const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; - const numberToCheck = JavaScript.valueToCode(block, 'NUMBER_TO_CHECK', - inputOrder) || '0'; + const numberToCheck = + generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || + '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = JavaScript.provideFunction_('mathIsPrime', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(n) { + const functionName = generator.provideFunction_('mathIsPrime', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) { // https://en.wikipedia.org/wiki/Primality_test#Naive_methods if (n == 2 || n == 3) { return true; @@ -189,8 +190,8 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(n) { `); code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = JavaScript.valueToCode(block, 'DIVISOR', - JavaScript.ORDER_MODULUS) || '0'; + const divisor = generator.valueToCode(block, 'DIVISOR', + Order.MODULUS) || '0'; code = numberToCheck + ' % ' + divisor + ' === 0'; } else { code = numberToCheck + suffix; @@ -198,58 +199,58 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(n) { return [code, outputOrder]; }; -JavaScript['math_change'] = function(block) { +export function math_change(block, generator) { // Add to a variable in place. - const argument0 = JavaScript.valueToCode(block, 'DELTA', - JavaScript.ORDER_ADDITION) || '0'; - const varName = JavaScript.nameDB_.getName( + const argument0 = generator.valueToCode(block, 'DELTA', + Order.ADDITION) || '0'; + const varName = generator.nameDB_.getName( block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = (typeof ' + varName + ' === \'number\' ? ' + varName + ' : 0) + ' + argument0 + ';\n'; }; // Rounding functions have a single operand. -JavaScript['math_round'] = JavaScript['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -JavaScript['math_trig'] = JavaScript['math_single']; +export const math_trig = math_single; -JavaScript['math_on_list'] = function(block) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); let list; let code; switch (func) { case 'SUM': - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_MEMBER) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.MEMBER) || '[]'; code = list + '.reduce(function(x, y) {return x + y;}, 0)'; break; case 'MIN': - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = 'Math.min.apply(null, ' + list + ')'; break; case 'MAX': - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = 'Math.max.apply(null, ' + list + ')'; break; case 'AVERAGE': { // mathMean([null,null,1,3]) === 2.0. - const functionName = JavaScript.provideFunction_('mathMean', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(myList) { + const functionName = generator.provideFunction_('mathMean', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList) { return myList.reduce(function(x, y) {return x + y;}, 0) / myList.length; } `); - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { // mathMedian([null,null,1,3]) === 2.0. - const functionName = JavaScript.provideFunction_('mathMedian', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(myList) { + const functionName = generator.provideFunction_('mathMedian', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList) { var localList = myList.filter(function (x) {return typeof x === 'number';}); if (!localList.length) return null; localList.sort(function(a, b) {return b - a;}); @@ -260,8 +261,8 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(myList) { } } `); - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } @@ -269,8 +270,8 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(myList) { // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. - const functionName = JavaScript.provideFunction_('mathModes', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(values) { + const functionName = generator.provideFunction_('mathModes', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(values) { var modes = []; var counts = []; var maxCount = 0; @@ -299,14 +300,15 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(values) { return modes; } `); - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { - const functionName = JavaScript.provideFunction_('mathStandardDeviation', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(numbers) { + const functionName = + generator.provideFunction_('mathStandardDeviation', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(numbers) { var n = numbers.length; if (!n) return null; var mean = numbers.reduce(function(x, y) {return x + y;}) / n; @@ -318,60 +320,61 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(numbers) { return Math.sqrt(variance); } `); - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'RANDOM': { - const functionName = JavaScript.provideFunction_('mathRandomList', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(list) { + const functionName = + generator.provideFunction_('mathRandomList', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) { var x = Math.floor(Math.random() * list.length); return list[x]; } `); - list = JavaScript.valueToCode(block, 'LIST', - JavaScript.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', + Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } default: throw Error('Unknown operator: ' + func); } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['math_modulo'] = function(block) { +export function math_modulo(block, generator) { // Remainder computation. - const argument0 = JavaScript.valueToCode(block, 'DIVIDEND', - JavaScript.ORDER_MODULUS) || '0'; - const argument1 = JavaScript.valueToCode(block, 'DIVISOR', - JavaScript.ORDER_MODULUS) || '0'; + const argument0 = generator.valueToCode(block, 'DIVIDEND', + Order.MODULUS) || '0'; + const argument1 = generator.valueToCode(block, 'DIVISOR', + Order.MODULUS) || '0'; const code = argument0 + ' % ' + argument1; - return [code, JavaScript.ORDER_MODULUS]; + return [code, Order.MODULUS]; }; -JavaScript['math_constrain'] = function(block) { +export function math_constrain(block, generator) { // Constrain a number between two limits. - const argument0 = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_NONE) || '0'; - const argument1 = JavaScript.valueToCode(block, 'LOW', - JavaScript.ORDER_NONE) || '0'; - const argument2 = JavaScript.valueToCode(block, 'HIGH', - JavaScript.ORDER_NONE) || 'Infinity'; + const argument0 = generator.valueToCode(block, 'VALUE', + Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'LOW', + Order.NONE) || '0'; + const argument2 = generator.valueToCode(block, 'HIGH', + Order.NONE) || 'Infinity'; const code = 'Math.min(Math.max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['math_random_int'] = function(block) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. - const argument0 = JavaScript.valueToCode(block, 'FROM', - JavaScript.ORDER_NONE) || '0'; - const argument1 = JavaScript.valueToCode(block, 'TO', - JavaScript.ORDER_NONE) || '0'; - const functionName = JavaScript.provideFunction_('mathRandomInt', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(a, b) { + const argument0 = generator.valueToCode(block, 'FROM', + Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'TO', + Order.NONE) || '0'; + const functionName = generator.provideFunction_('mathRandomInt', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(a, b) { if (a > b) { // Swap a and b to ensure a is smaller. var c = a; @@ -382,20 +385,20 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(a, b) { } `); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['math_random_float'] = function(block) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. - return ['Math.random()', JavaScript.ORDER_FUNCTION_CALL]; + return ['Math.random()', Order.FUNCTION_CALL]; }; -JavaScript['math_atan2'] = function(block) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. - const argument0 = JavaScript.valueToCode(block, 'X', - JavaScript.ORDER_NONE) || '0'; - const argument1 = JavaScript.valueToCode(block, 'Y', - JavaScript.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'X', + Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'Y', + Order.NONE) || '0'; return ['Math.atan2(' + argument1 + ', ' + argument0 + ') / Math.PI * 180', - JavaScript.ORDER_DIVISION]; + Order.DIVISION]; }; diff --git a/generators/javascript/procedures.js b/generators/javascript/procedures.js index e47e490f4..baa0ebe48 100644 --- a/generators/javascript/procedures.js +++ b/generators/javascript/procedures.js @@ -7,103 +7,108 @@ /** * @fileoverview Generating JavaScript for procedure blocks. */ -'use strict'; -goog.module('Blockly.JavaScript.procedures'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.procedures'); -const {NameType} = goog.require('Blockly.Names'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import {NameType} from '../../core/names.js'; +import {Order} from './javascript_generator.js'; -JavaScript['procedures_defreturn'] = function(block) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. - const funcName = JavaScript.nameDB_.getName( + const funcName = generator.nameDB_.getName( block.getFieldValue('NAME'), NameType.PROCEDURE); let xfix1 = ''; - if (JavaScript.STATEMENT_PREFIX) { - xfix1 += JavaScript.injectId(JavaScript.STATEMENT_PREFIX, block); + if (generator.STATEMENT_PREFIX) { + xfix1 += generator.injectId( + generator.STATEMENT_PREFIX, block); } - if (JavaScript.STATEMENT_SUFFIX) { - xfix1 += JavaScript.injectId(JavaScript.STATEMENT_SUFFIX, block); + if (generator.STATEMENT_SUFFIX) { + xfix1 += generator.injectId( + generator.STATEMENT_SUFFIX, block); } if (xfix1) { - xfix1 = JavaScript.prefixLines(xfix1, JavaScript.INDENT); + xfix1 = generator.prefixLines(xfix1, generator.INDENT); } let loopTrap = ''; - if (JavaScript.INFINITE_LOOP_TRAP) { - loopTrap = JavaScript.prefixLines( - JavaScript.injectId(JavaScript.INFINITE_LOOP_TRAP, block), - JavaScript.INDENT); + if (generator.INFINITE_LOOP_TRAP) { + loopTrap = generator.prefixLines( + generator.injectId( + generator.INFINITE_LOOP_TRAP, block), + generator.INDENT); } - const branch = JavaScript.statementToCode(block, 'STACK'); + const branch = generator.statementToCode(block, 'STACK'); let returnValue = - JavaScript.valueToCode(block, 'RETURN', JavaScript.ORDER_NONE) || ''; + generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. xfix2 = xfix1; } if (returnValue) { - returnValue = JavaScript.INDENT + 'return ' + returnValue + ';\n'; + returnValue = generator.INDENT + 'return ' + returnValue + ';\n'; } const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = JavaScript.nameDB_.getName(variables[i], NameType.VARIABLE); + args[i] = + generator.nameDB_.getName(variables[i], NameType.VARIABLE); } let code = 'function ' + funcName + '(' + args.join(', ') + ') {\n' + xfix1 + loopTrap + branch + xfix2 + returnValue + '}'; - code = JavaScript.scrub_(block, code); + code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. - JavaScript.definitions_['%' + funcName] = code; + generator.definitions_['%' + funcName] = code; return null; }; // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -JavaScript['procedures_defnoreturn'] = JavaScript['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -JavaScript['procedures_callreturn'] = function(block) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. - const funcName = JavaScript.nameDB_.getName( + const funcName = generator.nameDB_.getName( block.getFieldValue('NAME'), NameType.PROCEDURE); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = JavaScript.valueToCode(block, 'ARG' + i, JavaScript.ORDER_NONE) || + args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'null'; } const code = funcName + '(' + args.join(', ') + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['procedures_callnoreturn'] = function(block) { +export function procedures_callnoreturn(block, generator) { // Call a procedure with no return value. // Generated code is for a function call as a statement is the same as a // function call as a value, with the addition of line ending. - const tuple = JavaScript['procedures_callreturn'](block); + const tuple = generator.forBlock['procedures_callreturn'](block, generator); return tuple[0] + ';\n'; }; -JavaScript['procedures_ifreturn'] = function(block) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = - JavaScript.valueToCode(block, 'CONDITION', JavaScript.ORDER_NONE) || + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; let code = 'if (' + condition + ') {\n'; - if (JavaScript.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. - code += JavaScript.prefixLines( - JavaScript.injectId(JavaScript.STATEMENT_SUFFIX, block), - JavaScript.INDENT); + code += generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), + generator.INDENT); } if (block.hasReturnValue_) { const value = - JavaScript.valueToCode(block, 'VALUE', JavaScript.ORDER_NONE) || 'null'; - code += JavaScript.INDENT + 'return ' + value + ';\n'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'null'; + code += generator.INDENT + 'return ' + value + ';\n'; } else { - code += JavaScript.INDENT + 'return;\n'; + code += generator.INDENT + 'return;\n'; } code += '}\n'; return code; diff --git a/generators/javascript/text.js b/generators/javascript/text.js index 270c51368..e3761293b 100644 --- a/generators/javascript/text.js +++ b/generators/javascript/text.js @@ -7,12 +7,12 @@ /** * @fileoverview Generating JavaScript for text blocks. */ -'use strict'; -goog.module('Blockly.JavaScript.texts'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.JavaScript.texts'); -const {NameType} = goog.require('Blockly.Names'); -const {javascriptGenerator: JavaScript} = goog.require('Blockly.JavaScript'); +import {NameType} from '../../core/names.js'; +import {Order} from './javascript_generator.js'; /** @@ -29,9 +29,9 @@ const strRegExp = /^\s*'([^']|\\')*'\s*$/; */ const forceString = function(value) { if (strRegExp.test(value)) { - return [value, JavaScript.ORDER_ATOMIC]; + return [value, Order.ATOMIC]; } - return ['String(' + value + ')', JavaScript.ORDER_FUNCTION_CALL]; + return ['String(' + value + ')', Order.FUNCTION_CALL]; }; /** @@ -53,158 +53,162 @@ const getSubstringIndex = function(stringName, where, opt_at) { } }; -JavaScript['text'] = function(block) { +export function text(block, generator) { // Text value. - const code = JavaScript.quote_(block.getFieldValue('TEXT')); - return [code, JavaScript.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('TEXT')); + return [code, Order.ATOMIC]; }; -JavaScript['text_multiline'] = function(block) { +export function text_multiline(block, generator) { // Text value. - const code = JavaScript.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('+') !== -1 ? JavaScript.ORDER_ADDITION : - JavaScript.ORDER_ATOMIC; + const code = + generator.multiline_quote_(block.getFieldValue('TEXT')); + const order = code.indexOf('+') !== -1 ? Order.ADDITION : + Order.ATOMIC; return [code, order]; }; -JavaScript['text_join'] = function(block) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. switch (block.itemCount_) { case 0: - return ["''", JavaScript.ORDER_ATOMIC]; + return ["''", Order.ATOMIC]; case 1: { - const element = JavaScript.valueToCode(block, 'ADD0', - JavaScript.ORDER_NONE) || "''"; + const element = generator.valueToCode(block, 'ADD0', + Order.NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { - const element0 = JavaScript.valueToCode(block, 'ADD0', - JavaScript.ORDER_NONE) || "''"; - const element1 = JavaScript.valueToCode(block, 'ADD1', - JavaScript.ORDER_NONE) || "''"; + const element0 = generator.valueToCode(block, 'ADD0', + Order.NONE) || "''"; + const element1 = generator.valueToCode(block, 'ADD1', + Order.NONE) || "''"; const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; - return [code, JavaScript.ORDER_ADDITION]; + return [code, Order.ADDITION]; } default: { const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { - elements[i] = JavaScript.valueToCode(block, 'ADD' + i, - JavaScript.ORDER_NONE) || "''"; + elements[i] = generator.valueToCode(block, 'ADD' + i, + Order.NONE) || "''"; } const code = '[' + elements.join(',') + '].join(\'\')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } } }; -JavaScript['text_append'] = function(block) { +export function text_append(block, generator) { // Append to a variable in place. - const varName = JavaScript.nameDB_.getName( + const varName = generator.nameDB_.getName( block.getFieldValue('VAR'), NameType.VARIABLE); - const value = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || "''"; + const value = generator.valueToCode(block, 'TEXT', + Order.NONE) || "''"; const code = varName + ' += ' + forceString(value)[0] + ';\n'; return code; }; -JavaScript['text_length'] = function(block) { +export function text_length(block, generator) { // String or array length. - const text = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_MEMBER) || "''"; - return [text + '.length', JavaScript.ORDER_MEMBER]; + const text = generator.valueToCode(block, 'VALUE', + Order.MEMBER) || "''"; + return [text + '.length', Order.MEMBER]; }; -JavaScript['text_isEmpty'] = function(block) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? - const text = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_MEMBER) || "''"; - return ['!' + text + '.length', JavaScript.ORDER_LOGICAL_NOT]; + const text = generator.valueToCode(block, 'VALUE', + Order.MEMBER) || "''"; + return ['!' + text + '.length', Order.LOGICAL_NOT]; }; -JavaScript['text_indexOf'] = function(block) { +export function text_indexOf(block, generator) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'indexOf' : 'lastIndexOf'; - const substring = JavaScript.valueToCode(block, 'FIND', - JavaScript.ORDER_NONE) || "''"; - const text = JavaScript.valueToCode(block, 'VALUE', - JavaScript.ORDER_MEMBER) || "''"; + const substring = generator.valueToCode(block, 'FIND', + Order.NONE) || "''"; + const text = generator.valueToCode(block, 'VALUE', + Order.MEMBER) || "''"; const code = text + '.' + operator + '(' + substring + ')'; // Adjust index if using one-based indices. if (block.workspace.options.oneBasedIndex) { - return [code + ' + 1', JavaScript.ORDER_ADDITION]; + return [code + ' + 1', Order.ADDITION]; } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['text_charAt'] = function(block) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; - const textOrder = (where === 'RANDOM') ? JavaScript.ORDER_NONE : - JavaScript.ORDER_MEMBER; - const text = JavaScript.valueToCode(block, 'VALUE', textOrder) || "''"; + const textOrder = (where === 'RANDOM') ? Order.NONE : + Order.MEMBER; + const text = + generator.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = text + '.charAt(0)'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'LAST': { const code = text + '.slice(-1)'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'FROM_START': { - const at = JavaScript.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); // Adjust index if using one-based indices. const code = text + '.charAt(' + at + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'FROM_END': { - const at = JavaScript.getAdjusted(block, 'AT', 1, true); + const at = generator.getAdjusted(block, 'AT', 1, true); const code = text + '.slice(' + at + ').charAt(0)'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'RANDOM': { - const functionName = JavaScript.provideFunction_('textRandomLetter', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(text) { + const functionName = + generator.provideFunction_('textRandomLetter', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { var x = Math.floor(Math.random() * text.length); return text[x]; } `); const code = functionName + '(' + text + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } } throw Error('Unhandled option (text_charAt).'); }; -JavaScript['text_getSubstring'] = function(block) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); const requiresLengthCall = (where1 !== 'FROM_END' && where1 !== 'LAST' && where2 !== 'FROM_END' && where2 !== 'LAST'); - const textOrder = requiresLengthCall ? JavaScript.ORDER_MEMBER : - JavaScript.ORDER_NONE; - const text = JavaScript.valueToCode(block, 'STRING', textOrder) || "''"; + const textOrder = requiresLengthCall ? Order.MEMBER : + Order.NONE; + const text = + generator.valueToCode(block, 'STRING', textOrder) || "''"; let code; if (where1 === 'FIRST' && where2 === 'LAST') { code = text; - return [code, JavaScript.ORDER_NONE]; + return [code, Order.NONE]; } else if (text.match(/^'?\w+'?$/) || requiresLengthCall) { // If the text is a variable or literal or doesn't require a call for // length, don't generate a helper function. let at1; switch (where1) { case 'FROM_START': - at1 = JavaScript.getAdjusted(block, 'AT1'); + at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = JavaScript.getAdjusted(block, 'AT1', 1, false, - JavaScript.ORDER_SUBTRACTION); + at1 = generator.getAdjusted(block, 'AT1', 1, false, + Order.SUBTRACTION); at1 = text + '.length - ' + at1; break; case 'FIRST': @@ -216,11 +220,11 @@ JavaScript['text_getSubstring'] = function(block) { let at2; switch (where2) { case 'FROM_START': - at2 = JavaScript.getAdjusted(block, 'AT2', 1); + at2 = generator.getAdjusted(block, 'AT2', 1); break; case 'FROM_END': - at2 = JavaScript.getAdjusted(block, 'AT2', 0, false, - JavaScript.ORDER_SUBTRACTION); + at2 = generator.getAdjusted(block, 'AT2', 0, false, + Order.SUBTRACTION); at2 = text + '.length - ' + at2; break; case 'LAST': @@ -231,8 +235,8 @@ JavaScript['text_getSubstring'] = function(block) { } code = text + '.slice(' + at1 + ', ' + at2 + ')'; } else { - const at1 = JavaScript.getAdjusted(block, 'AT1'); - const at2 = JavaScript.getAdjusted(block, 'AT2'); + const at1 = generator.getAdjusted(block, 'AT1'); + const at2 = generator.getAdjusted(block, 'AT2'); const wherePascalCase = {'FIRST': 'First', 'LAST': 'Last', 'FROM_START': 'FromStart', 'FROM_END': 'FromEnd'}; // The value for 'FROM_END' and'FROM_START' depends on `at` so @@ -241,9 +245,9 @@ JavaScript['text_getSubstring'] = function(block) { (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; const at2Param = (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; - const functionName = JavaScript.provideFunction_( + const functionName = generator.provideFunction_( 'subsequence' + wherePascalCase[where1] + wherePascalCase[where2], ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param}) { var start = ${getSubstringIndex('sequence', where1, 'at1')}; var end = ${getSubstringIndex('sequence', where2, 'at2')} + 1; return sequence.slice(start, end); @@ -256,10 +260,10 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(sequence${at1Param}${at2Param} ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', ' + at2 : '') + ')'; } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['text_changeCase'] = function(block) { +export function text_changeCase(block, generator) { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.toUpperCase()', @@ -267,26 +271,28 @@ JavaScript['text_changeCase'] = function(block) { 'TITLECASE': null, }; const operator = OPERATORS[block.getFieldValue('CASE')]; - const textOrder = operator ? JavaScript.ORDER_MEMBER : JavaScript.ORDER_NONE; - const text = JavaScript.valueToCode(block, 'TEXT', textOrder) || "''"; + const textOrder = operator ? Order.MEMBER : Order.NONE; + const text = + generator.valueToCode(block, 'TEXT', textOrder) || "''"; let code; if (operator) { - // Upper and lower case are functions built into JavaScript. + // Upper and lower case are functions built into generator. code = text + operator; } else { // Title case is not a native JavaScript function. Define one. - const functionName = JavaScript.provideFunction_('textToTitleCase', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(str) { + const functionName = + generator.provideFunction_('textToTitleCase', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str) { return str.replace(/\\S+/g, function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();}); } `); code = functionName + '(' + text + ')'; } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['text_trim'] = function(block) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = { 'LEFT': ".replace(/^[\\s\\xa0]+/, '')", @@ -294,45 +300,45 @@ JavaScript['text_trim'] = function(block) { 'BOTH': '.trim()', }; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_MEMBER) || "''"; - return [text + operator, JavaScript.ORDER_FUNCTION_CALL]; + const text = generator.valueToCode(block, 'TEXT', + Order.MEMBER) || "''"; + return [text + operator, Order.FUNCTION_CALL]; }; -JavaScript['text_print'] = function(block) { +export function text_print(block, generator) { // Print statement. - const msg = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || "''"; + const msg = generator.valueToCode(block, 'TEXT', + Order.NONE) || "''"; return 'window.alert(' + msg + ');\n'; }; -JavaScript['text_prompt_ext'] = function(block) { +export function text_prompt_ext(block, generator) { // Prompt function. let msg; if (block.getField('TEXT')) { // Internal message. - msg = JavaScript.quote_(block.getFieldValue('TEXT')); + msg = generator.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = JavaScript.valueToCode(block, 'TEXT', JavaScript.ORDER_NONE) || "''"; + msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; } let code = 'window.prompt(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; if (toNumber) { code = 'Number(' + code + ')'; } - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['text_prompt'] = JavaScript['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -JavaScript['text_count'] = function(block) { - const text = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || "''"; - const sub = JavaScript.valueToCode(block, 'SUB', - JavaScript.ORDER_NONE) || "''"; - const functionName = JavaScript.provideFunction_('textCount', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { +export function text_count(block, generator) { + const text = generator.valueToCode(block, 'TEXT', + Order.NONE) || "''"; + const sub = generator.valueToCode(block, 'SUB', + Order.NONE) || "''"; + const functionName = generator.provideFunction_('textCount', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { if (needle.length === 0) { return haystack.length + 1; } else { @@ -341,31 +347,31 @@ function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) { } `); const code = functionName + '(' + text + ', ' + sub + ')'; - return [code, JavaScript.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -JavaScript['text_replace'] = function(block) { - const text = JavaScript.valueToCode(block, 'TEXT', - JavaScript.ORDER_NONE) || "''"; - const from = JavaScript.valueToCode(block, 'FROM', - JavaScript.ORDER_NONE) || "''"; - const to = JavaScript.valueToCode(block, 'TO', JavaScript.ORDER_NONE) || "''"; +export function text_replace(block, generator) { + const text = generator.valueToCode(block, 'TEXT', + Order.NONE) || "''"; + const from = generator.valueToCode(block, 'FROM', + Order.NONE) || "''"; + const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; // The regex escaping code below is taken from the implementation of // goog.string.regExpEscape. - const functionName = JavaScript.provideFunction_('textReplace', ` -function ${JavaScript.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) { + const functionName = generator.provideFunction_('textReplace', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) { needle = needle.replace(/([-()\\[\\]{}+?*.$\\^|,:# <= >= ~= == -Lua.ORDER_AND = 8; // and -Lua.ORDER_OR = 9; // or -Lua.ORDER_NONE = 99; - -/** - * Note: Lua is not supporting zero-indexing since the language itself is - * one-indexed, so the generator does not repoct the oneBasedIndex configuration - * option used for lists and text. - */ - -/** - * Whether the init method has been called. - * @type {?boolean} - */ -Lua.isInitialized = false; - -/** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ -Lua.init = function(workspace) { - // Call Blockly.CodeGenerator's init. - Object.getPrototypeOf(this).init.call(this); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - this.isInitialized = true; -}; - -/** - * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ -Lua.finish = function(code) { - // Convert the definitions dictionary into a list. - const definitions = Object.values(this.definitions_); - // Call Blockly.CodeGenerator's finish. - code = Object.getPrototypeOf(this).finish.call(this, code); - this.isInitialized = false; - - this.nameDB_.reset(); - return definitions.join('\n\n') + '\n\n\n' + code; -}; - -/** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. In Lua, an expression is not a legal statement, so we must assign - * the value to the (conventionally ignored) _. - * http://lua-users.org/wiki/ExpressionsAsStatements - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ -Lua.scrubNakedValue = function(line) { - return 'local _ = ' + line + '\n'; -}; - -/** - * Encode a string as a properly escaped Lua string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} Lua string. - * @protected - */ -Lua.quote_ = function(string) { - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; -}; - -/** - * Encode a string as a properly escaped multiline Lua string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} Lua string. - * @protected - */ -Lua.multiline_quote_ = function(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // .. '\n' .. - return lines.join(' .. \'\\n\' ..\n'); -}; - -/** - * Common tasks for generating Lua from blocks. - * Handles comments for the specified block and any connected value blocks. - * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The Lua code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} Lua code with comments and subsequent blocks added. - * @protected - */ -Lua.scrub_ = function(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment, '-- ') + '\n'; - } - // Collect comments for all value arguments. - // Don't collect comments for nested statements. - for (let i = 0; i < block.inputList.length; i++) { - if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '-- '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; -}; - -exports.luaGenerator = Lua; +// Install per-block-type generator functions: +Object.assign( + luaGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/lua/all.js b/generators/lua/all.js deleted file mode 100644 index 7f3bc7581..000000000 --- a/generators/lua/all.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating Lua for - * blocks. This is the entrypoint for lua_compressed.js. - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.Lua.all'); - -const moduleExports = goog.require('Blockly.Lua'); -goog.require('Blockly.Lua.colour'); -goog.require('Blockly.Lua.lists'); -goog.require('Blockly.Lua.logic'); -goog.require('Blockly.Lua.loops'); -goog.require('Blockly.Lua.math'); -goog.require('Blockly.Lua.procedures'); -goog.require('Blockly.Lua.texts'); -goog.require('Blockly.Lua.variables'); -goog.require('Blockly.Lua.variablesDynamic'); - -exports = moduleExports; diff --git a/generators/lua/colour.js b/generators/lua/colour.js index be3825ae5..a69dd41c6 100644 --- a/generators/lua/colour.js +++ b/generators/lua/colour.js @@ -7,46 +7,46 @@ /** * @fileoverview Generating Lua for colour blocks. */ -'use strict'; -goog.module('Blockly.Lua.colour'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.colour'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {Order} from './lua_generator.js'; -Lua['colour_picker'] = function(block) { +export function colour_picker(block, generator) { // Colour picker. - const code = Lua.quote_(block.getFieldValue('COLOUR')); - return [code, Lua.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('COLOUR')); + return [code, Order.ATOMIC]; }; -Lua['colour_random'] = function(block) { +export function colour_random(block, generator) { // Generate a random colour. const code = 'string.format("#%06x", math.random(0, 2^24 - 1))'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['colour_rgb'] = function(block) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. - const functionName = Lua.provideFunction_('colour_rgb', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) + const functionName = generator.provideFunction_('colour_rgb', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b) r = math.floor(math.min(100, math.max(0, r)) * 2.55 + .5) g = math.floor(math.min(100, math.max(0, g)) * 2.55 + .5) b = math.floor(math.min(100, math.max(0, b)) * 2.55 + .5) return string.format("#%02x%02x%02x", r, g, b) end `); - const r = Lua.valueToCode(block, 'RED', Lua.ORDER_NONE) || 0; - const g = Lua.valueToCode(block, 'GREEN', Lua.ORDER_NONE) || 0; - const b = Lua.valueToCode(block, 'BLUE', Lua.ORDER_NONE) || 0; + const r = generator.valueToCode(block, 'RED', Order.NONE) || 0; + const g = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; + const b = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; const code = functionName + '(' + r + ', ' + g + ', ' + b + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['colour_blend'] = function(block) { +export function colour_blend(block, generator) { // Blend two colours together. - const functionName = Lua.provideFunction_('colour_blend', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio) + const functionName = generator.provideFunction_('colour_blend', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio) local r1 = tonumber(string.sub(colour1, 2, 3), 16) local r2 = tonumber(string.sub(colour2, 2, 3), 16) local g1 = tonumber(string.sub(colour1, 4, 5), 16) @@ -61,11 +61,11 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio) end `); const colour1 = - Lua.valueToCode(block, 'COLOUR1', Lua.ORDER_NONE) || "'#000000'"; + generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; const colour2 = - Lua.valueToCode(block, 'COLOUR2', Lua.ORDER_NONE) || "'#000000'"; - const ratio = Lua.valueToCode(block, 'RATIO', Lua.ORDER_NONE) || 0; + generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; + const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0; const code = functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; diff --git a/generators/lua/lists.js b/generators/lua/lists.js index b67f9033c..8643911f3 100644 --- a/generators/lua/lists.js +++ b/generators/lua/lists.js @@ -7,33 +7,34 @@ /** * @fileoverview Generating Lua for list blocks. */ -'use strict'; -goog.module('Blockly.Lua.lists'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.lists'); -const {NameType} = goog.require('Blockly.Names'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {NameType} from '../../core/names.js'; +import {Order} from './lua_generator.js'; -Lua['lists_create_empty'] = function(block) { +export function lists_create_empty(block, generator) { // Create an empty list. - return ['{}', Lua.ORDER_HIGH]; + return ['{}', Order.HIGH]; }; -Lua['lists_create_with'] = function(block) { +export function lists_create_with(block, generator) { // Create a list with any number of elements of any type. const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { - elements[i] = Lua.valueToCode(block, 'ADD' + i, Lua.ORDER_NONE) || 'None'; + elements[i] = + generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'None'; } const code = '{' + elements.join(', ') + '}'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['lists_repeat'] = function(block) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. - const functionName = Lua.provideFunction_('create_list_repeated', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(item, count) + const functionName = generator.provideFunction_('create_list_repeated', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(item, count) local t = {} for i = 1, count do table.insert(t, item) @@ -41,33 +42,33 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(item, count) return t end `); - const element = Lua.valueToCode(block, 'ITEM', Lua.ORDER_NONE) || 'None'; - const repeatCount = Lua.valueToCode(block, 'NUM', Lua.ORDER_NONE) || '0'; + const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'None'; + const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const code = functionName + '(' + element + ', ' + repeatCount + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['lists_length'] = function(block) { +export function lists_length(block, generator) { // String or array length. - const list = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || '{}'; - return ['#' + list, Lua.ORDER_UNARY]; + const list = generator.valueToCode(block, 'VALUE', Order.UNARY) || '{}'; + return ['#' + list, Order.UNARY]; }; -Lua['lists_isEmpty'] = function(block) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? - const list = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || '{}'; + const list = generator.valueToCode(block, 'VALUE', Order.UNARY) || '{}'; const code = '#' + list + ' == 0'; - return [code, Lua.ORDER_RELATIONAL]; + return [code, Order.RELATIONAL]; }; -Lua['lists_indexOf'] = function(block) { +export function lists_indexOf(block, generator) { // Find an item in the list. - const item = Lua.valueToCode(block, 'FIND', Lua.ORDER_NONE) || "''"; - const list = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || '{}'; + const item = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '{}'; let functionName; if (block.getFieldValue('END') === 'FIRST') { - functionName = Lua.provideFunction_('first_index', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t, elem) + functionName = generator.provideFunction_('first_index', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t, elem) for k, v in ipairs(t) do if v == elem then return k @@ -77,8 +78,8 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t, elem) end `); } else { - functionName = Lua.provideFunction_('last_index', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t, elem) + functionName = generator.provideFunction_('last_index', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t, elem) for i = #t, 1, -1 do if t[i] == elem then return i @@ -89,7 +90,7 @@ end `); } const code = functionName + '(' + list + ', ' + item + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; /** @@ -113,12 +114,12 @@ const getListIndex = function(listName, where, opt_at) { } }; -Lua['lists_getIndex'] = function(block) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const list = Lua.valueToCode(block, 'VALUE', Lua.ORDER_HIGH) || '({})'; + const list = generator.valueToCode(block, 'VALUE', Order.HIGH) || '({})'; // If `list` would be evaluated more than once (which is the case for LAST, // FROM_END, and RANDOM) and is non-trivial, make sure to access it only once. @@ -128,21 +129,22 @@ Lua['lists_getIndex'] = function(block) { if (mode === 'REMOVE') { // We can use multiple statements. const atOrder = - (where === 'FROM_END') ? Lua.ORDER_ADDITIVE : Lua.ORDER_NONE; - let at = Lua.valueToCode(block, 'AT', atOrder) || '1'; + (where === 'FROM_END') ? Order.ADDITIVE : Order.NONE; + let at = generator.valueToCode(block, 'AT', atOrder) || '1'; const listVar = - Lua.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); at = getListIndex(listVar, where, at); const code = listVar + ' = ' + list + '\n' + 'table.remove(' + listVar + ', ' + at + ')\n'; return code; } else { // We need to create a procedure to avoid reevaluating values. - const at = Lua.valueToCode(block, 'AT', Lua.ORDER_NONE) || '1'; + const at = generator.valueToCode(block, 'AT', Order.NONE) || '1'; let functionName; if (mode === 'GET') { - functionName = Lua.provideFunction_('list_get_' + where.toLowerCase(), [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t' + + functionName = generator.provideFunction_( + 'list_get_' + where.toLowerCase(), [ + 'function ' + generator.FUNCTION_NAME_PLACEHOLDER_ + '(t' + // The value for 'FROM_END' and'FROM_START' depends on `at` so // we add it as a parameter. ((where === 'FROM_END' || where === 'FROM_START') ? ', at)' : @@ -151,8 +153,9 @@ Lua['lists_getIndex'] = function(block) { ]); } else { // `mode` === 'GET_REMOVE' functionName = - Lua.provideFunction_('list_remove_' + where.toLowerCase(), [ - 'function ' + Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t' + + generator.provideFunction_( + 'list_remove_' + where.toLowerCase(), [ + 'function ' + generator.FUNCTION_NAME_PLACEHOLDER_ + '(t' + // The value for 'FROM_END' and'FROM_START' depends on `at` so // we add it as a parameter. ((where === 'FROM_END' || where === 'FROM_START') ? ', at)' : @@ -167,23 +170,23 @@ Lua['lists_getIndex'] = function(block) { // pass it. ((where === 'FROM_END' || where === 'FROM_START') ? ', ' + at : '') + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; } } else { // Either `list` is a simple variable, or we only need to refer to `list` // once. const atOrder = (mode === 'GET' && where === 'FROM_END') ? - Lua.ORDER_ADDITIVE : - Lua.ORDER_NONE; - let at = Lua.valueToCode(block, 'AT', atOrder) || '1'; + Order.ADDITIVE : + Order.NONE; + let at = generator.valueToCode(block, 'AT', atOrder) || '1'; at = getListIndex(list, where, at); if (mode === 'GET') { const code = list + '[' + at + ']'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; } else { const code = 'table.remove(' + list + ', ' + at + ')'; if (mode === 'GET_REMOVE') { - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; } else { // `mode` === 'REMOVE' return code + '\n'; } @@ -191,14 +194,14 @@ Lua['lists_getIndex'] = function(block) { } }; -Lua['lists_setIndex'] = function(block) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. - let list = Lua.valueToCode(block, 'LIST', Lua.ORDER_HIGH) || '{}'; + let list = generator.valueToCode(block, 'LIST', Order.HIGH) || '{}'; const mode = block.getFieldValue('MODE') || 'SET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const at = Lua.valueToCode(block, 'AT', Lua.ORDER_ADDITIVE) || '1'; - const value = Lua.valueToCode(block, 'TO', Lua.ORDER_NONE) || 'None'; + const at = generator.valueToCode(block, 'AT', Order.ADDITIVE) || '1'; + const value = generator.valueToCode(block, 'TO', Order.NONE) || 'None'; let code = ''; // If `list` would be evaluated more than once (which is the case for LAST, @@ -207,7 +210,8 @@ Lua['lists_setIndex'] = function(block) { !list.match(/^\w+$/)) { // `list` is an expression, so we may not evaluate it more than once. // We can use multiple statements. - const listVar = Lua.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + const listVar = + generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); code = listVar + ' = ' + list + '\n'; list = listVar; } @@ -223,13 +227,13 @@ Lua['lists_setIndex'] = function(block) { return code + '\n'; }; -Lua['lists_getSublist'] = function(block) { +export function lists_getSublist(block, generator) { // Get sublist. - const list = Lua.valueToCode(block, 'LIST', Lua.ORDER_NONE) || '{}'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); - const at1 = Lua.valueToCode(block, 'AT1', Lua.ORDER_NONE) || '1'; - const at2 = Lua.valueToCode(block, 'AT2', Lua.ORDER_NONE) || '1'; + const at1 = generator.valueToCode(block, 'AT1', Order.NONE) || '1'; + const at2 = generator.valueToCode(block, 'AT2', Order.NONE) || '1'; // The value for 'FROM_END' and'FROM_START' depends on `at` so // we add it as a parameter. @@ -237,9 +241,9 @@ Lua['lists_getSublist'] = function(block) { (where1 === 'FROM_END' || where1 === 'FROM_START') ? ', at1' : ''; const at2Param = (where2 === 'FROM_END' || where2 === 'FROM_START') ? ', at2' : ''; - const functionName = Lua.provideFunction_( + const functionName = generator.provideFunction_( 'list_sublist_' + where1.toLowerCase() + '_' + where2.toLowerCase(), ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(source${at1Param}${at2Param}) +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(source${at1Param}${at2Param}) local t = {} local start = ${getListIndex('source', where1, 'at1')} local finish = ${getListIndex('source', where2, 'at2')} @@ -255,17 +259,17 @@ end ((where1 === 'FROM_END' || where1 === 'FROM_START') ? ', ' + at1 : '') + ((where2 === 'FROM_END' || where2 === 'FROM_START') ? ', ' + at2 : '') + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['lists_sort'] = function(block) { +export function lists_sort(block, generator) { // Block for sorting a list. - const list = Lua.valueToCode(block, 'LIST', Lua.ORDER_NONE) || '{}'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); - const functionName = Lua.provideFunction_('list_sort', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(list, typev, direction) + const functionName = generator.provideFunction_('list_sort', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list, typev, direction) local t = {} for n,v in pairs(list) do table.insert(t, v) end local compareFuncs = { @@ -289,21 +293,22 @@ end const code = functionName + '(' + list + ',"' + type + '", ' + direction + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['lists_split'] = function(block) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. - let input = Lua.valueToCode(block, 'INPUT', Lua.ORDER_NONE); - const delimiter = Lua.valueToCode(block, 'DELIM', Lua.ORDER_NONE) || "''"; + let input = generator.valueToCode(block, 'INPUT', Order.NONE); + const delimiter = + generator.valueToCode(block, 'DELIM', Order.NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { if (!input) { input = "''"; } - functionName = Lua.provideFunction_('list_string_split', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(input, delim) + functionName = generator.provideFunction_('list_string_split', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(input, delim) local t = {} local pos = 1 while true do @@ -328,14 +333,14 @@ end throw Error('Unknown mode: ' + mode); } const code = functionName + '(' + input + ', ' + delimiter + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['lists_reverse'] = function(block) { +export function lists_reverse(block, generator) { // Block for reversing a list. - const list = Lua.valueToCode(block, 'LIST', Lua.ORDER_NONE) || '{}'; - const functionName = Lua.provideFunction_('list_reverse', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(input) + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; + const functionName = generator.provideFunction_('list_reverse', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(input) local reversed = {} for i = #input, 1, -1 do table.insert(reversed, input[i]) @@ -344,5 +349,5 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(input) end `); const code = functionName + '(' + list + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; diff --git a/generators/lua/logic.js b/generators/lua/logic.js index 53adbeb2b..01c6b6c18 100644 --- a/generators/lua/logic.js +++ b/generators/lua/logic.js @@ -7,39 +7,43 @@ /** * @fileoverview Generating Lua for logic blocks. */ -'use strict'; -goog.module('Blockly.Lua.logic'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.logic'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {Order} from './lua_generator.js'; -Lua['controls_if'] = function(block) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = ''; - if (Lua.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - code += Lua.injectId(Lua.STATEMENT_PREFIX, block); + code += generator.injectId(generator.STATEMENT_PREFIX, block); } do { const conditionCode = - Lua.valueToCode(block, 'IF' + n, Lua.ORDER_NONE) || 'false'; - let branchCode = Lua.statementToCode(block, 'DO' + n); - if (Lua.STATEMENT_SUFFIX) { - branchCode = Lua.prefixLines( - Lua.injectId(Lua.STATEMENT_SUFFIX, block), Lua.INDENT) + branchCode; + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false'; + let branchCode = generator.statementToCode(block, 'DO' + n); + if (generator.STATEMENT_SUFFIX) { + branchCode = generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += (n > 0 ? 'else' : '') + 'if ' + conditionCode + ' then\n' + branchCode; n++; } while (block.getInput('IF' + n)); - if (block.getInput('ELSE') || Lua.STATEMENT_SUFFIX) { - let branchCode = Lua.statementToCode(block, 'ELSE'); - if (Lua.STATEMENT_SUFFIX) { - branchCode = Lua.prefixLines( - Lua.injectId(Lua.STATEMENT_SUFFIX, block), Lua.INDENT) + + if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { + let branchCode = generator.statementToCode(block, 'ELSE'); + if (generator.STATEMENT_SUFFIX) { + branchCode = + generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += 'else\n' + branchCode; @@ -47,25 +51,27 @@ Lua['controls_if'] = function(block) { return code + 'end\n'; }; -Lua['controls_ifelse'] = Lua['controls_if']; +export const controls_ifelse = controls_if; -Lua['logic_compare'] = function(block) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '~=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; const operator = OPERATORS[block.getFieldValue('OP')]; - const argument0 = Lua.valueToCode(block, 'A', Lua.ORDER_RELATIONAL) || '0'; - const argument1 = Lua.valueToCode(block, 'B', Lua.ORDER_RELATIONAL) || '0'; + const argument0 = + generator.valueToCode(block, 'A', Order.RELATIONAL) || '0'; + const argument1 = + generator.valueToCode(block, 'B', Order.RELATIONAL) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; - return [code, Lua.ORDER_RELATIONAL]; + return [code, Order.RELATIONAL]; }; -Lua['logic_operation'] = function(block) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? 'and' : 'or'; - const order = (operator === 'and') ? Lua.ORDER_AND : Lua.ORDER_OR; - let argument0 = Lua.valueToCode(block, 'A', order); - let argument1 = Lua.valueToCode(block, 'B', order); + const order = (operator === 'and') ? Order.AND : Order.OR; + let argument0 = generator.valueToCode(block, 'A', order); + let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { // If there are no arguments, then the return value is false. argument0 = 'false'; @@ -84,29 +90,31 @@ Lua['logic_operation'] = function(block) { return [code, order]; }; -Lua['logic_negate'] = function(block) { +export function logic_negate(block, generator) { // Negation. - const argument0 = Lua.valueToCode(block, 'BOOL', Lua.ORDER_UNARY) || 'true'; + const argument0 = + generator.valueToCode(block, 'BOOL', Order.UNARY) || 'true'; const code = 'not ' + argument0; - return [code, Lua.ORDER_UNARY]; + return [code, Order.UNARY]; }; -Lua['logic_boolean'] = function(block) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; - return [code, Lua.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -Lua['logic_null'] = function(block) { +export function logic_null(block, generator) { // Null data type. - return ['nil', Lua.ORDER_ATOMIC]; + return ['nil', Order.ATOMIC]; }; -Lua['logic_ternary'] = function(block) { +export function logic_ternary(block, generator) { // Ternary operator. - const value_if = Lua.valueToCode(block, 'IF', Lua.ORDER_AND) || 'false'; - const value_then = Lua.valueToCode(block, 'THEN', Lua.ORDER_AND) || 'nil'; - const value_else = Lua.valueToCode(block, 'ELSE', Lua.ORDER_OR) || 'nil'; + const value_if = generator.valueToCode(block, 'IF', Order.AND) || 'false'; + const value_then = + generator.valueToCode(block, 'THEN', Order.AND) || 'nil'; + const value_else = generator.valueToCode(block, 'ELSE', Order.OR) || 'nil'; const code = value_if + ' and ' + value_then + ' or ' + value_else; - return [code, Lua.ORDER_OR]; + return [code, Order.OR]; }; diff --git a/generators/lua/loops.js b/generators/lua/loops.js index 525f1a7d3..3c613cb98 100644 --- a/generators/lua/loops.js +++ b/generators/lua/loops.js @@ -7,13 +7,13 @@ /** * @fileoverview Generating Lua for loop blocks. */ -'use strict'; -goog.module('Blockly.Lua.loops'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.loops'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './lua_generator.js'; /** @@ -31,18 +31,19 @@ const CONTINUE_STATEMENT = 'goto continue\n'; * blockToCode. * * @param {string} branch Generated code of the loop body + * @param {string} indent Whitespace by which to indent a continue statement. * @return {string} Generated label or '' if unnecessary */ -const addContinueLabel = function(branch) { +function addContinueLabel(branch, indent) { if (branch.indexOf(CONTINUE_STATEMENT) !== -1) { // False positives are possible (e.g. a string literal), but are harmless. - return branch + Lua.INDENT + '::continue::\n'; + return branch + indent + '::continue::\n'; } else { return branch; } }; -Lua['controls_repeat_ext'] = function(block) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -50,50 +51,51 @@ Lua['controls_repeat_ext'] = function(block) { repeats = String(Number(block.getFieldValue('TIMES'))); } else { // External number. - repeats = Lua.valueToCode(block, 'TIMES', Lua.ORDER_NONE) || '0'; + repeats = generator.valueToCode(block, 'TIMES', Order.NONE) || '0'; } if (stringUtils.isNumber(repeats)) { repeats = parseInt(repeats, 10); } else { repeats = 'math.floor(' + repeats + ')'; } - let branch = Lua.statementToCode(block, 'DO'); - branch = Lua.addLoopTrap(branch, block); - branch = addContinueLabel(branch); - const loopVar = Lua.nameDB_.getDistinctName('count', NameType.VARIABLE); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); + branch = addContinueLabel(branch, generator.INDENT); + const loopVar = generator.nameDB_.getDistinctName('count', NameType.VARIABLE); const code = 'for ' + loopVar + ' = 1, ' + repeats + ' do\n' + branch + 'end\n'; return code; }; -Lua['controls_repeat'] = Lua['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -Lua['controls_whileUntil'] = function(block) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = - Lua.valueToCode( - block, 'BOOL', until ? Lua.ORDER_UNARY : Lua.ORDER_NONE) || + generator.valueToCode( + block, 'BOOL', until ? Order.UNARY : Order.NONE) || 'false'; - let branch = Lua.statementToCode(block, 'DO'); - branch = Lua.addLoopTrap(branch, block); - branch = addContinueLabel(branch); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); + branch = addContinueLabel(branch, generator.INDENT); if (until) { argument0 = 'not ' + argument0; } return 'while ' + argument0 + ' do\n' + branch + 'end\n'; }; -Lua['controls_for'] = function(block) { +export function controls_for(block, generator) { // For loop. const variable0 = - Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const startVar = Lua.valueToCode(block, 'FROM', Lua.ORDER_NONE) || '0'; - const endVar = Lua.valueToCode(block, 'TO', Lua.ORDER_NONE) || '0'; - const increment = Lua.valueToCode(block, 'BY', Lua.ORDER_NONE) || '1'; - let branch = Lua.statementToCode(block, 'DO'); - branch = Lua.addLoopTrap(branch, block); - branch = addContinueLabel(branch); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + const startVar = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const endVar = generator.valueToCode(block, 'TO', Order.NONE) || '0'; + const increment = generator.valueToCode(block, 'BY', Order.NONE) || '1'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); + branch = addContinueLabel(branch, generator.INDENT); let code = ''; let incValue; if (stringUtils.isNumber(startVar) && stringUtils.isNumber(endVar) && @@ -107,7 +109,8 @@ Lua['controls_for'] = function(block) { // Determine loop direction at start, in case one of the bounds // changes during loop execution. incValue = - Lua.nameDB_.getDistinctName(variable0 + '_inc', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_inc', NameType.VARIABLE); code += incValue + ' = '; if (stringUtils.isNumber(increment)) { code += Math.abs(increment) + '\n'; @@ -115,7 +118,7 @@ Lua['controls_for'] = function(block) { code += 'math.abs(' + increment + ')\n'; } code += 'if (' + startVar + ') > (' + endVar + ') then\n'; - code += Lua.INDENT + incValue + ' = -' + incValue + '\n'; + code += generator.INDENT + incValue + ' = -' + incValue + '\n'; code += 'end\n'; } code += @@ -124,38 +127,39 @@ Lua['controls_for'] = function(block) { return code; }; -Lua['controls_forEach'] = function(block) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = - Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const argument0 = Lua.valueToCode(block, 'LIST', Lua.ORDER_NONE) || '{}'; - let branch = Lua.statementToCode(block, 'DO'); - branch = Lua.addLoopTrap(branch, block); - branch = addContinueLabel(branch); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + const argument0 = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); + branch = addContinueLabel(branch, generator.INDENT); const code = 'for _, ' + variable0 + ' in ipairs(' + argument0 + ') do \n' + branch + 'end\n'; return code; }; -Lua['controls_flow_statements'] = function(block) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; - if (Lua.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - xfix += Lua.injectId(Lua.STATEMENT_PREFIX, block); + xfix += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (Lua.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. - xfix += Lua.injectId(Lua.STATEMENT_SUFFIX, block); + xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } - if (Lua.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { const loop = block.getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. - xfix += Lua.injectId(Lua.STATEMENT_PREFIX, loop); + xfix += generator.injectId(generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { diff --git a/generators/lua/lua_generator.js b/generators/lua/lua_generator.js new file mode 100644 index 000000000..587fea90b --- /dev/null +++ b/generators/lua/lua_generator.js @@ -0,0 +1,213 @@ +/** + * @license + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating Lua for blocks. + * Based on Ellen Spertus's blocky-lua project. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua'); + +import * as stringUtils from '../../core/utils/string.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * http://www.lua.org/manual/5.3/manual.html#3.4.8 + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // literals + // The next level was not explicit in documentation and inferred by Ellen. + HIGH: 1, // Function calls, tables[] + EXPONENTIATION: 2, // ^ + UNARY: 3, // not # - ~ + MULTIPLICATIVE: 4, // * / % + ADDITIVE: 5, // + - + CONCATENATION: 6, // .. + RELATIONAL: 7, // < > <= >= ~= == + AND: 8, // and + OR: 9, // or + NONE: 99, +}; + +/** + * Lua code generator class. + * + * Note: Lua is not supporting zero-indexing since the language itself is + * one-indexed, so the generator does not repoct the oneBasedIndex configuration + * option used for lists and text. + */ +export class LuaGenerator extends CodeGenerator { + constructor(name) { + super(name ?? 'Lua'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // Special character + '_,' + + // From theoriginalbit's script: + // https://github.com/espertus/blockly-lua/issues/6 + '__inext,assert,bit,colors,colours,coroutine,disk,dofile,error,fs,' + + 'fetfenv,getmetatable,gps,help,io,ipairs,keys,loadfile,loadstring,math,' + + 'native,next,os,paintutils,pairs,parallel,pcall,peripheral,print,' + + 'printError,rawequal,rawget,rawset,read,rednet,redstone,rs,select,' + + 'setfenv,setmetatable,sleep,string,table,term,textutils,tonumber,' + + 'tostring,turtle,type,unpack,vector,write,xpcall,_VERSION,__indext,' + + // Not included in the script, probably because it wasn't enabled: + 'HTTP,' + + // Keywords (http://www.lua.org/pil/1.3.html). + 'and,break,do,else,elseif,end,false,for,function,if,in,local,nil,not,' + + 'or,repeat,return,then,true,until,while,' + + // Metamethods (http://www.lua.org/manual/5.2/manual.html). + 'add,sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call,' + + // Basic functions (http://www.lua.org/manual/5.2/manual.html, + // section 6.1). + 'assert,collectgarbage,dofile,error,_G,getmetatable,inpairs,load,' + + 'loadfile,next,pairs,pcall,print,rawequal,rawget,rawlen,rawset,select,' + + 'setmetatable,tonumber,tostring,type,_VERSION,xpcall,' + + // Modules (http://www.lua.org/manual/5.2/manual.html, section 6.3). + 'require,package,string,table,math,bit32,io,file,os,debug' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + // Call Blockly.CodeGenerator's init. + super.init(); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + this.isInitialized = true; + }; + + /** + * Prepend the generated code with the variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const definitions = Object.values(this.definitions_); + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + return definitions.join('\n\n') + '\n\n\n' + code; + }; + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. In Lua, an expression is not a legal statement, so we must assign + * the value to the (conventionally ignored) _. + * http://lua-users.org/wiki/ExpressionsAsStatements + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return 'local _ = ' + line + '\n'; + }; + + /** + * Encode a string as a properly escaped Lua string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} Lua string. + * @protected + */ + quote_(string) { + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + }; + + /** + * Encode a string as a properly escaped multiline Lua string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} Lua string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // .. '\n' .. + return lines.join(' .. \'\\n\' ..\n'); + }; + + /** + * Common tasks for generating Lua from blocks. + * Handles comments for the specified block and any connected value blocks. + * Calls any statements following this block. + * @param {!Block} block The current block. + * @param {string} code The Lua code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this statement. + * @return {string} Lua code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment, '-- ') + '\n'; + } + // Collect comments for all value arguments. + // Don't collect comments for nested statements. + for (let i = 0; i < block.inputList.length; i++) { + if (block.inputList[i].type === inputTypes.VALUE) { + const childBlock = block.inputList[i].connection.targetBlock(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '-- '); + } + } + } + } + } + const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + }; +} diff --git a/generators/lua/math.js b/generators/lua/math.js index e14243ed3..eb9f55a39 100644 --- a/generators/lua/math.js +++ b/generators/lua/math.js @@ -7,56 +7,56 @@ /** * @fileoverview Generating Lua for math blocks. */ -'use strict'; -goog.module('Blockly.Lua.math'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.math'); -const {NameType} = goog.require('Blockly.Names'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {NameType} from '../../core/names.js'; +import {Order} from './lua_generator.js'; -Lua['math_number'] = function(block) { +export function math_number(block, generator) { // Numeric value. const code = Number(block.getFieldValue('NUM')); - const order = code < 0 ? Lua.ORDER_UNARY : Lua.ORDER_ATOMIC; + const order = code < 0 ? Order.UNARY : Order.ATOMIC; return [code, order]; }; -Lua['math_arithmetic'] = function(block) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { - 'ADD': [' + ', Lua.ORDER_ADDITIVE], - 'MINUS': [' - ', Lua.ORDER_ADDITIVE], - 'MULTIPLY': [' * ', Lua.ORDER_MULTIPLICATIVE], - 'DIVIDE': [' / ', Lua.ORDER_MULTIPLICATIVE], - 'POWER': [' ^ ', Lua.ORDER_EXPONENTIATION], + 'ADD': [' + ', Order.ADDITIVE], + 'MINUS': [' - ', Order.ADDITIVE], + 'MULTIPLY': [' * ', Order.MULTIPLICATIVE], + 'DIVIDE': [' / ', Order.MULTIPLICATIVE], + 'POWER': [' ^ ', Order.EXPONENTIATION], }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; const order = tuple[1]; - const argument0 = Lua.valueToCode(block, 'A', order) || '0'; - const argument1 = Lua.valueToCode(block, 'B', order) || '0'; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + operator + argument1; return [code, order]; }; -Lua['math_single'] = function(block) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let arg; if (operator === 'NEG') { // Negation is a special case given its different operator precedence. - arg = Lua.valueToCode(block, 'NUM', Lua.ORDER_UNARY) || '0'; - return ['-' + arg, Lua.ORDER_UNARY]; + arg = generator.valueToCode(block, 'NUM', Order.UNARY) || '0'; + return ['-' + arg, Order.UNARY]; } if (operator === 'POW10') { - arg = Lua.valueToCode(block, 'NUM', Lua.ORDER_EXPONENTIATION) || '0'; - return ['10 ^ ' + arg, Lua.ORDER_EXPONENTIATION]; + arg = generator.valueToCode(block, 'NUM', Order.EXPONENTIATION) || '0'; + return ['10 ^ ' + arg, Order.EXPONENTIATION]; } if (operator === 'ROUND') { - arg = Lua.valueToCode(block, 'NUM', Lua.ORDER_ADDITIVE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.ADDITIVE) || '0'; } else { - arg = Lua.valueToCode(block, 'NUM', Lua.ORDER_NONE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; } let code; @@ -107,43 +107,43 @@ Lua['math_single'] = function(block) { default: throw Error('Unknown math operator: ' + operator); } - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['math_constant'] = function(block) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { - 'PI': ['math.pi', Lua.ORDER_HIGH], - 'E': ['math.exp(1)', Lua.ORDER_HIGH], - 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Lua.ORDER_MULTIPLICATIVE], - 'SQRT2': ['math.sqrt(2)', Lua.ORDER_HIGH], - 'SQRT1_2': ['math.sqrt(1 / 2)', Lua.ORDER_HIGH], - 'INFINITY': ['math.huge', Lua.ORDER_HIGH], + 'PI': ['math.pi', Order.HIGH], + 'E': ['math.exp(1)', Order.HIGH], + 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Order.MULTIPLICATIVE], + 'SQRT2': ['math.sqrt(2)', Order.HIGH], + 'SQRT1_2': ['math.sqrt(1 / 2)', Order.HIGH], + 'INFINITY': ['math.huge', Order.HIGH], }; return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -Lua['math_number_property'] = function(block) { +export function math_number_property(block, generator) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. const PROPERTIES = { - 'EVEN': [' % 2 == 0', Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], - 'ODD': [' % 2 == 1', Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], - 'WHOLE': [' % 1 == 0', Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], - 'POSITIVE': [' > 0', Lua.ORDER_RELATIONAL, Lua.ORDER_RELATIONAL], - 'NEGATIVE': [' < 0', Lua.ORDER_RELATIONAL, Lua.ORDER_RELATIONAL], - 'DIVISIBLE_BY': [null, Lua.ORDER_MULTIPLICATIVE, Lua.ORDER_RELATIONAL], - 'PRIME': [null, Lua.ORDER_NONE, Lua.ORDER_HIGH], + 'EVEN': [' % 2 == 0', Order.MULTIPLICATIVE, Order.RELATIONAL], + 'ODD': [' % 2 == 1', Order.MULTIPLICATIVE, Order.RELATIONAL], + 'WHOLE': [' % 1 == 0', Order.MULTIPLICATIVE, Order.RELATIONAL], + 'POSITIVE': [' > 0', Order.RELATIONAL, Order.RELATIONAL], + 'NEGATIVE': [' < 0', Order.RELATIONAL, Order.RELATIONAL], + 'DIVISIBLE_BY': [null, Order.MULTIPLICATIVE, Order.RELATIONAL], + 'PRIME': [null, Order.NONE, Order.HIGH], }; const dropdownProperty = block.getFieldValue('PROPERTY'); const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; - const numberToCheck = Lua.valueToCode(block, 'NUMBER_TO_CHECK', + const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = Lua.provideFunction_('math_isPrime', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(n) + const functionName = generator.provideFunction_('math_isPrime', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(n) -- https://en.wikipedia.org/wiki/Primality_test#Naive_methods if n == 2 or n == 3 then return true @@ -164,12 +164,12 @@ end `); code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = Lua.valueToCode(block, 'DIVISOR', - Lua.ORDER_MULTIPLICATIVE) || '0'; - // If 'divisor' is some code that evals to 0, Lua will produce a nan. + const divisor = generator.valueToCode(block, 'DIVISOR', + Order.MULTIPLICATIVE) || '0'; + // If 'divisor' is some code that evals to 0, generator will produce a nan. // Let's produce nil if we can determine this at compile-time. if (divisor === '0') { - return ['nil', Lua.ORDER_ATOMIC]; + return ['nil', Order.ATOMIC]; } // The normal trick to implement ?: with and/or doesn't work here: // divisor == 0 and nil or number_to_check % divisor == 0 @@ -181,29 +181,31 @@ end return [code, outputOrder]; }; -Lua['math_change'] = function(block) { +export function math_change(block, generator) { // Add to a variable in place. - const argument0 = Lua.valueToCode(block, 'DELTA', Lua.ORDER_ADDITIVE) || '0'; + const argument0 = + generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; const varName = - Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = ' + varName + ' + ' + argument0 + '\n'; }; // Rounding functions have a single operand. -Lua['math_round'] = Lua['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -Lua['math_trig'] = Lua['math_single']; +export const math_trig = math_single; -Lua['math_on_list'] = function(block) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); - const list = Lua.valueToCode(block, 'LIST', Lua.ORDER_NONE) || '{}'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '{}'; let functionName; // Functions needed in more than one case. function provideSum() { - return Lua.provideFunction_('math_sum', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + return generator.provideFunction_('math_sum', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) local result = 0 for _, v in ipairs(t) do result = result + v @@ -220,8 +222,8 @@ end case 'MIN': // Returns 0 for the empty list. - functionName = Lua.provideFunction_('math_min', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + functionName = generator.provideFunction_('math_min', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) if #t == 0 then return 0 end @@ -238,8 +240,8 @@ end case 'AVERAGE': // Returns 0 for the empty list. - functionName = Lua.provideFunction_('math_average', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + functionName = generator.provideFunction_('math_average', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) if #t == 0 then return 0 end @@ -250,8 +252,8 @@ end case 'MAX': // Returns 0 for the empty list. - functionName = Lua.provideFunction_('math_max', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + functionName = generator.provideFunction_('math_max', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) if #t == 0 then return 0 end @@ -268,8 +270,8 @@ end case 'MEDIAN': // This operation excludes non-numbers. - functionName = Lua.provideFunction_('math_median', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + functionName = generator.provideFunction_('math_median', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) -- Source: http://lua-users.org/wiki/SimpleStats if #t == 0 then return 0 @@ -293,9 +295,9 @@ end case 'MODE': // As a list of numbers can contain more than one mode, // the returned result is provided as an array. - // The Lua version includes non-numbers. - functionName = Lua.provideFunction_('math_modes', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + // The generator version includes non-numbers. + functionName = generator.provideFunction_('math_modes', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) -- Source: http://lua-users.org/wiki/SimpleStats local counts = {} for _, v in ipairs(t) do @@ -323,8 +325,8 @@ end break; case 'STD_DEV': - functionName = Lua.provideFunction_('math_standard_deviation', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + functionName = generator.provideFunction_('math_standard_deviation', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) local m local vm local total = 0 @@ -345,8 +347,8 @@ end break; case 'RANDOM': - functionName = Lua.provideFunction_('math_random_list', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(t) + functionName = generator.provideFunction_('math_random_list', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(t) if #t == 0 then return nil end @@ -358,49 +360,49 @@ end default: throw Error('Unknown operator: ' + func); } - return [functionName + '(' + list + ')', Lua.ORDER_HIGH]; + return [functionName + '(' + list + ')', Order.HIGH]; }; -Lua['math_modulo'] = function(block) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = - Lua.valueToCode(block, 'DIVIDEND', Lua.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || '0'; const argument1 = - Lua.valueToCode(block, 'DIVISOR', Lua.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'DIVISOR', Order.MULTIPLICATIVE) || '0'; const code = argument0 + ' % ' + argument1; - return [code, Lua.ORDER_MULTIPLICATIVE]; + return [code, Order.MULTIPLICATIVE]; }; -Lua['math_constrain'] = function(block) { +export function math_constrain(block, generator) { // Constrain a number between two limits. - const argument0 = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const argument1 = - Lua.valueToCode(block, 'LOW', Lua.ORDER_NONE) || '-math.huge'; + generator.valueToCode(block, 'LOW', Order.NONE) || '-math.huge'; const argument2 = - Lua.valueToCode(block, 'HIGH', Lua.ORDER_NONE) || 'math.huge'; + generator.valueToCode(block, 'HIGH', Order.NONE) || 'math.huge'; const code = 'math.min(math.max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['math_random_int'] = function(block) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. - const argument0 = Lua.valueToCode(block, 'FROM', Lua.ORDER_NONE) || '0'; - const argument1 = Lua.valueToCode(block, 'TO', Lua.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; const code = 'math.random(' + argument0 + ', ' + argument1 + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['math_random_float'] = function(block) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. - return ['math.random()', Lua.ORDER_HIGH]; + return ['math.random()', Order.HIGH]; }; -Lua['math_atan2'] = function(block) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. - const argument0 = Lua.valueToCode(block, 'X', Lua.ORDER_NONE) || '0'; - const argument1 = Lua.valueToCode(block, 'Y', Lua.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; return [ - 'math.deg(math.atan2(' + argument1 + ', ' + argument0 + '))', Lua.ORDER_HIGH + 'math.deg(math.atan2(' + argument1 + ', ' + argument0 + '))', Order.HIGH ]; }; diff --git a/generators/lua/procedures.js b/generators/lua/procedures.js index a204855a7..cff655aae 100644 --- a/generators/lua/procedures.js +++ b/generators/lua/procedures.js @@ -7,99 +7,104 @@ /** * @fileoverview Generating Lua for procedure blocks. */ -'use strict'; -goog.module('Blockly.Lua.procedures'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.procedures'); -const {NameType} = goog.require('Blockly.Names'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {NameType} from '../../core/names.js'; +import {Order} from './lua_generator.js'; -Lua['procedures_defreturn'] = function(block) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. const funcName = - Lua.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); let xfix1 = ''; - if (Lua.STATEMENT_PREFIX) { - xfix1 += Lua.injectId(Lua.STATEMENT_PREFIX, block); + if (generator.STATEMENT_PREFIX) { + xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (Lua.STATEMENT_SUFFIX) { - xfix1 += Lua.injectId(Lua.STATEMENT_SUFFIX, block); + if (generator.STATEMENT_SUFFIX) { + xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (xfix1) { - xfix1 = Lua.prefixLines(xfix1, Lua.INDENT); + xfix1 = generator.prefixLines(xfix1, generator.INDENT); } let loopTrap = ''; - if (Lua.INFINITE_LOOP_TRAP) { - loopTrap = Lua.prefixLines( - Lua.injectId(Lua.INFINITE_LOOP_TRAP, block), Lua.INDENT); + if (generator.INFINITE_LOOP_TRAP) { + loopTrap = generator.prefixLines( + generator.injectId( + generator.INFINITE_LOOP_TRAP, block), generator.INDENT); } - let branch = Lua.statementToCode(block, 'STACK'); - let returnValue = Lua.valueToCode(block, 'RETURN', Lua.ORDER_NONE) || ''; + let branch = generator.statementToCode(block, 'STACK'); + let returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. xfix2 = xfix1; } if (returnValue) { - returnValue = Lua.INDENT + 'return ' + returnValue + '\n'; + returnValue = generator.INDENT + 'return ' + returnValue + '\n'; } else if (!branch) { branch = ''; } const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = Lua.nameDB_.getName(variables[i], NameType.VARIABLE); + args[i] = generator.nameDB_.getName(variables[i], NameType.VARIABLE); } let code = 'function ' + funcName + '(' + args.join(', ') + ')\n' + xfix1 + loopTrap + branch + xfix2 + returnValue + 'end\n'; - code = Lua.scrub_(block, code); + code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. - Lua.definitions_['%' + funcName] = code; + generator.definitions_['%' + funcName] = code; return null; }; // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -Lua['procedures_defnoreturn'] = Lua['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -Lua['procedures_callreturn'] = function(block) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = - Lua.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = Lua.valueToCode(block, 'ARG' + i, Lua.ORDER_NONE) || 'nil'; + args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'nil'; } const code = funcName + '(' + args.join(', ') + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['procedures_callnoreturn'] = function(block) { +export function procedures_callnoreturn(block, generator) { // Call a procedure with no return value. // Generated code is for a function call as a statement is the same as a // function call as a value, with the addition of line ending. - const tuple = Lua['procedures_callreturn'](block); + const tuple = generator.forBlock['procedures_callreturn'](block, generator); return tuple[0] + '\n'; }; -Lua['procedures_ifreturn'] = function(block) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = - Lua.valueToCode(block, 'CONDITION', Lua.ORDER_NONE) || 'false'; + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; let code = 'if ' + condition + ' then\n'; - if (Lua.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. code += - Lua.prefixLines(Lua.injectId(Lua.STATEMENT_SUFFIX, block), Lua.INDENT); + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT); } if (block.hasReturnValue_) { - const value = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || 'nil'; - code += Lua.INDENT + 'return ' + value + '\n'; + const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'nil'; + code += generator.INDENT + 'return ' + value + '\n'; } else { - code += Lua.INDENT + 'return\n'; + code += generator.INDENT + 'return\n'; } code += 'end\n'; return code; diff --git a/generators/lua/text.js b/generators/lua/text.js index 9dcc08b3b..2cf13f7c6 100644 --- a/generators/lua/text.js +++ b/generators/lua/text.js @@ -7,82 +7,84 @@ /** * @fileoverview Generating Lua for text blocks. */ -'use strict'; -goog.module('Blockly.Lua.texts'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.texts'); -const {NameType} = goog.require('Blockly.Names'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {NameType} from '../../core/names.js'; +import {Order} from './lua_generator.js'; -Lua['text'] = function(block) { +export function text(block, generator) { // Text value. - const code = Lua.quote_(block.getFieldValue('TEXT')); - return [code, Lua.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('TEXT')); + return [code, Order.ATOMIC]; }; -Lua['text_multiline'] = function(block) { +export function text_multiline(block, generator) { // Text value. - const code = Lua.multiline_quote_(block.getFieldValue('TEXT')); + const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = - code.indexOf('..') !== -1 ? Lua.ORDER_CONCATENATION : Lua.ORDER_ATOMIC; + code.indexOf('..') !== -1 ? Order.CONCATENATION : Order.ATOMIC; return [code, order]; }; -Lua['text_join'] = function(block) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. if (block.itemCount_ === 0) { - return ["''", Lua.ORDER_ATOMIC]; + return ["''", Order.ATOMIC]; } else if (block.itemCount_ === 1) { - const element = Lua.valueToCode(block, 'ADD0', Lua.ORDER_NONE) || "''"; + const element = generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; const code = 'tostring(' + element + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; } else if (block.itemCount_ === 2) { const element0 = - Lua.valueToCode(block, 'ADD0', Lua.ORDER_CONCATENATION) || "''"; + generator.valueToCode(block, 'ADD0', Order.CONCATENATION) || "''"; const element1 = - Lua.valueToCode(block, 'ADD1', Lua.ORDER_CONCATENATION) || "''"; + generator.valueToCode(block, 'ADD1', Order.CONCATENATION) || "''"; const code = element0 + ' .. ' + element1; - return [code, Lua.ORDER_CONCATENATION]; + return [code, Order.CONCATENATION]; } else { const elements = []; for (let i = 0; i < block.itemCount_; i++) { - elements[i] = Lua.valueToCode(block, 'ADD' + i, Lua.ORDER_NONE) || "''"; + elements[i] = + generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''"; } const code = 'table.concat({' + elements.join(', ') + '})'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; } }; -Lua['text_append'] = function(block) { +export function text_append(block, generator) { // Append to a variable in place. const varName = - Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const value = - Lua.valueToCode(block, 'TEXT', Lua.ORDER_CONCATENATION) || "''"; + generator.valueToCode(block, 'TEXT', Order.CONCATENATION) || "''"; return varName + ' = ' + varName + ' .. ' + value + '\n'; }; -Lua['text_length'] = function(block) { +export function text_length(block, generator) { // String or array length. - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || "''"; - return ['#' + text, Lua.ORDER_UNARY]; + const text = generator.valueToCode(block, 'VALUE', Order.UNARY) || "''"; + return ['#' + text, Order.UNARY]; }; -Lua['text_isEmpty'] = function(block) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_UNARY) || "''"; - return ['#' + text + ' == 0', Lua.ORDER_RELATIONAL]; + const text = generator.valueToCode(block, 'VALUE', Order.UNARY) || "''"; + return ['#' + text + ' == 0', Order.RELATIONAL]; }; -Lua['text_indexOf'] = function(block) { +export function text_indexOf(block, generator) { // Search the text for a substring. - const substring = Lua.valueToCode(block, 'FIND', Lua.ORDER_NONE) || "''"; - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || "''"; + const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; let functionName; if (block.getFieldValue('END') === 'FIRST') { - functionName = Lua.provideFunction_('firstIndexOf', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, substr) + functionName = generator.provideFunction_('firstIndexOf', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str, substr) local i = string.find(str, substr, 1, true) if i == nil then return 0 @@ -91,8 +93,8 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, substr) end `); } else { - functionName = Lua.provideFunction_('lastIndexOf', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, substr) + functionName = generator.provideFunction_('lastIndexOf', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str, substr) local i = string.find(string.reverse(str), string.reverse(substr), 1, true) if i then return #str + 2 - i - #substr @@ -102,20 +104,20 @@ end `); } const code = functionName + '(' + text + ', ' + substring + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_charAt'] = function(block) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; - const atOrder = (where === 'FROM_END') ? Lua.ORDER_UNARY : Lua.ORDER_NONE; - const at = Lua.valueToCode(block, 'AT', atOrder) || '1'; - const text = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || "''"; + const atOrder = (where === 'FROM_END') ? Order.UNARY : Order.NONE; + const at = generator.valueToCode(block, 'AT', atOrder) || '1'; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; let code; if (where === 'RANDOM') { - const functionName = Lua.provideFunction_('text_random_letter', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str) + const functionName = generator.provideFunction_('text_random_letter', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str) local index = math.random(string.len(str)) return string.sub(str, index, index) end @@ -140,25 +142,25 @@ end code = 'string.sub(' + text + ', ' + start + ', ' + start + ')'; } else { // use function to avoid reevaluation - const functionName = Lua.provideFunction_('text_char_at', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str, index) + const functionName = generator.provideFunction_('text_char_at', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str, index) return string.sub(str, index, index) end `); code = functionName + '(' + text + ', ' + start + ')'; } } - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_getSubstring'] = function(block) { +export function text_getSubstring(block, generator) { // Get substring. - const text = Lua.valueToCode(block, 'STRING', Lua.ORDER_NONE) || "''"; + const text = generator.valueToCode(block, 'STRING', Order.NONE) || "''"; // Get start index. const where1 = block.getFieldValue('WHERE1'); - const at1Order = (where1 === 'FROM_END') ? Lua.ORDER_UNARY : Lua.ORDER_NONE; - const at1 = Lua.valueToCode(block, 'AT1', at1Order) || '1'; + const at1Order = (where1 === 'FROM_END') ? Order.UNARY : Order.NONE; + const at1 = generator.valueToCode(block, 'AT1', at1Order) || '1'; let start; if (where1 === 'FIRST') { start = 1; @@ -172,8 +174,8 @@ Lua['text_getSubstring'] = function(block) { // Get end index. const where2 = block.getFieldValue('WHERE2'); - const at2Order = (where2 === 'FROM_END') ? Lua.ORDER_UNARY : Lua.ORDER_NONE; - const at2 = Lua.valueToCode(block, 'AT2', at2Order) || '1'; + const at2Order = (where2 === 'FROM_END') ? Order.UNARY : Order.NONE; + const at2 = generator.valueToCode(block, 'AT2', at2Order) || '1'; let end; if (where2 === 'LAST') { end = -1; @@ -185,13 +187,13 @@ Lua['text_getSubstring'] = function(block) { throw Error('Unhandled option (text_getSubstring)'); } const code = 'string.sub(' + text + ', ' + start + ', ' + end + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_changeCase'] = function(block) { +export function text_changeCase(block, generator) { // Change capitalization. const operator = block.getFieldValue('CASE'); - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; let functionName; if (operator === 'UPPERCASE') { functionName = 'string.upper'; @@ -201,8 +203,8 @@ Lua['text_changeCase'] = function(block) { // There are shorter versions at // http://lua-users.org/wiki/SciteTitleCase // that do not preserve whitespace. - functionName = Lua.provideFunction_('text_titlecase', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(str) + functionName = generator.provideFunction_('text_titlecase', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(str) local buf = {} local inWord = false for i = 1, #str do @@ -222,37 +224,37 @@ end `); } const code = functionName + '(' + text + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_trim'] = function(block) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = {LEFT: '^%s*(,-)', RIGHT: '(.-)%s*$', BOTH: '^%s*(.-)%s*$'}; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const code = 'string.gsub(' + text + ', "' + operator + '", "%1")'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_print'] = function(block) { +export function text_print(block, generator) { // Print statement. - const msg = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; + const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ')\n'; }; -Lua['text_prompt_ext'] = function(block) { +export function text_prompt_ext(block, generator) { // Prompt function. let msg; if (block.getField('TEXT')) { // Internal message. - msg = Lua.quote_(block.getFieldValue('TEXT')); + msg = generator.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; + msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; } - const functionName = Lua.provideFunction_('text_prompt', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(msg) + const functionName = generator.provideFunction_('text_prompt', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(msg) io.write(msg) io.flush() return io.read() @@ -264,16 +266,16 @@ end if (toNumber) { code = 'tonumber(' + code + ', 10)'; } - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_prompt'] = Lua['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -Lua['text_count'] = function(block) { - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; - const sub = Lua.valueToCode(block, 'SUB', Lua.ORDER_NONE) || "''"; - const functionName = Lua.provideFunction_('text_count', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) +export function text_count(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; + const functionName = generator.provideFunction_('text_count', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) if #needle == 0 then return #haystack + 1 end @@ -291,15 +293,15 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) end `); const code = functionName + '(' + text + ', ' + sub + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_replace'] = function(block) { - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; - const from = Lua.valueToCode(block, 'FROM', Lua.ORDER_NONE) || "''"; - const to = Lua.valueToCode(block, 'TO', Lua.ORDER_NONE) || "''"; - const functionName = Lua.provideFunction_('text_replace', ` -function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) +export function text_replace(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; + const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; + const functionName = generator.provideFunction_('text_replace', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) local buf = {} local i = 1 while i <= #haystack do @@ -317,11 +319,11 @@ function ${Lua.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) end `); const code = functionName + '(' + text + ', ' + from + ', ' + to + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; -Lua['text_reverse'] = function(block) { - const text = Lua.valueToCode(block, 'TEXT', Lua.ORDER_NONE) || "''"; +export function text_reverse(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const code = 'string.reverse(' + text + ')'; - return [code, Lua.ORDER_HIGH]; + return [code, Order.HIGH]; }; diff --git a/generators/lua/variables.js b/generators/lua/variables.js index 8fa590945..f149f8841 100644 --- a/generators/lua/variables.js +++ b/generators/lua/variables.js @@ -7,25 +7,27 @@ /** * @fileoverview Generating Lua for variable blocks. */ -'use strict'; -goog.module('Blockly.Lua.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.variables'); -const {NameType} = goog.require('Blockly.Names'); -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); +import {NameType} from '../../core/names.js'; +import {Order} from './lua_generator.js'; -Lua['variables_get'] = function(block) { +export function variables_get(block, generator) { // Variable getter. const code = - Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - return [code, Lua.ORDER_ATOMIC]; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + return [code, Order.ATOMIC]; }; -Lua['variables_set'] = function(block) { +export function variables_set(block, generator) { // Variable setter. - const argument0 = Lua.valueToCode(block, 'VALUE', Lua.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const varName = - Lua.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = ' + argument0 + '\n'; }; diff --git a/generators/lua/variables_dynamic.js b/generators/lua/variables_dynamic.js index 78a92a354..b78368a21 100644 --- a/generators/lua/variables_dynamic.js +++ b/generators/lua/variables_dynamic.js @@ -7,15 +7,13 @@ /** * @fileoverview Generating Lua for dynamic variable blocks. */ -'use strict'; -goog.module('Blockly.Lua.variablesDynamic'); - -const {luaGenerator: Lua} = goog.require('Blockly.Lua'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Lua.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Lua.variablesDynamic'); // Lua is dynamically typed. -Lua['variables_get_dynamic'] = Lua['variables_get']; -Lua['variables_set_dynamic'] = Lua['variables_set']; +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/php.js b/generators/php.js index 043a3c6a7..666f081f7 100644 --- a/generators/php.js +++ b/generators/php.js @@ -1,302 +1,40 @@ /** * @license - * Copyright 2015 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating PHP for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating PHP for + * blocks. This is the entrypoint for php_compressed.js. + * @suppress {extraRequire} */ -'use strict'; -goog.module('Blockly.PHP'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.all'); -const stringUtils = goog.require('Blockly.utils.string'); -const {Block} = goog.requireType('Blockly.Block'); -const {CodeGenerator} = goog.require('Blockly.CodeGenerator'); -const {inputTypes} = goog.require('Blockly.inputTypes'); -const {Names} = goog.require('Blockly.Names'); -const {Workspace} = goog.requireType('Blockly.Workspace'); +import {PhpGenerator} from './php/php_generator.js'; +import * as colour from './php/colour.js'; +import * as lists from './php/lists.js'; +import * as logic from './php/logic.js'; +import * as loops from './php/loops.js'; +import * as math from './php/math.js'; +import * as procedures from './php/procedures.js'; +import * as text from './php/text.js'; +import * as variables from './php/variables.js'; +import * as variablesDynamic from './php/variables_dynamic.js'; +export * from './php/php_generator.js'; /** - * PHP code generator. - * @type {!CodeGenerator} + * Php code generator instance. + * @type {!PhpGenerator} */ -const PHP = new CodeGenerator('PHP'); +export const phpGenerator = new PhpGenerator(); -/** - * List of illegal variable names. - * This is not intended to be a security feature. Blockly is 100% client-side, - * so bypassing this list is trivial. This is intended to prevent users from - * accidentally clobbering a built-in object or function. - */ -PHP.addReservedWords( - // http://php.net/manual/en/reserved.keywords.php - '__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,' + - 'clone,const,continue,declare,default,die,do,echo,else,elseif,empty,' + - 'enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,extends,' + - 'final,for,foreach,function,global,goto,if,implements,include,' + - 'include_once,instanceof,insteadof,interface,isset,list,namespace,new,or,' + - 'print,private,protected,public,require,require_once,return,static,' + - 'switch,throw,trait,try,unset,use,var,while,xor,' + - // http://php.net/manual/en/reserved.constants.php - 'PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,' + - 'PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,' + - 'PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,' + - 'PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,' + - 'PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,PHP_SYSCONFDIR,' + - 'PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,PHP_CONFIG_FILE_SCAN_DIR,' + - 'PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,E_NOTICE,E_CORE_ERROR,' + - 'E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING,E_USER_ERROR,' + - 'E_USER_WARNING,E_USER_NOTICE,E_DEPRECATED,E_USER_DEPRECATED,E_ALL,' + - 'E_STRICT,__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,' + - '__FILE__,__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__'); - -/** - * Order of operation ENUMs. - * http://php.net/manual/en/language.operators.precedence.php - */ -PHP.ORDER_ATOMIC = 0; // 0 "" ... -PHP.ORDER_CLONE = 1; // clone -PHP.ORDER_NEW = 1; // new -PHP.ORDER_MEMBER = 2.1; // [] -PHP.ORDER_FUNCTION_CALL = 2.2; // () -PHP.ORDER_POWER = 3; // ** -PHP.ORDER_INCREMENT = 4; // ++ -PHP.ORDER_DECREMENT = 4; // -- -PHP.ORDER_BITWISE_NOT = 4; // ~ -PHP.ORDER_CAST = 4; // (int) (float) (string) (array) ... -PHP.ORDER_SUPPRESS_ERROR = 4; // @ -PHP.ORDER_INSTANCEOF = 5; // instanceof -PHP.ORDER_LOGICAL_NOT = 6; // ! -PHP.ORDER_UNARY_PLUS = 7.1; // + -PHP.ORDER_UNARY_NEGATION = 7.2; // - -PHP.ORDER_MULTIPLICATION = 8.1; // * -PHP.ORDER_DIVISION = 8.2; // / -PHP.ORDER_MODULUS = 8.3; // % -PHP.ORDER_ADDITION = 9.1; // + -PHP.ORDER_SUBTRACTION = 9.2; // - -PHP.ORDER_STRING_CONCAT = 9.3; // . -PHP.ORDER_BITWISE_SHIFT = 10; // << >> -PHP.ORDER_RELATIONAL = 11; // < <= > >= -PHP.ORDER_EQUALITY = 12; // == != === !== <> <=> -PHP.ORDER_REFERENCE = 13; // & -PHP.ORDER_BITWISE_AND = 13; // & -PHP.ORDER_BITWISE_XOR = 14; // ^ -PHP.ORDER_BITWISE_OR = 15; // | -PHP.ORDER_LOGICAL_AND = 16; // && -PHP.ORDER_LOGICAL_OR = 17; // || -PHP.ORDER_IF_NULL = 18; // ?? -PHP.ORDER_CONDITIONAL = 19; // ?: -PHP.ORDER_ASSIGNMENT = 20; // = += -= *= /= %= <<= >>= ... -PHP.ORDER_LOGICAL_AND_WEAK = 21; // and -PHP.ORDER_LOGICAL_XOR = 22; // xor -PHP.ORDER_LOGICAL_OR_WEAK = 23; // or -PHP.ORDER_NONE = 99; // (...) - -/** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ -PHP.ORDER_OVERRIDES = [ - // (foo()).bar() -> foo().bar() - // (foo())[0] -> foo()[0] - [PHP.ORDER_MEMBER, PHP.ORDER_FUNCTION_CALL], - // (foo[0])[1] -> foo[0][1] - // (foo.bar).baz -> foo.bar.baz - [PHP.ORDER_MEMBER, PHP.ORDER_MEMBER], - // !(!foo) -> !!foo - [PHP.ORDER_LOGICAL_NOT, PHP.ORDER_LOGICAL_NOT], - // a * (b * c) -> a * b * c - [PHP.ORDER_MULTIPLICATION, PHP.ORDER_MULTIPLICATION], - // a + (b + c) -> a + b + c - [PHP.ORDER_ADDITION, PHP.ORDER_ADDITION], - // a && (b && c) -> a && b && c - [PHP.ORDER_LOGICAL_AND, PHP.ORDER_LOGICAL_AND], - // a || (b || c) -> a || b || c - [PHP.ORDER_LOGICAL_OR, PHP.ORDER_LOGICAL_OR] -]; - -/** - * Whether the init method has been called. - * @type {?boolean} - */ -PHP.isInitialized = false; - -/** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - */ -PHP.init = function(workspace) { - // Call Blockly.CodeGenerator's init. - Object.getPrototypeOf(this).init.call(this); - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_, '$'); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - this.isInitialized = true; -}; - -/** - * Prepend the generated code with the variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ -PHP.finish = function(code) { - // Convert the definitions dictionary into a list. - const definitions = Object.values(this.definitions_); - // Call Blockly.CodeGenerator's finish. - code = Object.getPrototypeOf(this).finish.call(this, code); - this.isInitialized = false; - - this.nameDB_.reset(); - return definitions.join('\n\n') + '\n\n\n' + code; -}; - -/** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. A trailing semicolon is needed to make this legal. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ -PHP.scrubNakedValue = function(line) { - return line + ';\n'; -}; - -/** - * Encode a string as a properly escaped PHP string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} PHP string. - * @protected - */ -PHP.quote_ = function(string) { - string = string.replace(/\\/g, '\\\\') - .replace(/\n/g, '\\\n') - .replace(/'/g, '\\\''); - return '\'' + string + '\''; -}; - -/** - * Encode a string as a properly escaped multiline PHP string, complete with - * quotes. - * @param {string} string Text to encode. - * @return {string} PHP string. - * @protected - */ -PHP.multiline_quote_ = function(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // . "\n" . - // Newline escaping only works in double-quoted strings. - return lines.join(' . \"\\n\" .\n'); -}; - -/** - * Common tasks for generating PHP from blocks. - * Handles comments for the specified block and any connected value blocks. - * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The PHP code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} PHP code with comments and subsequent blocks added. - * @protected - */ -PHP.scrub_ = function(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment, '// ') + '\n'; - } - // Collect comments for all value arguments. - // Don't collect comments for nested statements. - for (let i = 0; i < block.inputList.length; i++) { - if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '// '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; -}; - -/** - * Gets a property and adjusts the value while taking into account indexing. - * @param {!Block} block The block. - * @param {string} atId The property ID of the element to get. - * @param {number=} opt_delta Value to add. - * @param {boolean=} opt_negate Whether to negate the value. - * @param {number=} opt_order The highest order acting on this value. - * @return {string|number} - */ -PHP.getAdjusted = function(block, atId, opt_delta, opt_negate, opt_order) { - let delta = opt_delta || 0; - let order = opt_order || this.ORDER_NONE; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - let defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - let outerOrder = order; - let innerOrder; - if (delta > 0) { - outerOrder = this.ORDER_ADDITION; - innerOrder = this.ORDER_ADDITION; - } else if (delta < 0) { - outerOrder = this.ORDER_SUBTRACTION; - innerOrder = this.ORDER_SUBTRACTION; - } else if (opt_negate) { - outerOrder = this.ORDER_UNARY_NEGATION; - innerOrder = this.ORDER_UNARY_NEGATION; - } - let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = Number(at) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = at + ' + ' + delta; - } else if (delta < 0) { - at = at + ' - ' + -delta; - } - if (opt_negate) { - if (delta) { - at = '-(' + at + ')'; - } else { - at = '-' + at; - } - } - innerOrder = Math.floor(innerOrder); - order = Math.floor(order); - if (innerOrder && order >= innerOrder) { - at = '(' + at + ')'; - } - } - return at; -}; - -exports.phpGenerator = PHP; +// Install per-block-type generator functions: +Object.assign( + phpGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/php/all.js b/generators/php/all.js deleted file mode 100644 index 4f707814b..000000000 --- a/generators/php/all.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating PHP for - * blocks. This is the entrypoint for php_compressed.js. - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.PHP.all'); - -const moduleExports = goog.require('Blockly.PHP'); -goog.require('Blockly.PHP.colour'); -goog.require('Blockly.PHP.lists'); -goog.require('Blockly.PHP.logic'); -goog.require('Blockly.PHP.loops'); -goog.require('Blockly.PHP.math'); -goog.require('Blockly.PHP.procedures'); -goog.require('Blockly.PHP.texts'); -goog.require('Blockly.PHP.variables'); -goog.require('Blockly.PHP.variablesDynamic'); - -exports = moduleExports; diff --git a/generators/php/colour.js b/generators/php/colour.js index 09ac78bbb..a9874fcdf 100644 --- a/generators/php/colour.js +++ b/generators/php/colour.js @@ -7,37 +7,37 @@ /** * @fileoverview Generating PHP for colour blocks. */ -'use strict'; -goog.module('Blockly.PHP.colour'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.colour'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import {Order} from './php_generator.js'; -PHP['colour_picker'] = function(block) { +export function colour_picker(block, generator) { // Colour picker. - const code = PHP.quote_(block.getFieldValue('COLOUR')); - return [code, PHP.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('COLOUR')); + return [code, Order.ATOMIC]; }; -PHP['colour_random'] = function(block) { +export function colour_random(block, generator) { // Generate a random colour. - const functionName = PHP.provideFunction_('colour_random', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}() { + const functionName = generator.provideFunction_('colour_random', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); } `); const code = functionName + '()'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['colour_rgb'] = function(block) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. - const red = PHP.valueToCode(block, 'RED', PHP.ORDER_NONE) || 0; - const green = PHP.valueToCode(block, 'GREEN', PHP.ORDER_NONE) || 0; - const blue = PHP.valueToCode(block, 'BLUE', PHP.ORDER_NONE) || 0; - const functionName = PHP.provideFunction_('colour_rgb', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) { + const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; + const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; + const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; + const functionName = generator.provideFunction_('colour_rgb', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) { $r = round(max(min($r, 100), 0) * 2.55); $g = round(max(min($g, 100), 0) * 2.55); $b = round(max(min($b, 100), 0) * 2.55); @@ -49,16 +49,18 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($r, $g, $b) { } `); const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['colour_blend'] = function(block) { +export function colour_blend(block, generator) { // Blend two colours together. - const c1 = PHP.valueToCode(block, 'COLOUR1', PHP.ORDER_NONE) || "'#000000'"; - const c2 = PHP.valueToCode(block, 'COLOUR2', PHP.ORDER_NONE) || "'#000000'"; - const ratio = PHP.valueToCode(block, 'RATIO', PHP.ORDER_NONE) || 0.5; - const functionName = PHP.provideFunction_('colour_blend', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) { + const c1 = + generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; + const c2 = + generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; + const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; + const functionName = generator.provideFunction_('colour_blend', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) { $ratio = max(min($ratio, 1), 0); $r1 = hexdec(substr($c1, 1, 2)); $g1 = hexdec(substr($c1, 3, 2)); @@ -77,5 +79,5 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($c1, $c2, $ratio) { } `); const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/php/lists.js b/generators/php/lists.js index 95da17e69..5e1aa7f79 100644 --- a/generators/php/lists.js +++ b/generators/php/lists.js @@ -18,33 +18,33 @@ * PHP (because only variables can be passed by reference). * ex: end(true ? list1 : list2) */ -'use strict'; -goog.module('Blockly.PHP.lists'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.generator.lists'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './php_generator.js'; -PHP['lists_create_empty'] = function(block) { +export function lists_create_empty(block, generator) { // Create an empty list. - return ['array()', PHP.ORDER_FUNCTION_CALL]; + return ['array()', Order.FUNCTION_CALL]; }; -PHP['lists_create_with'] = function(block) { +export function lists_create_with(block, generator) { // Create a list with any number of elements of any type. let code = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { - code[i] = PHP.valueToCode(block, 'ADD' + i, PHP.ORDER_NONE) || 'null'; + code[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null'; } code = 'array(' + code.join(', ') + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['lists_repeat'] = function(block) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. - const functionName = PHP.provideFunction_('lists_repeat', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { + const functionName = generator.provideFunction_('lists_repeat', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { $array = array(); for ($index = 0; $index < $count; $index++) { $array[] = $value; @@ -52,16 +52,16 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value, $count) { return $array; } `); - const element = PHP.valueToCode(block, 'ITEM', PHP.ORDER_NONE) || 'null'; - const repeatCount = PHP.valueToCode(block, 'NUM', PHP.ORDER_NONE) || '0'; + const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'null'; + const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const code = functionName + '(' + element + ', ' + repeatCount + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['lists_length'] = function(block) { +export function lists_length(block, generator) { // String or array length. - const functionName = PHP.provideFunction_('length', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value) { + const functionName = generator.provideFunction_('length', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { if (is_string($value)) { return strlen($value); } else { @@ -69,21 +69,23 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value) { } } `); - const list = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; - return [functionName + '(' + list + ')', PHP.ORDER_FUNCTION_CALL]; + const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; + return [functionName + '(' + list + ')', Order.FUNCTION_CALL]; }; -PHP['lists_isEmpty'] = function(block) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? const argument0 = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_FUNCTION_CALL) || 'array()'; - return ['empty(' + argument0 + ')', PHP.ORDER_FUNCTION_CALL]; + generator.valueToCode(block, 'VALUE', Order.FUNCTION_CALL) + || 'array()'; + return ['empty(' + argument0 + ')', Order.FUNCTION_CALL]; }; -PHP['lists_indexOf'] = function(block) { +export function lists_indexOf(block, generator) { // Find an item in the list. - const argument0 = PHP.valueToCode(block, 'FIND', PHP.ORDER_NONE) || "''"; - const argument1 = PHP.valueToCode(block, 'VALUE', PHP.ORDER_MEMBER) || '[]'; + const argument0 = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const argument1 = + generator.valueToCode(block, 'VALUE', Order.MEMBER) || '[]'; let errorIndex = ' -1'; let indexAdjustment = ''; if (block.workspace.options.oneBasedIndex) { @@ -93,8 +95,8 @@ PHP['lists_indexOf'] = function(block) { let functionName; if (block.getFieldValue('END') === 'FIRST') { // indexOf - functionName = PHP.provideFunction_('indexOf', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { + functionName = generator.provideFunction_('indexOf', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { for ($index = 0; $index < count($haystack); $index++) { if ($haystack[$index] == $needle) return $index${indexAdjustment}; } @@ -103,8 +105,8 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { `); } else { // lastIndexOf - functionName = PHP.provideFunction_('lastIndexOf', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { + functionName = generator.provideFunction_('lastIndexOf', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { $last = ${errorIndex}; for ($index = 0; $index < count($haystack); $index++) { if ($haystack[$index] == $needle) $last = $index${indexAdjustment}; @@ -115,10 +117,10 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($haystack, $needle) { } const code = functionName + '(' + argument1 + ', ' + argument0 + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['lists_getIndex'] = function(block) { +export function lists_getIndex(block, generator) { // Get element at index. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; @@ -126,52 +128,52 @@ PHP['lists_getIndex'] = function(block) { case 'FIRST': if (mode === 'GET') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_MEMBER) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()'; const code = list + '[0]'; - return [code, PHP.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; const code = 'array_shift(' + list + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; return 'array_shift(' + list + ');\n'; } break; case 'LAST': if (mode === 'GET') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; const code = 'end(' + list + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'GET_REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; const code = 'array_pop(' + list + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; return 'array_pop(' + list + ');\n'; } break; case 'FROM_START': { - const at = PHP.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); if (mode === 'GET') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_MEMBER) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.MEMBER) || 'array()'; const code = list + '[' + at + ']'; - return [code, PHP.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; const code = 'array_splice(' + list + ', ' + at + ', 1)[0]'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; return 'array_splice(' + list + ', ' + at + ', 1);\n'; } break; @@ -179,48 +181,51 @@ PHP['lists_getIndex'] = function(block) { case 'FROM_END': if (mode === 'GET') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; - const at = PHP.getAdjusted(block, 'AT', 1, true); + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; + const at = generator.getAdjusted(block, 'AT', 1, true); const code = 'array_slice(' + list + ', ' + at + ', 1)[0]'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'GET_REMOVE' || mode === 'REMOVE') { const list = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; const at = - PHP.getAdjusted(block, 'AT', 1, false, PHP.ORDER_SUBTRACTION); + generator.getAdjusted(block, 'AT', 1, false, Order.SUBTRACTION); const code = 'array_splice(' + list + ', count(' + list + ') - ' + at + ', 1)[0]'; if (mode === 'GET_REMOVE') { - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return code + ';\n'; } } break; case 'RANDOM': { - const list = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'array()'; + const list = + generator.valueToCode(block, 'VALUE', Order.NONE) || 'array()'; if (mode === 'GET') { - const functionName = PHP.provideFunction_('lists_get_random_item', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list) { + const functionName = + generator.provideFunction_('lists_get_random_item', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) { return $list[rand(0,count($list)-1)]; } `); const code = functionName + '(' + list + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'GET_REMOVE') { const functionName = - PHP.provideFunction_('lists_get_remove_random_item', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list) { + generator.provideFunction_('lists_get_remove_random_item', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) { $x = rand(0,count($list)-1); unset($list[$x]); return array_values($list); } `); const code = functionName + '(' + list + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { - const functionName = PHP.provideFunction_('lists_remove_random_item', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list) { + const functionName = + generator.provideFunction_('lists_remove_random_item', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list) { unset($list[rand(0,count($list)-1)]); } `); @@ -232,12 +237,13 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list) { throw Error('Unhandled combination (lists_getIndex).'); }; -PHP['lists_setIndex'] = function(block) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const value = PHP.valueToCode(block, 'TO', PHP.ORDER_ASSIGNMENT) || 'null'; + const value = + generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || 'null'; // Cache non-trivial values to variables to prevent repeated look-ups. // Closure, which accesses and modifies 'list'. let cachedList; @@ -245,7 +251,8 @@ PHP['lists_setIndex'] = function(block) { if (cachedList.match(/^\$\w+$/)) { return ''; } - const listVar = PHP.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + const listVar = + generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); const code = listVar + ' = &' + cachedList + ';\n'; cachedList = listVar; return code; @@ -254,19 +261,21 @@ PHP['lists_setIndex'] = function(block) { case 'FIRST': if (mode === 'SET') { const list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_MEMBER) || 'array()'; + generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()'; return list + '[0] = ' + value + ';\n'; } else if (mode === 'INSERT') { const list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; return 'array_unshift(' + list + ', ' + value + ');\n'; } break; case 'LAST': { - const list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; + const list = + generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; if (mode === 'SET') { - const functionName = PHP.provideFunction_('lists_set_last_item', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) { + const functionName = + generator.provideFunction_('lists_set_last_item', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) { $list[count($list) - 1] = $value; } `); @@ -277,31 +286,34 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $value) { break; } case 'FROM_START': { - const at = PHP.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); if (mode === 'SET') { const list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_MEMBER) || 'array()'; + generator.valueToCode(block, 'LIST', Order.MEMBER) || 'array()'; return list + '[' + at + '] = ' + value + ';\n'; } else if (mode === 'INSERT') { const list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; + generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; return 'array_splice(' + list + ', ' + at + ', 0, ' + value + ');\n'; } break; } case 'FROM_END': { - const list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; - const at = PHP.getAdjusted(block, 'AT', 1); + const list = + generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; + const at = generator.getAdjusted(block, 'AT', 1); if (mode === 'SET') { - const functionName = PHP.provideFunction_('lists_set_from_end', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { + const functionName = + generator.provideFunction_('lists_set_from_end', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { $list[count($list) - $at] = $value; } `); return functionName + '(' + list + ', ' + at + ', ' + value + ');\n'; } else if (mode === 'INSERT') { - const functionName = PHP.provideFunction_('lists_insert_from_end', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { + const functionName = + generator.provideFunction_('lists_insert_from_end', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { return array_splice($list, count($list) - $at, 0, $value); } `); @@ -311,10 +323,11 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { } case 'RANDOM': cachedList = - PHP.valueToCode(block, 'LIST', PHP.ORDER_REFERENCE) || 'array()'; + generator.valueToCode(block, 'LIST', Order.REFERENCE) || 'array()'; let code = cacheList(); const list = cachedList; - const xVar = PHP.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); + const xVar = + generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); code += xVar + ' = rand(0, count(' + list + ')-1);\n'; if (mode === 'SET') { code += list + '[' + xVar + '] = ' + value + ';\n'; @@ -328,9 +341,9 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}(&$list, $at, $value) { throw Error('Unhandled combination (lists_setIndex).'); }; -PHP['lists_getSublist'] = function(block) { +export function lists_getSublist(block, generator) { // Get sublist. - const list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); let code; @@ -344,10 +357,11 @@ PHP['lists_getSublist'] = function(block) { let at1; switch (where1) { case 'FROM_START': - at1 = PHP.getAdjusted(block, 'AT1'); + at1 = generator.getAdjusted(block, 'AT1'); break; case 'FROM_END': - at1 = PHP.getAdjusted(block, 'AT1', 1, false, PHP.ORDER_SUBTRACTION); + at1 = + generator.getAdjusted(block, 'AT1', 1, false, Order.SUBTRACTION); at1 = 'count(' + list + ') - ' + at1; break; case 'FIRST': @@ -360,7 +374,8 @@ PHP['lists_getSublist'] = function(block) { let length; switch (where2) { case 'FROM_START': - at2 = PHP.getAdjusted(block, 'AT2', 0, false, PHP.ORDER_SUBTRACTION); + at2 = + generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION); length = at2 + ' - '; if (stringUtils.isNumber(String(at1)) || String(at1).match(/^\(.+\)$/)) { @@ -371,7 +386,8 @@ PHP['lists_getSublist'] = function(block) { length += ' + 1'; break; case 'FROM_END': - at2 = PHP.getAdjusted(block, 'AT2', 0, false, PHP.ORDER_SUBTRACTION); + at2 = + generator.getAdjusted(block, 'AT2', 0, false, Order.SUBTRACTION); length = 'count(' + list + ') - ' + at2 + ' - '; if (stringUtils.isNumber(String(at1)) || String(at1).match(/^\(.+\)$/)) { @@ -394,10 +410,11 @@ PHP['lists_getSublist'] = function(block) { } code = 'array_slice(' + list + ', ' + at1 + ', ' + length + ')'; } else { - const at1 = PHP.getAdjusted(block, 'AT1'); - const at2 = PHP.getAdjusted(block, 'AT2'); - const functionName = PHP.provideFunction_('lists_get_sublist', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) { + const at1 = generator.getAdjusted(block, 'AT1'); + const at2 = generator.getAdjusted(block, 'AT2'); + const functionName = + generator.provideFunction_('lists_get_sublist', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) { if ($where1 == 'FROM_END') { $at1 = count($list) - 1 - $at1; } else if ($where1 == 'FIRST') { @@ -421,16 +438,17 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list, $where1, $at1, $where2, $at2) code = functionName + '(' + list + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; } - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['lists_sort'] = function(block) { +export function lists_sort(block, generator) { // Block for sorting a list. - const listCode = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; + const listCode = + generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; const direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1; const type = block.getFieldValue('TYPE'); - const functionName = PHP.provideFunction_('lists_sort', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) { + const functionName = generator.provideFunction_('lists_sort', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) { $sortCmpFuncs = array( 'NUMERIC' => 'strnatcasecmp', 'TEXT' => 'strcmp', @@ -447,13 +465,14 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list, $type, $direction) { `); const sortCode = functionName + '(' + listCode + ', "' + type + '", ' + direction + ')'; - return [sortCode, PHP.ORDER_FUNCTION_CALL]; + return [sortCode, Order.FUNCTION_CALL]; }; -PHP['lists_split'] = function(block) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. - let value_input = PHP.valueToCode(block, 'INPUT', PHP.ORDER_NONE); - const value_delim = PHP.valueToCode(block, 'DELIM', PHP.ORDER_NONE) || "''"; + let value_input = generator.valueToCode(block, 'INPUT', Order.NONE); + const value_delim = + generator.valueToCode(block, 'DELIM', Order.NONE) || "''"; const mode = block.getFieldValue('MODE'); let functionName; if (mode === 'SPLIT') { @@ -470,12 +489,12 @@ PHP['lists_split'] = function(block) { throw Error('Unknown mode: ' + mode); } const code = functionName + '(' + value_delim + ', ' + value_input + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['lists_reverse'] = function(block) { +export function lists_reverse(block, generator) { // Block for reversing a list. - const list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const code = 'array_reverse(' + list + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/php/logic.js b/generators/php/logic.js index 40bd33459..5c7ea0e93 100644 --- a/generators/php/logic.js +++ b/generators/php/logic.js @@ -7,27 +7,30 @@ /** * @fileoverview Generating PHP for logic blocks. */ -'use strict'; -goog.module('Blockly.PHP.logic'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.logic'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import {Order} from './php_generator.js'; -PHP['controls_if'] = function(block) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = '', branchCode, conditionCode; - if (PHP.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - code += PHP.injectId(PHP.STATEMENT_PREFIX, block); + code += generator.injectId(generator.STATEMENT_PREFIX, block); } do { - conditionCode = PHP.valueToCode(block, 'IF' + n, PHP.ORDER_NONE) || 'false'; - branchCode = PHP.statementToCode(block, 'DO' + n); - if (PHP.STATEMENT_SUFFIX) { - branchCode = PHP.prefixLines( - PHP.injectId(PHP.STATEMENT_SUFFIX, block), PHP.INDENT) + + conditionCode = + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'false'; + branchCode = generator.statementToCode(block, 'DO' + n); + if (generator.STATEMENT_SUFFIX) { + branchCode = + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += (n > 0 ? ' else ' : '') + 'if (' + conditionCode + ') {\n' + @@ -35,11 +38,13 @@ PHP['controls_if'] = function(block) { n++; } while (block.getInput('IF' + n)); - if (block.getInput('ELSE') || PHP.STATEMENT_SUFFIX) { - branchCode = PHP.statementToCode(block, 'ELSE'); - if (PHP.STATEMENT_SUFFIX) { - branchCode = PHP.prefixLines( - PHP.injectId(PHP.STATEMENT_SUFFIX, block), PHP.INDENT) + + if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { + branchCode = generator.statementToCode(block, 'ELSE'); + if (generator.STATEMENT_SUFFIX) { + branchCode = + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += ' else {\n' + branchCode + '}'; @@ -47,28 +52,28 @@ PHP['controls_if'] = function(block) { return code + '\n'; }; -PHP['controls_ifelse'] = PHP['controls_if']; +export const controls_ifelse = controls_if; -PHP['logic_compare'] = function(block) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; const operator = OPERATORS[block.getFieldValue('OP')]; - const order = (operator === '==' || operator === '!=') ? PHP.ORDER_EQUALITY : - PHP.ORDER_RELATIONAL; - const argument0 = PHP.valueToCode(block, 'A', order) || '0'; - const argument1 = PHP.valueToCode(block, 'B', order) || '0'; + const order = (operator === '==' || operator === '!=') ? Order.EQUALITY : + Order.RELATIONAL; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; }; -PHP['logic_operation'] = function(block) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? '&&' : '||'; const order = - (operator === '&&') ? PHP.ORDER_LOGICAL_AND : PHP.ORDER_LOGICAL_OR; - let argument0 = PHP.valueToCode(block, 'A', order); - let argument1 = PHP.valueToCode(block, 'B', order); + (operator === '&&') ? Order.LOGICAL_AND : Order.LOGICAL_OR; + let argument0 = generator.valueToCode(block, 'A', order); + let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { // If there are no arguments, then the return value is false. argument0 = 'false'; @@ -87,33 +92,33 @@ PHP['logic_operation'] = function(block) { return [code, order]; }; -PHP['logic_negate'] = function(block) { +export function logic_negate(block, generator) { // Negation. - const order = PHP.ORDER_LOGICAL_NOT; - const argument0 = PHP.valueToCode(block, 'BOOL', order) || 'true'; + const order = Order.LOGICAL_NOT; + const argument0 = generator.valueToCode(block, 'BOOL', order) || 'true'; const code = '!' + argument0; return [code, order]; }; -PHP['logic_boolean'] = function(block) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'true' : 'false'; - return [code, PHP.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -PHP['logic_null'] = function(block) { +export function logic_null(block, generator) { // Null data type. - return ['null', PHP.ORDER_ATOMIC]; + return ['null', Order.ATOMIC]; }; -PHP['logic_ternary'] = function(block) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = - PHP.valueToCode(block, 'IF', PHP.ORDER_CONDITIONAL) || 'false'; + generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'false'; const value_then = - PHP.valueToCode(block, 'THEN', PHP.ORDER_CONDITIONAL) || 'null'; + generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'null'; const value_else = - PHP.valueToCode(block, 'ELSE', PHP.ORDER_CONDITIONAL) || 'null'; + generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'null'; const code = value_if + ' ? ' + value_then + ' : ' + value_else; - return [code, PHP.ORDER_CONDITIONAL]; + return [code, Order.CONDITIONAL]; }; diff --git a/generators/php/loops.js b/generators/php/loops.js index b90e0f04e..3e2cfab74 100644 --- a/generators/php/loops.js +++ b/generators/php/loops.js @@ -7,16 +7,16 @@ /** * @fileoverview Generating PHP for loop blocks. */ -'use strict'; -goog.module('Blockly.PHP.loops'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.loops'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './php_generator.js'; -PHP['controls_repeat_ext'] = function(block) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -24,15 +24,17 @@ PHP['controls_repeat_ext'] = function(block) { repeats = String(Number(block.getFieldValue('TIMES'))); } else { // External number. - repeats = PHP.valueToCode(block, 'TIMES', PHP.ORDER_ASSIGNMENT) || '0'; + repeats = generator.valueToCode(block, 'TIMES', Order.ASSIGNMENT) || '0'; } - let branch = PHP.statementToCode(block, 'DO'); - branch = PHP.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code = ''; - const loopVar = PHP.nameDB_.getDistinctName('count', NameType.VARIABLE); + const loopVar = + generator.nameDB_.getDistinctName('count', NameType.VARIABLE); let endVar = repeats; if (!repeats.match(/^\w+$/) && !stringUtils.isNumber(repeats)) { - endVar = PHP.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE); + endVar = + generator.nameDB_.getDistinctName('repeat_end', NameType.VARIABLE); code += endVar + ' = ' + repeats + ';\n'; } code += 'for (' + loopVar + ' = 0; ' + loopVar + ' < ' + endVar + '; ' + @@ -40,32 +42,35 @@ PHP['controls_repeat_ext'] = function(block) { return code; }; -PHP['controls_repeat'] = PHP['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -PHP['controls_whileUntil'] = function(block) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; let argument0 = - PHP.valueToCode( - block, 'BOOL', until ? PHP.ORDER_LOGICAL_NOT : PHP.ORDER_NONE) || + generator.valueToCode( + block, 'BOOL', until ? Order.LOGICAL_NOT : Order.NONE) || 'false'; - let branch = PHP.statementToCode(block, 'DO'); - branch = PHP.addLoopTrap(branch, block); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); if (until) { argument0 = '!' + argument0; } return 'while (' + argument0 + ') {\n' + branch + '}\n'; }; -PHP['controls_for'] = function(block) { +export function controls_for(block, generator) { // For loop. const variable0 = - PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const argument0 = PHP.valueToCode(block, 'FROM', PHP.ORDER_ASSIGNMENT) || '0'; - const argument1 = PHP.valueToCode(block, 'TO', PHP.ORDER_ASSIGNMENT) || '0'; - const increment = PHP.valueToCode(block, 'BY', PHP.ORDER_ASSIGNMENT) || '1'; - let branch = PHP.statementToCode(block, 'DO'); - branch = PHP.addLoopTrap(branch, block); + generator.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + const argument0 = + generator.valueToCode(block, 'FROM', Order.ASSIGNMENT) || '0'; + const argument1 = + generator.valueToCode(block, 'TO', Order.ASSIGNMENT) || '0'; + const increment = + generator.valueToCode(block, 'BY', Order.ASSIGNMENT) || '1'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code; if (stringUtils.isNumber(argument0) && stringUtils.isNumber(argument1) && stringUtils.isNumber(increment)) { @@ -86,19 +91,22 @@ PHP['controls_for'] = function(block) { let startVar = argument0; if (!argument0.match(/^\w+$/) && !stringUtils.isNumber(argument0)) { startVar = - PHP.nameDB_.getDistinctName(variable0 + '_start', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_start', NameType.VARIABLE); code += startVar + ' = ' + argument0 + ';\n'; } let endVar = argument1; if (!argument1.match(/^\w+$/) && !stringUtils.isNumber(argument1)) { endVar = - PHP.nameDB_.getDistinctName(variable0 + '_end', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_end', NameType.VARIABLE); code += endVar + ' = ' + argument1 + ';\n'; } // Determine loop direction at start, in case one of the bounds // changes during loop execution. const incVar = - PHP.nameDB_.getDistinctName(variable0 + '_inc', NameType.VARIABLE); + generator.nameDB_.getDistinctName( + variable0 + '_inc', NameType.VARIABLE); code += incVar + ' = '; if (stringUtils.isNumber(increment)) { code += Math.abs(increment) + ';\n'; @@ -106,7 +114,7 @@ PHP['controls_for'] = function(block) { code += 'abs(' + increment + ');\n'; } code += 'if (' + startVar + ' > ' + endVar + ') {\n'; - code += PHP.INDENT + incVar + ' = -' + incVar + ';\n'; + code += generator.INDENT + incVar + ' = -' + incVar + ';\n'; code += '}\n'; code += 'for (' + variable0 + ' = ' + startVar + '; ' + incVar + ' >= 0 ? ' + variable0 + ' <= ' + endVar + ' : ' + variable0 + @@ -116,39 +124,40 @@ PHP['controls_for'] = function(block) { return code; }; -PHP['controls_forEach'] = function(block) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = - PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = - PHP.valueToCode(block, 'LIST', PHP.ORDER_ASSIGNMENT) || '[]'; - let branch = PHP.statementToCode(block, 'DO'); - branch = PHP.addLoopTrap(branch, block); + generator.valueToCode(block, 'LIST', Order.ASSIGNMENT) || '[]'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block); let code = ''; code += 'foreach (' + argument0 + ' as ' + variable0 + ') {\n' + branch + '}\n'; return code; }; -PHP['controls_flow_statements'] = function(block) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; - if (PHP.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - xfix += PHP.injectId(PHP.STATEMENT_PREFIX, block); + xfix += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (PHP.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. - xfix += PHP.injectId(PHP.STATEMENT_SUFFIX, block); + xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } - if (PHP.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { const loop = block.getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. - xfix += PHP.injectId(PHP.STATEMENT_PREFIX, loop); + xfix += generator.injectId(generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { diff --git a/generators/php/math.js b/generators/php/math.js index 972f4286f..b5cd53404 100644 --- a/generators/php/math.js +++ b/generators/php/math.js @@ -7,18 +7,18 @@ /** * @fileoverview Generating PHP for math blocks. */ -'use strict'; -goog.module('Blockly.PHP.math'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.math'); -const {NameType} = goog.require('Blockly.Names'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import {NameType} from '../../core/names.js'; +import {Order} from './php_generator.js'; -PHP['math_number'] = function(block) { +export function math_number(block, generator) { // Numeric value. let code = Number(block.getFieldValue('NUM')); - const order = code >= 0 ? PHP.ORDER_ATOMIC : PHP.ORDER_UNARY_NEGATION; + const order = code >= 0 ? Order.ATOMIC : Order.UNARY_NEGATION; if (code === Infinity) { code = 'INF'; } else if (code === -Infinity) { @@ -27,43 +27,43 @@ PHP['math_number'] = function(block) { return [code, order]; }; -PHP['math_arithmetic'] = function(block) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { - 'ADD': [' + ', PHP.ORDER_ADDITION], - 'MINUS': [' - ', PHP.ORDER_SUBTRACTION], - 'MULTIPLY': [' * ', PHP.ORDER_MULTIPLICATION], - 'DIVIDE': [' / ', PHP.ORDER_DIVISION], - 'POWER': [' ** ', PHP.ORDER_POWER], + 'ADD': [' + ', Order.ADDITION], + 'MINUS': [' - ', Order.SUBTRACTION], + 'MULTIPLY': [' * ', Order.MULTIPLICATION], + 'DIVIDE': [' / ', Order.DIVISION], + 'POWER': [' ** ', Order.POWER], }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; const order = tuple[1]; - const argument0 = PHP.valueToCode(block, 'A', order) || '0'; - const argument1 = PHP.valueToCode(block, 'B', order) || '0'; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + operator + argument1; return [code, order]; }; -PHP['math_single'] = function(block) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; let arg; if (operator === 'NEG') { // Negation is a special case given its different operator precedence. - arg = PHP.valueToCode(block, 'NUM', PHP.ORDER_UNARY_NEGATION) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.UNARY_NEGATION) || '0'; if (arg[0] === '-') { // --3 is not legal in JS. arg = ' ' + arg; } code = '-' + arg; - return [code, PHP.ORDER_UNARY_NEGATION]; + return [code, Order.UNARY_NEGATION]; } if (operator === 'SIN' || operator === 'COS' || operator === 'TAN') { - arg = PHP.valueToCode(block, 'NUM', PHP.ORDER_DIVISION) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.DIVISION) || '0'; } else { - arg = PHP.valueToCode(block, 'NUM', PHP.ORDER_NONE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; } // First, handle cases which generate values that don't need parentheses // wrapping the code. @@ -103,7 +103,7 @@ PHP['math_single'] = function(block) { break; } if (code) { - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } // Second, handle cases which generate values that may need parentheses // wrapping the code. @@ -123,43 +123,44 @@ PHP['math_single'] = function(block) { default: throw Error('Unknown math operator: ' + operator); } - return [code, PHP.ORDER_DIVISION]; + return [code, Order.DIVISION]; }; -PHP['math_constant'] = function(block) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { - 'PI': ['M_PI', PHP.ORDER_ATOMIC], - 'E': ['M_E', PHP.ORDER_ATOMIC], - 'GOLDEN_RATIO': ['(1 + sqrt(5)) / 2', PHP.ORDER_DIVISION], - 'SQRT2': ['M_SQRT2', PHP.ORDER_ATOMIC], - 'SQRT1_2': ['M_SQRT1_2', PHP.ORDER_ATOMIC], - 'INFINITY': ['INF', PHP.ORDER_ATOMIC], + 'PI': ['M_PI', Order.ATOMIC], + 'E': ['M_E', Order.ATOMIC], + 'GOLDEN_RATIO': ['(1 + sqrt(5)) / 2', Order.DIVISION], + 'SQRT2': ['M_SQRT2', Order.ATOMIC], + 'SQRT1_2': ['M_SQRT1_2', Order.ATOMIC], + 'INFINITY': ['INF', Order.ATOMIC], }; return CONSTANTS[block.getFieldValue('CONSTANT')]; }; -PHP['math_number_property'] = function(block) { +export function math_number_property(block, generator) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. const PROPERTIES = { - 'EVEN': ['', ' % 2 == 0', PHP.ORDER_MODULUS, PHP.ORDER_EQUALITY], - 'ODD': ['', ' % 2 == 1', PHP.ORDER_MODULUS, PHP.ORDER_EQUALITY], - 'WHOLE': ['is_int(', ')', PHP.ORDER_NONE, PHP.ORDER_FUNCTION_CALL], - 'POSITIVE': ['', ' > 0', PHP.ORDER_RELATIONAL, PHP.ORDER_RELATIONAL], - 'NEGATIVE': ['', ' < 0', PHP.ORDER_RELATIONAL, PHP.ORDER_RELATIONAL], - 'DIVISIBLE_BY': [null, null, PHP.ORDER_MODULUS, PHP.ORDER_EQUALITY], - 'PRIME': [null, null, PHP.ORDER_NONE, PHP.ORDER_FUNCTION_CALL], + 'EVEN': ['', ' % 2 == 0', Order.MODULUS, Order.EQUALITY], + 'ODD': ['', ' % 2 == 1', Order.MODULUS, Order.EQUALITY], + 'WHOLE': ['is_int(', ')', Order.NONE, Order.FUNCTION_CALL], + 'POSITIVE': ['', ' > 0', Order.RELATIONAL, Order.RELATIONAL], + 'NEGATIVE': ['', ' < 0', Order.RELATIONAL, Order.RELATIONAL], + 'DIVISIBLE_BY': [null, null, Order.MODULUS, Order.EQUALITY], + 'PRIME': [null, null, Order.NONE, Order.FUNCTION_CALL], }; const dropdownProperty = block.getFieldValue('PROPERTY'); - const [prefix, suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; - const numberToCheck = PHP.valueToCode(block, 'NUMBER_TO_CHECK', + const [prefix, suffix, inputOrder, outputOrder] = + PROPERTIES[dropdownProperty]; + const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - const functionName = PHP.provideFunction_('math_isPrime', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($n) { + const functionName = generator.provideFunction_('math_isPrime', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($n) { // https://en.wikipedia.org/wiki/Primality_test#Naive_methods if ($n == 2 || $n == 3) { return true; @@ -180,10 +181,10 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($n) { `); code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = PHP.valueToCode(block, 'DIVISOR', - PHP.ORDER_MODULUS) || '0'; + const divisor = generator.valueToCode(block, 'DIVISOR', + Order.MODULUS) || '0'; if (divisor === '0') { - return ['false', PHP.ORDER_ATOMIC]; + return ['false', Order.ATOMIC]; } code = numberToCheck + ' % ' + divisor + ' == 0'; @@ -193,20 +194,22 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($n) { return [code, outputOrder]; }; -PHP['math_change'] = function(block) { +export function math_change(block, generator) { // Add to a variable in place. - const argument0 = PHP.valueToCode(block, 'DELTA', PHP.ORDER_ADDITION) || '0'; + const argument0 = + generator.valueToCode(block, 'DELTA', Order.ADDITION) || '0'; const varName = - PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' += ' + argument0 + ';\n'; }; // Rounding functions have a single operand. -PHP['math_round'] = PHP['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -PHP['math_trig'] = PHP['math_single']; +export const math_trig = math_single; -PHP['math_on_list'] = function(block) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); let list; @@ -214,38 +217,41 @@ PHP['math_on_list'] = function(block) { switch (func) { case 'SUM': list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_FUNCTION_CALL) || 'array()'; + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) + || 'array()'; code = 'array_sum(' + list + ')'; break; case 'MIN': list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_FUNCTION_CALL) || 'array()'; + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) + || 'array()'; code = 'min(' + list + ')'; break; case 'MAX': list = - PHP.valueToCode(block, 'LIST', PHP.ORDER_FUNCTION_CALL) || 'array()'; + generator.valueToCode(block, 'LIST', Order.FUNCTION_CALL) + || 'array()'; code = 'max(' + list + ')'; break; case 'AVERAGE': { - const functionName = PHP.provideFunction_('math_mean', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($myList) { + const functionName = generator.provideFunction_('math_mean', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($myList) { return array_sum($myList) / count($myList); } `); - list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || 'array()'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || 'array()'; code = functionName + '(' + list + ')'; break; } case 'MEDIAN': { - const functionName = PHP.provideFunction_('math_median', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($arr) { + const functionName = generator.provideFunction_('math_median', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($arr) { sort($arr,SORT_NUMERIC); return (count($arr) % 2) ? $arr[floor(count($arr) / 2)] : ($arr[floor(count($arr) / 2)] + $arr[floor(count($arr) / 2) - 1]) / 2; } `); - list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } @@ -253,8 +259,8 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($arr) { // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]. - const functionName = PHP.provideFunction_('math_modes', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($values) { + const functionName = generator.provideFunction_('math_modes', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($values) { if (empty($values)) return array(); $counts = array_count_values($values); arsort($counts); // Sort counts in descending order @@ -262,13 +268,14 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($values) { return $modes; } `); - list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'STD_DEV': { - const functionName = PHP.provideFunction_('math_standard_deviation', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($numbers) { + const functionName = + generator.provideFunction_('math_standard_deviation', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($numbers) { $n = count($numbers); if (!$n) return null; $mean = array_sum($numbers) / count($numbers); @@ -276,53 +283,54 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($numbers) { return sqrt(array_sum($devs) / (count($devs) - 1)); } `); - list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } case 'RANDOM': { - const functionName = PHP.provideFunction_('math_random_list', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($list) { + const functionName = generator.provideFunction_('math_random_list', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($list) { $x = rand(0, count($list)-1); return $list[$x]; } `); - list = PHP.valueToCode(block, 'LIST', PHP.ORDER_NONE) || '[]'; + list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; code = functionName + '(' + list + ')'; break; } default: throw Error('Unknown operator: ' + func); } - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['math_modulo'] = function(block) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = - PHP.valueToCode(block, 'DIVIDEND', PHP.ORDER_MODULUS) || '0'; - const argument1 = PHP.valueToCode(block, 'DIVISOR', PHP.ORDER_MODULUS) || '0'; + generator.valueToCode(block, 'DIVIDEND', Order.MODULUS) || '0'; + const argument1 = + generator.valueToCode(block, 'DIVISOR', Order.MODULUS) || '0'; const code = argument0 + ' % ' + argument1; - return [code, PHP.ORDER_MODULUS]; + return [code, Order.MODULUS]; }; -PHP['math_constrain'] = function(block) { +export function math_constrain(block, generator) { // Constrain a number between two limits. - const argument0 = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || '0'; - const argument1 = PHP.valueToCode(block, 'LOW', PHP.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'LOW', Order.NONE) || '0'; const argument2 = - PHP.valueToCode(block, 'HIGH', PHP.ORDER_NONE) || 'Infinity'; + generator.valueToCode(block, 'HIGH', Order.NONE) || 'Infinity'; const code = 'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['math_random_int'] = function(block) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. - const argument0 = PHP.valueToCode(block, 'FROM', PHP.ORDER_NONE) || '0'; - const argument1 = PHP.valueToCode(block, 'TO', PHP.ORDER_NONE) || '0'; - const functionName = PHP.provideFunction_('math_random_int', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($a, $b) { + const argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; + const functionName = generator.provideFunction_('math_random_int', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($a, $b) { if ($a > $b) { return rand($b, $a); } @@ -330,20 +338,20 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($a, $b) { } `); const code = functionName + '(' + argument0 + ', ' + argument1 + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['math_random_float'] = function(block) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. - return ['(float)rand()/(float)getrandmax()', PHP.ORDER_FUNCTION_CALL]; + return ['(float)rand()/(float)getrandmax()', Order.FUNCTION_CALL]; }; -PHP['math_atan2'] = function(block) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. - const argument0 = PHP.valueToCode(block, 'X', PHP.ORDER_NONE) || '0'; - const argument1 = PHP.valueToCode(block, 'Y', PHP.ORDER_NONE) || '0'; + const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; return [ 'atan2(' + argument1 + ', ' + argument0 + ') / pi() * 180', - PHP.ORDER_DIVISION + Order.DIVISION ]; }; diff --git a/generators/php/php_generator.js b/generators/php/php_generator.js new file mode 100644 index 000000000..59d595b83 --- /dev/null +++ b/generators/php/php_generator.js @@ -0,0 +1,310 @@ +/** + * @license + * Copyright 2015 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating PHP for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP'); + +import * as stringUtils from '../../core/utils/string.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * http://php.net/manual/en/language.operators.precedence.php + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + CLONE: 1, // clone + NEW: 1, // new + MEMBER: 2.1, // [] + FUNCTION_CALL: 2.2, // () + POWER: 3, // ** + INCREMENT: 4, // ++ + DECREMENT: 4, // -- + BITWISE_NOT: 4, // ~ + CAST: 4, // (int) (float) (string) (array) ... + SUPPRESS_ERROR: 4, // @ + INSTANCEOF: 5, // instanceof + LOGICAL_NOT: 6, // ! + UNARY_PLUS: 7.1, // + + UNARY_NEGATION: 7.2, // - + MULTIPLICATION: 8.1, // * + DIVISION: 8.2, // / + MODULUS: 8.3, // % + ADDITION: 9.1, // + + SUBTRACTION: 9.2, // - + STRING_CONCAT: 9.3, // . + BITWISE_SHIFT: 10, // << >> + RELATIONAL: 11, // < <= > >= + EQUALITY: 12, // == != === !== <> <=> + REFERENCE: 13, // & + BITWISE_AND: 13, // & + BITWISE_XOR: 14, // ^ + BITWISE_OR: 15, // | + LOGICAL_AND: 16, // && + LOGICAL_OR: 17, // || + IF_NULL: 18, // ?? + CONDITIONAL: 19, // ?: + ASSIGNMENT: 20, // = += -= *= /= %= <<= >>= ... + LOGICAL_AND_WEAK: 21, // and + LOGICAL_XOR: 22, // xor + LOGICAL_OR_WEAK: 23, // or + NONE: 99, // (...) +}; + +export class PhpGenerator extends CodeGenerator { + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + ORDER_OVERRIDES = [ + // (foo()).bar() -> foo().bar() + // (foo())[0] -> foo()[0] + [Order.MEMBER, Order.FUNCTION_CALL], + // (foo[0])[1] -> foo[0][1] + // (foo.bar).baz -> foo.bar.baz + [Order.MEMBER, Order.MEMBER], + // !(!foo) -> !!foo + [Order.LOGICAL_NOT, Order.LOGICAL_NOT], + // a * (b * c) -> a * b * c + [Order.MULTIPLICATION, Order.MULTIPLICATION], + // a + (b + c) -> a + b + c + [Order.ADDITION, Order.ADDITION], + // a && (b && c) -> a && b && c + [Order.LOGICAL_AND, Order.LOGICAL_AND], + // a || (b || c) -> a || b || c + [Order.LOGICAL_OR, Order.LOGICAL_OR] + ]; + + constructor(name) { + super(name ?? 'PHP'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // http://php.net/manual/en/reserved.keywords.php + '__halt_compiler,abstract,and,array,as,break,callable,case,catch,class,' + + 'clone,const,continue,declare,default,die,do,echo,else,elseif,empty,' + + 'enddeclare,endfor,endforeach,endif,endswitch,endwhile,eval,exit,' + + 'extends,final,for,foreach,function,global,goto,if,implements,include,' + + 'include_once,instanceof,insteadof,interface,isset,list,namespace,new,' + + 'or,print,private,protected,public,require,require_once,return,static,' + + 'switch,throw,trait,try,unset,use,var,while,xor,' + + // http://php.net/manual/en/reserved.constants.php + 'PHP_VERSION,PHP_MAJOR_VERSION,PHP_MINOR_VERSION,PHP_RELEASE_VERSION,' + + 'PHP_VERSION_ID,PHP_EXTRA_VERSION,PHP_ZTS,PHP_DEBUG,PHP_MAXPATHLEN,' + + 'PHP_OS,PHP_SAPI,PHP_EOL,PHP_INT_MAX,PHP_INT_SIZE,DEFAULT_INCLUDE_PATH,' + + 'PEAR_INSTALL_DIR,PEAR_EXTENSION_DIR,PHP_EXTENSION_DIR,PHP_PREFIX,' + + 'PHP_BINDIR,PHP_BINARY,PHP_MANDIR,PHP_LIBDIR,PHP_DATADIR,' + + 'PHP_SYSCONFDIR,PHP_LOCALSTATEDIR,PHP_CONFIG_FILE_PATH,' + + 'PHP_CONFIG_FILE_SCAN_DIR,PHP_SHLIB_SUFFIX,E_ERROR,E_WARNING,E_PARSE,' + + 'E_NOTICE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,' + + 'E_COMPILE_WARNING,E_USER_ERROR,E_USER_WARNING,E_USER_NOTICE,' + + 'E_DEPRECATED,E_USER_DEPRECATED,E_ALL,E_STRICT,' + + '__COMPILER_HALT_OFFSET__,TRUE,FALSE,NULL,__CLASS__,__DIR__,__FILE__,' + + '__FUNCTION__,__LINE__,__METHOD__,__NAMESPACE__,__TRAIT__' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + */ + init(workspace) { + super.init(workspace); + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_, '$'); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + this.isInitialized = true; + }; + + /** + * Prepend the generated code with the variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const definitions = Object.values(this.definitions_); + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + return definitions.join('\n\n') + '\n\n\n' + code; + }; + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. A trailing semicolon is needed to make this legal. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + ';\n'; + }; + + /** + * Encode a string as a properly escaped PHP string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} PHP string. + * @protected + */ + quote_(string) { + string = string.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\\n') + .replace(/'/g, '\\\''); + return '\'' + string + '\''; + }; + + /** + * Encode a string as a properly escaped multiline PHP string, complete with + * quotes. + * @param {string} string Text to encode. + * @return {string} PHP string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // . "\n" . + // Newline escaping only works in double-quoted strings. + return lines.join(' . \"\\n\" .\n'); + }; + + /** + * Common tasks for generating PHP from blocks. + * Handles comments for the specified block and any connected value blocks. + * Calls any statements following this block. + * @param {!Block} block The current block. + * @param {string} code The PHP code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this + * statement. + * @return {string} PHP code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment, '// ') + '\n'; + } + // Collect comments for all value arguments. + // Don't collect comments for nested statements. + for (let i = 0; i < block.inputList.length; i++) { + if (block.inputList[i].type === inputTypes.VALUE) { + const childBlock = block.inputList[i].connection.targetBlock(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '// '); + } + } + } + } + } + const nextBlock = + block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + }; + + /** + * Gets a property and adjusts the value while taking into account indexing. + * @param {!Block} block The block. + * @param {string} atId The property ID of the element to get. + * @param {number=} opt_delta Value to add. + * @param {boolean=} opt_negate Whether to negate the value. + * @param {number=} opt_order The highest order acting on this value. + * @return {string|number} + */ + getAdjusted(block, atId, opt_delta, opt_negate, opt_order) { + let delta = opt_delta || 0; + let order = opt_order || this.ORDER_NONE; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + let defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + let outerOrder = order; + let innerOrder; + if (delta > 0) { + outerOrder = this.ORDER_ADDITION; + innerOrder = this.ORDER_ADDITION; + } else if (delta < 0) { + outerOrder = this.ORDER_SUBTRACTION; + innerOrder = this.ORDER_SUBTRACTION; + } else if (opt_negate) { + outerOrder = this.ORDER_UNARY_NEGATION; + innerOrder = this.ORDER_UNARY_NEGATION; + } + let at = this.valueToCode(block, atId, outerOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = Number(at) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = at + ' + ' + delta; + } else if (delta < 0) { + at = at + ' - ' + -delta; + } + if (opt_negate) { + if (delta) { + at = '-(' + at + ')'; + } else { + at = '-' + at; + } + } + innerOrder = Math.floor(innerOrder); + order = Math.floor(order); + if (innerOrder && order >= innerOrder) { + at = '(' + at + ')'; + } + } + return at; + }; +} diff --git a/generators/php/procedures.js b/generators/php/procedures.js index fc8e45dd9..9ff0de082 100644 --- a/generators/php/procedures.js +++ b/generators/php/procedures.js @@ -7,16 +7,16 @@ /** * @fileoverview Generating PHP for procedure blocks. */ -'use strict'; -goog.module('Blockly.PHP.procedures'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.procedures'); -const Variables = goog.require('Blockly.Variables'); -const {NameType} = goog.require('Blockly.Names'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import * as Variables from '../../core/variables.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './php_generator.js'; -PHP['procedures_defreturn'] = function(block) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. // First, add a 'global' statement for every variable that is not shadowed by // a local parameter. @@ -26,99 +26,106 @@ PHP['procedures_defreturn'] = function(block) { for (let i = 0, variable; variable = usedVariables[i]; i++) { const varName = variable.name; if (block.getVars().indexOf(varName) === -1) { - globals.push(PHP.nameDB_.getName(varName, NameType.VARIABLE)); + globals.push(generator.nameDB_.getName(varName, NameType.VARIABLE)); } } // Add developer variables. const devVarList = Variables.allDeveloperVariables(workspace); for (let i = 0; i < devVarList.length; i++) { globals.push( - PHP.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); + generator.nameDB_.getName( + devVarList[i], NameType.DEVELOPER_VARIABLE)); } const globalStr = - globals.length ? PHP.INDENT + 'global ' + globals.join(', ') + ';\n' : ''; + globals.length ? + generator.INDENT + 'global ' + globals.join(', ') + ';\n' : ''; const funcName = - PHP.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); let xfix1 = ''; - if (PHP.STATEMENT_PREFIX) { - xfix1 += PHP.injectId(PHP.STATEMENT_PREFIX, block); + if (generator.STATEMENT_PREFIX) { + xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (PHP.STATEMENT_SUFFIX) { - xfix1 += PHP.injectId(PHP.STATEMENT_SUFFIX, block); + if (generator.STATEMENT_SUFFIX) { + xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (xfix1) { - xfix1 = PHP.prefixLines(xfix1, PHP.INDENT); + xfix1 = generator.prefixLines(xfix1, generator.INDENT); } let loopTrap = ''; - if (PHP.INFINITE_LOOP_TRAP) { - loopTrap = PHP.prefixLines( - PHP.injectId(PHP.INFINITE_LOOP_TRAP, block), PHP.INDENT); + if (generator.INFINITE_LOOP_TRAP) { + loopTrap = generator.prefixLines( + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT); } - const branch = PHP.statementToCode(block, 'STACK'); - let returnValue = PHP.valueToCode(block, 'RETURN', PHP.ORDER_NONE) || ''; + const branch = generator.statementToCode(block, 'STACK'); + let returnValue = generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. xfix2 = xfix1; } if (returnValue) { - returnValue = PHP.INDENT + 'return ' + returnValue + ';\n'; + returnValue = generator.INDENT + 'return ' + returnValue + ';\n'; } const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = PHP.nameDB_.getName(variables[i], NameType.VARIABLE); + args[i] = generator.nameDB_.getName(variables[i], NameType.VARIABLE); } let code = 'function ' + funcName + '(' + args.join(', ') + ') {\n' + globalStr + xfix1 + loopTrap + branch + xfix2 + returnValue + '}'; - code = PHP.scrub_(block, code); + code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. - PHP.definitions_['%' + funcName] = code; + generator.definitions_['%' + funcName] = code; return null; }; // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -PHP['procedures_defnoreturn'] = PHP['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -PHP['procedures_callreturn'] = function(block) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = - PHP.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = PHP.valueToCode(block, 'ARG' + i, PHP.ORDER_NONE) || 'null'; + args[i] = generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'null'; } const code = funcName + '(' + args.join(', ') + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['procedures_callnoreturn'] = function(block) { +export function procedures_callnoreturn(block, generator) { // Call a procedure with no return value. // Generated code is for a function call as a statement is the same as a // function call as a value, with the addition of line ending. - const tuple = PHP['procedures_callreturn'](block); + const tuple = generator.forBlock['procedures_callreturn'](block, generator); return tuple[0] + ';\n'; }; -PHP['procedures_ifreturn'] = function(block) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = - PHP.valueToCode(block, 'CONDITION', PHP.ORDER_NONE) || 'false'; + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'false'; let code = 'if (' + condition + ') {\n'; - if (PHP.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. code += - PHP.prefixLines(PHP.injectId(PHP.STATEMENT_SUFFIX, block), PHP.INDENT); + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT); } if (block.hasReturnValue_) { - const value = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || 'null'; - code += PHP.INDENT + 'return ' + value + ';\n'; + const value = generator.valueToCode(block, 'VALUE', Order.NONE) || 'null'; + code += generator.INDENT + 'return ' + value + ';\n'; } else { - code += PHP.INDENT + 'return;\n'; + code += generator.INDENT + 'return;\n'; } code += '}\n'; return code; diff --git a/generators/php/text.js b/generators/php/text.js index 4b0998863..91da583a8 100644 --- a/generators/php/text.js +++ b/generators/php/text.js @@ -7,156 +7,159 @@ /** * @fileoverview Generating PHP for text blocks. */ -'use strict'; -goog.module('Blockly.PHP.texts'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.texts'); -const {NameType} = goog.require('Blockly.Names'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import {NameType} from '../../core/names.js'; +import {Order} from './php_generator.js'; -PHP['text'] = function(block) { +export function text(block, generator) { // Text value. - const code = PHP.quote_(block.getFieldValue('TEXT')); - return [code, PHP.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('TEXT')); + return [code, Order.ATOMIC]; }; -PHP['text_multiline'] = function(block) { +export function text_multiline(block, generator) { // Text value. - const code = PHP.multiline_quote_(block.getFieldValue('TEXT')); + const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = - code.indexOf('.') !== -1 ? PHP.ORDER_STRING_CONCAT : PHP.ORDER_ATOMIC; + code.indexOf('.') !== -1 ? Order.STRING_CONCAT : Order.ATOMIC; return [code, order]; }; -PHP['text_join'] = function(block) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. if (block.itemCount_ === 0) { - return ["''", PHP.ORDER_ATOMIC]; + return ["''", Order.ATOMIC]; } else if (block.itemCount_ === 1) { - const element = PHP.valueToCode(block, 'ADD0', PHP.ORDER_NONE) || "''"; + const element = generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; const code = element; - return [code, PHP.ORDER_NONE]; + return [code, Order.NONE]; } else if (block.itemCount_ === 2) { const element0 = - PHP.valueToCode(block, 'ADD0', PHP.ORDER_STRING_CONCAT) || "''"; + generator.valueToCode(block, 'ADD0', Order.STRING_CONCAT) || "''"; const element1 = - PHP.valueToCode(block, 'ADD1', PHP.ORDER_STRING_CONCAT) || "''"; + generator.valueToCode(block, 'ADD1', Order.STRING_CONCAT) || "''"; const code = element0 + ' . ' + element1; - return [code, PHP.ORDER_STRING_CONCAT]; + return [code, Order.STRING_CONCAT]; } else { const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { - elements[i] = PHP.valueToCode(block, 'ADD' + i, PHP.ORDER_NONE) || "''"; + elements[i] = + generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''"; } const code = 'implode(\'\', array(' + elements.join(',') + '))'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } }; -PHP['text_append'] = function(block) { +export function text_append(block, generator) { // Append to a variable in place. const varName = - PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const value = PHP.valueToCode(block, 'TEXT', PHP.ORDER_ASSIGNMENT) || "''"; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + const value = + generator.valueToCode(block, 'TEXT', Order.ASSIGNMENT) || "''"; return varName + ' .= ' + value + ';\n'; }; -PHP['text_length'] = function(block) { +export function text_length(block, generator) { // String or array length. - const functionName = PHP.provideFunction_('length', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($value) { + const functionName = generator.provideFunction_('length', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($value) { if (is_string($value)) { return strlen($value); } return count($value); } `); - const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; - return [functionName + '(' + text + ')', PHP.ORDER_FUNCTION_CALL]; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; + return [functionName + '(' + text + ')', Order.FUNCTION_CALL]; }; -PHP['text_isEmpty'] = function(block) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? - const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; - return ['empty(' + text + ')', PHP.ORDER_FUNCTION_CALL]; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; + return ['empty(' + text + ')', Order.FUNCTION_CALL]; }; -PHP['text_indexOf'] = function(block) { +export function text_indexOf(block, generator) { // Search the text for a substring. const operator = block.getFieldValue('END') === 'FIRST' ? 'strpos' : 'strrpos'; - const substring = PHP.valueToCode(block, 'FIND', PHP.ORDER_NONE) || "''"; - const text = PHP.valueToCode(block, 'VALUE', PHP.ORDER_NONE) || "''"; + const substring = generator.valueToCode(block, 'FIND', Order.NONE) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; let errorIndex = ' -1'; let indexAdjustment = ''; if (block.workspace.options.oneBasedIndex) { errorIndex = ' 0'; indexAdjustment = ' + 1'; } - const functionName = PHP.provideFunction_( + const functionName = generator.provideFunction_( block.getFieldValue('END') === 'FIRST' ? 'text_indexOf' : 'text_lastIndexOf', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text, $search) { +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $search) { $pos = ${operator}($text, $search); return $pos === false ? ${errorIndex} : $pos${indexAdjustment}; } `); const code = functionName + '(' + text + ', ' + substring + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['text_charAt'] = function(block) { +export function text_charAt(block, generator) { // Get letter at index. const where = block.getFieldValue('WHERE') || 'FROM_START'; - const textOrder = (where === 'RANDOM') ? PHP.ORDER_NONE : PHP.ORDER_NONE; - const text = PHP.valueToCode(block, 'VALUE', textOrder) || "''"; + const textOrder = (where === 'RANDOM') ? Order.NONE : Order.NONE; + const text = generator.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = 'substr(' + text + ', 0, 1)'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'LAST': { const code = 'substr(' + text + ', -1)'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'FROM_START': { - const at = PHP.getAdjusted(block, 'AT'); + const at = generator.getAdjusted(block, 'AT'); const code = 'substr(' + text + ', ' + at + ', 1)'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'FROM_END': { - const at = PHP.getAdjusted(block, 'AT', 1, true); + const at = generator.getAdjusted(block, 'AT', 1, true); const code = 'substr(' + text + ', ' + at + ', 1)'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } case 'RANDOM': { - const functionName = PHP.provideFunction_('text_random_letter', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text) { + const functionName = generator.provideFunction_('text_random_letter', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text) { return $text[rand(0, strlen($text) - 1)]; } `); const code = functionName + '(' + text + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } } throw Error('Unhandled option (text_charAt).'); }; -PHP['text_getSubstring'] = function(block) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); - const text = PHP.valueToCode(block, 'STRING', PHP.ORDER_NONE) || "''"; + const text = generator.valueToCode(block, 'STRING', Order.NONE) || "''"; if (where1 === 'FIRST' && where2 === 'LAST') { const code = text; - return [code, PHP.ORDER_NONE]; + return [code, Order.NONE]; } else { - const at1 = PHP.getAdjusted(block, 'AT1'); - const at2 = PHP.getAdjusted(block, 'AT2'); - const functionName = PHP.provideFunction_('text_get_substring', ` -function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, $at2) { + const at1 = generator.getAdjusted(block, 'AT1'); + const at2 = generator.getAdjusted(block, 'AT2'); + const functionName = generator.provideFunction_('text_get_substring', ` +function ${generator.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, $at2) { if ($where1 == 'FROM_END') { $at1 = strlen($text) - 1 - $at1; } else if ($where1 == 'FIRST') { @@ -179,13 +182,13 @@ function ${PHP.FUNCTION_NAME_PLACEHOLDER_}($text, $where1, $at1, $where2, $at2) `); const code = functionName + '(' + text + ', \'' + where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } }; -PHP['text_changeCase'] = function(block) { +export function text_changeCase(block, generator) { // Change capitalization. - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; let code; if (block.getFieldValue('CASE') === 'UPPERCASE') { code = 'strtoupper(' + text + ')'; @@ -194,62 +197,62 @@ PHP['text_changeCase'] = function(block) { } else if (block.getFieldValue('CASE') === 'TITLECASE') { code = 'ucwords(strtolower(' + text + '))'; } - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['text_trim'] = function(block) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = {'LEFT': 'ltrim', 'RIGHT': 'rtrim', 'BOTH': 'trim'}; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; - return [operator + '(' + text + ')', PHP.ORDER_FUNCTION_CALL]; + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + return [operator + '(' + text + ')', Order.FUNCTION_CALL]; }; -PHP['text_print'] = function(block) { +export function text_print(block, generator) { // Print statement. - const msg = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; + const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ');\n'; }; -PHP['text_prompt_ext'] = function(block) { +export function text_prompt_ext(block, generator) { // Prompt function. let msg; if (block.getField('TEXT')) { // Internal message. - msg = PHP.quote_(block.getFieldValue('TEXT')); + msg = generator.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; + msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; } let code = 'readline(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; if (toNumber) { code = 'floatval(' + code + ')'; } - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['text_prompt'] = PHP['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -PHP['text_count'] = function(block) { - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; - const sub = PHP.valueToCode(block, 'SUB', PHP.ORDER_NONE) || "''"; +export function text_count(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; const code = 'strlen(' + sub + ') === 0' + ' ? strlen(' + text + ') + 1' + ' : substr_count(' + text + ', ' + sub + ')'; - return [code, PHP.ORDER_CONDITIONAL]; + return [code, Order.CONDITIONAL]; }; -PHP['text_replace'] = function(block) { - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; - const from = PHP.valueToCode(block, 'FROM', PHP.ORDER_NONE) || "''"; - const to = PHP.valueToCode(block, 'TO', PHP.ORDER_NONE) || "''"; +export function text_replace(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; + const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; const code = 'str_replace(' + from + ', ' + to + ', ' + text + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -PHP['text_reverse'] = function(block) { - const text = PHP.valueToCode(block, 'TEXT', PHP.ORDER_NONE) || "''"; +export function text_reverse(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; const code = 'strrev(' + text + ')'; - return [code, PHP.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/php/variables.js b/generators/php/variables.js index b919f266b..f29274da7 100644 --- a/generators/php/variables.js +++ b/generators/php/variables.js @@ -7,26 +7,28 @@ /** * @fileoverview Generating PHP for variable blocks. */ -'use strict'; -goog.module('Blockly.PHP.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.variables'); -const {NameType} = goog.require('Blockly.Names'); -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); +import {NameType} from '../../core/names.js'; +import {Order} from './php_generator.js'; -PHP['variables_get'] = function(block) { +export function variables_get(block, generator) { // Variable getter. const code = - PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - return [code, PHP.ORDER_ATOMIC]; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + return [code, Order.ATOMIC]; }; -PHP['variables_set'] = function(block) { +export function variables_set(block, generator) { // Variable setter. const argument0 = - PHP.valueToCode(block, 'VALUE', PHP.ORDER_ASSIGNMENT) || '0'; + generator.valueToCode(block, 'VALUE', Order.ASSIGNMENT) || '0'; const varName = - PHP.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = ' + argument0 + ';\n'; }; diff --git a/generators/php/variables_dynamic.js b/generators/php/variables_dynamic.js index f4d71f481..030edc92c 100644 --- a/generators/php/variables_dynamic.js +++ b/generators/php/variables_dynamic.js @@ -7,15 +7,13 @@ /** * @fileoverview Generating PHP for dynamic variable blocks. */ -'use strict'; -goog.module('Blockly.PHP.variablesDynamic'); - -const {phpGenerator: PHP} = goog.require('Blockly.PHP'); -/** @suppress {extraRequire} */ -goog.require('Blockly.PHP.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.PHP.variablesDynamic'); -// PHP is dynamically typed. -PHP['variables_get_dynamic'] = PHP['variables_get']; -PHP['variables_set_dynamic'] = PHP['variables_set']; +// generator is dynamically typed. +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/generators/python.js b/generators/python.js index 09d92d19d..19b64ffd2 100644 --- a/generators/python.js +++ b/generators/python.js @@ -1,333 +1,44 @@ /** * @license - * Copyright 2012 Google LLC + * Copyright 2021 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** - * @fileoverview Helper functions for generating Python for blocks. - * @suppress {checkTypes|globalThis} + * @fileoverview Complete helper functions for generating Python for + * blocks. This is the entrypoint for python_compressed.js. + * @suppress {extraRequire} */ -'use strict'; -goog.module('Blockly.Python'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.all'); -const stringUtils = goog.require('Blockly.utils.string'); -const Variables = goog.require('Blockly.Variables'); -const {Block} = goog.requireType('Blockly.Block'); -const {CodeGenerator} = goog.require('Blockly.CodeGenerator'); -const {inputTypes} = goog.require('Blockly.inputTypes'); -const {Names, NameType} = goog.require('Blockly.Names'); -const {Workspace} = goog.requireType('Blockly.Workspace'); +import {PythonGenerator} from './python/python_generator.js'; +import * as colour from './python/colour.js'; +import * as lists from './python/lists.js'; +import * as logic from './python/logic.js'; +import * as loops from './python/loops.js'; +import * as math from './python/math.js'; +import * as procedures from './python/procedures.js'; +import * as text from './python/text.js'; +import * as variables from './python/variables.js'; +import * as variablesDynamic from './python/variables_dynamic.js'; +export * from './python/python_generator.js'; /** - * Python code generator. - * @type {!CodeGenerator} + * Python code generator instance. + * @type {!PythonGenerator} */ -const Python = new CodeGenerator('Python'); +export const pythonGenerator = new PythonGenerator(); -/** - * List of illegal variable names. - * This is not intended to be a security feature. Blockly is 100% client-side, - * so bypassing this list is trivial. This is intended to prevent users from - * accidentally clobbering a built-in object or function. - */ -Python.addReservedWords( - // import keyword - // print(','.join(sorted(keyword.kwlist))) - // https://docs.python.org/3/reference/lexical_analysis.html#keywords - // https://docs.python.org/2/reference/lexical_analysis.html#keywords - 'False,None,True,and,as,assert,break,class,continue,def,del,elif,else,' + - 'except,exec,finally,for,from,global,if,import,in,is,lambda,nonlocal,not,' + - 'or,pass,print,raise,return,try,while,with,yield,' + - // https://docs.python.org/3/library/constants.html - // https://docs.python.org/2/library/constants.html - 'NotImplemented,Ellipsis,__debug__,quit,exit,copyright,license,credits,' + - // >>> print(','.join(sorted(dir(__builtins__)))) - // https://docs.python.org/3/library/functions.html - // https://docs.python.org/2/library/functions.html - 'ArithmeticError,AssertionError,AttributeError,BaseException,' + - 'BlockingIOError,BrokenPipeError,BufferError,BytesWarning,' + - 'ChildProcessError,ConnectionAbortedError,ConnectionError,' + - 'ConnectionRefusedError,ConnectionResetError,DeprecationWarning,EOFError,' + - 'Ellipsis,EnvironmentError,Exception,FileExistsError,FileNotFoundError,' + - 'FloatingPointError,FutureWarning,GeneratorExit,IOError,ImportError,' + - 'ImportWarning,IndentationError,IndexError,InterruptedError,' + - 'IsADirectoryError,KeyError,KeyboardInterrupt,LookupError,MemoryError,' + - 'ModuleNotFoundError,NameError,NotADirectoryError,NotImplemented,' + - 'NotImplementedError,OSError,OverflowError,PendingDeprecationWarning,' + - 'PermissionError,ProcessLookupError,RecursionError,ReferenceError,' + - 'ResourceWarning,RuntimeError,RuntimeWarning,StandardError,' + - 'StopAsyncIteration,StopIteration,SyntaxError,SyntaxWarning,SystemError,' + - 'SystemExit,TabError,TimeoutError,TypeError,UnboundLocalError,' + - 'UnicodeDecodeError,UnicodeEncodeError,UnicodeError,' + - 'UnicodeTranslateError,UnicodeWarning,UserWarning,ValueError,Warning,' + - 'ZeroDivisionError,_,__build_class__,__debug__,__doc__,__import__,' + - '__loader__,__name__,__package__,__spec__,abs,all,any,apply,ascii,' + - 'basestring,bin,bool,buffer,bytearray,bytes,callable,chr,classmethod,cmp,' + - 'coerce,compile,complex,copyright,credits,delattr,dict,dir,divmod,' + - 'enumerate,eval,exec,execfile,exit,file,filter,float,format,frozenset,' + - 'getattr,globals,hasattr,hash,help,hex,id,input,int,intern,isinstance,' + - 'issubclass,iter,len,license,list,locals,long,map,max,memoryview,min,' + - 'next,object,oct,open,ord,pow,print,property,quit,range,raw_input,reduce,' + - 'reload,repr,reversed,round,set,setattr,slice,sorted,staticmethod,str,' + - 'sum,super,tuple,type,unichr,unicode,vars,xrange,zip'); +// Add reserved words. This list should include all words mentioned +// in RESERVED WORDS: comments in the imports above. +pythonGenerator.addReservedWords('math,random,Number'); -/** - * Order of operation ENUMs. - * http://docs.python.org/reference/expressions.html#summary - */ -Python.ORDER_ATOMIC = 0; // 0 "" ... -Python.ORDER_COLLECTION = 1; // tuples, lists, dictionaries -Python.ORDER_STRING_CONVERSION = 1; // `expression...` -Python.ORDER_MEMBER = 2.1; // . [] -Python.ORDER_FUNCTION_CALL = 2.2; // () -Python.ORDER_EXPONENTIATION = 3; // ** -Python.ORDER_UNARY_SIGN = 4; // + - -Python.ORDER_BITWISE_NOT = 4; // ~ -Python.ORDER_MULTIPLICATIVE = 5; // * / // % -Python.ORDER_ADDITIVE = 6; // + - -Python.ORDER_BITWISE_SHIFT = 7; // << >> -Python.ORDER_BITWISE_AND = 8; // & -Python.ORDER_BITWISE_XOR = 9; // ^ -Python.ORDER_BITWISE_OR = 10; // | -Python.ORDER_RELATIONAL = 11; // in, not in, is, is not, - // <, <=, >, >=, <>, !=, == -Python.ORDER_LOGICAL_NOT = 12; // not -Python.ORDER_LOGICAL_AND = 13; // and -Python.ORDER_LOGICAL_OR = 14; // or -Python.ORDER_CONDITIONAL = 15; // if else -Python.ORDER_LAMBDA = 16; // lambda -Python.ORDER_NONE = 99; // (...) - -/** - * List of outer-inner pairings that do NOT require parentheses. - * @type {!Array>} - */ -Python.ORDER_OVERRIDES = [ - // (foo()).bar -> foo().bar - // (foo())[0] -> foo()[0] - [Python.ORDER_FUNCTION_CALL, Python.ORDER_MEMBER], - // (foo())() -> foo()() - [Python.ORDER_FUNCTION_CALL, Python.ORDER_FUNCTION_CALL], - // (foo.bar).baz -> foo.bar.baz - // (foo.bar)[0] -> foo.bar[0] - // (foo[0]).bar -> foo[0].bar - // (foo[0])[1] -> foo[0][1] - [Python.ORDER_MEMBER, Python.ORDER_MEMBER], - // (foo.bar)() -> foo.bar() - // (foo[0])() -> foo[0]() - [Python.ORDER_MEMBER, Python.ORDER_FUNCTION_CALL], - - // not (not foo) -> not not foo - [Python.ORDER_LOGICAL_NOT, Python.ORDER_LOGICAL_NOT], - // a and (b and c) -> a and b and c - [Python.ORDER_LOGICAL_AND, Python.ORDER_LOGICAL_AND], - // a or (b or c) -> a or b or c - [Python.ORDER_LOGICAL_OR, Python.ORDER_LOGICAL_OR] -]; - -/** - * Whether the init method has been called. - * @type {?boolean} - */ -Python.isInitialized = false; - -/** - * Initialise the database of variable names. - * @param {!Workspace} workspace Workspace to generate code from. - * @this {CodeGenerator} - */ -Python.init = function(workspace) { - // Call Blockly.CodeGenerator's init. - Object.getPrototypeOf(this).init.call(this); - - /** - * Empty loops or conditionals are not allowed in Python. - */ - this.PASS = this.INDENT + 'pass\n'; - - if (!this.nameDB_) { - this.nameDB_ = new Names(this.RESERVED_WORDS_); - } else { - this.nameDB_.reset(); - } - - this.nameDB_.setVariableMap(workspace.getVariableMap()); - this.nameDB_.populateVariables(workspace); - this.nameDB_.populateProcedures(workspace); - - const defvars = []; - // Add developer variables (not created or named by the user). - const devVarList = Variables.allDeveloperVariables(workspace); - for (let i = 0; i < devVarList.length; i++) { - defvars.push( - this.nameDB_.getName(devVarList[i], Names.DEVELOPER_VARIABLE_TYPE) + - ' = None'); - } - - // Add user variables, but only ones that are being used. - const variables = Variables.allUsedVarModels(workspace); - for (let i = 0; i < variables.length; i++) { - defvars.push( - this.nameDB_.getName(variables[i].getId(), NameType.VARIABLE) + - ' = None'); - } - - this.definitions_['variables'] = defvars.join('\n'); - this.isInitialized = true; -}; - -/** - * Prepend the generated code with import statements and variable definitions. - * @param {string} code Generated code. - * @return {string} Completed code. - */ -Python.finish = function(code) { - // Convert the definitions dictionary into a list. - const imports = []; - const definitions = []; - for (let name in this.definitions_) { - const def = this.definitions_[name]; - if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) { - imports.push(def); - } else { - definitions.push(def); - } - } - // Call Blockly.CodeGenerator's finish. - code = Object.getPrototypeOf(this).finish.call(this, code); - this.isInitialized = false; - - this.nameDB_.reset(); - const allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n'); - return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code; -}; - -/** - * Naked values are top-level blocks with outputs that aren't plugged into - * anything. - * @param {string} line Line of generated code. - * @return {string} Legal line of code. - */ -Python.scrubNakedValue = function(line) { - return line + '\n'; -}; - -/** - * Encode a string as a properly escaped Python string, complete with quotes. - * @param {string} string Text to encode. - * @return {string} Python string. - * @protected - */ -Python.quote_ = function(string) { - // Can't use goog.string.quote since % must also be escaped. - string = string.replace(/\\/g, '\\\\').replace(/\n/g, '\\\n'); - - // Follow the CPython behaviour of repr() for a non-byte string. - let quote = '\''; - if (string.indexOf('\'') !== -1) { - if (string.indexOf('"') === -1) { - quote = '"'; - } else { - string = string.replace(/'/g, '\\\''); - } - } - return quote + string + quote; -}; - -/** - * Encode a string as a properly escaped multiline Python string, complete - * with quotes. - * @param {string} string Text to encode. - * @return {string} Python string. - * @protected - */ -Python.multiline_quote_ = function(string) { - const lines = string.split(/\n/g).map(this.quote_); - // Join with the following, plus a newline: - // + '\n' + - return lines.join(' + \'\\n\' + \n'); -}; - -/** - * Common tasks for generating Python from blocks. - * Handles comments for the specified block and any connected value blocks. - * Calls any statements following this block. - * @param {!Block} block The current block. - * @param {string} code The Python code created for this block. - * @param {boolean=} opt_thisOnly True to generate code for only this statement. - * @return {string} Python code with comments and subsequent blocks added. - * @protected - */ -Python.scrub_ = function(block, code, opt_thisOnly) { - let commentCode = ''; - // Only collect comments for blocks that aren't inline. - if (!block.outputConnection || !block.outputConnection.targetConnection) { - // Collect comment for this block. - let comment = block.getCommentText(); - if (comment) { - comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); - commentCode += this.prefixLines(comment + '\n', '# '); - } - // Collect comments for all value arguments. - // Don't collect comments for nested statements. - for (let i = 0; i < block.inputList.length; i++) { - if (block.inputList[i].type === inputTypes.VALUE) { - const childBlock = block.inputList[i].connection.targetBlock(); - if (childBlock) { - comment = this.allNestedComments(childBlock); - if (comment) { - commentCode += this.prefixLines(comment, '# '); - } - } - } - } - } - const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); - const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); - return commentCode + code + nextCode; -}; - -/** - * Gets a property and adjusts the value, taking into account indexing. - * If a static int, casts to an integer, otherwise returns a code string. - * @param {!Block} block The block. - * @param {string} atId The property ID of the element to get. - * @param {number=} opt_delta Value to add. - * @param {boolean=} opt_negate Whether to negate the value. - * @return {string|number} - */ -Python.getAdjustedInt = function(block, atId, opt_delta, opt_negate) { - let delta = opt_delta || 0; - if (block.workspace.options.oneBasedIndex) { - delta--; - } - const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; - const atOrder = delta ? this.ORDER_ADDITIVE : this.ORDER_NONE; - let at = this.valueToCode(block, atId, atOrder) || defaultAtIndex; - - if (stringUtils.isNumber(at)) { - // If the index is a naked number, adjust it right now. - at = parseInt(at, 10) + delta; - if (opt_negate) { - at = -at; - } - } else { - // If the index is dynamic, adjust it in code. - if (delta > 0) { - at = 'int(' + at + ' + ' + delta + ')'; - } else if (delta < 0) { - at = 'int(' + at + ' - ' + -delta + ')'; - } else { - at = 'int(' + at + ')'; - } - if (opt_negate) { - at = '-' + at; - } - } - return at; -}; - -exports.pythonGenerator = Python; +// Install per-block-type generator functions: +Object.assign( + pythonGenerator.forBlock, + colour, lists, logic, loops, math, procedures, + text, variables, variablesDynamic +); diff --git a/generators/python/all.js b/generators/python/all.js deleted file mode 100644 index b6727dc46..000000000 --- a/generators/python/all.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Complete helper functions for generating Python for - * blocks. This is the entrypoint for python_compressed.js. - * @suppress {extraRequire} - */ -'use strict'; - -goog.module('Blockly.Python.all'); - -const moduleExports = goog.require('Blockly.Python'); -goog.require('Blockly.Python.colour'); -goog.require('Blockly.Python.lists'); -goog.require('Blockly.Python.logic'); -goog.require('Blockly.Python.loops'); -goog.require('Blockly.Python.math'); -goog.require('Blockly.Python.procedures'); -goog.require('Blockly.Python.texts'); -goog.require('Blockly.Python.variables'); -goog.require('Blockly.Python.variablesDynamic'); - -exports = moduleExports; diff --git a/generators/python/colour.js b/generators/python/colour.js index 1cbc33747..3236fc408 100644 --- a/generators/python/colour.js +++ b/generators/python/colour.js @@ -7,46 +7,46 @@ /** * @fileoverview Generating Python for colour blocks. */ -'use strict'; -goog.module('Blockly.Python.colour'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.colour'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import {Order} from './python_generator.js'; -Python['colour_picker'] = function(block) { +export function colour_picker(block, generator) { // Colour picker. - const code = Python.quote_(block.getFieldValue('COLOUR')); - return [code, Python.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('COLOUR')); + return [code, Order.ATOMIC]; }; -Python['colour_random'] = function(block) { +export function colour_random(block, generator) { // Generate a random colour. - Python.definitions_['import_random'] = 'import random'; + generator.definitions_['import_random'] = 'import random'; const code = '\'#%06x\' % random.randint(0, 2**24 - 1)'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['colour_rgb'] = function(block) { +export function colour_rgb(block, generator) { // Compose a colour from RGB components expressed as percentages. - const functionName = Python.provideFunction_('colour_rgb', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(r, g, b): + const functionName = generator.provideFunction_('colour_rgb', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(r, g, b): r = round(min(100, max(0, r)) * 2.55) g = round(min(100, max(0, g)) * 2.55) b = round(min(100, max(0, b)) * 2.55) return '#%02x%02x%02x' % (r, g, b) `); - const r = Python.valueToCode(block, 'RED', Python.ORDER_NONE) || 0; - const g = Python.valueToCode(block, 'GREEN', Python.ORDER_NONE) || 0; - const b = Python.valueToCode(block, 'BLUE', Python.ORDER_NONE) || 0; + const r = generator.valueToCode(block, 'RED', Order.NONE) || 0; + const g = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; + const b = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; const code = functionName + '(' + r + ', ' + g + ', ' + b + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['colour_blend'] = function(block) { +export function colour_blend(block, generator) { // Blend two colours together. - const functionName = Python.provideFunction_('colour_blend', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): + const functionName = generator.provideFunction_('colour_blend', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): r1, r2 = int(colour1[1:3], 16), int(colour2[1:3], 16) g1, g2 = int(colour1[3:5], 16), int(colour2[3:5], 16) b1, b2 = int(colour1[5:7], 16), int(colour2[5:7], 16) @@ -57,11 +57,13 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(colour1, colour2, ratio): return '#%02x%02x%02x' % (r, g, b) `); const colour1 = - Python.valueToCode(block, 'COLOUR1', Python.ORDER_NONE) || '\'#000000\''; + generator.valueToCode(block, 'COLOUR1', Order.NONE) + || '\'#000000\''; const colour2 = - Python.valueToCode(block, 'COLOUR2', Python.ORDER_NONE) || '\'#000000\''; - const ratio = Python.valueToCode(block, 'RATIO', Python.ORDER_NONE) || 0; + generator.valueToCode(block, 'COLOUR2', Order.NONE) + || '\'#000000\''; + const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0; const code = functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/python/lists.js b/generators/python/lists.js index e09127cfa..f4dbd1166 100644 --- a/generators/python/lists.js +++ b/generators/python/lists.js @@ -7,57 +7,57 @@ /** * @fileoverview Generating Python for list blocks. */ -'use strict'; -goog.module('Blockly.Python.lists'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.lists'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './python_generator.js'; -Python['lists_create_empty'] = function(block) { +export function lists_create_empty(block, generator) { // Create an empty list. - return ['[]', Python.ORDER_ATOMIC]; + return ['[]', Order.ATOMIC]; }; -Python['lists_create_with'] = function(block) { +export function lists_create_with(block, generator) { // Create a list with any number of elements of any type. const elements = new Array(block.itemCount_); for (let i = 0; i < block.itemCount_; i++) { elements[i] = - Python.valueToCode(block, 'ADD' + i, Python.ORDER_NONE) || 'None'; + generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'None'; } const code = '[' + elements.join(', ') + ']'; - return [code, Python.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -Python['lists_repeat'] = function(block) { +export function lists_repeat(block, generator) { // Create a list with one element repeated. - const item = Python.valueToCode(block, 'ITEM', Python.ORDER_NONE) || 'None'; + const item = generator.valueToCode(block, 'ITEM', Order.NONE) || 'None'; const times = - Python.valueToCode(block, 'NUM', Python.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; const code = '[' + item + '] * ' + times; - return [code, Python.ORDER_MULTIPLICATIVE]; + return [code, Order.MULTIPLICATIVE]; }; -Python['lists_length'] = function(block) { +export function lists_length(block, generator) { // String or array length. - const list = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '[]'; - return ['len(' + list + ')', Python.ORDER_FUNCTION_CALL]; + const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '[]'; + return ['len(' + list + ')', Order.FUNCTION_CALL]; }; -Python['lists_isEmpty'] = function(block) { +export function lists_isEmpty(block, generator) { // Is the string null or array empty? - const list = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'VALUE', Order.NONE) || '[]'; const code = 'not len(' + list + ')'; - return [code, Python.ORDER_LOGICAL_NOT]; + return [code, Order.LOGICAL_NOT]; }; -Python['lists_indexOf'] = function(block) { +export function lists_indexOf(block, generator) { // Find an item in the list. - const item = Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || '[]'; - const list = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''"; + const item = generator.valueToCode(block, 'FIND', Order.NONE) || '[]'; + const list = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; let errorIndex = ' -1'; let firstIndexAdjustment = ''; let lastIndexAdjustment = ' - 1'; @@ -70,41 +70,41 @@ Python['lists_indexOf'] = function(block) { let functionName; if (block.getFieldValue('END') === 'FIRST') { - functionName = Python.provideFunction_('first_index', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): + functionName = generator.provideFunction_('first_index', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): try: index = my_list.index(elem)${firstIndexAdjustment} except: index =${errorIndex} return index `); } else { - functionName = Python.provideFunction_('last_index', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): + functionName = generator.provideFunction_('last_index', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, elem): try: index = len(my_list) - my_list[::-1].index(elem)${lastIndexAdjustment} except: index =${errorIndex} return index `); } const code = functionName + '(' + list + ', ' + item + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['lists_getIndex'] = function(block) { +export function lists_getIndex(block, generator) { // Get element at index. // Note: Until January 2013 this block did not have MODE or WHERE inputs. const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; const listOrder = - (where === 'RANDOM') ? Python.ORDER_NONE : Python.ORDER_MEMBER; - const list = Python.valueToCode(block, 'VALUE', listOrder) || '[]'; + (where === 'RANDOM') ? Order.NONE : Order.MEMBER; + const list = generator.valueToCode(block, 'VALUE', listOrder) || '[]'; switch (where) { case 'FIRST': if (mode === 'GET') { const code = list + '[0]'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.pop(0)'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return list + '.pop(0)\n'; } @@ -112,55 +112,55 @@ Python['lists_getIndex'] = function(block) { case 'LAST': if (mode === 'GET') { const code = list + '[-1]'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.pop()'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return list + '.pop()\n'; } break; case 'FROM_START': { - const at = Python.getAdjustedInt(block, 'AT'); + const at = generator.getAdjustedInt(block, 'AT'); if (mode === 'GET') { const code = list + '[' + at + ']'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.pop(' + at + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return list + '.pop(' + at + ')\n'; } break; } case 'FROM_END': { - const at = Python.getAdjustedInt(block, 'AT', 1, true); + const at = generator.getAdjustedInt(block, 'AT', 1, true); if (mode === 'GET') { const code = list + '[' + at + ']'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } else if (mode === 'GET_REMOVE') { const code = list + '.pop(' + at + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return list + '.pop(' + at + ')\n'; } break; } case 'RANDOM': - Python.definitions_['import_random'] = 'import random'; + generator.definitions_['import_random'] = 'import random'; if (mode === 'GET') { const code = 'random.choice(' + list + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else { const functionName = - Python.provideFunction_('lists_remove_random_item', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): + generator.provideFunction_('lists_remove_random_item', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): x = int(random.random() * len(myList)) return myList.pop(x) `); const code = functionName + '(' + list + ')'; if (mode === 'GET_REMOVE') { - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } else if (mode === 'REMOVE') { return code + '\n'; } @@ -170,13 +170,13 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): throw Error('Unhandled combination (lists_getIndex).'); }; -Python['lists_setIndex'] = function(block) { +export function lists_setIndex(block, generator) { // Set element at index. // Note: Until February 2013 this block did not have MODE or WHERE inputs. - let list = Python.valueToCode(block, 'LIST', Python.ORDER_MEMBER) || '[]'; + let list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const mode = block.getFieldValue('MODE') || 'GET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; - const value = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || 'None'; + const value = generator.valueToCode(block, 'TO', Order.NONE) || 'None'; // Cache non-trivial values to variables to prevent repeated look-ups. // Closure, which accesses and modifies 'list'. function cacheList() { @@ -184,7 +184,7 @@ Python['lists_setIndex'] = function(block) { return ''; } const listVar = - Python.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); + generator.nameDB_.getDistinctName('tmp_list', NameType.VARIABLE); const code = listVar + ' = ' + list + '\n'; list = listVar; return code; @@ -206,7 +206,7 @@ Python['lists_setIndex'] = function(block) { } break; case 'FROM_START': { - const at = Python.getAdjustedInt(block, 'AT'); + const at = generator.getAdjustedInt(block, 'AT'); if (mode === 'SET') { return list + '[' + at + '] = ' + value + '\n'; } else if (mode === 'INSERT') { @@ -215,7 +215,7 @@ Python['lists_setIndex'] = function(block) { break; } case 'FROM_END': { - const at = Python.getAdjustedInt(block, 'AT', 1, true); + const at = generator.getAdjustedInt(block, 'AT', 1, true); if (mode === 'SET') { return list + '[' + at + '] = ' + value + '\n'; } else if (mode === 'INSERT') { @@ -224,9 +224,10 @@ Python['lists_setIndex'] = function(block) { break; } case 'RANDOM': { - Python.definitions_['import_random'] = 'import random'; + generator.definitions_['import_random'] = 'import random'; let code = cacheList(); - const xVar = Python.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); + const xVar = + generator.nameDB_.getDistinctName('tmp_x', NameType.VARIABLE); code += xVar + ' = int(random.random() * len(' + list + '))\n'; if (mode === 'SET') { code += list + '[' + xVar + '] = ' + value + '\n'; @@ -241,21 +242,21 @@ Python['lists_setIndex'] = function(block) { throw Error('Unhandled combination (lists_setIndex).'); }; -Python['lists_getSublist'] = function(block) { +export function lists_getSublist(block, generator) { // Get sublist. - const list = Python.valueToCode(block, 'LIST', Python.ORDER_MEMBER) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.MEMBER) || '[]'; const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); let at1; switch (where1) { case 'FROM_START': - at1 = Python.getAdjustedInt(block, 'AT1'); + at1 = generator.getAdjustedInt(block, 'AT1'); if (at1 === 0) { at1 = ''; } break; case 'FROM_END': - at1 = Python.getAdjustedInt(block, 'AT1', 1, true); + at1 = generator.getAdjustedInt(block, 'AT1', 1, true); break; case 'FIRST': at1 = ''; @@ -267,14 +268,14 @@ Python['lists_getSublist'] = function(block) { let at2; switch (where2) { case 'FROM_START': - at2 = Python.getAdjustedInt(block, 'AT2', 1); + at2 = generator.getAdjustedInt(block, 'AT2', 1); break; case 'FROM_END': - at2 = Python.getAdjustedInt(block, 'AT2', 0, true); + at2 = generator.getAdjustedInt(block, 'AT2', 0, true); // Ensure that if the result calculated is 0 that sub-sequence will // include all elements as expected. if (!stringUtils.isNumber(String(at2))) { - Python.definitions_['import_sys'] = 'import sys'; + generator.definitions_['import_sys'] = 'import sys'; at2 += ' or sys.maxsize'; } else if (at2 === 0) { at2 = ''; @@ -287,16 +288,16 @@ Python['lists_getSublist'] = function(block) { throw Error('Unhandled option (lists_getSublist)'); } const code = list + '[' + at1 + ' : ' + at2 + ']'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; }; -Python['lists_sort'] = function(block) { +export function lists_sort(block, generator) { // Block for sorting a list. - const list = (Python.valueToCode(block, 'LIST', Python.ORDER_NONE) || '[]'); + const list = (generator.valueToCode(block, 'LIST', Order.NONE) || '[]'); const type = block.getFieldValue('TYPE'); const reverse = block.getFieldValue('DIRECTION') === '1' ? 'False' : 'True'; - const sortFunctionName = Python.provideFunction_('lists_sort', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): + const sortFunctionName = generator.provideFunction_('lists_sort', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): def try_float(s): try: return float(s) @@ -314,33 +315,33 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(my_list, type, reverse): const code = sortFunctionName + '(' + list + ', "' + type + '", ' + reverse + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['lists_split'] = function(block) { +export function lists_split(block, generator) { // Block for splitting text into a list, or joining a list into text. const mode = block.getFieldValue('MODE'); let code; if (mode === 'SPLIT') { const value_input = - Python.valueToCode(block, 'INPUT', Python.ORDER_MEMBER) || "''"; - const value_delim = Python.valueToCode(block, 'DELIM', Python.ORDER_NONE); + generator.valueToCode(block, 'INPUT', Order.MEMBER) || "''"; + const value_delim = generator.valueToCode(block, 'DELIM', Order.NONE); code = value_input + '.split(' + value_delim + ')'; } else if (mode === 'JOIN') { const value_input = - Python.valueToCode(block, 'INPUT', Python.ORDER_NONE) || '[]'; + generator.valueToCode(block, 'INPUT', Order.NONE) || '[]'; const value_delim = - Python.valueToCode(block, 'DELIM', Python.ORDER_MEMBER) || "''"; + generator.valueToCode(block, 'DELIM', Order.MEMBER) || "''"; code = value_delim + '.join(' + value_input + ')'; } else { throw Error('Unknown mode: ' + mode); } - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['lists_reverse'] = function(block) { +export function lists_reverse(block, generator) { // Block for reversing a list. - const list = Python.valueToCode(block, 'LIST', Python.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; const code = 'list(reversed(' + list + '))'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; diff --git a/generators/python/logic.js b/generators/python/logic.js index ca6ab2d02..a71a0ef5f 100644 --- a/generators/python/logic.js +++ b/generators/python/logic.js @@ -7,41 +7,47 @@ /** * @fileoverview Generating Python for logic blocks. */ -'use strict'; -goog.module('Blockly.Python.logic'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.logic'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import {Order} from './python_generator.js'; -Python['controls_if'] = function(block) { +export function controls_if(block, generator) { // If/elseif/else condition. let n = 0; let code = '', branchCode, conditionCode; - if (Python.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - code += Python.injectId(Python.STATEMENT_PREFIX, block); + code += generator.injectId(generator.STATEMENT_PREFIX, block); } do { conditionCode = - Python.valueToCode(block, 'IF' + n, Python.ORDER_NONE) || 'False'; - branchCode = Python.statementToCode(block, 'DO' + n) || Python.PASS; - if (Python.STATEMENT_SUFFIX) { + generator.valueToCode(block, 'IF' + n, Order.NONE) || 'False'; + branchCode = + generator.statementToCode(block, 'DO' + n) || + generator.PASS; + if (generator.STATEMENT_SUFFIX) { branchCode = - Python.prefixLines( - Python.injectId(Python.STATEMENT_SUFFIX, block), Python.INDENT) + + generator.prefixLines( + generator.injectId(generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += (n === 0 ? 'if ' : 'elif ') + conditionCode + ':\n' + branchCode; n++; } while (block.getInput('IF' + n)); - if (block.getInput('ELSE') || Python.STATEMENT_SUFFIX) { - branchCode = Python.statementToCode(block, 'ELSE') || Python.PASS; - if (Python.STATEMENT_SUFFIX) { + if (block.getInput('ELSE') || generator.STATEMENT_SUFFIX) { + branchCode = + generator.statementToCode(block, 'ELSE') || generator.PASS; + if (generator.STATEMENT_SUFFIX) { branchCode = - Python.prefixLines( - Python.injectId(Python.STATEMENT_SUFFIX, block), Python.INDENT) + + generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), + generator.INDENT) + branchCode; } code += 'else:\n' + branchCode; @@ -49,27 +55,27 @@ Python['controls_if'] = function(block) { return code; }; -Python['controls_ifelse'] = Python['controls_if']; +export const controls_ifelse = controls_if; -Python['logic_compare'] = function(block) { +export function logic_compare(block, generator) { // Comparison operator. const OPERATORS = {'EQ': '==', 'NEQ': '!=', 'LT': '<', 'LTE': '<=', 'GT': '>', 'GTE': '>='}; const operator = OPERATORS[block.getFieldValue('OP')]; - const order = Python.ORDER_RELATIONAL; - const argument0 = Python.valueToCode(block, 'A', order) || '0'; - const argument1 = Python.valueToCode(block, 'B', order) || '0'; + const order = Order.RELATIONAL; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + ' ' + operator + ' ' + argument1; return [code, order]; }; -Python['logic_operation'] = function(block) { +export function logic_operation(block, generator) { // Operations 'and', 'or'. const operator = (block.getFieldValue('OP') === 'AND') ? 'and' : 'or'; const order = - (operator === 'and') ? Python.ORDER_LOGICAL_AND : Python.ORDER_LOGICAL_OR; - let argument0 = Python.valueToCode(block, 'A', order); - let argument1 = Python.valueToCode(block, 'B', order); + (operator === 'and') ? Order.LOGICAL_AND : Order.LOGICAL_OR; + let argument0 = generator.valueToCode(block, 'A', order); + let argument1 = generator.valueToCode(block, 'B', order); if (!argument0 && !argument1) { // If there are no arguments, then the return value is false. argument0 = 'False'; @@ -88,33 +94,33 @@ Python['logic_operation'] = function(block) { return [code, order]; }; -Python['logic_negate'] = function(block) { +export function logic_negate(block, generator) { // Negation. const argument0 = - Python.valueToCode(block, 'BOOL', Python.ORDER_LOGICAL_NOT) || 'True'; + generator.valueToCode(block, 'BOOL', Order.LOGICAL_NOT) || 'True'; const code = 'not ' + argument0; - return [code, Python.ORDER_LOGICAL_NOT]; + return [code, Order.LOGICAL_NOT]; }; -Python['logic_boolean'] = function(block) { +export function logic_boolean(block, generator) { // Boolean values true and false. const code = (block.getFieldValue('BOOL') === 'TRUE') ? 'True' : 'False'; - return [code, Python.ORDER_ATOMIC]; + return [code, Order.ATOMIC]; }; -Python['logic_null'] = function(block) { +export function logic_null(block, generator) { // Null data type. - return ['None', Python.ORDER_ATOMIC]; + return ['None', Order.ATOMIC]; }; -Python['logic_ternary'] = function(block) { +export function logic_ternary(block, generator) { // Ternary operator. const value_if = - Python.valueToCode(block, 'IF', Python.ORDER_CONDITIONAL) || 'False'; + generator.valueToCode(block, 'IF', Order.CONDITIONAL) || 'False'; const value_then = - Python.valueToCode(block, 'THEN', Python.ORDER_CONDITIONAL) || 'None'; + generator.valueToCode(block, 'THEN', Order.CONDITIONAL) || 'None'; const value_else = - Python.valueToCode(block, 'ELSE', Python.ORDER_CONDITIONAL) || 'None'; + generator.valueToCode(block, 'ELSE', Order.CONDITIONAL) || 'None'; const code = value_then + ' if ' + value_if + ' else ' + value_else; - return [code, Python.ORDER_CONDITIONAL]; + return [code, Order.CONDITIONAL]; }; diff --git a/generators/python/loops.js b/generators/python/loops.js index 99e1f6bbf..f66d90802 100644 --- a/generators/python/loops.js +++ b/generators/python/loops.js @@ -7,16 +7,16 @@ /** * @fileoverview Generating Python for loop blocks. */ -'use strict'; -goog.module('Blockly.Python.loops'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.loops'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './python_generator.js'; -Python['controls_repeat_ext'] = function(block) { +export function controls_repeat_ext(block, generator) { // Repeat n times. let repeats; if (block.getField('TIMES')) { @@ -24,68 +24,70 @@ Python['controls_repeat_ext'] = function(block) { repeats = String(parseInt(block.getFieldValue('TIMES'), 10)); } else { // External number. - repeats = Python.valueToCode(block, 'TIMES', Python.ORDER_NONE) || '0'; + repeats = generator.valueToCode(block, 'TIMES', Order.NONE) || '0'; } if (stringUtils.isNumber(repeats)) { repeats = parseInt(repeats, 10); } else { repeats = 'int(' + repeats + ')'; } - let branch = Python.statementToCode(block, 'DO'); - branch = Python.addLoopTrap(branch, block) || Python.PASS; - const loopVar = Python.nameDB_.getDistinctName('count', NameType.VARIABLE); + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block) || generator.PASS; + const loopVar = + generator.nameDB_.getDistinctName('count', NameType.VARIABLE); const code = 'for ' + loopVar + ' in range(' + repeats + '):\n' + branch; return code; }; -Python['controls_repeat'] = Python['controls_repeat_ext']; +export const controls_repeat = controls_repeat_ext; -Python['controls_whileUntil'] = function(block) { +export function controls_whileUntil(block, generator) { // Do while/until loop. const until = block.getFieldValue('MODE') === 'UNTIL'; - let argument0 = Python.valueToCode( + let argument0 = generator.valueToCode( block, 'BOOL', - until ? Python.ORDER_LOGICAL_NOT : Python.ORDER_NONE) || + until ? Order.LOGICAL_NOT : Order.NONE) || 'False'; - let branch = Python.statementToCode(block, 'DO'); - branch = Python.addLoopTrap(branch, block) || Python.PASS; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block) || generator.PASS; if (until) { argument0 = 'not ' + argument0; } return 'while ' + argument0 + ':\n' + branch; }; -Python['controls_for'] = function(block) { +export function controls_for(block, generator) { // For loop. const variable0 = - Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - let argument0 = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || '0'; - let argument1 = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || '0'; - let increment = Python.valueToCode(block, 'BY', Python.ORDER_NONE) || '1'; - let branch = Python.statementToCode(block, 'DO'); - branch = Python.addLoopTrap(branch, block) || Python.PASS; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + let argument0 = generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + let argument1 = generator.valueToCode(block, 'TO', Order.NONE) || '0'; + let increment = generator.valueToCode(block, 'BY', Order.NONE) || '1'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block) || generator.PASS; let code = ''; let range; // Helper functions. const defineUpRange = function() { - return Python.provideFunction_('upRange', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): + return generator.provideFunction_('upRange', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): while start <= stop: yield start start += abs(step) `); }; const defineDownRange = function() { - return Python.provideFunction_('downRange', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): + return generator.provideFunction_('downRange', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): while start >= stop: yield start start -= abs(step) `); }; - // Arguments are legal Python code (numbers or strings returned by scrub()). + // Arguments are legal generator code (numbers or strings returned by scrub()). const generateUpDownRange = function(start, end, inc) { return '(' + start + ' <= ' + end + ') and ' + defineUpRange() + '(' + start + ', ' + end + ', ' + inc + ') or ' + defineDownRange() + '(' + @@ -136,7 +138,7 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): arg = Number(arg); } else if (!arg.match(/^\w+$/)) { // Not a variable, it's complicated. - const varName = Python.nameDB_.getDistinctName( + const varName = generator.nameDB_.getDistinctName( variable0 + suffix, NameType.VARIABLE); code += varName + ' = ' + arg + '\n'; arg = varName; @@ -163,37 +165,38 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(start, stop, step): return code; }; -Python['controls_forEach'] = function(block) { +export function controls_forEach(block, generator) { // For each loop. const variable0 = - Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); const argument0 = - Python.valueToCode(block, 'LIST', Python.ORDER_RELATIONAL) || '[]'; - let branch = Python.statementToCode(block, 'DO'); - branch = Python.addLoopTrap(branch, block) || Python.PASS; + generator.valueToCode(block, 'LIST', Order.RELATIONAL) || '[]'; + let branch = generator.statementToCode(block, 'DO'); + branch = generator.addLoopTrap(branch, block) || generator.PASS; const code = 'for ' + variable0 + ' in ' + argument0 + ':\n' + branch; return code; }; -Python['controls_flow_statements'] = function(block) { +export function controls_flow_statements(block, generator) { // Flow statements: continue, break. let xfix = ''; - if (Python.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { // Automatic prefix insertion is switched off for this block. Add manually. - xfix += Python.injectId(Python.STATEMENT_PREFIX, block); + xfix += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (Python.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the break/continue is triggered. - xfix += Python.injectId(Python.STATEMENT_SUFFIX, block); + xfix += generator.injectId(generator.STATEMENT_SUFFIX, block); } - if (Python.STATEMENT_PREFIX) { + if (generator.STATEMENT_PREFIX) { const loop = block.getSurroundLoop(); if (loop && !loop.suppressPrefixSuffix) { // Inject loop's statement prefix here since the regular one at the end // of the loop will not get executed if 'continue' is triggered. // In the case of 'break', a prefix is needed due to the loop's suffix. - xfix += Python.injectId(Python.STATEMENT_PREFIX, loop); + xfix += generator.injectId(generator.STATEMENT_PREFIX, loop); } } switch (block.getFieldValue('FLOW')) { diff --git a/generators/python/math.js b/generators/python/math.js index 077f1acdd..da792346c 100644 --- a/generators/python/math.js +++ b/generators/python/math.js @@ -7,71 +7,72 @@ /** * @fileoverview Generating Python for math blocks. */ -'use strict'; -goog.module('Blockly.Python.math'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.math'); -const {NameType} = goog.require('Blockly.Names'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import {NameType} from '../../core/names.js'; +import {Order} from './python_generator.js'; // If any new block imports any library, add that library name here. -Python.addReservedWords('math,random,Number'); +// RESERVED WORDS: 'math,random,Number' -Python['math_number'] = function(block) { +export function math_number(block, generator) { // Numeric value. let code = Number(block.getFieldValue('NUM')); let order; if (code === Infinity) { code = 'float("inf")'; - order = Python.ORDER_FUNCTION_CALL; + order = Order.FUNCTION_CALL; } else if (code === -Infinity) { code = '-float("inf")'; - order = Python.ORDER_UNARY_SIGN; + order = Order.UNARY_SIGN; } else { - order = code < 0 ? Python.ORDER_UNARY_SIGN : Python.ORDER_ATOMIC; + order = code < 0 ? Order.UNARY_SIGN : Order.ATOMIC; } return [code, order]; }; -Python['math_arithmetic'] = function(block) { +export function math_arithmetic(block, generator) { // Basic arithmetic operators, and power. const OPERATORS = { - 'ADD': [' + ', Python.ORDER_ADDITIVE], - 'MINUS': [' - ', Python.ORDER_ADDITIVE], - 'MULTIPLY': [' * ', Python.ORDER_MULTIPLICATIVE], - 'DIVIDE': [' / ', Python.ORDER_MULTIPLICATIVE], - 'POWER': [' ** ', Python.ORDER_EXPONENTIATION], + 'ADD': [' + ', Order.ADDITIVE], + 'MINUS': [' - ', Order.ADDITIVE], + 'MULTIPLY': [' * ', Order.MULTIPLICATIVE], + 'DIVIDE': [' / ', Order.MULTIPLICATIVE], + 'POWER': [' ** ', Order.EXPONENTIATION], }; const tuple = OPERATORS[block.getFieldValue('OP')]; const operator = tuple[0]; const order = tuple[1]; - const argument0 = Python.valueToCode(block, 'A', order) || '0'; - const argument1 = Python.valueToCode(block, 'B', order) || '0'; + const argument0 = generator.valueToCode(block, 'A', order) || '0'; + const argument1 = generator.valueToCode(block, 'B', order) || '0'; const code = argument0 + operator + argument1; return [code, order]; // In case of 'DIVIDE', division between integers returns different results - // in Python 2 and 3. However, is not an issue since Blockly does not + // in generator 2 and 3. However, is not an issue since Blockly does not // guarantee identical results in all languages. To do otherwise would // require every operator to be wrapped in a function call. This would kill // legibility of the generated code. }; -Python['math_single'] = function(block) { +export function math_single(block, generator) { // Math operators with single operand. const operator = block.getFieldValue('OP'); let code; let arg; if (operator === 'NEG') { // Negation is a special case given its different operator precedence. - code = Python.valueToCode(block, 'NUM', Python.ORDER_UNARY_SIGN) || '0'; - return ['-' + code, Python.ORDER_UNARY_SIGN]; + code = generator.valueToCode(block, 'NUM', Order.UNARY_SIGN) || '0'; + return ['-' + code, Order.UNARY_SIGN]; } - Python.definitions_['import_math'] = 'import math'; + generator.definitions_['import_math'] = 'import math'; if (operator === 'SIN' || operator === 'COS' || operator === 'TAN') { - arg = Python.valueToCode(block, 'NUM', Python.ORDER_MULTIPLICATIVE) || '0'; + arg = + generator.valueToCode(block, 'NUM', Order.MULTIPLICATIVE) || '0'; } else { - arg = Python.valueToCode(block, 'NUM', Python.ORDER_NONE) || '0'; + arg = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; } // First, handle cases which generate values that don't need parentheses // wrapping the code. @@ -114,7 +115,7 @@ Python['math_single'] = function(block) { break; } if (code) { - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } // Second, handle cases which generate values that may need parentheses // wrapping the code. @@ -131,52 +132,52 @@ Python['math_single'] = function(block) { default: throw Error('Unknown math operator: ' + operator); } - return [code, Python.ORDER_MULTIPLICATIVE]; + return [code, Order.MULTIPLICATIVE]; }; -Python['math_constant'] = function(block) { +export function math_constant(block, generator) { // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. const CONSTANTS = { - 'PI': ['math.pi', Python.ORDER_MEMBER], - 'E': ['math.e', Python.ORDER_MEMBER], - 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Python.ORDER_MULTIPLICATIVE], - 'SQRT2': ['math.sqrt(2)', Python.ORDER_MEMBER], - 'SQRT1_2': ['math.sqrt(1.0 / 2)', Python.ORDER_MEMBER], - 'INFINITY': ['float(\'inf\')', Python.ORDER_ATOMIC], + 'PI': ['math.pi', Order.MEMBER], + 'E': ['math.e', Order.MEMBER], + 'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Order.MULTIPLICATIVE], + 'SQRT2': ['math.sqrt(2)', Order.MEMBER], + 'SQRT1_2': ['math.sqrt(1.0 / 2)', Order.MEMBER], + 'INFINITY': ['float(\'inf\')', Order.ATOMIC], }; const constant = block.getFieldValue('CONSTANT'); if (constant !== 'INFINITY') { - Python.definitions_['import_math'] = 'import math'; + generator.definitions_['import_math'] = 'import math'; } return CONSTANTS[constant]; }; -Python['math_number_property'] = function(block) { +export function math_number_property(block, generator) { // Check if a number is even, odd, prime, whole, positive, or negative // or if it is divisible by certain number. Returns true or false. const PROPERTIES = { - 'EVEN': [' % 2 == 0', Python.ORDER_MULTIPLICATIVE, Python.ORDER_RELATIONAL], - 'ODD': [' % 2 == 1', Python.ORDER_MULTIPLICATIVE, Python.ORDER_RELATIONAL], - 'WHOLE': [' % 1 == 0', Python.ORDER_MULTIPLICATIVE, - Python.ORDER_RELATIONAL], - 'POSITIVE': [' > 0', Python.ORDER_RELATIONAL, Python.ORDER_RELATIONAL], - 'NEGATIVE': [' < 0', Python.ORDER_RELATIONAL, Python.ORDER_RELATIONAL], - 'DIVISIBLE_BY': [null, Python.ORDER_MULTIPLICATIVE, - Python.ORDER_RELATIONAL], - 'PRIME': [null, Python.ORDER_NONE, Python.ORDER_FUNCTION_CALL], + 'EVEN': [' % 2 == 0', Order.MULTIPLICATIVE, Order.RELATIONAL], + 'ODD': [' % 2 == 1', Order.MULTIPLICATIVE, Order.RELATIONAL], + 'WHOLE': [' % 1 == 0', Order.MULTIPLICATIVE, + Order.RELATIONAL], + 'POSITIVE': [' > 0', Order.RELATIONAL, Order.RELATIONAL], + 'NEGATIVE': [' < 0', Order.RELATIONAL, Order.RELATIONAL], + 'DIVISIBLE_BY': [null, Order.MULTIPLICATIVE, + Order.RELATIONAL], + 'PRIME': [null, Order.NONE, Order.FUNCTION_CALL], } const dropdownProperty = block.getFieldValue('PROPERTY'); const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty]; - const numberToCheck = Python.valueToCode(block, 'NUMBER_TO_CHECK', + const numberToCheck = generator.valueToCode(block, 'NUMBER_TO_CHECK', inputOrder) || '0'; let code; if (dropdownProperty === 'PRIME') { // Prime is a special case as it is not a one-liner test. - Python.definitions_['import_math'] = 'import math'; - Python.definitions_['from_numbers_import_Number'] = + generator.definitions_['import_math'] = 'import math'; + generator.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; - const functionName = Python.provideFunction_('math_isPrime', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(n): + const functionName = generator.provideFunction_('math_isPrime', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(n): # https://en.wikipedia.org/wiki/Primality_test#Naive_methods # If n is not a number but a string, try parsing it. if not isinstance(n, Number): @@ -197,11 +198,11 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(n): `); code = functionName + '(' + numberToCheck + ')'; } else if (dropdownProperty === 'DIVISIBLE_BY') { - const divisor = Python.valueToCode(block, 'DIVISOR', - Python.ORDER_MULTIPLICATIVE) || '0'; - // If 'divisor' is some code that evals to 0, Python will raise an error. + const divisor = generator.valueToCode(block, 'DIVISOR', + Order.MULTIPLICATIVE) || '0'; + // If 'divisor' is some code that evals to 0, generator will raise an error. if (divisor === '0') { - return ['False', Python.ORDER_ATOMIC]; + return ['False', Order.ATOMIC]; } code = numberToCheck + ' % ' + divisor + ' == 0'; } else { @@ -210,27 +211,28 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(n): return [code, outputOrder]; }; -Python['math_change'] = function(block) { +export function math_change(block, generator) { // Add to a variable in place. - Python.definitions_['from_numbers_import_Number'] = + generator.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; const argument0 = - Python.valueToCode(block, 'DELTA', Python.ORDER_ADDITIVE) || '0'; + generator.valueToCode(block, 'DELTA', Order.ADDITIVE) || '0'; const varName = - Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = (' + varName + ' if isinstance(' + varName + ', Number) else 0) + ' + argument0 + '\n'; }; // Rounding functions have a single operand. -Python['math_round'] = Python['math_single']; +export const math_round = math_single; // Trigonometry functions have a single operand. -Python['math_trig'] = Python['math_single']; +export const math_trig = math_single; -Python['math_on_list'] = function(block) { +export function math_on_list(block, generator) { // Math functions for lists. const func = block.getFieldValue('OP'); - const list = Python.valueToCode(block, 'LIST', Python.ORDER_NONE) || '[]'; + const list = generator.valueToCode(block, 'LIST', Order.NONE) || '[]'; let code; switch (func) { case 'SUM': @@ -243,12 +245,12 @@ Python['math_on_list'] = function(block) { code = 'max(' + list + ')'; break; case 'AVERAGE': { - Python.definitions_['from_numbers_import_Number'] = + generator.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; // This operation excludes null and values that aren't int or float: // math_mean([null, null, "aString", 1, 9]) -> 5.0 - const functionName = Python.provideFunction_('math_mean', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): + const functionName = generator.provideFunction_('math_mean', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): localList = [e for e in myList if isinstance(e, Number)] if not localList: return return float(sum(localList)) / len(localList) @@ -257,12 +259,12 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): break; } case 'MEDIAN': { - Python.definitions_['from_numbers_import_Number'] = + generator.definitions_['from_numbers_import_Number'] = 'from numbers import Number'; // This operation excludes null values: // math_median([null, null, 1, 3]) -> 2.0 - const functionName = Python.provideFunction_( 'math_median', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): + const functionName = generator.provideFunction_( 'math_median', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(myList): localList = sorted([e for e in myList if isinstance(e, Number)]) if not localList: return if len(localList) % 2 == 0: @@ -277,8 +279,8 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList): // As a list of numbers can contain more than one mode, // the returned result is provided as an array. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1] - const functionName = Python.provideFunction_('math_modes', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(some_list): + const functionName = generator.provideFunction_('math_modes', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(some_list): modes = [] # Using a lists of [item, count] to keep count rather than dict # to avoid "unhashable" errors when the counted item is itself a list or dict. @@ -302,9 +304,10 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(some_list): break; } case 'STD_DEV': { - Python.definitions_['import_math'] = 'import math'; - const functionName = Python.provideFunction_('math_standard_deviation', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(numbers): + generator.definitions_['import_math'] = 'import math'; + const functionName = + generator.provideFunction_('math_standard_deviation', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(numbers): n = len(numbers) if n == 0: return mean = float(sum(numbers)) / n @@ -315,59 +318,65 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(numbers): break; } case 'RANDOM': - Python.definitions_['import_random'] = 'import random'; + generator.definitions_['import_random'] = 'import random'; code = 'random.choice(' + list + ')'; break; default: throw Error('Unknown operator: ' + func); } - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['math_modulo'] = function(block) { +export function math_modulo(block, generator) { // Remainder computation. const argument0 = - Python.valueToCode(block, 'DIVIDEND', Python.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'DIVIDEND', Order.MULTIPLICATIVE) || + '0'; const argument1 = - Python.valueToCode(block, 'DIVISOR', Python.ORDER_MULTIPLICATIVE) || '0'; + generator.valueToCode(block, 'DIVISOR', Order.MULTIPLICATIVE) || + '0'; const code = argument0 + ' % ' + argument1; - return [code, Python.ORDER_MULTIPLICATIVE]; + return [code, Order.MULTIPLICATIVE]; }; -Python['math_constrain'] = function(block) { +export function math_constrain(block, generator) { // Constrain a number between two limits. const argument0 = - Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '0'; - const argument1 = Python.valueToCode(block, 'LOW', Python.ORDER_NONE) || '0'; + generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + const argument1 = + generator.valueToCode(block, 'LOW', Order.NONE) || '0'; const argument2 = - Python.valueToCode(block, 'HIGH', Python.ORDER_NONE) || 'float(\'inf\')'; + generator.valueToCode(block, 'HIGH', Order.NONE) || + 'float(\'inf\')'; const code = 'min(max(' + argument0 + ', ' + argument1 + '), ' + argument2 + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['math_random_int'] = function(block) { +export function math_random_int(block, generator) { // Random integer between [X] and [Y]. - Python.definitions_['import_random'] = 'import random'; - const argument0 = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || '0'; - const argument1 = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || '0'; + generator.definitions_['import_random'] = 'import random'; + const argument0 = + generator.valueToCode(block, 'FROM', Order.NONE) || '0'; + const argument1 = + generator.valueToCode(block, 'TO', Order.NONE) || '0'; const code = 'random.randint(' + argument0 + ', ' + argument1 + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['math_random_float'] = function(block) { +export function math_random_float(block, generator) { // Random fraction between 0 and 1. - Python.definitions_['import_random'] = 'import random'; - return ['random.random()', Python.ORDER_FUNCTION_CALL]; + generator.definitions_['import_random'] = 'import random'; + return ['random.random()', Order.FUNCTION_CALL]; }; -Python['math_atan2'] = function(block) { +export function math_atan2(block, generator) { // Arctangent of point (X, Y) in degrees from -180 to 180. - Python.definitions_['import_math'] = 'import math'; - const argument0 = Python.valueToCode(block, 'X', Python.ORDER_NONE) || '0'; - const argument1 = Python.valueToCode(block, 'Y', Python.ORDER_NONE) || '0'; + generator.definitions_['import_math'] = 'import math'; + const argument0 = generator.valueToCode(block, 'X', Order.NONE) || '0'; + const argument1 = generator.valueToCode(block, 'Y', Order.NONE) || '0'; return [ 'math.atan2(' + argument1 + ', ' + argument0 + ') / math.pi * 180', - Python.ORDER_MULTIPLICATIVE + Order.MULTIPLICATIVE ]; }; diff --git a/generators/python/procedures.js b/generators/python/procedures.js index d3737566f..65e17ddc3 100644 --- a/generators/python/procedures.js +++ b/generators/python/procedures.js @@ -7,16 +7,16 @@ /** * @fileoverview Generating Python for procedure blocks. */ -'use strict'; -goog.module('Blockly.Python.procedures'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.procedures'); -const Variables = goog.require('Blockly.Variables'); -const {NameType} = goog.require('Blockly.Names'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import * as Variables from '../../core/variables.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './python_generator.js'; -Python['procedures_defreturn'] = function(block) { +export function procedures_defreturn(block, generator) { // Define a procedure with a return value. // First, add a 'global' statement for every variable that is not shadowed by // a local parameter. @@ -26,104 +26,110 @@ Python['procedures_defreturn'] = function(block) { for (let i = 0, variable; (variable = usedVariables[i]); i++) { const varName = variable.name; if (block.getVars().indexOf(varName) === -1) { - globals.push(Python.nameDB_.getName(varName, NameType.VARIABLE)); + globals.push(generator.nameDB_.getName(varName, NameType.VARIABLE)); } } // Add developer variables. const devVarList = Variables.allDeveloperVariables(workspace); for (let i = 0; i < devVarList.length; i++) { globals.push( - Python.nameDB_.getName(devVarList[i], NameType.DEVELOPER_VARIABLE)); + generator.nameDB_.getName( + devVarList[i], NameType.DEVELOPER_VARIABLE)); } const globalString = globals.length ? - Python.INDENT + 'global ' + globals.join(', ') + '\n' : + generator.INDENT + 'global ' + globals.join(', ') + '\n' : ''; const funcName = - Python.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); let xfix1 = ''; - if (Python.STATEMENT_PREFIX) { - xfix1 += Python.injectId(Python.STATEMENT_PREFIX, block); + if (generator.STATEMENT_PREFIX) { + xfix1 += generator.injectId(generator.STATEMENT_PREFIX, block); } - if (Python.STATEMENT_SUFFIX) { - xfix1 += Python.injectId(Python.STATEMENT_SUFFIX, block); + if (generator.STATEMENT_SUFFIX) { + xfix1 += generator.injectId(generator.STATEMENT_SUFFIX, block); } if (xfix1) { - xfix1 = Python.prefixLines(xfix1, Python.INDENT); + xfix1 = generator.prefixLines(xfix1, generator.INDENT); } let loopTrap = ''; - if (Python.INFINITE_LOOP_TRAP) { - loopTrap = Python.prefixLines( - Python.injectId(Python.INFINITE_LOOP_TRAP, block), Python.INDENT); + if (generator.INFINITE_LOOP_TRAP) { + loopTrap = generator.prefixLines( + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT); } - let branch = Python.statementToCode(block, 'STACK'); + let branch = generator.statementToCode(block, 'STACK'); let returnValue = - Python.valueToCode(block, 'RETURN', Python.ORDER_NONE) || ''; + generator.valueToCode(block, 'RETURN', Order.NONE) || ''; let xfix2 = ''; if (branch && returnValue) { // After executing the function body, revisit this block for the return. xfix2 = xfix1; } if (returnValue) { - returnValue = Python.INDENT + 'return ' + returnValue + '\n'; + returnValue = generator.INDENT + 'return ' + returnValue + '\n'; } else if (!branch) { - branch = Python.PASS; + branch = generator.PASS; } const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = Python.nameDB_.getName(variables[i], NameType.VARIABLE); + args[i] = generator.nameDB_.getName(variables[i], NameType.VARIABLE); } let code = 'def ' + funcName + '(' + args.join(', ') + '):\n' + globalString + xfix1 + loopTrap + branch + xfix2 + returnValue; - code = Python.scrub_(block, code); + code = generator.scrub_(block, code); // Add % so as not to collide with helper functions in definitions list. - Python.definitions_['%' + funcName] = code; + generator.definitions_['%' + funcName] = code; return null; }; // Defining a procedure without a return value uses the same generator as // a procedure with a return value. -Python['procedures_defnoreturn'] = Python['procedures_defreturn']; +export const procedures_defnoreturn = procedures_defreturn; -Python['procedures_callreturn'] = function(block) { +export function procedures_callreturn(block, generator) { // Call a procedure with a return value. const funcName = - Python.nameDB_.getName(block.getFieldValue('NAME'), NameType.PROCEDURE); + generator.nameDB_.getName( + block.getFieldValue('NAME'), NameType.PROCEDURE); const args = []; const variables = block.getVars(); for (let i = 0; i < variables.length; i++) { - args[i] = Python.valueToCode(block, 'ARG' + i, Python.ORDER_NONE) || 'None'; + args[i] = + generator.valueToCode(block, 'ARG' + i, Order.NONE) || 'None'; } const code = funcName + '(' + args.join(', ') + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['procedures_callnoreturn'] = function(block) { +export function procedures_callnoreturn(block, generator) { // Call a procedure with no return value. // Generated code is for a function call as a statement is the same as a // function call as a value, with the addition of line ending. - const tuple = Python['procedures_callreturn'](block); + const tuple = generator.forBlock['procedures_callreturn'](block, generator); return tuple[0] + '\n'; }; -Python['procedures_ifreturn'] = function(block) { +export function procedures_ifreturn(block, generator) { // Conditionally return value from a procedure. const condition = - Python.valueToCode(block, 'CONDITION', Python.ORDER_NONE) || 'False'; + generator.valueToCode(block, 'CONDITION', Order.NONE) || 'False'; let code = 'if ' + condition + ':\n'; - if (Python.STATEMENT_SUFFIX) { + if (generator.STATEMENT_SUFFIX) { // Inject any statement suffix here since the regular one at the end // will not get executed if the return is triggered. - code += Python.prefixLines( - Python.injectId(Python.STATEMENT_SUFFIX, block), Python.INDENT); + code += generator.prefixLines( + generator.injectId( + generator.STATEMENT_SUFFIX, block), generator.INDENT); } if (block.hasReturnValue_) { const value = - Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || 'None'; - code += Python.INDENT + 'return ' + value + '\n'; + generator.valueToCode(block, 'VALUE', Order.NONE) || 'None'; + code += generator.INDENT + 'return ' + value + '\n'; } else { - code += Python.INDENT + 'return\n'; + code += generator.INDENT + 'return\n'; } return code; }; diff --git a/generators/python/python_generator.js b/generators/python/python_generator.js new file mode 100644 index 000000000..5897b4366 --- /dev/null +++ b/generators/python/python_generator.js @@ -0,0 +1,340 @@ +/** + * @license + * Copyright 2012 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper functions for generating Python for blocks. + * @suppress {checkTypes|globalThis} + */ + +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python'); + +import * as stringUtils from '../../core/utils/string.js'; +import * as Variables from '../../core/variables.js'; +// import type {Block} from '../../core/block.js'; +import {CodeGenerator} from '../../core/generator.js'; +import {Names, NameType} from '../../core/names.js'; +// import type {Workspace} from '../../core/workspace.js'; +import {inputTypes} from '../../core/inputs/input_types.js'; + + +/** + * Order of operation ENUMs. + * http://docs.python.org/reference/expressions.html#summary + * @enum {number} + */ +export const Order = { + ATOMIC: 0, // 0 "" ... + COLLECTION: 1, // tuples, lists, dictionaries + STRING_CONVERSION: 1, // `expression...` + MEMBER: 2.1, // . [] + FUNCTION_CALL: 2.2, // () + EXPONENTIATION: 3, // ** + UNARY_SIGN: 4, // + - + BITWISE_NOT: 4, // ~ + MULTIPLICATIVE: 5, // * / // % + ADDITIVE: 6, // + - + BITWISE_SHIFT: 7, // << >> + BITWISE_AND: 8, // & + BITWISE_XOR: 9, // ^ + BITWISE_OR: 10, // | + RELATIONAL: 11, // in, not in, is, is not, >, >=, <>, !=, == + LOGICAL_NOT: 12, // not + LOGICAL_AND: 13, // and + LOGICAL_OR: 14, // or + CONDITIONAL: 15, // if else + LAMBDA: 16, // lambda + NONE: 99, // (...) +}; + +/** + * PythonScript code generator class. + */ +export class PythonGenerator extends CodeGenerator { + /** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array>} + */ + ORDER_OVERRIDES = [ + // (foo()).bar -> foo().bar + // (foo())[0] -> foo()[0] + [Order.FUNCTION_CALL, Order.MEMBER], + // (foo())() -> foo()() + [Order.FUNCTION_CALL, Order.FUNCTION_CALL], + // (foo.bar).baz -> foo.bar.baz + // (foo.bar)[0] -> foo.bar[0] + // (foo[0]).bar -> foo[0].bar + // (foo[0])[1] -> foo[0][1] + [Order.MEMBER, Order.MEMBER], + // (foo.bar)() -> foo.bar() + // (foo[0])() -> foo[0]() + [Order.MEMBER, Order.FUNCTION_CALL], + + // not (not foo) -> not not foo + [Order.LOGICAL_NOT, Order.LOGICAL_NOT], + // a and (b and c) -> a and b and c + [Order.LOGICAL_AND, Order.LOGICAL_AND], + // a or (b or c) -> a or b or c + [Order.LOGICAL_OR, Order.LOGICAL_OR] + ]; + + constructor(name) { + super(name ?? 'Python'); + this.isInitialized = false; + + // Copy Order values onto instance for backwards compatibility + // while ensuring they are not part of the publically-advertised + // API. + // + // TODO(#7085): deprecate these in due course. (Could initially + // replace data properties with get accessors that call + // deprecate.warn().) + for (const key in Order) { + this['ORDER_' + key] = Order[key]; + } + + // List of illegal variable names. This is not intended to be a + // security feature. Blockly is 100% client-side, so bypassing + // this list is trivial. This is intended to prevent users from + // accidentally clobbering a built-in object or function. + this.addReservedWords( + // import keyword + // print(','.join(sorted(keyword.kwlist))) + // https://docs.python.org/3/reference/lexical_analysis.html#keywords + // https://docs.python.org/2/reference/lexical_analysis.html#keywords + 'False,None,True,and,as,assert,break,class,continue,def,del,elif,else,' + + 'except,exec,finally,for,from,global,if,import,in,is,lambda,nonlocal,' + + 'not,or,pass,print,raise,return,try,while,with,yield,' + + // https://docs.python.org/3/library/constants.html + // https://docs.python.org/2/library/constants.html + 'NotImplemented,Ellipsis,__debug__,quit,exit,copyright,license,credits,' + + // >>> print(','.join(sorted(dir(__builtins__)))) + // https://docs.python.org/3/library/functions.html + // https://docs.python.org/2/library/functions.html + 'ArithmeticError,AssertionError,AttributeError,BaseException,' + + 'BlockingIOError,BrokenPipeError,BufferError,BytesWarning,' + + 'ChildProcessError,ConnectionAbortedError,ConnectionError,' + + 'ConnectionRefusedError,ConnectionResetError,DeprecationWarning,' + + 'EOFError,Ellipsis,EnvironmentError,Exception,FileExistsError,' + + 'FileNotFoundError,FloatingPointError,FutureWarning,GeneratorExit,' + + 'IOError,ImportError,ImportWarning,IndentationError,IndexError,' + + 'InterruptedError,IsADirectoryError,KeyError,KeyboardInterrupt,' + + 'LookupError,MemoryError,ModuleNotFoundError,NameError,' + + 'NotADirectoryError,NotImplemented,NotImplementedError,OSError,' + + 'OverflowError,PendingDeprecationWarning,PermissionError,' + + 'ProcessLookupError,RecursionError,ReferenceError,ResourceWarning,' + + 'RuntimeError,RuntimeWarning,StandardError,StopAsyncIteration,' + + 'StopIteration,SyntaxError,SyntaxWarning,SystemError,SystemExit,' + + 'TabError,TimeoutError,TypeError,UnboundLocalError,UnicodeDecodeError,' + + 'UnicodeEncodeError,UnicodeError,UnicodeTranslateError,UnicodeWarning,' + + 'UserWarning,ValueError,Warning,ZeroDivisionError,_,__build_class__,' + + '__debug__,__doc__,__import__,__loader__,__name__,__package__,__spec__,' + + 'abs,all,any,apply,ascii,basestring,bin,bool,buffer,bytearray,bytes,' + + 'callable,chr,classmethod,cmp,coerce,compile,complex,copyright,credits,' + + 'delattr,dict,dir,divmod,enumerate,eval,exec,execfile,exit,file,filter,' + + 'float,format,frozenset,getattr,globals,hasattr,hash,help,hex,id,input,' + + 'int,intern,isinstance,issubclass,iter,len,license,list,locals,long,' + + 'map,max,memoryview,min,next,object,oct,open,ord,pow,print,property,' + + 'quit,range,raw_input,reduce,reload,repr,reversed,round,set,setattr,' + + 'slice,sorted,staticmethod,str,sum,super,tuple,type,unichr,unicode,' + + 'vars,xrange,zip' + ); + } + + /** + * Initialise the database of variable names. + * @param {!Workspace} workspace Workspace to generate code from. + * @this {CodeGenerator} + */ + init(workspace) { + super.init(workspace); + + /** + * Empty loops or conditionals are not allowed in Python. + */ + this.PASS = this.INDENT + 'pass\n'; + + if (!this.nameDB_) { + this.nameDB_ = new Names(this.RESERVED_WORDS_); + } else { + this.nameDB_.reset(); + } + + this.nameDB_.setVariableMap(workspace.getVariableMap()); + this.nameDB_.populateVariables(workspace); + this.nameDB_.populateProcedures(workspace); + + const defvars = []; + // Add developer variables (not created or named by the user). + const devVarList = Variables.allDeveloperVariables(workspace); + for (let i = 0; i < devVarList.length; i++) { + defvars.push( + this.nameDB_.getName(devVarList[i], Names.DEVELOPER_VARIABLE_TYPE) + + ' = None'); + } + + // Add user variables, but only ones that are being used. + const variables = Variables.allUsedVarModels(workspace); + for (let i = 0; i < variables.length; i++) { + defvars.push( + this.nameDB_.getName(variables[i].getId(), NameType.VARIABLE) + + ' = None'); + } + + this.definitions_['variables'] = defvars.join('\n'); + this.isInitialized = true; + } + + /** + * Prepend the generated code with import statements and variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ + finish(code) { + // Convert the definitions dictionary into a list. + const imports = []; + const definitions = []; + for (let name in this.definitions_) { + const def = this.definitions_[name]; + if (def.match(/^(from\s+\S+\s+)?import\s+\S+/)) { + imports.push(def); + } else { + definitions.push(def); + } + } + // Call Blockly.CodeGenerator's finish. + code = super.finish(code); + this.isInitialized = false; + + this.nameDB_.reset(); + const allDefs = imports.join('\n') + '\n\n' + definitions.join('\n\n'); + return allDefs.replace(/\n\n+/g, '\n\n').replace(/\n*$/, '\n\n\n') + code; + } + + /** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ + scrubNakedValue(line) { + return line + '\n'; + } + + /** + * Encode a string as a properly escaped Python string, complete with quotes. + * @param {string} string Text to encode. + * @return {string} Python string. + * @protected + */ + quote_(string) { + string = string.replace(/\\/g, '\\\\').replace(/\n/g, '\\\n'); + + // Follow the CPython behaviour of repr() for a non-byte string. + let quote = '\''; + if (string.indexOf('\'') !== -1) { + if (string.indexOf('"') === -1) { + quote = '"'; + } else { + string = string.replace(/'/g, '\\\''); + } + } + return quote + string + quote; + } + + /** + * Encode a string as a properly escaped multiline Python string, complete + * with quotes. + * @param {string} string Text to encode. + * @return {string} Python string. + * @protected + */ + multiline_quote_(string) { + const lines = string.split(/\n/g).map(this.quote_); + // Join with the following, plus a newline: + // + '\n' + + return lines.join(' + \'\\n\' + \n'); + } + + /** + * Common tasks for generating Python from blocks. + * Handles comments for the specified block and any connected value blocks. + * Calls any statements following this block. + * @param {!Block} block The current block. + * @param {string} code The Python code created for this block. + * @param {boolean=} opt_thisOnly True to generate code for only this statement. + * @return {string} Python code with comments and subsequent blocks added. + * @protected + */ + scrub_(block, code, opt_thisOnly) { + let commentCode = ''; + // Only collect comments for blocks that aren't inline. + if (!block.outputConnection || !block.outputConnection.targetConnection) { + // Collect comment for this block. + let comment = block.getCommentText(); + if (comment) { + comment = stringUtils.wrap(comment, this.COMMENT_WRAP - 3); + commentCode += this.prefixLines(comment + '\n', '# '); + } + // Collect comments for all value arguments. + // Don't collect comments for nested statements. + for (let i = 0; i < block.inputList.length; i++) { + if (block.inputList[i].type === inputTypes.VALUE) { + const childBlock = block.inputList[i].connection.targetBlock(); + if (childBlock) { + comment = this.allNestedComments(childBlock); + if (comment) { + commentCode += this.prefixLines(comment, '# '); + } + } + } + } + } + const nextBlock = block.nextConnection && block.nextConnection.targetBlock(); + const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock); + return commentCode + code + nextCode; + } + + /** + * Gets a property and adjusts the value, taking into account indexing. + * If a static int, casts to an integer, otherwise returns a code string. + * @param {!Block} block The block. + * @param {string} atId The property ID of the element to get. + * @param {number=} opt_delta Value to add. + * @param {boolean=} opt_negate Whether to negate the value. + * @return {string|number} + */ + getAdjustedInt(block, atId, opt_delta, opt_negate) { + let delta = opt_delta || 0; + if (block.workspace.options.oneBasedIndex) { + delta--; + } + const defaultAtIndex = block.workspace.options.oneBasedIndex ? '1' : '0'; + const atOrder = delta ? this.ORDER_ADDITIVE : this.ORDER_NONE; + let at = this.valueToCode(block, atId, atOrder) || defaultAtIndex; + + if (stringUtils.isNumber(at)) { + // If the index is a naked number, adjust it right now. + at = parseInt(at, 10) + delta; + if (opt_negate) { + at = -at; + } + } else { + // If the index is dynamic, adjust it in code. + if (delta > 0) { + at = 'int(' + at + ' + ' + delta + ')'; + } else if (delta < 0) { + at = 'int(' + at + ' - ' + -delta + ')'; + } else { + at = 'int(' + at + ')'; + } + if (opt_negate) { + at = '-' + at; + } + } + return at; + } +} diff --git a/generators/python/text.js b/generators/python/text.js index 3a378091c..84fef5515 100644 --- a/generators/python/text.js +++ b/generators/python/text.js @@ -7,26 +7,26 @@ /** * @fileoverview Generating Python for text blocks. */ -'use strict'; -goog.module('Blockly.Python.texts'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.texts'); -const stringUtils = goog.require('Blockly.utils.string'); -const {NameType} = goog.require('Blockly.Names'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import * as stringUtils from '../../core/utils/string.js'; +import {NameType} from '../../core/names.js'; +import {Order} from './python_generator.js'; -Python['text'] = function(block) { +export function text(block, generator) { // Text value. - const code = Python.quote_(block.getFieldValue('TEXT')); - return [code, Python.ORDER_ATOMIC]; + const code = generator.quote_(block.getFieldValue('TEXT')); + return [code, Order.ATOMIC]; }; -Python['text_multiline'] = function(block) { +export function text_multiline(block, generator) { // Text value. - const code = Python.multiline_quote_(block.getFieldValue('TEXT')); + const code = generator.multiline_quote_(block.getFieldValue('TEXT')); const order = - code.indexOf('+') !== -1 ? Python.ORDER_ADDITIVE : Python.ORDER_ATOMIC; + code.indexOf('+') !== -1 ? Order.ADDITIVE : Order.ATOMIC; return [code, order]; }; @@ -45,137 +45,140 @@ const strRegExp = /^\s*'([^']|\\')*'\s*$/; */ const forceString = function(value) { if (strRegExp.test(value)) { - return [value, Python.ORDER_ATOMIC]; + return [value, Order.ATOMIC]; } - return ['str(' + value + ')', Python.ORDER_FUNCTION_CALL]; + return ['str(' + value + ')', Order.FUNCTION_CALL]; }; -Python['text_join'] = function(block) { +export function text_join(block, generator) { // Create a string made up of any number of elements of any type. // Should we allow joining by '-' or ',' or any other characters? switch (block.itemCount_) { case 0: - return ["''", Python.ORDER_ATOMIC]; + return ["''", Order.ATOMIC]; case 1: { const element = - Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || "''"; + generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; const codeAndOrder = forceString(element); return codeAndOrder; } case 2: { const element0 = - Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || "''"; + generator.valueToCode(block, 'ADD0', Order.NONE) || "''"; const element1 = - Python.valueToCode(block, 'ADD1', Python.ORDER_NONE) || "''"; + generator.valueToCode(block, 'ADD1', Order.NONE) || "''"; const code = forceString(element0)[0] + ' + ' + forceString(element1)[0]; - return [code, Python.ORDER_ADDITIVE]; + return [code, Order.ADDITIVE]; } default: { const elements = []; for (let i = 0; i < block.itemCount_; i++) { elements[i] = - Python.valueToCode(block, 'ADD' + i, Python.ORDER_NONE) || "''"; + generator.valueToCode(block, 'ADD' + i, Order.NONE) || "''"; } - const tempVar = Python.nameDB_.getDistinctName('x', NameType.VARIABLE); + const tempVar = + generator.nameDB_.getDistinctName('x', NameType.VARIABLE); const code = '\'\'.join([str(' + tempVar + ') for ' + tempVar + ' in [' + elements.join(', ') + ']])'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } } }; -Python['text_append'] = function(block) { +export function text_append(block, generator) { // Append to a variable in place. const varName = - Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - const value = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''"; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + const value = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return varName + ' = str(' + varName + ') + ' + forceString(value)[0] + '\n'; }; -Python['text_length'] = function(block) { +export function text_length(block, generator) { // Is the string null or array empty? - const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''"; - return ['len(' + text + ')', Python.ORDER_FUNCTION_CALL]; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; + return ['len(' + text + ')', Order.FUNCTION_CALL]; }; -Python['text_isEmpty'] = function(block) { +export function text_isEmpty(block, generator) { // Is the string null or array empty? - const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''"; + const text = generator.valueToCode(block, 'VALUE', Order.NONE) || "''"; const code = 'not len(' + text + ')'; - return [code, Python.ORDER_LOGICAL_NOT]; + return [code, Order.LOGICAL_NOT]; }; -Python['text_indexOf'] = function(block) { +export function text_indexOf(block, generator) { // Search the text for a substring. // Should we allow for non-case sensitive??? const operator = block.getFieldValue('END') === 'FIRST' ? 'find' : 'rfind'; const substring = - Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || "''"; + generator.valueToCode(block, 'FIND', Order.NONE) || "''"; const text = - Python.valueToCode(block, 'VALUE', Python.ORDER_MEMBER) || "''"; + generator.valueToCode(block, 'VALUE', Order.MEMBER) || "''"; const code = text + '.' + operator + '(' + substring + ')'; if (block.workspace.options.oneBasedIndex) { - return [code + ' + 1', Python.ORDER_ADDITIVE]; + return [code + ' + 1', Order.ADDITIVE]; } - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['text_charAt'] = function(block) { +export function text_charAt(block, generator) { // Get letter at index. // Note: Until January 2013 this block did not have the WHERE input. const where = block.getFieldValue('WHERE') || 'FROM_START'; const textOrder = - (where === 'RANDOM') ? Python.ORDER_NONE : Python.ORDER_MEMBER; - const text = Python.valueToCode(block, 'VALUE', textOrder) || "''"; + (where === 'RANDOM') ? Order.NONE : Order.MEMBER; + const text = generator.valueToCode(block, 'VALUE', textOrder) || "''"; switch (where) { case 'FIRST': { const code = text + '[0]'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } case 'LAST': { const code = text + '[-1]'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } case 'FROM_START': { - const at = Python.getAdjustedInt(block, 'AT'); + const at = generator.getAdjustedInt(block, 'AT'); const code = text + '[' + at + ']'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } case 'FROM_END': { - const at = Python.getAdjustedInt(block, 'AT', 1, true); + const at = generator.getAdjustedInt(block, 'AT', 1, true); const code = text + '[' + at + ']'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; } case 'RANDOM': { - Python.definitions_['import_random'] = 'import random'; - const functionName = Python.provideFunction_('text_random_letter', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(text): + generator.definitions_['import_random'] = 'import random'; + const functionName = + generator.provideFunction_('text_random_letter', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(text): x = int(random.random() * len(text)) return text[x] `); const code = functionName + '(' + text + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; } } throw Error('Unhandled option (text_charAt).'); }; -Python['text_getSubstring'] = function(block) { +export function text_getSubstring(block, generator) { // Get substring. const where1 = block.getFieldValue('WHERE1'); const where2 = block.getFieldValue('WHERE2'); const text = - Python.valueToCode(block, 'STRING', Python.ORDER_MEMBER) || "''"; + generator.valueToCode(block, 'STRING', Order.MEMBER) || "''"; let at1; switch (where1) { case 'FROM_START': - at1 = Python.getAdjustedInt(block, 'AT1'); + at1 = generator.getAdjustedInt(block, 'AT1'); if (at1 === 0) { at1 = ''; } break; case 'FROM_END': - at1 = Python.getAdjustedInt(block, 'AT1', 1, true); + at1 = generator.getAdjustedInt(block, 'AT1', 1, true); break; case 'FIRST': at1 = ''; @@ -187,14 +190,14 @@ Python['text_getSubstring'] = function(block) { let at2; switch (where2) { case 'FROM_START': - at2 = Python.getAdjustedInt(block, 'AT2', 1); + at2 = generator.getAdjustedInt(block, 'AT2', 1); break; case 'FROM_END': - at2 = Python.getAdjustedInt(block, 'AT2', 0, true); + at2 = generator.getAdjustedInt(block, 'AT2', 0, true); // Ensure that if the result calculated is 0 that sub-sequence will // include all elements as expected. if (!stringUtils.isNumber(String(at2))) { - Python.definitions_['import_sys'] = 'import sys'; + generator.definitions_['import_sys'] = 'import sys'; at2 += ' or sys.maxsize'; } else if (at2 === 0) { at2 = ''; @@ -207,10 +210,10 @@ Python['text_getSubstring'] = function(block) { throw Error('Unhandled option (text_getSubstring)'); } const code = text + '[' + at1 + ' : ' + at2 + ']'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; }; -Python['text_changeCase'] = function(block) { +export function text_changeCase(block, generator) { // Change capitalization. const OPERATORS = { 'UPPERCASE': '.upper()', @@ -218,12 +221,12 @@ Python['text_changeCase'] = function(block) { 'TITLECASE': '.title()' }; const operator = OPERATORS[block.getFieldValue('CASE')]; - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; + const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + operator; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['text_trim'] = function(block) { +export function text_trim(block, generator) { // Trim spaces. const OPERATORS = { 'LEFT': '.lstrip()', @@ -231,21 +234,21 @@ Python['text_trim'] = function(block) { 'BOTH': '.strip()' }; const operator = OPERATORS[block.getFieldValue('MODE')]; - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; + const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + operator; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['text_print'] = function(block) { +export function text_print(block, generator) { // Print statement. - const msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''"; + const msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; return 'print(' + msg + ')\n'; }; -Python['text_prompt_ext'] = function(block) { +export function text_prompt_ext(block, generator) { // Prompt function. - const functionName = Python.provideFunction_('text_prompt', ` -def ${Python.FUNCTION_NAME_PLACEHOLDER_}(msg): + const functionName = generator.provideFunction_('text_prompt', ` +def ${generator.FUNCTION_NAME_PLACEHOLDER_}(msg): try: return raw_input(msg) except NameError: @@ -254,38 +257,38 @@ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(msg): let msg; if (block.getField('TEXT')) { // Internal message. - msg = Python.quote_(block.getFieldValue('TEXT')); + msg = generator.quote_(block.getFieldValue('TEXT')); } else { // External message. - msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''"; + msg = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; } let code = functionName + '(' + msg + ')'; const toNumber = block.getFieldValue('TYPE') === 'NUMBER'; if (toNumber) { code = 'float(' + code + ')'; } - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['text_prompt'] = Python['text_prompt_ext']; +export const text_prompt = text_prompt_ext; -Python['text_count'] = function(block) { - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; - const sub = Python.valueToCode(block, 'SUB', Python.ORDER_NONE) || "''"; +export function text_count(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; + const sub = generator.valueToCode(block, 'SUB', Order.NONE) || "''"; const code = text + '.count(' + sub + ')'; - return [code, Python.ORDER_FUNCTION_CALL]; + return [code, Order.FUNCTION_CALL]; }; -Python['text_replace'] = function(block) { - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; - const from = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || "''"; - const to = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || "''"; +export function text_replace(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; + const from = generator.valueToCode(block, 'FROM', Order.NONE) || "''"; + const to = generator.valueToCode(block, 'TO', Order.NONE) || "''"; const code = text + '.replace(' + from + ', ' + to + ')'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; }; -Python['text_reverse'] = function(block) { - const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''"; +export function text_reverse(block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.MEMBER) || "''"; const code = text + '[::-1]'; - return [code, Python.ORDER_MEMBER]; + return [code, Order.MEMBER]; }; diff --git a/generators/python/variables.js b/generators/python/variables.js index 7abce6b42..5228bb892 100644 --- a/generators/python/variables.js +++ b/generators/python/variables.js @@ -7,26 +7,28 @@ /** * @fileoverview Generating Python for variable blocks. */ -'use strict'; -goog.module('Blockly.Python.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.variables'); -const {NameType} = goog.require('Blockly.Names'); -const {pythonGenerator: Python} = goog.require('Blockly.Python'); +import {NameType} from '../../core/names.js'; +import {Order} from './python_generator.js'; -Python['variables_get'] = function(block) { +export function variables_get(block, generator) { // Variable getter. const code = - Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); - return [code, Python.ORDER_ATOMIC]; + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); + return [code, Order.ATOMIC]; }; -Python['variables_set'] = function(block) { +export function variables_set(block, generator) { // Variable setter. const argument0 = - Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '0'; + generator.valueToCode(block, 'VALUE', Order.NONE) || '0'; const varName = - Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE); + generator.nameDB_.getName( + block.getFieldValue('VAR'), NameType.VARIABLE); return varName + ' = ' + argument0 + '\n'; }; diff --git a/generators/python/variables_dynamic.js b/generators/python/variables_dynamic.js index b2632eece..5a19faf47 100644 --- a/generators/python/variables_dynamic.js +++ b/generators/python/variables_dynamic.js @@ -7,15 +7,13 @@ /** * @fileoverview Generating Python for dynamic variable blocks. */ -'use strict'; -goog.module('Blockly.Python.variablesDynamic'); - -const {pythonGenerator: Python} = goog.require('Blockly.Python'); -/** @suppress {extraRequire} */ -goog.require('Blockly.Python.variables'); +import * as goog from '../../closure/goog/goog.js'; +goog.declareModuleId('Blockly.Python.variablesDynamic'); -// Python is dynamically typed. -Python['variables_get_dynamic'] = Python['variables_get']; -Python['variables_set_dynamic'] = Python['variables_set']; +// generator is dynamically typed. +export { + variables_get as variables_get_dynamic, + variables_set as variables_set_dynamic, +} from './variables.js'; diff --git a/gulpfile.js b/gulpfile.js index 0bad4309f..d2ad650c6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -15,10 +15,8 @@ const gulp = require('gulp'); const buildTasks = require('./scripts/gulpfiles/build_tasks'); const packageTasks = require('./scripts/gulpfiles/package_tasks'); const gitTasks = require('./scripts/gulpfiles/git_tasks'); -const licenseTasks = require('./scripts/gulpfiles/license_tasks'); const appengineTasks = require('./scripts/gulpfiles/appengine_tasks'); const releaseTasks = require('./scripts/gulpfiles/release_tasks'); -const cleanupTasks = require('./scripts/gulpfiles/cleanup_tasks'); const docsTasks = require('./scripts/gulpfiles/docs_tasks'); const testTasks = require('./scripts/gulpfiles/test_tasks'); @@ -27,7 +25,7 @@ module.exports = { default: buildTasks.build, // Main sequence targets. They already invoke prerequisites. - langfiles: buildTasks.langfiles, // Build build/msg/*.js from msg/json/*. + langfiles: buildTasks.langfiles, // Build build/msg/*.js from msg/json/*. tsc: buildTasks.tsc, deps: buildTasks.deps, minify: buildTasks.minify, @@ -41,10 +39,7 @@ module.exports = { gitUpdateGithubPages: gitTasks.updateGithubPages, // Manually-invokable targets, with prerequisites where required. - format: buildTasks.format, - messages: buildTasks.messages, // Generate msg/json/en.json et al. - sortRequires: cleanupTasks.sortRequires, - checkLicenses: licenseTasks.checkLicenses, + messages: buildTasks.messages, // Generate msg/json/en.json et al. clean: gulp.parallel(buildTasks.cleanBuildDir, packageTasks.cleanReleaseDir), test: testTasks.test, testGenerators: testTasks.generators, diff --git a/jsconfig.json b/jsconfig.json index ba31d905b..8986a9688 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,11 +2,6 @@ "compilerOptions": { "target": "es5" }, - "include": [ - "core/**/**/*", - "typings/blockly.d.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["core/**/**/*", "typings/blockly.d.ts"], + "exclude": ["node_modules"] } diff --git a/msg/json/be.json b/msg/json/be.json index 42db4ac1d..3b4954ed1 100644 --- a/msg/json/be.json +++ b/msg/json/be.json @@ -2,6 +2,7 @@ "@metadata": { "authors": [ "No Sleep till Krupki", + "Plaga med", "SimondR", "ZlyiLev" ] @@ -279,7 +280,7 @@ "LISTS_GET_SUBLIST_TOOLTIP": "Стварае копію ўказанай частцы спісу.", "LISTS_SORT_TITLE": "сартаваць %1 %2 %3", "LISTS_SORT_TOOLTIP": "Сартаваць копію спісу.", - "LISTS_SORT_ORDER_ASCENDING": "па ўзрастанню", + "LISTS_SORT_ORDER_ASCENDING": "па ўзрастанні", "LISTS_SORT_ORDER_DESCENDING": "па спаданню", "LISTS_SORT_TYPE_NUMERIC": "лікавая", "LISTS_SORT_TYPE_TEXT": "па алфавіце", diff --git a/msg/json/bg.json b/msg/json/bg.json index af46cf206..134fed565 100644 --- a/msg/json/bg.json +++ b/msg/json/bg.json @@ -111,6 +111,12 @@ "LOGIC_TERNARY_TOOLTIP": "Провери условието в „тест“. Ако условието е истина, върни стойността „ако е вярно“, иначе върни стойността „ако е невярно“.", "MATH_NUMBER_HELPURL": "https://bg.wikipedia.org/wiki/Число", "MATH_NUMBER_TOOLTIP": "Число.", + "MATH_TRIG_SIN": "синус", + "MATH_TRIG_COS": "косинус", + "MATH_TRIG_TAN": "тангенс", + "MATH_TRIG_ASIN": "аркус синус", + "MATH_TRIG_ACOS": "аркус косинус", + "MATH_TRIG_ATAN": "аркус тангенс", "MATH_ARITHMETIC_HELPURL": "https://bg.wikipedia.org/wiki/Аритметика", "MATH_ARITHMETIC_TOOLTIP_ADD": "Върни сумата на двете числа.", "MATH_ARITHMETIC_TOOLTIP_MINUS": "Върни разликата на двете числа.", @@ -251,6 +257,7 @@ "LISTS_GET_INDEX_GET": "вземи", "LISTS_GET_INDEX_GET_REMOVE": "вземи и премахни", "LISTS_GET_INDEX_REMOVE": "премахни", + "LISTS_GET_INDEX_FROM_START": "№", "LISTS_GET_INDEX_FROM_END": "№ от края", "LISTS_GET_INDEX_FIRST": "първия", "LISTS_GET_INDEX_LAST": "последния", diff --git a/msg/json/br.json b/msg/json/br.json index a152d9fc1..21f4b835c 100644 --- a/msg/json/br.json +++ b/msg/json/br.json @@ -1,6 +1,7 @@ { "@metadata": { "authors": [ + "Adriendelucca", "Fohanno", "Fulup", "Gwenn-Ael", @@ -107,6 +108,12 @@ "LOGIC_TERNARY_TOOLTIP": "Gwiriañ an amplegad e 'prouad'. M'eo gwir an amplegad, distreiñ an dalvoudenn 'm'eo gwir'; anez distreiñ ar moned 'm'eo faos'.", "MATH_NUMBER_HELPURL": "https://br.wikipedia.org/wiki/Niver", "MATH_NUMBER_TOOLTIP": "Un niver.", + "MATH_TRIG_SIN": "sin", + "MATH_TRIG_COS": "cos", + "MATH_TRIG_TAN": "tan", + "MATH_TRIG_ASIN": "asin", + "MATH_TRIG_ACOS": "acos", + "MATH_TRIG_ATAN": "atan", "MATH_ARITHMETIC_HELPURL": "https://br.wikipedia.org/wiki/Aritmetik", "MATH_ARITHMETIC_TOOLTIP_ADD": "Distreiñ sammad daou niver.", "MATH_ARITHMETIC_TOOLTIP_MINUS": "Distreiñ diforc'h daou niver", @@ -243,6 +250,7 @@ "LISTS_GET_INDEX_GET": "tapout", "LISTS_GET_INDEX_GET_REMOVE": "tapout ha lemel", "LISTS_GET_INDEX_REMOVE": "lemel", + "LISTS_GET_INDEX_FROM_START": "#", "LISTS_GET_INDEX_FROM_END": "# adalek ar fin", "LISTS_GET_INDEX_FIRST": "kentañ", "LISTS_GET_INDEX_LAST": "diwezhañ", diff --git a/msg/json/constants.json b/msg/json/constants.json index f2801d355..5d2b80a63 100644 --- a/msg/json/constants.json +++ b/msg/json/constants.json @@ -1 +1 @@ -{"MATH_HUE": "230", "LOOPS_HUE": "120", "LISTS_HUE": "260", "LOGIC_HUE": "210", "VARIABLES_HUE": "330", "TEXTS_HUE": "160", "PROCEDURES_HUE": "290", "COLOUR_HUE": "20", "VARIABLES_DYNAMIC_HUE": "310"} \ No newline at end of file +{"LOGIC_HUE": "210", "LOOPS_HUE": "120", "MATH_HUE": "230", "TEXTS_HUE": "160", "LISTS_HUE": "260", "COLOUR_HUE": "20", "VARIABLES_HUE": "330", "VARIABLES_DYNAMIC_HUE": "310", "PROCEDURES_HUE": "290"} \ No newline at end of file diff --git a/msg/json/en.json b/msg/json/en.json index 3830c1798..e0e618554 100644 --- a/msg/json/en.json +++ b/msg/json/en.json @@ -1,7 +1,7 @@ { "@metadata": { "author": "Ellen Spertus ", - "lastupdated": "2021-07-01 14:44:40.033432", + "lastupdated": "2023-06-28 19:16:16.457616", "locale": "en", "messagedocumentation" : "qqq" }, @@ -38,6 +38,7 @@ "NEW_VARIABLE_TITLE": "New variable name:", "VARIABLE_ALREADY_EXISTS": "A variable named '%1' already exists.", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "A variable named '%1' already exists for another type: '%2'.", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "A variable named '%1' already exists as a parameter in the procedure '%2'.", "DELETE_VARIABLE_CONFIRMATION": "Delete %1 uses of the '%2' variable?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "Can't delete the variable '%1' because it's part of the definition of the function '%2'", "DELETE_VARIABLE": "Delete the '%1' variable", diff --git a/msg/json/fr.json b/msg/json/fr.json index 19225a337..f388f98cc 100644 --- a/msg/json/fr.json +++ b/msg/json/fr.json @@ -85,7 +85,7 @@ "CONTROLS_FOR_TITLE": "compter avec %1 de %2 à %3 par %4", "CONTROLS_FOREACH_HELPURL": "https://fr.wikipedia.org/wiki/Structure_de_contrôle#Itérateurs", "CONTROLS_FOREACH_TITLE": "pour chaque élément %1 dans la liste %2", - "CONTROLS_FOREACH_TOOLTIP": "Pour chaque élément d’une liste, assigner la valeur de l’élément à la variable « %1 », puis exécuter des instructions.", + "CONTROLS_FOREACH_TOOLTIP": "Pour chaque élément d’une liste, affecter la valeur de l’élément à la variable « %1 », puis exécuter quelques instructions.", "CONTROLS_FLOW_STATEMENTS_HELPURL": "https://fr.wikipedia.org/wiki/Structure_de_contrôle#Commandes_de_sortie_de_boucle", "CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK": "quitter la boucle", "CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE": "passer à l’itération de boucle suivante", diff --git a/msg/json/hi.json b/msg/json/hi.json index c3a8eeaca..6103b5bb7 100644 --- a/msg/json/hi.json +++ b/msg/json/hi.json @@ -237,6 +237,7 @@ "LISTS_GET_INDEX_GET": "प्राप्त", "LISTS_GET_INDEX_GET_REMOVE": "प्राप्त करे और हटाए", "LISTS_GET_INDEX_REMOVE": "निकालें", + "LISTS_GET_INDEX_FROM_START": "#", "LISTS_GET_INDEX_FROM_END": "अंत से #", "LISTS_GET_INDEX_FIRST": "पहला", "LISTS_GET_INDEX_LAST": "आखिरी", diff --git a/msg/json/inh.json b/msg/json/inh.json index 274b5e658..c121fe38a 100644 --- a/msg/json/inh.json +++ b/msg/json/inh.json @@ -25,7 +25,7 @@ "EXPAND_BLOCK": "Хьайоаржае блок", "EXPAND_ALL": "Хьайоаржае блокаш", "DISABLE_BLOCK": "ДIайоае блок", - "ENABLE_BLOCK": "Хьалотае блок", + "ENABLE_BLOCK": "Блок хьалсага", "HELP": "Новкъoстал", "UNDO": "Юхадаккха", "REDO": "Юхадоаладе", diff --git a/msg/json/ja.json b/msg/json/ja.json index 4560bc130..b85adf44b 100644 --- a/msg/json/ja.json +++ b/msg/json/ja.json @@ -6,6 +6,7 @@ "Gulpin", "Kebhr", "Kkairri", + "MagicAho", "Oda", "Omotecho", "Otokoume", @@ -123,7 +124,7 @@ "LOGIC_TERNARY_IF_FALSE": "false の場合", "LOGIC_TERNARY_TOOLTIP": "'テスト' の条件をチェックします。条件が true の場合、'true' の値を返します。それ以外の場合 'false' のを返します。", "MATH_NUMBER_HELPURL": "https://ja.wikipedia.org/wiki/数", - "MATH_NUMBER_TOOLTIP": "数です。", + "MATH_NUMBER_TOOLTIP": "数字。", "MATH_TRIG_SIN": "sin", "MATH_TRIG_COS": "cos", "MATH_TRIG_TAN": "tan", diff --git a/msg/json/kn.json b/msg/json/kn.json index 21c8bc93f..c134988b2 100644 --- a/msg/json/kn.json +++ b/msg/json/kn.json @@ -110,10 +110,12 @@ "LOGIC_TERNARY_IF_FALSE": "ಸುಳ್ಳು ಆಗಿದ್ದರೆ", "LOGIC_TERNARY_TOOLTIP": "'ಪರೀಕ್ಷೆ'ಯಲ್ಲಿನ ಷರತ್ತನ್ನು ಪರಿಶೀಲಿಸಿ. ಷರತ್ತು ಸರಿಯಾಗಿದ್ದರೆ, 'ಸತ್ಯವಾಗಿದ್ದರೆ' ಮೌಲ್ಯವನ್ನು; ಇಲ್ಲದಿದ್ದರೆ 'ಸುಳ್ಳಾಗಿದ್ದರೆ' ಮೌಲ್ಯವನ್ನೂ ಹಿಂತಿರುಗಿಸುವುದು.", "MATH_NUMBER_TOOLTIP": "ಒಂದು ಸಂಖ್ಯೆ.", - "MATH_TRIG_SIN": "ಪಾಪ", - "MATH_TRIG_TAN": "ಕಂದುಬಣ್ಣ", - "MATH_TRIG_ASIN": "ಅಸಿನ್", - "MATH_TRIG_ACOS": "ಅಕೋಸ್", + "MATH_TRIG_SIN": "ಸೈನ್", + "MATH_TRIG_COS": "ಕಾಸ್", + "MATH_TRIG_TAN": "ಟ್ಯಾನ್", + "MATH_TRIG_ASIN": "ಆರ್ಕ್-ಸೈನ್", + "MATH_TRIG_ACOS": "ಆರ್ಕ್-ಕಾಸ್", + "MATH_TRIG_ATAN": "ಆರ್ಕ್-ಟ್ಯಾನ್", "MATH_ARITHMETIC_TOOLTIP_ADD": "ಎರಡು ಸಂಖ್ಯೆಗಳ ಮೊತ್ತವನ್ನು ಹಿಂತಿರುಗಿಸಿ.", "MATH_ARITHMETIC_TOOLTIP_MINUS": "ಎರಡು ಸಂಖ್ಯೆಗಳ ವ್ಯತ್ಯಾಸವನ್ನು ಹಿಂತಿರುಗಿಸಿ.", "MATH_ARITHMETIC_TOOLTIP_MULTIPLY": "ಎರಡು ಸಂಖ್ಯೆಗಳ ಗುಣಲಬ್ಧವನ್ನು ಹಿಂತಿರುಗಿಸಿ.", @@ -146,9 +148,9 @@ "MATH_CHANGE_TITLE": "%1 ಅನ್ನು %2 ರಿಂದ ಬದಲಾಯಿಸಿ", "MATH_CHANGE_TOOLTIP": "ಚರಾಂಶ '%1' ಕ್ಕೆ ಒಂದು ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ.", "MATH_ROUND_TOOLTIP": "ಒಂದು ಸಂಖ್ಯೆಯನ್ನು ಮೇಲಿನ ಅಥವಾ ಕೆಳಗಿನ ಪೂರ್ಣಾಂಕ ಮಾಡಿ.", - "MATH_ROUND_OPERATOR_ROUND": "ಸುತ್ತು", - "MATH_ROUND_OPERATOR_ROUNDUP": "ಮೇಲಿನ ಪೂರ್ಣಾಂಕ ಮಾಡಿ.", - "MATH_ROUND_OPERATOR_ROUNDDOWN": "ಕೆಳಗಿನ ಪೂರ್ಣಾಂಕ ಮಾಡಿ.", + "MATH_ROUND_OPERATOR_ROUND": "ಪೂರ್ಣಾಂಕಗೊಳಿಸು", + "MATH_ROUND_OPERATOR_ROUNDUP": "ಮೇಲಿನ-ಅಂಕಕ್ಕೆ ಪೂರ್ಣಾಂಕಗೊಳಿಸಿ", + "MATH_ROUND_OPERATOR_ROUNDDOWN": "ಕೆಳಗಿನ-ಅಂಕಕ್ಕೆ ಪೂರ್ಣಾಂಕಗೊಳಿಸಿ", "MATH_ONLIST_OPERATOR_SUM": "ಪಟ್ಟಿಯ ಮೊತ್ತ", "MATH_ONLIST_TOOLTIP_SUM": "ಪಟ್ಟಿಯಲ್ಲಿರುವ ಎಲ್ಲಾ ಸಂಖ್ಯೆಗಳ ಮೊತ್ತವನ್ನು ಹಿಂತಿರುಗಿಸಿ.", "MATH_ONLIST_OPERATOR_MIN": "ಪಟ್ಟಿಯ ಕನಿಷ್ಠ", diff --git a/msg/json/ky.json b/msg/json/ky.json index cf2af9899..0cdf8cb97 100644 --- a/msg/json/ky.json +++ b/msg/json/ky.json @@ -57,10 +57,13 @@ "CONTROLS_IF_MSG_ELSE": "башка", "LOGIC_OPERATION_OR": "же", "LOGIC_NEGATE_TITLE": " %1 эмес", + "LOGIC_BOOLEAN_TRUE": "чындык", + "LOGIC_BOOLEAN_FALSE": "жаңылыш", "LOGIC_TERNARY_CONDITION": "текшерүү", "LOGIC_TERNARY_IF_TRUE": "туура болсо", "LOGIC_TERNARY_IF_FALSE": "ката болсо", "MATH_TRIG_HELPURL": "https://ky.wikipedia.org/wiki/Тригонометриялык_функциялар", "TEXT_ISEMPTY_TITLE": "%1 бош", - "DIALOG_OK": "OK" + "DIALOG_OK": "OK", + "DIALOG_CANCEL": "Жокко чыгаруу" } diff --git a/msg/json/lb.json b/msg/json/lb.json index 3734add45..e373761e6 100644 --- a/msg/json/lb.json +++ b/msg/json/lb.json @@ -147,7 +147,7 @@ "PROCEDURES_BEFORE_PARAMS": "mat:", "PROCEDURES_CALL_BEFORE_PARAMS": "mat:", "PROCEDURES_DEFNORETURN_COMMENT": "Dës Funktioun beschreiwen...", - "PROCEDURES_DEFRETURN_RETURN": "zréck", + "PROCEDURES_DEFRETURN_RETURN": "zeréck", "PROCEDURES_CREATE_DO": "'%1' uleeën", "PROCEDURES_IFRETURN_WARNING": "Opgepasst: Dëse Block däerf nëmmen bannent enger Funktiounsdefitioun benotzt ginn.", "WORKSPACE_COMMENT_DEFAULT_TEXT": "Sot eppes...", diff --git a/msg/json/lt.json b/msg/json/lt.json index 9818697c1..1bfef92d4 100644 --- a/msg/json/lt.json +++ b/msg/json/lt.json @@ -3,11 +3,13 @@ "authors": [ "Eitvys200", "Jurgis", + "Nokeoo", "Nuodas", "Zygimantus" ] }, "VARIABLES_DEFAULT_NAME": "elementas", + "UNNAMED_KEY": "bevardis", "TODAY": "Šiandien", "DUPLICATE_BLOCK": "Kopijuoti", "ADD_COMMENT": "Palikti komentarą", @@ -104,6 +106,9 @@ "LOGIC_TERNARY_TOOLTIP": "Jeigu sąlygą tenkinama, grąžina pirmą reikšmę, o jei ne - antrąją.", "MATH_NUMBER_HELPURL": "https://lt.wikipedia.org/wiki/Skaičius", "MATH_NUMBER_TOOLTIP": "Skaičius.", + "MATH_TRIG_SIN": "sin", + "MATH_TRIG_COS": "cos", + "MATH_TRIG_TAN": "tan", "MATH_ARITHMETIC_TOOLTIP_ADD": "Grąžina dviejų skaičių sumą.", "MATH_ARITHMETIC_TOOLTIP_MINUS": "Grąžina dviejų skaičių skirtumą.", "MATH_ARITHMETIC_TOOLTIP_MULTIPLY": "Grąžina dviejų skaičių sandaugą.", @@ -181,6 +186,7 @@ "TEXT_INDEXOF_TITLE": "tekste %1 %2 %3", "TEXT_INDEXOF_OPERATOR_FIRST": "rask,kur pirmą kartą paminėta", "TEXT_INDEXOF_OPERATOR_LAST": "rask,kur paskutinį kartą paminėta", + "TEXT_CHARAT_TITLE": "tekste %1 %2", "TEXT_CHARAT_FROM_START": "gauti raidę #", "TEXT_CHARAT_FROM_END": "raidė nuo galo #", "TEXT_CHARAT_FIRST": "gauti pirmą raidę", @@ -244,6 +250,10 @@ "LISTS_GET_INDEX_TOOLTIP_GET_FIRST": "Grąžina pirmąjį sąrašo elementą.", "LISTS_GET_INDEX_TOOLTIP_GET_LAST": "Grąžina paskutinį elementą iš sąrašo.", "LISTS_GET_INDEX_TOOLTIP_GET_RANDOM": "Grąžina atsitiktinį elementą iš sąrašo.", + "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM": "Pašalina ir grąžina objektą nurodytoje sąrašo pozicijoje.", + "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST": "Pašalina ir grąžina pirmąjį sąrašo objektą.", + "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST": "Pašalina ir grąžina paskutinį sąrašo objektą.", + "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM": "Pašalina ir grąžina atsitiktinį sąrašo objektą.", "LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM": "Pašalina objektą iš nurodytos vietos sąraše.", "LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST": "Pašalina pirmą objektą sąraše.", "LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST": "Pašalina paskutinį objektą sąraše.", @@ -251,8 +261,13 @@ "LISTS_SET_INDEX_SET": "priskirk elementui", "LISTS_SET_INDEX_INSERT": "įterpk į vietą", "LISTS_SET_INDEX_INPUT_TO": "kaip", + "LISTS_SET_INDEX_TOOLTIP_SET_FROM": "Nustato objektą nurodytoje sąrašo vietoje.", + "LISTS_SET_INDEX_TOOLTIP_SET_FIRST": "Nustato pirmąjį sąrašo objektą.", + "LISTS_SET_INDEX_TOOLTIP_SET_LAST": "Nustato paskutinį sąrašo objektą.", + "LISTS_SET_INDEX_TOOLTIP_SET_RANDOM": "Sąraše nustato atsitiktinį objektą.", "LISTS_SET_INDEX_TOOLTIP_INSERT_FROM": "Įterpią objektą į nurodytą poziciją sąraše.", "LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST": "Įterpia objektą į sąrašo pradžią.", + "LISTS_SET_INDEX_TOOLTIP_INSERT_LAST": "Pridėkite objektą prie sąrašo pabaigos.", "LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM": "Įterpia objektą į atsitiktinę sąrašo vietą.", "LISTS_GET_SUBLIST_START_FROM_START": "sąrašo dalis nuo #", "LISTS_GET_SUBLIST_START_FROM_END": "sąrašo dalis nuo # nuo galo", @@ -268,9 +283,15 @@ "LISTS_SORT_TYPE_NUMERIC": "skaitmeninis", "LISTS_SORT_TYPE_TEXT": "abėcėlės", "LISTS_SORT_TYPE_IGNORECASE": "abecėlės, ignoruoti raidžių dydį", + "LISTS_SPLIT_LIST_FROM_TEXT": "sudaryti sąrašą iš teksto", + "LISTS_SPLIT_TEXT_FROM_LIST": "sukurti tekstą iš sąrašo", "LISTS_SPLIT_WITH_DELIMITER": "su dalikliu", + "LISTS_REVERSE_MESSAGE0": "atvirkštinis %1", + "LISTS_REVERSE_TOOLTIP": "Apverskite sąrašo kopiją.", + "VARIABLES_GET_TOOLTIP": "Grąžina šio kintamojo reikšmę.", "VARIABLES_GET_CREATE_SET": "Sukurk \"priskirk %1\"", "VARIABLES_SET": "priskirk %1 = %2", + "VARIABLES_SET_TOOLTIP": "Nustato, kad šis kintamasis būtų lygus įvesties vertei.", "VARIABLES_SET_CREATE_GET": "Sukurti 'kintamasis %1'", "PROCEDURES_DEFNORETURN_TITLE": "komanda:", "PROCEDURES_DEFNORETURN_PROCEDURE": "daryk kažką", @@ -292,6 +313,8 @@ "PROCEDURES_IFRETURN_TOOLTIP": "Jeigu pirma reikšmė yra teisinga (sąlyga tenkinama), grąžina antrą reikšmę.", "PROCEDURES_IFRETURN_WARNING": "Perspėjimas: šis blokas gali būti naudojamas tik aprašant funkciją.", "WORKSPACE_COMMENT_DEFAULT_TEXT": "Kažką pasakykite...", + "WORKSPACE_ARIA_LABEL": "Blockly darbalaukis", + "COLLAPSED_WARNINGS_WARNING": "Suskleistuose blokuose yra įspėjimų.", "DIALOG_OK": "Gerai", "DIALOG_CANCEL": "Atšaukti" } diff --git a/msg/json/nb.json b/msg/json/nb.json index 53cb4d06b..1f79b0c83 100644 --- a/msg/json/nb.json +++ b/msg/json/nb.json @@ -173,6 +173,7 @@ "MATH_RANDOM_FLOAT_TITLE_RANDOM": "tilfeldig flyttall", "MATH_RANDOM_FLOAT_TOOLTIP": "Returner et tilfeldig flyttall mellom 0.0 (inkludert) og 1.0 (ikke inkludert).", "MATH_ATAN2_TITLE": "atan2 av X:%1 Y:%2", + "MATH_ATAN2_TOOLTIP": "Returner arctangensen til punkt (X, Y) i grader fra -180 til 180.", "TEXT_TEXT_TOOLTIP": "En bokstav, ett ord eller en linje med tekst.", "TEXT_JOIN_TITLE_CREATEWITH": "lag tekst med", "TEXT_JOIN_TOOLTIP": "Opprett en tekst ved å sette sammen et antall elementer.", @@ -321,6 +322,8 @@ "PROCEDURES_IFRETURN_TOOLTIP": "Hvis en verdi er sann, returner da en annen verdi.", "PROCEDURES_IFRETURN_WARNING": "Advarsel: Denne blokken kan bare benyttes innenfor en funksjonsdefinisjon.", "WORKSPACE_COMMENT_DEFAULT_TEXT": "Si noe …", + "WORKSPACE_ARIA_LABEL": "Blockly-arbeidsrom", + "COLLAPSED_WARNINGS_WARNING": "Sammenslåtte blokker inneholder advarsler.", "DIALOG_OK": "OK", "DIALOG_CANCEL": "Avbryt" } diff --git a/msg/json/qqq.json b/msg/json/qqq.json index 3cfa464f8..0457526c5 100644 --- a/msg/json/qqq.json +++ b/msg/json/qqq.json @@ -43,21 +43,22 @@ "NEW_VARIABLE_TITLE": "prompt - Prompts the user to enter the name for a new variable. See [https://github.com/google/blockly/wiki/Variables#dropdown-menu https://github.com/google/blockly/wiki/Variables#dropdown-menu].", "VARIABLE_ALREADY_EXISTS": "alert - Tells the user that the name they entered is already in use.", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "alert - Tells the user that the name they entered is already in use for another type.", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "alert - Tells the user that the name they entered is already in use as a parameter to a procedure, that the variable they are renaming also exists on. Renaming would create two parameters with the same name, which is not allowed.", "DELETE_VARIABLE_CONFIRMATION": "confirm - Ask the user to confirm their deletion of multiple uses of a variable.", "CANNOT_DELETE_VARIABLE_PROCEDURE": "alert - Tell the user that they can't delete a variable because it's part of the definition of a function.", "DELETE_VARIABLE": "dropdown choice - Delete the currently selected variable.", "COLOUR_PICKER_HELPURL": "{{Optional}} url - Information about colour.", "COLOUR_PICKER_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#picking-a-colour-from-a-palette https://github.com/google/blockly/wiki/Colour#picking-a-colour-from-a-palette].", - "COLOUR_RANDOM_HELPURL": "{{Ignored}} url - A link that displays a random colour each time you visit it.", + "COLOUR_RANDOM_HELPURL": "{{Optional}} url - A link that displays a random colour each time you visit it.", "COLOUR_RANDOM_TITLE": "block text - Title of block that generates a colour at random.", "COLOUR_RANDOM_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#generating-a-random-colour https://github.com/google/blockly/wiki/Colour#generating-a-random-colour].", - "COLOUR_RGB_HELPURL": "{{Ignored}} url - A link for colour codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners.", + "COLOUR_RGB_HELPURL": "{{Optional}} url - A link for colour codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners.", "COLOUR_RGB_TITLE": "block text - Title of block for [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", "COLOUR_RGB_RED": "block input text - The amount of red (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].\n{{Identical|Red}}", "COLOUR_RGB_GREEN": "block input text - The amount of green (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", "COLOUR_RGB_BLUE": "block input text - The amount of blue (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].\n{{Identical|Blue}}", "COLOUR_RGB_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", - "COLOUR_BLEND_HELPURL": "{{Ignored}} url - A useful link that displays blending of two colours.", + "COLOUR_BLEND_HELPURL": "{{Optional}} url - A useful link that displays blending of two colours.", "COLOUR_BLEND_TITLE": "block text - A verb for blending two shades of paint.", "COLOUR_BLEND_COLOUR1": "block input text - The first of two colours to [https://github.com/google/blockly/wiki/Colour#blending-colours blend].", "COLOUR_BLEND_COLOUR2": "block input text - The second of two colours to [https://github.com/google/blockly/wiki/Colour#blending-colours blend].", @@ -237,7 +238,7 @@ "TEXT_CHARAT_FIRST": "block text - Indicates that the first letter of the following piece of text should be retrieved. See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", "TEXT_CHARAT_LAST": "block text - Indicates that the last letter (or number, punctuation mark, etc.) of the following piece of text should be retrieved. See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", "TEXT_CHARAT_RANDOM": "block text - Indicates that any letter (or number, punctuation mark, etc.) in the following piece of text should be randomly selected. See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", - "TEXT_CHARAT_TAIL": "{{Optional}}\nblock text - Text that goes after the rightmost block/dropdown when getting a single letter from a piece of text, as in [https://blockly-demo.appspot.com/static/apps/code/index.html#3m23km these blocks] or shown below. For most languages, this will be blank. [[File:Blockly-text-get.png]]", + "TEXT_CHARAT_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - Text that goes after the rightmost block/dropdown when getting a single letter from a piece of text, as in [https://blockly-demo.appspot.com/static/apps/code/index.html#3m23km these blocks] or shown below. For most languages, this will be blank. [[File:Blockly-text-get.png]]", "TEXT_CHARAT_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Text#extracting-a-single-character https://github.com/google/blockly/wiki/Text#extracting-a-single-character]. [[File:Blockly-text-get.png]]", "TEXT_GET_SUBSTRING_TOOLTIP": "See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text].", "TEXT_GET_SUBSTRING_HELPURL": "{{Optional}} url - Information about extracting characters from text. Reminder: urls are the lowest priority translations. Feel free to skip.", @@ -248,7 +249,7 @@ "TEXT_GET_SUBSTRING_END_FROM_START": "dropdown - Indicates that the following number specifies the position (relative to the start position) of the end of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_END_FROM_END": "dropdown - Indicates that the following number specifies the position (relative to the end position) of the end of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_END_LAST": "block text - Indicates that a region ending with the last letter of the preceding piece of text should be extracted. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", - "TEXT_GET_SUBSTRING_TAIL": "{{Optional}}\nblock text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text extracting a region of text]. In most languages, this will be the empty string. [[File:Blockly-get-substring.png]]", + "TEXT_GET_SUBSTRING_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text extracting a region of text]. In most languages, this will be the empty string. [[File:Blockly-get-substring.png]]", "TEXT_CHANGECASE_HELPURL": "{{Optional}} url - Information about the case of letters (upper-case and lower-case).", "TEXT_CHANGECASE_TOOLTIP": "tooltip - Describes a block to adjust the case of letters. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", "TEXT_CHANGECASE_OPERATOR_UPPERCASE": "block text - Indicates that all of the letters in the following piece of text should be capitalized. If your language does not use case, you may indicate that this is not applicable to your language. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", @@ -307,7 +308,7 @@ "LISTS_GET_INDEX_FIRST": "dropdown - Indicates that the '''first''' item should be [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessed in a list]. [[File:Blockly-list-get-item.png]]", "LISTS_GET_INDEX_LAST": "dropdown - Indicates that the '''last''' item should be [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessed in a list]. [[File:Blockly-list-get-item.png]]", "LISTS_GET_INDEX_RANDOM": "dropdown - Indicates that a '''random''' item should be [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessed in a list]. [[File:Blockly-list-get-item.png]]", - "LISTS_GET_INDEX_TAIL": "{{Optional}}\nblock text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessing an item from a list]. In most languages, this will be the empty string. [[File:Blockly-list-get-item.png]]", + "LISTS_GET_INDEX_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessing an item from a list]. In most languages, this will be the empty string. [[File:Blockly-list-get-item.png]]", "LISTS_INDEX_FROM_START_TOOLTIP": "tooltip - Indicates the ordinal number that the first item in a list is referenced by. %1 will be replaced by either '#0' or '#1' depending on the indexing mode.", "LISTS_INDEX_FROM_END_TOOLTIP": "tooltip - Indicates the ordinal number that the last item in a list is referenced by. %1 will be replaced by either '#0' or '#1' depending on the indexing mode.", "LISTS_GET_INDEX_TOOLTIP_GET_FROM": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-single-item https://github.com/google/blockly/wiki/Lists#getting-a-single-item] for more information.", @@ -341,7 +342,7 @@ "LISTS_GET_SUBLIST_END_FROM_START": "dropdown - Indicates that an index relative to the front of the list should be used to specify the end of the range from which to [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist]. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_END_FROM_END": "dropdown - Indicates that an index relative to the end of the list should be used to specify the end of the range from which to [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist]. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_END_LAST": "dropdown - Indicates that the '''last''' item in the given list should be [https://github.com/google/blockly/wiki/Lists#getting-a-sublist the end of the selected sublist]. [[File:Blockly-get-sublist.png]]", - "LISTS_GET_SUBLIST_TAIL": "{{Optional}}\nblock text - This appears in the rightmost position ('tail') of the sublist block, as described at [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist]. In English and most other languages, this is the empty string. [[File:Blockly-get-sublist.png]]", + "LISTS_GET_SUBLIST_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - This appears in the rightmost position ('tail') of the sublist block, as described at [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist]. In English and most other languages, this is the empty string. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist] for more information. [[File:Blockly-get-sublist.png]]", "LISTS_SORT_HELPURL": "{{Optional}} url - Information describing sorting a list.", "LISTS_SORT_TITLE": "Sort as type %1 (numeric or alphabetic) in order %2 (ascending or descending) a list of items %3.\n{{Identical|Sort}}", @@ -360,7 +361,7 @@ "LISTS_REVERSE_HELPURL": "{{Optional}} url - Information describing reversing a list.", "LISTS_REVERSE_MESSAGE0": "block text - Title of block that returns a copy of a list (%1) with the order of items reversed.", "LISTS_REVERSE_TOOLTIP": "tooltip - Short description for a block that reverses a copy of a list.", - "ORDINAL_NUMBER_SUFFIX": "{{Optional}}\ngrammar - Text that follows an ordinal number (a number that indicates position relative to other numbers). In most languages, such text appears before the number, so this should be blank. An exception is Hungarian. See [[Translating:Blockly#Ordinal_numbers]] for more information.", + "ORDINAL_NUMBER_SUFFIX": "{{Optional|Supply translation only if your language requires it. Most do not.}} grammar - Text that follows an ordinal number (a number that indicates position relative to other numbers). In most languages, such text appears before the number, so this should be blank. An exception is Hungarian. See [[Translating:Blockly#Ordinal_numbers]] for more information.", "VARIABLES_GET_HELPURL": "{{Optional}} url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists.", "VARIABLES_GET_TOOLTIP": "tooltip - This gets the value of the named variable without modifying it.", "VARIABLES_GET_CREATE_SET": "context menu - Selecting this creates a block to set (change) the value of this variable. \n\nParameters:\n* %1 - the name of the variable.", @@ -373,7 +374,7 @@ "PROCEDURES_DEFNORETURN_PROCEDURE": "default name - This acts as a placeholder for the name of a function on a function definition block, as shown on [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#w7cfju this block]. The user will replace it with the function's name.", "PROCEDURES_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's definition block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", "PROCEDURES_CALL_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's caller block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", - "PROCEDURES_DEFNORETURN_DO": "{{Optional}}\nblock text - This appears next to the function's 'body', the blocks that should be run when the function is called, as shown in [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function definition].", + "PROCEDURES_DEFNORETURN_DO": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - This appears next to the function's 'body', the blocks that should be run when the function is called, as shown in [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function definition].", "PROCEDURES_DEFNORETURN_TOOLTIP": "tooltip", "PROCEDURES_DEFNORETURN_COMMENT": "Placeholder text that the user is encouraged to replace with a description of what their function does.", "PROCEDURES_DEFRETURN_HELPURL": "{{Optional}} url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that have return values.", diff --git a/msg/json/skr-arab.json b/msg/json/skr-arab.json index 39d84d2ee..3ca0fa350 100644 --- a/msg/json/skr-arab.json +++ b/msg/json/skr-arab.json @@ -57,6 +57,12 @@ "LOGIC_TERNARY_IF_TRUE": "اگر سچ ہے", "LOGIC_TERNARY_IF_FALSE": "اگر کوڑ ہے", "MATH_NUMBER_TOOLTIP": "ہک عدد", + "MATH_TRIG_SIN": "سائن", + "MATH_TRIG_COS": "کاس", + "MATH_TRIG_TAN": "ٹین", + "MATH_TRIG_ASIN": "اسائن", + "MATH_TRIG_ACOS": "اکاس", + "MATH_TRIG_ATAN": "اٹین", "MATH_SINGLE_OP_ROOT": "مربعی جذر", "MATH_SINGLE_OP_ABSOLUTE": "مطلق", "MATH_IS_EVEN": "جفت ہے", @@ -69,6 +75,7 @@ "MATH_ONLIST_OPERATOR_MIN": "لسٹ وچوں سب توں گھٹ", "MATH_ONLIST_OPERATOR_MAX": "لسٹ وچوں سب توں ودھ", "MATH_ONLIST_OPERATOR_AVERAGE": "فہرست دی اوسط", + "MATH_ONLIST_OPERATOR_MEDIAN": "تندیر دا وسطانیہ", "TEXT_CREATE_JOIN_TITLE_JOIN": "شامل تھیوو", "TEXT_LENGTH_TITLE": "%1 دی لمباݨ", "TEXT_ISEMPTY_TITLE": "%1 خالی ہے", @@ -76,6 +83,9 @@ "TEXT_CHARAT_FIRST": "پہلا حرف گھنو", "TEXT_CHARAT_LAST": "چھیکڑی حرف گھنو", "TEXT_GET_SUBSTRING_INPUT_IN_TEXT": "ٹیکسٹ وچ", + "TEXT_CHANGECASE_OPERATOR_UPPERCASE": "وݙی اے بی سی", + "TEXT_CHANGECASE_OPERATOR_LOWERCASE": "چھوٹی اے بی سی", + "TEXT_CHANGECASE_OPERATOR_TITLECASE": "عنوان حالت", "TEXT_PRINT_TITLE": "%1 چھاپو", "LISTS_CREATE_EMPTY_TITLE": "خالی تندیر بݨاؤ", "LISTS_CREATE_WITH_CONTAINER_TITLE_ADD": "فہرست", @@ -84,6 +94,7 @@ "LISTS_GET_INDEX_GET": "گھنو", "LISTS_GET_INDEX_GET_REMOVE": "گھنو تے ہٹاؤ", "LISTS_GET_INDEX_REMOVE": "ہٹاؤ", + "LISTS_GET_INDEX_FROM_START": "#", "LISTS_GET_INDEX_FROM_END": "# چھیکڑ کنوں", "LISTS_GET_INDEX_FIRST": "پہلا", "LISTS_GET_INDEX_LAST": "چھیکڑی", diff --git a/msg/json/synonyms.json b/msg/json/synonyms.json index 944aa9bda..df3a96326 100644 --- a/msg/json/synonyms.json +++ b/msg/json/synonyms.json @@ -1 +1 @@ -{"PROCEDURES_DEFRETURN_TITLE": "PROCEDURES_DEFNORETURN_TITLE", "CONTROLS_IF_IF_TITLE_IF": "CONTROLS_IF_MSG_IF", "CONTROLS_WHILEUNTIL_INPUT_DO": "CONTROLS_REPEAT_INPUT_DO", "CONTROLS_IF_MSG_THEN": "CONTROLS_REPEAT_INPUT_DO", "LISTS_GET_SUBLIST_INPUT_IN_LIST": "LISTS_INLIST", "CONTROLS_IF_ELSE_TITLE_ELSE": "CONTROLS_IF_MSG_ELSE", "PROCEDURES_DEFRETURN_PROCEDURE": "PROCEDURES_DEFNORETURN_PROCEDURE", "TEXT_CREATE_JOIN_ITEM_TITLE_ITEM": "VARIABLES_DEFAULT_NAME", "LISTS_GET_INDEX_INPUT_IN_LIST": "LISTS_INLIST", "PROCEDURES_DEFRETURN_COMMENT": "PROCEDURES_DEFNORETURN_COMMENT", "CONTROLS_IF_ELSEIF_TITLE_ELSEIF": "CONTROLS_IF_MSG_ELSEIF", "PROCEDURES_DEFRETURN_DO": "PROCEDURES_DEFNORETURN_DO", "CONTROLS_FOR_INPUT_DO": "CONTROLS_REPEAT_INPUT_DO", "LISTS_GET_INDEX_HELPURL": "LISTS_INDEX_OF_HELPURL", "LISTS_INDEX_OF_INPUT_IN_LIST": "LISTS_INLIST", "CONTROLS_FOREACH_INPUT_DO": "CONTROLS_REPEAT_INPUT_DO", "LISTS_CREATE_WITH_ITEM_TITLE": "VARIABLES_DEFAULT_NAME", "TEXT_APPEND_VARIABLE": "VARIABLES_DEFAULT_NAME", "MATH_CHANGE_TITLE_ITEM": "VARIABLES_DEFAULT_NAME", "LISTS_SET_INDEX_INPUT_IN_LIST": "LISTS_INLIST"} \ No newline at end of file +{"CONTROLS_WHILEUNTIL_INPUT_DO": "CONTROLS_REPEAT_INPUT_DO", "CONTROLS_FOR_INPUT_DO": "CONTROLS_REPEAT_INPUT_DO", "CONTROLS_FOREACH_INPUT_DO": "CONTROLS_REPEAT_INPUT_DO", "CONTROLS_IF_MSG_THEN": "CONTROLS_REPEAT_INPUT_DO", "CONTROLS_IF_IF_TITLE_IF": "CONTROLS_IF_MSG_IF", "CONTROLS_IF_ELSEIF_TITLE_ELSEIF": "CONTROLS_IF_MSG_ELSEIF", "CONTROLS_IF_ELSE_TITLE_ELSE": "CONTROLS_IF_MSG_ELSE", "MATH_CHANGE_TITLE_ITEM": "VARIABLES_DEFAULT_NAME", "TEXT_CREATE_JOIN_ITEM_TITLE_ITEM": "VARIABLES_DEFAULT_NAME", "TEXT_APPEND_VARIABLE": "VARIABLES_DEFAULT_NAME", "LISTS_CREATE_WITH_ITEM_TITLE": "VARIABLES_DEFAULT_NAME", "LISTS_INDEX_OF_INPUT_IN_LIST": "LISTS_INLIST", "LISTS_GET_INDEX_HELPURL": "LISTS_INDEX_OF_HELPURL", "LISTS_GET_INDEX_INPUT_IN_LIST": "LISTS_INLIST", "LISTS_SET_INDEX_INPUT_IN_LIST": "LISTS_INLIST", "LISTS_GET_SUBLIST_INPUT_IN_LIST": "LISTS_INLIST", "PROCEDURES_DEFRETURN_TITLE": "PROCEDURES_DEFNORETURN_TITLE", "PROCEDURES_DEFRETURN_PROCEDURE": "PROCEDURES_DEFNORETURN_PROCEDURE", "PROCEDURES_DEFRETURN_DO": "PROCEDURES_DEFNORETURN_DO", "PROCEDURES_DEFRETURN_COMMENT": "PROCEDURES_DEFNORETURN_COMMENT"} \ No newline at end of file diff --git a/msg/json/xmf.json b/msg/json/xmf.json index 1e2b9980c..c1ae0d980 100644 --- a/msg/json/xmf.json +++ b/msg/json/xmf.json @@ -30,9 +30,14 @@ "CHANGE_VALUE_TITLE": "შანულობაშ თირუა:", "RENAME_VARIABLE": "ჯოხოშ თირუა მათირეფონი…", "RENAME_VARIABLE_TITLE": "არძა მათირეფონი '%1' ჯოხოშ თირუა -შა:", - "NEW_VARIABLE": "აკოქიმინი მათირეფონი...", + "NEW_VARIABLE": "გაჭყით მათირეფონი...", + "NEW_STRING_VARIABLE": "გაჭყით ვეშ მათირეფონი...", + "NEW_NUMBER_VARIABLE": "გაჭყით რიცხობური მათირეფონი...", + "NEW_COLOUR_VARIABLE": "გაჭყით ფერამი მათირეფონი...", + "NEW_VARIABLE_TYPE_TITLE": "ახალი მათირეფონი ტიპი:", "NEW_VARIABLE_TITLE": "ახალი მათირეფონიშ ჯოხო:", - "VARIABLE_ALREADY_EXISTS": "მათირეფონი ჯოხოთი '%1' უკვე არსენებს.", + "VARIABLE_ALREADY_EXISTS": "მათირეფონი ჯოხოთი '%1' უკვე რე.", + "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "მათირეფონი ჯოხოთი '%1' უკვე რე შხვა ტიპიშო: '%2'.", "DELETE_VARIABLE_CONFIRMATION": "'%2' მათირეფონიშ გჷმორინაფა %1 ბლასათო?", "DELETE_VARIABLE": "'%1' მათირეფონიშ ლასუა", "COLOUR_PICKER_HELPURL": "https://xmf.wikipedia.org/wiki/ფერი", @@ -74,6 +79,9 @@ "LOGIC_NULL_TOOLTIP": "დჷთმართინუანს მუთუნ ვარს.", "MATH_NUMBER_HELPURL": "https://xmf.wikipedia.org/wiki/რიცხუ", "MATH_NUMBER_TOOLTIP": "რიცხუ.", + "MATH_TRIG_SIN": "sin", + "MATH_TRIG_COS": "cos", + "MATH_TRIG_TAN": "tan", "MATH_ARITHMETIC_HELPURL": "https://xmf.wikipedia.org/wiki/არითმეტიკა", "MATH_SINGLE_OP_ROOT": "კვადრატული ჯინჯი", "MATH_SINGLE_OP_ABSOLUTE": "მოდული", diff --git a/msg/json/zh-hans.json b/msg/json/zh-hans.json index 952dcb024..bc487d6e1 100644 --- a/msg/json/zh-hans.json +++ b/msg/json/zh-hans.json @@ -10,6 +10,7 @@ "Espertus", "Htq110219891", "Hudafu", + "Jin1358", "Lantx", "Liuxinyu970226", "Luotiancheng", @@ -115,17 +116,17 @@ "LOGIC_OPERATION_AND": "并且", "LOGIC_OPERATION_TOOLTIP_OR": "如果至少有一个输入结果为真,则返回真。", "LOGIC_OPERATION_OR": "或", - "LOGIC_NEGATE_TITLE": "非%1", - "LOGIC_NEGATE_TOOLTIP": "如果输入结果为假,则返回真;如果输入结果为真,则返回假。", - "LOGIC_BOOLEAN_TRUE": "真", - "LOGIC_BOOLEAN_FALSE": "假", - "LOGIC_BOOLEAN_TOOLTIP": "返回真或假。", + "LOGIC_NEGATE_TITLE": "非 %1", + "LOGIC_NEGATE_TOOLTIP": "如果输入结果为false,则返回true;如果输入结果为true,则返回false。", + "LOGIC_BOOLEAN_TRUE": "true", + "LOGIC_BOOLEAN_FALSE": "false", + "LOGIC_BOOLEAN_TOOLTIP": "返回 true 或 false。", "LOGIC_NULL": "空", "LOGIC_NULL_TOOLTIP": "返回空值。", "LOGIC_TERNARY_HELPURL": "https://zh.wikipedia.org/wiki/条件运算符", "LOGIC_TERNARY_CONDITION": "断言", - "LOGIC_TERNARY_IF_TRUE": "如果为真", - "LOGIC_TERNARY_IF_FALSE": "如果为假", + "LOGIC_TERNARY_IF_TRUE": "如果为 true", + "LOGIC_TERNARY_IF_FALSE": "如果为 false", "LOGIC_TERNARY_TOOLTIP": "检查“断言”里的条件语句。如果条件为真,则返回“如果为真”的值,否则,则返回“如果为假”的值。", "MATH_NUMBER_HELPURL": "https://zh.wikipedia.org/wiki/数", "MATH_NUMBER_TOOLTIP": "一个数值。", diff --git a/msg/messages.js b/msg/messages.js index e8ffe4aff..b1d762f0f 100644 --- a/msg/messages.js +++ b/msg/messages.js @@ -586,7 +586,7 @@ Blockly.Msg.MATH_IS_EVEN = 'is even'; /// dropdown - A number is '''odd''' if it is not a multiple of 2. For example, 3 is odd (yielding true), but 4 is not (false). The opposite of "odd" is "even". Blockly.Msg.MATH_IS_ODD = 'is odd'; /** @type {string} */ -/// dropdown - A number is [https://en.wikipedia.org/wiki/Prime prime] if it cannot be evenly divided by any positive integers except for 1 and itself. For example, 5 is prime, but 6 is not because 2 × 3 = 6. +/// dropdown - A number is [https://en.wikipedia.org/wiki/Prime_number prime] if it cannot be evenly divided by any positive integers except for 1 and itself. For example, 5 is prime, but 6 is not because 2 × 3 = 6. Blockly.Msg.MATH_IS_PRIME = 'is prime'; /** @type {string} */ /// dropdown - A number is '''whole''' if it is an [https://en.wikipedia.org/wiki/Integer integer]. For example, 5 is whole, but 5.1 is not. @@ -882,6 +882,7 @@ Blockly.Msg.TEXT_CHARAT_LAST = 'get last letter'; /// [[File:Blockly-text-get.png]] Blockly.Msg.TEXT_CHARAT_RANDOM = 'get random letter'; /** @type {string} */ +/// {{Optional|Supply translation only if your language requires it. Most do not.}} /// block text - Text that goes after the rightmost block/dropdown when getting a single letter from /// a piece of text, as in [https://blockly-demo.appspot.com/static/apps/code/index.html#3m23km these /// blocks] or shown below. For most languages, this will be blank. @@ -954,6 +955,7 @@ Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END = 'to letter # from end'; /// [[File:Blockly-get-substring.png]] Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST = 'to last letter'; /** @type {string} */ +/// {{Optional|Supply translation only if your language requires it. Most do not.}} /// block text - Text that should go after the rightmost block/dropdown when /// [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text /// extracting a region of text]. In most languages, this will be the empty string. @@ -1115,7 +1117,7 @@ Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP = 'Add an item to the list.'; /// {{Optional}} url - Information about [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. Blockly.Msg.LISTS_REPEAT_HELPURL = 'https://github.com/google/blockly/wiki/Lists#create-list-with'; /** @type {string} */ -/// {{Optional}} url - See [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. +/// tooltip - See [https://github.com/google/blockly/wiki/Lists#create-list-with creating a list with multiple copies of a single item]. Blockly.Msg.LISTS_REPEAT_TOOLTIP = 'Creates a list consisting of the given value repeated the specified number of times.'; /** @type {string} */ /// block text - See [https://github.com/google/blockly/wiki/Lists#create-list-with @@ -1220,6 +1222,7 @@ Blockly.Msg.LISTS_GET_INDEX_LAST = 'last'; /// [[File:Blockly-list-get-item.png]] Blockly.Msg.LISTS_GET_INDEX_RANDOM = 'random'; /** @type {string} */ +/// {{Optional|Supply translation only if your language requires it. Most do not.}} /// block text - Text that should go after the rightmost block/dropdown when /// [https://github.com/google/blockly/wiki/Lists#getting-a-single-item /// accessing an item from a list]. In most languages, this will be the empty string. @@ -1356,6 +1359,7 @@ Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END = 'to # from end'; /// [[File:Blockly-get-sublist.png]] Blockly.Msg.LISTS_GET_SUBLIST_END_LAST = 'to last'; /** @type {string} */ +/// {{Optional|Supply translation only if your language requires it. Most do not.}} /// block text - This appears in the rightmost position ("tail") of the /// sublist block, as described at /// [https://github.com/google/blockly/wiki/Lists#getting-a-sublist @@ -1426,6 +1430,7 @@ Blockly.Msg.LISTS_REVERSE_MESSAGE0 = 'reverse %1'; Blockly.Msg.LISTS_REVERSE_TOOLTIP = 'Reverse a copy of a list.'; /** @type {string} */ +/// {{Optional|Supply translation only if your language requires it. Most do not.}} /// grammar - Text that follows an ordinal number (a number that indicates /// position relative to other numbers). In most languages, such text appears /// before the number, so this should be blank. An exception is Hungarian. @@ -1484,6 +1489,7 @@ Blockly.Msg.PROCEDURES_BEFORE_PARAMS = 'with:'; /// function with parameters]. Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS = 'with:'; /** @type {string} */ +/// {{Optional|Supply translation only if your language requires it. Most do not.}} /// block text - This appears next to the function's "body", the blocks that should be /// run when the function is called, as shown in /// [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample diff --git a/package-lock.json b/package-lock.json index 0b7fcb5b4..acaac9945 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,38 +1,37 @@ { "name": "blockly", - "version": "9.3.3", + "version": "10.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "blockly", - "version": "9.3.3", + "version": "10.0.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "jsdom": "21.1.1" + "jsdom": "22.1.0" }, "devDependencies": { "@blockly/block-test": "^3.0.0", "@blockly/dev-tools": "^5.0.0", "@blockly/theme-modern": "^3.0.0", - "@hyperjump/json-schema": "^0.23.3", - "@microsoft/api-documenter": "^7.19.16", + "@hyperjump/json-schema": "^1.5.0", + "@microsoft/api-documenter": "^7.22.4", "@microsoft/api-extractor": "^7.29.5", "@typescript-eslint/eslint-plugin": "^5.33.1", "@wdio/selenium-standalone-service": "^8.0.2", "async-done": "^2.0.0", "chai": "^4.2.0", - "clang-format": "^1.6.0", "closure-calculate-chunks": "^3.0.2", - "concurrently": "^7.4.0", + "concurrently": "^8.0.1", "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", - "eslint-plugin-jsdoc": "^40.0.0", - "google-closure-compiler": "^20230206.0.0", - "google-closure-deps": "^20230206.0.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-jsdoc": "^46.2.6", + "google-closure-compiler": "^20230502.0.0", + "google-closure-deps": "^20230502.0.0", "gulp": "^4.0.2", - "gulp-clang-format": "^1.0.27", "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", "gulp-header": "^2.0.9", @@ -44,15 +43,14 @@ "gulp-sourcemaps": "^3.0.0", "gulp-umd": "^2.0.0", "http-server": "^14.0.0", - "js-green-licenses": "^4.0.0", "json5": "^2.2.0", "markdown-tables-to-json": "^0.1.7", "mocha": "^10.0.0", - "patch-package": "^6.4.7", + "patch-package": "^7.0.0", + "prettier": "2.8.8", "readline-sync": "^1.4.10", - "rimraf": "^4.0.7", + "rimraf": "^5.0.0", "selenium-standalone": "^8.0.3", - "through2": "^4.0.2", "typescript": "^5.0.2", "webdriverio": "^8.0.5", "yargs": "^17.2.1" @@ -164,10 +162,22 @@ "node": ">=4" } }, + "node_modules/@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@blockly/block-test": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-3.0.8.tgz", - "integrity": "sha512-foW4fVKCrrmeImXNgVJZ+XjMENpOz3z3P/ob1IxixPnQ3QyO2Yjh0rfPDkSteJjto5k9Q+XhcvlW1w42gyjIKg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-3.1.2.tgz", + "integrity": "sha512-a/Y21p6KcZIeRCsfx+Caqw9HEYyfGA1gwwWYi+nJttRpJCO8W0IAG6cMqNRDn0vPCoWdIa8o9xhoMBdyPURU3Q==", "dev": true, "engines": { "node": ">=8.17.0" @@ -177,16 +187,16 @@ } }, "node_modules/@blockly/dev-tools": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-5.2.4.tgz", - "integrity": "sha512-7FF5PByhq9EhKeYty0GFZp8b7l3qWrEIiqyVTgp/xN7iI/9N6scsDVZfCWVDSmZHgiaTHkEcQW/697IRm69HDA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-5.3.1.tgz", + "integrity": "sha512-3xpxjJgv//z7uItocjEvJnkjHQCaCQHCuB0z5P6u9ysQcFaWmSRNcf5rCEjHSVUlQU22pAhhjytpM8mVzszh6w==", "dev": true, "dependencies": { - "@blockly/block-test": "^3.0.8", - "@blockly/theme-dark": "^4.0.7", - "@blockly/theme-deuteranopia": "^3.0.7", - "@blockly/theme-highcontrast": "^3.0.7", - "@blockly/theme-tritanopia": "^3.0.8", + "@blockly/block-test": "^3.1.2", + "@blockly/theme-dark": "^4.0.10", + "@blockly/theme-deuteranopia": "^3.0.10", + "@blockly/theme-highcontrast": "^3.0.10", + "@blockly/theme-tritanopia": "^3.0.11", "chai": "^4.2.0", "dat.gui": "^0.7.7", "lodash.assign": "^4.2.0", @@ -202,9 +212,9 @@ } }, "node_modules/@blockly/theme-dark": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@blockly/theme-dark/-/theme-dark-4.0.7.tgz", - "integrity": "sha512-NegmW6DxvDISxonJ8gtp/CGxYZmtNYyjR2t7V5GzwnZhU1QV1aZVJk49f3CbFftQm3W93wHZ9HQn80oTbOCbiQ==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-dark/-/theme-dark-4.0.10.tgz", + "integrity": "sha512-deoNn1SsjEVAAXjOrQag/s5X49DsHSID4McwvPLl6lRh0C8C0G9zRSEF90n/mqlLu855Y4xnfzxJWxTdTKUikQ==", "dev": true, "engines": { "node": ">=8.17.0" @@ -214,9 +224,9 @@ } }, "node_modules/@blockly/theme-deuteranopia": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@blockly/theme-deuteranopia/-/theme-deuteranopia-3.0.7.tgz", - "integrity": "sha512-51NF9RqqiskCefMPwMO9JS5l+Q1ubyryx5XUwNV7Dl8LljmyaQzBy2xu6MVIG/yZDY1qR7oS2scsyOffb++oFQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-deuteranopia/-/theme-deuteranopia-3.0.10.tgz", + "integrity": "sha512-m+RxF+DlP+BA5FPQYunPWoFXGlfspTqF97uGkkA0RK8P5UadXbWf4XQAscoMX96zjyfHME8GyQiwmdCcLHm9OQ==", "dev": true, "engines": { "node": ">=8.17.0" @@ -226,9 +236,9 @@ } }, "node_modules/@blockly/theme-highcontrast": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@blockly/theme-highcontrast/-/theme-highcontrast-3.0.7.tgz", - "integrity": "sha512-uwpDXhcXXaXxWT1xkWECUBD0ao1+hzK9iLf5FWktnSqfyj6galLVjB2mxRb3ZTaVuhjvw7/qhjYzVPqzL2NKgg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-highcontrast/-/theme-highcontrast-3.0.10.tgz", + "integrity": "sha512-4PSJgObr/F57YsE54Tg2y6+qVvJoRrwGysP0K7fZhGKB6XMVEmwbzG6pds3SE9y7AHtaQ3lQOauojciwOUHUTg==", "dev": true, "engines": { "node": ">=8.17.0" @@ -238,9 +248,9 @@ } }, "node_modules/@blockly/theme-modern": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-3.0.6.tgz", - "integrity": "sha512-A59AMr3hZRHugC3qGSpZ2gtGlQkuWtj2qj7B5jLbVoSKNJuPRJt1DKQElvriev5GhangjSmrQZULPhAAIEXLsQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-3.0.10.tgz", + "integrity": "sha512-02px8PlvC3ZcVSZGLf7fYhni6xohoMg3P56ZSaP/Js4L+TDD4tzHDK9vE8BpYmmV0ZmwReKiN/fazrUwhYNSFA==", "dev": true, "engines": { "node": ">=8.17.0" @@ -250,9 +260,9 @@ } }, "node_modules/@blockly/theme-tritanopia": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@blockly/theme-tritanopia/-/theme-tritanopia-3.0.8.tgz", - "integrity": "sha512-17n3LAFwOJHkBeeVUYoRZ+ATSDpmOQuP41n9ZxDb0N3VbnjkFEVRT+saG3wQgcHD6F7KoVHkK2GChbmcYrU+Ig==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@blockly/theme-tritanopia/-/theme-tritanopia-3.0.11.tgz", + "integrity": "sha512-bGtOpYglDx50yxOAZiIcgnbtWQZaSSlfMTALjkw/1+sxa0kwq2Oxws02uQ4LMni/8o9jY/dvoEGLStXFby0FzA==", "dev": true, "engines": { "node": ">=8.17.0" @@ -262,17 +272,17 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", - "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz", + "integrity": "sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg==", "dev": true, "dependencies": { "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.1.0" + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "node": ">=16" } }, "node_modules/@eslint-community/eslint-utils": { @@ -300,14 +310,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -322,6 +332,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -350,6 +369,20 @@ "node": ">=0.4.0" } }, + "node_modules/@gulp-sourcemaps/identity-map/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@gulp-sourcemaps/identity-map/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -394,6 +427,21 @@ "node": ">=0.10.0" } }, + "node_modules/@gulp-sourcemaps/map-sources/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -405,9 +453,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -437,28 +485,13 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@hyperjump/json": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@hyperjump/json/-/json-0.1.0.tgz", - "integrity": "sha512-jWsAOHjweWhi0UEBCN57YZzyTt76Z6Fm/OJXOfNBJbEZt569AcTRsjv6Dqj5t4gQhW9td72oquiyaVp9oHbhBQ==", - "dev": true, - "dependencies": { - "@hyperjump/json-pointer": "^0.9.2", - "moo": "^0.5.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jdesrosiers" - } - }, "node_modules/@hyperjump/json-pointer": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-0.9.6.tgz", - "integrity": "sha512-3szMJLfz+1wtfPHnGi1sHzwFfFdZqIZLCCYtaD47vLZMAQCbtoBRVZn44jJgIQ6v37+8fom5rsxSSIMKWi0zbg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-1.0.1.tgz", + "integrity": "sha512-vV2pSc7JCwbKEMzh8kr/ICZdO+UZbA3aZ7N8t7leDi9cduWKa9yoP5LS04LnsbErlPbUNHvWBFlbTaR/o/uf7A==", "dev": true, - "hasInstallScript": true, "dependencies": { - "just-curry-it": "^5.2.1" + "just-curry-it": "^5.3.0" }, "funding": { "type": "github", @@ -466,34 +499,22 @@ } }, "node_modules/@hyperjump/json-schema": { - "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-0.23.3.tgz", - "integrity": "sha512-3YqAQpYY1U16SeP+LfMzBySqW5FHF9gZcOMKeeRywJkowsZ1JuOOiYCmYXptlRwXSB1jVa9ITC0lvfO84OaSQQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-1.5.1.tgz", + "integrity": "sha512-PJhd73x9eXgG2+U1W3uXU4KKHjw7xiar2OVpygpiD5Ky0OBu/b4qSMLgH1JX9Wg6FCaKy9Xpt+0zXpgLRQc6dw==", "dev": true, - "hasInstallScript": true, "dependencies": { - "@hyperjump/json-schema-core": "^0.28.0", - "fastest-stable-stringify": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jdesrosiers" - } - }, - "node_modules/@hyperjump/json-schema-core": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@hyperjump/json-schema-core/-/json-schema-core-0.28.5.tgz", - "integrity": "sha512-+f5P3oHYCQru3s+Ha+E10rIyEvyK0Hfa2oj3+cDoGaVMbT4Jg5TgCoIM7B5rl3t3KRA7EOmrLjKFGeLi5yd1pg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@hyperjump/json": "^0.1.0", - "@hyperjump/json-pointer": "^0.9.4", - "@hyperjump/pact": "^0.2.3", + "@hyperjump/json-pointer": "^1.0.0", + "@hyperjump/pact": "^1.0.0", + "@hyperjump/uri": "^1.2.0", "content-type": "^1.0.4", - "node-fetch": "^2.6.5", - "pubsub-js": "^1.9.4", - "uri-js": "^4.4.1" + "fastest-stable-stringify": "^2.0.2", + "just-curry-it": "^5.3.0", + "undici": "^5.19.1", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" }, "funding": { "type": "github", @@ -501,38 +522,137 @@ } }, "node_modules/@hyperjump/pact": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-0.2.4.tgz", - "integrity": "sha512-BGmyLaUSCMVyHrwXr67rMxgiQHPHwcmVCjROoY8q232EpMz9d9aFCkgGhdx//yEfHM7zgsm0zZ8RD/F89uPySg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-1.0.0.tgz", + "integrity": "sha512-d4YYXhVictDbkhjWbRA+Qk8PiufDTSRZg7tWMU07sl0wA/rh9AtL0cPJdpxSsjtDVDBlNSjsOr+RTKDU2IKqEA==", "dev": true, - "hasInstallScript": true, "dependencies": { - "just-curry-it": "^3.1.0" + "just-curry-it": "^5.3.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/jdesrosiers" } }, - "node_modules/@hyperjump/pact/node_modules/just-curry-it": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-3.2.1.tgz", - "integrity": "sha512-Q8206k8pTY7krW32cdmPsP+DqqLgWx/hYPSj9/+7SYqSqz7UuwPbfSe07lQtvuuaVyiSJveXk0E5RydOuWwsEg==", - "dev": true + "node_modules/@hyperjump/uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hyperjump/uri/-/uri-1.2.0.tgz", + "integrity": "sha512-v/OE8Kg0xdd1wYRjyAI8zPxQEAgWuhqSy5mJm0/FAIUdN6S6b75DBUSl2J3ps6QSCID3fnjXqJyevrOOH67YAA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } }, - "node_modules/@microsoft/api-documenter": { - "version": "7.19.26", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.19.26.tgz", - "integrity": "sha512-sFhYmO8k6CMFJ20D/LP1B7GdH+JfwmSKO/xTXnm63WA3+AX7g94G4TlKlc1FXdHFS2qhHnlm4qZUD3fgfs1vqg==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.25.3", + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@microsoft/api-documenter": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.22.20.tgz", + "integrity": "sha512-jqb4qRHWzF1bykWroZVdk7VjeRdcTiNd0Qz7IhMGWEx5jwN2upyjpDYBvDHLkoFn8uKiNqveWz7MAHY/4MOFbw==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor-model": "7.27.3", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "3.53.3", - "@rushstack/ts-command-line": "4.13.1", + "@rushstack/node-core-library": "3.59.4", + "@rushstack/ts-command-line": "4.15.1", "colors": "~1.2.1", "js-yaml": "~3.13.1", - "resolve": "~1.17.0" + "resolve": "~1.22.1" }, "bin": { "api-documenter": "bin/api-documenter" @@ -560,67 +680,55 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@microsoft/api-documenter/node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@microsoft/api-extractor": { - "version": "7.34.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.34.4.tgz", - "integrity": "sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==", + "version": "7.35.4", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.35.4.tgz", + "integrity": "sha512-E/DIIlgu1ZW+AD+Y0UuVe/30rO+Km0CRkDU1aOKQntwKtv/+FodtRAUvzU/vAxq5lQHSsy6jJErLiXR6G3fupA==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.26.4", + "@microsoft/api-extractor-model": "7.27.3", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2", - "@rushstack/rig-package": "0.3.18", - "@rushstack/ts-command-line": "4.13.2", + "@rushstack/node-core-library": "3.59.4", + "@rushstack/rig-package": "0.3.21", + "@rushstack/ts-command-line": "4.15.1", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.22.1", "semver": "~7.3.0", "source-map": "~0.6.1", - "typescript": "~4.8.4" + "typescript": "~5.0.4" }, "bin": { "api-extractor": "bin/api-extractor" } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.25.3.tgz", - "integrity": "sha512-WWxBUq77p2iZ+5VF7Nmrm3y/UtqCh5bYV8ii3khwq3w99+fXWpvfsAhgSLsC7k8XDQc6De4ssMxH6He/qe1pzg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.27.3.tgz", + "integrity": "sha512-fSFvw7otYHduOkyshjTbapKKgwF8bgquVHvgF8VgeKtMYvqXkoaj7W6VcM7PNY7E2bbblhUgC4XNdqZLD4SJGw==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.53.3" + "@rushstack/node-core-library": "3.59.4" } }, "node_modules/@microsoft/api-extractor/node_modules/@microsoft/api-extractor-model": { - "version": "7.26.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.26.4.tgz", - "integrity": "sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.27.3.tgz", + "integrity": "sha512-fSFvw7otYHduOkyshjTbapKKgwF8bgquVHvgF8VgeKtMYvqXkoaj7W6VcM7PNY7E2bbblhUgC4XNdqZLD4SJGw==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2" + "@rushstack/node-core-library": "3.59.4" } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/node-core-library": { - "version": "3.55.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.55.2.tgz", - "integrity": "sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==", + "version": "3.59.4", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.59.4.tgz", + "integrity": "sha512-YAKJDC6Mz/KA1D7bvB88WaRX3knt/ZuLzkRu5G9QADGSjLtvTWzCNCytRF2PCSaaHOZaZsWul4F1KQdgFgUDqA==", "dev": true, "dependencies": { "colors": "~1.2.1", @@ -641,9 +749,9 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/ts-command-line": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.2.tgz", - "integrity": "sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.15.1.tgz", + "integrity": "sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==", "dev": true, "dependencies": { "@types/argparse": "1.0.38", @@ -694,16 +802,16 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/@microsoft/api-extractor/node_modules/universalify": { @@ -781,28 +889,102 @@ "node": ">= 8" } }, - "node_modules/@rushstack/node-core-library": { - "version": "3.53.3", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.53.3.tgz", - "integrity": "sha512-H0+T5koi5MFhJUd5ND3dI3bwLhvlABetARl78L3lWftJVQEPyzcgTStvTTRiIM5mCltyTM8VYm6BuCtNUuxD0Q==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rushstack/node-core-library": { + "version": "3.59.4", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.59.4.tgz", + "integrity": "sha512-YAKJDC6Mz/KA1D7bvB88WaRX3knt/ZuLzkRu5G9QADGSjLtvTWzCNCytRF2PCSaaHOZaZsWul4F1KQdgFgUDqA==", "dev": true, "dependencies": { - "@types/node": "12.20.24", "colors": "~1.2.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", "jju": "~1.4.0", - "resolve": "~1.17.0", + "resolve": "~1.22.1", "semver": "~7.3.0", "z-schema": "~5.0.2" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@rushstack/node-core-library/node_modules/@types/node": { - "version": "12.20.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", - "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", - "dev": true - }, "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -826,18 +1008,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/@rushstack/node-core-library/node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@rushstack/node-core-library/node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -848,9 +1018,9 @@ } }, "node_modules/@rushstack/rig-package": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.18.tgz", - "integrity": "sha512-SGEwNTwNq9bI3pkdd01yCaH+gAsHqs0uxfGvtw9b0LJXH52qooWXnrFTRRLG1aL9pf+M2CARdrA9HLHJys3jiQ==", + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.21.tgz", + "integrity": "sha512-6KPBuZYP/b9U0Qwy1J4vjYtXvLavdmVT7mMelErfqqZ3P/ywoxlFITGr9ZbqD1zmfIVrIfC2jrM6gfm7OHPRhQ==", "dev": true, "dependencies": { "resolve": "~1.22.1", @@ -858,9 +1028,9 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.1.tgz", - "integrity": "sha512-UTQMRyy/jH1IS2U+6pyzyn9xQ2iMcoUKkTcZUzOP/aaMiKlWLwCTDiBVwhw/M1crDx6apF9CwyjuWO9r1SBdJQ==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.15.1.tgz", + "integrity": "sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==", "dev": true, "dependencies": { "@types/argparse": "1.0.38", @@ -991,9 +1161,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "node_modules/@types/keyv": { @@ -1005,16 +1175,10 @@ "@types/node": "*" } }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, "node_modules/@types/node": { - "version": "18.11.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", - "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", + "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", "dev": true }, "node_modules/@types/normalize-package-data": { @@ -1033,9 +1197,9 @@ } }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, "node_modules/@types/vinyl": { @@ -1055,9 +1219,9 @@ "dev": true }, "node_modules/@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", "dev": true, "dependencies": { "@types/node": "*" @@ -1074,15 +1238,15 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz", - "integrity": "sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", + "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/type-utils": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/type-utils": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -1108,13 +1272,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", + "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1125,9 +1289,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1138,12 +1302,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -1201,13 +1365,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz", - "integrity": "sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", + "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -1228,9 +1392,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1241,13 +1405,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", + "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1268,12 +1432,12 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -1327,17 +1491,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.55.0.tgz", - "integrity": "sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", + "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -1353,13 +1517,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", + "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1370,9 +1534,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1383,13 +1547,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", + "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1410,12 +1574,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -1458,18 +1622,18 @@ } }, "node_modules/@wdio/config": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.3.11.tgz", - "integrity": "sha512-sZ1SZkBEZWqSDHqrQxspsrc+OBqf3qyx2c4gVvAovOd1hnT4EcWhlJu1Asp3H0Mti115XstwQabOW4sSsdmVCw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.11.0.tgz", + "integrity": "sha512-nBQXsXbPCjddtI/3rAK5yFs3eD3f0T3lZMivweTkLLR7GKBxGjiFoBjXtfqUrHJYa+2uwfXrwxo6y+dA6fVbuw==", "dev": true, "dependencies": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", "decamelize": "^6.0.0", - "deepmerge-ts": "^4.2.2", - "glob": "^8.0.3", - "import-meta-resolve": "^2.1.0", + "deepmerge-ts": "^5.0.0", + "glob": "^10.2.2", + "import-meta-resolve": "^3.0.0", "read-pkg-up": "^9.1.0" }, "engines": { @@ -1514,19 +1678,22 @@ } }, "node_modules/@wdio/config/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", + "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1548,15 +1715,18 @@ } }, "node_modules/@wdio/config/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@wdio/config/node_modules/p-limit": { @@ -1646,20 +1816,32 @@ } }, "node_modules/@wdio/logger": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.1.0.tgz", - "integrity": "sha512-QRC5b7FF4JIYUCqggnVA0sZ80TwIUFN9JyBSbuGuMxaSLSLujSo7WfuSrnQXVvsRbnJ16wWwJWYigfLkxOW86Q==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz", + "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==", "dev": true, "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "strip-ansi": "^7.1.0" }, "engines": { "node": "^16.13 || >=18" } }, + "node_modules/@wdio/logger/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/@wdio/logger/node_modules/chalk": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", @@ -1672,276 +1854,76 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@wdio/logger/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@wdio/protocols": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.3.11.tgz", - "integrity": "sha512-EXGuZC4Nvl8QPT6gQ9tpeH+TL9P5oRdQofaJA893OX37gU2OWiSNyA7AHr/0/UIHWFax4udpVQ6syTQsy6uWWA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.11.0.tgz", + "integrity": "sha512-eXTMYt/XoaX53H/Q2qmsn1uWthIC5aSTGtX9YyXD/AkagG2hXeX3lLmzNWBaSIvKR+vWXRYbg3Y/7IvL2s25Wg==", "dev": true }, "node_modules/@wdio/repl": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.1.0.tgz", - "integrity": "sha512-96G4TzbYnRf95+GURo15FYt6iTYq85nbWU6YQedLRAV15RfSp4foKTbAnq++bKKMALNL6gdzTc+HGhQr3Q0sQg==", + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.10.1.tgz", + "integrity": "sha512-VZ1WFHTNKjR8Ga97TtV2SZM6fvRjWbYI2i/f4pJB4PtusorKvONAMJf2LQcUBIyzbVobqr7KSrcjmSwRolI+yw==", "dev": true, "dependencies": { - "@types/node": "^18.0.0" + "@types/node": "^20.1.0" }, "engines": { "node": "^16.13 || >=18" } }, "node_modules/@wdio/selenium-standalone-service": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-8.3.11.tgz", - "integrity": "sha512-gRcsaH5KG49r3r3HCOUjG1Mh+1301YJaKgqPyTulaooH5Ia2boEGeI1ijT7F14PBuiuIuha6i3us7TPecgeuVw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-8.11.0.tgz", + "integrity": "sha512-Xve5TjSKtQQZI39O0b+Iek/XG5IZgUdR5Dgp04kx3lQFfqZ5AmE0+1BUP08jbVBiS8R5QYUoHZ3X02bo/k9TFA==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.10.4", "selenium-standalone": "^8.2.1" }, "engines": { "node": "^16.13 || >=18" } }, - "node_modules/@wdio/selenium-standalone-service/node_modules/@wdio/config": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.3.11.tgz", - "integrity": "sha512-sZ1SZkBEZWqSDHqrQxspsrc+OBqf3qyx2c4gVvAovOd1hnT4EcWhlJu1Asp3H0Mti115XstwQabOW4sSsdmVCw==", - "dev": true, - "dependencies": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^4.2.2", - "glob": "^8.0.3", - "import-meta-resolve": "^2.1.0", - "read-pkg-up": "^9.1.0" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/@wdio/types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.3.0.tgz", - "integrity": "sha512-TTs3ETVOJtooTIY/u2+feeBnMBx2Hb4SEItN31r0pFncn37rnIZXE/buywLNf5wvZW9ukxoCup5hCnohOR27eQ==", - "dev": true, - "dependencies": { - "@types/node": "^18.0.0" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/@wdio/utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.3.0.tgz", - "integrity": "sha512-BbgzxAu4AN99l9hwaRUvkvBJLeIxON7F6ts+vq4LASRiGVRErrwYGeO9H3ffYgpCAaYSvXnw2GtOf2SQGaPoLA==", - "dev": true, - "dependencies": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "import-meta-resolve": "^2.2.0", - "p-iteration": "^1.1.8" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", - "dev": true, - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/selenium-standalone-service/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wdio/types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.3.0.tgz", - "integrity": "sha512-TTs3ETVOJtooTIY/u2+feeBnMBx2Hb4SEItN31r0pFncn37rnIZXE/buywLNf5wvZW9ukxoCup5hCnohOR27eQ==", + "version": "8.10.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.10.4.tgz", + "integrity": "sha512-aLJ1QQW+hhALeRK3bvMLjIrlUVyhOs3Od+91pR4Z4pLwyeNG1bJZCJRD5bAJK/mm7CnFa0NsdixPS9jJxZcRrw==", "dev": true, "dependencies": { - "@types/node": "^18.0.0" + "@types/node": "^20.1.0" }, "engines": { "node": "^16.13 || >=18" } }, "node_modules/@wdio/utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.3.0.tgz", - "integrity": "sha512-BbgzxAu4AN99l9hwaRUvkvBJLeIxON7F6ts+vq4LASRiGVRErrwYGeO9H3ffYgpCAaYSvXnw2GtOf2SQGaPoLA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.11.0.tgz", + "integrity": "sha512-XBl1zalk5UPu8QKZ7LZIA82Ad363fpNHZHP5uI5OxUFnk4ZPWgY9eCWpeD+4f9a0DS0w2Dro15E4PORNX84pIw==", "dev": true, "dependencies": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "import-meta-resolve": "^2.2.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.10.4", + "import-meta-resolve": "^3.0.0", "p-iteration": "^1.1.8" }, "engines": { @@ -1975,6 +1957,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -1986,6 +1969,8 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "peer": true, "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -2004,6 +1989,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -2159,6 +2145,21 @@ "node": ">= 6" } }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/archiver/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -2179,6 +2180,15 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2245,15 +2255,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -2263,15 +2264,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -2347,15 +2339,6 @@ "node": ">=8" } }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -2365,15 +2348,6 @@ "node": ">=0.10.0" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -2580,15 +2554,6 @@ "node": ">= 0.8" } }, - "node_modules/beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2777,11 +2742,29 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } }, "node_modules/bytes": { "version": "3.1.2", @@ -2861,41 +2844,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -2973,9 +2921,9 @@ "dev": true }, "node_modules/chrome-launcher": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.1.tgz", - "integrity": "sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "dev": true, "dependencies": { "@types/node": "*", @@ -2990,26 +2938,31 @@ "node": ">=12.13.0" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/clang-format": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.8.0.tgz", - "integrity": "sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw==", + "node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", "dev": true, "dependencies": { - "async": "^3.2.3", - "glob": "^7.0.0", - "resolve": "^1.1.6" + "mitt": "3.0.0" }, - "bin": { - "check-clang-format": "bin/check-clang-format.js", - "clang-format": "index.js", - "git-clang-format": "bin/git-clang-format" + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" } }, "node_modules/class-utils": { @@ -3101,20 +3054,6 @@ "node": ">=0.10.0" } }, - "node_modules/cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -3170,6 +3109,21 @@ "readable-stream": "^2.3.5" } }, + "node_modules/cloneable-readable/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/closure-calculate-chunks": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/closure-calculate-chunks/-/closure-calculate-chunks-3.1.1.tgz", @@ -3376,6 +3330,21 @@ "typedarray": "^0.0.6" } }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", @@ -3395,27 +3364,27 @@ } }, "node_modules/concurrently": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", - "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.0.tgz", + "integrity": "sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA==", "dev": true, "dependencies": { - "chalk": "^4.1.0", - "date-fns": "^2.29.1", + "chalk": "^4.1.2", + "date-fns": "^2.30.0", "lodash": "^4.17.21", - "rxjs": "^7.0.0", - "shell-quote": "^1.7.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", "tree-kill": "^1.2.2", - "yargs": "^17.3.1" + "yargs": "^17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^12.20.0 || ^14.13.0 || >=16.0.0" + "node": "^14.13.0 || >=16.0.0" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" @@ -3437,9 +3406,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "engines": { "node": ">= 0.6" @@ -3532,12 +3501,12 @@ } }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, "dependencies": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.11" } }, "node_modules/cross-spawn": { @@ -3645,10 +3614,13 @@ } }, "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, "engines": { "node": ">=0.11" }, @@ -3657,15 +3629,6 @@ "url": "https://opencollective.com/date-fns" } }, - "node_modules/dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3711,28 +3674,6 @@ "node": ">=0.10.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -3786,27 +3727,19 @@ "node": ">=6" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/deepmerge-ts": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-4.2.2.tgz", - "integrity": "sha512-Ka3Kb21tiWjvQvS9U+1Dx+aqFAHsdTnMdYptLTmC2VAmDFMugWMY1e15aTODstipmCun8iNuqeSfcx6rsUUk0Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", "dev": true, "engines": { - "node": ">=12.4.0" + "node": ">=16.0.0" } }, "node_modules/default-compare": { @@ -3891,21 +3824,21 @@ } }, "node_modules/devtools": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.3.11.tgz", - "integrity": "sha512-Wevpd4fZqCveNJv4qSpT+dER5UARLVffpeSEBLBqtrvrNZ6e39x2jWgb838Kf6okuIHWWBXq0IJuRpMda50mXw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.11.0.tgz", + "integrity": "sha512-j1wXFQyjswJ6doAV1+h4Bxl8+Oeb8SMpWTpBVa0DurGsxfft8sU2OhDlMo5tx/zbX82X5sGyJDMnKHqBJ2XRvQ==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/protocols": "8.3.11", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.11.0", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", "chrome-launcher": "^0.15.0", "edge-paths": "^3.0.5", - "import-meta-resolve": "^2.1.0", - "puppeteer-core": "19.7.1", + "import-meta-resolve": "^3.0.0", + "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", "uuid": "^9.0.0", @@ -3916,15 +3849,15 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1103684", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1103684.tgz", - "integrity": "sha512-44Qr4zFQkzW8r4WdDOSuQoxIzPDczY/K1RDfyxzEsiG2nSzbTojDW3becX5+HrDw3gENG8jY6ffbHZ2/Ix5LSA==", + "version": "0.0.1152884", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1152884.tgz", + "integrity": "sha512-9eP6OmCoU1cWArpXLuzyZQcBJ2PkINOh8Nwx8W5i8u6NDigDE5/mPlLLBAfshwn5YVvIz6ZQ9jbs0PZvKGccdQ==", "dev": true }, "node_modules/devtools/node_modules/which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", "dev": true, "dependencies": { "isexe": "^2.0.0" @@ -3980,45 +3913,6 @@ "node": ">=12" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "dependencies": { - "readable-stream": "~1.1.9" - } - }, - "node_modules/duplexer2/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, "node_modules/duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -4031,6 +3925,21 @@ "stream-shift": "^1.0.0" } }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/each-props": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", @@ -4053,6 +3962,12 @@ "node": ">=0.10.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/edge-paths": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", @@ -4173,6 +4088,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "peer": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -4194,6 +4111,8 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -4202,6 +4121,8 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "peer": true, "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -4214,6 +4135,8 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "peer": true, "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -4230,6 +4153,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -4238,7 +4163,9 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4247,6 +4174,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "peer": true, "dependencies": { "prelude-ls": "~1.1.2" }, @@ -4255,13 +4184,16 @@ } }, "node_modules/eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", - "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -4270,24 +4202,22 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -4295,7 +4225,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -4322,31 +4251,60 @@ "eslint": ">=5.16.0" } }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-plugin-jsdoc": { - "version": "40.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.0.0.tgz", - "integrity": "sha512-LOPyIu1vAVvGPkye3ci0moj0iNf3f8bmin6do2DYDj+77NRXWnkmhKRy8swWsatUs3mB5jYPWPUsFg9pyfEiyA==", + "version": "46.2.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.6.tgz", + "integrity": "sha512-zIaK3zbSrKuH12bP+SPybPgcHSM6MFzh3HFeaODzmsF1N8C1l8dzJ22cW1aq4g0+nayU1VMjmNf7hg0dpShLrA==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.36.1", + "@es-joy/jsdoccomment": "~0.39.4", + "are-docs-informative": "^0.0.2", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", - "esquery": "^1.4.0", - "semver": "^7.3.8", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.5.1", "spdx-expression-parse": "^3.0.1" }, "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" + "node": ">=16" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -4354,6 +4312,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-scope/node_modules/estraverse": { @@ -4365,40 +4326,16 @@ "node": ">=4.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/glob-parent": { @@ -4414,14 +4351,14 @@ } }, "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4434,6 +4371,7 @@ "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, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -4443,9 +4381,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -4497,6 +4435,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -4511,21 +4450,6 @@ "es5-ext": "~0.10.14" } }, - "node_modules/event-stream": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", - "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" - } - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -4870,7 +4794,8 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "node_modules/fastest-stable-stringify": { "version": "2.0.2", @@ -5070,6 +4995,21 @@ "readable-stream": "^2.3.6" } }, + "node_modules/flush-write-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/follow-redirects": { "version": "1.14.8", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", @@ -5111,6 +5051,22 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5145,12 +5101,6 @@ "node": ">=0.10.0" } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5184,6 +5134,21 @@ "node": ">= 0.10" } }, + "node_modules/fs-mkdirp-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/fs-mkdirp-stream/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -5220,21 +5185,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "dev": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5366,6 +5316,21 @@ "node": ">=0.10.0" } }, + "node_modules/glob-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/glob-watcher": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", @@ -5563,6 +5528,21 @@ "node": ">=0.10.0" } }, + "node_modules/glob-watcher/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/glob-watcher/node_modules/readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -5633,9 +5613,9 @@ } }, "node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -5692,13 +5672,13 @@ } }, "node_modules/google-closure-compiler": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230206.0.0.tgz", - "integrity": "sha512-gGscQOcO/75AlHyw78v87u0nGKJHWqOrQ224Ks91HH1iISgF+xZ8GYosU/8s5VD66x3VD0tJKXM2rIoGOA1ycA==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230502.0.0.tgz", + "integrity": "sha512-C2WZkuRnXpNjU2nc0W/Cgxm6t2VlwEyUJOTaGHaLr6qZCXK0L1uhOneKWN2X7AORKdzyLW6Tq8ONxRc7eODGJg==", "dev": true, "dependencies": { "chalk": "4.x", - "google-closure-compiler-java": "^20230206.0.0", + "google-closure-compiler-java": "^20230502.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" @@ -5710,21 +5690,21 @@ "node": ">=10" }, "optionalDependencies": { - "google-closure-compiler-linux": "^20230206.0.0", - "google-closure-compiler-osx": "^20230206.0.0", - "google-closure-compiler-windows": "^20230206.0.0" + "google-closure-compiler-linux": "^20230502.0.0", + "google-closure-compiler-osx": "^20230502.0.0", + "google-closure-compiler-windows": "^20230502.0.0" } }, "node_modules/google-closure-compiler-java": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230206.0.0.tgz", - "integrity": "sha512-OcnDf29yx4JNU13HpptADI2ckl9hEchktSHs2XSLQ/xStUAJQGQOl96to5IYh2VuFgn3Ssaw6M3c6At2pJr7wQ==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230502.0.0.tgz", + "integrity": "sha512-2nMQPQz2ppU9jvHhz2zpUP5jBDAqZp4gFVOEvirEyfUuLLkHwAvU2Tl1c7xaKX+Z4uMxpxttxcwdIjQhV2g8eQ==", "dev": true }, "node_modules/google-closure-compiler-linux": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230206.0.0.tgz", - "integrity": "sha512-06N6w2elsnZMMA4Gf/vN2A3XzWvu+gUTrBczaw0KQL48GgdLq6OgAXrcopbGdi/K8Gz1WAcG0qf2ccG8dSqYNg==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230502.0.0.tgz", + "integrity": "sha512-4NDgPKJXQHUxEyJoVFPVMQPJs5at7ThOXa9u3+9UeYk2K+vtW5wVZlmW07VOy8Mk/O/n2dp+Vl+wuE35BIiHAA==", "cpu": [ "x32", "x64" @@ -5736,9 +5716,9 @@ ] }, "node_modules/google-closure-compiler-osx": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230206.0.0.tgz", - "integrity": "sha512-lJ/Y4HTk+KdL6PhLmmalP/3DdzGK0mS0+htuFP6y4t9+QXiUKnpHWx/VDQ3Fwm2fWEzqDxfhX3R+wC9lBvFiAg==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230502.0.0.tgz", + "integrity": "sha512-jB13dcbu8O02cG3JcCCVZku1oI0ZirJc/Sr9xcGHY5MMyw3qEMlXb3IU97W6UXLcg2wCRawMWadOwL9K4L9lfQ==", "cpu": [ "x32", "x64", @@ -5751,9 +5731,9 @@ ] }, "node_modules/google-closure-compiler-windows": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230206.0.0.tgz", - "integrity": "sha512-4KPr7XPiOs8g4Ao3T+70egf14avCEne26XF4Mur4Fg5511ym1uEN+NlEyjBOAmfUFfaA7BYDsA8iBzDIetKrnw==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230502.0.0.tgz", + "integrity": "sha512-wW5/liBxejvUViiBNo8/C9Vnhw+Lm+n3RdfE4spNkmdH9bcpKM+KQBLrPPakW17P3HbAPOPZ0L1RsrmyLYA5Cg==", "cpu": [ "x32", "x64" @@ -5765,9 +5745,9 @@ ] }, "node_modules/google-closure-deps": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230206.0.0.tgz", - "integrity": "sha512-MT0JDygFjCTavsOGfRRE1o3teF/ZZD1a8NbeVlGW0oM3OTY2xkOpoPMvSely4khfFV4i4qqjmlMvtwD8cT7adg==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230502.0.0.tgz", + "integrity": "sha512-d74eiwhPn5E9Uq9wZ8qYvO2FRGf/anI6q/Z2JsyNbsQG7lWw9xq+F0gwKSdnPOwR1MODOD2Pe3mnjD+I5Qntcg==", "dev": true, "dependencies": { "minimatch": "^3.0.4", @@ -5842,6 +5822,12 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -5869,30 +5855,6 @@ "node": ">= 0.10" } }, - "node_modules/gulp-clang-format": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/gulp-clang-format/-/gulp-clang-format-1.0.27.tgz", - "integrity": "sha512-Jj4PGuNXKdqVCh9fijvL7wdzma5TQRJz1vv8FjOjnSkfq3s/mvbdE/jq+5HG1c/q+jcYkXTEGkYT3CrdnJOLaQ==", - "dev": true, - "dependencies": { - "clang-format": "^1.0.32", - "fancy-log": "^1.3.2", - "gulp-diff": "^1.0.0", - "plugin-error": "^1.0.1", - "stream-combiner2": "^1.1.1", - "through2": "^2.0.3" - } - }, - "node_modules/gulp-clang-format/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/gulp-cli": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", @@ -6144,6 +6106,21 @@ "node": ">= 0.10" } }, + "node_modules/gulp-concat/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/gulp-concat/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6154,38 +6131,6 @@ "xtend": "~4.0.1" } }, - "node_modules/gulp-diff": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-diff/-/gulp-diff-1.0.0.tgz", - "integrity": "sha1-EBsjcS3WsQe9B9BauI6jrEhf7Xc=", - "dev": true, - "dependencies": { - "cli-color": "^1.0.0", - "diff": "^2.0.2", - "event-stream": "^3.1.5", - "gulp-util": "^3.0.6", - "through2": "^2.0.0" - } - }, - "node_modules/gulp-diff/node_modules/diff": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", - "integrity": "sha512-9wfm3RLzMp/PyTFWuw9liEzdlxsdGixCW0ZTU1XDmtlAkvpVXTPGF8KnfSs0hm3BPbg19OrUPPsRkHXoREpP1g==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/gulp-diff/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/gulp-gzip": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/gulp-gzip/-/gulp-gzip-1.4.2.tgz", @@ -6215,6 +6160,21 @@ "node": ">=0.10.0" } }, + "node_modules/gulp-gzip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/gulp-gzip/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6237,6 +6197,21 @@ "through2": "^2.0.0" } }, + "node_modules/gulp-header/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/gulp-header/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6342,6 +6317,20 @@ "node": ">=8" } }, + "node_modules/gulp-shell/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gulp-shell/node_modules/through2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", @@ -6386,6 +6375,21 @@ "node": ">=0.4.0" } }, + "node_modules/gulp-sourcemaps/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/gulp-sourcemaps/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6416,6 +6420,21 @@ "through2": "^2.0.3" } }, + "node_modules/gulp-umd/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/gulp-umd/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6426,166 +6445,6 @@ "xtend": "~4.0.1" } }, - "node_modules/gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", - "dev": true, - "dependencies": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/gulp-util/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/gulp-util/node_modules/clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "node_modules/gulp-util/node_modules/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, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/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, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/gulp-util/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-util/node_modules/vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "dependencies": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - }, - "engines": { - "node": ">= 0.9" - } - }, "node_modules/gulplog": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", @@ -6598,15 +6457,6 @@ "node": ">= 0.10" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6619,18 +6469,6 @@ "node": ">= 0.4.0" } }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6640,18 +6478,6 @@ "node": ">=8" } }, - "node_modules/has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -6887,9 +6713,9 @@ } }, "node_modules/import-meta-resolve": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.2.0.tgz", - "integrity": "sha512-CpPOtiCHxP9HdtDM5F45tNiAe66Cqlv3f5uHoJjt+KlaLrUh9/Wz9vepADZ78SlqEo62aDWZtj9ydMGXV+CPnw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", + "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", "dev": true, "funding": { "type": "github", @@ -6905,15 +6731,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7012,16 +6829,19 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, "dependencies": { - "ci-info": "^2.0.0" + "builtin-modules": "^3.3.0" }, - "bin": { - "is-ci": "bin.js" + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-core-module": { @@ -7191,15 +7011,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -7241,18 +7052,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", @@ -7350,40 +7149,30 @@ "url": "https://bevry.me/fund" } }, + "node_modules/jackspeak": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz", + "integrity": "sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, - "node_modules/js-green-licenses": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-green-licenses/-/js-green-licenses-4.0.0.tgz", - "integrity": "sha512-kcgTOaZmpDpINcRAOKKhjHtBN6zibMVTC8qfPUOpowQtI/6fUgdmwJLJ0ycCb0pUO3ZYKn++56sy8IlG60p5mg==", - "dev": true, - "dependencies": { - "gaxios": "^5.0.0", - "meow": "^9.0.0", - "npm-package-arg": "^8.0.0", - "package-json": "^7.0.0", - "semver": "^7.3.2", - "spdx-correct": "^3.0.0", - "spdx-satisfies": "^5.0.0", - "strip-json-comments": "^3.0.0" - }, - "bin": { - "jsgl": "build/src/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7403,33 +7192,30 @@ } }, "node_modules/jsdoc-type-pratt-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", - "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true, "engines": { "node": ">=12.0.0" } }, "node_modules/jsdom": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", - "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dependencies": { "abab": "^2.0.6", - "acorn": "^8.8.2", - "acorn-globals": "^7.0.0", "cssstyle": "^3.0.0", "data-urls": "^4.0.0", "decimal.js": "^10.4.3", "domexception": "^4.0.0", - "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", + "nwsapi": "^2.2.4", "parse5": "^7.1.2", "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", @@ -7444,7 +7230,7 @@ "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=14" + "node": ">=16" }, "peerDependencies": { "canvas": "^2.5.0" @@ -7502,26 +7288,6 @@ "node": ">=14" } }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -7571,9 +7337,9 @@ } }, "node_modules/just-curry-it": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.2.1.tgz", - "integrity": "sha512-M8qhhO9WVNc3yZgf3qfiNxMIsQlHqFHJ3vMI8N/rkp852h1utOB/N3ebS8jeXGAwYSbkdd0K6zP9eZneUtjHwA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.3.0.tgz", + "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==", "dev": true }, "node_modules/just-debounce": { @@ -7616,9 +7382,9 @@ } }, "node_modules/ky": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.2.tgz", - "integrity": "sha512-f6oS2rKUcPu5FzdqCDbFpmzis/JlqFZw8uIHm/jf8Kc3vtnW+VDhuashOAKyBZv8bFiZFZUMNxTC0JtahEvujA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", + "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", "dev": true, "engines": { "node": ">=14.16" @@ -7652,6 +7418,21 @@ "node": ">= 0.6.3" } }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -7721,9 +7502,9 @@ } }, "node_modules/lighthouse-logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.3.0.tgz", - "integrity": "sha512-BbqAKApLb9ywUli+0a+PcV04SyJ/N1q/8qgCNe6U97KbPCS1BTksEuHFLYdvc8DltuhfxIUBqDZsC0bBGtl3lA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "dev": true, "dependencies": { "debug": "^2.6.9", @@ -7800,60 +7581,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "node_modules/lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "node_modules/lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "node_modules/lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "node_modules/lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "node_modules/lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "node_modules/lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, "node_modules/lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, "node_modules/lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", @@ -7878,15 +7611,6 @@ "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", "dev": true }, - "node_modules/lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "dependencies": { - "lodash._root": "^3.0.0" - } - }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -7899,18 +7623,6 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "node_modules/lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -7923,17 +7635,6 @@ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", "dev": true }, - "node_modules/lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "dependencies": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "node_modules/lodash.mapvalues": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", @@ -7946,12 +7647,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, "node_modules/lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -8087,18 +7782,6 @@ "node": ">=0.10.0" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -8196,53 +7879,6 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8407,15 +8043,6 @@ "node": ">=4" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8434,28 +8061,20 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, "engines": { - "node": ">= 6" + "node": ">=8" } }, - "node_modules/minimist-options/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -8615,26 +8234,11 @@ "integrity": "sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ==", "dev": true }, - "node_modules/moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "dev": true - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "dependencies": { - "duplexer2": "0.0.2" - } - }, "node_modules/mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", @@ -8712,12 +8316,6 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "node_modules/nise": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", @@ -8732,9 +8330,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -8821,20 +8419,6 @@ "node": ">= 0.10" } }, - "node_modules/npm-package-arg": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", - "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "semver": "^7.3.4", - "validate-npm-package-name": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -8845,9 +8429,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz", + "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -9095,6 +8679,21 @@ "readable-stream": "^2.0.1" } }, + "node_modules/ordered-read-streams/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -9164,33 +8763,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-7.0.0.tgz", - "integrity": "sha512-CHJqc94AA8YfSLHGQT3DbvSIuE12NLFekpM4n7LRrAd3dOJtA911+4xe9q6nC3/jcKraq7nNS9VxgtT0KC+diA==", - "dev": true, - "dependencies": { - "got": "^11.8.2", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9274,17 +8846,17 @@ } }, "node_modules/patch-package": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", - "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-7.0.0.tgz", + "integrity": "sha512-eYunHbnnB2ghjTNc5iL1Uo7TsGMuXk0vibX3RFcE/CdVdXzmdbMsG/4K4IgoSuIkLTI5oHrMQk4+NkFqSed0BQ==", "dev": true, "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", - "cross-spawn": "^6.0.5", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^9.0.0", - "is-ci": "^2.0.0", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", "open": "^7.4.2", @@ -9292,32 +8864,16 @@ "semver": "^5.6.0", "slash": "^2.0.0", "tmp": "^0.0.33", - "yaml": "^1.10.2" + "yaml": "^2.2.2" }, "bin": { "patch-package": "index.js" }, "engines": { - "node": ">=10", + "node": ">=14", "npm": ">5" } }, - "node_modules/patch-package/node_modules/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, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, "node_modules/patch-package/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -9333,15 +8889,6 @@ "node": ">=10" } }, - "node_modules/patch-package/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/patch-package/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -9363,27 +8910,6 @@ "semver": "bin/semver" } }, - "node_modules/patch-package/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/patch-package/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/patch-package/node_modules/slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", @@ -9393,18 +8919,6 @@ "node": ">=6" } }, - "node_modules/patch-package/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -9465,6 +8979,31 @@ "node": ">=0.10.0" } }, + "node_modules/path-scurry": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", + "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "dev": true, + "dependencies": { + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.0.3.tgz", + "integrity": "sha512-cyjNRew29d4kbgnz1sjDqxg7qg8NW4s+HQzCGjeon7DV5T2yDije16W9HaUFV1dhVEMh+SjrOcK0TomBmf3Egg==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -9503,15 +9042,6 @@ "node": "*" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "dependencies": { - "through": "~2.3" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -9681,6 +9211,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -9737,12 +9282,6 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, - "node_modules/pubsub-js": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.4.tgz", - "integrity": "sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A==", - "dev": true - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9783,59 +9322,36 @@ } }, "node_modules/puppeteer-core": { - "version": "19.7.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.7.1.tgz", - "integrity": "sha512-4b5Go25IA+0xrUIw0Qtqi4nxc0qwdu/C7VT1+tFPl1W27207YT+7bxfANC3PjXMlS6bcbzinCf5YfGqMl8tfyQ==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, "dependencies": { - "cross-fetch": "3.1.5", + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", "debug": "4.3.4", - "devtools-protocol": "0.0.1094867", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.11.0" + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": ">=14.1.0" + "node": ">=16.0.0" }, "peerDependencies": { - "chromium-bidi": "0.4.3", "typescript": ">= 4.7.4" }, "peerDependenciesMeta": { - "chromium-bidi": { - "optional": true - }, "typescript": { "optional": true } } }, "node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1094867", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz", - "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==", + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true }, - "node_modules/puppeteer-core/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -9903,172 +9419,43 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/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, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/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==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", + "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", "dev": true, "dependencies": { - "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" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "node_modules/readdir-glob": { @@ -10113,18 +9500,11 @@ "node": ">= 0.10" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/regex-not": { "version": "1.0.2", @@ -10139,42 +9519,6 @@ "node": ">=0.10.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/registry-auth-token": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", - "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", - "dev": true, - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -10202,6 +9546,21 @@ "node": ">= 0.10" } }, + "node_modules/remove-bom-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/remove-bom-stream/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -10236,15 +9595,6 @@ "node": ">=0.10" } }, - "node_modules/replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/replace-homedir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", @@ -10279,6 +9629,21 @@ "node": ">=0.8.0" } }, + "node_modules/replacestream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10413,10 +9778,13 @@ "dev": true }, "node_modules/rimraf": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", - "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", + "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", "dev": true, + "dependencies": { + "glob": "^10.2.5" + }, "bin": { "rimraf": "dist/cjs/src/bin.js" }, @@ -10427,6 +9795,52 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.6.tgz", + "integrity": "sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -10456,18 +9870,18 @@ } }, "node_modules/rxjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", - "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { - "tslib": "~2.1.0" + "tslib": "^2.1.0" } }, "node_modules/rxjs/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", "dev": true }, "node_modules/safe-buffer": { @@ -10571,21 +9985,6 @@ "ieee754": "^1.2.1" } }, - "node_modules/selenium-standalone/node_modules/readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/selenium-standalone/node_modules/tar-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.0.0.tgz", @@ -10736,10 +10135,13 @@ } }, "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/sigma": { "version": "1.2.1", @@ -10747,6 +10149,18 @@ "integrity": "sha512-9Z0m1pssXv6sndPMvOzXnM1mVO73YCWDE6X5bKxJyG+9J0B9zJkgtgoBM7cnxEaJMzmrbxPceKTVpwF7cS/xqA==", "dev": true }, + "node_modules/signal-exit": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", + "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/sinon": { "version": "9.2.4", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", @@ -11010,22 +10424,11 @@ } }, "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, - "node_modules/spdx-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, - "dependencies": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -11058,35 +10461,6 @@ "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", "dev": true }, - "node_modules/spdx-ranges": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true - }, - "node_modules/spdx-satisfies": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz", - "integrity": "sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==", - "dev": true, - "dependencies": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -11201,35 +10575,6 @@ "node": ">=0.10.0" } }, - "node_modules/stream-combiner": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - }, - "node_modules/stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-combiner2/node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } - }, "node_modules/stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -11287,6 +10632,15 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/streamx": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.13.2.tgz", @@ -11329,6 +10683,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11341,6 +10710,28 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -11371,18 +10762,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11534,18 +10913,9 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "dependencies": { - "readable-stream": "3" - } - }, "node_modules/through2-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", @@ -11556,6 +10926,21 @@ "xtend": "~4.0.0" } }, + "node_modules/through2-filter/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/through2-filter/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -11566,20 +10951,6 @@ "xtend": "~4.0.1" } }, - "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/time-stamp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", @@ -11696,6 +11067,21 @@ "node": ">= 0.10" } }, + "node_modules/to-through/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/to-through/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -11750,15 +11136,6 @@ "tree-kill": "cli.js" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11826,22 +11203,22 @@ "dev": true }, "node_modules/typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/ua-parser-js": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", - "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", "dev": true, "funding": [ { @@ -11912,6 +11289,18 @@ "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", "dev": true }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dev": true, + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -12102,15 +11491,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "dependencies": { - "builtins": "^1.0.3" - } - }, "node_modules/validator": { "version": "13.7.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", @@ -12174,6 +11554,21 @@ "node": ">= 0.10" } }, + "node_modules/vinyl-fs/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "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" + } + }, "node_modules/vinyl-fs/node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -12244,20 +11639,20 @@ } }, "node_modules/webdriver": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.3.11.tgz", - "integrity": "sha512-1Dw8tN+c+LdnIXizEB+RrH9LbHGuTEDUtPqxT0fd80F+g/RqZvX3bv3ABD7ZVvvdE2ck6XbATnVX2/rYrQxSYQ==", + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.11.1.tgz", + "integrity": "sha512-hSpUZYzUA65t4DDtKujCHUX6hpFTUleb7lWMcf5xjPz8sxWrK9R8NIw7pXt/GU6PVS331nGAaYkzoXrqz2VB8w==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", + "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/protocols": "8.3.11", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", - "deepmerge-ts": "^4.2.2", - "got": "^12.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.11.0", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", + "deepmerge-ts": "^5.0.0", + "got": "^ 12.6.1", "ky": "^0.33.0", "ws": "^8.8.0" }, @@ -12266,9 +11661,9 @@ } }, "node_modules/webdriver/node_modules/@sindresorhus/is": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", - "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.4.1.tgz", + "integrity": "sha512-axlrvsHlHlFmKKMEg4VyvMzFr93JWJj4eIfXY1STVuO2fsImCa7ncaiG5gC8HKOX590AW5RtRsC41/B+OfrSqw==", "dev": true, "engines": { "node": ">=14.16" @@ -12299,9 +11694,9 @@ } }, "node_modules/webdriver/node_modules/cacheable-request": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.8.tgz", - "integrity": "sha512-IDVO5MJ4LItE6HKFQTqT2ocAQsisOoCTUDu1ddCmnhyiwFQjXNPp4081Xj23N4tO+AFEFNzGuNEf/c8Gwwt15A==", + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.10.tgz", + "integrity": "sha512-v6WB+Epm/qO4Hdlio/sfUn69r5Shgh39SsE9DSd4bIezP0mblOlObI+I0kUEM7J0JFc+I7pSeMeYaOYtX1N/VQ==", "dev": true, "dependencies": { "@types/http-cache-semantics": "^4.0.1", @@ -12329,15 +11724,15 @@ } }, "node_modules/webdriver/node_modules/got": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/got/-/got-12.5.3.tgz", - "integrity": "sha512-8wKnb9MGU8IPGRIo+/ukTy9XLJBwDiCpIf5TVzQ9Cpol50eMTpBq2GAuDsuDIz7hTYmZgMgC1e9ydr6kSDWs3w==", + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "dependencies": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.1", + "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", @@ -12427,36 +11822,36 @@ } }, "node_modules/webdriverio": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.3.11.tgz", - "integrity": "sha512-sN2jmkwSQj0r9IRLvpzfmLh/OvX3l4G+l+W26ksoLr+qxdOfRmoNfxc4CGMt/Uin515ajajqPWrxFFbdeL3uIQ==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.11.2.tgz", + "integrity": "sha512-e/9WkdNTfWeoaSo2UzK0Giec/nQX3i7U9J8esimhozH/EpwSqIaEJ2pRRlxRVafEhe2OBG1QDhnLnDjdCC5Hxg==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/protocols": "8.3.11", - "@wdio/repl": "8.1.0", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.11.0", + "@wdio/repl": "8.10.1", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", "archiver": "^5.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "8.3.11", - "devtools-protocol": "^0.0.1103684", + "devtools": "8.11.0", + "devtools-protocol": "^0.0.1152884", "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^2.1.0", + "import-meta-resolve": "^3.0.0", "is-plain-obj": "^4.1.0", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^7.0.0", - "puppeteer-core": "19.7.1", + "minimatch": "^9.0.0", + "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "8.3.11" + "webdriver": "8.11.1" }, "engines": { "node": "^16.13 || >=18" @@ -12484,15 +11879,15 @@ } }, "node_modules/webdriverio/node_modules/minimatch": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.2.0.tgz", - "integrity": "sha512-rMRHmwySzopAQjmWW6TkAKCEDKNaY/HuV/c2YkWWuWnfkTwApt0V4hnYzzPnZ/5Gcd2+8MPncSyuOGPl3xPvcg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -12575,6 +11970,7 @@ "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, "engines": { "node": ">=0.10.0" } @@ -12602,6 +11998,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -12609,16 +12023,15 @@ "dev": true }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -12667,18 +12080,18 @@ "dev": true }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "dev": true, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -12927,24 +12340,33 @@ } } }, + "@babel/runtime": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, "@blockly/block-test": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-3.0.8.tgz", - "integrity": "sha512-foW4fVKCrrmeImXNgVJZ+XjMENpOz3z3P/ob1IxixPnQ3QyO2Yjh0rfPDkSteJjto5k9Q+XhcvlW1w42gyjIKg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-3.1.2.tgz", + "integrity": "sha512-a/Y21p6KcZIeRCsfx+Caqw9HEYyfGA1gwwWYi+nJttRpJCO8W0IAG6cMqNRDn0vPCoWdIa8o9xhoMBdyPURU3Q==", "dev": true, "requires": {} }, "@blockly/dev-tools": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-5.2.4.tgz", - "integrity": "sha512-7FF5PByhq9EhKeYty0GFZp8b7l3qWrEIiqyVTgp/xN7iI/9N6scsDVZfCWVDSmZHgiaTHkEcQW/697IRm69HDA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-5.3.1.tgz", + "integrity": "sha512-3xpxjJgv//z7uItocjEvJnkjHQCaCQHCuB0z5P6u9ysQcFaWmSRNcf5rCEjHSVUlQU22pAhhjytpM8mVzszh6w==", "dev": true, "requires": { - "@blockly/block-test": "^3.0.8", - "@blockly/theme-dark": "^4.0.7", - "@blockly/theme-deuteranopia": "^3.0.7", - "@blockly/theme-highcontrast": "^3.0.7", - "@blockly/theme-tritanopia": "^3.0.8", + "@blockly/block-test": "^3.1.2", + "@blockly/theme-dark": "^4.0.10", + "@blockly/theme-deuteranopia": "^3.0.10", + "@blockly/theme-highcontrast": "^3.0.10", + "@blockly/theme-tritanopia": "^3.0.11", "chai": "^4.2.0", "dat.gui": "^0.7.7", "lodash.assign": "^4.2.0", @@ -12954,49 +12376,49 @@ } }, "@blockly/theme-dark": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@blockly/theme-dark/-/theme-dark-4.0.7.tgz", - "integrity": "sha512-NegmW6DxvDISxonJ8gtp/CGxYZmtNYyjR2t7V5GzwnZhU1QV1aZVJk49f3CbFftQm3W93wHZ9HQn80oTbOCbiQ==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-dark/-/theme-dark-4.0.10.tgz", + "integrity": "sha512-deoNn1SsjEVAAXjOrQag/s5X49DsHSID4McwvPLl6lRh0C8C0G9zRSEF90n/mqlLu855Y4xnfzxJWxTdTKUikQ==", "dev": true, "requires": {} }, "@blockly/theme-deuteranopia": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@blockly/theme-deuteranopia/-/theme-deuteranopia-3.0.7.tgz", - "integrity": "sha512-51NF9RqqiskCefMPwMO9JS5l+Q1ubyryx5XUwNV7Dl8LljmyaQzBy2xu6MVIG/yZDY1qR7oS2scsyOffb++oFQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-deuteranopia/-/theme-deuteranopia-3.0.10.tgz", + "integrity": "sha512-m+RxF+DlP+BA5FPQYunPWoFXGlfspTqF97uGkkA0RK8P5UadXbWf4XQAscoMX96zjyfHME8GyQiwmdCcLHm9OQ==", "dev": true, "requires": {} }, "@blockly/theme-highcontrast": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@blockly/theme-highcontrast/-/theme-highcontrast-3.0.7.tgz", - "integrity": "sha512-uwpDXhcXXaXxWT1xkWECUBD0ao1+hzK9iLf5FWktnSqfyj6galLVjB2mxRb3ZTaVuhjvw7/qhjYzVPqzL2NKgg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-highcontrast/-/theme-highcontrast-3.0.10.tgz", + "integrity": "sha512-4PSJgObr/F57YsE54Tg2y6+qVvJoRrwGysP0K7fZhGKB6XMVEmwbzG6pds3SE9y7AHtaQ3lQOauojciwOUHUTg==", "dev": true, "requires": {} }, "@blockly/theme-modern": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-3.0.6.tgz", - "integrity": "sha512-A59AMr3hZRHugC3qGSpZ2gtGlQkuWtj2qj7B5jLbVoSKNJuPRJt1DKQElvriev5GhangjSmrQZULPhAAIEXLsQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-3.0.10.tgz", + "integrity": "sha512-02px8PlvC3ZcVSZGLf7fYhni6xohoMg3P56ZSaP/Js4L+TDD4tzHDK9vE8BpYmmV0ZmwReKiN/fazrUwhYNSFA==", "dev": true, "requires": {} }, "@blockly/theme-tritanopia": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@blockly/theme-tritanopia/-/theme-tritanopia-3.0.8.tgz", - "integrity": "sha512-17n3LAFwOJHkBeeVUYoRZ+ATSDpmOQuP41n9ZxDb0N3VbnjkFEVRT+saG3wQgcHD6F7KoVHkK2GChbmcYrU+Ig==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@blockly/theme-tritanopia/-/theme-tritanopia-3.0.11.tgz", + "integrity": "sha512-bGtOpYglDx50yxOAZiIcgnbtWQZaSSlfMTALjkw/1+sxa0kwq2Oxws02uQ4LMni/8o9jY/dvoEGLStXFby0FzA==", "dev": true, "requires": {} }, "@es-joy/jsdoccomment": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz", - "integrity": "sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz", + "integrity": "sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg==", "dev": true, "requires": { "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~3.1.0" + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" } }, "@eslint-community/eslint-utils": { @@ -13015,14 +12437,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -13031,6 +12453,12 @@ "strip-json-comments": "^3.1.1" } }, + "@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true + }, "@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -13050,6 +12478,17 @@ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13087,6 +12526,21 @@ "remove-trailing-separator": "^1.0.1" } }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -13100,9 +12554,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -13122,80 +12576,124 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@hyperjump/json": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@hyperjump/json/-/json-0.1.0.tgz", - "integrity": "sha512-jWsAOHjweWhi0UEBCN57YZzyTt76Z6Fm/OJXOfNBJbEZt569AcTRsjv6Dqj5t4gQhW9td72oquiyaVp9oHbhBQ==", - "dev": true, - "requires": { - "@hyperjump/json-pointer": "^0.9.2", - "moo": "^0.5.1" - } - }, "@hyperjump/json-pointer": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-0.9.6.tgz", - "integrity": "sha512-3szMJLfz+1wtfPHnGi1sHzwFfFdZqIZLCCYtaD47vLZMAQCbtoBRVZn44jJgIQ6v37+8fom5rsxSSIMKWi0zbg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-1.0.1.tgz", + "integrity": "sha512-vV2pSc7JCwbKEMzh8kr/ICZdO+UZbA3aZ7N8t7leDi9cduWKa9yoP5LS04LnsbErlPbUNHvWBFlbTaR/o/uf7A==", "dev": true, "requires": { - "just-curry-it": "^5.2.1" + "just-curry-it": "^5.3.0" } }, "@hyperjump/json-schema": { - "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-0.23.3.tgz", - "integrity": "sha512-3YqAQpYY1U16SeP+LfMzBySqW5FHF9gZcOMKeeRywJkowsZ1JuOOiYCmYXptlRwXSB1jVa9ITC0lvfO84OaSQQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-1.5.1.tgz", + "integrity": "sha512-PJhd73x9eXgG2+U1W3uXU4KKHjw7xiar2OVpygpiD5Ky0OBu/b4qSMLgH1JX9Wg6FCaKy9Xpt+0zXpgLRQc6dw==", "dev": true, "requires": { - "@hyperjump/json-schema-core": "^0.28.0", - "fastest-stable-stringify": "^2.0.2" - } - }, - "@hyperjump/json-schema-core": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/@hyperjump/json-schema-core/-/json-schema-core-0.28.5.tgz", - "integrity": "sha512-+f5P3oHYCQru3s+Ha+E10rIyEvyK0Hfa2oj3+cDoGaVMbT4Jg5TgCoIM7B5rl3t3KRA7EOmrLjKFGeLi5yd1pg==", - "dev": true, - "requires": { - "@hyperjump/json": "^0.1.0", - "@hyperjump/json-pointer": "^0.9.4", - "@hyperjump/pact": "^0.2.3", + "@hyperjump/json-pointer": "^1.0.0", + "@hyperjump/pact": "^1.0.0", + "@hyperjump/uri": "^1.2.0", "content-type": "^1.0.4", - "node-fetch": "^2.6.5", - "pubsub-js": "^1.9.4", - "uri-js": "^4.4.1" + "fastest-stable-stringify": "^2.0.2", + "just-curry-it": "^5.3.0", + "undici": "^5.19.1", + "uuid": "^9.0.0" } }, "@hyperjump/pact": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-0.2.4.tgz", - "integrity": "sha512-BGmyLaUSCMVyHrwXr67rMxgiQHPHwcmVCjROoY8q232EpMz9d9aFCkgGhdx//yEfHM7zgsm0zZ8RD/F89uPySg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-1.0.0.tgz", + "integrity": "sha512-d4YYXhVictDbkhjWbRA+Qk8PiufDTSRZg7tWMU07sl0wA/rh9AtL0cPJdpxSsjtDVDBlNSjsOr+RTKDU2IKqEA==", "dev": true, "requires": { - "just-curry-it": "^3.1.0" + "just-curry-it": "^5.3.0" + } + }, + "@hyperjump/uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hyperjump/uri/-/uri-1.2.0.tgz", + "integrity": "sha512-v/OE8Kg0xdd1wYRjyAI8zPxQEAgWuhqSy5mJm0/FAIUdN6S6b75DBUSl2J3ps6QSCID3fnjXqJyevrOOH67YAA==", + "dev": true + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "dependencies": { - "just-curry-it": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-3.2.1.tgz", - "integrity": "sha512-Q8206k8pTY7krW32cdmPsP+DqqLgWx/hYPSj9/+7SYqSqz7UuwPbfSe07lQtvuuaVyiSJveXk0E5RydOuWwsEg==", + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } } } }, "@microsoft/api-documenter": { - "version": "7.19.26", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.19.26.tgz", - "integrity": "sha512-sFhYmO8k6CMFJ20D/LP1B7GdH+JfwmSKO/xTXnm63WA3+AX7g94G4TlKlc1FXdHFS2qhHnlm4qZUD3fgfs1vqg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.22.20.tgz", + "integrity": "sha512-jqb4qRHWzF1bykWroZVdk7VjeRdcTiNd0Qz7IhMGWEx5jwN2upyjpDYBvDHLkoFn8uKiNqveWz7MAHY/4MOFbw==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.25.3", + "@microsoft/api-extractor-model": "7.27.3", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "3.53.3", - "@rushstack/ts-command-line": "4.13.1", + "@rushstack/node-core-library": "3.59.4", + "@rushstack/ts-command-line": "4.15.1", "colors": "~1.2.1", "js-yaml": "~3.13.1", - "resolve": "~1.17.0" + "resolve": "~1.22.1" }, "dependencies": { "argparse": { @@ -13216,53 +12714,44 @@ "argparse": "^1.0.7", "esprima": "^4.0.0" } - }, - "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" - } } } }, "@microsoft/api-extractor": { - "version": "7.34.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.34.4.tgz", - "integrity": "sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==", + "version": "7.35.4", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.35.4.tgz", + "integrity": "sha512-E/DIIlgu1ZW+AD+Y0UuVe/30rO+Km0CRkDU1aOKQntwKtv/+FodtRAUvzU/vAxq5lQHSsy6jJErLiXR6G3fupA==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.26.4", + "@microsoft/api-extractor-model": "7.27.3", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2", - "@rushstack/rig-package": "0.3.18", - "@rushstack/ts-command-line": "4.13.2", + "@rushstack/node-core-library": "3.59.4", + "@rushstack/rig-package": "0.3.21", + "@rushstack/ts-command-line": "4.15.1", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.22.1", "semver": "~7.3.0", "source-map": "~0.6.1", - "typescript": "~4.8.4" + "typescript": "~5.0.4" }, "dependencies": { "@microsoft/api-extractor-model": { - "version": "7.26.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.26.4.tgz", - "integrity": "sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.27.3.tgz", + "integrity": "sha512-fSFvw7otYHduOkyshjTbapKKgwF8bgquVHvgF8VgeKtMYvqXkoaj7W6VcM7PNY7E2bbblhUgC4XNdqZLD4SJGw==", "dev": true, "requires": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.55.2" + "@rushstack/node-core-library": "3.59.4" } }, "@rushstack/node-core-library": { - "version": "3.55.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.55.2.tgz", - "integrity": "sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==", + "version": "3.59.4", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.59.4.tgz", + "integrity": "sha512-YAKJDC6Mz/KA1D7bvB88WaRX3knt/ZuLzkRu5G9QADGSjLtvTWzCNCytRF2PCSaaHOZaZsWul4F1KQdgFgUDqA==", "dev": true, "requires": { "colors": "~1.2.1", @@ -13275,9 +12764,9 @@ } }, "@rushstack/ts-command-line": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.2.tgz", - "integrity": "sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.15.1.tgz", + "integrity": "sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -13322,9 +12811,9 @@ "dev": true }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true }, "universalify": { @@ -13336,14 +12825,14 @@ } }, "@microsoft/api-extractor-model": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.25.3.tgz", - "integrity": "sha512-WWxBUq77p2iZ+5VF7Nmrm3y/UtqCh5bYV8ii3khwq3w99+fXWpvfsAhgSLsC7k8XDQc6De4ssMxH6He/qe1pzg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.27.3.tgz", + "integrity": "sha512-fSFvw7otYHduOkyshjTbapKKgwF8bgquVHvgF8VgeKtMYvqXkoaj7W6VcM7PNY7E2bbblhUgC4XNdqZLD4SJGw==", "dev": true, "requires": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.53.3" + "@rushstack/node-core-library": "3.59.4" } }, "@microsoft/tsdoc": { @@ -13402,28 +12891,73 @@ "fastq": "^1.6.0" } }, - "@rushstack/node-core-library": { - "version": "3.53.3", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.53.3.tgz", - "integrity": "sha512-H0+T5koi5MFhJUd5ND3dI3bwLhvlABetARl78L3lWftJVQEPyzcgTStvTTRiIM5mCltyTM8VYm6BuCtNUuxD0Q==", + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "dependencies": { + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } + }, + "@rushstack/node-core-library": { + "version": "3.59.4", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.59.4.tgz", + "integrity": "sha512-YAKJDC6Mz/KA1D7bvB88WaRX3knt/ZuLzkRu5G9QADGSjLtvTWzCNCytRF2PCSaaHOZaZsWul4F1KQdgFgUDqA==", "dev": true, "requires": { - "@types/node": "12.20.24", "colors": "~1.2.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", "jju": "~1.4.0", - "resolve": "~1.17.0", + "resolve": "~1.22.1", "semver": "~7.3.0", "z-schema": "~5.0.2" }, "dependencies": { - "@types/node": { - "version": "12.20.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", - "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", - "dev": true - }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -13444,15 +12978,6 @@ "graceful-fs": "^4.1.6" } }, - "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" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -13462,9 +12987,9 @@ } }, "@rushstack/rig-package": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.18.tgz", - "integrity": "sha512-SGEwNTwNq9bI3pkdd01yCaH+gAsHqs0uxfGvtw9b0LJXH52qooWXnrFTRRLG1aL9pf+M2CARdrA9HLHJys3jiQ==", + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.21.tgz", + "integrity": "sha512-6KPBuZYP/b9U0Qwy1J4vjYtXvLavdmVT7mMelErfqqZ3P/ywoxlFITGr9ZbqD1zmfIVrIfC2jrM6gfm7OHPRhQ==", "dev": true, "requires": { "resolve": "~1.22.1", @@ -13472,9 +12997,9 @@ } }, "@rushstack/ts-command-line": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.13.1.tgz", - "integrity": "sha512-UTQMRyy/jH1IS2U+6pyzyn9xQ2iMcoUKkTcZUzOP/aaMiKlWLwCTDiBVwhw/M1crDx6apF9CwyjuWO9r1SBdJQ==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.15.1.tgz", + "integrity": "sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -13597,9 +13122,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "@types/keyv": { @@ -13611,16 +13136,10 @@ "@types/node": "*" } }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, "@types/node": { - "version": "18.11.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", - "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", + "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", "dev": true }, "@types/normalize-package-data": { @@ -13639,9 +13158,9 @@ } }, "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, "@types/vinyl": { @@ -13661,9 +13180,9 @@ "dev": true }, "@types/ws": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", - "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", "dev": true, "requires": { "@types/node": "*" @@ -13680,15 +13199,15 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz", - "integrity": "sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", + "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/type-utils": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/type-utils": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -13698,28 +13217,28 @@ }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", + "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" } }, "@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true }, "@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" } } @@ -13750,31 +13269,31 @@ } }, "@typescript-eslint/type-utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.55.0.tgz", - "integrity": "sha512-ObqxBgHIXj8rBNm0yh8oORFrICcJuZPZTqtAFh0oZQyr5DnAHZWfyw54RwpEEH+fD8suZaI0YxvWu5tYE/WswA==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", + "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.55.0", - "@typescript-eslint/utils": "5.55.0", + "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "tsutils": "^3.21.0" }, "dependencies": { "@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", + "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -13783,12 +13302,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" } } @@ -13818,45 +13337,45 @@ } }, "@typescript-eslint/utils": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.55.0.tgz", - "integrity": "sha512-FkW+i2pQKcpDC3AY6DU54yl8Lfl14FVGYDgBTyGKB75cCwV3KpkpTMFi9d9j2WAJ4271LR2HeC5SEWF/CZmmfw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", + "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.55.0", - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/typescript-estree": "5.55.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", + "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" } }, "@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", + "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", + "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -13865,12 +13384,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", + "version": "5.59.11", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", + "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.55.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" } }, @@ -13898,18 +13417,18 @@ } }, "@wdio/config": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.3.11.tgz", - "integrity": "sha512-sZ1SZkBEZWqSDHqrQxspsrc+OBqf3qyx2c4gVvAovOd1hnT4EcWhlJu1Asp3H0Mti115XstwQabOW4sSsdmVCw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.11.0.tgz", + "integrity": "sha512-nBQXsXbPCjddtI/3rAK5yFs3eD3f0T3lZMivweTkLLR7GKBxGjiFoBjXtfqUrHJYa+2uwfXrwxo6y+dA6fVbuw==", "dev": true, "requires": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", "decamelize": "^6.0.0", - "deepmerge-ts": "^4.2.2", - "glob": "^8.0.3", - "import-meta-resolve": "^2.1.0", + "deepmerge-ts": "^5.0.0", + "glob": "^10.2.2", + "import-meta-resolve": "^3.0.0", "read-pkg-up": "^9.1.0" }, "dependencies": { @@ -13939,16 +13458,16 @@ } }, "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", + "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" } }, "locate-path": { @@ -13961,9 +13480,9 @@ } }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -14025,219 +13544,86 @@ } }, "@wdio/logger": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.1.0.tgz", - "integrity": "sha512-QRC5b7FF4JIYUCqggnVA0sZ80TwIUFN9JyBSbuGuMxaSLSLujSo7WfuSrnQXVvsRbnJ16wWwJWYigfLkxOW86Q==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.11.0.tgz", + "integrity": "sha512-IsuKSaYi7NKEdgA57h8muzlN/MVp1dQG+V4C//7g4m03YJUnNQLvDhJzLjdeNTfvZy61U7foQSyt+3ktNzZkXA==", "dev": true, "requires": { "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "strip-ansi": "^7.1.0" }, "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, "chalk": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "dev": true + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } } } }, "@wdio/protocols": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.3.11.tgz", - "integrity": "sha512-EXGuZC4Nvl8QPT6gQ9tpeH+TL9P5oRdQofaJA893OX37gU2OWiSNyA7AHr/0/UIHWFax4udpVQ6syTQsy6uWWA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.11.0.tgz", + "integrity": "sha512-eXTMYt/XoaX53H/Q2qmsn1uWthIC5aSTGtX9YyXD/AkagG2hXeX3lLmzNWBaSIvKR+vWXRYbg3Y/7IvL2s25Wg==", "dev": true }, "@wdio/repl": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.1.0.tgz", - "integrity": "sha512-96G4TzbYnRf95+GURo15FYt6iTYq85nbWU6YQedLRAV15RfSp4foKTbAnq++bKKMALNL6gdzTc+HGhQr3Q0sQg==", + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.10.1.tgz", + "integrity": "sha512-VZ1WFHTNKjR8Ga97TtV2SZM6fvRjWbYI2i/f4pJB4PtusorKvONAMJf2LQcUBIyzbVobqr7KSrcjmSwRolI+yw==", "dev": true, "requires": { - "@types/node": "^18.0.0" + "@types/node": "^20.1.0" } }, "@wdio/selenium-standalone-service": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-8.3.11.tgz", - "integrity": "sha512-gRcsaH5KG49r3r3HCOUjG1Mh+1301YJaKgqPyTulaooH5Ia2boEGeI1ijT7F14PBuiuIuha6i3us7TPecgeuVw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/selenium-standalone-service/-/selenium-standalone-service-8.11.0.tgz", + "integrity": "sha512-Xve5TjSKtQQZI39O0b+Iek/XG5IZgUdR5Dgp04kx3lQFfqZ5AmE0+1BUP08jbVBiS8R5QYUoHZ3X02bo/k9TFA==", "dev": true, "requires": { - "@types/node": "^18.0.0", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.10.4", "selenium-standalone": "^8.2.1" - }, - "dependencies": { - "@wdio/config": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.3.11.tgz", - "integrity": "sha512-sZ1SZkBEZWqSDHqrQxspsrc+OBqf3qyx2c4gVvAovOd1hnT4EcWhlJu1Asp3H0Mti115XstwQabOW4sSsdmVCw==", - "dev": true, - "requires": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", - "decamelize": "^6.0.0", - "deepmerge-ts": "^4.2.2", - "glob": "^8.0.3", - "import-meta-resolve": "^2.1.0", - "read-pkg-up": "^9.1.0" - } - }, - "@wdio/types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.3.0.tgz", - "integrity": "sha512-TTs3ETVOJtooTIY/u2+feeBnMBx2Hb4SEItN31r0pFncn37rnIZXE/buywLNf5wvZW9ukxoCup5hCnohOR27eQ==", - "dev": true, - "requires": { - "@types/node": "^18.0.0" - } - }, - "@wdio/utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.3.0.tgz", - "integrity": "sha512-BbgzxAu4AN99l9hwaRUvkvBJLeIxON7F6ts+vq4LASRiGVRErrwYGeO9H3ffYgpCAaYSvXnw2GtOf2SQGaPoLA==", - "dev": true, - "requires": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "import-meta-resolve": "^2.2.0", - "p-iteration": "^1.1.8" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", - "dev": true - }, - "find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "requires": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dev": true, - "requires": { - "p-locate": "^6.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "requires": { - "p-limit": "^4.0.0" - } - }, - "path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true - }, - "read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", - "dev": true, - "requires": { - "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" - } - }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true - } } }, "@wdio/types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.3.0.tgz", - "integrity": "sha512-TTs3ETVOJtooTIY/u2+feeBnMBx2Hb4SEItN31r0pFncn37rnIZXE/buywLNf5wvZW9ukxoCup5hCnohOR27eQ==", + "version": "8.10.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.10.4.tgz", + "integrity": "sha512-aLJ1QQW+hhALeRK3bvMLjIrlUVyhOs3Od+91pR4Z4pLwyeNG1bJZCJRD5bAJK/mm7CnFa0NsdixPS9jJxZcRrw==", "dev": true, "requires": { - "@types/node": "^18.0.0" + "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.3.0.tgz", - "integrity": "sha512-BbgzxAu4AN99l9hwaRUvkvBJLeIxON7F6ts+vq4LASRiGVRErrwYGeO9H3ffYgpCAaYSvXnw2GtOf2SQGaPoLA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.11.0.tgz", + "integrity": "sha512-XBl1zalk5UPu8QKZ7LZIA82Ad363fpNHZHP5uI5OxUFnk4ZPWgY9eCWpeD+4f9a0DS0w2Dro15E4PORNX84pIw==", "dev": true, "requires": { - "@wdio/logger": "8.1.0", - "@wdio/types": "8.3.0", - "import-meta-resolve": "^2.2.0", + "@wdio/logger": "8.11.0", + "@wdio/types": "8.10.4", + "import-meta-resolve": "^3.0.0", "p-iteration": "^1.1.8" } }, @@ -14264,12 +13650,15 @@ "acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true }, "acorn-globals": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "peer": true, "requires": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -14285,7 +13674,8 @@ "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true }, "agent-base": { "version": "6.0.2", @@ -14412,6 +13802,23 @@ "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "archy": { @@ -14420,6 +13827,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -14468,24 +13881,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -14544,24 +13945,12 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "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 }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -14721,12 +14110,6 @@ "safe-buffer": "5.1.2" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -14873,12 +14256,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -14939,31 +14331,6 @@ "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 - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "dependencies": { - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - } - } - }, "chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -15018,9 +14385,9 @@ "dev": true }, "chrome-launcher": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.1.tgz", - "integrity": "sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "dev": true, "requires": { "@types/node": "*", @@ -15029,23 +14396,21 @@ "lighthouse-logger": "^1.0.0" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "clang-format": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.8.0.tgz", - "integrity": "sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw==", + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", "dev": true, "requires": { - "async": "^3.2.3", - "glob": "^7.0.0", - "resolve": "^1.1.6" + "mitt": "3.0.0" } }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -15120,20 +14485,6 @@ } } }, - "cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -15181,6 +14532,23 @@ "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "closure-calculate-chunks": { @@ -15343,6 +14711,23 @@ "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "concat-with-sourcemaps": { @@ -15363,20 +14748,20 @@ } }, "concurrently": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", - "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.0.tgz", + "integrity": "sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA==", "dev": true, "requires": { - "chalk": "^4.1.0", - "date-fns": "^2.29.1", + "chalk": "^4.1.2", + "date-fns": "^2.30.0", "lodash": "^4.17.21", - "rxjs": "^7.0.0", - "shell-quote": "^1.7.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", "tree-kill": "^1.2.2", - "yargs": "^17.3.1" + "yargs": "^17.7.2" }, "dependencies": { "supports-color": { @@ -15391,9 +14776,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { @@ -15467,12 +14852,12 @@ } }, "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, "requires": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.11" } }, "cross-spawn": { @@ -15572,16 +14957,13 @@ } }, "date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "dev": true - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } }, "debug": { "version": "4.3.4", @@ -15619,24 +15001,6 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - } - } - }, "decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -15674,21 +15038,16 @@ "type-detect": "^4.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "deepmerge-ts": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-4.2.2.tgz", - "integrity": "sha512-Ka3Kb21tiWjvQvS9U+1Dx+aqFAHsdTnMdYptLTmC2VAmDFMugWMY1e15aTODstipmCun8iNuqeSfcx6rsUUk0Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", "dev": true }, "default-compare": { @@ -15749,21 +15108,21 @@ "dev": true }, "devtools": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.3.11.tgz", - "integrity": "sha512-Wevpd4fZqCveNJv4qSpT+dER5UARLVffpeSEBLBqtrvrNZ6e39x2jWgb838Kf6okuIHWWBXq0IJuRpMda50mXw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.11.0.tgz", + "integrity": "sha512-j1wXFQyjswJ6doAV1+h4Bxl8+Oeb8SMpWTpBVa0DurGsxfft8sU2OhDlMo5tx/zbX82X5sGyJDMnKHqBJ2XRvQ==", "dev": true, "requires": { - "@types/node": "^18.0.0", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/protocols": "8.3.11", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.11.0", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", "chrome-launcher": "^0.15.0", "edge-paths": "^3.0.5", - "import-meta-resolve": "^2.1.0", - "puppeteer-core": "19.7.1", + "import-meta-resolve": "^3.0.0", + "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", "uuid": "^9.0.0", @@ -15771,9 +15130,9 @@ }, "dependencies": { "which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -15782,9 +15141,9 @@ } }, "devtools-protocol": { - "version": "0.0.1103684", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1103684.tgz", - "integrity": "sha512-44Qr4zFQkzW8r4WdDOSuQoxIzPDczY/K1RDfyxzEsiG2nSzbTojDW3becX5+HrDw3gENG8jY6ffbHZ2/Ix5LSA==", + "version": "0.0.1152884", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1152884.tgz", + "integrity": "sha512-9eP6OmCoU1cWArpXLuzyZQcBJ2PkINOh8Nwx8W5i8u6NDigDE5/mPlLLBAfshwn5YVvIz6ZQ9jbs0PZvKGccdQ==", "dev": true }, "dir-glob": { @@ -15821,47 +15180,6 @@ "webidl-conversions": "^7.0.0" } }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -15872,6 +15190,23 @@ "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "each-props": { @@ -15895,6 +15230,12 @@ } } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "edge-paths": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", @@ -15994,6 +15335,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "peer": true, "requires": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -16005,12 +15348,16 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "peer": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -16020,6 +15367,8 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "peer": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -16032,18 +15381,24 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "peer": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "peer": true, "requires": { "prelude-ls": "~1.1.2" } @@ -16051,13 +15406,16 @@ } }, "eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", - "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -16066,24 +15424,22 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -16091,7 +15447,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -16115,25 +15470,45 @@ "dev": true, "requires": {} }, + "eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "requires": {} + }, "eslint-plugin-jsdoc": { - "version": "40.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-40.0.0.tgz", - "integrity": "sha512-LOPyIu1vAVvGPkye3ci0moj0iNf3f8bmin6do2DYDj+77NRXWnkmhKRy8swWsatUs3mB5jYPWPUsFg9pyfEiyA==", + "version": "46.2.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.6.tgz", + "integrity": "sha512-zIaK3zbSrKuH12bP+SPybPgcHSM6MFzh3HFeaODzmsF1N8C1l8dzJ22cW1aq4g0+nayU1VMjmNf7hg0dpShLrA==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "~0.36.1", + "@es-joy/jsdoccomment": "~0.39.4", + "are-docs-informative": "^0.0.2", "comment-parser": "1.3.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", - "esquery": "^1.4.0", - "semver": "^7.3.8", + "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", + "semver": "^7.5.1", "spdx-expression-parse": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -16148,49 +15523,33 @@ } } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true }, "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -16230,7 +15589,8 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "event-emitter": { "version": "0.3.5", @@ -16242,21 +15602,6 @@ "es5-ext": "~0.10.14" } }, - "event-stream": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz", - "integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" - } - }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -16542,7 +15887,8 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true }, "fastest-stable-stringify": { "version": "2.0.2", @@ -16707,6 +16053,23 @@ "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "follow-redirects": { @@ -16730,6 +16093,16 @@ "for-in": "^1.0.1" } }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -16755,12 +16128,6 @@ "map-cache": "^0.2.2" } }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -16788,6 +16155,21 @@ "through2": "^2.0.3" }, "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -16819,18 +16201,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "gaxios": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", - "integrity": "sha512-TjtV2AJOZoMQqRYoy5eM8cCQogYwazWNYLQ72QB0kwa6vHHruYkGmhhyrlzbmgNHK1dNnuP2WSH81urfzyN2Og==", - "dev": true, - "requires": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - } - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -16928,6 +16298,21 @@ "requires": { "is-extglob": "^2.1.0" } + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } } } }, @@ -17091,6 +16476,21 @@ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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", @@ -17150,9 +16550,9 @@ } }, "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -17190,52 +16590,52 @@ } }, "google-closure-compiler": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230206.0.0.tgz", - "integrity": "sha512-gGscQOcO/75AlHyw78v87u0nGKJHWqOrQ224Ks91HH1iISgF+xZ8GYosU/8s5VD66x3VD0tJKXM2rIoGOA1ycA==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20230502.0.0.tgz", + "integrity": "sha512-C2WZkuRnXpNjU2nc0W/Cgxm6t2VlwEyUJOTaGHaLr6qZCXK0L1uhOneKWN2X7AORKdzyLW6Tq8ONxRc7eODGJg==", "dev": true, "requires": { "chalk": "4.x", - "google-closure-compiler-java": "^20230206.0.0", - "google-closure-compiler-linux": "^20230206.0.0", - "google-closure-compiler-osx": "^20230206.0.0", - "google-closure-compiler-windows": "^20230206.0.0", + "google-closure-compiler-java": "^20230502.0.0", + "google-closure-compiler-linux": "^20230502.0.0", + "google-closure-compiler-osx": "^20230502.0.0", + "google-closure-compiler-windows": "^20230502.0.0", "minimist": "1.x", "vinyl": "2.x", "vinyl-sourcemaps-apply": "^0.2.0" } }, "google-closure-compiler-java": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230206.0.0.tgz", - "integrity": "sha512-OcnDf29yx4JNU13HpptADI2ckl9hEchktSHs2XSLQ/xStUAJQGQOl96to5IYh2VuFgn3Ssaw6M3c6At2pJr7wQ==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20230502.0.0.tgz", + "integrity": "sha512-2nMQPQz2ppU9jvHhz2zpUP5jBDAqZp4gFVOEvirEyfUuLLkHwAvU2Tl1c7xaKX+Z4uMxpxttxcwdIjQhV2g8eQ==", "dev": true }, "google-closure-compiler-linux": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230206.0.0.tgz", - "integrity": "sha512-06N6w2elsnZMMA4Gf/vN2A3XzWvu+gUTrBczaw0KQL48GgdLq6OgAXrcopbGdi/K8Gz1WAcG0qf2ccG8dSqYNg==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20230502.0.0.tgz", + "integrity": "sha512-4NDgPKJXQHUxEyJoVFPVMQPJs5at7ThOXa9u3+9UeYk2K+vtW5wVZlmW07VOy8Mk/O/n2dp+Vl+wuE35BIiHAA==", "dev": true, "optional": true }, "google-closure-compiler-osx": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230206.0.0.tgz", - "integrity": "sha512-lJ/Y4HTk+KdL6PhLmmalP/3DdzGK0mS0+htuFP6y4t9+QXiUKnpHWx/VDQ3Fwm2fWEzqDxfhX3R+wC9lBvFiAg==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20230502.0.0.tgz", + "integrity": "sha512-jB13dcbu8O02cG3JcCCVZku1oI0ZirJc/Sr9xcGHY5MMyw3qEMlXb3IU97W6UXLcg2wCRawMWadOwL9K4L9lfQ==", "dev": true, "optional": true }, "google-closure-compiler-windows": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230206.0.0.tgz", - "integrity": "sha512-4KPr7XPiOs8g4Ao3T+70egf14avCEne26XF4Mur4Fg5511ym1uEN+NlEyjBOAmfUFfaA7BYDsA8iBzDIetKrnw==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20230502.0.0.tgz", + "integrity": "sha512-wW5/liBxejvUViiBNo8/C9Vnhw+Lm+n3RdfE4spNkmdH9bcpKM+KQBLrPPakW17P3HbAPOPZ0L1RsrmyLYA5Cg==", "dev": true, "optional": true }, "google-closure-deps": { - "version": "20230206.0.0", - "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230206.0.0.tgz", - "integrity": "sha512-MT0JDygFjCTavsOGfRRE1o3teF/ZZD1a8NbeVlGW0oM3OTY2xkOpoPMvSely4khfFV4i4qqjmlMvtwD8cT7adg==", + "version": "20230502.0.0", + "resolved": "https://registry.npmjs.org/google-closure-deps/-/google-closure-deps-20230502.0.0.tgz", + "integrity": "sha512-d74eiwhPn5E9Uq9wZ8qYvO2FRGf/anI6q/Z2JsyNbsQG7lWw9xq+F0gwKSdnPOwR1MODOD2Pe3mnjD+I5Qntcg==", "dev": true, "requires": { "minimatch": "^3.0.4", @@ -17296,6 +16696,12 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -17317,32 +16723,6 @@ "vinyl-fs": "^3.0.0" } }, - "gulp-clang-format": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/gulp-clang-format/-/gulp-clang-format-1.0.27.tgz", - "integrity": "sha512-Jj4PGuNXKdqVCh9fijvL7wdzma5TQRJz1vv8FjOjnSkfq3s/mvbdE/jq+5HG1c/q+jcYkXTEGkYT3CrdnJOLaQ==", - "dev": true, - "requires": { - "clang-format": "^1.0.32", - "fancy-log": "^1.3.2", - "gulp-diff": "^1.0.0", - "plugin-error": "^1.0.1", - "stream-combiner2": "^1.1.1", - "through2": "^2.0.3" - }, - "dependencies": { - "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" - } - } - } - }, "gulp-cli": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", @@ -17554,36 +16934,20 @@ "vinyl": "^2.0.0" }, "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "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" } - } - } - }, - "gulp-diff": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-diff/-/gulp-diff-1.0.0.tgz", - "integrity": "sha1-EBsjcS3WsQe9B9BauI6jrEhf7Xc=", - "dev": true, - "requires": { - "cli-color": "^1.0.0", - "diff": "^2.0.2", - "event-stream": "^3.1.5", - "gulp-util": "^3.0.6", - "through2": "^2.0.0" - }, - "dependencies": { - "diff": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", - "integrity": "sha512-9wfm3RLzMp/PyTFWuw9liEzdlxsdGixCW0ZTU1XDmtlAkvpVXTPGF8KnfSs0hm3BPbg19OrUPPsRkHXoREpP1g==", - "dev": true }, "through2": { "version": "2.0.5", @@ -17620,6 +16984,21 @@ "ansi-wrap": "^0.1.0" } }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -17644,6 +17023,21 @@ "through2": "^2.0.0" }, "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -17741,6 +17135,17 @@ "supports-color": "^7.1.0" } }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "through2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", @@ -17778,6 +17183,21 @@ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17807,128 +17227,20 @@ "through2": "^2.0.3" }, "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "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" } - } - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "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 - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "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" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true }, "through2": { "version": "2.0.5", @@ -17939,17 +17251,6 @@ "readable-stream": "~2.3.6", "xtend": "~4.0.1" } - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } } } }, @@ -17962,12 +17263,6 @@ "glogg": "^1.0.0" } }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -17977,30 +17272,12 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, "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 }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -18167,9 +17444,9 @@ "dev": true }, "import-meta-resolve": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-2.2.0.tgz", - "integrity": "sha512-CpPOtiCHxP9HdtDM5F45tNiAe66Cqlv3f5uHoJjt+KlaLrUh9/Wz9vepADZ78SlqEo62aDWZtj9ydMGXV+CPnw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", + "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", "dev": true }, "imurmurhash": { @@ -18178,12 +17455,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -18266,13 +17537,13 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, "requires": { - "ci-info": "^2.0.0" + "builtin-modules": "^3.3.0" } }, "is-core-module": { @@ -18399,12 +17670,6 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -18437,12 +17702,6 @@ "is-unc-path": "^1.0.0" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", @@ -18513,34 +17772,22 @@ "textextensions": "^3.2.0" } }, + "jackspeak": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz", + "integrity": "sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, - "js-green-licenses": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-green-licenses/-/js-green-licenses-4.0.0.tgz", - "integrity": "sha512-kcgTOaZmpDpINcRAOKKhjHtBN6zibMVTC8qfPUOpowQtI/6fUgdmwJLJ0ycCb0pUO3ZYKn++56sy8IlG60p5mg==", - "dev": true, - "requires": { - "gaxios": "^5.0.0", - "meow": "^9.0.0", - "npm-package-arg": "^8.0.0", - "package-json": "^7.0.0", - "semver": "^7.3.2", - "spdx-correct": "^3.0.0", - "spdx-satisfies": "^5.0.0", - "strip-json-comments": "^3.0.0" - } - }, - "js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18557,30 +17804,27 @@ } }, "jsdoc-type-pratt-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz", - "integrity": "sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true }, "jsdom": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", - "integrity": "sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "requires": { "abab": "^2.0.6", - "acorn": "^8.8.2", - "acorn-globals": "^7.0.0", "cssstyle": "^3.0.0", "data-urls": "^4.0.0", "decimal.js": "^10.4.3", "domexception": "^4.0.0", - "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", + "nwsapi": "^2.2.4", "parse5": "^7.1.2", "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", @@ -18629,12 +17873,6 @@ "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "requires": {} } } }, @@ -18679,9 +17917,9 @@ } }, "just-curry-it": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.2.1.tgz", - "integrity": "sha512-M8qhhO9WVNc3yZgf3qfiNxMIsQlHqFHJ3vMI8N/rkp852h1utOB/N3ebS8jeXGAwYSbkdd0K6zP9eZneUtjHwA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.3.0.tgz", + "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==", "dev": true }, "just-debounce": { @@ -18721,9 +17959,9 @@ } }, "ky": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.2.tgz", - "integrity": "sha512-f6oS2rKUcPu5FzdqCDbFpmzis/JlqFZw8uIHm/jf8Kc3vtnW+VDhuashOAKyBZv8bFiZFZUMNxTC0JtahEvujA==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", + "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", "dev": true }, "last-run": { @@ -18743,6 +17981,23 @@ "dev": true, "requires": { "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "lcid": { @@ -18801,9 +18056,9 @@ } }, "lighthouse-logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.3.0.tgz", - "integrity": "sha512-BbqAKApLb9ywUli+0a+PcV04SyJ/N1q/8qgCNe6U97KbPCS1BTksEuHFLYdvc8DltuhfxIUBqDZsC0bBGtl3lA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "dev": true, "requires": { "debug": "^2.6.9", @@ -18872,60 +18127,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, "lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", @@ -18950,15 +18157,6 @@ "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", "dev": true }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -18971,18 +18169,6 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -18995,17 +18181,6 @@ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", "dev": true }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.mapvalues": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", @@ -19018,12 +18193,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -19133,12 +18302,6 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true - }, "map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -19228,40 +18391,6 @@ } } }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "dependencies": { - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -19390,12 +18519,6 @@ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -19411,24 +18534,17 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "dependencies": { - "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==", - "dev": true - } - } + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true }, "mixin-deep": { "version": "1.3.2", @@ -19549,26 +18665,11 @@ "integrity": "sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ==", "dev": true }, - "moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "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==" }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", @@ -19633,12 +18734,6 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "nise": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", @@ -19653,9 +18748,9 @@ } }, "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -19718,17 +18813,6 @@ "once": "^1.3.2" } }, - "npm-package-arg": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", - "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "semver": "^7.3.4", - "validate-npm-package-name": "^3.0.0" - } - }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -19736,9 +18820,9 @@ "dev": true }, "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==" + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz", + "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==" }, "object-assign": { "version": "4.1.1", @@ -19928,6 +19012,23 @@ "dev": true, "requires": { "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + } } }, "os-locale": { @@ -19975,24 +19076,6 @@ "p-limit": "^3.0.2" } }, - "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 - }, - "package-json": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-7.0.0.tgz", - "integrity": "sha512-CHJqc94AA8YfSLHGQT3DbvSIuE12NLFekpM4n7LRrAd3dOJtA911+4xe9q6nC3/jcKraq7nNS9VxgtT0KC+diA==", - "dev": true, - "requires": { - "got": "^11.8.2", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^7.3.5" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -20052,17 +19135,17 @@ "dev": true }, "patch-package": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", - "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-7.0.0.tgz", + "integrity": "sha512-eYunHbnnB2ghjTNc5iL1Uo7TsGMuXk0vibX3RFcE/CdVdXzmdbMsG/4K4IgoSuIkLTI5oHrMQk4+NkFqSed0BQ==", "dev": true, "requires": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", - "cross-spawn": "^6.0.5", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", "find-yarn-workspace-root": "^2.0.0", "fs-extra": "^9.0.0", - "is-ci": "^2.0.0", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", "open": "^7.4.2", @@ -20070,22 +19153,9 @@ "semver": "^5.6.0", "slash": "^2.0.0", "tmp": "^0.0.33", - "yaml": "^1.10.2" + "yaml": "^2.2.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" - } - }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -20098,12 +19168,6 @@ "universalify": "^2.0.0" } }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -20119,35 +19183,11 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "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": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "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" - } } } }, @@ -20196,6 +19236,24 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, + "path-scurry": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", + "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "dev": true, + "requires": { + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.0.3.tgz", + "integrity": "sha512-cyjNRew29d4kbgnz1sjDqxg7qg8NW4s+HQzCGjeon7DV5T2yDije16W9HaUFV1dhVEMh+SjrOcK0TomBmf3Egg==", + "dev": true + } + } + }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -20230,15 +19288,6 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "requires": { - "through": "~2.3" - } - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -20371,6 +19420,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -20412,12 +19467,6 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, - "pubsub-js": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.4.tgz", - "integrity": "sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A==", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -20457,37 +19506,24 @@ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, "puppeteer-core": { - "version": "19.7.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.7.1.tgz", - "integrity": "sha512-4b5Go25IA+0xrUIw0Qtqi4nxc0qwdu/C7VT1+tFPl1W27207YT+7bxfANC3PjXMlS6bcbzinCf5YfGqMl8tfyQ==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, "requires": { - "cross-fetch": "3.1.5", + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", "debug": "4.3.4", - "devtools-protocol": "0.0.1094867", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.11.0" + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "dependencies": { "devtools-protocol": { - "version": "0.0.1094867", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz", - "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==", + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } } } }, @@ -20535,139 +19571,28 @@ "safe-buffer": "^5.1.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true - } - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "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" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "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 - } - } - }, "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==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.0.tgz", + "integrity": "sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==", "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" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } } }, "readdir-glob": { @@ -20703,15 +19628,11 @@ "resolve": "^1.1.6" } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "regex-not": { "version": "1.0.2", @@ -20723,30 +19644,6 @@ "safe-regex": "^1.1.0" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "registry-auth-token": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", - "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", - "dev": true, - "requires": { - "rc": "1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -20768,6 +19665,21 @@ "through2": "^2.0.3" }, "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -20798,12 +19710,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, "replace-homedir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", @@ -20831,6 +19737,21 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } } } }, @@ -20944,10 +19865,46 @@ "dev": true }, "rimraf": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", - "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", - "dev": true + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", + "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", + "dev": true, + "requires": { + "glob": "^10.2.5" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.6.tgz", + "integrity": "sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + } + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } }, "rrweb-cssom": { "version": "0.6.0", @@ -20964,18 +19921,18 @@ } }, "rxjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", - "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "requires": { - "tslib": "~2.1.0" + "tslib": "^2.1.0" }, "dependencies": { "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", "dev": true } } @@ -21057,18 +20014,6 @@ "ieee754": "^1.2.1" } }, - "readable-stream": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", - "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10" - } - }, "tar-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.0.0.tgz", @@ -21186,9 +20131,9 @@ "dev": true }, "shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "dev": true }, "sigma": { @@ -21197,6 +20142,12 @@ "integrity": "sha512-9Z0m1pssXv6sndPMvOzXnM1mVO73YCWDE6X5bKxJyG+9J0B9zJkgtgoBM7cnxEaJMzmrbxPceKTVpwF7cS/xqA==", "dev": true }, + "signal-exit": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", + "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", + "dev": true + }, "sinon": { "version": "9.2.4", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", @@ -21417,22 +20368,11 @@ "dev": true }, "spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, - "spdx-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, - "requires": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -21465,32 +20405,6 @@ "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", "dev": true }, - "spdx-ranges": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true - }, - "spdx-satisfies": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz", - "integrity": "sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==", - "dev": true, - "requires": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -21584,37 +20498,6 @@ } } }, - "stream-combiner": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "through": "~2.3.4" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - } - } - }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -21671,6 +20554,12 @@ } } }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true + }, "streamx": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.13.2.tgz", @@ -21707,6 +20596,17 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -21724,6 +20624,23 @@ } } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + } + } + }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -21739,15 +20656,6 @@ "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", "dev": true }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -21867,31 +20775,9 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "requires": { - "readable-stream": "3" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "through2-filter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", @@ -21902,6 +20788,21 @@ "xtend": "~4.0.0" }, "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -22007,6 +20908,21 @@ "through2": "^2.0.3" }, "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -22053,12 +20969,6 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, - "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -22108,15 +21018,15 @@ "dev": true }, "typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", + "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", "dev": true }, "ua-parser-js": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", - "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", "dev": true }, "unbzip2-stream": { @@ -22167,6 +21077,15 @@ "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", "dev": true }, + "undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dev": true, + "requires": { + "busboy": "^1.6.0" + } + }, "union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -22325,15 +21244,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "requires": { - "builtins": "^1.0.3" - } - }, "validator": { "version": "13.7.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", @@ -22393,6 +21303,21 @@ "vinyl-sourcemap": "^1.1.0" }, "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -22449,28 +21374,28 @@ } }, "webdriver": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.3.11.tgz", - "integrity": "sha512-1Dw8tN+c+LdnIXizEB+RrH9LbHGuTEDUtPqxT0fd80F+g/RqZvX3bv3ABD7ZVvvdE2ck6XbATnVX2/rYrQxSYQ==", + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.11.1.tgz", + "integrity": "sha512-hSpUZYzUA65t4DDtKujCHUX6hpFTUleb7lWMcf5xjPz8sxWrK9R8NIw7pXt/GU6PVS331nGAaYkzoXrqz2VB8w==", "dev": true, "requires": { - "@types/node": "^18.0.0", + "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/protocols": "8.3.11", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", - "deepmerge-ts": "^4.2.2", - "got": "^12.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.11.0", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", + "deepmerge-ts": "^5.0.0", + "got": "^ 12.6.1", "ky": "^0.33.0", "ws": "^8.8.0" }, "dependencies": { "@sindresorhus/is": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", - "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.4.1.tgz", + "integrity": "sha512-axlrvsHlHlFmKKMEg4VyvMzFr93JWJj4eIfXY1STVuO2fsImCa7ncaiG5gC8HKOX590AW5RtRsC41/B+OfrSqw==", "dev": true }, "@szmarczak/http-timer": { @@ -22489,9 +21414,9 @@ "dev": true }, "cacheable-request": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.8.tgz", - "integrity": "sha512-IDVO5MJ4LItE6HKFQTqT2ocAQsisOoCTUDu1ddCmnhyiwFQjXNPp4081Xj23N4tO+AFEFNzGuNEf/c8Gwwt15A==", + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.10.tgz", + "integrity": "sha512-v6WB+Epm/qO4Hdlio/sfUn69r5Shgh39SsE9DSd4bIezP0mblOlObI+I0kUEM7J0JFc+I7pSeMeYaOYtX1N/VQ==", "dev": true, "requires": { "@types/http-cache-semantics": "^4.0.1", @@ -22510,15 +21435,15 @@ "dev": true }, "got": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/got/-/got-12.5.3.tgz", - "integrity": "sha512-8wKnb9MGU8IPGRIo+/ukTy9XLJBwDiCpIf5TVzQ9Cpol50eMTpBq2GAuDsuDIz7hTYmZgMgC1e9ydr6kSDWs3w==", + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "requires": { "@sindresorhus/is": "^5.2.0", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.1", + "cacheable-request": "^10.2.8", "decompress-response": "^6.0.0", "form-data-encoder": "^2.1.2", "get-stream": "^6.0.1", @@ -22574,36 +21499,36 @@ } }, "webdriverio": { - "version": "8.3.11", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.3.11.tgz", - "integrity": "sha512-sN2jmkwSQj0r9IRLvpzfmLh/OvX3l4G+l+W26ksoLr+qxdOfRmoNfxc4CGMt/Uin515ajajqPWrxFFbdeL3uIQ==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.11.2.tgz", + "integrity": "sha512-e/9WkdNTfWeoaSo2UzK0Giec/nQX3i7U9J8esimhozH/EpwSqIaEJ2pRRlxRVafEhe2OBG1QDhnLnDjdCC5Hxg==", "dev": true, "requires": { - "@types/node": "^18.0.0", - "@wdio/config": "8.3.11", - "@wdio/logger": "8.1.0", - "@wdio/protocols": "8.3.11", - "@wdio/repl": "8.1.0", - "@wdio/types": "8.3.0", - "@wdio/utils": "8.3.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.11.0", + "@wdio/logger": "8.11.0", + "@wdio/protocols": "8.11.0", + "@wdio/repl": "8.10.1", + "@wdio/types": "8.10.4", + "@wdio/utils": "8.11.0", "archiver": "^5.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "8.3.11", - "devtools-protocol": "^0.0.1103684", + "devtools": "8.11.0", + "devtools-protocol": "^0.0.1152884", "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^2.1.0", + "import-meta-resolve": "^3.0.0", "is-plain-obj": "^4.1.0", "lodash.clonedeep": "^4.5.0", "lodash.zip": "^4.2.0", - "minimatch": "^7.0.0", - "puppeteer-core": "19.7.1", + "minimatch": "^9.0.0", + "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "8.3.11" + "webdriver": "8.11.1" }, "dependencies": { "brace-expansion": { @@ -22622,9 +21547,9 @@ "dev": true }, "minimatch": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.2.0.tgz", - "integrity": "sha512-rMRHmwySzopAQjmWW6TkAKCEDKNaY/HuV/c2YkWWuWnfkTwApt0V4hnYzzPnZ/5Gcd2+8MPncSyuOGPl3xPvcg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -22689,7 +21614,8 @@ "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==" + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true }, "workerpool": { "version": "6.2.1", @@ -22708,6 +21634,17 @@ "strip-ansi": "^6.0.0" } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -22715,10 +21652,9 @@ "dev": true }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "dev": true, + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "requires": {} }, "xml-name-validator": { @@ -22750,15 +21686,15 @@ "dev": true }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", "dev": true }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", diff --git a/package.json b/package.json index 901b6caac..47b9e742e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "9.3.3", + "version": "10.0.0", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" @@ -33,11 +33,10 @@ "deployDemos:beta": "npm ci && gulp deployDemosBeta", "deps": "gulp deps", "docs": "gulp docs", - "format": "gulp format", - "format:sortrequires": "gulp sortRequires", + "format": "prettier --write .", + "format:check": "prettier --check .", "generate:langfiles": "exit 1 # Deprecated; use \"npm run messages\" instead.", "messages": "gulp messages", - "license": "gulp checkLicenses", "lint": "eslint .", "lint:fix": "eslint . --fix", "langfiles": "gulp langfiles", @@ -52,6 +51,7 @@ "start": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir 'build/src' --declarationDir 'build/declarations'\" \"http-server ./ -s -o /tests/playground.html -c-1\"", "tsc": "gulp tsc", "test": "gulp test", + "test:browser": "npx mocha ./tests/browser/test --config ./tests/browser/test/.mocharc.js", "test:generators": "gulp testGenerators", "test:mocha:interactive": "http-server ./ -o /tests/mocha/index.html -c-1", "test:compile:advanced": "gulp buildAdvancedCompilationTest --debug", @@ -71,23 +71,22 @@ "@blockly/block-test": "^3.0.0", "@blockly/dev-tools": "^5.0.0", "@blockly/theme-modern": "^3.0.0", - "@hyperjump/json-schema": "^0.23.3", - "@microsoft/api-documenter": "^7.19.16", + "@hyperjump/json-schema": "^1.5.0", + "@microsoft/api-documenter": "^7.22.4", "@microsoft/api-extractor": "^7.29.5", "@typescript-eslint/eslint-plugin": "^5.33.1", "@wdio/selenium-standalone-service": "^8.0.2", "async-done": "^2.0.0", "chai": "^4.2.0", - "clang-format": "^1.6.0", "closure-calculate-chunks": "^3.0.2", - "concurrently": "^7.4.0", + "concurrently": "^8.0.1", "eslint": "^8.4.1", "eslint-config-google": "^0.14.0", - "eslint-plugin-jsdoc": "^40.0.0", - "google-closure-compiler": "^20230206.0.0", - "google-closure-deps": "^20230206.0.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-jsdoc": "^46.2.6", + "google-closure-compiler": "^20230502.0.0", + "google-closure-deps": "^20230502.0.0", "gulp": "^4.0.2", - "gulp-clang-format": "^1.0.27", "gulp-concat": "^2.6.1", "gulp-gzip": "^1.4.2", "gulp-header": "^2.0.9", @@ -99,20 +98,19 @@ "gulp-sourcemaps": "^3.0.0", "gulp-umd": "^2.0.0", "http-server": "^14.0.0", - "js-green-licenses": "^4.0.0", "json5": "^2.2.0", "markdown-tables-to-json": "^0.1.7", "mocha": "^10.0.0", - "patch-package": "^6.4.7", + "patch-package": "^7.0.0", + "prettier": "2.8.8", "readline-sync": "^1.4.10", - "rimraf": "^4.0.7", + "rimraf": "^5.0.0", "selenium-standalone": "^8.0.3", - "through2": "^4.0.2", "typescript": "^5.0.2", "webdriverio": "^8.0.5", "yargs": "^17.2.1" }, "dependencies": { - "jsdom": "21.1.1" + "jsdom": "22.1.0" } } diff --git a/patches/@microsoft+api-documenter+7.19.24.patch b/patches/@microsoft+api-documenter+7.22.4.patch similarity index 82% rename from patches/@microsoft+api-documenter+7.19.24.patch rename to patches/@microsoft+api-documenter+7.22.4.patch index 9b11e786a..6e039a391 100644 --- a/patches/@microsoft+api-documenter+7.19.24.patch +++ b/patches/@microsoft+api-documenter+7.22.4.patch @@ -1,8 +1,8 @@ diff --git a/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js b/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js -index 421f89f..b9d329b 100644 +index 5284d10..f2f9d14 100644 --- a/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js +++ b/node_modules/@microsoft/api-documenter/lib/documenters/MarkdownDocumenter.js -@@ -856,12 +856,15 @@ class MarkdownDocumenter { +@@ -877,12 +877,15 @@ class MarkdownDocumenter { } _writeBreadcrumb(output, apiItem) { const configuration = this._tsdocConfiguration; @@ -24,7 +24,7 @@ index 421f89f..b9d329b 100644 for (const hierarchyItem of apiItem.getHierarchy()) { switch (hierarchyItem.kind) { case api_extractor_model_1.ApiItemKind.Model: -@@ -871,18 +874,24 @@ class MarkdownDocumenter { +@@ -892,18 +895,24 @@ class MarkdownDocumenter { // this may change in the future. break; default: @@ -33,10 +33,10 @@ index 421f89f..b9d329b 100644 - configuration, - text: ' > ' - }), -+ // Only print the breadcrumb separator if it's not the first item we're printing. + if (!first) { ++ // Only print the breadcrumb separator if it's not the first item we're printing. + output.appendNodeInParagraph( -+ new tsdoc_1.DocPlainText({ ++ new tsdoc_1.DocPlainText({ + configuration, + text: ' > ' + }) @@ -55,7 +55,7 @@ index 421f89f..b9d329b 100644 } } } -@@ -947,11 +956,13 @@ class MarkdownDocumenter { +@@ -968,11 +977,8 @@ class MarkdownDocumenter { // For overloaded methods, add a suffix such as "MyClass.myMethod_2". let qualifiedName = Utilities_1.Utilities.getSafeFilenameForName(hierarchyItem.displayName); if (api_extractor_model_1.ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { @@ -65,22 +65,16 @@ index 421f89f..b9d329b 100644 - qualifiedName += `_${hierarchyItem.overloadIndex - 1}`; - } + // https://github.com/microsoft/rushstack/issues/1921 -+ // if (hierarchyItem.overloadIndex > 1) { -+ // // Subtract one for compatibility with earlier releases of API Documenter. -+ // // (This will get revamped when we fix GitHub issue #1308) -+ // qualifiedName += `_${hierarchyItem.overloadIndex - 1}`; -+ // } + qualifiedName += `_${hierarchyItem.overloadIndex}`; } switch (hierarchyItem.kind) { case api_extractor_model_1.ApiItemKind.Model: -@@ -962,7 +973,9 @@ class MarkdownDocumenter { +@@ -983,7 +989,8 @@ class MarkdownDocumenter { baseName = Utilities_1.Utilities.getSafeFilenameForName(node_core_library_1.PackageName.getUnscopedName(hierarchyItem.displayName)); break; default: - baseName += '.' + qualifiedName; + // https://github.com/microsoft/rushstack/issues/1921 -+ // baseName += '.' + qualifiedName; + baseName += '.' + qualifiedName + '_' + hierarchyItem.kind.toLowerCase(); } } diff --git a/scripts/gulpfiles/appengine_tasks.js b/scripts/gulpfiles/appengine_tasks.js index 5e9f9e871..09f9e29a7 100644 --- a/scripts/gulpfiles/appengine_tasks.js +++ b/scripts/gulpfiles/appengine_tasks.js @@ -11,11 +11,11 @@ const gulp = require('gulp'); const fs = require('fs'); -const rimraf = require('rimraf'); const path = require('path'); const execSync = require('child_process').execSync; const buildTasks = require('./build_tasks.js'); const packageTasks = require('./package_tasks.js'); +const {rimraf} = require('rimraf'); const packageJson = require('../../package.json'); const demoTmpDir = '../_deploy'; diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index e16e8656e..4b84bbf59 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -16,12 +16,8 @@ gulp.sourcemaps = require('gulp-sourcemaps'); const path = require('path'); const fs = require('fs'); const {exec, execSync} = require('child_process'); -const through2 = require('through2'); -const clangFormat = require('clang-format'); -const clangFormatter = require('gulp-clang-format'); const closureCompiler = require('google-closure-compiler').gulp(); -const closureDeps = require('google-closure-deps'); const argv = require('yargs').argv; const {rimraf} = require('rimraf'); @@ -79,12 +75,15 @@ const NAMESPACE_PROPERTY = '__namespace__'; * chunk. * - .exports: an expression evaluating to the exports/Module object * of module that is the chunk's entrypoint / top level module. - * - .reexport: if running in a browser, save the chunk's exports - * object (or a single export of it; see reexportOnly, below) at - * this location in the global namespace. - * - .reexportOnly: if reexporting and this property is set, - * save only the correspondingly-named export. Otherwise - * save the whole export object. + * - .scriptExport: When the chunk is loaded as a script (e.g., via a + * - + Mocha Tests for Blockly @@ -16,8 +16,8 @@
- - + + @@ -34,20 +34,6 @@ loadCompressed: false, depsFiles: ['build/deps.js', 'build/deps.mocha.js'], requires: [ - // Blockly modules needed by tests. - 'Blockly', - 'Blockly.libraryBlocks', - 'Blockly.Dart', - 'Blockly.Dart.texts', - 'Blockly.JavaScript', - 'Blockly.JavaScript.texts', - 'Blockly.Lua', - 'Blockly.Lua.texts', - 'Blockly.PHP', - 'Blockly.PHP.texts', - 'Blockly.Python', - 'Blockly.Python.texts', - // Test modules. 'Blockly.test.astNode', 'Blockly.test.blockJson', @@ -65,6 +51,7 @@ 'Blockly.test.eventBlockCreate', 'Blockly.test.eventBlockDelete', 'Blockly.test.eventBlockDrag', + 'Blockly.test.eventBlockFieldIntermediateChange', 'Blockly.test.eventBlockMove', 'Blockly.test.eventBubbleOpen', 'Blockly.test.eventClick', @@ -98,6 +85,7 @@ 'Blockly.test.flyout', 'Blockly.test.generator', 'Blockly.test.gesture', + 'Blockly.test.icon', 'Blockly.test.input', 'Blockly.test.insertionMarker', 'Blockly.test.insertionMarkerManager', @@ -113,6 +101,7 @@ 'Blockly.test.procedureMap', 'Blockly.test.procedures', 'Blockly.test.registry', + 'Blockly.test.renderManagement', 'Blockly.test.serialization', 'Blockly.test.shortcutRegistry', 'Blockly.test.touch', @@ -131,18 +120,21 @@ 'Blockly.test.xml', 'Blockly.test.zoomControls', ], - additionalScripts: [ + scripts: [ 'build/msg/en.js', 'tests/playgrounds/screenshot.js', 'node_modules/@blockly/dev-tools/dist/index.js', ], - } + };
-