diff --git a/.github/workflows/build-main-docs.yml b/.github/workflows/build-main-docs.yml new file mode 100644 index 000000000..e8483cc37 --- /dev/null +++ b/.github/workflows/build-main-docs.yml @@ -0,0 +1,41 @@ +name: Build Main Docs + +on: + workflow_call: + inputs: + package: + description: The plugin to publish the docs for + required: true + type: string + +jobs: + check-make-docs: + runs-on: ubuntu-22.04 + outputs: + files_exists: ${{ steps.check_files.outputs.files_exists }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check file existence + id: check_files + uses: andstor/file-existence-action@v3 + with: + files: "plugins/${{ inputs.package }}/make_docs.py" + + build-plugin: + if: needs.check-make-docs.outputs.files_exists == 'true' + needs: check-make-docs + uses: ./.github/workflows/build-python-package.yml + with: + package: ${{ inputs.package }} + + main-docs: + needs: build-plugin + uses: ./.github/workflows/make-docs.yml + secrets: inherit + with: + package: ${{ inputs.package }} + version: 'main' + + diff --git a/.github/workflows/build-python-package.yml b/.github/workflows/build-python-package.yml new file mode 100644 index 000000000..f75dda9a2 --- /dev/null +++ b/.github/workflows/build-python-package.yml @@ -0,0 +1,54 @@ +name: Build Python Package + +on: + workflow_call: + inputs: + package: + required: true + type: string + +jobs: + build-python-package: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check file existence + id: check_files + uses: andstor/file-existence-action@v3 + with: + files: "plugins/${{ inputs.package }}/src/js/package.json" + + - name: Setup Node + if: steps.check_files.outputs.files_exists == 'true' + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install npm dependencies + if: steps.check_files.outputs.files_exists == 'true' + run: npm ci + + - name: Build npm packages + if: steps.check_files.outputs.files_exists == 'true' + run: npm run build -- --scope "@deephaven/js-plugin-${{ inputs.package }}" + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install build dependencies + run: python -m pip install --upgrade setuptools wheel build + + - name: Build wheel + run: python -m build --wheel --sdist plugins/${{ inputs.package }} + + - name: Upload dist + uses: actions/upload-artifact@v3 + with: + name: dist-${{ inputs.package }} + path: plugins/${{ inputs.package }}/dist/ + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/make-docs.yml b/.github/workflows/make-docs.yml new file mode 100644 index 000000000..42a6e9531 --- /dev/null +++ b/.github/workflows/make-docs.yml @@ -0,0 +1,57 @@ +name: Make Docs + +on: + workflow_call: + inputs: + package: + required: true + type: string + version: + required: true + type: string + +jobs: + make-docs: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + # assume that the dist artifact is already available from calling build-python-package.yml before this workflow + - name: Download dist + uses: actions/download-artifact@v3 + with: + name: dist-${{ inputs.package }} + path: plugins/${{ inputs.package }}/dist/ + + - name: Install requirements + run: pip install -r plugins/${{ inputs.package }}/requirements.txt + + - name: Install wheel + run: pip install plugins/${{ inputs.package }}/dist/*.whl + + - name: Run make_docs.py + run: python plugins/${{ inputs.package }}/make_docs.py + + - name: Setup rclone + run: | + sudo apt-get update + sudo apt-get install -y rclone + + mkdir -p $HOME/.config + mkdir -p $HOME/.config/rclone + + cat << EOF > $HOME/.config/rclone/rclone.conf + [plugindocs] + type = s3 + provider = Cloudflare + access_key_id = ${{ secrets.DOCS_CLOUDFLARE_ACCESS_KEY_ID }} + secret_access_key = ${{ secrets.DOCS_CLOUDFLARE_SECRET_ACCESS_KEY }} + endpoint = ${{ secrets.DOCS_CLOUDFLARE_ENDPOINT }} + no_check_bucket = true + acl = private + EOF + + + - name: Sync docs + run: rclone sync plugins/${{ inputs.package }}/docs/build/markdown/ plugindocs:website/deephaven/deephaven-plugins/${{ inputs.package }}/${{ inputs.version }}/ \ No newline at end of file diff --git a/.github/workflows/modified-plugin.yml b/.github/workflows/modified-plugin.yml index 4a28a5f1d..789a8e250 100644 --- a/.github/workflows/modified-plugin.yml +++ b/.github/workflows/modified-plugin.yml @@ -99,5 +99,18 @@ jobs: needs: filter-release if: ${{ needs.filter-release.outputs.package != '' }} uses: ./.github/workflows/release-python-package.yml + secrets: inherit with: package: ${{ needs.filter-release.outputs.package }} + + # Main docs are built here, whereas release docs are built in release-python-package.yml + build-main-docs: + needs: changes + if: ${{ needs.changes.outputs.packages != '[]' && needs.changes.outputs.packages != '' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + strategy: + matrix: + package: ${{ fromJSON(needs.changes.outputs.packages) }} + uses: ./.github/workflows/build-main-docs.yml + secrets: inherit + with: + package: ${{ matrix.package }} \ No newline at end of file diff --git a/.github/workflows/publish-alpha.yml b/.github/workflows/publish-alpha.yml index 682e50996..3cb43f076 100644 --- a/.github/workflows/publish-alpha.yml +++ b/.github/workflows/publish-alpha.yml @@ -21,10 +21,11 @@ jobs: with: fetch-depth: '0' # Need the history to properly select the canary version number ref: ${{ github.event.inputs.ref }} - - uses: actions/setup-node@v3 + - name: Setup Node + uses: actions/setup-node@v4 with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' + node-version-file: '.nvmrc' + cache: 'npm' - name: Install dependencies run: npm ci - name: Build packages diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 0c6e04568..d40d0068c 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -15,10 +15,10 @@ jobs: with: ref: ${{ github.ref }} - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' + node-version-file: '.nvmrc' + cache: 'npm' - name: Install dependencies run: npm ci - name: Build production diff --git a/.github/workflows/release-python-package.yml b/.github/workflows/release-python-package.yml index c8045590a..512f51dd3 100644 --- a/.github/workflows/release-python-package.yml +++ b/.github/workflows/release-python-package.yml @@ -13,54 +13,71 @@ jobs: with: package: ${{ inputs.package }} - build-dist: + build-plugin: needs: test + uses: ./.github/workflows/build-python-package.yml + with: + package: ${{ inputs.package }} + + release: + needs: build-plugin runs-on: ubuntu-22.04 permissions: id-token: write + outputs: + version: ${{ steps.extract_version.outputs.version }} steps: - name: Checkout uses: actions/checkout@v3 - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: '18.x' - registry-url: 'https://registry.npmjs.org' - - - name: Check file existence - id: check_files - uses: andstor/file-existence-action@v3 - with: - files: "plugins/${{ inputs.package }}/src/js/package.json" - - - name: Install npm dependencies - if: steps.check_files.outputs.files_exists == 'true' - run: npm ci - - - name: Build npm packages - if: steps.check_files.outputs.files_exists == 'true' - run: npm run build -- --scope "@deephaven/js-plugin-${{ inputs.package }}" - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Download dist + uses: actions/download-artifact@v3 with: - python-version: '3.x' + name: dist-${{ inputs.package }} + path: plugins/${{ inputs.package }}/dist/ - - name: Install build dependencies - run: python -m pip install --upgrade setuptools wheel build + - name: Get the name of the whl file + id: get_whl + run: | + WHL_DIR=plugins/${{ inputs.package }}/dist/ + WHL_FILE=$(ls $WHL_DIR | grep .whl) + echo "whl_path=${WHL_DIR}${WHL_FILE}" >> $GITHUB_OUTPUT - - name: Build wheel - run: python -m build --wheel --sdist plugins/${{ inputs.package }} + - name: Install pkginfo + run: pip install pkginfo - - name: Upload dist - uses: actions/upload-artifact@v3 - with: - name: dist - path: plugins/${{ inputs.package }}/dist/ - if-no-files-found: error + - name: Extract package version + id: extract_version + run: | + VERSION=$(python -c 'import pkginfo; print(pkginfo.Wheel("${{ steps.get_whl.outputs.whl_path }}").version)') + echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Publish package uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: plugins/${{ inputs.package }}/dist/ + + check-make-docs: + runs-on: ubuntu-22.04 + outputs: + files_exists: ${{ steps.check_files.outputs.files_exists }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check file existence + id: check_files + uses: andstor/file-existence-action@v3 + with: + files: "plugins/${{ inputs.package }}/make_docs.py" + + release-docs: + if: needs.check-make-docs.outputs.files_exists == 'true' + needs: + - release + - check-make-docs + uses: ./.github/workflows/make-docs.yml + secrets: inherit + with: + package: ${{ inputs.package }} + version: ${{ needs.release.outputs.version }} diff --git a/package-lock.json b/package-lock.json index e71796cbf..e6277070f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26252,7 +26252,8 @@ }, "node_modules/proxy-compare": { "version": "3.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.0.tgz", + "integrity": "sha512-y44MCkgtZUCT9tZGuE278fB7PWVf7fRYy0vbRXAts2o5F0EfC4fIQrvQQGBJo1WJbFcVLXzApOscyJuZqHQc1w==" }, "node_modules/proxy-from-env": { "version": "1.1.0", @@ -26261,7 +26262,8 @@ }, "node_modules/proxy-memoize": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/proxy-memoize/-/proxy-memoize-3.0.0.tgz", + "integrity": "sha512-2fs4eIg4w6SfOjKHGVdg5tJ9WgHifEXKo2gfS/+tHGajO2YtAu03lLs+ltNKnteGKvq3SvHromkZeKus4J39/g==", "dependencies": { "proxy-compare": "^3.0.0" } @@ -32185,21 +32187,21 @@ "version": "0.16.0", "license": "Apache-2.0", "dependencies": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/dashboard": "^0.81.1", - "@deephaven/dashboard-core-plugins": "^0.81.1", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-components": "^0.81.1", - "@deephaven/jsapi-types": "^1.0.0-dev0.34.3", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/utils": "^0.81.0", + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/dashboard": "^0.84.0", + "@deephaven/dashboard-core-plugins": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", + "@deephaven/jsapi-types": "^1.0.0-dev0.35.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/react-fontawesome": "^0.2.0", "@react-types/shared": "^3.22.0", "classnames": "^2.5.1", @@ -32220,83 +32222,18 @@ "react-dom": "^17.0.2" } }, - "plugins/ui/src/js/node_modules/@adobe/react-spectrum": { - "version": "3.33.1", - "license": "Apache-2.0", - "dependencies": { - "@internationalized/string": "^3.2.0", - "@react-aria/i18n": "^3.10.0", - "@react-aria/ssr": "^3.9.1", - "@react-aria/utils": "^3.23.0", - "@react-aria/visually-hidden": "^3.8.8", - "@react-spectrum/actionbar": "^3.4.1", - "@react-spectrum/actiongroup": "^3.10.1", - "@react-spectrum/avatar": "^3.0.8", - "@react-spectrum/badge": "^3.1.9", - "@react-spectrum/breadcrumbs": "^3.9.3", - "@react-spectrum/button": "^3.16.0", - "@react-spectrum/buttongroup": "^3.6.9", - "@react-spectrum/calendar": "^3.4.5", - "@react-spectrum/checkbox": "^3.9.2", - "@react-spectrum/combobox": "^3.12.1", - "@react-spectrum/contextualhelp": "^3.6.7", - "@react-spectrum/datepicker": "^3.9.2", - "@react-spectrum/dialog": "^3.8.7", - "@react-spectrum/divider": "^3.5.9", - "@react-spectrum/dnd": "^3.3.6", - "@react-spectrum/form": "^3.7.2", - "@react-spectrum/icon": "^3.7.9", - "@react-spectrum/illustratedmessage": "^3.4.9", - "@react-spectrum/image": "^3.4.9", - "@react-spectrum/inlinealert": "^3.2.1", - "@react-spectrum/labeledvalue": "^3.1.10", - "@react-spectrum/layout": "^3.6.1", - "@react-spectrum/link": "^3.6.3", - "@react-spectrum/list": "^3.7.6", - "@react-spectrum/listbox": "^3.12.5", - "@react-spectrum/menu": "^3.17.0", - "@react-spectrum/meter": "^3.4.9", - "@react-spectrum/numberfield": "^3.8.2", - "@react-spectrum/overlays": "^5.5.3", - "@react-spectrum/picker": "^3.14.1", - "@react-spectrum/progress": "^3.7.3", - "@react-spectrum/provider": "^3.9.3", - "@react-spectrum/radio": "^3.7.2", - "@react-spectrum/searchfield": "^3.8.2", - "@react-spectrum/slider": "^3.6.5", - "@react-spectrum/statuslight": "^3.5.9", - "@react-spectrum/switch": "^3.5.1", - "@react-spectrum/table": "^3.12.6", - "@react-spectrum/tabs": "^3.8.6", - "@react-spectrum/tag": "^3.2.2", - "@react-spectrum/text": "^3.5.1", - "@react-spectrum/textfield": "^3.11.2", - "@react-spectrum/theme-dark": "^3.5.7", - "@react-spectrum/theme-default": "^3.5.7", - "@react-spectrum/theme-light": "^3.4.7", - "@react-spectrum/tooltip": "^3.6.3", - "@react-spectrum/view": "^3.6.6", - "@react-spectrum/well": "^3.4.9", - "@react-stately/collections": "^3.10.4", - "@react-stately/data": "^3.11.0", - "@react-types/shared": "^3.22.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" - } - }, "plugins/ui/src/js/node_modules/@deephaven/chart": { - "version": "0.81.1", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/chart/-/chart-0.84.0.tgz", + "integrity": "sha512-UUtwO27zr8m213ZTmmO/Xv9XL28phSIb/OLBSpKtbW4iL+rXdzFf3t42q7kGfukXf4MUG2i+CyG/GKiKe+3vZw==", "dependencies": { - "@deephaven/components": "^0.81.1", - "@deephaven/icons": "^0.81.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0", "buffer": "^6.0.3", "fast-deep-equal": "^3.1.3", "lodash.debounce": "^4.0.8", @@ -32319,14 +32256,15 @@ "license": "Apache-2.0" }, "plugins/ui/src/js/node_modules/@deephaven/components": { - "version": "0.81.1", - "license": "Apache-2.0", - "dependencies": { - "@adobe/react-spectrum": "3.33.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/components/-/components-0.84.0.tgz", + "integrity": "sha512-3lUz+JGQQMxdG9MmQbReHfWr4h0FiIOJtH17DIQnelYP+VDoiqWFfg4c/uEs7EMRIQAtKi8NI88DrXzoEwhS7w==", + "dependencies": { + "@adobe/react-spectrum": "3.35.1", + "@deephaven/icons": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", "@react-spectrum/theme-default": "^3.5.1", @@ -32357,55 +32295,17 @@ "react-dom": ">=16.8.0" } }, - "plugins/ui/src/js/node_modules/@deephaven/console": { - "version": "0.81.1", - "license": "Apache-2.0", - "dependencies": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", - "@fortawesome/react-fontawesome": "^0.2.0", - "classnames": "^2.3.1", - "linkifyjs": "^4.1.0", - "lodash.debounce": "^4.0.8", - "lodash.throttle": "^4.1.1", - "memoize-one": "^5.1.1", - "memoizee": "^0.4.15", - "monaco-editor": "^0.41.0", - "nanoid": "^5.0.7", - "papaparse": "5.3.2", - "popper.js": "^1.16.1", - "prop-types": "^15.7.2", - "shell-quote": "^1.7.2" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "plugins/ui/src/js/node_modules/@deephaven/console/node_modules/@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" - }, "plugins/ui/src/js/node_modules/@deephaven/dashboard": { - "version": "0.81.1", - "license": "Apache-2.0", - "dependencies": { - "@deephaven/components": "^0.81.1", - "@deephaven/golden-layout": "^0.81.1", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/utils": "^0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/dashboard/-/dashboard-0.84.0.tgz", + "integrity": "sha512-AsYI6ppk0OtB3p/S+VVMuWO/RJJ/V3XcLQsBy5oA1GG+2sD6AMjtFG5QbTGQopI995UPEcUWnLODWr3CcPItQg==", + "dependencies": { + "@deephaven/components": "^0.84.0", + "@deephaven/golden-layout": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/utils": "^0.84.0", "fast-deep-equal": "^3.1.3", "lodash.ismatch": "^4.1.1", "lodash.throttle": "^4.1.1", @@ -32423,29 +32323,30 @@ } }, "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins": { - "version": "0.81.1", - "license": "Apache-2.0", - "dependencies": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/console": "^0.81.1", - "@deephaven/dashboard": "^0.81.1", - "@deephaven/file-explorer": "^0.81.1", - "@deephaven/filters": "^0.81.0", - "@deephaven/golden-layout": "^0.81.1", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-components": "^0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/dashboard-core-plugins/-/dashboard-core-plugins-0.84.0.tgz", + "integrity": "sha512-rhEO3M8f34fA0Dn5vex/lUwkgEqhkaOuAXmYjmKIK5TI220uxia930aaVaDBFw/vhacktNEH6KGNRR1ZWjugxg==", + "dependencies": { + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/console": "^0.84.0", + "@deephaven/dashboard": "^0.84.0", + "@deephaven/file-explorer": "^0.84.0", + "@deephaven/filters": "^0.84.0", + "@deephaven/golden-layout": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/react-fontawesome": "^0.2.0", "classnames": "^2.3.1", "fast-deep-equal": "^3.1.3", @@ -32472,19 +32373,52 @@ "react-redux": "^7.2.4" } }, - "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins/node_modules/@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" + "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins/node_modules/@deephaven/console": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.84.0.tgz", + "integrity": "sha512-S5QAkns4vt5e1r2T3VQ/c8YhOnXNi+blTk/n4rHE1VAhH6qphRp1pehF9TQzRG859cSXT3JH2CBoe9efi7WVbw==", + "dependencies": { + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-types": "1.0.0-dev0.34.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "classnames": "^2.3.1", + "linkifyjs": "^4.1.0", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "memoize-one": "^5.1.1", + "memoizee": "^0.4.15", + "monaco-editor": "^0.41.0", + "nanoid": "^5.0.7", + "papaparse": "5.3.2", + "popper.js": "^1.16.1", + "prop-types": "^15.7.2", + "shell-quote": "^1.7.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } }, - "plugins/ui/src/js/node_modules/@deephaven/file-explorer": { - "version": "0.81.1", - "license": "Apache-2.0", + "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins/node_modules/@deephaven/file-explorer": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/file-explorer/-/file-explorer-0.84.0.tgz", + "integrity": "sha512-AJ1kJZXhkRz+b2H26eolfr9eYkDxbWL1AsNjwu42Quv54/cncOu2WaDXkbOhVwlpLfae20f1P3X0JnTVFFP20w==", "dependencies": { - "@deephaven/components": "^0.81.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", "classnames": "^2.3.1", @@ -32498,18 +32432,47 @@ "react": ">=16.8.0" } }, - "plugins/ui/src/js/node_modules/@deephaven/filters": { - "version": "0.81.0", - "license": "Apache-2.0", + "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins/node_modules/@deephaven/golden-layout": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.84.0.tgz", + "integrity": "sha512-Pmy157rhKbf7U5YV6rBg7/r1ZHTHATw2UMQ501JEKAbOThrHIWrzktKk2Kfd4kpDs1CYPZsNJEuYwkzd+Xy2wA==", + "dependencies": { + "@deephaven/components": "^0.84.0", + "jquery": "^3.6.0", + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins/node_modules/@deephaven/jsapi-types": { + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" + }, + "plugins/ui/src/js/node_modules/@deephaven/dashboard-core-plugins/node_modules/@deephaven/storage": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.84.0.tgz", + "integrity": "sha512-P/KJT2U04WypIxvx1Ws7l/02jUwIPdkonfUH9ezUK5YXzakwmMiXBvV2/jTBGrRpw/BfDZOkj25FlpDA4yib/A==", + "dependencies": { + "@deephaven/filters": "^0.84.0", + "@deephaven/log": "^0.84.0", + "lodash.throttle": "^4.1.1" + }, "engines": { "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8.0" } }, - "plugins/ui/src/js/node_modules/@deephaven/golden-layout": { - "version": "0.81.1", - "license": "Apache-2.0", + "plugins/ui/src/js/node_modules/@deephaven/dashboard/node_modules/@deephaven/golden-layout": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.84.0.tgz", + "integrity": "sha512-Pmy157rhKbf7U5YV6rBg7/r1ZHTHATw2UMQ501JEKAbOThrHIWrzktKk2Kfd4kpDs1CYPZsNJEuYwkzd+Xy2wA==", "dependencies": { - "@deephaven/components": "^0.81.1", + "@deephaven/components": "^0.84.0", "jquery": "^3.6.0", "nanoid": "^5.0.7" }, @@ -32518,11 +32481,20 @@ "react-dom": ">=16.8.0" } }, + "plugins/ui/src/js/node_modules/@deephaven/filters": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/filters/-/filters-0.84.0.tgz", + "integrity": "sha512-hu6DiGoFrMrJKP2sNFmBjHkMx6CKLDNTNlnt6WAdCbfw+tUZmc60seD0/l0Z5P/Z70TJUxqWyjUcWnE2KToMog==", + "engines": { + "node": ">=16" + } + }, "plugins/ui/src/js/node_modules/@deephaven/grid": { - "version": "0.81.0", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/grid/-/grid-0.84.0.tgz", + "integrity": "sha512-IHCkad8zEO/M11ykLFhunDI40bKwJGadXf+NcsyTHdSkvATYlMe/EHaKZKXhzQtnoQmEv4kwkFSomiZ+oU4N0g==", "dependencies": { - "@deephaven/utils": "^0.81.0", + "@deephaven/utils": "^0.84.0", "classnames": "^2.3.1", "color-convert": "^2.0.1", "event-target-shim": "^6.0.2", @@ -32540,8 +32512,9 @@ } }, "plugins/ui/src/js/node_modules/@deephaven/icons": { - "version": "0.81.0", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/icons/-/icons-0.84.0.tgz", + "integrity": "sha512-aynGI8lBV0xLt4ivWAYIzULtyfJQwmgA/4npZruorSrS+x9ELrwAsZtPGqXAmZ0kDTLHenTKIBVOj1nSo9NvAg==", "dependencies": { "@fortawesome/fontawesome-common-types": "^6.1.1" }, @@ -32551,21 +32524,22 @@ } }, "plugins/ui/src/js/node_modules/@deephaven/iris-grid": { - "version": "0.81.1", - "license": "Apache-2.0", - "dependencies": { - "@deephaven/components": "^0.81.1", - "@deephaven/console": "^0.81.1", - "@deephaven/filters": "^0.81.0", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/jsapi-components": "^0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/iris-grid/-/iris-grid-0.84.0.tgz", + "integrity": "sha512-ilM9FxcsKeRIANgw5p5yN2cu5OtxRYHRLJ8dyF21ND9xjQ0e58hByqBnIclVuhakuaTl6qacxkCebjZgooxOIg==", + "dependencies": { + "@deephaven/components": "^0.84.0", + "@deephaven/console": "^0.84.0", + "@deephaven/filters": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.2", @@ -32591,19 +32565,73 @@ "react-dom": ">=16.8.0" } }, + "plugins/ui/src/js/node_modules/@deephaven/iris-grid/node_modules/@deephaven/console": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.84.0.tgz", + "integrity": "sha512-S5QAkns4vt5e1r2T3VQ/c8YhOnXNi+blTk/n4rHE1VAhH6qphRp1pehF9TQzRG859cSXT3JH2CBoe9efi7WVbw==", + "dependencies": { + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-types": "1.0.0-dev0.34.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "classnames": "^2.3.1", + "linkifyjs": "^4.1.0", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "memoize-one": "^5.1.1", + "memoizee": "^0.4.15", + "monaco-editor": "^0.41.0", + "nanoid": "^5.0.7", + "papaparse": "5.3.2", + "popper.js": "^1.16.1", + "prop-types": "^15.7.2", + "shell-quote": "^1.7.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "plugins/ui/src/js/node_modules/@deephaven/iris-grid/node_modules/@deephaven/jsapi-types": { "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" + }, + "plugins/ui/src/js/node_modules/@deephaven/iris-grid/node_modules/@deephaven/storage": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.84.0.tgz", + "integrity": "sha512-P/KJT2U04WypIxvx1Ws7l/02jUwIPdkonfUH9ezUK5YXzakwmMiXBvV2/jTBGrRpw/BfDZOkj25FlpDA4yib/A==", + "dependencies": { + "@deephaven/filters": "^0.84.0", + "@deephaven/log": "^0.84.0", + "lodash.throttle": "^4.1.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">=16.8.0" + } }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-bootstrap": { - "version": "0.81.1", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-bootstrap/-/jsapi-bootstrap-0.84.0.tgz", + "integrity": "sha512-Aew3Lo+T9DRV1MLL89jeWORiMZ940gBQNz3kWbW7z/stcTQR89Wxynjt7tiaNRnBxBb0TgYrhphjlz01LAKjUw==", "dependencies": { - "@deephaven/components": "^0.81.1", + "@deephaven/components": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0" + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0" }, "engines": { "node": ">=16" @@ -32614,19 +32642,21 @@ }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-bootstrap/node_modules/@deephaven/jsapi-types": { "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-components": { - "version": "0.81.1", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-components/-/jsapi-components-0.84.0.tgz", + "integrity": "sha512-suo2BSyBxNoR7KxS2DCX+FxCBdvaq3eFrrSjqLG3HmxOg3ps3qLdqoiLEpn6cVw0cZTqS0K2jGbkE9oyoDj+tA==", "dependencies": { - "@deephaven/components": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", + "@deephaven/components": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@types/js-cookie": "^3.0.3", "classnames": "^2.3.2", "js-cookie": "^3.0.5", @@ -32642,20 +32672,23 @@ }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-components/node_modules/@deephaven/jsapi-types": { "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.3", - "license": "Apache-2.0" + "version": "1.0.0-dev0.35.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.35.0.tgz", + "integrity": "sha512-X35g2ktmXbiTwjMNF20IkuNawJJ6Tlvrv23VuUVIjWHkpWcmyCYWIBle2zo7QAF6nnJpkccwFKJiC+TIkWl7hg==" }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-utils": { - "version": "0.81.0", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-utils/-/jsapi-utils-0.84.0.tgz", + "integrity": "sha512-CwYs9NHPs3wb+D0PM4VTBrqXzaHNHmuHTkep8XitZHAr814jOGcQhnkTauSpo+ulPwnheweO8C8wMjjBH/VojQ==", "dependencies": { - "@deephaven/filters": "^0.81.0", + "@deephaven/filters": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/log": "^0.84.0", + "@deephaven/utils": "^0.84.0", "lodash.clamp": "^4.0.3", "nanoid": "^5.0.7" }, @@ -32665,11 +32698,13 @@ }, "plugins/ui/src/js/node_modules/@deephaven/jsapi-utils/node_modules/@deephaven/jsapi-types": { "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" }, "plugins/ui/src/js/node_modules/@deephaven/log": { - "version": "0.81.0", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/log/-/log-0.84.0.tgz", + "integrity": "sha512-B3KS2n/dT8GPblR8Top9GPB53pMmUglAMUVg93V3X10EPemE1jwiYEoJuceZ20JpRncCfRVuazrNYP7GItjWPQ==", "dependencies": { "event-target-shim": "^6.0.2" }, @@ -32678,16 +32713,17 @@ } }, "plugins/ui/src/js/node_modules/@deephaven/plugin": { - "version": "0.81.1", - "license": "Apache-2.0", - "dependencies": { - "@deephaven/components": "^0.81.1", - "@deephaven/golden-layout": "^0.81.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/plugin/-/plugin-0.84.0.tgz", + "integrity": "sha512-G3f3WGc159TVUZOekZPmUAChshdDqE7OzuigGXeFlv/CCmlQhX5t1BWwqfYRFXkHwV5zk9UUyuQEhrNAUS+Zmg==", + "dependencies": { + "@deephaven/components": "^0.84.0", + "@deephaven/golden-layout": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", "@fortawesome/fontawesome-common-types": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.0" }, @@ -32698,17 +32734,33 @@ "react": ">=16.8.0" } }, + "plugins/ui/src/js/node_modules/@deephaven/plugin/node_modules/@deephaven/golden-layout": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.84.0.tgz", + "integrity": "sha512-Pmy157rhKbf7U5YV6rBg7/r1ZHTHATw2UMQ501JEKAbOThrHIWrzktKk2Kfd4kpDs1CYPZsNJEuYwkzd+Xy2wA==", + "dependencies": { + "@deephaven/components": "^0.84.0", + "jquery": "^3.6.0", + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "plugins/ui/src/js/node_modules/@deephaven/plugin/node_modules/@deephaven/jsapi-types": { "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" }, "plugins/ui/src/js/node_modules/@deephaven/react-hooks": { - "version": "0.81.0", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/react-hooks/-/react-hooks-0.84.0.tgz", + "integrity": "sha512-VIcBAkfdyKI5JbITW5kouDfyfEm1ggUhbAYNWRUFlRSPNkoWxFfflc4eplQAfmj/VDjaVAizWNjc1JbH8OG/tg==", "dependencies": { - "@adobe/react-spectrum": "3.33.1", - "@deephaven/log": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@adobe/react-spectrum": "3.35.1", + "@deephaven/log": "^0.84.0", + "@deephaven/utils": "^0.84.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.7" @@ -32721,13 +32773,14 @@ } }, "plugins/ui/src/js/node_modules/@deephaven/redux": { - "version": "0.81.1", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/redux/-/redux-0.84.0.tgz", + "integrity": "sha512-mj05IMOPtKhol5eLdiHORdyvnUv/bH7/sSV18q8hYJ3eHtpKyvuou3256s4NSpvgUCjyxTvQbknR9qemk1fVAw==", "dependencies": { "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", "fast-deep-equal": "^3.1.3", "proxy-memoize": "^3.0.0", "redux-thunk": "2.4.1" @@ -32741,26 +32794,13 @@ }, "plugins/ui/src/js/node_modules/@deephaven/redux/node_modules/@deephaven/jsapi-types": { "version": "1.0.0-dev0.34.0", - "license": "Apache-2.0" - }, - "plugins/ui/src/js/node_modules/@deephaven/storage": { - "version": "0.81.0", - "license": "Apache-2.0", - "dependencies": { - "@deephaven/filters": "^0.81.0", - "@deephaven/log": "^0.81.0", - "lodash.throttle": "^4.1.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "react": ">=16.8.0" - } + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" }, "plugins/ui/src/js/node_modules/@deephaven/utils": { - "version": "0.81.0", - "license": "Apache-2.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/utils/-/utils-0.84.0.tgz", + "integrity": "sha512-eWTlmxtdYFuV7q127AwMyLnf5j0FGpPG6gJ+2ChpFeMS+ZfOHpgcRHZC2+j3y/b5z4mGm2bIjzQDJpMNPL4thg==", "engines": { "node": ">=16" } @@ -32789,7 +32829,8 @@ }, "plugins/ui/src/js/node_modules/color-convert": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dependencies": { "color-name": "~1.1.4" }, @@ -32799,7 +32840,8 @@ }, "plugins/ui/src/js/node_modules/color-name": { "version": "1.1.4", - "license": "MIT" + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "plugins/ui/src/js/node_modules/event-target-shim": { "version": "6.0.2", @@ -32829,7 +32871,8 @@ }, "plugins/ui/src/js/node_modules/redux-thunk": { "version": "2.4.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", "peerDependencies": { "redux": "^4" } @@ -36250,21 +36293,21 @@ "@deephaven/js-plugin-ui": { "version": "file:plugins/ui/src/js", "requires": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/dashboard": "^0.81.1", - "@deephaven/dashboard-core-plugins": "^0.81.1", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-components": "^0.81.1", - "@deephaven/jsapi-types": "^1.0.0-dev0.34.3", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/utils": "^0.81.0", + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/dashboard": "^0.84.0", + "@deephaven/dashboard-core-plugins": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", + "@deephaven/jsapi-types": "^1.0.0-dev0.35.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/react-fontawesome": "^0.2.0", "@react-types/shared": "^3.22.0", "@types/react": "^17.0.2", @@ -36279,77 +36322,18 @@ "vite": "~4.1.4" }, "dependencies": { - "@adobe/react-spectrum": { - "version": "3.33.1", - "requires": { - "@internationalized/string": "^3.2.0", - "@react-aria/i18n": "^3.10.0", - "@react-aria/ssr": "^3.9.1", - "@react-aria/utils": "^3.23.0", - "@react-aria/visually-hidden": "^3.8.8", - "@react-spectrum/actionbar": "^3.4.1", - "@react-spectrum/actiongroup": "^3.10.1", - "@react-spectrum/avatar": "^3.0.8", - "@react-spectrum/badge": "^3.1.9", - "@react-spectrum/breadcrumbs": "^3.9.3", - "@react-spectrum/button": "^3.16.0", - "@react-spectrum/buttongroup": "^3.6.9", - "@react-spectrum/calendar": "^3.4.5", - "@react-spectrum/checkbox": "^3.9.2", - "@react-spectrum/combobox": "^3.12.1", - "@react-spectrum/contextualhelp": "^3.6.7", - "@react-spectrum/datepicker": "^3.9.2", - "@react-spectrum/dialog": "^3.8.7", - "@react-spectrum/divider": "^3.5.9", - "@react-spectrum/dnd": "^3.3.6", - "@react-spectrum/form": "^3.7.2", - "@react-spectrum/icon": "^3.7.9", - "@react-spectrum/illustratedmessage": "^3.4.9", - "@react-spectrum/image": "^3.4.9", - "@react-spectrum/inlinealert": "^3.2.1", - "@react-spectrum/labeledvalue": "^3.1.10", - "@react-spectrum/layout": "^3.6.1", - "@react-spectrum/link": "^3.6.3", - "@react-spectrum/list": "^3.7.6", - "@react-spectrum/listbox": "^3.12.5", - "@react-spectrum/menu": "^3.17.0", - "@react-spectrum/meter": "^3.4.9", - "@react-spectrum/numberfield": "^3.8.2", - "@react-spectrum/overlays": "^5.5.3", - "@react-spectrum/picker": "^3.14.1", - "@react-spectrum/progress": "^3.7.3", - "@react-spectrum/provider": "^3.9.3", - "@react-spectrum/radio": "^3.7.2", - "@react-spectrum/searchfield": "^3.8.2", - "@react-spectrum/slider": "^3.6.5", - "@react-spectrum/statuslight": "^3.5.9", - "@react-spectrum/switch": "^3.5.1", - "@react-spectrum/table": "^3.12.6", - "@react-spectrum/tabs": "^3.8.6", - "@react-spectrum/tag": "^3.2.2", - "@react-spectrum/text": "^3.5.1", - "@react-spectrum/textfield": "^3.11.2", - "@react-spectrum/theme-dark": "^3.5.7", - "@react-spectrum/theme-default": "^3.5.7", - "@react-spectrum/theme-light": "^3.4.7", - "@react-spectrum/tooltip": "^3.6.3", - "@react-spectrum/view": "^3.6.6", - "@react-spectrum/well": "^3.4.9", - "@react-stately/collections": "^3.10.4", - "@react-stately/data": "^3.11.0", - "@react-types/shared": "^3.22.0" - } - }, "@deephaven/chart": { - "version": "0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/chart/-/chart-0.84.0.tgz", + "integrity": "sha512-UUtwO27zr8m213ZTmmO/Xv9XL28phSIb/OLBSpKtbW4iL+rXdzFf3t42q7kGfukXf4MUG2i+CyG/GKiKe+3vZw==", "requires": { - "@deephaven/components": "^0.81.1", - "@deephaven/icons": "^0.81.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0", "buffer": "^6.0.3", "fast-deep-equal": "^3.1.3", "lodash.debounce": "^4.0.8", @@ -36367,13 +36351,15 @@ } }, "@deephaven/components": { - "version": "0.81.1", - "requires": { - "@adobe/react-spectrum": "3.33.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/components/-/components-0.84.0.tgz", + "integrity": "sha512-3lUz+JGQQMxdG9MmQbReHfWr4h0FiIOJtH17DIQnelYP+VDoiqWFfg4c/uEs7EMRIQAtKi8NI88DrXzoEwhS7w==", + "requires": { + "@adobe/react-spectrum": "3.35.1", + "@deephaven/icons": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", "@react-spectrum/theme-default": "^3.5.1", @@ -36397,77 +36383,61 @@ "react-window": "^1.8.6" } }, - "@deephaven/console": { - "version": "0.81.1", - "requires": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", - "@fortawesome/react-fontawesome": "^0.2.0", - "classnames": "^2.3.1", - "linkifyjs": "^4.1.0", - "lodash.debounce": "^4.0.8", - "lodash.throttle": "^4.1.1", - "memoize-one": "^5.1.1", - "memoizee": "^0.4.15", - "monaco-editor": "^0.41.0", - "nanoid": "^5.0.7", - "papaparse": "5.3.2", - "popper.js": "^1.16.1", - "prop-types": "^15.7.2", - "shell-quote": "^1.7.2" - }, - "dependencies": { - "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" - } - } - }, "@deephaven/dashboard": { - "version": "0.81.1", - "requires": { - "@deephaven/components": "^0.81.1", - "@deephaven/golden-layout": "^0.81.1", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/utils": "^0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/dashboard/-/dashboard-0.84.0.tgz", + "integrity": "sha512-AsYI6ppk0OtB3p/S+VVMuWO/RJJ/V3XcLQsBy5oA1GG+2sD6AMjtFG5QbTGQopI995UPEcUWnLODWr3CcPItQg==", + "requires": { + "@deephaven/components": "^0.84.0", + "@deephaven/golden-layout": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/utils": "^0.84.0", "fast-deep-equal": "^3.1.3", "lodash.ismatch": "^4.1.1", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.7", "prop-types": "^15.7.2" + }, + "dependencies": { + "@deephaven/golden-layout": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.84.0.tgz", + "integrity": "sha512-Pmy157rhKbf7U5YV6rBg7/r1ZHTHATw2UMQ501JEKAbOThrHIWrzktKk2Kfd4kpDs1CYPZsNJEuYwkzd+Xy2wA==", + "requires": { + "@deephaven/components": "^0.84.0", + "jquery": "^3.6.0", + "nanoid": "^5.0.7" + } + } } }, "@deephaven/dashboard-core-plugins": { - "version": "0.81.1", - "requires": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/console": "^0.81.1", - "@deephaven/dashboard": "^0.81.1", - "@deephaven/file-explorer": "^0.81.1", - "@deephaven/filters": "^0.81.0", - "@deephaven/golden-layout": "^0.81.1", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-components": "^0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/dashboard-core-plugins/-/dashboard-core-plugins-0.84.0.tgz", + "integrity": "sha512-rhEO3M8f34fA0Dn5vex/lUwkgEqhkaOuAXmYjmKIK5TI220uxia930aaVaDBFw/vhacktNEH6KGNRR1ZWjugxg==", + "requires": { + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/console": "^0.84.0", + "@deephaven/dashboard": "^0.84.0", + "@deephaven/file-explorer": "^0.84.0", + "@deephaven/filters": "^0.84.0", + "@deephaven/golden-layout": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/react-fontawesome": "^0.2.0", "classnames": "^2.3.1", "fast-deep-equal": "^3.1.3", @@ -36486,41 +36456,90 @@ "remark-math": "^5.1.1" }, "dependencies": { + "@deephaven/console": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.84.0.tgz", + "integrity": "sha512-S5QAkns4vt5e1r2T3VQ/c8YhOnXNi+blTk/n4rHE1VAhH6qphRp1pehF9TQzRG859cSXT3JH2CBoe9efi7WVbw==", + "requires": { + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-types": "1.0.0-dev0.34.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "classnames": "^2.3.1", + "linkifyjs": "^4.1.0", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "memoize-one": "^5.1.1", + "memoizee": "^0.4.15", + "monaco-editor": "^0.41.0", + "nanoid": "^5.0.7", + "papaparse": "5.3.2", + "popper.js": "^1.16.1", + "prop-types": "^15.7.2", + "shell-quote": "^1.7.2" + } + }, + "@deephaven/file-explorer": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/file-explorer/-/file-explorer-0.84.0.tgz", + "integrity": "sha512-AJ1kJZXhkRz+b2H26eolfr9eYkDxbWL1AsNjwu42Quv54/cncOu2WaDXkbOhVwlpLfae20f1P3X0JnTVFFP20w==", + "requires": { + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", + "@fortawesome/fontawesome-svg-core": "^6.2.1", + "@fortawesome/react-fontawesome": "^0.2.0", + "classnames": "^2.3.1", + "lodash.throttle": "^4.1.1", + "prop-types": "^15.7.2" + } + }, + "@deephaven/golden-layout": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.84.0.tgz", + "integrity": "sha512-Pmy157rhKbf7U5YV6rBg7/r1ZHTHATw2UMQ501JEKAbOThrHIWrzktKk2Kfd4kpDs1CYPZsNJEuYwkzd+Xy2wA==", + "requires": { + "@deephaven/components": "^0.84.0", + "jquery": "^3.6.0", + "nanoid": "^5.0.7" + } + }, "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" + }, + "@deephaven/storage": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.84.0.tgz", + "integrity": "sha512-P/KJT2U04WypIxvx1Ws7l/02jUwIPdkonfUH9ezUK5YXzakwmMiXBvV2/jTBGrRpw/BfDZOkj25FlpDA4yib/A==", + "requires": { + "@deephaven/filters": "^0.84.0", + "@deephaven/log": "^0.84.0", + "lodash.throttle": "^4.1.1" + } } } }, - "@deephaven/file-explorer": { - "version": "0.81.1", - "requires": { - "@deephaven/components": "^0.81.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", - "@fortawesome/fontawesome-svg-core": "^6.2.1", - "@fortawesome/react-fontawesome": "^0.2.0", - "classnames": "^2.3.1", - "lodash.throttle": "^4.1.1", - "prop-types": "^15.7.2" - } - }, "@deephaven/filters": { - "version": "0.81.0" - }, - "@deephaven/golden-layout": { - "version": "0.81.1", - "requires": { - "@deephaven/components": "^0.81.1", - "jquery": "^3.6.0", - "nanoid": "^5.0.7" - } + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/filters/-/filters-0.84.0.tgz", + "integrity": "sha512-hu6DiGoFrMrJKP2sNFmBjHkMx6CKLDNTNlnt6WAdCbfw+tUZmc60seD0/l0Z5P/Z70TJUxqWyjUcWnE2KToMog==" }, "@deephaven/grid": { - "version": "0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/grid/-/grid-0.84.0.tgz", + "integrity": "sha512-IHCkad8zEO/M11ykLFhunDI40bKwJGadXf+NcsyTHdSkvATYlMe/EHaKZKXhzQtnoQmEv4kwkFSomiZ+oU4N0g==", "requires": { - "@deephaven/utils": "^0.81.0", + "@deephaven/utils": "^0.84.0", "classnames": "^2.3.1", "color-convert": "^2.0.1", "event-target-shim": "^6.0.2", @@ -36532,26 +36551,30 @@ } }, "@deephaven/icons": { - "version": "0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/icons/-/icons-0.84.0.tgz", + "integrity": "sha512-aynGI8lBV0xLt4ivWAYIzULtyfJQwmgA/4npZruorSrS+x9ELrwAsZtPGqXAmZ0kDTLHenTKIBVOj1nSo9NvAg==", "requires": { "@fortawesome/fontawesome-common-types": "^6.1.1" } }, "@deephaven/iris-grid": { - "version": "0.81.1", - "requires": { - "@deephaven/components": "^0.81.1", - "@deephaven/console": "^0.81.1", - "@deephaven/filters": "^0.81.0", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/jsapi-components": "^0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/iris-grid/-/iris-grid-0.84.0.tgz", + "integrity": "sha512-ilM9FxcsKeRIANgw5p5yN2cu5OtxRYHRLJ8dyF21ND9xjQ0e58hByqBnIclVuhakuaTl6qacxkCebjZgooxOIg==", + "requires": { + "@deephaven/components": "^0.84.0", + "@deephaven/console": "^0.84.0", + "@deephaven/filters": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/storage": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.2", @@ -36570,36 +36593,83 @@ "react-transition-group": "^4.4.2" }, "dependencies": { + "@deephaven/console": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/console/-/console-0.84.0.tgz", + "integrity": "sha512-S5QAkns4vt5e1r2T3VQ/c8YhOnXNi+blTk/n4rHE1VAhH6qphRp1pehF9TQzRG859cSXT3JH2CBoe9efi7WVbw==", + "requires": { + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-types": "1.0.0-dev0.34.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/storage": "^0.84.0", + "@deephaven/utils": "^0.84.0", + "@fortawesome/react-fontawesome": "^0.2.0", + "classnames": "^2.3.1", + "linkifyjs": "^4.1.0", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "memoize-one": "^5.1.1", + "memoizee": "^0.4.15", + "monaco-editor": "^0.41.0", + "nanoid": "^5.0.7", + "papaparse": "5.3.2", + "popper.js": "^1.16.1", + "prop-types": "^15.7.2", + "shell-quote": "^1.7.2" + } + }, "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" + }, + "@deephaven/storage": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/storage/-/storage-0.84.0.tgz", + "integrity": "sha512-P/KJT2U04WypIxvx1Ws7l/02jUwIPdkonfUH9ezUK5YXzakwmMiXBvV2/jTBGrRpw/BfDZOkj25FlpDA4yib/A==", + "requires": { + "@deephaven/filters": "^0.84.0", + "@deephaven/log": "^0.84.0", + "lodash.throttle": "^4.1.1" + } } } }, "@deephaven/jsapi-bootstrap": { - "version": "0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-bootstrap/-/jsapi-bootstrap-0.84.0.tgz", + "integrity": "sha512-Aew3Lo+T9DRV1MLL89jeWORiMZ940gBQNz3kWbW7z/stcTQR89Wxynjt7tiaNRnBxBb0TgYrhphjlz01LAKjUw==", "requires": { - "@deephaven/components": "^0.81.1", + "@deephaven/components": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0" + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0" }, "dependencies": { "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" } } }, "@deephaven/jsapi-components": { - "version": "0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-components/-/jsapi-components-0.84.0.tgz", + "integrity": "sha512-suo2BSyBxNoR7KxS2DCX+FxCBdvaq3eFrrSjqLG3HmxOg3ps3qLdqoiLEpn6cVw0cZTqS0K2jGbkE9oyoDj+tA==", "requires": { - "@deephaven/components": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", + "@deephaven/components": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@types/js-cookie": "^3.0.3", "classnames": "^2.3.2", "js-cookie": "^3.0.5", @@ -36608,92 +36678,116 @@ }, "dependencies": { "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" } } }, "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.3" + "version": "1.0.0-dev0.35.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.35.0.tgz", + "integrity": "sha512-X35g2ktmXbiTwjMNF20IkuNawJJ6Tlvrv23VuUVIjWHkpWcmyCYWIBle2zo7QAF6nnJpkccwFKJiC+TIkWl7hg==" }, "@deephaven/jsapi-utils": { - "version": "0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-utils/-/jsapi-utils-0.84.0.tgz", + "integrity": "sha512-CwYs9NHPs3wb+D0PM4VTBrqXzaHNHmuHTkep8XitZHAr814jOGcQhnkTauSpo+ulPwnheweO8C8wMjjBH/VojQ==", "requires": { - "@deephaven/filters": "^0.81.0", + "@deephaven/filters": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@deephaven/log": "^0.84.0", + "@deephaven/utils": "^0.84.0", "lodash.clamp": "^4.0.3", "nanoid": "^5.0.7" }, "dependencies": { "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" } } }, "@deephaven/log": { - "version": "0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/log/-/log-0.84.0.tgz", + "integrity": "sha512-B3KS2n/dT8GPblR8Top9GPB53pMmUglAMUVg93V3X10EPemE1jwiYEoJuceZ20JpRncCfRVuazrNYP7GItjWPQ==", "requires": { "event-target-shim": "^6.0.2" } }, "@deephaven/plugin": { - "version": "0.81.1", - "requires": { - "@deephaven/components": "^0.81.1", - "@deephaven/golden-layout": "^0.81.1", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/plugin/-/plugin-0.84.0.tgz", + "integrity": "sha512-G3f3WGc159TVUZOekZPmUAChshdDqE7OzuigGXeFlv/CCmlQhX5t1BWwqfYRFXkHwV5zk9UUyuQEhrNAUS+Zmg==", + "requires": { + "@deephaven/components": "^0.84.0", + "@deephaven/golden-layout": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/log": "^0.81.0", - "@deephaven/react-hooks": "^0.81.0", + "@deephaven/log": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", "@fortawesome/fontawesome-common-types": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.0" }, "dependencies": { + "@deephaven/golden-layout": { + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/golden-layout/-/golden-layout-0.84.0.tgz", + "integrity": "sha512-Pmy157rhKbf7U5YV6rBg7/r1ZHTHATw2UMQ501JEKAbOThrHIWrzktKk2Kfd4kpDs1CYPZsNJEuYwkzd+Xy2wA==", + "requires": { + "@deephaven/components": "^0.84.0", + "jquery": "^3.6.0", + "nanoid": "^5.0.7" + } + }, "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" } } }, "@deephaven/react-hooks": { - "version": "0.81.0", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/react-hooks/-/react-hooks-0.84.0.tgz", + "integrity": "sha512-VIcBAkfdyKI5JbITW5kouDfyfEm1ggUhbAYNWRUFlRSPNkoWxFfflc4eplQAfmj/VDjaVAizWNjc1JbH8OG/tg==", "requires": { - "@adobe/react-spectrum": "3.33.1", - "@deephaven/log": "^0.81.0", - "@deephaven/utils": "^0.81.0", + "@adobe/react-spectrum": "3.35.1", + "@deephaven/log": "^0.84.0", + "@deephaven/utils": "^0.84.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.7" } }, "@deephaven/redux": { - "version": "0.81.1", + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/redux/-/redux-0.84.0.tgz", + "integrity": "sha512-mj05IMOPtKhol5eLdiHORdyvnUv/bH7/sSV18q8hYJ3eHtpKyvuou3256s4NSpvgUCjyxTvQbknR9qemk1fVAw==", "requires": { "@deephaven/jsapi-types": "1.0.0-dev0.34.0", - "@deephaven/jsapi-utils": "^0.81.0", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", + "@deephaven/jsapi-utils": "^0.84.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", "fast-deep-equal": "^3.1.3", "proxy-memoize": "^3.0.0", "redux-thunk": "2.4.1" }, "dependencies": { "@deephaven/jsapi-types": { - "version": "1.0.0-dev0.34.0" + "version": "1.0.0-dev0.34.0", + "resolved": "https://registry.npmjs.org/@deephaven/jsapi-types/-/jsapi-types-1.0.0-dev0.34.0.tgz", + "integrity": "sha512-UiIbmCaMx5mPOGCWdgOCfZtccMhh55jv3qzeN3qBp3YUi46uGfWY5kfCU3hWRtaQvUgO7n0XhBKTd4K/pxv9ng==" } } }, - "@deephaven/storage": { - "version": "0.81.0", - "requires": { - "@deephaven/filters": "^0.81.0", - "@deephaven/log": "^0.81.0", - "lodash.throttle": "^4.1.1" - } - }, "@deephaven/utils": { - "version": "0.81.0" + "version": "0.84.0", + "resolved": "https://registry.npmjs.org/@deephaven/utils/-/utils-0.84.0.tgz", + "integrity": "sha512-eWTlmxtdYFuV7q127AwMyLnf5j0FGpPG6gJ+2ChpFeMS+ZfOHpgcRHZC2+j3y/b5z4mGm2bIjzQDJpMNPL4thg==" }, "buffer": { "version": "6.0.3", @@ -36704,12 +36798,16 @@ }, "color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.4" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "event-target-shim": { "version": "6.0.2" @@ -36719,6 +36817,8 @@ }, "redux-thunk": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz", + "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==", "requires": {} }, "typescript": { @@ -51521,7 +51621,9 @@ "dev": true }, "proxy-compare": { - "version": "3.0.0" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.0.tgz", + "integrity": "sha512-y44MCkgtZUCT9tZGuE278fB7PWVf7fRYy0vbRXAts2o5F0EfC4fIQrvQQGBJo1WJbFcVLXzApOscyJuZqHQc1w==" }, "proxy-from-env": { "version": "1.1.0", @@ -51529,6 +51631,8 @@ }, "proxy-memoize": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proxy-memoize/-/proxy-memoize-3.0.0.tgz", + "integrity": "sha512-2fs4eIg4w6SfOjKHGVdg5tJ9WgHifEXKo2gfS/+tHGajO2YtAu03lLs+ltNKnteGKvq3SvHromkZeKus4J39/g==", "requires": { "proxy-compare": "^3.0.0" } diff --git a/plugins/plotly-express/make_docs.py b/plugins/plotly-express/make_docs.py index 5b31339ec..fa399006e 100644 --- a/plugins/plotly-express/make_docs.py +++ b/plugins/plotly-express/make_docs.py @@ -2,6 +2,13 @@ BUILT_DOCS = "docs/build/markdown" +# save original directory so we can return to it +cwd = os.getcwd() + +# change to the directory of this file +dirname = os.path.dirname(__file__) +os.chdir(dirname) + os.system("make clean") print("Building markdown") @@ -13,22 +20,26 @@ os.system(f"rm {BUILT_DOCS}/index.md") -# go through each markdown file, look for ### deephaven.plot.express then escape any < characters -# this ensures function default values are shown -for root, dirs, files in os.walk(BUILT_DOCS): - for file in files: - if file.endswith(".md"): - with open(os.path.join(root, file), "r") as f: - lines = f.readlines() - with open(os.path.join(root, file), "w") as f: - for line in lines: - if "### deephaven.plot.express." in line: - # remove escaped \* with * as it's not needed when in a code block - line = line.replace("\\*", "*") - # first add the lines here - line = line.replace("### deephaven.plot.express.", "") - before = "\n\n```python\n" - after = "```\n\n\n" - line = before + line + after - # then here - f.write(line) +try: + # go through each markdown file, look for ### deephaven.plot.express then add the syntax block + for root, dirs, files in os.walk(BUILT_DOCS): + for file in files: + if file.endswith(".md"): + with open(os.path.join(root, file), "r") as f: + lines = f.readlines() + with open(os.path.join(root, file), "w") as f: + for line in lines: + if "### deephaven.plot.express." in line: + # remove escaped \* with * as it's not needed when in a code block + line = line.replace("\\*", "*") + # first add the lines here + line = line.replace("### deephaven.plot.express.", "") + before = "\n\n```python\n" + after = "```\n\n\n" + line = before + line + after + # then here + f.write(line) + +finally: + # ensure we always return to the original directory, even if an exception is raised + os.chdir(cwd) diff --git a/plugins/plotly-express/src/deephaven/plot/express/data/data_generators.py b/plugins/plotly-express/src/deephaven/plot/express/data/data_generators.py index 65cdee448..0acef0370 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/data/data_generators.py +++ b/plugins/plotly-express/src/deephaven/plot/express/data/data_generators.py @@ -654,9 +654,10 @@ def create_empty(val: Any, reps: int) -> list[Any]: gapminder_no_2007.loc[:, ["year"]] = gapminder_no_2007["year"].apply( lambda x: create_years(x, 5) ) - gapminder_no_2007.loc[:, ["lifeExp", "pop", "gdpPercap"]] = gapminder_no_2007[ - ["lifeExp", "pop", "gdpPercap"] - ].map(lambda x: create_empty(x, 5)) + for col in ["lifeExp", "pop", "gdpPercap"]: + gapminder_no_2007.loc[:, [col]] = gapminder_no_2007[col].apply( + lambda x: create_empty(x, 5) + ) gapminder_no_2007 = gapminder_no_2007.explode( column=["year", "lifeExp", "pop", "gdpPercap"] ) @@ -672,9 +673,10 @@ def create_empty(val: Any, reps: int) -> list[Any]: ) # expand pre-2007 dataset into consecutive months - gapminder_no_2007.loc[:, ["lifeExp", "pop", "gdpPercap"]] = gapminder_no_2007[ - ["lifeExp", "pop", "gdpPercap"] - ].map(lambda x: create_empty(x, 12)) + for col in ["lifeExp", "pop", "gdpPercap"]: + gapminder_no_2007.loc[:, [col]] = gapminder_no_2007[col].apply( + lambda x: create_empty(x, 12) + ) gapminder_no_2007 = gapminder_no_2007.explode( column=["month", "lifeExp", "pop", "gdpPercap"] ) diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py index 372763d9e..ed67460f7 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/bar.py @@ -1,6 +1,5 @@ from __future__ import annotations -from numbers import Number from typing import Callable from plotly import express as px @@ -38,8 +37,8 @@ def bar( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, barmode: str = "relative", log_x: bool = False, @@ -221,8 +220,8 @@ def timeline( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, range_x: list[int] | None = None, range_y: list[int] | None = None, diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/hierarchial.py b/plugins/plotly-express/src/deephaven/plot/express/plots/hierarchial.py index ce6295695..3654b9a69 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/hierarchial.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/hierarchial.py @@ -1,6 +1,5 @@ from __future__ import annotations -from numbers import Number from typing import Callable from plotly import express as px @@ -23,8 +22,8 @@ def treemap( color_discrete_sequence: list[str] | None = None, color_discrete_map: dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, labels: dict[str, str] | None = None, title: str | None = None, template: str | None = None, @@ -91,8 +90,8 @@ def sunburst( color_discrete_sequence: list[str] | None = None, color_discrete_map: dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, labels: dict[str, str] | None = None, title: str | None = None, template: str | None = None, @@ -159,8 +158,8 @@ def icicle( color_discrete_sequence: list[str] | None = None, color_discrete_map: dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, labels: dict[str, str] | None = None, title: str | None = None, template: str | None = None, diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py b/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py index 61af14358..2557b84d7 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/maps.py @@ -1,6 +1,5 @@ from __future__ import annotations -from numbers import Number from typing import Callable from plotly import express as px @@ -41,8 +40,8 @@ def scatter_geo( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, projection: str | None = None, scope: str | None = None, @@ -175,8 +174,8 @@ def scatter_mapbox( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, zoom: float | None = None, center: dict[str, float] | None = None, diff --git a/plugins/plotly-express/src/deephaven/plot/express/plots/scatter.py b/plugins/plotly-express/src/deephaven/plot/express/plots/scatter.py index 71e20f44f..6ba28acd4 100644 --- a/plugins/plotly-express/src/deephaven/plot/express/plots/scatter.py +++ b/plugins/plotly-express/src/deephaven/plot/express/plots/scatter.py @@ -1,6 +1,5 @@ from __future__ import annotations -from numbers import Number from typing import Callable from plotly import express as px @@ -44,8 +43,8 @@ def scatter( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, xaxis_sequence: list[int] | None = None, yaxis_sequence: list[int] | None = None, opacity: float | None = None, @@ -221,8 +220,8 @@ def scatter_3d( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, log_x: bool = False, log_y: bool = False, @@ -367,8 +366,8 @@ def scatter_polar( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, direction: str = "clockwise", start_angle: int = 90, @@ -493,8 +492,8 @@ def scatter_ternary( | dict[str | tuple[str], str] | None = None, color_continuous_scale: list[str] | None = None, - range_color: list[Number] | None = None, - color_continuous_midpoint: Number | None = None, + range_color: list[float] | None = None, + color_continuous_midpoint: float | None = None, opacity: float | None = None, title: str | None = None, template: str | None = None, diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index 82cdd8a40..4119b9f2b 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1882,41 +1882,45 @@ ui_table( on_quick_filter: Callable[[ColumnName, QuickFilterExpression], None] | None on_freeze_column: Callable[[ColumnName], None] | None, on_hide_column: Callable[[ColumnName], None] | None, - on_sort: Callable[[ColumnName, LiteralSortDirection], None] | None + on_sort: Callable[[ColumnName, LiteralSortDirection], None] | None, + context_menu: ResolvableContextMenuItem | list[ResolvableContextMenuItem] | None, + context_header_menu: ResolvableContextMenuItem | list[ResolvableContextMenuItem] | None, ) -> UITable ``` -| Parameter | Type | Description | -| ------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `always_fetch_columns` | `list[ColumnName] \| None` | The columns to always fetch from the server. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | -| `front_columns` | `list[ColumnName] \| None` | The columns to show at the front of the table. These will not be moveable in the UI. | -| `back_columns` | `list[ColumnName] \| None` | The columns to show at the back of the table. These will not be moveable in the UI. | -| `frozen_columns` | `list[ColumnName] \| None` | The columns to freeze to the front of the table. These will always be visible and not affected by horizontal scrolling. | -| `hidden_columns` | `list[ColumnName] \| None` | The columns to hide by default from the table. The user can still resize the columns to view them. | -| `column_groups` | `list[ColumnGroup] \| None` | The column groups to show in the table. Columns will be moved adjacent to the first child in the group. Groups will be shown in the table header. | -| `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | -| `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | -| `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | -| `show_column_headers` | `bool \| None` | `True` to show the column headers by default, `False` to not. | -| `selection_mode` | `SelectionMode \| None` | Can be `MULTIPLE` to allow multiple selection or `SINGLE` to not allow it. | -| `selection_area` | `SelectionArea \| None` | The unit that is selected on press. Can be `ROW`, `COLUMN`, or `CELL`. | -| `selection_style` | `SelectionStyleCombination \| None` | The style of the selection. Can be `HIGHLIGHT`, `CHECKBOX`, or a combination of those. | -| `selected_rows` | `RowIndexCombination \| None` | The rows that are selected by default. Only valid if `selection_area` is `ROW`. | -| `selected_columns` | `ColumnIndexCombination \| None` | The columns that are selected by default. Only valid if `selection_area` is `COLUMN`. | -| `selected_cells` | `CellIndexCombination \| None` | The cells that are selected by default. Only valid if `selection_area` is `CELL`. | -| `density` | `DensityMode \| None` | The density of the table. Can be `COMPACT`, `REGULAR`, or `SPACIOUS`. | -| `column_display_names` | `dict[ColumnName, ColumnNameCombination] \| None` | The display names. If a sequence of column names is provided for a column, the display name will be set to the longest column name that can be fully displayed. | -| `on_row_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is released (such as a click). The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | -| `on_row_double_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is double pressed. The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | -| `on_cell_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is released (such as a click). The first parameter is the cell index, and the second is the cell data. | -| `on_cell_double_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is double pressed. The first parameter is the cell index, and the second is the cell data. | -| `on_column_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is released (such as a click). The only parameter is the column name. | -| `on_column_double_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a cell in a column is double pressed. The only parameter is the column name. | -| `on_search` | `Callable[[str], None] \| None` | The callback function to run when the search bar is used. The only parameter is the search string. | -| `on_quick_filter` | `Callable[[ColumnName, QuickFilterExpression], None] \| None` | The callback function to run when a quick filter is applied. The first parameter is the column name, and the second is the quick filter expression. | -| `on_freeze_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is frozen. The only parameter is the frozen column name. | -| `on_hide_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is hidden. The only parameter is the hidden column name. | -| `on_sort` | `Callable[[ColumnName, LiteralSortDirection], None] \| None` | The callback function to run when a column is sorted. The first parameter is the column name, and the second is the sort direction. | +| Parameter | Type | Description | +| ------------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `always_fetch_columns` | `list[ColumnName] \| None` | The columns to always fetch from the server. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | +| `front_columns` | `list[ColumnName] \| None` | The columns to show at the front of the table. These will not be moveable in the UI. | +| `back_columns` | `list[ColumnName] \| None` | The columns to show at the back of the table. These will not be moveable in the UI. | +| `frozen_columns` | `list[ColumnName] \| None` | The columns to freeze to the front of the table. These will always be visible and not affected by horizontal scrolling. | +| `hidden_columns` | `list[ColumnName] \| None` | The columns to hide by default from the table. The user can still resize the columns to view them. | +| `column_groups` | `list[ColumnGroup] \| None` | The column groups to show in the table. Columns will be moved adjacent to the first child in the group. Groups will be shown in the table header. | +| `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | +| `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | +| `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | +| `show_column_headers` | `bool \| None` | `True` to show the column headers by default, `False` to not. | +| `selection_mode` | `SelectionMode \| None` | Can be `MULTIPLE` to allow multiple selection or `SINGLE` to not allow it. | +| `selection_area` | `SelectionArea \| None` | The unit that is selected on press. Can be `ROW`, `COLUMN`, or `CELL`. | +| `selection_style` | `SelectionStyleCombination \| None` | The style of the selection. Can be `HIGHLIGHT`, `CHECKBOX`, or a combination of those. | +| `selected_rows` | `RowIndexCombination \| None` | The rows that are selected by default. Only valid if `selection_area` is `ROW`. | +| `selected_columns` | `ColumnIndexCombination \| None` | The columns that are selected by default. Only valid if `selection_area` is `COLUMN`. | +| `selected_cells` | `CellIndexCombination \| None` | The cells that are selected by default. Only valid if `selection_area` is `CELL`. | +| `density` | `DensityMode \| None` | The density of the table. Can be `COMPACT`, `REGULAR`, or `SPACIOUS`. | +| `column_display_names` | `dict[ColumnName, ColumnNameCombination] \| None` | The display names. If a sequence of column names is provided for a column, the display name will be set to the longest column name that can be fully displayed. | +| `on_row_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is released (such as a click). The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | +| `on_row_double_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is double pressed. The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | +| `on_cell_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is released (such as a click). The first parameter is the cell index, and the second is the cell data. | +| `on_cell_double_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is double pressed. The first parameter is the cell index, and the second is the cell data. | +| `on_column_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is released (such as a click). The only parameter is the column name. | +| `on_column_double_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a cell in a column is double pressed. The only parameter is the column name. | +| `on_search` | `Callable[[str], None] \| None` | The callback function to run when the search bar is used. The only parameter is the search string. | +| `on_quick_filter` | `Callable[[ColumnName, QuickFilterExpression], None] \| None` | The callback function to run when a quick filter is applied. The first parameter is the column name, and the second is the quick filter expression. | +| `on_freeze_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is frozen. The only parameter is the frozen column name. | +| `on_hide_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is hidden. The only parameter is the hidden column name. | +| `on_sort` | `Callable[[ColumnName, LiteralSortDirection], None] \| None` | The callback function to run when a column is sorted. The first parameter is the column name, and the second is the sort direction. | +| `context_menu` | `ResolvableContextMenuItem \| list[ResolvableContextMenuItem] \| None` | The context menu items to show when right-clicking on a cell in the table. Can contain `ContextMenuSubmenuItem`s to define submenus. | +| `context_header_menu` | `ResolvableContextMenuItem \| list[ResolvableContextMenuItem] \| None` | The context menu items to show when right-clicking on the column header (i.e. column name). Can contain `ContextMenuSubmenuItem`s to define submenus. | `ui.table` will also support the below methods. @@ -2023,28 +2027,6 @@ ui_table.color_row( | `color` | `Color \| None` | The text color. Accepts hex color strings or Deephaven color names. | | `background_color` | `Color \| None` | The background color. Accepts hex color strings or Deephaven color names. | -##### context_menu - -Add custom items to the context menu. You can provide a list of actions that always appear, or a callback that can process the selection and send back menu items asynchronously. You can also specify whether you want the menu items provided for a cell context menu, a header context menu, or some combination of those. You can also chain multiple sets of menu items by calling `.context_menu` multiple times. - -###### Syntax - -```py -ui_table.context_menu( - items: ContextMenuAction - | list[ContextMenuAction] - | Callable[[CellIndex, RowData], ContextMenuAction | list[ContextMenuAction]], - mode: ContextMenuMode = "CELL", -) -> UITable -``` - -###### Parameters - -| Parameter | Type | Description | -| --------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `items` | `ContextMenuAction \| list[ContextMenuAction] \| Callable[[CellIndex, RowData], ContextMenuAction \| list[ContextMenuAction]]` | The items to add to the context menu. May be a single `ContextMenuAction`, a list of `ContextMenuAction` objects, or a callback function that takes the cell index and row data and returns either a single `ContextMenuAction` or a list of `ContextMenuAction` objects. | -| `mode` | `ContextMenuMode` | Which specific context menu(s) to add the menu item(s) to. Can be one or more modes. Using `None` will add menu items in all cases.
- `CELL`: Triggered from a cell.
- `ROW_HEADER`: Triggered from a row header.
- `COLUMN_HEADER`: Triggered from a column header. | - ##### data_bar Applies data bar formatting to the specified column. @@ -2311,7 +2293,6 @@ ColumnName = str ColumnData = list[Any] # ID of a component. Used for linking. ComponentId = str -ContextMenuAction = dict[str, Any] ContextMenuModeOption = Literal["CELL", "ROW_HEADER", "COLUMN_HEADER"] ContextMenuMode = ContextMenuModeOption | list[ContextMenuModeOption] | None DataBarAxis = Literal["PROPORTIONAL", "MIDDLE", "DIRECTIONAL"] @@ -2413,6 +2394,105 @@ class ColumnGroup(TypedDict): # Optional background color of the group header color: NotRequired[str] + +class ContextMenuActionParams(TypedDict): + """ + Parameters given to a context menu action + """ + + value: Any + """ + Value of the cell. + """ + + text_value: str + """ + Rendered text for the cell. + """ + + column_name: str + """ + Name of the column. + """ + + is_column_header: bool + """ + Whether the context menu was opened on a column header. + """ + + is_row_header: bool + """ + Whether the context menu was opened on a row header. + """ + + +ContextMenuAction = Callable[[ContextMenuActionParams], None] +""" +The action to execute when the context menu item is clicked. +""" + + +class ContextMenuItemBase(TypedDict): + """ + Base props that context menu items and submenu items share. + """ + + title: str + """ + Title to display for the action. + """ + + icon: NotRequired[str] + """ + The name of the icon to display next to the action. + The name must be a valid name for ui.icon. + """ + + description: NotRequired[str] + """ + Description for the action. Will be used as a tooltip for the action. + """ + + +class ContextMenuActionItem(ContextMenuItemBase): + """ + An item that appears in a context menu and performs an action when clicked. + """ + + action: ContextMenuAction + """ + Action to run when the menu item is clicked. + """ + + +class ContextMenuSubmenuItem(ContextMenuItemBase): + """ + An item that contains a submenu for a context menu. + """ + + actions: list["ResolvableContextMenuItem"] + """ + A list of actions that will form the submenu for the item. + """ + + +ContextMenuItem = ContextMenuActionItem | ContextMenuSubmenuItem +""" +An item that can appear in a context menu. +May contain an action item or a submenu item. +""" + +ResolvableContextMenuItem = ( + ContextMenuItem + | Callable[ + [ContextMenuActionParams], ContextMenuItem | List[ContextMenuItem] | None + ] +) +""" +A context menu item or a function that returns a list of context menu items or None. +This can be used to dynamically generate context menu items based on the cell the menu is opened on. +""" + ``` #### Context diff --git a/plugins/ui/docs/README.md b/plugins/ui/docs/README.md index c8d245d4d..856938da3 100644 --- a/plugins/ui/docs/README.md +++ b/plugins/ui/docs/README.md @@ -204,6 +204,7 @@ my_checkbox = ui_checkbox() ![Checkbox](_assets/checkbox.png) ## ActionGroup (string values) + An ActionGroup is a grouping of ActionButtons that are related to one another. ```python @@ -227,6 +228,7 @@ my_action_group = ui_action_group() ``` ## ActionMenu (string values) + ActionMenu combines an ActionButton with a Menu for simple "more actions" use cases. ```python @@ -356,6 +358,7 @@ def ui_picker_table(): pick_table = ui_picker_table() ``` + ![Use a picker to select from a table](_assets/pick_table.png) ## Picker (item table source) @@ -402,6 +405,7 @@ pick_table_source = ui_picker_table_source() ![Use a picker to select from a table source](_assets/pick_table_source.png) ## ListView (string values) + A list view that can be used to create a list of selectable items. Here's a basic example for selecting from a list of string values and displaying the selected key in a text field. ```python @@ -487,6 +491,7 @@ def ui_list_view_table(): lv_table = ui_list_view_table() ``` + ![Use a list view to select from a table](_assets/lv_table.png) ## ListView (item table source) @@ -594,6 +599,7 @@ my_list_view_action_group = ui_list_view_action_group() ``` ## ListView (list action menu) + A list view can take a `list_action_menu` as its `actions` prop. ```python @@ -1283,6 +1289,72 @@ te = ui.table( ![Table events](table_events.png) +### ui.table Context Menu + +Items can be added to the bottom of the `ui.table` context menu (right-click menu) by using the `context_menu` or `context_header_menu` props. The `context_menu` prop adds items to the cell context menu, while the `context_header_menu` prop adds items to the column header context menu. + +Menu items must have a `title` and either an `action` or `actions` prop. They may have an `icon` which is the name of the icon that will be passed to `ui.icon`. + +The `action` prop is a callback that is called when the item is clicked and receives info about the cell that was clicked when the menu was opened. + +The `actions` prop is an array of menu items that will be displayed in a sub-menu. Sub-menus can contain other sub-menus for a nested menu. + +Menu items can be dynamically created by instead passing a function as the context item. The function will be called with the data of the cell that was clicked when the menu was opened, and must return the menu items or None. + +```py +from deephaven import ui +import deephaven.plot.express as dx + +t = ui.table( + dx.data.stocks(), + context_menu=[ + { + "title": "Context item", + "icon": "dhTruck", + "action": lambda d: print("Context item", d) + }, + { + "title": "Nested menu", + "actions": [ + { + "title": "Nested item 1", + "action": lambda d: print("Nested item 1", d) + } + { + "title": "Nested item 2", + "icon": "vsCheck" + "action": lambda d: print("Nested item 2", d) + } + ] + } + ], + context_header_menu={ + "title": "Header context menu item", + "action": lambda d: print("Header context menu item", d) + } +) +``` + +The following example shows creating context menu items dynamically so that the item only appears on the `sym` column. If multiple functions are passed in a list, each will be called and any items they return will be added to the context menu. + +```py +from deephaven import ui +import deephaven.plot.express as dx + +def create_context_menu(data): + if data["column_name"] == "sym": + return { + "title": f"Print {data['value']}", + "action": lambda d: print(d['value']) + } + return None + +t = ui.table( + dx.data.stocks(), + context_menu=create_context_menu +) +``` + ## Re-using components In a previous example, we created a text_filter_table component. We can re-use that component, and display two tables with an input filter side-by-side: diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index 2047ce050..abd022bf3 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -3,9 +3,7 @@ from .action_menu import action_menu from .basic import ( component_element, - heading, icon_wrapper, - illustrated_message, form, switch, tabs, @@ -24,8 +22,10 @@ from .date_picker import date_picker from .flex import flex from .fragment import fragment +from .heading import heading from .grid import grid from .icon import icon +from .illustrated_message import illustrated_message from .item import item from .item_table_source import item_table_source from .list_action_group import list_action_group diff --git a/plugins/ui/src/deephaven/ui/components/action_button.py b/plugins/ui/src/deephaven/ui/components/action_button.py index 3f31fc5dc..faef01337 100644 --- a/plugins/ui/src/deephaven/ui/components/action_button.py +++ b/plugins/ui/src/deephaven/ui/components/action_button.py @@ -17,7 +17,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) @@ -45,12 +44,12 @@ def action_button( is_quiet: bool | None = None, static_color: StaticColor | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -78,7 +77,7 @@ def action_button( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, exclude_from_tab_order: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/action_group.py b/plugins/ui/src/deephaven/ui/components/action_group.py index f167892ee..6e4bc2024 100644 --- a/plugins/ui/src/deephaven/ui/components/action_group.py +++ b/plugins/ui/src/deephaven/ui/components/action_group.py @@ -1,5 +1,4 @@ from __future__ import annotations -from numbers import Number from typing import Any, Callable, Iterable @@ -43,12 +42,12 @@ def action_group( on_change: Callable[[Key], None] | None = None, on_selection_change: Callable[[Selection], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_column: str | None = None, @@ -76,7 +75,7 @@ def action_group( right: DimensionValue | None = None, start: DimensionValue | None = None, end: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/action_menu.py b/plugins/ui/src/deephaven/ui/components/action_menu.py index 9b8601853..640dae31d 100644 --- a/plugins/ui/src/deephaven/ui/components/action_menu.py +++ b/plugins/ui/src/deephaven/ui/components/action_menu.py @@ -1,5 +1,4 @@ from __future__ import annotations -from numbers import Number from typing import Callable, Iterable from .item import Item @@ -45,12 +44,12 @@ def action_menu( on_action: Callable[[ActionKey], None] | None = None, on_open_change: Callable[[bool], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -78,7 +77,7 @@ def action_menu( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/basic.py b/plugins/ui/src/deephaven/ui/components/basic.py index 92cd315e9..ce008d6f8 100644 --- a/plugins/ui/src/deephaven/ui/components/basic.py +++ b/plugins/ui/src/deephaven/ui/components/basic.py @@ -12,14 +12,6 @@ def component_element(name: str, /, *children: Any, **props: Any) -> BaseElement return BaseElement(f"deephaven.ui.components.{name}", *children, **props) -def heading(*children, **props): - """ - Python implementation for the Adobe React Spectrum Heading component. - https://react-spectrum.adobe.com/react-spectrum/Heading.html - """ - return component_element("Heading", *children, **props) - - def icon_wrapper(*children, **props): """ Python implementation for the Adobe React Spectrum Icon component. @@ -31,14 +23,6 @@ def icon_wrapper(*children, **props): return component_element("Icon", *children, **props) -def illustrated_message(*children, **props): - """ - Python implementation for the Adobe React Spectrum IllustratedMessage component. - https://react-spectrum.adobe.com/react-spectrum/IllustratedMessage.html - """ - return component_element("IllustratedMessage", *children, **props) - - def form(*children, **props): """ Python implementation for the Adobe React Spectrum Form component. diff --git a/plugins/ui/src/deephaven/ui/components/button.py b/plugins/ui/src/deephaven/ui/components/button.py index d13d7cab6..fe6e5b135 100644 --- a/plugins/ui/src/deephaven/ui/components/button.py +++ b/plugins/ui/src/deephaven/ui/components/button.py @@ -20,7 +20,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) from .basic import component_element @@ -51,12 +50,12 @@ def button( on_key_down: KeyboardEventCallable | None = None, on_key_up: KeyboardEventCallable | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_column: str | None = None, grid_row: str | None = None, @@ -84,7 +83,7 @@ def button( right: DimensionValue | None = None, start: DimensionValue | None = None, end: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, exclude_from_tab_order: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/button_group.py b/plugins/ui/src/deephaven/ui/components/button_group.py index 8f08d78ad..d88d1a09a 100644 --- a/plugins/ui/src/deephaven/ui/components/button_group.py +++ b/plugins/ui/src/deephaven/ui/components/button_group.py @@ -9,7 +9,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) from .basic import component_element @@ -22,12 +21,12 @@ def button_group( orientation: Orientation = "horizontal", alignment: AlignSelf = "start", flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_column: str | None = None, @@ -55,7 +54,7 @@ def button_group( right: DimensionValue | None = None, start: DimensionValue | None = None, end: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, UNSAFE_class_name: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/checkbox.py b/plugins/ui/src/deephaven/ui/components/checkbox.py index e9818d780..33b77c62d 100644 --- a/plugins/ui/src/deephaven/ui/components/checkbox.py +++ b/plugins/ui/src/deephaven/ui/components/checkbox.py @@ -11,7 +11,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) from .basic import component_element @@ -40,12 +39,12 @@ def checkbox( on_key_down: KeyboardEventCallable | None = None, on_key_up: KeyboardEventCallable | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -73,7 +72,7 @@ def checkbox( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, exclude_from_tab_order: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/combo_box.py b/plugins/ui/src/deephaven/ui/components/combo_box.py index 41b21bc09..fc77072c0 100644 --- a/plugins/ui/src/deephaven/ui/components/combo_box.py +++ b/plugins/ui/src/deephaven/ui/components/combo_box.py @@ -6,7 +6,6 @@ FocusEventCallable, KeyboardEventCallable, LayoutFlex, - Number, DimensionValue, AlignSelf, JustifySelf, @@ -85,12 +84,12 @@ def combo_box( on_key_down: Callable[[KeyboardEventCallable], None] | None = None, on_key_up: Callable[[KeyboardEventCallable], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -118,7 +117,7 @@ def combo_box( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/content.py b/plugins/ui/src/deephaven/ui/components/content.py index dd7e27130..5db0f1062 100644 --- a/plugins/ui/src/deephaven/ui/components/content.py +++ b/plugins/ui/src/deephaven/ui/components/content.py @@ -7,7 +7,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) from .basic import component_element @@ -17,12 +16,12 @@ def content( *children: Any, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -50,7 +49,7 @@ def content( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, UNSAFE_class_name: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/contextual_help.py b/plugins/ui/src/deephaven/ui/components/contextual_help.py index 95e3cc155..c509b23a8 100644 --- a/plugins/ui/src/deephaven/ui/components/contextual_help.py +++ b/plugins/ui/src/deephaven/ui/components/contextual_help.py @@ -1,5 +1,4 @@ from __future__ import annotations -from numbers import Number from typing import Any, Callable from .types import ( # Validation @@ -23,18 +22,18 @@ def contextual_help( placement: Placement | None = "bottom start", is_open: bool | None = None, default_open: bool | None = None, - container_padding: Number | None = None, - offset: Number | None = None, - cross_offset: Number | None = None, + container_padding: float | None = None, + offset: float | None = None, + cross_offset: float | None = None, should_flip: bool | None = None, on_open_change: Callable[[bool], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_column: str | None = None, @@ -62,7 +61,7 @@ def contextual_help( right: DimensionValue | None = None, start: DimensionValue | None = None, end: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/date_picker.py b/plugins/ui/src/deephaven/ui/components/date_picker.py index 255ec472d..51e47e131 100644 --- a/plugins/ui/src/deephaven/ui/components/date_picker.py +++ b/plugins/ui/src/deephaven/ui/components/date_picker.py @@ -6,7 +6,6 @@ FocusEventCallable, KeyboardEventCallable, LayoutFlex, - Number, DimensionValue, AlignSelf, JustifySelf, @@ -112,12 +111,12 @@ def date_picker( on_open_change: Callable[[bool], None] | None = None, on_change: Callable[[Date], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -145,7 +144,7 @@ def date_picker( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/grid.py b/plugins/ui/src/deephaven/ui/components/grid.py index 56a60f943..32a43ea94 100644 --- a/plugins/ui/src/deephaven/ui/components/grid.py +++ b/plugins/ui/src/deephaven/ui/components/grid.py @@ -7,7 +7,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, GridFlow, JustifyItems, @@ -35,12 +34,12 @@ def grid( column_gap: DimensionValue | None = None, row_gap: DimensionValue | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -68,7 +67,7 @@ def grid( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, UNSAFE_class_name: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/heading.py b/plugins/ui/src/deephaven/ui/components/heading.py new file mode 100644 index 000000000..86d657030 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/heading.py @@ -0,0 +1,149 @@ +from __future__ import annotations +from typing import Any +from .types import ( + # Layout + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, + HeadingLevel, +) +from .basic import component_element +from ..elements import Element + + +def heading( + *children: Any, + level: HeadingLevel = 3, + flex: LayoutFlex | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: int | None = None, + grid_area: str | None = None, + grid_row: str | None = None, + grid_row_start: str | None = None, + grid_row_end: str | None = None, + grid_column: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + margin: DimensionValue | None = None, + margin_top: DimensionValue | None = None, + margin_bottom: DimensionValue | None = None, + margin_start: DimensionValue | None = None, + margin_end: DimensionValue | None = None, + margin_x: DimensionValue | None = None, + margin_y: DimensionValue | None = None, + width: DimensionValue | None = None, + height: DimensionValue | None = None, + min_width: DimensionValue | None = None, + min_height: DimensionValue | None = None, + max_width: DimensionValue | None = None, + max_height: DimensionValue | None = None, + position: Position | None = None, + top: DimensionValue | None = None, + bottom: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + z_index: int | None = None, + is_hidden: bool | None = None, + id: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, +) -> Element: + """ + A layout container using CSS grid. Supports Spectrum dimensions as values to ensure consistent and adaptive sizing and spacing. + + Args: + children: The content to render within the container. + level: Sets heading level, h1 through h6. Defaults to 3. + flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available. + flex_grow: When used in a flex layout, specifies how the element will grow to fit the space available. + flex_shrink: When used in a flex layout, specifies how the element will shrink to fit the space available. + flex_basis: When used in a flex layout, specifies the initial main size of the element. + align_self: Overrides the alignItems property of a flex or grid container. + justify_self: Species how the element is justified inside a flex or grid container. + order: The layout order for the element within a flex or grid container. + grid_area: When used in a grid layout, specifies the named grid area that the element should be placed in within the grid. + grid_row: When used in a grid layout, specifies the row the element should be placed in within the grid. + grid_column: When used in a grid layout, specifies the column the element should be placed in within the grid. + grid_row_start: When used in a grid layout, specifies the starting row to span within the grid. + grid_row_end: When used in a grid layout, specifies the ending row to span within the grid. + grid_column_start: When used in a grid layout, specifies the starting column to span within the grid. + grid_column_end: When used in a grid layout, specifies the ending column to span within the grid + margin: The margin for all four sides of the element. + margin_top: The margin for the top side of the element. + margin_bottom: The margin for the bottom side of the element. + margin_start: The margin for the logical start side of the element, depending on layout direction. + margin_end: The margin for the logical end side of the element, depending on layout direction. + margin_x: The margin for the left and right sides of the element. + margin_y: The margin for the top and bottom sides of the element. + width: The width of the element. + min_width: The minimum width of the element. + max_width: The maximum width of the element. + height: The height of the element. + min_height: The minimum height of the element. + max_height: The maximum height of the element + position: The position of the element. + top: The distance from the top of the containing element. + bottom: The distance from the bottom of the containing element. + left: The distance from the left of the containing element. + right: The distance from the right of the containing element. + start: The distance from the start of the containing element, depending on layout direction. + end: The distance from the end of the containing element, depending on layout direction. + z_index: The stack order of the element. + is_hidden: Whether the element is hidden. + id: The unique identifier of the element. + UNSAFE_class_name: A CSS class to apply to the element. + UNSAFE_style: A CSS style to apply to the element. + """ + return component_element( + "Heading", + children=children, + level=level, + flex=flex, + flex_grow=flex_grow, + flex_shrink=flex_shrink, + flex_basis=flex_basis, + align_self=align_self, + justify_self=justify_self, + order=order, + grid_area=grid_area, + grid_row=grid_row, + grid_row_start=grid_row_start, + grid_row_end=grid_row_end, + grid_column=grid_column, + grid_column_start=grid_column_start, + grid_column_end=grid_column_end, + margin=margin, + margin_top=margin_top, + margin_bottom=margin_bottom, + margin_start=margin_start, + margin_end=margin_end, + margin_x=margin_x, + margin_y=margin_y, + width=width, + height=height, + min_width=min_width, + min_height=min_height, + max_width=max_width, + max_height=max_height, + position=position, + top=top, + bottom=bottom, + start=start, + end=end, + left=left, + right=right, + z_index=z_index, + is_hidden=is_hidden, + id=id, + UNSAFE_class_name=UNSAFE_class_name, + UNSAFE_style=UNSAFE_style, + ) diff --git a/plugins/ui/src/deephaven/ui/components/illustrated_message.py b/plugins/ui/src/deephaven/ui/components/illustrated_message.py new file mode 100644 index 000000000..92c625057 --- /dev/null +++ b/plugins/ui/src/deephaven/ui/components/illustrated_message.py @@ -0,0 +1,159 @@ +from __future__ import annotations +from typing import Any +from .types import ( + AlignSelf, + CSSProperties, + DimensionValue, + JustifySelf, + LayoutFlex, + Position, +) +from .basic import component_element +from ..elements import Element + + +def illustrated_message( + *children: Any, + flex: LayoutFlex | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, + flex_basis: DimensionValue | None = None, + align_self: AlignSelf | None = None, + justify_self: JustifySelf | None = None, + order: int | None = None, + grid_area: str | None = None, + grid_row: str | None = None, + grid_column: str | None = None, + grid_column_start: str | None = None, + grid_column_end: str | None = None, + grid_row_start: str | None = None, + grid_row_end: str | None = None, + margin: DimensionValue | None = None, + margin_top: DimensionValue | None = None, + margin_bottom: DimensionValue | None = None, + margin_start: DimensionValue | None = None, + margin_end: DimensionValue | None = None, + margin_x: DimensionValue | None = None, + margin_y: DimensionValue | None = None, + width: DimensionValue | None = None, + height: DimensionValue | None = None, + min_width: DimensionValue | None = None, + min_height: DimensionValue | None = None, + max_width: DimensionValue | None = None, + max_height: DimensionValue | None = None, + position: Position | None = None, + top: DimensionValue | None = None, + bottom: DimensionValue | None = None, + left: DimensionValue | None = None, + right: DimensionValue | None = None, + start: DimensionValue | None = None, + end: DimensionValue | None = None, + z_index: int | None = None, + is_hidden: bool | None = None, + id: str | None = None, + UNSAFE_class_name: str | None = None, + UNSAFE_style: CSSProperties | None = None, +) -> Element: + """ + An IllustratedMessage displays an illustration and a message, usually for an empty state or an error page. + Args: + *children: The content of the IllustratedMessage which consist of three areas: an illustration, a title, and a body. + flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available. + flex_grow: When used in a flex layout, specifies how the element will grow to fit the space available. + flex_shrink: When used in a flex layout, specifies how the element will shrink to fit the space available. + flex_basis: When used in a flex layout, specifies the initial main size of the element. + align_self: Overrides the alignItems property of a flex or grid container. + justify_self: Species how the element is justified inside a flex or grid container. + order: The layout order for the element within a flex or grid container. + grid_area: When used in a grid layout specifies, specifies the named grid area that the element should be placed in within the grid. + grid_row: When used in a grid layout, specifies the row the element should be placed in within the grid. + grid_column: When used in a grid layout, specifies the column the element should be placed in within the grid. + grid_row_start: When used in a grid layout, specifies the starting row to span within the grid. + grid_row_end: When used in a grid layout, specifies the ending row to span within the grid. + grid_column_start: When used in a grid layout, specifies the starting column to span within the grid. + grid_column_end: When used in a grid layout, specifies the ending column to span within the grid. + margin: The margin for all four sides of the element. + margin_top: The margin for the top side of the element. + margin_bottom: The margin for the bottom side of the element. + margin_start: The margin for the logical start side of the element, depending on layout direction. + margin_end: The margin for the logical end side of the element, depending on layout direction. + margin_x: The margin for the left and right sides of the element. + margin_y: The margin for the top and bottom sides of the element. + width: The width of the element. + height: The height of the element. + min_width: The minimum width of the element. + min_height: The minimum height of the element. + max_width: The maximum width of the element. + max_height: The maximum height of the element. + position: Specifies how the element is position. + top: The top position of the element. + bottom: The bottom position of the element. + left: The left position of the element. + right: The right position of the element. + start: The logical start position of the element, depending on layout direction. + end: The logical end position of the element, depending on layout direction. + z_index: The stacking order for the element + is_hidden: Hides the element. + id: The unique identifier of the element. + UNSAFE_class_name: Set the CSS className for the element. Only use as a last resort. Use style props instead. + UNSAFE_style: Set the inline style for the element. Only use as a last resort. Use style props instead. + + Examples: + prompt = ui.illustrated_message( + ui.heading("Enter URL above"), + ui.content("Enter a URL of a CSV above and click 'Load' to load it"), + ) + warning = ui.illustrated_message( + ui.icon("vsWarning"), + ui.heading("Warning"), + ui.content("This is a warning message."), + ) + error_message = ui.illustrated_message( + ui.icon("vsWarning", size="XXL", margin_bottom="size-10"), + ui.heading("Invalid Input"), + ui.content("Please enter 'Sym' and 'Exchange' above"), + ) + """ + return component_element( + "IllustratedMessage", + *children, + flex=flex, + flex_grow=flex_grow, + flex_shrink=flex_shrink, + flex_basis=flex_basis, + align_self=align_self, + justify_self=justify_self, + order=order, + grid_area=grid_area, + grid_row=grid_row, + grid_column=grid_column, + grid_column_start=grid_column_start, + grid_column_end=grid_column_end, + grid_row_start=grid_row_start, + grid_row_end=grid_row_end, + margin=margin, + margin_top=margin_top, + margin_bottom=margin_bottom, + margin_start=margin_start, + margin_end=margin_end, + margin_x=margin_x, + margin_y=margin_y, + width=width, + height=height, + min_width=min_width, + min_height=min_height, + max_width=max_width, + max_height=max_height, + position=position, + top=top, + bottom=bottom, + left=left, + right=right, + start=start, + end=end, + z_index=z_index, + is_hidden=is_hidden, + id=id, + UNSAFE_class_name=UNSAFE_class_name, + UNSAFE_style=UNSAFE_style, + ) diff --git a/plugins/ui/src/deephaven/ui/components/item.py b/plugins/ui/src/deephaven/ui/components/item.py index d7482baed..282f4c18d 100644 --- a/plugins/ui/src/deephaven/ui/components/item.py +++ b/plugins/ui/src/deephaven/ui/components/item.py @@ -1,19 +1,32 @@ -from typing import Any, Union +from __future__ import annotations +from typing import Any, Union, List from ..elements import BaseElement from ..types import Stringable +from .._internal.utils import create_props from .basic import component_element ItemElement = BaseElement Item = Union[Stringable, ItemElement] +ItemList = List[Item] -def item(*children: Stringable, **props: Any) -> ItemElement: +def item( + *children: Stringable, + title: str | None = None, + text_value: str | None = None, + aria_label: str | None = None, + **props: Any, +) -> ItemElement: """ An item that can be added to a menu, such as a picker Args: children: The options to render within the item. + title: Rendered contents of the item if `children` contains child items. + text_value: A string representation of the item's contents, used for features like typeahead. + aria_label: An accessibility label for this item. **props: Any other Item prop. """ + children, props = create_props(locals()) return component_element("Item", *children, **props) diff --git a/plugins/ui/src/deephaven/ui/components/list_action_group.py b/plugins/ui/src/deephaven/ui/components/list_action_group.py index 2c35ec9a1..45831594c 100644 --- a/plugins/ui/src/deephaven/ui/components/list_action_group.py +++ b/plugins/ui/src/deephaven/ui/components/list_action_group.py @@ -1,6 +1,5 @@ from __future__ import annotations -from numbers import Number from typing import Callable, Iterable from .basic import component_element @@ -52,12 +51,12 @@ def list_action_group( selected_keys: SelectedKeys | Iterable[str] | None = None, default_selected_keys: SelectedKeys | Iterable[str] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_column: str | None = None, @@ -85,7 +84,7 @@ def list_action_group( right: DimensionValue | None = None, start: DimensionValue | None = None, end: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/list_action_menu.py b/plugins/ui/src/deephaven/ui/components/list_action_menu.py index 95137b306..d61d18bf1 100644 --- a/plugins/ui/src/deephaven/ui/components/list_action_menu.py +++ b/plugins/ui/src/deephaven/ui/components/list_action_menu.py @@ -1,7 +1,6 @@ from __future__ import annotations -from numbers import Number -from typing import Callable, Iterable, Union +from typing import Callable, Iterable from .basic import component_element @@ -40,12 +39,12 @@ def list_action_menu( is_open: bool | None = None, default_open: bool | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -73,7 +72,7 @@ def list_action_menu( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/number_field.py b/plugins/ui/src/deephaven/ui/components/number_field.py index 6a13ad07d..82d4f0c39 100644 --- a/plugins/ui/src/deephaven/ui/components/number_field.py +++ b/plugins/ui/src/deephaven/ui/components/number_field.py @@ -13,7 +13,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, LabelPosition, Align, @@ -56,12 +55,12 @@ def number_field( on_key_up: KeyboardEventCallable | None = None, on_change: Callable[[float], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -89,7 +88,7 @@ def number_field( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/range_slider.py b/plugins/ui/src/deephaven/ui/components/range_slider.py index 9587abe95..2fdf4e8cd 100644 --- a/plugins/ui/src/deephaven/ui/components/range_slider.py +++ b/plugins/ui/src/deephaven/ui/components/range_slider.py @@ -11,7 +11,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, LabelPosition, ) @@ -29,9 +28,9 @@ def range_slider( contextual_help: Any | None = None, orientation: Orientation = "horizontal", is_disabled: bool | None = None, - min_value: Number = 0, - max_value: Number = 100, - step: Number = 1, + min_value: float = 0, + max_value: float = 100, + step: float = 1, value: SliderChange | None = None, default_value: SliderChange | None = None, label: Any | None = None, @@ -39,12 +38,12 @@ def range_slider( on_change_end: SliderChangeCallable | None = None, on_change: SliderChangeCallable | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -72,7 +71,7 @@ def range_slider( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/slider.py b/plugins/ui/src/deephaven/ui/components/slider.py index e15f8675f..cf8b60ad7 100644 --- a/plugins/ui/src/deephaven/ui/components/slider.py +++ b/plugins/ui/src/deephaven/ui/components/slider.py @@ -9,7 +9,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, LabelPosition, ) @@ -19,7 +18,7 @@ def slider( is_filled: bool | None = None, - fill_offset: Number | None = None, + fill_offset: float | None = None, track_gradient: list[str] | None = None, # format_options, # omitted because need to connect it to Deephaven formatting options as well label_position: LabelPosition = "top", @@ -28,22 +27,22 @@ def slider( contextual_help: Any | None = None, orientation: Orientation = "horizontal", is_disabled: bool | None = None, - min_value: Number = 0, - max_value: Number = 100, - step: Number = 1, - value: Number | None = None, - default_value: Number | None = None, + min_value: float = 0, + max_value: float = 100, + step: float = 1, + value: float | None = None, + default_value: float | None = None, label: Any | None = None, name: str | None = None, - on_change_end: Callable[[Number], None] | None = None, - on_change: Callable[[Number], None] | None = None, + on_change_end: Callable[[float], None] | None = None, + on_change: Callable[[float], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -71,7 +70,7 @@ def slider( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, aria_label: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/table.py b/plugins/ui/src/deephaven/ui/components/table.py index b0c92e655..b03ce2317 100644 --- a/plugins/ui/src/deephaven/ui/components/table.py +++ b/plugins/ui/src/deephaven/ui/components/table.py @@ -9,6 +9,7 @@ ColumnPressCallback, QuickFilterExpression, RowPressCallback, + ResolvableContextMenuItem, ) @@ -29,6 +30,12 @@ def table( frozen_columns: list[ColumnName] | None = None, hidden_columns: list[ColumnName] | None = None, column_groups: list[ColumnGroup] | None = None, + context_menu: ( + ResolvableContextMenuItem | list[ResolvableContextMenuItem] | None + ) = None, + context_header_menu: ( + ResolvableContextMenuItem | list[ResolvableContextMenuItem] | None + ) = None, ) -> UITable: """ Customization to how a table is displayed, how it behaves, and listen to UI events. @@ -62,6 +69,12 @@ def table( column_groups: Columns to group together. The groups will be shown in the table header. Group names must be unique within the column and group names. Groups may be nested by providing the group name as a child of another group. + context_menu: The context menu items to show when a cell is right clicked. + May contain action items or submenu items. + May also be a function that receives the cell data and returns the context menu items or None. + context_header_menu: The context menu items to show when a column header is right clicked. + May contain action items or submenu items. + May also be a function that receives the column header data and returns the context menu items or None. """ props = locals() del props["table"] diff --git a/plugins/ui/src/deephaven/ui/components/text_field.py b/plugins/ui/src/deephaven/ui/components/text_field.py index e8b84a99f..524626cd2 100644 --- a/plugins/ui/src/deephaven/ui/components/text_field.py +++ b/plugins/ui/src/deephaven/ui/components/text_field.py @@ -13,7 +13,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, LabelPosition, Align, @@ -58,12 +57,12 @@ def text_field( on_key_up: KeyboardEventCallable | None = None, on_change: Callable[[str], None] | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -91,7 +90,7 @@ def text_field( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, exclude_from_tab_order: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/toggle_button.py b/plugins/ui/src/deephaven/ui/components/toggle_button.py index d86ce2ba5..a9a16c018 100644 --- a/plugins/ui/src/deephaven/ui/components/toggle_button.py +++ b/plugins/ui/src/deephaven/ui/components/toggle_button.py @@ -17,7 +17,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) from .basic import component_element @@ -46,12 +45,12 @@ def toggle_button( on_key_down: KeyboardEventCallable | None = None, on_key_up: KeyboardEventCallable | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_column: str | None = None, grid_row: str | None = None, @@ -79,7 +78,7 @@ def toggle_button( right: DimensionValue | None = None, start: DimensionValue | None = None, end: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, exclude_from_tab_order: bool | None = None, diff --git a/plugins/ui/src/deephaven/ui/components/types/accessibility.py b/plugins/ui/src/deephaven/ui/components/types/accessibility.py index 92c671840..ea229ff8b 100644 --- a/plugins/ui/src/deephaven/ui/components/types/accessibility.py +++ b/plugins/ui/src/deephaven/ui/components/types/accessibility.py @@ -9,3 +9,5 @@ AriaHasPopup = Union[BoolLiteral, Literal["menu", "listbox", "tree", "grid", "dialog"]] AriaPressed = Union[BoolLiteral, Literal["mixed"]] AriaAutoComplete = Union[BoolLiteral, Literal["inline", "list", "both", "none"]] + +HeadingLevel = Literal[1, 2, 3, 4, 5, 6] diff --git a/plugins/ui/src/deephaven/ui/components/types/layout.py b/plugins/ui/src/deephaven/ui/components/types/layout.py index 7727529a8..0795b3eaf 100644 --- a/plugins/ui/src/deephaven/ui/components/types/layout.py +++ b/plugins/ui/src/deephaven/ui/components/types/layout.py @@ -129,14 +129,12 @@ Alignment = Literal["start", "end"] -Number = Union[int, float] - -LayoutFlex = Union[str, Number, bool] +LayoutFlex = Union[str, float, bool] """ The flex CSS shorthand property sets how a flex item will grow or shrink to fit the space available in its flex container. """ -DimensionValue = Union[str, Number] +DimensionValue = Union[str, float] """ A dimension value can be a string providing a unit, such as "10px", or a number, which is assumed to be in pixels. """ diff --git a/plugins/ui/src/deephaven/ui/components/view.py b/plugins/ui/src/deephaven/ui/components/view.py index 25982a1e0..4d2abb8f2 100644 --- a/plugins/ui/src/deephaven/ui/components/view.py +++ b/plugins/ui/src/deephaven/ui/components/view.py @@ -12,7 +12,6 @@ DimensionValue, JustifySelf, LayoutFlex, - Number, Position, ) from .basic import component_element @@ -23,12 +22,12 @@ def view( *children: Any, element_type: ElementTypes | None = None, flex: LayoutFlex | None = None, - flex_grow: Number | None = None, - flex_shrink: Number | None = None, + flex_grow: float | None = None, + flex_shrink: float | None = None, flex_basis: DimensionValue | None = None, align_self: AlignSelf | None = None, justify_self: JustifySelf | None = None, - order: Number | None = None, + order: int | None = None, grid_area: str | None = None, grid_row: str | None = None, grid_row_start: str | None = None, @@ -84,7 +83,7 @@ def view( end: DimensionValue | None = None, left: DimensionValue | None = None, right: DimensionValue | None = None, - z_index: Number | None = None, + z_index: int | None = None, is_hidden: bool | None = None, id: str | None = None, UNSAFE_class_name: str | None = None, diff --git a/plugins/ui/src/deephaven/ui/elements/UITable.py b/plugins/ui/src/deephaven/ui/elements/UITable.py index 4e8fa1324..77680f052 100644 --- a/plugins/ui/src/deephaven/ui/elements/UITable.py +++ b/plugins/ui/src/deephaven/ui/elements/UITable.py @@ -18,12 +18,8 @@ AggregationOperation, QuickFilterExpression, Color, - ContextMenuAction, - CellIndex, CellPressCallback, ColumnPressCallback, - RowData, - ContextMenuMode, DataBarAxis, DataBarValuePlacement, DataBarDirection, @@ -307,42 +303,6 @@ def color_row( """ raise NotImplementedError() - def context_menu( - self, - items: ( - ContextMenuAction - | list[ContextMenuAction] - | Callable[ - [CellIndex, RowData], ContextMenuAction | list[ContextMenuAction] - ] - ), - mode: ContextMenuMode = "CELL", - ) -> "UITable": - """ - Add custom items to the context menu. - You can provide a list of actions that always appear, - or a callback that can process the selection and send back menu items asynchronously. - You can also specify whether you want the menu items provided for a cell context menu, - a header context menu, or some combination of those. - You can also chain multiple sets of menu items by calling `.context_menu` multiple times. - - Args: - items: The items to add to the context menu. - May be a single `ContextMenuAction`, a list of `ContextMenuAction` objects, - or a callback function that takes the cell index and row data and returns either a single - `ContextMenuAction` or a list of `ContextMenuAction` objects. - mode: Which specific context menu(s) to add the menu item(s) to. - Can be one or more modes. - Using `None` will add menu items in all cases. - `CELL`: Triggered from a cell. - `ROW_HEADER`: Triggered from a row header. - `COLUMN_HEADER`: Triggered from a column header. - - Returns: - A new UITable - """ - raise NotImplementedError() - def data_bar( self, col: str, diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index 63af59722..a5c8228ac 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -86,6 +86,105 @@ class ColumnGroup(TypedDict): """ +class ContextMenuActionParams(TypedDict): + """ + Parameters given to a context menu action + """ + + value: Any + """ + Value of the cell. + """ + + text_value: str + """ + Rendered text for the cell. + """ + + column_name: str + """ + Name of the column. + """ + + is_column_header: bool + """ + Whether the context menu was opened on a column header. + """ + + is_row_header: bool + """ + Whether the context menu was opened on a row header. + """ + + +ContextMenuAction = Callable[[ContextMenuActionParams], None] +""" +The action to execute when the context menu item is clicked. +""" + + +class ContextMenuItemBase(TypedDict): + """ + Base props that context menu items and submenu items share. + """ + + title: str + """ + Title to display for the action. + """ + + icon: NotRequired[str] + """ + The name of the icon to display next to the action. + The name must be a valid name for ui.icon. + """ + + description: NotRequired[str] + """ + Description for the action. Will be used as a tooltip for the action. + """ + + +class ContextMenuActionItem(ContextMenuItemBase): + """ + An item that appears in a context menu and performs an action when clicked. + """ + + action: ContextMenuAction + """ + Action to run when the menu item is clicked. + """ + + +class ContextMenuSubmenuItem(ContextMenuItemBase): + """ + An item that contains a submenu for a context menu. + """ + + actions: List["ResolvableContextMenuItem"] + """ + A list of actions that will form the submenu for the item. + """ + + +ContextMenuItem = Union[ContextMenuActionItem, ContextMenuSubmenuItem] +""" +An item that can appear in a context menu. +May contain an action item or a submenu item. +""" + +ResolvableContextMenuItem = Union[ + ContextMenuItem, + Callable[ + [ContextMenuActionParams], Union[ContextMenuItem, List[ContextMenuItem], None] + ], +] +""" +A context menu item or a function that returns a list of context menu items or None. +This can be used to dynamically generate context menu items based on the cell the menu is opened on. +""" + + class SliderChange(TypedDict): """ Data for a range slider change event. @@ -150,7 +249,6 @@ class SliderChange(TypedDict): DeephavenColor = Literal["salmon", "lemonchiffon"] HexColor = str Color = Union[DeephavenColor, HexColor] -ContextMenuAction = Dict[str, Any] ContextMenuModeOption = Literal["CELL", "ROW_HEADER", "COLUMN_HEADER"] ContextMenuMode = Union[ContextMenuModeOption, List[ContextMenuModeOption], None] DataBarAxis = Literal["PROPORTIONAL", "MIDDLE", "DIRECTIONAL"] diff --git a/plugins/ui/src/js/package.json b/plugins/ui/src/js/package.json index 77e68facb..71affb24f 100644 --- a/plugins/ui/src/js/package.json +++ b/plugins/ui/src/js/package.json @@ -41,21 +41,21 @@ "react-dom": "^17.0.2" }, "dependencies": { - "@deephaven/chart": "^0.81.1", - "@deephaven/components": "^0.81.1", - "@deephaven/dashboard": "^0.81.1", - "@deephaven/dashboard-core-plugins": "^0.81.1", - "@deephaven/grid": "^0.81.0", - "@deephaven/icons": "^0.81.0", - "@deephaven/iris-grid": "^0.81.1", - "@deephaven/jsapi-bootstrap": "^0.81.1", - "@deephaven/jsapi-components": "^0.81.1", - "@deephaven/jsapi-types": "^1.0.0-dev0.34.3", - "@deephaven/log": "^0.81.0", - "@deephaven/plugin": "^0.81.1", - "@deephaven/react-hooks": "^0.81.0", - "@deephaven/redux": "^0.81.1", - "@deephaven/utils": "^0.81.0", + "@deephaven/chart": "^0.84.0", + "@deephaven/components": "^0.84.0", + "@deephaven/dashboard": "^0.84.0", + "@deephaven/dashboard-core-plugins": "^0.84.0", + "@deephaven/grid": "^0.84.0", + "@deephaven/icons": "^0.84.0", + "@deephaven/iris-grid": "^0.84.0", + "@deephaven/jsapi-bootstrap": "^0.84.0", + "@deephaven/jsapi-components": "^0.84.0", + "@deephaven/jsapi-types": "^1.0.0-dev0.35.0", + "@deephaven/log": "^0.84.0", + "@deephaven/plugin": "^0.84.0", + "@deephaven/react-hooks": "^0.84.0", + "@deephaven/redux": "^0.84.0", + "@deephaven/utils": "^0.84.0", "@fortawesome/react-fontawesome": "^0.2.0", "@react-types/shared": "^3.22.0", "classnames": "^2.5.1", diff --git a/plugins/ui/src/js/src/elements/IconElementView.tsx b/plugins/ui/src/js/src/elements/IconElementView.tsx index 0b56d01cd..ca214eeda 100644 --- a/plugins/ui/src/js/src/elements/IconElementView.tsx +++ b/plugins/ui/src/js/src/elements/IconElementView.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Icon } from '@deephaven/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { getIcon, IconElementNode } from './utils/IconElementUtils'; @@ -18,9 +17,9 @@ export function IconElementView({ } return ( - - {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + /* eslint-disable-next-line react/jsx-props-no-spreading */ + + ); } diff --git a/plugins/ui/src/js/src/elements/UITable/UITable.tsx b/plugins/ui/src/js/src/elements/UITable/UITable.tsx index 11cdd8f51..4383da4c4 100644 --- a/plugins/ui/src/js/src/elements/UITable/UITable.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITable.tsx @@ -1,8 +1,10 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { DehydratedQuickFilter, IrisGrid, + IrisGridType, + type IrisGridContextMenuData, IrisGridModel, IrisGridModelFactory, IrisGridProps, @@ -12,10 +14,11 @@ import { useApi } from '@deephaven/jsapi-bootstrap'; import type { dh } from '@deephaven/jsapi-types'; import Log from '@deephaven/log'; import { getSettings, RootState } from '@deephaven/redux'; -import { EMPTY_ARRAY } from '@deephaven/utils'; -import { UITableProps } from './UITableUtils'; +import { GridMouseHandler } from '@deephaven/grid'; +import { UITableProps, wrapContextActions } from './UITableUtils'; import UITableMouseHandler from './UITableMouseHandler'; import JsTableProxy from './JsTableProxy'; +import UITableContextMenuHandler from './UITableContextMenuHandler'; const log = Log.module('@deephaven/js-plugin-ui/UITable'); @@ -37,8 +40,11 @@ export function UITable({ frozenColumns, hiddenColumns, columnGroups, + contextMenu, + contextHeaderMenu, }: UITableProps): JSX.Element | null { const dh = useApi(); + const [irisGrid, setIrisGrid] = useState(null); const [model, setModel] = useState(); const [columns, setColumns] = useState(); const utils = useMemo(() => new IrisGridUtils(dh), [dh]); @@ -108,8 +114,8 @@ export function UITable({ const mouseHandlers = useMemo( () => - model - ? [ + model && irisGrid + ? ([ new UITableMouseHandler( model, onCellPress, @@ -119,19 +125,36 @@ export function UITable({ onRowPress, onRowDoublePress ), - ] - : EMPTY_ARRAY, + new UITableContextMenuHandler( + dh, + irisGrid, + model, + contextMenu, + contextHeaderMenu + ), + ] as readonly GridMouseHandler[]) + : undefined, [ model, + dh, + irisGrid, onCellPress, onCellDoublePress, onColumnPress, onColumnDoublePress, onRowPress, onRowDoublePress, + contextMenu, + contextHeaderMenu, ] ); + const onContextMenu = useCallback( + (data: IrisGridContextMenuData) => + wrapContextActions(contextMenu ?? [], data), + [contextMenu] + ); + const irisGridProps = useMemo( () => ({ @@ -142,6 +165,7 @@ export function UITable({ quickFilters: hydratedQuickFilters, isFilterBarShown: showQuickFilters, settings, + onContextMenu, }) satisfies Partial, [ mouseHandlers, @@ -151,6 +175,7 @@ export function UITable({ hydratedSorts, hydratedQuickFilters, settings, + onContextMenu, ] ); @@ -159,8 +184,12 @@ export function UITable({ return model ? (
- {/* eslint-disable-next-line react/jsx-props-no-spreading */} - + setIrisGrid(ref)} + model={model} + // eslint-disable-next-line react/jsx-props-no-spreading + {...irisGridProps} + />
) : null; } diff --git a/plugins/ui/src/js/src/elements/UITable/UITableContextMenuHandler.ts b/plugins/ui/src/js/src/elements/UITable/UITableContextMenuHandler.ts new file mode 100644 index 000000000..e0698e5d4 --- /dev/null +++ b/plugins/ui/src/js/src/elements/UITable/UITableContextMenuHandler.ts @@ -0,0 +1,68 @@ +import { GridPoint, ModelIndex } from '@deephaven/grid'; +import type { ResolvableContextAction } from '@deephaven/components'; +import { + IrisGridModel, + IrisGridType, + IrisGridContextMenuHandler, +} from '@deephaven/iris-grid'; +import type { dh as DhType } from '@deephaven/jsapi-types'; +import { UITableProps, wrapContextActions } from './UITableUtils'; + +/** + * Context menu handler for UITable. + */ +class UITableContextMenuHandler extends IrisGridContextMenuHandler { + private model: IrisGridModel; + + private contextMenuItems: UITableProps['contextMenu']; + + private contextColumnHeaderItems: UITableProps['contextHeaderMenu']; + + constructor( + dh: typeof DhType, + irisGrid: IrisGridType, + model: IrisGridModel, + contextMenuItems: UITableProps['contextMenu'], + contextColumnHeaderItems: UITableProps['contextHeaderMenu'] + ) { + super(irisGrid, dh); + this.order -= 1; // Make it just above the default handler priority + this.irisGrid = irisGrid; + this.model = model; + this.contextMenuItems = contextMenuItems; + this.contextColumnHeaderItems = contextColumnHeaderItems; + } + + getHeaderActions( + modelIndex: ModelIndex, + gridPoint: GridPoint + ): ResolvableContextAction[] { + const { irisGrid, contextColumnHeaderItems, model } = this; + + const { column: columnIndex } = gridPoint; + const modelColumn = irisGrid.getModelColumn(columnIndex); + + if (!contextColumnHeaderItems || modelColumn == null) { + return super.getHeaderActions(modelIndex, gridPoint); + } + + const { columns } = model; + + const sourceCell = model.sourceForCell(modelColumn, 0); + const { column: sourceColumn } = sourceCell; + const column = columns[sourceColumn]; + + return [ + ...super.getHeaderActions(modelIndex, gridPoint), + ...wrapContextActions(contextColumnHeaderItems, { + value: null, + valueText: null, + rowIndex: null, + columnIndex: sourceColumn, + column, + }), + ]; + } +} + +export default UITableContextMenuHandler; diff --git a/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx index fccd6e0ff..f72b7da53 100644 --- a/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx @@ -1,7 +1,24 @@ import type { dh } from '@deephaven/jsapi-types'; -import { ColumnName, DehydratedSort, RowIndex } from '@deephaven/iris-grid'; +import { + ColumnName, + DehydratedSort, + IrisGridContextMenuData, + RowIndex, +} from '@deephaven/iris-grid'; +import type { + ContextAction, + ResolvableContextAction, +} from '@deephaven/components'; +import { ensureArray } from '@deephaven/utils'; import { ELEMENT_KEY, ElementNode, isElementNode } from '../utils/ElementUtils'; -import { ELEMENT_NAME, ElementName } from '../model/ElementConstants'; + +import { getIcon } from '../utils/IconElementUtils'; +import { + ELEMENT_NAME, + ELEMENT_PREFIX, + ElementName, + ElementPrefix, +} from '../model/ElementConstants'; export type CellData = { type: string; @@ -18,6 +35,26 @@ export type ColumnIndex = number; export type RowDataMap = Record; +export interface UIContextItemParams { + value: unknown; + text_value: string | null; + column_name: string; + is_column_header: boolean; + is_row_header: boolean; +} + +export type UIContextItem = Omit & { + action?: (params: UIContextItemParams) => void; + + actions?: ResolvableUIContextItem[]; +}; + +type ResolvableUIContextItem = + | UIContextItem + | (( + params: UIContextItemParams + ) => Promise); + export type UITableProps = { table: dh.WidgetExportedObject; onCellPress?: (cellIndex: [ColumnIndex, RowIndex], data: CellData) => void; @@ -39,6 +76,8 @@ export type UITableProps = { frozenColumns?: string[]; hiddenColumns?: string[]; columnGroups?: dh.ColumnGroup[]; + contextMenu?: ResolvableUIContextItem | ResolvableUIContextItem[]; + contextHeaderMenu?: ResolvableUIContextItem | ResolvableUIContextItem[]; }; export type UITableNode = Required< @@ -51,3 +90,64 @@ export function isUITable(obj: unknown): obj is UITableNode { (obj as UITableNode)[ELEMENT_KEY] === ELEMENT_NAME.uiTable ); } + +function wrapUIContextItem( + item: UIContextItem, + data: Omit +): ContextAction { + return { + group: 999999, // Default to the end of the menu + ...item, + icon: item.icon + ? getIcon(`${ELEMENT_PREFIX.icon}${item.icon}` as ElementPrefix['icon']) + : undefined, + action: item.action + ? () => { + item.action?.({ + value: data.value, + text_value: data.valueText, + column_name: data.column.name, + is_column_header: data.rowIndex == null, + is_row_header: data.columnIndex == null, + }); + } + : undefined, + actions: item.actions ? wrapContextActions(item.actions, data) : undefined, + } satisfies ContextAction; +} + +function wrapUIContextItems( + items: UIContextItem | UIContextItem[], + data: Omit +): ContextAction[] { + return ensureArray(items).map(item => wrapUIContextItem(item, data)); +} + +/** + * Wraps context item actions from the server so they are called with the cell info. + * @param items The context items from the server + * @param data The context menu data to use for the context items + * @returns Context items with the UI actions wrapped so they receive the cell info + */ +export function wrapContextActions( + items: ResolvableUIContextItem | ResolvableUIContextItem[], + data: Omit +): ResolvableContextAction[] { + return ensureArray(items).map(item => { + if (typeof item === 'function') { + return async () => + wrapUIContextItems( + (await item({ + value: data.value, + text_value: data.valueText, + column_name: data.column.name, + is_column_header: data.rowIndex == null, + is_row_header: data.columnIndex == null, + })) ?? [], + data + ); + } + + return wrapUIContextItem(item, data); + }); +} diff --git a/plugins/ui/src/js/src/layout/ReactPanel.test.tsx b/plugins/ui/src/js/src/layout/ReactPanel.test.tsx index f776149cf..a30e72c80 100644 --- a/plugins/ui/src/js/src/layout/ReactPanel.test.tsx +++ b/plugins/ui/src/js/src/layout/ReactPanel.test.tsx @@ -1,6 +1,10 @@ import React from 'react'; import { render, within } from '@testing-library/react'; -import { LayoutUtils, useListener } from '@deephaven/dashboard'; +import { + LayoutUtils, + WidgetDescriptor, + useListener, +} from '@deephaven/dashboard'; import { TestUtils } from '@deephaven/utils'; import ReactPanel from './ReactPanel'; import { @@ -8,10 +12,17 @@ import { ReactPanelManagerContext, } from './ReactPanelManager'; import { ReactPanelProps } from './LayoutUtils'; -import PortalPanelManager from './PortalPanelManager'; -import PortalPanelManagerContext from './PortalPanelManagerContext'; +import PortalPanelManagerContext, { + PortalPanelMap, +} from './PortalPanelManagerContext'; +import WidgetStatusContext, { WidgetStatus } from './WidgetStatusContext'; const mockPanelId = 'test-panel-id'; +const defaultDescriptor = { name: 'test-name', type: 'test-type' }; +const defaultStatus: WidgetStatus = { + status: 'ready', + descriptor: defaultDescriptor, +}; beforeEach(() => { jest.clearAllMocks(); @@ -19,7 +30,7 @@ beforeEach(() => { function makeReactPanelManager({ children, - metadata = { name: 'test-name', type: 'test-type' }, + metadata = defaultDescriptor, onClose = jest.fn(), onOpen = jest.fn(), getPanelId = jest.fn(() => mockPanelId), @@ -41,23 +52,32 @@ function makeReactPanelManager({ function makeTestComponent({ children, - metadata = { name: 'test-name', type: 'test-type' }, + metadata = defaultDescriptor, onClose = jest.fn(), onOpen = jest.fn(), getPanelId = jest.fn(() => mockPanelId), + portals = new Map(), + status = defaultStatus, title = 'test title', -}: Partial & Partial = {}) { +}: Partial & + Partial & { + metadata?: WidgetDescriptor; + portals?: PortalPanelMap; + status?: WidgetStatus; + } = {}) { return ( - - {makeReactPanelManager({ - children, - metadata, - onClose, - onOpen, - getPanelId, - title, - })} - + + + {makeReactPanelManager({ + children, + metadata, + onClose, + onOpen, + getPanelId, + title, + })} + + ); } @@ -181,14 +201,13 @@ it('does not call openComponent or setActiveContentItem if panel already exists const metadata = { type: 'bar' }; const children = 'hello'; const { rerender } = render( - - {makeReactPanelManager({ - children, - onOpen, - onClose, - metadata, - })} - + makeTestComponent({ + children, + onOpen, + onClose, + metadata, + portals, + }) ); expect(LayoutUtils.openComponent).not.toHaveBeenCalled(); expect(LayoutUtils.closeComponent).not.toHaveBeenCalled(); @@ -200,14 +219,13 @@ it('does not call openComponent or setActiveContentItem if panel already exists // Now check that it focuses it if it's called after the metadata changes rerender( - - {makeReactPanelManager({ - children: 'world', - onOpen, - onClose, - metadata: { type: 'baz' }, - })} - + makeTestComponent({ + children: 'world', + onOpen, + onClose, + metadata: { type: 'baz' }, + portals, + }) ); expect(LayoutUtils.openComponent).not.toHaveBeenCalled(); @@ -274,22 +292,36 @@ it('catches an error thrown by children, renders error view', () => { const portals = new Map([[mockPanelId, portal]]); const { rerender } = render( - - {makeReactPanelManager({ - children: , - })} - + makeTestComponent({ + children: , + portals, + }) ); const { getByText } = within(portal); - expect(getByText('Error: test error')).toBeDefined(); + expect(getByText('test error')).toBeDefined(); rerender( - - {makeReactPanelManager({ - children:
Hello
, - })} -
+ makeTestComponent({ + children:
Hello
, + portals, + }) ); expect(getByText('Hello')).toBeDefined(); }); + +it('displays an error if the widget is in an error state', () => { + const error = new Error('test error'); + const portal = document.createElement('div'); + const portals = new Map([[mockPanelId, portal]]); + const status: WidgetStatus = { + status: 'error', + descriptor: defaultDescriptor, + error, + }; + + render(makeTestComponent({ portals, status })); + + const { getByText } = within(portal); + expect(getByText('test error')).toBeDefined(); +}); diff --git a/plugins/ui/src/js/src/layout/ReactPanel.tsx b/plugins/ui/src/js/src/layout/ReactPanel.tsx index af908c754..cf3fdebd0 100644 --- a/plugins/ui/src/js/src/layout/ReactPanel.tsx +++ b/plugins/ui/src/js/src/layout/ReactPanel.tsx @@ -7,13 +7,7 @@ import { useLayoutManager, useListener, } from '@deephaven/dashboard'; -import { - View, - ViewProps, - Flex, - FlexProps, - ErrorBoundary, -} from '@deephaven/components'; +import { View, ViewProps, Flex, FlexProps } from '@deephaven/components'; import Log from '@deephaven/log'; import PortalPanel from './PortalPanel'; import { ReactPanelControl, useReactPanel } from './ReactPanelManager'; @@ -21,7 +15,9 @@ import { ReactPanelProps } from './LayoutUtils'; import { useParentItem } from './ParentItemContext'; import { ReactPanelContext } from './ReactPanelContext'; import { usePortalPanelManager } from './PortalPanelManagerContext'; -import ReactPanelContentOverlay from './ReactPanelContentOverlay'; +import ReactPanelErrorBoundary from './ReactPanelErrorBoundary'; +import useWidgetStatus from './useWidgetStatus'; +import WidgetErrorView from '../widget/WidgetErrorView'; const log = Log.module('@deephaven/js-plugin-ui/ReactPanel'); @@ -172,6 +168,7 @@ function ReactPanel({ }, [parent, metadata, onOpen, panelId, title] ); + const widgetStatus = useWidgetStatus(); return portal ? ReactDOM.createPortal( @@ -205,11 +202,19 @@ function ReactPanel({ rowGap={rowGap} columnGap={columnGap} > - {/* Have an ErrorBoundary around the children to display an error in the panel if there's any errors thrown when rendering the children */} - {children} + + {/** + * Don't render the children if there's an error with the widget. If there's an error with the widget, we can assume the children won't render properly, + * but we still want the panels to appear so things don't disappear/jump around. + */} + {widgetStatus.status === 'error' ? ( + + ) : ( + children + )} + - , portal, contentKey diff --git a/plugins/ui/src/js/src/layout/ReactPanelContentOverlay.tsx b/plugins/ui/src/js/src/layout/ReactPanelContentOverlay.tsx deleted file mode 100644 index 5e1c693c4..000000000 --- a/plugins/ui/src/js/src/layout/ReactPanelContentOverlay.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { usePanelContentOverlay } from './usePanelContentOverlay'; - -/** A panel that uses the ReactPanelContentOverlayContext and if that content is set, renders it in a view with a partially transparent background */ -export function ReactPanelContentOverlay(): JSX.Element | null { - const overlayContent = usePanelContentOverlay(); - return overlayContent != null ? ( -
{overlayContent}
- ) : null; -} - -export default ReactPanelContentOverlay; diff --git a/plugins/ui/src/js/src/layout/ReactPanelContentOverlayContext.tsx b/plugins/ui/src/js/src/layout/ReactPanelContentOverlayContext.tsx deleted file mode 100644 index ceb248ec2..000000000 --- a/plugins/ui/src/js/src/layout/ReactPanelContentOverlayContext.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createContext } from 'react'; - -/** Context that defined a ReactNode to overlay on top of the content in a ReactPanel */ -export const ReactPanelContentOverlayContext = - createContext(null); - -export default ReactPanelContentOverlayContext; diff --git a/plugins/ui/src/js/src/layout/ReactPanelErrorBoundary.tsx b/plugins/ui/src/js/src/layout/ReactPanelErrorBoundary.tsx new file mode 100644 index 000000000..ed7444b67 --- /dev/null +++ b/plugins/ui/src/js/src/layout/ReactPanelErrorBoundary.tsx @@ -0,0 +1,59 @@ +import Log from '@deephaven/log'; +import React, { Component, ReactNode } from 'react'; +import WidgetErrorView from '../widget/WidgetErrorView'; + +const log = Log.module('ReactPanelErrorBoundary'); + +export interface ReactPanelErrorBoundaryProps { + /** Children to catch errors from. Error will reset when the children have been updated. */ + children: ReactNode; +} + +export interface ReactPanelErrorBoundaryState { + /** Currently displayed error. Reset when children are updated. */ + error?: Error; +} + +/** + * Error boundary for catching render errors in React. Displays an error message until the children have updated. + */ +export class ReactPanelErrorBoundary extends Component< + ReactPanelErrorBoundaryProps, + ReactPanelErrorBoundaryState +> { + static getDerivedStateFromError(error: Error): ReactPanelErrorBoundaryState { + return { error }; + } + + constructor(props: ReactPanelErrorBoundaryProps) { + super(props); + this.state = { error: undefined }; + } + + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly + ): void { + const { children } = this.props; + if (prevProps.children !== children && prevState.error != null) { + log.debug( + 'ReactPanelErrorBoundary clearing previous error', + prevState.error, + children + ); + this.setState({ error: undefined }); + } + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + log.error('Error caught by ErrorBoundary', error, errorInfo); + } + + render(): ReactNode { + const { children } = this.props; + const { error } = this.state; + return error != null ? : children; + } +} + +export default ReactPanelErrorBoundary; diff --git a/plugins/ui/src/js/src/layout/WidgetStatusContext.tsx b/plugins/ui/src/js/src/layout/WidgetStatusContext.tsx new file mode 100644 index 000000000..c2c7874c6 --- /dev/null +++ b/plugins/ui/src/js/src/layout/WidgetStatusContext.tsx @@ -0,0 +1,28 @@ +import { WidgetDescriptor } from '@deephaven/dashboard'; +import { createContext } from 'react'; + +export type WidgetStatusLoading = { + status: 'loading'; + descriptor: WidgetDescriptor; +}; + +export type WidgetStatusError = { + status: 'error'; + descriptor: WidgetDescriptor; + error: NonNullable; +}; + +export type WidgetStatusReady = { + status: 'ready'; + descriptor: WidgetDescriptor; +}; + +export type WidgetStatus = + | WidgetStatusLoading + | WidgetStatusError + | WidgetStatusReady; + +/** Status of the widget within this context */ +export const WidgetStatusContext = createContext(null); + +export default WidgetStatusContext; diff --git a/plugins/ui/src/js/src/layout/usePanelContentOverlay.ts b/plugins/ui/src/js/src/layout/usePanelContentOverlay.ts deleted file mode 100644 index 8b3aeb457..000000000 --- a/plugins/ui/src/js/src/layout/usePanelContentOverlay.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from 'react'; -import { ReactPanelContentOverlayContext } from './ReactPanelContentOverlayContext'; - -/** - * Gets the overlay content from the nearest panel context. - * @returns The overlay content or null if not in a panel - */ -export function usePanelContentOverlay(): React.ReactNode | null { - return useContext(ReactPanelContentOverlayContext); -} - -export default usePanelContentOverlay; diff --git a/plugins/ui/src/js/src/layout/useWidgetStatus.ts b/plugins/ui/src/js/src/layout/useWidgetStatus.ts new file mode 100644 index 000000000..4a00521b9 --- /dev/null +++ b/plugins/ui/src/js/src/layout/useWidgetStatus.ts @@ -0,0 +1,12 @@ +import { useContextOrThrow } from '@deephaven/react-hooks'; +import { WidgetStatus, WidgetStatusContext } from './WidgetStatusContext'; + +/** + * Gets the widget status from the closest WidgetStatusContext. + * @returns Widget status or throws an error if WidgetStatusContext is not set + */ +export function useWidgetStatus(): WidgetStatus { + return useContextOrThrow(WidgetStatusContext); +} + +export default useWidgetStatus; diff --git a/plugins/ui/src/js/src/styles.scss b/plugins/ui/src/js/src/styles.scss index 17ea7d971..4d48aa843 100644 --- a/plugins/ui/src/js/src/styles.scss +++ b/plugins/ui/src/js/src/styles.scss @@ -31,27 +31,6 @@ } } -.dh-react-panel-overlay { - background-color: bg-opacity(80); - backdrop-filter: blur(5px); - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - padding: $spacer; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 1000; - - .ui-widget-error-view { - width: 100%; - overflow: auto; - } -} - .ui-text-wrap-balance { text-wrap: balance; } diff --git a/plugins/ui/src/js/src/widget/WidgetErrorView.tsx b/plugins/ui/src/js/src/widget/WidgetErrorView.tsx index 115e7ef1c..6cf78847a 100644 --- a/plugins/ui/src/js/src/widget/WidgetErrorView.tsx +++ b/plugins/ui/src/js/src/widget/WidgetErrorView.tsx @@ -21,7 +21,7 @@ import { } from './WidgetUtils'; /** Component that display an error message. Will automatically show a button for more info and an action button if the error has an Action defined */ -function WidgetErrorView({ +export function WidgetErrorView({ error, }: { error: NonNullable; diff --git a/plugins/ui/src/js/src/widget/WidgetHandler.test.tsx b/plugins/ui/src/js/src/widget/WidgetHandler.test.tsx index ae6288872..208c2d744 100644 --- a/plugins/ui/src/js/src/widget/WidgetHandler.test.tsx +++ b/plugins/ui/src/js/src/widget/WidgetHandler.test.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { act, render } from '@testing-library/react'; import { useWidget } from '@deephaven/jsapi-bootstrap'; +import { dh } from '@deephaven/jsapi-types'; +import { TestUtils } from '@deephaven/utils'; import WidgetHandler, { WidgetHandlerProps } from './WidgetHandler'; import { DocumentHandlerProps } from './DocumentHandler'; import { @@ -11,10 +13,14 @@ import { } from './WidgetTestUtils'; const mockApi = { Widget: { EVENT_MESSAGE: 'message' } }; -let mockWidgetWrapper: ReturnType = { - widget: null, +const defaultWidgetWrapper: ReturnType = { + widget: TestUtils.createMockProxy({ + getDataAsString: jest.fn(() => ''), + exportedObjects: [], + }), error: null, }; +let mockWidgetWrapper: ReturnType = defaultWidgetWrapper; jest.mock('@deephaven/jsapi-bootstrap', () => ({ useApi: jest.fn(() => mockApi), useWidget: jest.fn(() => mockWidgetWrapper), @@ -43,7 +49,7 @@ function makeWidgetHandler({ } beforeEach(() => { - mockWidgetWrapper = { widget: null, error: null }; + mockWidgetWrapper = defaultWidgetWrapper; mockDocumentHandler.mockClear(); }); diff --git a/plugins/ui/src/js/src/widget/WidgetHandler.tsx b/plugins/ui/src/js/src/widget/WidgetHandler.tsx index 43f665322..e4184cfd1 100644 --- a/plugins/ui/src/js/src/widget/WidgetHandler.tsx +++ b/plugins/ui/src/js/src/widget/WidgetHandler.tsx @@ -36,8 +36,10 @@ import { } from './WidgetTypes'; import DocumentHandler from './DocumentHandler'; import { getComponentForElement, wrapCallable } from './WidgetUtils'; +import WidgetStatusContext, { + WidgetStatus, +} from '../layout/WidgetStatusContext'; import WidgetErrorView from './WidgetErrorView'; -import ReactPanelContentOverlayContext from '../layout/ReactPanelContentOverlayContext'; const log = Log.module('@deephaven/js-plugin-ui/WidgetHandler'); @@ -322,33 +324,31 @@ function WidgetHandler({ [jsonClient, initialData, sendSetState, updateExportedObjects, widget] ); - const errorView = useMemo(() => { + const renderedDocument = useMemo(() => { + if (document != null) { + return document; + } if (error != null) { + // If there's an error and the document hasn't rendered yet, explicitly show an error view return ; } return null; - }, [error]); + }, [document, error]); - const contentOverlay = useMemo(() => { - // We only show it as an overlay if there's already a document to show - // If there isn't, then we'll just render this as the document so it forces a panel to open - if (errorView != null && document != null) { - return errorView; + const widgetStatus: WidgetStatus = useMemo(() => { + if (error != null) { + return { status: 'error', descriptor: widgetDescriptor, error }; } - return null; - }, [document, errorView]); - - const renderedDocument = useMemo(() => { - if (document != null) { - return document; + if (renderedDocument != null) { + return { status: 'ready', descriptor: widgetDescriptor }; } - return errorView; - }, [document, errorView]); + return { status: 'loading', descriptor: widgetDescriptor }; + }, [error, renderedDocument, widgetDescriptor]); return useMemo( () => - renderedDocument != null ? ( - + renderedDocument ? ( + {renderedDocument} - + ) : null, [ - contentOverlay, widgetDescriptor, renderedDocument, initialData, onClose, onDataChange, + widgetStatus, ] ); } diff --git a/plugins/ui/src/ui.schema.json b/plugins/ui/src/ui.schema.json index 82cccb99b..e4fa98d86 100644 --- a/plugins/ui/src/ui.schema.json +++ b/plugins/ui/src/ui.schema.json @@ -60,6 +60,16 @@ "type": "array", "prefixItems": [{ "type": "object" }], "items": false + }, + "callCallableParams": { + "type": "array", + "prefixItems": [{ "type": "string" }, { "type": "array" }], + "items": false + }, + "closeCallableParams": { + "type": "array", + "prefixItems": [{ "type": "string" }], + "items": false } }, "type": "object", @@ -67,8 +77,15 @@ "jsonrpc": "2.0", "method": { "anyOf": [ - { "enum": ["documentUpdated", "documentError"] }, - { "pattern": "^cb_(0-9)+_(0-9)+$" } + { + "enum": [ + "documentUpdated", + "documentError", + "setState", + "callCallable", + "closeCallable" + ] + } ] }, "allOf": [ @@ -111,15 +128,24 @@ { "if": { "properties": { - "method": { "pattern": "^cb_(0-9)+_(0-9)+$" } + "method": { "pattern": "callCallable" } } }, "then": { "properties": { - "params": { - "type": "array", - "items": { "type": "any" } - } + "params": { "$ref": "#/defs/callCallableParams" } + } + } + }, + { + "if": { + "properties": { + "method": { "pattern": "closeCallable" } + } + }, + "then": { + "properties": { + "params": { "$ref": "#/defs/closeCallableParams" } } } } diff --git a/ruff.toml b/ruff.toml index 96bed7376..b2c9ff227 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,5 +1,8 @@ [lint] -select = ["ANN001"] +select = ["ANN001", "TID251"] [lint.per-file-ignores] -"**/{test,matplotlib,json,plotly}/*" = ["ANN001"] \ No newline at end of file +"**/{test,matplotlib,json,plotly}/*" = ["ANN001"] + +[lint.flake8-tidy-imports.banned-api] +"numbers".msg = "Import from numbers is likely an accident. `float` includes `int` and is likely the desired type." \ No newline at end of file diff --git a/tests/ui.spec.ts b/tests/ui.spec.ts index 232233169..b07180fc5 100644 --- a/tests/ui.spec.ts +++ b/tests/ui.spec.ts @@ -24,7 +24,6 @@ test('boom component shows an error in a panel', async ({ page }) => { await expect( page.locator(selector.REACT_PANEL_VISIBLE).getByText('BOOM!') ).toBeVisible(); - await expect(page.locator(selector.REACT_PANEL_OVERLAY)).not.toBeVisible(); }); test('boom counter component shows error overlay after clicking the button twice', async ({ @@ -43,12 +42,10 @@ test('boom counter component shows error overlay after clicking the button twice await expect(btn).toBeVisible(); btn.click(); - const overlayLocator = page.locator(selector.REACT_PANEL_OVERLAY); - await expect( - overlayLocator.getByText('ValueError', { exact: true }) + panelLocator.getByText('ValueError', { exact: true }) ).toBeVisible(); - await expect(overlayLocator.getByText('BOOM! Value too big.')).toBeVisible(); + await expect(panelLocator.getByText('BOOM! Value too big.')).toBeVisible(); }); test('UI all components render', async ({ page }) => {