mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-04 21:19:27 +08:00
Merge remote-tracking branch 'origin/develop' into feat/commercial
This commit is contained in:
commit
4ab9f59b2e
619
.github/workflows/manual-npm-publish-license-kit.yml
vendored
Normal file
619
.github/workflows/manual-npm-publish-license-kit.yml
vendored
Normal file
@ -0,0 +1,619 @@
|
||||
name: Manual npm publish license-kit
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
APP_NAME: license-kit
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
# 'on':
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# tags-ignore:
|
||||
# - '**'
|
||||
# paths-ignore:
|
||||
# - '**/*.md'
|
||||
# - LICENSE
|
||||
# - '**/*.gitignore'
|
||||
# - .editorconfig
|
||||
# - docs/**
|
||||
# pull_request: null
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
build: yarn build --target x86_64-apple-darwin
|
||||
- host: windows-latest
|
||||
build: yarn build --target x86_64-pc-windows-msvc
|
||||
target: x86_64-pc-windows-msvc
|
||||
- host: windows-latest
|
||||
build: yarn build --target i686-pc-windows-msvc
|
||||
target: i686-pc-windows-msvc
|
||||
- host: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
build: yarn build --target x86_64-unknown-linux-gnu
|
||||
- host: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: yarn build --target x86_64-unknown-linux-musl
|
||||
- host: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
build: yarn build --target aarch64-apple-darwin
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||
build: yarn build --target aarch64-unknown-linux-gnu
|
||||
- host: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
setup: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-linux-gnueabihf -y
|
||||
build: yarn build --target armv7-unknown-linux-gnueabihf
|
||||
- host: ubuntu-latest
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
build: yarn build --target armv7-unknown-linux-musleabihf
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-linux-android
|
||||
build: yarn build --target aarch64-linux-android
|
||||
- host: ubuntu-latest
|
||||
target: armv7-linux-androideabi
|
||||
build: yarn build --target armv7-linux-androideabi
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: |-
|
||||
set -e &&
|
||||
rustup target add aarch64-unknown-linux-musl &&
|
||||
yarn build --target aarch64-unknown-linux-musl
|
||||
- host: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
build: yarn build --target aarch64-pc-windows-msvc
|
||||
- host: ubuntu-latest
|
||||
target: riscv64gc-unknown-linux-gnu
|
||||
setup: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-riscv64-linux-gnu -y
|
||||
build: yarn build --target riscv64gc-unknown-linux-gnu
|
||||
name: stable - ${{ matrix.settings.target }} - node@20
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
- name: Install
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ matrix.settings.target }}
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
.cargo-cache
|
||||
target/
|
||||
key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}
|
||||
- uses: goto-bus-stop/setup-zig@v2
|
||||
if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' || matrix.settings.target == 'armv7-unknown-linux-musleabihf' }}
|
||||
with:
|
||||
version: 0.13.0
|
||||
- name: Setup toolchain
|
||||
run: ${{ matrix.settings.setup }}
|
||||
if: ${{ matrix.settings.setup }}
|
||||
shell: bash
|
||||
- name: Setup node x86
|
||||
if: matrix.settings.target == 'i686-pc-windows-msvc'
|
||||
run: yarn config set supportedArchitectures.cpu "ia32"
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Setup node x86
|
||||
uses: actions/setup-node@v4
|
||||
if: matrix.settings.target == 'i686-pc-windows-msvc'
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
architecture: x86
|
||||
- name: Install OpenSSL dev
|
||||
if: ${{ matrix.settings.host == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libssl-dev
|
||||
- name: Build in docker
|
||||
uses: addnab/docker-run-action@v3
|
||||
if: ${{ matrix.settings.docker }}
|
||||
with:
|
||||
image: ${{ matrix.settings.docker }}
|
||||
options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build'
|
||||
run: ${{ matrix.settings.build }}
|
||||
- name: Build
|
||||
run: ${{ matrix.settings.build }}
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
shell: bash
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-${{ matrix.settings.target }}
|
||||
path: ${{ env.APP_NAME }}.*.node
|
||||
if-no-files-found: error
|
||||
build-freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build FreeBSD
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Build
|
||||
id: build
|
||||
uses: cross-platform-actions/action@v0.27.0
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
RUSTUP_IO_THREADS: 1
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '14.2'
|
||||
memory: 8G
|
||||
cpu_count: 3
|
||||
environment_variables: 'DEBUG RUSTUP_IO_THREADS'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y -f curl node libnghttp2 npm openssl
|
||||
sudo npm install -g yarn --ignore-scripts
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain beta
|
||||
source "$HOME/.cargo/env"
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
echo "~~~~ node -v ~~~~"
|
||||
node -v
|
||||
echo "~~~~ yarn --version ~~~~"
|
||||
yarn --version
|
||||
pwd
|
||||
ls -lah
|
||||
whoami
|
||||
env
|
||||
freebsd-version
|
||||
yarn install
|
||||
yarn build
|
||||
rm -rf node_modules
|
||||
rm -rf target
|
||||
rm -rf .yarn/cache
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-freebsd
|
||||
path: ${{ env.APP_NAME }}.*.node
|
||||
if-no-files-found: error
|
||||
test-macOS-windows-binding:
|
||||
name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- host: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
architecture: x64
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-${{ matrix.settings.target }}
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Test bindings
|
||||
run: yarn test
|
||||
test-linux-x64-gnu-binding:
|
||||
name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-unknown-linux-gnu
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Test bindings
|
||||
run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test
|
||||
test-linux-x64-musl-binding:
|
||||
name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.libc "musl"
|
||||
yarn install
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-unknown-linux-musl
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Test bindings
|
||||
run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine yarn test
|
||||
test-linux-aarch64-gnu-binding:
|
||||
name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-unknown-linux-gnu
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.cpu "arm64"
|
||||
yarn config set supportedArchitectures.libc "glibc"
|
||||
yarn install
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:${{ matrix.node }}-slim
|
||||
options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build'
|
||||
run: |
|
||||
set -e
|
||||
yarn test
|
||||
ls -la
|
||||
test-linux-aarch64-musl-binding:
|
||||
name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-unknown-linux-musl
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.cpu "arm64"
|
||||
yarn config set supportedArchitectures.libc "musl"
|
||||
yarn install
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:lts-alpine
|
||||
options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build'
|
||||
run: |
|
||||
set -e
|
||||
yarn test
|
||||
test-linux-arm-gnueabihf-binding:
|
||||
name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-armv7-unknown-linux-gnueabihf
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.cpu "arm"
|
||||
yarn install
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:${{ matrix.node }}-bullseye-slim
|
||||
options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build'
|
||||
run: |
|
||||
set -e
|
||||
yarn test
|
||||
ls -la
|
||||
universal-macOS:
|
||||
name: Build universal macOS binary
|
||||
needs:
|
||||
- build
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download macOS x64 artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-apple-darwin
|
||||
path: artifacts
|
||||
- name: Download macOS arm64 artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-apple-darwin
|
||||
path: artifacts
|
||||
- name: Combine binaries
|
||||
run: yarn universal
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-universal-apple-darwin
|
||||
path: ${{ env.APP_NAME }}.*.node
|
||||
if-no-files-found: error
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-freebsd
|
||||
- test-macOS-windows-binding
|
||||
- test-linux-x64-gnu-binding
|
||||
- test-linux-x64-musl-binding
|
||||
- test-linux-aarch64-gnu-binding
|
||||
- test-linux-aarch64-musl-binding
|
||||
- test-linux-arm-gnueabihf-binding
|
||||
- universal-macOS
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.NOCOBASE_APP_ID }}
|
||||
private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }}
|
||||
repositories: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Move artifacts
|
||||
run: yarn artifacts
|
||||
- name: List packages
|
||||
run: ls -R ./npm
|
||||
shell: bash
|
||||
- name: Publish
|
||||
run: |
|
||||
npm config set provenance true
|
||||
if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$";
|
||||
then
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
npm publish --access public
|
||||
elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+";
|
||||
then
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
npm publish --tag next --access public
|
||||
else
|
||||
echo "Not a release, skipping publish"
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
@ -34,20 +34,6 @@ export interface SchemaSettingsChildrenProps {
|
||||
children: SchemaSettingsItemType[];
|
||||
}
|
||||
|
||||
const typeComponentMap = {
|
||||
item: SchemaSettingsItem,
|
||||
itemGroup: SchemaSettingsItemGroup,
|
||||
subMenu: SchemaSettingsSubMenu,
|
||||
divider: SchemaSettingsDivider,
|
||||
remove: SchemaSettingsRemove,
|
||||
select: SchemaSettingsSelectItem,
|
||||
cascader: SchemaSettingsCascaderItem,
|
||||
switch: SchemaSettingsSwitchItem,
|
||||
popup: SchemaSettingsPopupItem,
|
||||
actionModal: SchemaSettingsActionModalItem,
|
||||
modal: SchemaSettingsModalItem,
|
||||
};
|
||||
|
||||
const SchemaSettingsChildErrorFallback: FC<
|
||||
FallbackProps & {
|
||||
title: string;
|
||||
@ -113,6 +99,19 @@ export const SchemaSettingsChild: FC<SchemaSettingsItemType> = (props) => {
|
||||
hideIfNoChildren,
|
||||
componentProps,
|
||||
} = props as any;
|
||||
const typeComponentMap = {
|
||||
item: SchemaSettingsItem,
|
||||
itemGroup: SchemaSettingsItemGroup,
|
||||
subMenu: SchemaSettingsSubMenu,
|
||||
divider: SchemaSettingsDivider,
|
||||
remove: SchemaSettingsRemove,
|
||||
select: SchemaSettingsSelectItem,
|
||||
cascader: SchemaSettingsCascaderItem,
|
||||
switch: SchemaSettingsSwitchItem,
|
||||
popup: SchemaSettingsPopupItem,
|
||||
actionModal: SchemaSettingsActionModalItem,
|
||||
modal: SchemaSettingsModalItem,
|
||||
};
|
||||
const useChildrenRes = useChildren();
|
||||
const useComponentPropsRes = useComponentProps();
|
||||
const findComponent = useFindComponent();
|
||||
|
@ -22,8 +22,15 @@ import { useCreateFormBlockProps } from '../modules/blocks/data-blocks/form/hook
|
||||
import { useEditFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockDecoratorProps';
|
||||
import { useEditFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockProps';
|
||||
import { useDataFormItemProps } from '../modules/blocks/data-blocks/form/hooks/useDataFormItemProps';
|
||||
import { useGridCardBlockDecoratorProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps';
|
||||
import { useListBlockDecoratorProps } from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps';
|
||||
import {
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockItemProps,
|
||||
useGridCardBlockProps,
|
||||
} from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps';
|
||||
import {
|
||||
useListBlockDecoratorProps,
|
||||
useListBlockProps,
|
||||
} from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps';
|
||||
import { useTableSelectorDecoratorProps } from '../modules/blocks/data-blocks/table-selector/hooks/useTableSelectorDecoratorProps';
|
||||
import { TableColumnSchemaToolbar } from '../modules/blocks/data-blocks/table/TableColumnSchemaToolbar';
|
||||
import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||
@ -80,11 +87,14 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
||||
useTableSelectorProps,
|
||||
useTableBlockDecoratorProps,
|
||||
useListBlockDecoratorProps,
|
||||
useListBlockProps,
|
||||
useTableSelectorDecoratorProps,
|
||||
useCollapseBlockDecoratorProps,
|
||||
useFilterFormBlockProps,
|
||||
useFilterFormBlockDecoratorProps,
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockItemProps,
|
||||
useGridCardBlockProps,
|
||||
useFormItemProps,
|
||||
useDataFormItemProps,
|
||||
}}
|
||||
@ -141,11 +151,14 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
||||
useTableSelectorProps,
|
||||
useTableBlockDecoratorProps,
|
||||
useListBlockDecoratorProps,
|
||||
useListBlockProps,
|
||||
useTableSelectorDecoratorProps,
|
||||
useCollapseBlockDecoratorProps,
|
||||
useFilterFormBlockProps,
|
||||
useFilterFormBlockDecoratorProps,
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockProps,
|
||||
useGridCardBlockItemProps,
|
||||
useFormItemProps,
|
||||
useDataFormItemProps,
|
||||
});
|
||||
|
@ -1508,6 +1508,7 @@ export const useAssociationFilterBlockProps = () => {
|
||||
run,
|
||||
valueKey,
|
||||
labelKey,
|
||||
dataScopeFilter: filter,
|
||||
};
|
||||
};
|
||||
async function doReset({
|
||||
|
30
packages/core/client/src/common/AppNotFound.tsx
Normal file
30
packages/core/client/src/common/AppNotFound.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const AppNotFound = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle={t('Sorry, the page you visited does not exist.')}
|
||||
extra={
|
||||
<Button onClick={() => navigate('/', { replace: true })} type="primary">
|
||||
Back Home
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
@ -7,5 +7,6 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export * from './AppNotFound';
|
||||
export * from './SelectWithTitle';
|
||||
export * from './useFieldComponentName';
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
import { CollectionRecord } from '../collection-record';
|
||||
import { BlockRequestProvider } from './DataBlockRequestProvider';
|
||||
import { DataBlockResourceProvider } from './DataBlockResourceProvider';
|
||||
import { BlockLinkageRuleProvider } from '../../modules/blocks/BlockLinkageRuleProvider';
|
||||
|
||||
export interface AllDataBlockProps {
|
||||
collection: string | CollectionOptions;
|
||||
@ -189,13 +190,15 @@ export const DataBlockProvider: FC<Partial<AllDataBlockProps>> = withDynamicSche
|
||||
<CollectionManagerProvider dataSource={dataSource}>
|
||||
<AssociationOrCollectionProvider collection={collection} association={association}>
|
||||
<ACLCollectionProvider>
|
||||
<DataBlockResourceProvider>
|
||||
<BlockRequestProvider>
|
||||
<DataBlockCollector params={props.params}>
|
||||
<RerenderDataBlockProvider>{children}</RerenderDataBlockProvider>
|
||||
</DataBlockCollector>
|
||||
</BlockRequestProvider>
|
||||
</DataBlockResourceProvider>
|
||||
<BlockLinkageRuleProvider>
|
||||
<DataBlockResourceProvider>
|
||||
<BlockRequestProvider>
|
||||
<DataBlockCollector params={props.params}>
|
||||
<RerenderDataBlockProvider>{children}</RerenderDataBlockProvider>
|
||||
</DataBlockCollector>
|
||||
</BlockRequestProvider>
|
||||
</DataBlockResourceProvider>
|
||||
</BlockLinkageRuleProvider>
|
||||
</ACLCollectionProvider>
|
||||
</AssociationOrCollectionProvider>
|
||||
</CollectionManagerProvider>
|
||||
|
@ -500,7 +500,8 @@
|
||||
"Turn pages": "Seiten umblättern",
|
||||
"Others": "Andere",
|
||||
"Other records": "Andere Datensätze",
|
||||
"Save as template": "Als Vorlage speichern",
|
||||
"Save as reference template": "Als Referenzvorlage speichern",
|
||||
"Save as inherited template": "Als vererbte Vorlage speichern",
|
||||
"Save as block template": "Als Blockvorlage speichern",
|
||||
"Block templates": "Blockvorlagen",
|
||||
"Block template": "Blockvorlage",
|
||||
@ -589,6 +590,7 @@
|
||||
"Blank block": "Leerer Block",
|
||||
"Duplicate template": "Vorlage duplizieren",
|
||||
"Reference template": "Referenzvorlage",
|
||||
"Inherited template": "Vererbte Vorlage",
|
||||
"Create calendar block": "Kalenderblock erstellen",
|
||||
"Create kanban block": "Kanban-Block erstellen",
|
||||
"Grouping field": "Gruppierungsfeld",
|
||||
|
@ -505,7 +505,7 @@
|
||||
"Save as block template": "Save as block template",
|
||||
"Block templates": "Block templates",
|
||||
"Block template": "Block template",
|
||||
"Convert reference to duplicate": "Convert reference to duplicate",
|
||||
"Convert template to duplicate": "Convert template to duplicate",
|
||||
"Template name": "Template name",
|
||||
"Block type": "Block type",
|
||||
"No blocks to connect": "No blocks to connect",
|
||||
@ -590,6 +590,7 @@
|
||||
"Blank block": "Blank block",
|
||||
"Duplicate template": "Duplicate template",
|
||||
"Reference template": "Reference template",
|
||||
"Inherited template": "Inherited template",
|
||||
"Create calendar block": "Create calendar block",
|
||||
"Create kanban block": "Create kanban block",
|
||||
"Grouping field": "Grouping field",
|
||||
@ -889,7 +890,6 @@
|
||||
"No pages yet, please configure first": "No pages yet, please configure first",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode",
|
||||
"Deprecated": "Deprecated",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "The following old template features have been deprecated and will be removed in next version.",
|
||||
"Full permissions": "Full permissions",
|
||||
"Refresh data blocks": "Refresh data blocks",
|
||||
"Select data blocks to refresh": "Select data blocks to refresh",
|
||||
|
@ -470,7 +470,8 @@
|
||||
"Turn pages": "Pasar páginas",
|
||||
"Others": "Otros",
|
||||
"Other records": "Otros registros",
|
||||
"Save as template": "Guardar como plantilla",
|
||||
"Save as reference template": "Guardar como plantilla de referencia",
|
||||
"Save as inherited template": "Guardar como plantilla heredada",
|
||||
"Save as block template": "Guardar como plantilla de bloque",
|
||||
"Block templates": "Bloquear plantillas",
|
||||
"Block template": "Plantilla de bloque",
|
||||
@ -560,6 +561,7 @@
|
||||
"Blank block": "Bloque en blanco",
|
||||
"Duplicate template": "Duplicar plantilla",
|
||||
"Reference template": "Plantilla de referencia",
|
||||
"Inherited template": "Plantilla heredada",
|
||||
"Create calendar block": "Crear bloque de calendario",
|
||||
"Create kanban block": "Crear bloque kanban",
|
||||
"Grouping field": "Campo de agrupación",
|
||||
@ -806,7 +808,6 @@
|
||||
"No pages yet, please configure first": "Aún no hay páginas, por favor configura primero",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Haga clic en el icono \"Editor de UI\" en la esquina superior derecha para entrar en el modo de Editor de UI.",
|
||||
"Deprecated": "Obsoleto",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Las siguientes características de plantilla antigua han quedado obsoletas y se eliminarán en la próxima versión.",
|
||||
"Full permissions": "Todos los derechos",
|
||||
"Refresh data blocks": "Actualizar bloques de datos",
|
||||
"Select data blocks to refresh": "Actualizar bloques de datos",
|
||||
|
@ -485,7 +485,8 @@
|
||||
"Turn pages": "Tourner les pages",
|
||||
"Others": "Autres",
|
||||
"Other records": "Autres enregistrements",
|
||||
"Save as template": "Enregistrer en tant que modèle",
|
||||
"Save as reference template": "Enregistrer en tant que modèle de référence",
|
||||
"Save as inherited template": "Enregistrer en tant que modèle hérité",
|
||||
"Save as block template": "Enregistrer en tant que modèle de bloc",
|
||||
"Block templates": "Modèles de bloc",
|
||||
"Block template": "Modèle de bloc",
|
||||
@ -573,6 +574,7 @@
|
||||
"Blank block": "Bloc vierge",
|
||||
"Duplicate template": "Dupliquer le modèle",
|
||||
"Reference template": "Référencer le modèle",
|
||||
"Inherited template": "Modèle hérité",
|
||||
"Create calendar block": "Créer un bloc de calendrier",
|
||||
"Create kanban block": "Créer un bloc kanban",
|
||||
"Grouping field": "Champ de regroupement",
|
||||
@ -826,7 +828,6 @@
|
||||
"No pages yet, please configure first": "Pas encore de pages, veuillez configurer d'abord",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||
"Deprecated": "Déprécié",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Les fonctionnalités des anciens modèles ont été dépréciées et seront supprimées dans la prochaine version.",
|
||||
"Full permissions": "Tous les droits",
|
||||
"Refresh data blocks": "Actualiser les blocs de données",
|
||||
"Select data blocks to refresh": "Actualiser les blocs de données",
|
||||
|
@ -494,7 +494,8 @@
|
||||
"Turn pages": "Volta pagine",
|
||||
"Others": "Altri",
|
||||
"Other records": "Altri record",
|
||||
"Save as template": "Salva come modello",
|
||||
"Save as reference template": "Salva come modello di riferimento",
|
||||
"Save as inherited template": "Salva come modello ereditato",
|
||||
"Save as block template": "Salva come modello blocco",
|
||||
"Block templates": "Modelli blocco",
|
||||
"Block template": "Modello blocco",
|
||||
@ -580,6 +581,7 @@
|
||||
"Blank block": "Blocco vuoto",
|
||||
"Duplicate template": "Modello duplicato",
|
||||
"Reference template": "Modello di riferimento",
|
||||
"Inherited template": "Modello ereditato",
|
||||
"Create calendar block": "Crea blocco calendario",
|
||||
"Create kanban block": "Crea blocco kanban",
|
||||
"Grouping field": "Campo di raggruppamento",
|
||||
|
@ -397,7 +397,8 @@
|
||||
"Turn pages": "ページをめくる",
|
||||
"Others": "その他",
|
||||
"Other records": "他のレコード",
|
||||
"Save as template": "テンプレートとして保存",
|
||||
"Save as reference template": "参照テンプレートとして保存",
|
||||
"Save as inherited template": "継承テンプレートとして保存",
|
||||
"Save as block template": "ブロックテンプレートとして保存",
|
||||
"Block templates": "ブロックテンプレート",
|
||||
"Block template": "ブロックテンプレート",
|
||||
@ -472,6 +473,7 @@
|
||||
"Blank block": "空のブロック",
|
||||
"Duplicate template": "テンプレートをコピー",
|
||||
"Reference template": "テンプレートを参照",
|
||||
"Inherited template": "継承テンプレート",
|
||||
"Create calendar block": "カレンダーブロックの作成",
|
||||
"Create kanban block": "かんばんブロックの作成",
|
||||
"Grouping field": "グループフィールド",
|
||||
@ -1044,7 +1046,6 @@
|
||||
"No pages yet, please configure first": "まだページがありません。最初に設定してください",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "ユーザーインターフェースエディターモードに入るには、右上隅の「UIエディタ」アイコンをクリックしてください",
|
||||
"Deprecated": "非推奨",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "次の古いテンプレート機能は非推奨になり、次のバージョンで削除されます。",
|
||||
"Full permissions": "すべての権限",
|
||||
"Refresh data blocks": "データブロックを更新",
|
||||
"Select data blocks to refresh": "データブロックを選択して更新",
|
||||
|
@ -517,7 +517,8 @@
|
||||
"Turn pages": "페이지 넘김",
|
||||
"Others": "기타",
|
||||
"Other records": "기타 레코드",
|
||||
"Save as template": "템플릿으로 저장",
|
||||
"Save as reference template": "참조 템플릿으로 저장",
|
||||
"Save as inherited template": "상속 템플릿으로 저장",
|
||||
"Save as block template": "블록 템플릿으로 저장",
|
||||
"Block templates": "블록 템플릿",
|
||||
"Block template": "블록 템플릿",
|
||||
@ -601,6 +602,7 @@
|
||||
"Blank block": "빈 블록",
|
||||
"Duplicate template": "템플릿 복제",
|
||||
"Reference template": "참조 템플릿",
|
||||
"Inherited template": "상속 템플릿",
|
||||
"Create calendar block": "캘린더 블록 생성",
|
||||
"Create kanban block": "칸반 블록 생성",
|
||||
"Grouping field": "그루핑 필드",
|
||||
@ -917,7 +919,6 @@
|
||||
"No pages yet, please configure first": "아직 페이지가 없습니다. 먼저 설정하십시오",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "사용자 인터페이스 편집기 모드에 들어가려면 오른쪽 상단의 \"UI 편집기\" 아이콘을 클릭하십시오",
|
||||
"Deprecated": "사용 중단됨",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "다음 오래된 템플릿 기능은 사용 중단되었으며 다음 버전에서 제거될 것입니다.",
|
||||
"Full permissions": "모든 권한",
|
||||
"Refresh data blocks": "데이터 블록 새로 고침",
|
||||
"Select data blocks to refresh": "데이터 블록을 선택하여 새로 고침",
|
||||
|
@ -504,7 +504,8 @@
|
||||
"Turn pages": "Pagina's omslaan",
|
||||
"Others": "Overigen",
|
||||
"Other records": "Andere records",
|
||||
"Save as template": "Opslaan als sjabloon",
|
||||
"Save as reference template": "Opslaan als referentiesjabloon",
|
||||
"Save as inherited template": "Opslaan als overerfde sjabloon",
|
||||
"Save as block template": "Opslaan als bloksjabloon",
|
||||
"Block templates": "Bloksjablonen",
|
||||
"Block template": "Bloksjabloon",
|
||||
@ -593,6 +594,7 @@
|
||||
"Blank block": "Leeg blok",
|
||||
"Duplicate template": "Sjabloon dupliceren",
|
||||
"Reference template": "Sjabloon refereren",
|
||||
"Inherited template": "Overerfde sjabloon",
|
||||
"Create calendar block": "Kalenderblok maken",
|
||||
"Create kanban block": "Kanbanblok maken",
|
||||
"Grouping field": "Groepeer veld",
|
||||
|
@ -433,7 +433,8 @@
|
||||
"Turn pages": "Virar páginas",
|
||||
"Others": "Outros",
|
||||
"Other records": "Outros registros",
|
||||
"Save as template": "Salvar como modelo",
|
||||
"Save as reference template": "Salvar como modelo de referência",
|
||||
"Save as inherited template": "Salvar como modelo herdado",
|
||||
"Save as block template": "Salvar como modelo de bloco",
|
||||
"Block templates": "Modelos de bloco",
|
||||
"Block template": "Modelo de bloco",
|
||||
@ -525,6 +526,7 @@
|
||||
"Blank block": "Bloco em branco",
|
||||
"Duplicate template": "Duplicar modelo",
|
||||
"Reference template": "Modelo de referência",
|
||||
"Inherited template": "Modelo herdado",
|
||||
"Create calendar block": "Criar bloco de calendário",
|
||||
"Create kanban block": "Criar bloco Kanban",
|
||||
"Grouping field": "Campo de agrupamento",
|
||||
@ -781,7 +783,6 @@
|
||||
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la.",
|
||||
"Deprecated": "Descontinuado",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "As seguintes funcionalidades de modelo antigo foram descontinuadas e serão removidas na próxima versão.",
|
||||
"Full permissions": "Todas as permissões",
|
||||
"Refresh data blocks": "Atualizar blocos de dados",
|
||||
"Select data blocks to refresh": "Selecionar blocos de dados para atualizar",
|
||||
|
@ -337,7 +337,8 @@
|
||||
"Turn pages": "Перелистывать страницы",
|
||||
"Others": "Другие",
|
||||
"Other records": "Другие записи",
|
||||
"Save as template": "Сохранить как шаблон",
|
||||
"Save as reference template": "Сохранить как шаблон ссылки",
|
||||
"Save as inherited template": "Сохранить как шаблон наследования",
|
||||
"Save as block template": "Сохранить как шаблон Блока",
|
||||
"Block templates": "Шаблоны блоков",
|
||||
"Convert reference to duplicate": "Преобразовать ссылку в дубликат",
|
||||
@ -411,6 +412,7 @@
|
||||
"Blank block": "Пустой блок",
|
||||
"Duplicate template": "Дублировать шаблон",
|
||||
"Reference template": "Справочный шаблон",
|
||||
"Inherited template": "Наследуемый шаблон",
|
||||
"Create calendar block": "Создать блок календаря",
|
||||
"Create kanban block": "Создать блок Канбан",
|
||||
"Grouping field": "Поле группировки",
|
||||
@ -610,7 +612,6 @@
|
||||
"Are you sure you want to hide this tab?": "Вы уверены, что хотите скрыть эту вкладку?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее.",
|
||||
"Deprecated": "Устаревший",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Следующие старые функции шаблонов устарели и будут удалены в следующей версии.",
|
||||
"Full permissions": "Полные права",
|
||||
"Refresh data blocks": "Обновить блоки данных",
|
||||
"Select data blocks to refresh": "Выберите блоки данных для обновления",
|
||||
|
@ -336,7 +336,8 @@
|
||||
"Turn pages": "Sayfaları çevir",
|
||||
"Others": "Diğerleri",
|
||||
"Other records": "Diğer kayıtlar",
|
||||
"Save as template": "Şablon olarak kaydet",
|
||||
"Save as reference template": "Referans şablonu olarak kaydet",
|
||||
"Save as inherited template": "Kalıtım şablonu olarak kaydet",
|
||||
"Save as block template": "Blok şablonu olarak kaydet",
|
||||
"Block templates": "Blok şablonları",
|
||||
"Block template": "Blok şablonu",
|
||||
@ -411,6 +412,7 @@
|
||||
"Blank block": "Boş blok",
|
||||
"Duplicate template": "Şablonun kopyasını oluştur",
|
||||
"Reference template": "Referans şablon",
|
||||
"Inherited template": "Kalıtım şablonu",
|
||||
"Create calendar block": "Takvim bloğu oluştur",
|
||||
"Create kanban block": "Kanban bloğu oluştur",
|
||||
"Grouping field": "Alan gruplandırma",
|
||||
@ -608,7 +610,6 @@
|
||||
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor.",
|
||||
"Deprecated": "Kullanımdan kaldırıldı",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Aşağıdaki eski şablon özellikleri kullanımdan kaldırıldı ve gelecek sürümlerde kaldırılacaktır.",
|
||||
"Full permissions": "Tüm izinler",
|
||||
"Refresh data blocks": "Yenile veri blokları",
|
||||
"Select data blocks to refresh": "Veri bloklarını yenilemek için seçin",
|
||||
|
@ -487,7 +487,8 @@
|
||||
"Turn pages": "Переключати сторінки",
|
||||
"Others": "Інші",
|
||||
"Other records": "Інші записи",
|
||||
"Save as template": "Зберегти як шаблон",
|
||||
"Save as reference template": "Зберегти як шаблон посилання",
|
||||
"Save as inherited template": "Зберегти як шаблон нащадка",
|
||||
"Save as block template": "Зберегти як шаблон блока",
|
||||
"Block templates": "Шаблони блоків",
|
||||
"Block template": "Шаблон блока",
|
||||
@ -577,6 +578,7 @@
|
||||
"Blank block": "Порожній блок",
|
||||
"Duplicate template": "Дублювати шаблон",
|
||||
"Reference template": "Посилання на шаблон",
|
||||
"Inherited template": "Нащадковий шаблон",
|
||||
"Create calendar block": "Створити блок календаря",
|
||||
"Create kanban block": "Створити блок канбану",
|
||||
"Grouping field": "Поле для групування",
|
||||
@ -824,7 +826,6 @@
|
||||
"Are you sure you want to hide this tab?": "Ви впевнені, що хочете приховати цю вкладку?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її.",
|
||||
"Deprecated": "Застаріло",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Наступні старі функції шаблонів були застарілі і будуть видалені в наступній версії.",
|
||||
"Full permissions": "Повні права",
|
||||
"Refresh data blocks": "Оновити дані блоків",
|
||||
"Select data blocks to refresh": "Виберіть блоки даних для оновлення",
|
||||
|
@ -527,7 +527,8 @@
|
||||
"Turn pages": "翻页",
|
||||
"Others": "其他",
|
||||
"Other records": "其他记录",
|
||||
"Save as template": "保存为模板",
|
||||
"Save as reference template": "保存为引用模板",
|
||||
"Save as inherited template": "保存为继承模板",
|
||||
"Save as block template": "保存为区块模板",
|
||||
"Block templates": "区块模板",
|
||||
"Block template": "区块模板",
|
||||
@ -611,6 +612,7 @@
|
||||
"Blank block": "空区块",
|
||||
"Duplicate template": "复制模板",
|
||||
"Reference template": "引用模板",
|
||||
"Inherited template": "继承模板",
|
||||
"Create calendar block": "创建日历区块",
|
||||
"Create kanban block": "创建看板区块",
|
||||
"Grouping field": "分组字段",
|
||||
@ -1088,7 +1090,6 @@
|
||||
"Are you sure you want to hide this tab?": "你确定要隐藏该标签页吗?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。",
|
||||
"Deprecated": "已弃用",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "以下旧的模板功能已弃用,将在下个版本移除。",
|
||||
"Full permissions": "全部权限",
|
||||
"Enable index column": "启用序号列",
|
||||
"Date scope": "日期范围",
|
||||
@ -1101,7 +1102,9 @@
|
||||
"Italic": "斜体",
|
||||
"Response record":"响应结果记录",
|
||||
"Colon":"冒号",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。",
|
||||
"No pages yet, please configure first": "暂无页面,请先配置",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式"
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。",
|
||||
"Block Linkage rules":"区块联动规则",
|
||||
"Field Linkage rules":"字段联动规则"
|
||||
}
|
||||
|
@ -518,7 +518,8 @@
|
||||
"Turn pages": "翻頁",
|
||||
"Others": "其他",
|
||||
"Other records": "其他記錄",
|
||||
"Save as template": "儲存為模板",
|
||||
"Save as reference template": "儲存為引用模板",
|
||||
"Save as inherited template": "儲存為繼承模板",
|
||||
"Save as block template": "儲存為區塊模板",
|
||||
"Block templates": "區塊模板",
|
||||
"Block template": "區塊模板",
|
||||
@ -602,6 +603,7 @@
|
||||
"Blank block": "空區塊",
|
||||
"Duplicate template": "複製模板",
|
||||
"Reference template": "引用模板",
|
||||
"Inherited template": "繼承模板",
|
||||
"Create calendar block": "建立日曆區塊",
|
||||
"Create kanban block": "建立看板區塊",
|
||||
"Grouping field": "群組欄位",
|
||||
@ -915,7 +917,6 @@
|
||||
"Are you sure you want to hide this tab?": "你確定要隱藏這個標籤嗎?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。",
|
||||
"Deprecated": "已棄用",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "以下舊的模板功能已棄用,將在下個版本移除。",
|
||||
"Full permissions": "完全權限",
|
||||
"Refresh data blocks": "刷新數據區塊",
|
||||
"Select data blocks to refresh": "選擇要刷新的數據區塊",
|
||||
|
@ -33,8 +33,9 @@ test.describe('Link', () => {
|
||||
).toHaveCount(1);
|
||||
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||
await page.getByLabel('block-item-users-URL').getByLabel('textbox').click();
|
||||
await page
|
||||
.getByLabel('block-item-users-table-URL')
|
||||
.getByLabel('block-item-users-URL')
|
||||
.getByLabel('textbox')
|
||||
.fill(await nocoPage.getUrl());
|
||||
await page.getByPlaceholder('Name').fill('id');
|
||||
@ -102,7 +103,7 @@ test.describe('Link', () => {
|
||||
await page.getByLabel('action-Action.Link-Link-').hover();
|
||||
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:link-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||
await page.getByLabel('block-item-users-table-URL').getByLabel('textbox').fill(otherPageUrl);
|
||||
await page.getByLabel('block-item-users-URL').getByLabel('textbox').fill(otherPageUrl);
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
|
||||
await page.getByLabel('action-Action.Link-Link-').click();
|
||||
|
@ -69,6 +69,21 @@ export const addNewActionSettings = new SchemaSettings({
|
||||
return isChildCollectionAction;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { association } = useDataBlockProps() || {};
|
||||
const { name } = useCollection_deprecated();
|
||||
const { getCollectionField } = useCollectionManager_deprecated();
|
||||
const associationField = getCollectionField(association);
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
collectionName: associationField?.collectionName || name,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
sort: 100,
|
||||
|
@ -51,6 +51,16 @@ export const bulkDeleteActionSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'remove',
|
||||
sort: 100,
|
||||
|
@ -53,14 +53,33 @@ export const filterActionSettings = new SchemaSettings({
|
||||
default: fieldSchema?.['x-component-props']?.icon,
|
||||
'x-component-props': {},
|
||||
},
|
||||
onlyIcon: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
title: t('Icon only'),
|
||||
default: fieldSchema?.['x-component-props']?.onlyIcon,
|
||||
'x-component-props': {},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['icon'],
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: '{{!$deps[0]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ISchema,
|
||||
onSubmit: ({ title, icon }) => {
|
||||
onSubmit: ({ title, icon, onlyIcon }) => {
|
||||
fieldSchema.title = title;
|
||||
field.title = title;
|
||||
field.componentProps.icon = icon;
|
||||
field.componentProps.onlyIcon = onlyIcon;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props'].icon = icon;
|
||||
fieldSchema['x-component-props'].onlyIcon = onlyIcon;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
|
@ -15,6 +15,7 @@ import { useDesignable } from '../../../';
|
||||
import { useSchemaToolbar } from '../../../application';
|
||||
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||
import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { useCollectionManager_deprecated } from '../../../collection-manager';
|
||||
import {
|
||||
SchemaSettingsLinkageRules,
|
||||
SchemaSettingsModalItem,
|
||||
@ -96,6 +97,9 @@ export const customizeLinkActionSettings = new SchemaSettings({
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
const { association } = useDataBlockProps() || {};
|
||||
const { getCollectionField } = useCollectionManager_deprecated();
|
||||
const associationField = getCollectionField(association);
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useEffect, useState } from 'react';
|
||||
import { useFieldSchema, useForm } from '@formily/react';
|
||||
import { last, isEqual } from 'lodash';
|
||||
import { uid } from '@formily/shared';
|
||||
import { reaction } from '@formily/reactive';
|
||||
import { useLocalVariables, useVariables } from '../../variables';
|
||||
import { useReactiveLinkageEffect } from './utils';
|
||||
import { useDesignable } from '../../';
|
||||
import { forEachLinkageRule } from '../../schema-settings/LinkageRules/forEachLinkageRule';
|
||||
import {
|
||||
getVariableValuesInCondition,
|
||||
getVariableValuesInExpression,
|
||||
} from '../../schema-settings/LinkageRules/bindLinkageRulesToFiled';
|
||||
|
||||
const getLinkageRules = (fieldSchema) => {
|
||||
if (!fieldSchema) {
|
||||
return [];
|
||||
}
|
||||
let linkageRules = fieldSchema?.['x-block-linkage-rules'] || [];
|
||||
fieldSchema.mapProperties((schema) => {
|
||||
if (schema['x-block-linkage-rules']) {
|
||||
linkageRules = schema['x-block-linkage-rules'];
|
||||
}
|
||||
});
|
||||
return linkageRules?.filter((k) => !k.disabled);
|
||||
};
|
||||
|
||||
export const BlockLinkageRuleProvider = (props) => {
|
||||
const schema = useFieldSchema();
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
const { designable } = useDesignable();
|
||||
const form = useForm();
|
||||
const linkageRules = useMemo(() => getLinkageRules(schema), [schema]);
|
||||
const [triggerLinkageUpdate, setTriggerLinkageUpdate] = useState(null);
|
||||
const displayResult = useReactiveLinkageEffect(linkageRules, variables, localVariables, triggerLinkageUpdate);
|
||||
const shouldCalculateFormLinkage = schema?.['x-decorator'] === 'FormItem' && !form.readPretty && linkageRules.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldCalculateFormLinkage) {
|
||||
const id = uid();
|
||||
const disposes = [];
|
||||
|
||||
// 延迟执行,防止一开始获取到的 form.values 值是旧的
|
||||
setTimeout(() => {
|
||||
form.addEffects(id, () => {
|
||||
forEachLinkageRule(linkageRules, (action, rule) => {
|
||||
return reaction(
|
||||
() => {
|
||||
// 获取条件中的变量值
|
||||
const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables });
|
||||
// 获取 value 表达式中的变量值
|
||||
const variableValuesInExpression = getVariableValuesInExpression({ action, localVariables });
|
||||
const result = [variableValuesInCondition, variableValuesInExpression]
|
||||
.map((item) => JSON.stringify(item))
|
||||
.join(',');
|
||||
return result;
|
||||
},
|
||||
() => {
|
||||
setTriggerLinkageUpdate(uid());
|
||||
},
|
||||
{ fireImmediately: true, equals: isEqual },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 清理副作用
|
||||
return () => {
|
||||
form.removeEffects(id);
|
||||
disposes.forEach((dispose) => {
|
||||
dispose();
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [linkageRules, shouldCalculateFormLinkage]);
|
||||
if (!linkageRules.length) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
if (displayResult === null) return null;
|
||||
if (last(displayResult) === 'hidden') {
|
||||
if (designable) {
|
||||
return <div style={{ opacity: 0.3 }}>{props.children}</div>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return props.children;
|
||||
};
|
@ -45,7 +45,7 @@ test.describe('multi data details block schema settings', () => {
|
||||
// 禁用规则,联动规则失效
|
||||
await page.getByLabel('block-item-CardItem-users-').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:detailsWithPagination-users').hover();
|
||||
await page.getByText('Linkage rules').click();
|
||||
await page.getByText('Field Linkage rules').click();
|
||||
await page.getByRole('switch', { name: 'On Off' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
await page.reload();
|
||||
|
@ -14,7 +14,7 @@ import { SchemaSettings } from '../../../../application/schema-settings/SchemaSe
|
||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||
import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider';
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollection_deprecated, useSortFields } from '../../../../collection-manager';
|
||||
import { useSortFields } from '../../../../collection-manager';
|
||||
import { removeNullCondition, useDesignable } from '../../../../schema-component';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
@ -24,6 +24,8 @@ import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettin
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { setDataLoadingModeSettingsItem } from './setDataLoadingModeSettingsItem';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
import { useCollection } from '../../../../data-source';
|
||||
|
||||
const commonItems: SchemaSettingsItemType[] = [
|
||||
{
|
||||
@ -35,13 +37,28 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
readPretty: true,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -49,7 +66,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
name: 'dataScope',
|
||||
Component: SchemaSettingsDataScope,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { form } = useFormBlockContext();
|
||||
const field = useField();
|
||||
@ -83,7 +100,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
name: 'sortingRules',
|
||||
type: 'modal',
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField();
|
||||
@ -201,7 +218,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
name: 'template',
|
||||
Component: SchemaSettingsTemplate,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { componentNamePrefix } = useBlockTemplateContext();
|
||||
const defaultResource =
|
||||
|
@ -8,14 +8,17 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
const commonItems: SchemaSettingsItemType[] = [
|
||||
{
|
||||
@ -27,13 +30,28 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
readPretty: true,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -310,7 +310,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
@ -438,7 +438,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
@ -563,7 +563,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
@ -701,7 +701,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
|
@ -18,7 +18,7 @@ test.describe('deprecated variables', () => {
|
||||
await page.getByLabel('action-Action.Link-Edit').click();
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:editForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await expect(page.getByLabel('variable-tag').getByText('Current record / Nickname')).toBeVisible();
|
||||
|
||||
// 2. 但是变量列表中是禁用状态
|
||||
@ -57,7 +57,7 @@ test.describe('deprecated variables', () => {
|
||||
// 4. 再次打开弹窗,变量列表中的弃用变量不再显示
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:editForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).last().click();
|
||||
await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toBeHidden();
|
||||
// 使下拉菜单消失
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
@ -21,6 +22,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
export const createFormBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:createForm',
|
||||
@ -34,12 +36,27 @@ export const createFormBlockSettings = new SchemaSettings({
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
@ -21,6 +22,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
export const editFormBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:editForm',
|
||||
@ -34,12 +36,27 @@ export const editFormBlockSettings = new SchemaSettings({
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -28,7 +28,7 @@ test.describe('where grid card block can be added', () => {
|
||||
await expect(page.getByLabel('block-item-BlockItem-users-grid-card')).toBeVisible();
|
||||
});
|
||||
|
||||
test('popup', async ({ page, mockPage }) => {
|
||||
test.skip('popup', async ({ page, mockPage }) => {
|
||||
await mockPage(oneEmptyTableWithUsers).goto();
|
||||
|
||||
// 1. 打开弹窗,通过 Associated records 创建一个列表区块
|
||||
|
@ -24,6 +24,8 @@ import { useBlockTemplateContext } from '../../../../schema-templates/BlockTempl
|
||||
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
||||
import { SetTheCountOfColumnsDisplayedInARow } from './SetTheCountOfColumnsDisplayedInARow';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
|
||||
export const gridCardBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:gridCard',
|
||||
@ -32,6 +34,19 @@ export const gridCardBlockSettings = new SchemaSettings({
|
||||
name: 'setTheBlockHeight',
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SetTheCountOfColumnsDisplayedInARow',
|
||||
Component: SetTheCountOfColumnsDisplayedInARow,
|
||||
|
@ -27,3 +27,11 @@ export function useGridCardBlockDecoratorProps(props) {
|
||||
parseVariableLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export function useGridCardBlockItemProps() {
|
||||
return {};
|
||||
}
|
||||
|
||||
export function useGridCardBlockProps() {
|
||||
return {};
|
||||
}
|
||||
|
@ -22,3 +22,7 @@ export function useListBlockDecoratorProps(props) {
|
||||
parentRecord,
|
||||
};
|
||||
}
|
||||
|
||||
export function useListBlockProps() {
|
||||
return {};
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettin
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
|
||||
export const listBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:list',
|
||||
@ -34,6 +36,19 @@ export const listBlockSettings = new SchemaSettings({
|
||||
name: 'setTheBlockHeight',
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SetTheDataScope',
|
||||
Component: SchemaSettingsDataScope,
|
||||
|
@ -61,14 +61,11 @@ export const TableBlockInitializer = ({
|
||||
|
||||
export const useCreateTableBlock = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { getCollection } = useCollectionManager_deprecated();
|
||||
|
||||
const createTableBlock = ({ item }) => {
|
||||
const collection = getCollection(item.name, item.dataSource);
|
||||
const schema = createTableBlockUISchema({
|
||||
collectionName: item.name,
|
||||
dataSource: item.dataSource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
});
|
||||
insert(schema);
|
||||
};
|
||||
|
@ -17,14 +17,14 @@ test('action linkage by row data', async ({ page, mockPage }) => {
|
||||
.getByLabel('action-Action.Link-Edit-update-roles-table-admin')
|
||||
.locator('.nb-action-title');
|
||||
const adminEditActionStyle = await adminEditAction.evaluate((element) => {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const computedStyle = window.getComputedStyle(element.querySelector('.nb-action-title'));
|
||||
return {
|
||||
opacity: computedStyle.opacity,
|
||||
};
|
||||
});
|
||||
const rootEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-root').locator('.nb-action-title');
|
||||
const rootEditActionStyle = await rootEditAction.evaluate((element) => {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const computedStyle = window.getComputedStyle(element.querySelector('.nb-action-title'));
|
||||
return {
|
||||
opacity: computedStyle.opacity,
|
||||
// 添加其他你需要的样式属性
|
||||
|
@ -39,7 +39,7 @@ test.describe('where table block can be added', () => {
|
||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'childAssociationField' }).waitFor({ state: 'detached' });
|
||||
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||
await page.getByRole('menuitem', { name: 'Associated records right' }).last().hover();
|
||||
await page.getByRole('menuitem', { name: 'childAssociationField' }).click();
|
||||
await page
|
||||
.getByTestId('drawer-Action.Container-childCollection-View record')
|
||||
@ -50,8 +50,9 @@ test.describe('where table block can be added', () => {
|
||||
// 添加父表关系区块
|
||||
await page.getByRole('menuitem', { name: 'Table right' }).waitFor({ state: 'detached' });
|
||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||
await page.getByRole('menuitem', { name: 'Associated records right' }).waitFor({ state: 'detached' });
|
||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||
await page.getByRole('menuitem', { name: 'Associated records right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'parentAssociationField' }).click();
|
||||
await page.getByLabel('schema-initializer-TableV2-table:configureColumns-parentTargetCollection').hover();
|
||||
await page.getByRole('menuitem', { name: 'parentTargetText' }).click();
|
||||
@ -72,6 +73,7 @@ test.describe('where table block can be added', () => {
|
||||
|
||||
// 通过 Other records 创建一个表格区块
|
||||
await page.getByLabel('schema-initializer-Grid-popup').hover();
|
||||
await page.getByRole('menuitem', { name: 'Other records right' }).waitFor({ state: 'detached' });
|
||||
await page.getByRole('menuitem', { name: 'Table right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'Other records right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'Users' }).click();
|
||||
|
@ -17,7 +17,7 @@ vi.mock('@formily/shared', () => {
|
||||
|
||||
describe('createTableBLockSchemaV2', () => {
|
||||
it('should create a default table block schema with minimum options', () => {
|
||||
const options = { dataSource: 'abc', collectionName: 'users', association: 'users.roles', rowKey: 'rowKey' };
|
||||
const options = { dataSource: 'abc', collectionName: 'users', association: 'users.roles' };
|
||||
const schema = createTableBlockUISchema(options);
|
||||
|
||||
expect(schema).toMatchInlineSnapshot(`
|
||||
@ -85,7 +85,6 @@ describe('createTableBLockSchemaV2', () => {
|
||||
"params": {
|
||||
"pageSize": 20,
|
||||
},
|
||||
"rowKey": "rowKey",
|
||||
"showIndex": true,
|
||||
},
|
||||
"x-filter-targets": [],
|
||||
|
@ -13,10 +13,9 @@ import { uid } from '@formily/shared';
|
||||
export const createTableBlockUISchema = (options: {
|
||||
dataSource: string;
|
||||
collectionName?: string;
|
||||
rowKey?: string;
|
||||
association?: string;
|
||||
}): ISchema => {
|
||||
const { collectionName, dataSource, rowKey, association } = options;
|
||||
const { collectionName, dataSource, association } = options;
|
||||
|
||||
if (!dataSource) {
|
||||
throw new Error('dataSource is required');
|
||||
@ -35,7 +34,6 @@ export const createTableBlockUISchema = (options: {
|
||||
params: {
|
||||
pageSize: 20,
|
||||
},
|
||||
rowKey,
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
},
|
||||
|
@ -11,15 +11,19 @@ import { useFieldSchema } from '@formily/react';
|
||||
import { useMemo } from 'react';
|
||||
import { useParsedFilter } from '../../../../../block-provider/hooks/useParsedFilter';
|
||||
import { useParentRecordCommon } from '../../../useParentRecordCommon';
|
||||
import { useDataSourceManager } from '../../../../../data-source';
|
||||
|
||||
export const useTableBlockDecoratorProps = (props) => {
|
||||
const { params, parseVariableLoading } = useTableBlockParams(props);
|
||||
const parentRecord = useParentRecordCommon(props.association);
|
||||
const dm = useDataSourceManager();
|
||||
const collection = dm.getDataSource(props.dataSource)?.collectionManager.getCollection(props.collection);
|
||||
|
||||
return {
|
||||
params,
|
||||
parentRecord,
|
||||
parseVariableLoading,
|
||||
rowKey: collection?.filterTargetKey || 'id',
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -26,6 +26,8 @@ import { setTheDataScopeSchemaSettingsItem } from '../../../../schema-settings/s
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
|
||||
import { SchemaSettingsItemType } from '../../../../application';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
const enabledIndexColumn: SchemaSettingsItemType = {
|
||||
name: 'enableIndexColumn',
|
||||
@ -64,6 +66,19 @@ export const tableBlockSettings = new SchemaSettings({
|
||||
name: 'setTheBlockHeight',
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'treeTable',
|
||||
type: 'switch',
|
||||
@ -138,7 +153,6 @@ export const tableBlockSettings = new SchemaSettings({
|
||||
const { resource } = field.decoratorProps;
|
||||
const collectionField = resource && getCollectionField(resource);
|
||||
const api = useAPIClient();
|
||||
|
||||
return {
|
||||
title: t('Enable drag and drop sorting'),
|
||||
checked: field.decoratorProps.dragSort,
|
||||
|
@ -12,11 +12,14 @@ import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { FilterBlockType } from '../../../../filter-provider';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
||||
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||
|
||||
export const filterCollapseBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:filterCollapse',
|
||||
@ -29,6 +32,19 @@ export const filterCollapseBlockSettings = new SchemaSettings({
|
||||
name: 'setTheBlockHeight',
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ConvertReferenceToDuplicate',
|
||||
Component: SchemaSettingsTemplate,
|
||||
|
@ -19,6 +19,7 @@ import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/Schema
|
||||
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
export const filterFormBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:filterForm',
|
||||
@ -48,12 +49,27 @@ export const filterFormBlockSettings = new SchemaSettings({
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -7,11 +7,14 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useField } from '@formily/react';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
import { SchemaSettingsRenderEngine } from '../../../../schema-settings/SchemaSettingsRenderEngine';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
export const markdownBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:markdown',
|
||||
items: [
|
||||
@ -21,7 +24,6 @@ export const markdownBlockSettings = new SchemaSettings({
|
||||
useComponentProps() {
|
||||
const field = useField();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
title: t('Edit markdown'),
|
||||
onClick: () => {
|
||||
@ -34,6 +36,27 @@ export const markdownBlockSettings = new SchemaSettings({
|
||||
name: 'setTheBlockHeight',
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const underForm = fieldSchema['x-decorator'] === 'FormItem';
|
||||
return {
|
||||
title: underForm ? t('Linkage rules') : t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
returnScope: (options) => {
|
||||
return options.filter((v) => {
|
||||
if (!underForm) {
|
||||
return !['$nForm', '$nRecord'].includes(v.value);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setBlockTemplate',
|
||||
Component: SchemaSettingsRenderEngine,
|
||||
|
91
packages/core/client/src/modules/blocks/utils.ts
Normal file
91
packages/core/client/src/modules/blocks/utils.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { VariableOption, VariablesContextType } from '../../variables/types';
|
||||
import { conditionAnalyses } from '../../schema-component/common/utils/uitls';
|
||||
import { useApp } from '../../application';
|
||||
import { useCollectionRecord } from '../../data-source';
|
||||
|
||||
enum ActionType {
|
||||
Visible = 'visible',
|
||||
Hidden = 'hidden',
|
||||
}
|
||||
|
||||
const linkageAction = async (
|
||||
{
|
||||
operator,
|
||||
condition,
|
||||
variables,
|
||||
localVariables,
|
||||
conditionType,
|
||||
displayResult,
|
||||
}: {
|
||||
operator;
|
||||
condition;
|
||||
variables: VariablesContextType;
|
||||
localVariables: VariableOption[];
|
||||
conditionType: 'advanced';
|
||||
displayResult: any[];
|
||||
},
|
||||
jsonLogic: any,
|
||||
) => {
|
||||
switch (operator) {
|
||||
case ActionType.Visible:
|
||||
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables, conditionType }, jsonLogic)) {
|
||||
displayResult.push(ActionType.Visible);
|
||||
}
|
||||
return displayResult;
|
||||
case ActionType.Hidden:
|
||||
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables, conditionType }, jsonLogic)) {
|
||||
displayResult.push(ActionType.Hidden);
|
||||
}
|
||||
return displayResult;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const useReactiveLinkageEffect = (
|
||||
linkageRules: any[],
|
||||
variables: VariablesContextType,
|
||||
localVariables: VariableOption[],
|
||||
triggerLinkageUpdate,
|
||||
) => {
|
||||
const app = useApp();
|
||||
const jsonLogic = app.jsonLogic;
|
||||
const [displayResult, setDisplayResult] = useState<string[] | null>(null);
|
||||
const record = useCollectionRecord();
|
||||
useEffect(() => {
|
||||
const runLinkages = async () => {
|
||||
const result: string[] = [];
|
||||
|
||||
for (const rule of linkageRules.filter((r) => !r.disabled)) {
|
||||
for (const action of rule.actions || []) {
|
||||
await linkageAction(
|
||||
{
|
||||
operator: action.operator,
|
||||
condition: rule.condition,
|
||||
variables,
|
||||
localVariables,
|
||||
conditionType: rule.conditionType,
|
||||
displayResult: result,
|
||||
},
|
||||
jsonLogic,
|
||||
);
|
||||
}
|
||||
}
|
||||
setDisplayResult(result);
|
||||
};
|
||||
|
||||
runLinkages();
|
||||
}, [linkageRules, triggerLinkageUpdate, record]);
|
||||
|
||||
return displayResult;
|
||||
};
|
@ -14,12 +14,13 @@ import { getSubAppName } from '@nocobase/sdk';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import { Button, Modal, Result, Spin } from 'antd';
|
||||
import React, { FC } from 'react';
|
||||
import { Navigate, useNavigate } from 'react-router-dom';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { ACLPlugin } from '../acl';
|
||||
import { Application } from '../application';
|
||||
import { Plugin } from '../application/Plugin';
|
||||
import { BlockSchemaComponentPlugin } from '../block-provider';
|
||||
import { CollectionPlugin } from '../collection-manager';
|
||||
import { AppNotFound } from '../common/AppNotFound';
|
||||
import { RemoteDocumentTitlePlugin } from '../document-title';
|
||||
import { PinnedListPlugin } from '../plugin-manager';
|
||||
import { PMPlugin } from '../pm';
|
||||
@ -260,22 +261,6 @@ const AppMaintainingDialog: FC<{ app: Application; error: Error }> = observer(
|
||||
{ displayName: 'AppMaintainingDialog' },
|
||||
);
|
||||
|
||||
export const AppNotFound = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
extra={
|
||||
<Button onClick={() => navigate('/', { replace: true })} type="primary">
|
||||
Back Home
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export class NocoBaseBuildInPlugin extends Plugin {
|
||||
async afterAdd() {
|
||||
this.app.addComponents({
|
||||
|
@ -10,7 +10,7 @@
|
||||
export * from './PluginManagerLink';
|
||||
import { PageHeader } from '@ant-design/pro-layout';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { Button, Col, Divider, Input, List, Modal, Result, Row, Space, Spin, Table, Tabs, TableProps } from 'antd';
|
||||
import { Button, Col, Divider, Input, List, Modal, Row, Space, Spin, Table, TableProps, Tabs } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -19,6 +19,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { css } from '@emotion/css';
|
||||
import { useACLRoleContext } from '../acl/ACLProvider';
|
||||
import { useAPIClient, useRequest } from '../api-client';
|
||||
import { AppNotFound } from '../common/AppNotFound';
|
||||
import { useDocumentTitle } from '../document-title';
|
||||
import { useToken } from '../style';
|
||||
import { PluginCard } from './PluginCard';
|
||||
@ -147,7 +148,7 @@ function BulkEnableButton({ plugins = [] }) {
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
},
|
||||
] as TableProps['columns']
|
||||
] as TableProps<any>['columns']
|
||||
}
|
||||
dataSource={items}
|
||||
/>
|
||||
@ -409,6 +410,6 @@ export const PluginManager = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Result status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
|
||||
<AppNotFound />
|
||||
);
|
||||
};
|
||||
|
@ -9,11 +9,12 @@
|
||||
|
||||
import { PageHeader } from '@ant-design/pro-layout';
|
||||
import { css } from '@emotion/css';
|
||||
import { Layout, Menu, Result } from 'antd';
|
||||
import { Layout, Menu } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { createContext, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Navigate, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { ADMIN_SETTINGS_PATH, PluginSettingsPageType, useApp } from '../application';
|
||||
import { AppNotFound } from '../common/AppNotFound';
|
||||
import { useDocumentTitle } from '../document-title';
|
||||
import { useCompile } from '../schema-component';
|
||||
import { useStyles } from './style';
|
||||
@ -223,13 +224,7 @@ export const AdminSettingsLayout = () => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.pageContent}>
|
||||
{currentSetting ? (
|
||||
<Outlet />
|
||||
) : (
|
||||
<Result status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.pageContent}>{currentSetting ? <Outlet /> : <AppNotFound />}</div>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</div>
|
||||
|
@ -19,7 +19,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Link, Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
ACLRolesCheckProvider,
|
||||
AppNotFound,
|
||||
CurrentAppInfoProvider,
|
||||
DndContext,
|
||||
Icon,
|
||||
@ -47,6 +46,7 @@ import {
|
||||
useLocationNoUpdate,
|
||||
} from '../../../application/CustomRouterContextProvider';
|
||||
import { Plugin } from '../../../application/Plugin';
|
||||
import { AppNotFound } from '../../../common/AppNotFound';
|
||||
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
|
||||
import { menuItemInitializer } from '../../../modules/menu/menuItemInitializer';
|
||||
import { useMenuTranslation } from '../../../schema-component/antd/menu/locale';
|
||||
|
@ -47,6 +47,11 @@ const components = { TreeSelect };
|
||||
const toItems = (routes: NocoBaseDesktopRoute[], { t, compile }) => {
|
||||
const items = [];
|
||||
for (const route of routes) {
|
||||
// filter out the tabs
|
||||
if (route.type === NocoBaseDesktopRouteType.tabs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const item = {
|
||||
label: isVariable(route.title) ? compile(route.title) : t(route.title),
|
||||
value: `${route.id}||${route.type}`,
|
||||
|
@ -10,32 +10,16 @@
|
||||
import { observer } from '@formily/react';
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||
import Action from './Action';
|
||||
import { ComposedAction } from './types';
|
||||
import { Icon } from '../../../icon';
|
||||
|
||||
const WrapperComponent = React.forwardRef(
|
||||
({ component: Component = 'a', icon, onlyIcon, children, ...restProps }: any, ref) => {
|
||||
return (
|
||||
<Component ref={ref} {...restProps}>
|
||||
<Tooltip title={restProps.title}>
|
||||
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
|
||||
</Tooltip>
|
||||
{onlyIcon ? children[1] : children}
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
WrapperComponent.displayName = 'WrapperComponentLink';
|
||||
|
||||
export const ActionLink: ComposedAction = withDynamicSchemaProps(
|
||||
observer((props: any) => {
|
||||
return (
|
||||
<Action
|
||||
{...props}
|
||||
component={props.component || WrapperComponent}
|
||||
component={props.component || 'a'}
|
||||
className={classnames('nb-action-link', props.className)}
|
||||
isLink
|
||||
/>
|
||||
|
@ -10,7 +10,7 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { observer, Schema, useField, useFieldSchema, useForm } from '@formily/react';
|
||||
import { isPortalInBody } from '@nocobase/utils/client';
|
||||
import { App, Button } from 'antd';
|
||||
import { App, Button, Tooltip } from 'antd';
|
||||
import classnames from 'classnames';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -369,6 +369,7 @@ Action.Popover = function ActionPopover(props) {
|
||||
{props.children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
return (
|
||||
<StablePopover
|
||||
{...props}
|
||||
@ -618,6 +619,22 @@ const RenderButtonInner = observer(
|
||||
const actionTitle = typeof rawTitle === 'string' ? t(rawTitle, { ns: NAMESPACE_UI_SCHEMA }) : rawTitle;
|
||||
const { opacity, ...restButtonStyle } = buttonStyle;
|
||||
const linkStyle = isLink && opacity ? { opacity } : undefined;
|
||||
const WrapperComponent = React.forwardRef(
|
||||
({ component: Component = tarComponent || Button, icon, onlyIcon, children, ...restProps }: any, ref) => {
|
||||
return (
|
||||
<Component ref={ref} {...restProps}>
|
||||
{onlyIcon ? (
|
||||
<Tooltip title={restProps.title}>
|
||||
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
|
||||
)}
|
||||
{onlyIcon ? children[1] : children}
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
return (
|
||||
<SortableItem
|
||||
role="button"
|
||||
@ -630,10 +647,11 @@ const RenderButtonInner = observer(
|
||||
disabled={disabled}
|
||||
style={isLink ? restButtonStyle : buttonStyle}
|
||||
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
|
||||
component={tarComponent || Button}
|
||||
component={onlyIcon || tarComponent ? WrapperComponent : tarComponent || Button}
|
||||
className={classnames(componentCls, hashId, className, 'nb-action')}
|
||||
type={type === 'danger' ? undefined : type}
|
||||
title={actionTitle}
|
||||
onlyIcon={onlyIcon}
|
||||
>
|
||||
{!onlyIcon && actionTitle && (
|
||||
<span className={icon ? 'nb-action-title' : null} style={linkStyle}>
|
||||
|
@ -118,8 +118,5 @@ describe('Action.Popover', () => {
|
||||
});
|
||||
|
||||
fireEvent.mouseLeave(btn);
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector('.ant-popover')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -81,6 +81,7 @@ export interface ActionProps extends ButtonProps {
|
||||
* @internal
|
||||
*/
|
||||
addChild?: boolean;
|
||||
onlyIcon?: boolean;
|
||||
}
|
||||
|
||||
export type ComposedAction = React.FC<ActionProps> & {
|
||||
|
@ -41,6 +41,7 @@ export const AssociationFilterItem = withDynamicSchemaProps(
|
||||
handleSearchInput: _handleSearchInput,
|
||||
params,
|
||||
run,
|
||||
dataScopeFilter,
|
||||
valueKey: _valueKey,
|
||||
labelKey: _labelKey,
|
||||
defaultCollapse,
|
||||
@ -94,7 +95,7 @@ export const AssociationFilterItem = withDynamicSchemaProps(
|
||||
if (searchVisible || filter) {
|
||||
run({
|
||||
...params?.[0],
|
||||
filter: undefined,
|
||||
filter: dataScopeFilter,
|
||||
});
|
||||
}
|
||||
setSearchVisible(!searchVisible);
|
||||
|
@ -6,10 +6,9 @@
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import cls from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useSchemaToolbarRender } from '../../../application';
|
||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||
@ -18,6 +17,8 @@ import { useProps } from '../../hooks';
|
||||
import { ErrorFallback } from '../error-fallback';
|
||||
import { useStyles } from './BlockItem.style';
|
||||
import { useGetAriaLabelOfBlockItem } from './hooks/useGetAriaLabelOfBlockItem';
|
||||
import { useCollection } from '../../../data-source';
|
||||
import { BlockLinkageRuleProvider } from '../../../modules/blocks/BlockLinkageRuleProvider';
|
||||
|
||||
export interface BlockItemProps {
|
||||
name?: string;
|
||||
@ -35,8 +36,9 @@ export const BlockItem: React.FC<BlockItemProps> = withDynamicSchemaProps(
|
||||
const { render } = useSchemaToolbarRender(fieldSchema);
|
||||
const { getAriaLabel } = useGetAriaLabelOfBlockItem(props.name);
|
||||
const label = useMemo(() => getAriaLabel(), [getAriaLabel]);
|
||||
|
||||
return (
|
||||
const collection = useCollection();
|
||||
const markdownField = fieldSchema['x-decorator'] === 'FormItem' && fieldSchema['x-block-linkage-rules'];
|
||||
const content = (
|
||||
<SortableItem
|
||||
role="button"
|
||||
aria-label={label}
|
||||
@ -49,6 +51,8 @@ export const BlockItem: React.FC<BlockItemProps> = withDynamicSchemaProps(
|
||||
</ErrorBoundary>
|
||||
</SortableItem>
|
||||
);
|
||||
|
||||
return collection && !markdownField ? content : <BlockLinkageRuleProvider>{content}</BlockLinkageRuleProvider>;
|
||||
},
|
||||
{ displayName: 'BlockItem' },
|
||||
);
|
||||
|
@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useToken } from '../../../style';
|
||||
import { MarkdownReadPretty } from '../markdown';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
import { useCollection } from '../../../data-source';
|
||||
|
||||
export const BlockItemCardContext = createContext({});
|
||||
|
||||
@ -25,6 +26,8 @@ export const BlockItemCard = React.forwardRef<HTMLDivElement, CardProps | any>((
|
||||
const [titleHeight, setTitleHeight] = useState(0);
|
||||
const titleRef = useRef<HTMLDivElement | null>(null);
|
||||
const { t } = useTranslation();
|
||||
const collection = useCollection();
|
||||
console.log();
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (titleRef.current) {
|
||||
|
@ -51,7 +51,7 @@ const InternalFilterAction = React.memo((props: FilterActionProps) => {
|
||||
const form = useMemo<Form>(() => props.form || createForm(), []);
|
||||
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { options, onSubmit, onReset, Container = StablePopover, icon } = useProps(props);
|
||||
const { options, onSubmit, onReset, Container = StablePopover, icon, onlyIcon } = useProps(props);
|
||||
|
||||
const onOpenChange = useCallback((visible: boolean): void => {
|
||||
setVisible(visible);
|
||||
@ -77,7 +77,6 @@ const InternalFilterAction = React.memo((props: FilterActionProps) => {
|
||||
/>
|
||||
);
|
||||
}, [field, fieldSchema, form, onReset, onSubmit, options]);
|
||||
|
||||
return (
|
||||
<FilterActionContext.Provider value={filterActionContextValue}>
|
||||
<Container
|
||||
@ -90,7 +89,7 @@ const InternalFilterAction = React.memo((props: FilterActionProps) => {
|
||||
>
|
||||
{/* Adding a div here can prevent unnecessary re-rendering of Action */}
|
||||
<div>
|
||||
<Action onClick={handleClick} icon={icon} />
|
||||
<Action onClick={handleClick} icon={icon} onlyIcon={onlyIcon} />
|
||||
</div>
|
||||
</Container>
|
||||
</FilterActionContext.Provider>
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/css';
|
||||
import { observer } from '@formily/react';
|
||||
import { sortTree } from '@nocobase/utils/client';
|
||||
import { Cascader, Select, Space } from 'antd';
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -25,7 +26,7 @@ export const FilterItem = observer(
|
||||
const remove = useContext(RemoveConditionContext);
|
||||
const {
|
||||
schema,
|
||||
fields,
|
||||
fields: _fields,
|
||||
operators,
|
||||
dataIndex,
|
||||
operator,
|
||||
@ -35,7 +36,8 @@ export const FilterItem = observer(
|
||||
setValue,
|
||||
collectionField,
|
||||
} = useValues();
|
||||
const [options, setOptions] = useState(compile(fields));
|
||||
const fields = useMemo(() => sortTree(_fields, 'children', 'children', false), [_fields]);
|
||||
const [options, setOptions] = useState(() => compile(fields));
|
||||
const style = useMemo(() => ({ marginBottom: 8 }), []);
|
||||
const fieldNames = useMemo(
|
||||
() => ({
|
||||
@ -84,6 +86,12 @@ export const FilterItem = observer(
|
||||
className={css`
|
||||
width: 160px;
|
||||
`}
|
||||
popupClassName={css`
|
||||
.ant-cascader-menu {
|
||||
height: fit-content;
|
||||
max-height: 50vh;
|
||||
}
|
||||
`}
|
||||
allowClear
|
||||
fieldNames={fieldNames}
|
||||
value={dataIndex}
|
||||
|
@ -535,11 +535,19 @@ export const EditOperator = () => {
|
||||
}
|
||||
|
||||
field.componentProps = componentProps;
|
||||
fieldSchema['x-component-props'] = componentProps;
|
||||
fieldSchema.default = null;
|
||||
field.value = null;
|
||||
field.initialValue = null;
|
||||
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
['x-component-props']: componentProps,
|
||||
['x-filter-operator']: v,
|
||||
// Clear default value when switching operators. Some operators require the default value to be an array,
|
||||
// while others don't. Without clearing it, the filtering API would throw an error
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
|
@ -152,65 +152,65 @@ describe('form.settings', () => {
|
||||
title: 'Edit block title',
|
||||
type: 'modal',
|
||||
},
|
||||
{
|
||||
title: 'Linkage rules',
|
||||
type: 'modal',
|
||||
modalChecker: {
|
||||
modalTitle: 'Linkage rules',
|
||||
contentText: 'Add linkage rule',
|
||||
async customCheck() {
|
||||
// await userEvent.click(screen.getByText('Add linkage rule'));
|
||||
// await waitFor(() => {
|
||||
// expect(screen.queryByText('Add condition')).toBeInTheDocument();
|
||||
// })
|
||||
// await userEvent.click(screen.getByText('Add condition'));
|
||||
// await waitFor(() => {
|
||||
// expect(screen.queryByText('Select field')).toBeInTheDocument();
|
||||
// })
|
||||
// await userEvent.click(screen.getByText('Select field'));
|
||||
// await waitFor(() => {
|
||||
// expect(screen.queryByTitle('Username')).toBeInTheDocument();
|
||||
// })
|
||||
// await userEvent.click(screen.getByTitle('Username'));
|
||||
// const dialog = screen.queryByRole('dialog');
|
||||
// await userEvent.type(dialog.querySelectorAll('.ant-input')[1], '1');
|
||||
// const properties = screen.queryByTestId('select-linkage-property-field');
|
||||
// await userEvent.click(screen.getByText('Select field'));
|
||||
// await waitFor(() => {
|
||||
// expect(properties.querySelector(`[title=Nickname]`)).toBeInTheDocument();
|
||||
// })
|
||||
// await userEvent.click(properties.querySelector(`[title=Nickname]`));
|
||||
// await userEvent.click(screen.getByText('action'));
|
||||
// await waitFor(() => {
|
||||
// expect(screen.queryByText('Hidden')).toBeInTheDocument();
|
||||
// })
|
||||
// await userEvent.click(screen.getByText('Hidden'));
|
||||
},
|
||||
// async afterSubmit() {
|
||||
// await checkSchema({
|
||||
// "x-linkage-rules": [
|
||||
// {
|
||||
// "condition": {
|
||||
// "$and": [
|
||||
// {
|
||||
// "username": {}
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// "actions": [
|
||||
// {
|
||||
// "targetFields": [
|
||||
// "nickname"
|
||||
// ],
|
||||
// "operator": "none"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// })
|
||||
// },
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: 'Linkage rules',
|
||||
// type: 'modal',
|
||||
// modalChecker: {
|
||||
// modalTitle: 'Linkage rules',
|
||||
// contentText: 'Add linkage rule',
|
||||
// async customCheck() {
|
||||
// // await userEvent.click(screen.getByText('Add linkage rule'));
|
||||
// // await waitFor(() => {
|
||||
// // expect(screen.queryByText('Add condition')).toBeInTheDocument();
|
||||
// // })
|
||||
// // await userEvent.click(screen.getByText('Add condition'));
|
||||
// // await waitFor(() => {
|
||||
// // expect(screen.queryByText('Select field')).toBeInTheDocument();
|
||||
// // })
|
||||
// // await userEvent.click(screen.getByText('Select field'));
|
||||
// // await waitFor(() => {
|
||||
// // expect(screen.queryByTitle('Username')).toBeInTheDocument();
|
||||
// // })
|
||||
// // await userEvent.click(screen.getByTitle('Username'));
|
||||
// // const dialog = screen.queryByRole('dialog');
|
||||
// // await userEvent.type(dialog.querySelectorAll('.ant-input')[1], '1');
|
||||
// // const properties = screen.queryByTestId('select-linkage-property-field');
|
||||
// // await userEvent.click(screen.getByText('Select field'));
|
||||
// // await waitFor(() => {
|
||||
// // expect(properties.querySelector(`[title=Nickname]`)).toBeInTheDocument();
|
||||
// // })
|
||||
// // await userEvent.click(properties.querySelector(`[title=Nickname]`));
|
||||
// // await userEvent.click(screen.getByText('action'));
|
||||
// // await waitFor(() => {
|
||||
// // expect(screen.queryByText('Hidden')).toBeInTheDocument();
|
||||
// // })
|
||||
// // await userEvent.click(screen.getByText('Hidden'));
|
||||
// },
|
||||
// // async afterSubmit() {
|
||||
// // await checkSchema({
|
||||
// // "x-linkage-rules": [
|
||||
// // {
|
||||
// // "condition": {
|
||||
// // "$and": [
|
||||
// // {
|
||||
// // "username": {}
|
||||
// // }
|
||||
// // ]
|
||||
// // },
|
||||
// // "actions": [
|
||||
// // {
|
||||
// // "targetFields": [
|
||||
// // "nickname"
|
||||
// // ],
|
||||
// // "operator": "none"
|
||||
// // }
|
||||
// // ]
|
||||
// // }
|
||||
// // ]
|
||||
// // })
|
||||
// // },
|
||||
// },
|
||||
// },
|
||||
{
|
||||
title: 'Form data templates',
|
||||
type: 'modal',
|
||||
|
@ -46,7 +46,7 @@ interface Props {
|
||||
|
||||
export const DynamicComponent = (props: Props) => {
|
||||
const { setScopes, nullable, constantAbel, changeOnSelect, readOnly = false } = props;
|
||||
const { disabled } = useContext(FilterContext) || {};
|
||||
const { disabled, returnScope } = useContext(FilterContext) || {};
|
||||
const record = useCollectionRecordData();
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
@ -68,6 +68,7 @@ export const DynamicComponent = (props: Props) => {
|
||||
localVariables,
|
||||
getAllCollectionsInheritChain,
|
||||
})}
|
||||
returnScope={returnScope}
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
@ -26,7 +26,7 @@ export const LinkageFilter: any = withDynamicSchemaProps(
|
||||
const { useDataSource = useDef } = props;
|
||||
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { dynamicComponent, className, collectionName } = useProps(props);
|
||||
const { dynamicComponent, className, collectionName, returnScope } = useProps(props);
|
||||
const [scopes, setScopes] = useState([]);
|
||||
|
||||
const field = useField<ObjectFieldModel>();
|
||||
@ -54,6 +54,7 @@ export const LinkageFilter: any = withDynamicSchemaProps(
|
||||
collectionName,
|
||||
scopes,
|
||||
setScopes,
|
||||
returnScope,
|
||||
}}
|
||||
>
|
||||
<FilterGroup {...props} bordered={false} />
|
||||
|
@ -20,6 +20,7 @@ export interface FilterContextProps {
|
||||
collectionName?: string;
|
||||
scopes?: any[];
|
||||
setScopes?: any;
|
||||
returnScope?: any;
|
||||
}
|
||||
|
||||
export const RemoveConditionContext = createContext(null);
|
||||
|
@ -44,7 +44,7 @@ const findOption = (str, options) => {
|
||||
|
||||
// 进入下一层 children 查找
|
||||
if (Array.isArray(option.children) || option.isLeaf === false) {
|
||||
currentOptions = option.children || option.field.children;
|
||||
currentOptions = option.children || option?.field?.children || [];
|
||||
} else {
|
||||
return option; // 没有 children 直接返回
|
||||
}
|
||||
|
@ -30,10 +30,10 @@ import {
|
||||
useNavigateNoUpdate,
|
||||
useRouterBasename,
|
||||
} from '../../../application/CustomRouterContextProvider';
|
||||
import { AppNotFound } from '../../../common/AppNotFound';
|
||||
import { useDocumentTitle } from '../../../document-title';
|
||||
import { useGlobalTheme } from '../../../global-theme';
|
||||
import { Icon } from '../../../icon';
|
||||
import { AppNotFound } from '../../../nocobase-buildin-plugin';
|
||||
import {
|
||||
NocoBaseDesktopRouteType,
|
||||
NocoBaseRouteContext,
|
||||
@ -234,7 +234,10 @@ const InternalPageContent = (props: PageContentProps) => {
|
||||
// Create a clean search string or empty string if only '?' remains
|
||||
const searchString = searchParams.toString() ? `?${searchParams.toString()}` : '';
|
||||
|
||||
navigate(location.pathname.replace(activeKey, oldTab.schemaUid) + searchString);
|
||||
const newPath =
|
||||
location.pathname + (location.pathname.endsWith('/') ? `tabs/${oldTab.schemaUid}` : `/tabs/${oldTab.schemaUid}`);
|
||||
navigate(newPath + searchString);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,11 @@
|
||||
import { ISchema, Schema } from '@formily/json-schema';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { Result } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { FC, default as React, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Location, useLocation } from 'react-router-dom';
|
||||
import { useAPIClient } from '../../../api-client';
|
||||
import { AppNotFound } from '../../../common/AppNotFound';
|
||||
import { DataBlockProvider } from '../../../data-source/data-block/DataBlockProvider';
|
||||
import { BlockRequestContextProvider } from '../../../data-source/data-block/DataBlockRequestProvider';
|
||||
import { useKeepAlive } from '../../../route-switch/antd/admin-layout/KeepAlive';
|
||||
@ -475,10 +474,7 @@ function get404Schema() {
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': function Com() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Result status="404" title="404" subTitle={t('Sorry, the page you visited does not exist.')} />
|
||||
);
|
||||
return <AppNotFound />;
|
||||
},
|
||||
'x-uid': uid(),
|
||||
'x-async': false,
|
||||
|
@ -41,7 +41,7 @@ export const usePopupSettings = () => {
|
||||
const isOldMobileMode = pathname?.includes('/mobile/') || hash?.includes('/mobile/');
|
||||
const isNewMobileMode = pathname?.includes('/m/');
|
||||
const isPCMode = pathname?.includes('/admin/');
|
||||
const isMobileTemplateSettingsPage = pathname?.includes('/m/block-templates/');
|
||||
const isMobileTemplateSettingsPage = pathname?.includes('/m/block-templates/inherited');
|
||||
|
||||
return (
|
||||
(isPCMode || isNewMobileMode) &&
|
||||
|
@ -194,7 +194,8 @@ const useTableColumns = (
|
||||
return css`
|
||||
.nb-action-link {
|
||||
margin: -${token.paddingContentVerticalLG}px -${token.marginSM}px;
|
||||
padding: ${token.paddingContentVerticalLG}px ${token.paddingSM + 4}px;
|
||||
padding: ${token.paddingContentVerticalLG}px ${token.paddingContentVerticalLG}px ${token.paddingSM}px
|
||||
${token.paddingSM}px;
|
||||
}
|
||||
`;
|
||||
}, [token.paddingContentVerticalLG, token.marginSM, token.margin]);
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
SchemaSettingsRemove,
|
||||
SchemaSettingsSelectItem,
|
||||
SchemaSettingsSwitchItem,
|
||||
SchemaSettingsLinkageRules,
|
||||
} from '../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
@ -127,10 +128,12 @@ export const TableBlockDesigner = () => {
|
||||
[dn, field.decoratorProps, fieldSchema, service],
|
||||
);
|
||||
const api = useAPIClient();
|
||||
|
||||
return (
|
||||
<GeneralSchemaDesigner template={template} title={title || name}>
|
||||
<SchemaSettingsBlockTitleItem />
|
||||
<SchemaSettingsBlockHeightItem />
|
||||
<SchemaSettingsLinkageRules category="block" title={t('Block Linkage rules')} />
|
||||
{collection?.tree && collectionField?.collectionName === collectionField?.target && (
|
||||
<SchemaSettingsSwitchItem
|
||||
title={t('Tree table')}
|
||||
|
@ -33,6 +33,10 @@ describe('Table.settings', () => {
|
||||
title: 'Set block height',
|
||||
type: 'modal',
|
||||
},
|
||||
{
|
||||
title: 'Block linkage rules',
|
||||
type: 'modal',
|
||||
},
|
||||
{
|
||||
title: 'Enable drag and drop sorting',
|
||||
type: 'switch',
|
||||
|
@ -357,7 +357,7 @@ function FinallyButton({
|
||||
}}
|
||||
style={{
|
||||
...props?.style,
|
||||
display: !designable && field?.data?.hidden && 'none',
|
||||
display: !designable && field?.data?.hidden ? 'none' : 'inline-block',
|
||||
opacity: designable && field?.data?.hidden && 0.1,
|
||||
...buttonStyle,
|
||||
}}
|
||||
|
@ -1,29 +0,0 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { Space, Tooltip } from 'antd';
|
||||
|
||||
export const DeprecatedTemplateTitle = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Space>
|
||||
{t('Deprecated')}
|
||||
<Tooltip
|
||||
title={t('The following old template features have been deprecated and will be removed in next version.')}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export const DeprecatedTemplateTitleElement = <DeprecatedTemplateTitle />;
|
@ -39,7 +39,6 @@ export const RecordAssociationBlockInitializer = () => {
|
||||
} else {
|
||||
insert(
|
||||
createTableBlockUISchema({
|
||||
rowKey: collection.filterTargetKey,
|
||||
dataSource: collection.dataSource,
|
||||
association: association,
|
||||
}),
|
||||
@ -62,7 +61,6 @@ export function useCreateAssociationTableBlock() {
|
||||
|
||||
insert(
|
||||
createTableBlockUISchema({
|
||||
rowKey: collection.filterTargetKey,
|
||||
dataSource: collection.dataSource,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
}),
|
||||
|
@ -36,7 +36,6 @@ import { isAssocField } from '../filter-provider/utils';
|
||||
import { useActionContext, useCompile, useDesignable } from '../schema-component';
|
||||
import { useSchemaTemplateManager } from '../schema-templates';
|
||||
import { useBlockTemplateContext } from '../schema-templates/BlockTemplateProvider';
|
||||
import { DeprecatedTemplateTitleElement } from './components/DeprecatedTemplateTitle';
|
||||
|
||||
export const itemsMerge = (items1) => {
|
||||
return items1;
|
||||
@ -838,66 +837,71 @@ export const useRecordCollectionDataSourceItems = (
|
||||
.filter((template) => {
|
||||
return ['FormItem', 'ReadPrettyFormItem'].includes(componentName) || template.resourceName === resourceName;
|
||||
});
|
||||
const extralCollectionMenuItems = Array.from(initializerMenusGenerators.values())
|
||||
const inheritedTemplatesMenuItems = Array.from(initializerMenusGenerators.values())
|
||||
.map((generator) => generator({ collection, componentName }))
|
||||
.filter(Boolean)
|
||||
.flat();
|
||||
if ((!templates.length && !extralCollectionMenuItems.length) || isInTemplateSettingPage()) {
|
||||
if ((!templates.length && !inheritedTemplatesMenuItems.length) || isInTemplateSettingPage()) {
|
||||
return [];
|
||||
}
|
||||
const index = 0;
|
||||
const deprecatedTemplatesMenuItems = [];
|
||||
const allTemplatesMenuItems: SchemaInitializerItemType[] = [
|
||||
{
|
||||
type: 'divider',
|
||||
name: 'divider',
|
||||
},
|
||||
];
|
||||
if (templates.length) {
|
||||
deprecatedTemplatesMenuItems.push(
|
||||
allTemplatesMenuItems.push(
|
||||
{
|
||||
type: 'divider',
|
||||
key: `${collectionName || componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
title: t('Duplicate template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'copy',
|
||||
name: collection.name,
|
||||
template,
|
||||
item,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: DeprecatedTemplateTitleElement,
|
||||
children: [
|
||||
{
|
||||
key: `${collectionName || componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
title: t('Duplicate template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'copy',
|
||||
name: collection.name,
|
||||
template,
|
||||
item,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: `${collectionName || componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
title: t('Reference template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'reference',
|
||||
name: collection.name,
|
||||
template,
|
||||
item,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
key: `${collectionName || componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
title: t('Reference template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'reference',
|
||||
name: collection.name,
|
||||
template,
|
||||
item,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
if (inheritedTemplatesMenuItems.length) {
|
||||
allTemplatesMenuItems.push({
|
||||
type: 'subMenu',
|
||||
name: 'inherited_templates',
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_inherited`,
|
||||
title: t('Inherited template'),
|
||||
children: inheritedTemplatesMenuItems,
|
||||
});
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: `${collectionName || componentName}_table_blank`,
|
||||
@ -906,8 +910,7 @@ export const useRecordCollectionDataSourceItems = (
|
||||
title: t('Blank block'),
|
||||
item,
|
||||
},
|
||||
...extralCollectionMenuItems,
|
||||
...deprecatedTemplatesMenuItems,
|
||||
...allTemplatesMenuItems,
|
||||
];
|
||||
};
|
||||
|
||||
@ -1533,13 +1536,13 @@ const getChildren = ({
|
||||
|
||||
return componentName && template.componentName === componentName;
|
||||
});
|
||||
const extralCollectionMenuItems = Array.from(initializerMenusGenerators.values())
|
||||
const inheritedTemplatesMenuItems = Array.from(initializerMenusGenerators.values())
|
||||
.map((generator) => {
|
||||
return generator({ item, index, componentName, association });
|
||||
})
|
||||
.filter(Boolean)
|
||||
.flat();
|
||||
if ((!templates.length && !extralCollectionMenuItems.length) || isInTemplateSettingPage()) {
|
||||
if ((!templates.length && !inheritedTemplatesMenuItems.length) || isInTemplateSettingPage()) {
|
||||
return {
|
||||
type: 'item',
|
||||
name: item.name,
|
||||
@ -1547,66 +1550,72 @@ const getChildren = ({
|
||||
dataSource,
|
||||
};
|
||||
}
|
||||
const deprecatedTemplatesMenuItems = [];
|
||||
const allTemplatesMenuItems: SchemaInitializerItemType[] = [
|
||||
{
|
||||
type: 'divider',
|
||||
name: 'divider',
|
||||
},
|
||||
];
|
||||
if (templates.length) {
|
||||
deprecatedTemplatesMenuItems.push(
|
||||
allTemplatesMenuItems.push(
|
||||
{
|
||||
type: 'divider',
|
||||
key: `${componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
dataSource,
|
||||
title: t('Duplicate template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'copy',
|
||||
name: item.name,
|
||||
template,
|
||||
dataSource,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: DeprecatedTemplateTitleElement,
|
||||
children: [
|
||||
{
|
||||
key: `${componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
key: `${componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
dataSource,
|
||||
title: t('Reference template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'reference',
|
||||
name: item.name,
|
||||
template,
|
||||
dataSource,
|
||||
title: t('Duplicate template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'copy',
|
||||
name: item.name,
|
||||
template,
|
||||
dataSource,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: `${componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
dataSource,
|
||||
title: t('Reference template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'reference',
|
||||
name: item.name,
|
||||
template,
|
||||
dataSource,
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
title: templateName || t('Untitled'),
|
||||
};
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
if (inheritedTemplatesMenuItems.length) {
|
||||
allTemplatesMenuItems.push({
|
||||
type: 'subMenu',
|
||||
name: 'inherited_templates',
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_inherited`,
|
||||
dataSource,
|
||||
title: t('Inherited template'),
|
||||
children: inheritedTemplatesMenuItems,
|
||||
});
|
||||
}
|
||||
return {
|
||||
key: `${componentName}_table_subMenu_${index}`,
|
||||
type: 'subMenu',
|
||||
@ -1620,8 +1629,7 @@ const getChildren = ({
|
||||
dataSource,
|
||||
title: t('Blank block'),
|
||||
},
|
||||
...extralCollectionMenuItems,
|
||||
...deprecatedTemplatesMenuItems,
|
||||
...allTemplatesMenuItems,
|
||||
],
|
||||
};
|
||||
});
|
||||
@ -1804,11 +1812,11 @@ function useAssociationFields({
|
||||
return template.componentName === componentName;
|
||||
});
|
||||
const keyPrefix = `associationFiled_table_subMenu`;
|
||||
const extralCollectionMenuItems = Array.from(initializerMenusGenerators.values())
|
||||
const inheritedTemplatesMenuItems = Array.from(initializerMenusGenerators.values())
|
||||
.map((generator) => generator({ collection, index, field, componentName, keyPrefix, name }))
|
||||
.filter(Boolean)
|
||||
.flat();
|
||||
if ((!templates.length && !extralCollectionMenuItems.length) || isInTemplateSettingPage()) {
|
||||
if ((!templates.length && !inheritedTemplatesMenuItems.length) || isInTemplateSettingPage()) {
|
||||
return {
|
||||
type: 'item',
|
||||
name: `${field.collectionName}.${field.name}`,
|
||||
@ -1818,70 +1826,77 @@ function useAssociationFields({
|
||||
associationField: field,
|
||||
};
|
||||
}
|
||||
const deprecatedTemplatesMenuItems = [];
|
||||
const allTemplatesMenuItems: SchemaInitializerItemType[] = [
|
||||
{
|
||||
type: 'divider',
|
||||
name: 'divider',
|
||||
},
|
||||
];
|
||||
if (templates.length) {
|
||||
deprecatedTemplatesMenuItems.push(
|
||||
allTemplatesMenuItems.push(
|
||||
{
|
||||
type: 'divider',
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
dataSource,
|
||||
title: t('Duplicate template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'copy',
|
||||
name: `${field.collectionName}.${field.name}`,
|
||||
collectionName: field.target,
|
||||
template,
|
||||
dataSource,
|
||||
title: templateName || t('Untitled'),
|
||||
associationField: field,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: DeprecatedTemplateTitleElement,
|
||||
children: [
|
||||
{
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_copy`,
|
||||
type: 'subMenu',
|
||||
name: 'copy',
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
dataSource,
|
||||
title: t('Reference template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'reference',
|
||||
name: `${field.collectionName}.${field.name}`,
|
||||
collectionName: field.target,
|
||||
template,
|
||||
dataSource,
|
||||
title: t('Duplicate template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'copy',
|
||||
name: `${field.collectionName}.${field.name}`,
|
||||
collectionName: field.target,
|
||||
template,
|
||||
dataSource,
|
||||
title: templateName || t('Untitled'),
|
||||
associationField: field,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_ref`,
|
||||
type: 'subMenu',
|
||||
name: 'ref',
|
||||
dataSource,
|
||||
title: t('Reference template'),
|
||||
children: templates.map((template) => {
|
||||
const templateName = [
|
||||
componentNamePrefix + 'FormItem',
|
||||
componentNamePrefix + 'ReadPrettyFormItem',
|
||||
].includes(template?.componentName)
|
||||
? `${template?.name} ${t('(Fields only)')}`
|
||||
: template?.name;
|
||||
return {
|
||||
type: 'item',
|
||||
mode: 'reference',
|
||||
name: `${field.collectionName}.${field.name}`,
|
||||
collectionName: field.target,
|
||||
template,
|
||||
dataSource,
|
||||
title: templateName || t('Untitled'),
|
||||
associationField: field,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
title: templateName || t('Untitled'),
|
||||
associationField: field,
|
||||
};
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
if (inheritedTemplatesMenuItems.length) {
|
||||
allTemplatesMenuItems.push({
|
||||
type: 'subMenu',
|
||||
name: 'inherited_templates',
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}_inherited`,
|
||||
dataSource,
|
||||
title: t('Inherited template'),
|
||||
children: inheritedTemplatesMenuItems,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
key: `associationFiled_${componentName}_table_subMenu_${index}`,
|
||||
type: 'subMenu',
|
||||
@ -1897,8 +1912,7 @@ function useAssociationFields({
|
||||
title: t('Blank block'),
|
||||
associationField: field,
|
||||
},
|
||||
...extralCollectionMenuItems,
|
||||
...deprecatedTemplatesMenuItems,
|
||||
...allTemplatesMenuItems,
|
||||
],
|
||||
};
|
||||
});
|
||||
@ -1917,7 +1931,7 @@ function useAssociationFields({
|
||||
]);
|
||||
}
|
||||
|
||||
const isInTemplateSettingPage = () => window.location.pathname.includes('/block-templates/');
|
||||
const isInTemplateSettingPage = () => window.location.pathname.includes('/block-templates/inherited');
|
||||
|
||||
const initializerMenusGenerators = new Map<
|
||||
string,
|
||||
|
@ -263,7 +263,7 @@ const InternalSchemaToolbar: FC<SchemaToolbarProps> = React.memo((props) => {
|
||||
if (typeof title === 'string') return [compile(title)];
|
||||
if (Array.isArray(title)) {
|
||||
if (title.length === 1 && fieldSchema['x-template-title']) {
|
||||
templateTitleLabel.current = t('Template');
|
||||
templateTitleLabel.current = t('Inherited template');
|
||||
return compile([title[0], fieldSchema['x-template-title']]);
|
||||
}
|
||||
return compile(title);
|
||||
|
@ -15,8 +15,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { withDynamicSchemaProps } from '../../hoc/withDynamicSchemaProps';
|
||||
import { useProps } from '../../schema-component/hooks/useProps';
|
||||
import { FormButtonLinkageRuleAction, FormFieldLinkageRuleAction } from './LinkageRuleAction';
|
||||
import { FieldStyleLinkageRuleAction } from './FieldStyleLinkageRuleAction';
|
||||
import { FieldStyleLinkageRuleAction } from './components/FieldStyleLinkageRuleAction';
|
||||
import { BlockLinkageRuleAction } from './components/BlockLinkageRuleAction';
|
||||
import { RemoveActionContext } from './context';
|
||||
|
||||
export const LinkageRuleActions = observer(
|
||||
(props: any): any => {
|
||||
const { linkageOptions, category, elementType } = props;
|
||||
@ -28,6 +30,7 @@ export const LinkageRuleActions = observer(
|
||||
button: FormButtonLinkageRuleAction,
|
||||
field: FormFieldLinkageRuleAction,
|
||||
style: FieldStyleLinkageRuleAction,
|
||||
block: BlockLinkageRuleAction,
|
||||
};
|
||||
return field?.value?.map((item, index) => {
|
||||
return (
|
||||
@ -41,7 +44,7 @@ export const LinkageRuleActions = observer(
|
||||
);
|
||||
|
||||
export interface LinkageRuleActionGroupProps {
|
||||
type: 'button' | 'field' | 'style';
|
||||
type: 'button' | 'field' | 'style' | 'block';
|
||||
linkageOptions: any;
|
||||
collectionName: string;
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ function getFieldValuesInCondition({ linkageRules, formValues }) {
|
||||
});
|
||||
}
|
||||
|
||||
function getVariableValuesInCondition({
|
||||
export function getVariableValuesInCondition({
|
||||
linkageRules,
|
||||
localVariables,
|
||||
}: {
|
||||
@ -166,7 +166,7 @@ function getVariableValuesInCondition({
|
||||
});
|
||||
}
|
||||
|
||||
function getVariableValuesInExpression({ action, localVariables }) {
|
||||
export function getVariableValuesInExpression({ action, localVariables }) {
|
||||
const actionValue = action.value;
|
||||
const mode = actionValue?.mode;
|
||||
const value = actionValue?.value || actionValue?.result;
|
||||
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/css';
|
||||
import { TreeSelect } from '@formily/antd-v5';
|
||||
import { observer } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { Select, Space } from 'antd';
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCompile } from '../../..';
|
||||
import { DynamicComponent } from '../DynamicComponent';
|
||||
import { RemoveActionContext } from '../context';
|
||||
import { ActionType } from '../type';
|
||||
import { useValues } from '../useValues';
|
||||
|
||||
export const BlockLinkageRuleAction = observer(
|
||||
(props: any) => {
|
||||
const { value, options } = props;
|
||||
const { t } = useTranslation();
|
||||
const compile = useCompile();
|
||||
const [editFlag, setEditFlag] = useState(false);
|
||||
const remove = useContext(RemoveActionContext);
|
||||
const { schema, operator, setOperator, setValue } = useValues(options);
|
||||
const operators = useMemo(
|
||||
() =>
|
||||
compile([
|
||||
{ label: t('Visible'), value: ActionType.Visible, schema: {} },
|
||||
{ label: t('Hidden'), value: ActionType.Hidden, schema: {} },
|
||||
]),
|
||||
[compile, t],
|
||||
);
|
||||
|
||||
const onChange = useCallback(
|
||||
(value) => {
|
||||
const flag = [ActionType.Value].includes(value);
|
||||
setEditFlag(flag);
|
||||
setOperator(value);
|
||||
},
|
||||
[setOperator],
|
||||
);
|
||||
|
||||
const onChangeValue = useCallback(
|
||||
(value) => {
|
||||
setValue(value);
|
||||
},
|
||||
[setValue],
|
||||
);
|
||||
|
||||
const closeStyle = useMemo(() => ({ color: '#bfbfbf' }), []);
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Space>
|
||||
<Select
|
||||
data-testid="select-linkage-properties"
|
||||
popupMatchSelectWidth={false}
|
||||
value={operator}
|
||||
options={operators}
|
||||
onChange={onChange}
|
||||
placeholder={t('action')}
|
||||
/>
|
||||
{editFlag &&
|
||||
React.createElement(DynamicComponent, {
|
||||
value,
|
||||
schema,
|
||||
onChange: onChangeValue,
|
||||
})}
|
||||
{!props.disabled && (
|
||||
<a role="button" aria-label="icon-close">
|
||||
<CloseCircleOutlined onClick={remove} style={closeStyle} />
|
||||
</a>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ displayName: 'FormButtonLinkageRuleAction' },
|
||||
);
|
@ -10,15 +10,14 @@
|
||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/css';
|
||||
import { observer } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { Select, Space } from 'antd';
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCompile } from '../..';
|
||||
import { ValueDynamicComponent } from './ValueDynamicComponent';
|
||||
import { RemoveActionContext } from './context';
|
||||
import { ActionType } from './type';
|
||||
import { useValues } from './useValues';
|
||||
import { useCompile } from '../../..';
|
||||
import { ValueDynamicComponent } from '../ValueDynamicComponent';
|
||||
import { RemoveActionContext } from '../context';
|
||||
import { ActionType } from '../type';
|
||||
import { useValues } from '../useValues';
|
||||
|
||||
const colorSchema = {
|
||||
type: 'string',
|
@ -25,7 +25,7 @@ import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
|
||||
import { EnableLinkage } from './components/EnableLinkage';
|
||||
import { ArrayCollapse } from './components/LinkageHeader';
|
||||
import { useFlag } from '../../flag-provider';
|
||||
|
||||
import { LinkageRuleCategory } from './type';
|
||||
export interface Props {
|
||||
dynamicComponent: any;
|
||||
}
|
||||
@ -93,8 +93,18 @@ const transformDefaultValue = (values, variableKey) => {
|
||||
export const FormLinkageRules = withDynamicSchemaProps(
|
||||
observer((props: Props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { options, defaultValues, collectionName, form, variables, localVariables, record, dynamicComponent } =
|
||||
useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const {
|
||||
options,
|
||||
defaultValues,
|
||||
collectionName,
|
||||
form,
|
||||
variables,
|
||||
localVariables,
|
||||
record,
|
||||
dynamicComponent,
|
||||
category,
|
||||
returnScope,
|
||||
} = useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { name } = useCollection_deprecated();
|
||||
const { getAllCollectionsInheritChain } = useCollectionManager_deprecated();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
@ -102,6 +112,16 @@ export const FormLinkageRules = withDynamicSchemaProps(
|
||||
const components = useMemo(() => ({ ArrayCollapse }), []);
|
||||
const { isInSubTable, isInSubForm } = useFlag();
|
||||
const variableKey = getActiveContextName(isInSubTable || isInSubForm, shouldDisplayCurrentForm);
|
||||
const returnTargetScope =
|
||||
returnScope ??
|
||||
((options) =>
|
||||
options.filter((v) => {
|
||||
console.log(category);
|
||||
if (category === LinkageRuleCategory.block) {
|
||||
return !['$nForm', '$nRecord'].includes(v.value);
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
const schema = useMemo(
|
||||
() => ({
|
||||
type: 'object',
|
||||
@ -197,6 +217,9 @@ export const FormLinkageRules = withDynamicSchemaProps(
|
||||
conditionAdvanced: {
|
||||
'x-component': 'LinkageFilter',
|
||||
'x-visible': '{{$deps[0] === "advanced"}}',
|
||||
'x-component-props': {
|
||||
returnScope: returnTargetScope,
|
||||
},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['.conditionType', '.condition'],
|
||||
|
@ -33,10 +33,12 @@ export enum LinkageRuleCategory {
|
||||
default = 'default',
|
||||
style = 'style',
|
||||
button = 'button',
|
||||
block = 'block',
|
||||
}
|
||||
|
||||
export const LinkageRuleDataKeyMap: Record<`${LinkageRuleCategory}`, string> = {
|
||||
[LinkageRuleCategory.style]: 'x-linkage-style-rules',
|
||||
[LinkageRuleCategory.default]: 'x-linkage-rules',
|
||||
[LinkageRuleCategory.button]: 'x-linkage-rules',
|
||||
[LinkageRuleCategory.block]: 'x-block-linkage-rules',
|
||||
};
|
||||
|
@ -342,13 +342,13 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
|
||||
}
|
||||
return (
|
||||
<SchemaSettingsItem
|
||||
title="Save as block template"
|
||||
title={t('Save as reference template')}
|
||||
onClick={async () => {
|
||||
setVisible(false);
|
||||
const collection = collectionName && cm?.getCollection(collectionName);
|
||||
const gridSchema = findGridSchema(fieldSchema);
|
||||
const values = await FormDialog(
|
||||
t('Save as template'),
|
||||
t('Save as reference template'),
|
||||
() => {
|
||||
const componentTitle = {
|
||||
FormItem: t('Form'),
|
||||
@ -414,7 +414,7 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
|
||||
dn.refresh();
|
||||
}}
|
||||
>
|
||||
{t('Save as block template')}
|
||||
{t('Save as reference template')}
|
||||
</SchemaSettingsItem>
|
||||
);
|
||||
};
|
||||
@ -1103,7 +1103,7 @@ export const SchemaSettingsDefaultSortingRules = function DefaultSortingRules(pr
|
||||
};
|
||||
|
||||
export const SchemaSettingsLinkageRules = function LinkageRules(props) {
|
||||
const { collectionName, readPretty, Component, afterSubmit } = props;
|
||||
const { collectionName, readPretty, Component, afterSubmit, title: settingTitle, returnScope } = props;
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { form } = useFormBlockContext();
|
||||
const { dn } = useDesignable();
|
||||
@ -1129,7 +1129,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
|
||||
const getRules = useCallback(() => {
|
||||
return gridSchema?.[dataKey] || fieldSchema?.[dataKey] || [];
|
||||
}, [gridSchema, fieldSchema, dataKey]);
|
||||
const title = titleMap[category] || t('Linkage rules');
|
||||
const title = settingTitle || titleMap[category] || t('Linkage rules');
|
||||
const flagVales = useFlag();
|
||||
const schema = useMemo<ISchema>(
|
||||
() => ({
|
||||
@ -1151,6 +1151,7 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
|
||||
localVariables,
|
||||
record,
|
||||
formBlockType,
|
||||
returnScope,
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -1166,19 +1167,23 @@ export const SchemaSettingsLinkageRules = function LinkageRules(props) {
|
||||
rules.push(_.omit(_.pickBy(rule, _.identity), ['conditionBasic', 'conditionAdvanced']));
|
||||
}
|
||||
const templateId = gridSchema['x-component'] === 'BlockTemplate' && gridSchema['x-component-props']?.templateId;
|
||||
const uid = (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid'];
|
||||
const uid =
|
||||
category !== LinkageRuleCategory.block
|
||||
? (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid']
|
||||
: fieldSchema['x-uid'];
|
||||
const schema = {
|
||||
['x-uid']: uid,
|
||||
};
|
||||
gridSchema[dataKey] = rules;
|
||||
schema[dataKey] = rules;
|
||||
fieldSchema[dataKey] = rules;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
dn.refresh();
|
||||
afterSubmit?.();
|
||||
},
|
||||
[dn, getTemplateById, gridSchema, dataKey, afterSubmit],
|
||||
[dn, getTemplateById, gridSchema, dataKey, afterSubmit, category],
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -40,7 +40,7 @@ export function SchemaSettingsTemplate(props) {
|
||||
if (template) {
|
||||
return (
|
||||
<SchemaSettingsItem
|
||||
title="Convert reference to duplicate"
|
||||
title={t('Convert reference to duplicate')}
|
||||
onClick={async () => {
|
||||
const schema = await copyTemplateSchema(template);
|
||||
const removed = tdn.removeWithoutEmit();
|
||||
@ -59,12 +59,12 @@ export function SchemaSettingsTemplate(props) {
|
||||
}
|
||||
return (
|
||||
<SchemaSettingsItem
|
||||
title="Save as template"
|
||||
title={t('Save as reference template')}
|
||||
onClick={async () => {
|
||||
setVisible(false);
|
||||
const collection = collectionName && getCollection(collectionName);
|
||||
const values = await FormDialog(
|
||||
t('Save as template'),
|
||||
t('Save as reference template'),
|
||||
() => {
|
||||
return (
|
||||
<FormLayout layout={'vertical'}>
|
||||
@ -115,7 +115,7 @@ export function SchemaSettingsTemplate(props) {
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('Save as template')}
|
||||
{t('Save as reference template')}
|
||||
</SchemaSettingsItem>
|
||||
);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ test('getURLSearchParamsChildren should return an array of options with expected
|
||||
|
||||
const result = getURLSearchParamsChildren(queryParams);
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toMatchObject([
|
||||
{
|
||||
label: 'param1',
|
||||
value: 'param1',
|
||||
|
@ -16,6 +16,7 @@ import { useLocationSearch } from '../../../application/CustomRouterContextProvi
|
||||
import { useFlag } from '../../../flag-provider/hooks/useFlag';
|
||||
import { Option } from '../type';
|
||||
import { getLabelWithTooltip } from './useBaseVariable';
|
||||
import { string } from '../../../collection-manager/interfaces/properties/operators';
|
||||
|
||||
export const getURLSearchParams = (search: string) => {
|
||||
if (search.startsWith('?')) {
|
||||
@ -32,6 +33,7 @@ export const getURLSearchParamsChildren = (queryParams: Record<string, any>): Op
|
||||
value: key,
|
||||
key,
|
||||
isLeaf: true,
|
||||
operators: string,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -32,3 +32,4 @@ export * from './isPatternDisabled';
|
||||
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
|
||||
export * from './VariableInput';
|
||||
export { replaceVariables } from './LinkageRules/bindLinkageRulesToFiled';
|
||||
export * from './LinkageRules/type';
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { APIClient } from '../api-client';
|
||||
import { Application } from '../application';
|
||||
|
||||
class MockAPIClient extends APIClient {
|
||||
mockAdapter() {
|
||||
@ -18,6 +19,9 @@ class MockAPIClient extends APIClient {
|
||||
|
||||
export const mockAPIClient = () => {
|
||||
const apiClient = new MockAPIClient();
|
||||
const app = new Application();
|
||||
apiClient.app = app;
|
||||
|
||||
const mockRequest = apiClient.mockAdapter();
|
||||
return { apiClient, mockRequest };
|
||||
};
|
||||
|
@ -129,7 +129,7 @@ mockRequest.onGet('/users/0/belongsToField:get').reply(() => {
|
||||
},
|
||||
];
|
||||
});
|
||||
mockRequest.onGet('/users/0/hasManyField:list?pageSize=9999').reply(() => {
|
||||
mockRequest.onGet('/users/0/hasManyField:list?paginate=false').reply(() => {
|
||||
return [
|
||||
200,
|
||||
{
|
||||
@ -142,7 +142,7 @@ mockRequest.onGet('/users/0/hasManyField:list?pageSize=9999').reply(() => {
|
||||
},
|
||||
];
|
||||
});
|
||||
mockRequest.onGet('/users/0/belongsToManyField:list?pageSize=9999').reply(() => {
|
||||
mockRequest.onGet('/users/0/belongsToManyField:list?paginate=false').reply(() => {
|
||||
return [
|
||||
200,
|
||||
{
|
||||
@ -156,7 +156,7 @@ mockRequest.onGet('/users/0/belongsToManyField:list?pageSize=9999').reply(() =>
|
||||
},
|
||||
];
|
||||
});
|
||||
mockRequest.onGet('/test/0/hasManyField:list?pageSize=9999').reply(() => {
|
||||
mockRequest.onGet('/test/0/hasManyField:list?paginate=false').reply(() => {
|
||||
return [
|
||||
200,
|
||||
{
|
||||
|
@ -20,7 +20,7 @@ export const WaitApp = async () => {
|
||||
|
||||
const loadError = screen.queryByText('App Error');
|
||||
if (loadError) {
|
||||
expectNoTsError(screen.queryByText('App Error')).not.toBeInTheDocument();
|
||||
// expectNoTsError(screen.queryByText('App Error')).not.toBeInTheDocument();
|
||||
}
|
||||
|
||||
const renderError = screen.queryByText('Render Failed');
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { hasEmptyValue } from '../client';
|
||||
import { hasEmptyValue, sortTree } from '../client';
|
||||
|
||||
describe('hasEmptyValue', () => {
|
||||
it('should return false when there is no empty value', () => {
|
||||
@ -80,3 +80,147 @@ describe('hasEmptyValue', () => {
|
||||
expect(hasEmptyValue(obj)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortTree', () => {
|
||||
it('should return the original tree when tree is empty', () => {
|
||||
expect(sortTree(null, 'order')).toBeNull();
|
||||
expect(sortTree([], 'order')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should sort tree nodes by a specific field in ascending order', () => {
|
||||
const tree = [
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
{ id: 2, name: 'B', children: [] },
|
||||
];
|
||||
|
||||
const result = sortTree(tree, 'id');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
{ id: 2, name: 'B', children: [] },
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort tree nodes by a specific field in descending order', () => {
|
||||
const tree = [
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
{ id: 2, name: 'B', children: [] },
|
||||
];
|
||||
|
||||
const result = sortTree(tree, 'id', 'children', false);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
{ id: 2, name: 'B', children: [] },
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should sort tree nodes with nested children', () => {
|
||||
const tree = [
|
||||
{
|
||||
id: 3,
|
||||
name: 'C',
|
||||
items: [
|
||||
{ id: 2, name: 'C-2' },
|
||||
{ id: 1, name: 'C-1' },
|
||||
],
|
||||
},
|
||||
{ id: 1, name: 'A', items: [] },
|
||||
{
|
||||
id: 2,
|
||||
name: 'B',
|
||||
items: [
|
||||
{ id: 3, name: 'B-3' },
|
||||
{ id: 1, name: 'B-1' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const result = sortTree(tree, 'id', 'items');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'A', items: [] },
|
||||
{
|
||||
id: 2,
|
||||
name: 'B',
|
||||
items: [
|
||||
{ id: 1, name: 'B-1' },
|
||||
{ id: 3, name: 'B-3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'C',
|
||||
items: [
|
||||
{ id: 1, name: 'C-1' },
|
||||
{ id: 2, name: 'C-2' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support sorting by function', () => {
|
||||
const tree = [
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
{ id: 2, name: 'B', children: [] },
|
||||
];
|
||||
|
||||
const sortByName = (node) => node.name;
|
||||
const result = sortTree(tree, sortByName);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
{ id: 2, name: 'B', children: [] },
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle complex nested structures', () => {
|
||||
const tree = [
|
||||
{
|
||||
id: 2,
|
||||
name: 'B',
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
name: 'B-3',
|
||||
children: [
|
||||
{ id: 2, name: 'B-3-2' },
|
||||
{ id: 1, name: 'B-3-1' },
|
||||
],
|
||||
},
|
||||
{ id: 1, name: 'B-1', children: [] },
|
||||
],
|
||||
},
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
];
|
||||
|
||||
const result = sortTree(tree, 'id');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'A', children: [] },
|
||||
{
|
||||
id: 2,
|
||||
name: 'B',
|
||||
children: [
|
||||
{ id: 1, name: 'B-1', children: [] },
|
||||
{
|
||||
id: 3,
|
||||
name: 'B-3',
|
||||
children: [
|
||||
{ id: 1, name: 'B-3-1' },
|
||||
{ id: 2, name: 'B-3-2' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ id: 3, name: 'C', children: [] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -7,6 +7,8 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
export const isString = (value: any): value is string => {
|
||||
return typeof value === 'string';
|
||||
};
|
||||
@ -56,12 +58,12 @@ export const nextTick = (fn: () => void) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用树节点深度优先遍历函数
|
||||
* @param {Object|Array} tree - 要遍历的树结构
|
||||
* @param {Function} callback - 遍历每个节点时执行的回调函数,返回真值时停止遍历并返回当前节点
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {string|Function} options.childrenKey - 子节点的属性名,默认为'children',也可以是一个函数
|
||||
* @returns {any|undefined} - 找到的节点或undefined
|
||||
* Generic tree node depth-first traversal function
|
||||
* @param {Object|Array} tree - The tree structure to traverse
|
||||
* @param {Function} callback - The callback function executed for each node, stops traversing and returns the current node when a truthy value is returned
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {string|Function} options.childrenKey - The property name of child nodes, defaults to 'children', can also be a function
|
||||
* @returns {any|undefined} - The found node or undefined
|
||||
*/
|
||||
export function treeFind<T = any>(
|
||||
tree: T | T[],
|
||||
@ -74,20 +76,20 @@ export function treeFind<T = any>(
|
||||
|
||||
const { childrenKey = 'children' } = options;
|
||||
|
||||
// 处理根节点是数组的情况
|
||||
// Handle case where the root node is an array
|
||||
const nodes = Array.isArray(tree) ? [...tree] : [tree];
|
||||
|
||||
// 深度优先搜索
|
||||
// Depth-first search
|
||||
for (const node of nodes) {
|
||||
// 对当前节点调用回调函数
|
||||
// Call callback function on the current node
|
||||
if (callback(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// 获取子节点
|
||||
// Get child nodes
|
||||
const children = typeof childrenKey === 'function' ? childrenKey(node) : (node as any)[childrenKey];
|
||||
|
||||
// 递归处理子节点
|
||||
// Recursively process child nodes
|
||||
if (Array.isArray(children) && children.length > 0) {
|
||||
const found = treeFind(children, callback, options);
|
||||
if (found !== undefined) {
|
||||
@ -98,3 +100,31 @@ export function treeFind<T = any>(
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a tree structure
|
||||
* @param {Array} tree - Tree structure array
|
||||
* @param {string|Function} sortBy - Sort field or sort function
|
||||
* @param {string} childrenKey - The key name of child nodes, defaults to 'children'
|
||||
* @param {boolean} isAsc - Whether to sort in ascending order, defaults to true
|
||||
* @returns {Array} - The sorted tree structure
|
||||
*/
|
||||
export function sortTree(tree: any[], sortBy: string | Function, childrenKey = 'children', isAsc = true) {
|
||||
if (!tree || !Array.isArray(tree) || tree.length === 0) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Sort nodes at the current level
|
||||
const sortedTree = _.orderBy(tree, sortBy, isAsc ? 'asc' : 'desc');
|
||||
|
||||
// Recursively sort child nodes
|
||||
return sortedTree.map((node) => {
|
||||
if (node[childrenKey] && node[childrenKey].length > 0) {
|
||||
return {
|
||||
...node,
|
||||
[childrenKey]: sortTree(node[childrenKey], sortBy, childrenKey, isAsc),
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
@ -126,6 +126,16 @@ export const deprecatedBulkEditActionSettings = new SchemaSettings({
|
||||
name: 'updateMode',
|
||||
Component: UpdateMode,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'remove',
|
||||
sort: 100,
|
||||
|
@ -8,18 +8,17 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
SchemaSettings,
|
||||
useBlockTemplateContext,
|
||||
SchemaSettingsLayoutItem,
|
||||
SchemaSettingsDataTemplates,
|
||||
useFormBlockContext,
|
||||
SchemaSettingsFormItemTemplate,
|
||||
useCollection,
|
||||
useCollection_deprecated,
|
||||
SchemaSettingsBlockHeightItem,
|
||||
SchemaSettingsBlockTitleItem,
|
||||
SchemaSettingsLinkageRules,
|
||||
LinkageRuleCategory,
|
||||
} from '@nocobase/client';
|
||||
|
||||
export const bulkEditFormBlockSettings = new SchemaSettings({
|
||||
@ -34,12 +33,23 @@ export const bulkEditFormBlockSettings = new SchemaSettings({
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -19,6 +19,8 @@ import {
|
||||
useRecord,
|
||||
useURLAndHTMLSchema,
|
||||
useVariableOptions,
|
||||
SchemaSettingsLinkageRules,
|
||||
LinkageRuleCategory,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -200,6 +202,17 @@ const commonOptions: any = {
|
||||
name: 'setTheBlockHeight',
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user