From 91aa7221ca92823813bb6cffa7419755de20494a Mon Sep 17 00:00:00 2001 From: will wade Date: Tue, 16 Jun 2026 19:15:22 +0100 Subject: [PATCH] Replace JS demo with WASM DasherCore build - Add WASM build of native DasherCore C++ engine via Emscripten - Add DasherCore as git submodule (branch feature-CAPI) - Add demo.html with canvas rendering, mouse/touch input, speed control, auto-speed toggle, learning toggle - Add dasher-wasm-wrapper.js JS API wrapper - Add GitHub Actions workflow to build and deploy to Pages - Remove old browser/ JS demo, tests/, Keyboard/ - Update all copyright notices to dasher-project contributors - Rewrite README with build instructions and embedding docs --- .github/workflows/build-deploy.yml | 72 + .github/workflows/main.yml | 24 - .github/workflows/test.yml | 61 - .gitmodules | 4 + Keyboard/readme.md | 4 - browser/dasher/autoSpeedControl.js | 84 - browser/dasher/controllerpointer.js | 466 - browser/dasher/controllerrandom.js | 121 - browser/dasher/controlpanel.css | 75 - browser/dasher/controlpanel.js | 1012 - browser/dasher/controlpanelspecification.js | 162 - browser/dasher/keyhandler.css | 25 - browser/dasher/keyhandler.js | 70 - browser/dasher/languageManager.js | 535 - browser/dasher/languagePalette.js | 176 - browser/dasher/limits.js | 236 - browser/dasher/messageDisplay.js | 83 - browser/dasher/messageStore.js | 182 - browser/dasher/palette.js | 178 - browser/dasher/piece.js | 194 - browser/dasher/pointer.js | 362 - browser/dasher/predictor.js | 97 - browser/dasher/predictor_dummy.js | 38 - browser/dasher/predictor_ppm.js | 245 - browser/dasher/predictor_ppm_new.js | 593 - browser/dasher/predictor_test.js | 48 - browser/dasher/speech.js | 104 - browser/dasher/third_party/gutenberg/LICENSE | 1 - .../dasher/third_party/gutenberg/README.md | 10 - browser/dasher/third_party/gutenberg/alice.js | 3777 -- .../dasher/third_party/gutenberg/sherlock.js | 12765 ------- browser/dasher/third_party/jslm/LICENSE | 202 - browser/dasher/third_party/jslm/README.md | 76 - .../third_party/jslm/ppm_language_model.js | 504 - browser/dasher/third_party/jslm/vocabulary.js | 91 - browser/dasher/third_party/readme.md | 9 - browser/dasher/userinterface.css | 791 - browser/dasher/userinterface.js | 2066 -- browser/dasher/viewer.js | 423 - browser/dasher/zoombox.js | 374 - browser/favicon.ico | Bin 318 -> 0 bytes browser/index.html | 41 - browser/index.js | 15 - browser/pagebuilder.css | 187 - browser/pagebuilder.js | 389 - browser/vendor/README.md | 13 - browser/vendor/ppmpredictor.esm.js | 2618 -- browser/vendor/ppmpredictor.esm.js.map | 1 - documents/Backlog.md | 2 +- documents/Development.md | 2 +- documents/Loading.md | 2 +- .../ZoomingUserInterface.md | 2 +- .../02BasicDefinitions/BasicDefinitions.md | 2 +- .../03ZoomingRules/ZoomingRules.md | 2 +- .../04ZoomingSolver/ZoomingSolver.md | 2 +- .../05ZoomBoxPalette/ZoomBoxPalette.md | 2 +- .../Specification/EditingGuide/readme.md | 2 +- documents/Specification/notes.md | 2 +- documents/Specification/readme.md | 2 +- index.html | 43 - package.json | 12 +- readme.md | 158 +- tests/.eslintrc.yml | 29 - tests/babel.config.js | 13 - tests/cypress.config.js | 12 - tests/cypress/integration/Dasher_spec.js | 16 - tests/cypress/plugins/index.js | 22 - .../Dasher -- A screenshot will be saved.png | Bin 76123 -> 0 bytes tests/cypress/support/commands.js | 25 - tests/cypress/support/index.js | 20 - tests/jest.config.js | 194 - tests/jest.setup.js | 16 - tests/jest/messageStore.test.js | 23 - tests/package-lock.json | 10395 ------ tests/package.json | 28 - tests/readme.md | 18 - tests/screenshot.png | Bin 50440 -> 0 bytes wasm-build/.gitignore | 7 + wasm-build/README.md | 176 + wasm-build/build.bat | 78 + wasm-build/build.sh | 70 + wasm-build/dasher-wasm-wrapper.js | 411 + wasm-build/dashercore-src | 1 + .../data-bundle/Strings/strings_en.json | 221 + ...eutsch.german.with.limited.punctuation.xml | 216 + ...h.german.with.numerals.and.punctuation.xml | 468 + wasm-build/data-bundle/alphabets/alphabet.dtd | 82 + .../alphabets/alphabet.english.latex.xml | 401 + .../alphabets/alphabet.english.lower.case.xml | 140 + ...lish.with.accents.numerals.punctuation.xml | 502 + ...habet.english.with.limited.punctuation.xml | 265 + ....with.numerals.and.limited.punctuation.xml | 303 + ....with.numerals.and.lots.of.punctuation.xml | 439 + .../alphabet.english.without.punctuation.xml | 224 + .../alphabets/alphabet.wa.english.en-Latn.xml | 215 + .../alphabets/alphabet.wa.french.fr-Latn.xml | 311 + .../alphabets/alphabet.wa.german.de-Latn.xml | 239 + .../alphabets/alphabet.wa.spanish.es-Latn.xml | 257 + wasm-build/data-bundle/colour/colour.blue.xml | 426 + wasm-build/data-bundle/colour/colour.ean.xml | 273 + .../data-bundle/colour/colour.euroasian.xml | 415 + .../data-bundle/colour/colour.euroasian2.xml | 430 + .../data-bundle/colour/colour.jamie.xml | 847 + .../data-bundle/colour/colour.rainbow.xml | 414 + wasm-build/data-bundle/colour/colour.thai.xml | 448 + .../data-bundle/colour/colour.turbo.xml | 416 + .../data-bundle/colour/colour.vowels.xml | 432 + .../data-bundle/colour/colour.vowels2.xml | 476 + wasm-build/data-bundle/colour/colour.xml | 270 + .../data-bundle/colours/colour.blue.xml | 426 + wasm-build/data-bundle/colours/colour.ean.xml | 273 + .../data-bundle/colours/colour.euroasian.xml | 415 + .../data-bundle/colours/colour.euroasian2.xml | 430 + .../data-bundle/colours/colour.jamie.xml | 847 + .../data-bundle/colours/colour.rainbow.xml | 414 + .../data-bundle/colours/colour.thai.xml | 448 + .../data-bundle/colours/colour.turbo.xml | 416 + .../data-bundle/colours/colour.vowels.xml | 432 + .../data-bundle/colours/colour.vowels2.xml | 476 + .../training/gamemode_english_GB.txt | 183 + .../training/training_english_GB.txt | 664 + .../training/training_wa_de_Latn.txt | 30765 ++++++++++++++++ .../training/training_wa_en_Latn.txt | 30765 ++++++++++++++++ .../training/training_wa_es_Latn.txt | 30762 +++++++++++++++ .../training/training_wa_fr_Latn.txt | 30764 +++++++++++++++ wasm-build/demo.html | 358 + wasm-build/exports.json | 81 + wasm-build/package.json | 29 + wasm-build/server.js | 69 + 129 files changed, 139112 insertions(+), 40428 deletions(-) create mode 100644 .github/workflows/build-deploy.yml delete mode 100644 .github/workflows/main.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 Keyboard/readme.md delete mode 100644 browser/dasher/autoSpeedControl.js delete mode 100644 browser/dasher/controllerpointer.js delete mode 100644 browser/dasher/controllerrandom.js delete mode 100644 browser/dasher/controlpanel.css delete mode 100644 browser/dasher/controlpanel.js delete mode 100644 browser/dasher/controlpanelspecification.js delete mode 100644 browser/dasher/keyhandler.css delete mode 100644 browser/dasher/keyhandler.js delete mode 100644 browser/dasher/languageManager.js delete mode 100644 browser/dasher/languagePalette.js delete mode 100644 browser/dasher/limits.js delete mode 100644 browser/dasher/messageDisplay.js delete mode 100644 browser/dasher/messageStore.js delete mode 100644 browser/dasher/palette.js delete mode 100644 browser/dasher/piece.js delete mode 100644 browser/dasher/pointer.js delete mode 100644 browser/dasher/predictor.js delete mode 100644 browser/dasher/predictor_dummy.js delete mode 100644 browser/dasher/predictor_ppm.js delete mode 100644 browser/dasher/predictor_ppm_new.js delete mode 100644 browser/dasher/predictor_test.js delete mode 100644 browser/dasher/speech.js delete mode 100644 browser/dasher/third_party/gutenberg/LICENSE delete mode 100644 browser/dasher/third_party/gutenberg/README.md delete mode 100644 browser/dasher/third_party/gutenberg/alice.js delete mode 100644 browser/dasher/third_party/gutenberg/sherlock.js delete mode 100644 browser/dasher/third_party/jslm/LICENSE delete mode 100644 browser/dasher/third_party/jslm/README.md delete mode 100644 browser/dasher/third_party/jslm/ppm_language_model.js delete mode 100644 browser/dasher/third_party/jslm/vocabulary.js delete mode 100644 browser/dasher/third_party/readme.md delete mode 100644 browser/dasher/userinterface.css delete mode 100644 browser/dasher/userinterface.js delete mode 100644 browser/dasher/viewer.js delete mode 100644 browser/dasher/zoombox.js delete mode 100644 browser/favicon.ico delete mode 100644 browser/index.html delete mode 100644 browser/index.js delete mode 100644 browser/pagebuilder.css delete mode 100644 browser/pagebuilder.js delete mode 100644 browser/vendor/README.md delete mode 100644 browser/vendor/ppmpredictor.esm.js delete mode 100644 browser/vendor/ppmpredictor.esm.js.map delete mode 100644 index.html delete mode 100644 tests/.eslintrc.yml delete mode 100644 tests/babel.config.js delete mode 100644 tests/cypress.config.js delete mode 100644 tests/cypress/integration/Dasher_spec.js delete mode 100644 tests/cypress/plugins/index.js delete mode 100644 tests/cypress/screenshots/Dasher_spec.js/Dasher -- A screenshot will be saved.png delete mode 100644 tests/cypress/support/commands.js delete mode 100644 tests/cypress/support/index.js delete mode 100644 tests/jest.config.js delete mode 100644 tests/jest.setup.js delete mode 100644 tests/jest/messageStore.test.js delete mode 100644 tests/package-lock.json delete mode 100644 tests/package.json delete mode 100644 tests/readme.md delete mode 100644 tests/screenshot.png create mode 100644 wasm-build/.gitignore create mode 100644 wasm-build/README.md create mode 100644 wasm-build/build.bat create mode 100644 wasm-build/build.sh create mode 100644 wasm-build/dasher-wasm-wrapper.js create mode 160000 wasm-build/dashercore-src create mode 100644 wasm-build/data-bundle/Strings/strings_en.json create mode 100644 wasm-build/data-bundle/alphabets/alphabet.deutsch.german.with.limited.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.deutsch.german.with.numerals.and.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.dtd create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.latex.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.lower.case.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.with.accents.numerals.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.with.limited.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.with.numerals.and.limited.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.with.numerals.and.lots.of.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.english.without.punctuation.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.wa.english.en-Latn.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.wa.french.fr-Latn.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.wa.german.de-Latn.xml create mode 100644 wasm-build/data-bundle/alphabets/alphabet.wa.spanish.es-Latn.xml create mode 100644 wasm-build/data-bundle/colour/colour.blue.xml create mode 100644 wasm-build/data-bundle/colour/colour.ean.xml create mode 100644 wasm-build/data-bundle/colour/colour.euroasian.xml create mode 100644 wasm-build/data-bundle/colour/colour.euroasian2.xml create mode 100644 wasm-build/data-bundle/colour/colour.jamie.xml create mode 100644 wasm-build/data-bundle/colour/colour.rainbow.xml create mode 100644 wasm-build/data-bundle/colour/colour.thai.xml create mode 100644 wasm-build/data-bundle/colour/colour.turbo.xml create mode 100644 wasm-build/data-bundle/colour/colour.vowels.xml create mode 100644 wasm-build/data-bundle/colour/colour.vowels2.xml create mode 100644 wasm-build/data-bundle/colour/colour.xml create mode 100644 wasm-build/data-bundle/colours/colour.blue.xml create mode 100644 wasm-build/data-bundle/colours/colour.ean.xml create mode 100644 wasm-build/data-bundle/colours/colour.euroasian.xml create mode 100644 wasm-build/data-bundle/colours/colour.euroasian2.xml create mode 100644 wasm-build/data-bundle/colours/colour.jamie.xml create mode 100644 wasm-build/data-bundle/colours/colour.rainbow.xml create mode 100644 wasm-build/data-bundle/colours/colour.thai.xml create mode 100644 wasm-build/data-bundle/colours/colour.turbo.xml create mode 100644 wasm-build/data-bundle/colours/colour.vowels.xml create mode 100644 wasm-build/data-bundle/colours/colour.vowels2.xml create mode 100644 wasm-build/data-bundle/training/gamemode_english_GB.txt create mode 100644 wasm-build/data-bundle/training/training_english_GB.txt create mode 100644 wasm-build/data-bundle/training/training_wa_de_Latn.txt create mode 100644 wasm-build/data-bundle/training/training_wa_en_Latn.txt create mode 100644 wasm-build/data-bundle/training/training_wa_es_Latn.txt create mode 100644 wasm-build/data-bundle/training/training_wa_fr_Latn.txt create mode 100644 wasm-build/demo.html create mode 100644 wasm-build/exports.json create mode 100644 wasm-build/package.json create mode 100644 wasm-build/server.js diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml new file mode 100644 index 0000000..af82a1a --- /dev/null +++ b/.github/workflows/build-deploy.yml @@ -0,0 +1,72 @@ +name: Build and Deploy Demo + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Emscripten + uses: mymindstorm/setup-emsdk@v14 + with: + version: latest + + - name: Verify emcc + run: emcc --version + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install Ninja + run: sudo apt-get update && sudo apt-get install -y ninja-build + + - name: Build WASM module + working-directory: wasm-build + run: bash build.sh + + - name: Stage demo for Pages + run: | + mkdir -p _site + cp wasm-build/demo.html _site/index.html + cp wasm-build/dasher-wasm-wrapper.js _site/ + cp -r wasm-build/wasm _site/wasm + touch _site/.nojekyll + + - name: Upload artifact + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 660eac9..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: CI - -on: pull_request -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: tests/package-lock.json - - - name: Install dependencies - working-directory: tests - run: npm ci - - - name: Run ESLint - working-directory: tests - run: npm run eslint - continue-on-error: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 2f40946..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Test - -on: - push: - pull_request: - -jobs: - unit: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - node-version: [22] - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: npm - cache-dependency-path: tests/package-lock.json - - - name: Install dependencies - working-directory: tests - run: npm ci - - - name: Run Jest tests - working-directory: tests - run: npm run test - - - name: Run ESLint - working-directory: tests - run: npm run eslint - continue-on-error: true - - e2e: - runs-on: ubuntu-latest - needs: unit - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: tests/package-lock.json - - - name: Install app dependencies - run: npm ci - - - name: Install dependencies - working-directory: tests - run: npm ci - - - name: Run Cypress tests - working-directory: tests - run: npm run cy:test diff --git a/.gitmodules b/.gitmodules index e69de29..eb2135b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "wasm-build/dashercore-src"] + path = wasm-build/dashercore-src + url = https://github.com/dasher-project/DasherCore.git + branch = feature-CAPI diff --git a/Keyboard/readme.md b/Keyboard/readme.md deleted file mode 100644 index f68223b..0000000 --- a/Keyboard/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -# Keyboard Proof of Concept -This directory was for custom keyboard proofs of concept. That code has been -moved to another repository. See: -[https://github.com/dasher-project/dasher-captivewebview/tree/main/Keyboard](https://github.com/dasher-project/dasher-captivewebview/tree/main/Keyboard) diff --git a/browser/dasher/autoSpeedControl.js b/browser/dasher/autoSpeedControl.js deleted file mode 100644 index 67823db..0000000 --- a/browser/dasher/autoSpeedControl.js +++ /dev/null @@ -1,84 +0,0 @@ -// (c) 2026 The ACE Centre-North, UK registered charity 1089313. -// MIT licensed, see https://opensource.org/licenses/MIT - -export default class AutoSpeedControl { - constructor() { - this._factor = 1; - this._angles = []; - this._maxSamples = 24; - this._minFactor = 0.65; - this._maxFactor = 1.35; - this._minRadius = 55; - this._sampleScale = 480; - this._sampleOffset = 10; - this._upRate = 0.015; - this._downRate = 0.03; - this._lowVariance = 0.12; - this._highVariance = 0.32; - } - - get factor() { - return this._factor; - } - - reset() { - this._factor = 1; - this._angles = []; - } - - update(rawX, rawY, baseSpeed, elapsedMillis) { - if ( - typeof rawX !== 'number' || - typeof rawY !== 'number' || - typeof baseSpeed !== 'number' - ) { - return this._factor; - } - - const radius = Math.hypot(rawX, rawY); - if (radius < this._minRadius || baseSpeed <= 0) { - this._angles = []; - this._factor += (1 - this._factor) * 0.08; - return this._factor; - } - - const sampleTarget = Math.max( - 8, - Math.round(this._sampleScale / Math.max(0.01, baseSpeed) + this._sampleOffset), - ); - this._maxSamples = Math.min(60, sampleTarget); - - this._angles.push(Math.atan2(rawY, rawX)); - if (this._angles.length > this._maxSamples) { - this._angles.shift(); - } - - if (this._angles.length < 8) { - return this._factor; - } - - let meanX = 0; - let meanY = 0; - this._angles.forEach((angle) => { - meanX += Math.cos(angle); - meanY += Math.sin(angle); - }); - meanX /= this._angles.length; - meanY /= this._angles.length; - - const meanVectorLength = Math.hypot(meanX, meanY); - const variance = 1 - Math.min(1, meanVectorLength); - const stepScale = Math.max(0.4, Math.min(2.2, elapsedMillis / 33)); - - if (variance < this._lowVariance) { - this._factor += this._upRate * stepScale; - } else if (variance > this._highVariance) { - this._factor -= this._downRate * stepScale; - } else { - this._factor += (1 - this._factor) * 0.02 * stepScale; - } - - this._factor = Math.max(this._minFactor, Math.min(this._maxFactor, this._factor)); - return this._factor; - } -} diff --git a/browser/dasher/controllerpointer.js b/browser/dasher/controllerpointer.js deleted file mode 100644 index a9c9b26..0000000 --- a/browser/dasher/controllerpointer.js +++ /dev/null @@ -1,466 +0,0 @@ -// (c) 2020 The ACE Centre-North, UK registered charity 1089313. -// MIT licensed, see https://opensource.org/licenses/MIT - -export default class ControllerPointer { - constructor(pointer, predictor) { - this._pointer = pointer; - this._predictor = predictor; - - this._frozen = null; - this._frozenTarget = null; - } - - get predictor() { - return this._predictor; - } - set predictor(predictor) { - this._predictor = predictor; - } - - get frozen() { - return this._frozen; - } - set frozen(frozen) { - if (frozen !== null && this._frozen === null) { - this._frozenTarget = null; - } - this._frozen = frozen; - } - - get going() { - return this._pointer.going; - } - - async populate(rootBox, limits) { - // Comment out one or other of the following. - - // // Set left; solve height. - // const width = this.limits.spawnMargin * 2; - // const left = this._limits.right - width; - // const height = this._limits.solve_height(left); - - // Set height; solve left. - const height = limits.height / 2; - const left = limits.solve_left(height); - const width = limits.right - left; - rootBox.set_dimensions(left, width, 0, height); - - this._configure_box(rootBox); - this._configure_child_boxes(rootBox); - - if (rootBox.factoryData.totalWeight === undefined) { - await this._predict_weights(rootBox, limits); - } else { - this._arrange_children(rootBox, limits); - } - } - - _configure_child_boxes(parentBox) { - parentBox.instantiate_child_boxes(this._configure_box.bind(this)); - } - _configure_box(parentBox) { - if (parentBox.factoryData === undefined) { - parentBox.factoryData = { - 'weight': parentBox.template.weight, - 'totalWeight': undefined, - 'gettingWeights': false, - }; - } - } - - async _predict_weights(parentBox, limits) { - if (parentBox.factoryData.gettingWeights) { - return; - } - parentBox.factoryData.gettingWeights = true; - - if (parentBox.template.cssClass === null) { - await this.predictor( - parentBox.messageCodePoints, parentBox.message, - parentBox.factoryData.predictorData, - parentBox.template.palette, - this._set_weight.bind(this, parentBox), - ); - } - - // Weights of all child boxes have been set. Now calculate the total - // child box weight. - // Child boxes that are groups don't have their own weights. Instead, - // they get the sum of their child box weights. - parentBox.childBoxes.forEach((child) => { - if ( - child.template.weight === null && - child.factoryData.totalWeight === undefined - ) { - child.factoryData.weight = this._calculate_total_weight(child); - } - }); - this._calculate_total_weight(parentBox); - - parentBox.factoryData.gettingWeights = false; - - this._arrange_children(parentBox, limits); - } - _calculate_total_weight(zoomBox) { - const totalWeight = zoomBox.childBoxes.reduce( - (accumulator, child) => - accumulator + child.factoryData.weight, 0); - zoomBox.factoryData.totalWeight = totalWeight; - // console.log( - // `total weight "${zoomBox.message}"`, zoomBox.template.cssClass, - // totalWeight - // ); - return totalWeight; - } - - _set_weight(parentBox, codePoint, weight, predictorData) { - const path = parentBox.template.palette.indexMap.get(codePoint); - if (path === undefined) { - throw new Error([ - `Set weight outside palette ${codePoint}`, - ` "${String.fromCodePoint(codePoint)}"`, - ` under "${parentBox.message}".`, - ].join('')); - } - let target = parentBox; - for (const index of path) { - target = target.childBoxes[index]; - } - target.factoryData.weight = weight; - target.factoryData.predictorData = predictorData; - } - - // Arrange some or all child boxes. There are three procedures, selected by - // the `up` parameter: - // - // - up:undefined - // Assume this box already has its top set and arrange child boxes to - // occupy it. - // - up:true - // Arrange all the child boxes above an initialiser specified by its - // index. Assume that the initialiser has its top set. - // - up:false - // Arrange all the child boxes below an initialiser specified by its - // index. Assume that the initialiser has its bottom set. - // - // Returns the bottom or top value of the last child arranged. - // - // This method is where child boxes get their dimension set, including child - // boxes that have only just become big enough to render. This method is - // also where child boxes get erased and their grandchild boxes deleted. - _arrange_children(parentBox, limits, up, initialiser) { - const unitHeight = parentBox.height / parentBox.factoryData.totalWeight; - - let childTop; - let childBottom; - if (up === undefined) { - childTop = parentBox.top; - initialiser = -1; - up = false; - } else if (up) { - childBottom = parentBox.childBoxes[initialiser].top; - } else { - childTop = parentBox.childBoxes[initialiser].bottom; - } - const direction = (up ? -1 : 1); - - const childLength = parentBox.childBoxes.length; - for ( - let index = initialiser + direction; - index >= 0 && index < childLength; - index += direction - ) { - const childBox = parentBox.childBoxes[index]; - const childHeight = childBox.factoryData.weight * unitHeight; - if (up) { - childTop = childBottom - childHeight; - } else { - childBottom = childTop + childHeight; - } - - const shouldSpawn = ( - limits.spawnThreshold === undefined || - childHeight >= limits.spawnThreshold) && - childBottom > limits.top && childTop < limits.bottom; - - if (shouldSpawn) { - const childLeft = limits.solve_left(childHeight); - const childWidth = limits.width - childLeft; - - childBox.set_dimensions( - childLeft, childWidth, - childBottom - (childHeight / 2), childHeight); - - this._configure_child_boxes(childBox); - if (childBox.factoryData.totalWeight === undefined) { - // Next function is async but this code doesn't wait for it - // to finish. - this._predict_weights(childBox, limits); - } else { - this._arrange_children(childBox, limits); - } - } else { - childBox.erase(); - // Clear the child boxes only if their weights could be - // generated again. - if ( - childBox.template.cssClass === null && - childBox.childBoxes !== undefined - ) { - // console.log(`erasing childs "${childBox.cssClass} "${childBox.message}"`); - if (!childBox.factoryData.gettingWeights) { - // throw new Error( - // 'Erasing when prediction is in progress.') - childBox.clear_child_boxes(); - childBox.factoryData.totalWeight = undefined; - } - } - } - - if (up) { - childBottom -= childHeight; - } else { - childTop += childHeight; - } - } - return up ? childBottom : childTop; - } - - control(rootBox, limits) { - if (!this.going) { - return; - } - - if (this.report_frozen(rootBox, limits, true)) { - return; - } - - // Select a target to which the move will be applied. - // Target is the box at the right-hand edge of the window and at the - // same height as the pointer. - // Subtract one from the limit because boxes extend exactly to the - // edge. - const path = []; - const maxRight = limits.solverRight - 3; - const holderX = ( - limits.targetRight ? limits.solverRight : ( - this._pointer.rawX > maxRight ? maxRight : this._pointer.rawX - ) - ); - const target = rootBox.holder(holderX, this._pointer.rawY, path); - - let moveX; - let moveY; - if (limits.targetRight) { - moveX = 0 - this._pointer.x; - moveY = this._pointer.y; - } else { - const calculation = this._calculate_move(target, limits); - moveX = calculation.moveX; - moveY = calculation.moveY; - } - - if (target === null) { - // If the pointer is outside even the root box, apply the move - // to the root box anyway. - path[0] = -1; - } - - const applied = this._apply_move( - rootBox, moveX, moveY, path, limits, 0); - if (!applied) { - console.log('Not applied.'); - } - } - - report_frozen(rootBox, limits, onlyIfTargetChanged=false) { - if (this.frozen === null) { - return false; - } - // Pointer Y is positive upwards but Box Y is positive downwards. X has - // the same sense in both Pointer and Box. The holder method adjusts for - // that already. - const target = rootBox.holder( - this._pointer.rawX, this._pointer.rawY, []); - if (onlyIfTargetChanged && ( - target === null || target === this._frozenTarget - )) { - return true; - } - this._frozenTarget = target; - // const report = {"box": target, "p0x": p0x, "p0y": p0y}; - this._frozen(this._calculate_move(target, limits)); - return true; - } - - _calculate_move(target, limits) { - const p0x = this._pointer.rawX; - const p0y = 0 - this._pointer.rawY; - const calculation = { - 'box': target, 'p0x': p0x, 'p0y': p0y, - 'lx': limits.left, 'rx': limits.solverRight}; - if (target === null) { - Object.assign(calculation, { - 'moveX': 0 - this._pointer.x, - 'moveY': this._pointer.y, - }); - } else { - const pointerX = this._pointer.x; - let tx1 = target.left - pointerX; - let adjustX = null; - if (tx1 < limits.left && pointerX < 0) { - adjustX = (limits.left - tx1) * p0x / limits.left; - tx1 += adjustX; - } - const h1 = limits.solve_height(tx1); - const on0 = (p0y - target.middle) / target.height; - const p1y = p0y + this._pointer.y; - // Algebra here: - // on0 = (p1y - ty1) / h1 - // (on0 * h1) - p1y = -1 * ty1 - // p1y - (on0 * h1) = ty1 - const ty1 = p1y - (on0 * h1); - const on1 = (p1y - ty1) / h1; - - Object.assign(calculation, { - 'message': target.message, - 'adjustX': adjustX, - 'on0': on0, 'on1': on1, - 'h0': target.height, 'h1': h1, - 'tx0': target.left, 'tx1': tx1, - 'ty0': target.middle, 'ty1': ty1, - 'moveX': tx1 - target.left, 'moveY': ty1 - target.middle, - }); - } - - return calculation; - } - - _apply_move(hereBox, moveX, moveY, path, limits, position) { - const index = path[position]; - const isRootBox = position === 0; - - if (index === -1) { - // End of the path; attempt to apply the move here. - return this._apply_move_here( - hereBox, moveX, moveY, limits, isRootBox); - } - - // Attempt to apply the move to the specified child. - const target = hereBox.childBoxes[index]; - const applied = this._apply_move( - target, moveX, moveY, path, limits, position + 1); - if (!applied) { - // If it wasn't applied to a child, attempt to apply here instead. - return this._apply_move_here( - hereBox, moveX, moveY, limits, isRootBox); - } - - // Now adjust this box so that it is congruent to the new height of the - // target child. The following must become true: - // this.child_weight(index) * unitHeight = target.height - // Where: - // unitHeight = this.height / this.totalWeight - // Therefore: - const height = ( - (target.height / target.factoryData.weight) * - hereBox.factoryData.totalWeight); - - // Unless this is the root box and adjusting its size would cause its - // rect to be erased. In that case, undo the move by arranging the child - // boxes based on this box's current height and position. - if (isRootBox && height <= limits.drawThresholdRect) { - console.log('Undo height', height, limits.drawThresholdRect); - this._arrange_children(hereBox, limits); - return false; - } - - hereBox.height = height; - - // Arrange the child boxes, in two parts. First, push up everything - // above the target. Second, push down everything below the target. - const top = this._arrange_children(hereBox, limits, true, index); - if (top === undefined) { - throw new Error('Top undefined.'); - } - this._arrange_children(hereBox, limits, false, index); - - // Finalise the adjustment to this box. - hereBox.left = limits.solve_left(height); - hereBox.width = limits.width - hereBox.left; - hereBox.middle = top + (height / 2); - - return true; - } - - _apply_move_here(hereBox, moveX, moveY, limits, isRootBox) { - const movedLeft = hereBox.left + moveX; - // At any point to the right of the last gradient, the solver will - // return a minimum height. This has some undesirable side effects. - // These can be avoided by rejecting the move if it would put this box - // into that zone. - if (movedLeft >= limits.solverRight) { - if (isRootBox) { - console.log('AMH left', movedLeft, limits.solverRight); - } - return false; - } - - const solvedHeight = limits.solve_height(movedLeft); - // The root box shouldn't ever have its rect erased. If this move is - // being applied to the root box, and would result in erasure, reject - // the move. - if (isRootBox && solvedHeight <= limits.drawThresholdRect) { - console.log('AMH height', solvedHeight, limits.drawThresholdRect); - return false; - } - - hereBox.left = movedLeft; - hereBox.width = limits.width - hereBox.left; - hereBox.height = solvedHeight; - hereBox.middle += moveY; - this._arrange_children(hereBox, limits); - return true; - } - - build(parentBox, childBox, limits) { - const index = childBox.trimmedIndex; - - // Code here is meant to arrange the parent in such a way that the - // child height doesn't change. Check that by storing the height here. - const childHeightBefore = Math.round(childBox.height * 100); - - // Calculate the parent height from the height of the child, via the - // parent unitHeight. Then solve the left position of the parent - // from its height. Set width as usual. - const unitHeight = childBox.height / childBox.factoryData.weight; - const height = unitHeight * parentBox.factoryData.totalWeight; - const left = limits.solve_left(height); - const width = limits.width - left; - parentBox.set_dimensions(left, width, undefined, height); - - // Now calculate the top of the parent by adding the tops of all the - // child boxes above this one. They will all have undefined dimensions - // but won't have zero weight. Fortunately, the same calculation is - // required by another method, so call it here. - const top = this._arrange_children(parentBox, limits, true, index); - if (top === undefined) { - throw new Error('Top undefined.'); - } - parentBox.middle = top + (height / 2); - - // Clear the parent insertion parameters. - childBox.trimmedParent = null; - childBox.trimmedIndex = undefined; - - this._arrange_children(parentBox, limits); - - // Check the child height wasn't changed. This should maybe be an - // assertion except that asserting in production would be awkward. - const childHeightAfter = Math.round(childBox.height * 100); - if (childHeightAfter !== childHeightBefore) { - console.log( - 'build check failed', childHeightBefore, childHeightAfter); - } - } -} diff --git a/browser/dasher/controllerrandom.js b/browser/dasher/controllerrandom.js deleted file mode 100644 index 030db81..0000000 --- a/browser/dasher/controllerrandom.js +++ /dev/null @@ -1,121 +0,0 @@ -// (c) 2020 The ACE Centre-North, UK registered charity 1089313. -// MIT licensed, see https://opensource.org/licenses/MIT - -import Palette from './palette.js'; - -class PaletteSmall extends Palette { - // Override: - constructor(texts) { - super(); - this._groupDefinitions = [{'name': null, 'texts': texts}]; - } - - // Override: - build() { - super.build(); - this.rootTemplate.childTemplates.forEach( - (template) => template.childTemplates = [], - ); - // console.log("PaletteSmall", this.rootTemplate); - return this; - } - - // Override: - get groupDefinitions() { - return this._groupDefinitions; - } -} - -export default class ControllerRandom { - constructor(texts) { - this._palette = new PaletteSmall(texts).build(); - this._rectHeight = undefined; - this._going = true; - } - - get palette() { - return this._palette; - } - - get going() { - return this._going; - } - set going(going) { - this._going = going; - } - - control(rootBox) { - if (this._rectHeight === undefined || !this.going) { - return; - } - - const heightMin = this._rectHeight * 0.75; - const heightMax = this._rectHeight * 1.75; - const widthMin = this._rectHeight * 2; - const widthMax = rootBox.width; - let top = rootBox.top; - rootBox.childBoxes.forEach((childBox) => { - const xChange = childBox.controllerData.xChange; - const yChange = childBox.controllerData.yChange; - const xDelta = (50 + Math.random() * 250) * xChange; - const yDelta = heightMin * Math.random() * yChange; - - let left; - let width = childBox.width + xDelta; - if ( - (width < widthMin && xChange < 0) || - (width > widthMax && xChange > 0) - ) { - // Reverse direction; don't move. - childBox.controllerData.xChange *= -1; - width = undefined; - } else { - left = (rootBox.left + rootBox.width) - width; - } - - let height = childBox.height; - if ( - (height + yDelta < heightMin && yChange < 0) || - (height + yDelta > heightMax && yChange > 0) - ) { - // Reverse direction, don't change height. But, top will - // still have to change because adjacent child boxes will - // have moved probably. - childBox.controllerData.yChange *= -1; - } else { - height += yDelta; - } - - childBox.set_dimensions(left, width, top + (height / 2), height); - - top += height; - }); - } - - async populate(rootBox, limits) { - rootBox.set_dimensions( - limits.width * -0.45, - limits.width * 0.9, - 0, limits.height * 0.9, - ); - - rootBox.instantiate_child_boxes(() => {}); - - this._rectHeight = (rootBox.height / rootBox.childBoxes.length) * 0.75; - - let top = rootBox.top; - const width = this._rectHeight * 2; - const left = (rootBox.left + rootBox.width) - width; - rootBox.childBoxes.forEach((childBox, index) => { - const xChange = 1 - ((index % 2) * 2); - childBox.controllerData = {'xChange': xChange, 'yChange': xChange}; - childBox.set_dimensions( - left, width, top + (this._rectHeight / 2), this._rectHeight, - ); - // Next line will set childBoxes to an empty array - childBox.instantiate_child_boxes(); - - top += this._rectHeight; - }); - } -} diff --git a/browser/dasher/controlpanel.css b/browser/dasher/controlpanel.css deleted file mode 100644 index 52163ee..0000000 --- a/browser/dasher/controlpanel.css +++ /dev/null @@ -1,75 +0,0 @@ -/* -(c) 2020 The ACE Centre-North, UK registered charity 1089313. -MIT licensed, see https://opensource.org/licenses/MIT -*/ - -/* -Cascading Style Sheet for the control panel. This file is imported by the -userinterface.css file. - -Try to follow the Block Element Modifier convention, see: -https://en.bem.info/methodology/naming-convention/ -*/ - -/* Style for the parent of all panels. */ -.control-panel__parent { - display: flex; - flex-direction: row; - overflow-x: scroll; -} - -/* Style for all panels. */ -.control-panel__panel { - display: inline-block; - flex: none; - /* border: 0.25rem red dashed; */ -} - -/* Legends will be clickable and styled as buttons, but smaller. */ -legend.cwv-button { - padding: 0.25em 0.5em 0.25em 0.5em; -} - -/* Forward and back navigation hints on clickable legends. - */ -.control-panel__panel:nth-of-type(1) > legend.cwv-button::after { - content: " >"; -} -.control-panel__panel:nth-of-type(n+2) > legend.cwv-button::before { - content: "< "; -} - -/* Navigator panel. - */ -.control-panel__structure-navigator > div { - display: grid; - grid-template-columns: repeat(3, 1fr); - align-items: center; -} - -.control-panel__structure-navigator > div .cwv-button -{ - margin: 0.25em; -} - -.control-panel__structure-navigator > div .cwv-button::after { - content: " >"; -} - -/* Fading result display, used by the afterInstantiate.manager piece. - */ -.control-panel__result { - opacity: 1; - display: inline; -} - -.control-panel__result-stale { - opacity: 0; - transition: opacity var(--transition) linear; -} - -.control-panel__result pre { - font-family: monospace; - white-space: pre; - background-color: var(--field-colour); -} diff --git a/browser/dasher/controlpanel.js b/browser/dasher/controlpanel.js deleted file mode 100644 index 2f18532..0000000 --- a/browser/dasher/controlpanel.js +++ /dev/null @@ -1,1012 +0,0 @@ -// (c) 2021 The ACE Centre-North, UK registered charity 1089313. -// MIT licensed, see https://opensource.org/licenses/MIT - -import Piece from './piece.js'; - -import PageBuilder from '../pagebuilder.js'; - -const pathJoin = '_'; -const stringType = typeof ''; - -class Control { - constructor(parentPiece, path, $) { - this.$ = $; - - this._piece = undefined; - this._node = undefined; - this._valueListener = null; - this._addedListener = null; - this._optionList = undefined; - this._active = undefined; - - this._selectedIndex = undefined; - this._selectedString = undefined; - - this._panelListener = null; - - this._label = Control.make_label( - $, path, this._labelFirst && $.control !== 'button'); - this._identifier = path.join(pathJoin); - - this._listenerType = ( - this.$.control === 'button' ? 'click' : - this.$.control === 'select' ? 'input' : - 'change' - ); - - if ($.control === 'button') { - this._construct_button(parentPiece); - } else { - if ($.control === 'select') { - this._construct_select(parentPiece); - } else { - this._construct_input(parentPiece); - } - this._add_panel_listener(); - } - } - - get panelListener() { - return this._panelListener; - } - set panelListener(panelListener) { - this._panelListener = panelListener; - } - _add_panel_listener() { - this.node.addEventListener( - this._listenerType, this._panel_listener.bind(this)); - } - _panel_listener(event) { - if (this.panelListener !== null) { - this.panelListener(event); - } - } - - get piece() { - return this._piece; - } - get node() { - return this._piece === undefined ? this._node : this._piece.node; - } - - get active() { - return this._active; - } - set active(active) { - this._active = active; - if (this.node !== undefined) { - if (active) { - this.node.removeAttribute('disabled'); - } else { - this.node.setAttribute('disabled', true); - } - } - } - - static make_label($, path, colon) { - return $.label === undefined ? [ - path[path.length - 1][0].toLocaleUpperCase(), - path[path.length - 1].slice(1), - colon ? ':' : '', - ].join('') : $.label; - } - - get _labelFirst() { - return this.$.control !== 'checkbox' && this.$.control !== 'number'; - } - - _construct_button(parentPiece) { - this._node = PageBuilder.add_button( - this._label, - parentPiece === undefined ? undefined : parentPiece.node); - this.active = !!this.active; - } - - _construct_select(parentPiece) { - const attributes = {'id': this._identifier, 'name': this._identifier}; - this._with_label(parentPiece, () => { - this._piece = new Piece('select', parentPiece, attributes); - }); - this.active = !!this.active; - - // If option strings were set early, create