mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 01:36:52 +00:00
Merge branch 'next' into fix/subtable
This commit is contained in:
commit
1fe78a5d07
4
.github/workflows/build-pro-image.yml
vendored
4
.github/workflows/build-pro-image.yml
vendored
@ -11,7 +11,7 @@ on:
|
||||
- 'next'
|
||||
paths:
|
||||
- 'packages/**'
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile.pro'
|
||||
- '.github/workflows/build-pro-image.yml'
|
||||
|
||||
jobs:
|
||||
@ -139,7 +139,7 @@ jobs:
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
file: Dockerfile.pro
|
||||
build-args: |
|
||||
VERDACCIO_URL=http://localhost:4873/
|
||||
COMMIT_HASH=${GITHUB_SHA}
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,6 +32,7 @@ storage/plugins
|
||||
storage/tar
|
||||
storage/tmp
|
||||
storage/app.watch.ts
|
||||
storage/.upgrading
|
||||
storage/logs-e2e
|
||||
storage/uploads-e2e
|
||||
storage/.pm2-*
|
||||
|
65
Dockerfile.pro
Normal file
65
Dockerfile.pro
Normal file
@ -0,0 +1,65 @@
|
||||
FROM node:20.13-bullseye as builder
|
||||
ARG VERDACCIO_URL=http://host.docker.internal:10104/
|
||||
ARG COMMIT_HASH
|
||||
ARG APPEND_PRESET_LOCAL_PLUGINS
|
||||
ARG BEFORE_PACK_NOCOBASE="ls -l"
|
||||
ARG PLUGINS_DIRS
|
||||
|
||||
ENV PLUGINS_DIRS=${PLUGINS_DIRS}
|
||||
|
||||
|
||||
RUN npx npm-cli-adduser --username test --password test -e test@nocobase.com -r $VERDACCIO_URL
|
||||
|
||||
RUN apt-get update && apt-get install -y jq
|
||||
WORKDIR /tmp
|
||||
COPY . /tmp
|
||||
RUN yarn install && yarn build --no-dts
|
||||
|
||||
RUN cd /tmp && \
|
||||
NEWVERSION="$(cat lerna.json | jq '.version' | tr -d '"').$(date +'%Y%m%d%H%M%S')" \
|
||||
&& git checkout -b release-$(date +'%Y%m%d%H%M%S') \
|
||||
&& yarn lerna version ${NEWVERSION} -y --no-git-tag-version
|
||||
RUN git config user.email "test@mail.com" \
|
||||
&& git config user.name "test" && git add . \
|
||||
&& git commit -m "chore(versions): test publish packages"
|
||||
RUN yarn release:force --registry $VERDACCIO_URL
|
||||
|
||||
RUN yarn config set registry $VERDACCIO_URL
|
||||
WORKDIR /app
|
||||
RUN cd /app \
|
||||
&& yarn config set network-timeout 600000 -g \
|
||||
&& yarn create nocobase-app my-nocobase-app -a -e APP_ENV=production -e APPEND_PRESET_LOCAL_PLUGINS=$APPEND_PRESET_LOCAL_PLUGINS \
|
||||
&& cd /app/my-nocobase-app \
|
||||
&& yarn install --production
|
||||
|
||||
WORKDIR /app/my-nocobase-app
|
||||
RUN $BEFORE_PACK_NOCOBASE
|
||||
|
||||
RUN cd /app \
|
||||
&& rm -rf my-nocobase-app/packages/app/client/src/.umi \
|
||||
&& rm -rf nocobase.tar.gz \
|
||||
&& tar -zcf ./nocobase.tar.gz -C /app/my-nocobase-app .
|
||||
|
||||
FROM node:20.13-bullseye-slim
|
||||
RUN apt-get update && apt-get install -y nginx
|
||||
RUN rm -rf /etc/nginx/sites-enabled/default
|
||||
|
||||
# install postgresql-client and mysql-client
|
||||
RUN apt update && apt install -y wget postgresql-common gnupg \
|
||||
&& /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y \
|
||||
&& apt install -y postgresql-client-16 \
|
||||
&& wget https://downloads.mysql.com/archives/get/p/23/file/mysql-community-client-core_8.1.0-1debian11_amd64.deb \
|
||||
&& dpkg -x mysql-community-client-core_8.1.0-1debian11_amd64.deb /tmp/mysql-client \
|
||||
&& cp /tmp/mysql-client/usr/bin/mysqldump /usr/bin/ \
|
||||
&& cp /tmp/mysql-client/usr/bin/mysql /usr/bin/
|
||||
|
||||
COPY ./docker/nocobase/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf
|
||||
COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz
|
||||
|
||||
WORKDIR /app/nocobase
|
||||
|
||||
RUN mkdir -p /app/nocobase/storage/uploads/ && echo "$COMMIT_HASH" >> /app/nocobase/storage/uploads/COMMIT_HASH
|
||||
|
||||
COPY ./docker/nocobase/docker-entrypoint.sh /app/
|
||||
|
||||
CMD ["/app/docker-entrypoint.sh"]
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": [
|
||||
"--ignore-engines"
|
||||
],
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
"command": {
|
||||
"version": {
|
||||
"forcePublish": true,
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/acl",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/resourcer": "1.3.27-beta",
|
||||
"@nocobase/utils": "1.3.27-beta",
|
||||
"@nocobase/resourcer": "1.4.0-alpha",
|
||||
"@nocobase/utils": "1.4.0-alpha",
|
||||
"minimatch": "^5.1.1"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@nocobase/actions",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/cache": "1.3.27-beta",
|
||||
"@nocobase/database": "1.3.27-beta",
|
||||
"@nocobase/resourcer": "1.3.27-beta"
|
||||
"@nocobase/cache": "1.4.0-alpha",
|
||||
"@nocobase/database": "1.4.0-alpha",
|
||||
"@nocobase/resourcer": "1.4.0-alpha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -75,8 +75,8 @@ export class SortAbleCollection {
|
||||
|
||||
// insert source position to target position
|
||||
async move(sourceInstanceId: TargetKey, targetInstanceId: TargetKey, options: MoveOptions = {}) {
|
||||
const sourceInstance = await this.collection.repository.findById(sourceInstanceId);
|
||||
const targetInstance = await this.collection.repository.findById(targetInstanceId);
|
||||
const sourceInstance = await this.collection.repository.findByTargetKey(sourceInstanceId);
|
||||
const targetInstance = await this.collection.repository.findByTargetKey(targetInstanceId);
|
||||
|
||||
if (this.scopeKey && sourceInstance.get(this.scopeKey) !== targetInstance.get(this.scopeKey)) {
|
||||
await sourceInstance.update({
|
||||
@ -88,7 +88,7 @@ export class SortAbleCollection {
|
||||
}
|
||||
|
||||
async changeScope(sourceInstanceId: TargetKey, targetScope: any, method?: string) {
|
||||
const sourceInstance = await this.collection.repository.findById(sourceInstanceId);
|
||||
const sourceInstance = await this.collection.repository.findByTargetKey(sourceInstanceId);
|
||||
const targetScopeValue = targetScope[this.scopeKey];
|
||||
|
||||
if (targetScopeValue && sourceInstance.get(this.scopeKey) !== targetScopeValue) {
|
||||
@ -108,7 +108,7 @@ export class SortAbleCollection {
|
||||
}
|
||||
|
||||
async sticky(sourceInstanceId: TargetKey) {
|
||||
const sourceInstance = await this.collection.repository.findById(sourceInstanceId);
|
||||
const sourceInstance = await this.collection.repository.findByTargetKey(sourceInstanceId);
|
||||
await sourceInstance.update(
|
||||
{
|
||||
[this.field.get('name')]: 0,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getUmiConfig, IndexGenerator } from '@nocobase/devtools/umiConfig';
|
||||
import { generatePlugins, getUmiConfig } from '@nocobase/devtools/umiConfig';
|
||||
import path from 'path';
|
||||
import { defineConfig } from 'umi';
|
||||
|
||||
@ -8,17 +8,11 @@ process.env.MFSU_AD = 'none';
|
||||
process.env.DID_YOU_KNOW = 'none';
|
||||
|
||||
const pluginPrefix = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',').filter((item) => !item.includes('preset')); // 因为现在 preset 是直接引入的,所以不能忽略,如果以后 preset 也是动态插件的形式引入,那么这里可以去掉
|
||||
|
||||
const pluginDirs = (process.env.PLUGIN_PATH || 'packages/plugins/,packages/samples/,packages/pro-plugins/')
|
||||
.split(',').map(item => path.join(process.cwd(), item));
|
||||
|
||||
const outputPluginPath = path.join(__dirname, 'src', '.plugins');
|
||||
const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs);
|
||||
indexGenerator.generate();
|
||||
|
||||
const isDevCmd = !!process.env.IS_DEV_CMD;
|
||||
const appPublicPath = isDevCmd ? '/' : '{{env.APP_PUBLIC_PATH}}';
|
||||
|
||||
generatePlugins();
|
||||
|
||||
export default defineConfig({
|
||||
title: 'Loading...',
|
||||
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false,
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@nocobase/app",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/database": "1.3.27-beta",
|
||||
"@nocobase/preset-nocobase": "1.3.27-beta",
|
||||
"@nocobase/server": "1.3.27-beta"
|
||||
"@nocobase/database": "1.4.0-alpha",
|
||||
"@nocobase/preset-nocobase": "1.4.0-alpha",
|
||||
"@nocobase/server": "1.4.0-alpha"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "1.3.27-beta"
|
||||
"@nocobase/client": "1.4.0-alpha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/auth",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.3.27-beta",
|
||||
"@nocobase/cache": "1.3.27-beta",
|
||||
"@nocobase/database": "1.3.27-beta",
|
||||
"@nocobase/resourcer": "1.3.27-beta",
|
||||
"@nocobase/utils": "1.3.27-beta",
|
||||
"@nocobase/actions": "1.4.0-alpha",
|
||||
"@nocobase/cache": "1.4.0-alpha",
|
||||
"@nocobase/database": "1.4.0-alpha",
|
||||
"@nocobase/resourcer": "1.4.0-alpha",
|
||||
"@nocobase/utils": "1.4.0-alpha",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
|
@ -71,7 +71,7 @@ describe('middleware', () => {
|
||||
hasFn.mockImplementation(() => true);
|
||||
const res = await agent.resource('auth').check();
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.text).toContain('token is not available');
|
||||
expect(res.text).toContain('Token is invalid');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -109,7 +109,10 @@ export class AuthManager {
|
||||
return async (ctx: Context & { auth: Auth }, next: Next) => {
|
||||
const token = ctx.getBearerToken();
|
||||
if (token && (await ctx.app.authManager.jwt.blacklist?.has(token))) {
|
||||
return ctx.throw(401, ctx.t('token is not available'));
|
||||
return ctx.throw(401, {
|
||||
code: 'TOKEN_INVALID',
|
||||
message: ctx.t('Token is invalid'),
|
||||
});
|
||||
}
|
||||
|
||||
const name = ctx.get(this.options.authKey) || this.options.default;
|
||||
|
@ -69,14 +69,14 @@ export class BaseAuth extends Auth {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const { userId, roleName } = await this.jwt.decode(token);
|
||||
const { userId, roleName, iat, temp } = await this.jwt.decode(token);
|
||||
|
||||
if (roleName) {
|
||||
this.ctx.headers['x-role'] = roleName;
|
||||
}
|
||||
|
||||
const cache = this.ctx.cache as Cache;
|
||||
return await cache.wrap(this.getCacheKey(userId), () =>
|
||||
const user = await cache.wrap(this.getCacheKey(userId), () =>
|
||||
this.userRepository.findOne({
|
||||
filter: {
|
||||
id: userId,
|
||||
@ -84,6 +84,10 @@ export class BaseAuth extends Auth {
|
||||
raw: true,
|
||||
}),
|
||||
);
|
||||
if (temp && user.passwordChangeTz && iat * 1000 < user.passwordChangeTz) {
|
||||
throw new Error('Token is invalid');
|
||||
}
|
||||
return user;
|
||||
} catch (err) {
|
||||
this.ctx.logger.error(err, { method: 'check' });
|
||||
return null;
|
||||
@ -106,6 +110,7 @@ export class BaseAuth extends Auth {
|
||||
}
|
||||
const token = this.jwt.sign({
|
||||
userId: user.id,
|
||||
temp: true,
|
||||
});
|
||||
return {
|
||||
user,
|
||||
@ -119,7 +124,7 @@ export class BaseAuth extends Auth {
|
||||
return;
|
||||
}
|
||||
const { userId } = await this.jwt.decode(token);
|
||||
await this.ctx.app.emitAsync('beforeSignOut', { userId });
|
||||
await this.ctx.app.emitAsync('cache:del:roles', { userId });
|
||||
await this.ctx.cache.del(this.getCacheKey(userId));
|
||||
return await this.jwt.block(token);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/build",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "Library build tool based on rollup.",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
2
packages/core/cache/package.json
vendored
2
packages/core/cache/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cache",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cli",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
@ -8,7 +8,7 @@
|
||||
"nocobase": "./bin/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/app": "1.3.27-beta",
|
||||
"@nocobase/app": "1.4.0-alpha",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@umijs/utils": "3.5.20",
|
||||
"chalk": "^4.1.1",
|
||||
@ -25,7 +25,7 @@
|
||||
"tsx": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/devtools": "1.3.27-beta"
|
||||
"@nocobase/devtools": "1.4.0-alpha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -9,8 +9,12 @@
|
||||
|
||||
const chalk = require('chalk');
|
||||
const { Command } = require('commander');
|
||||
const { runAppCommand, runInstall, run, postCheck, nodeCheck, promptForTs } = require('../util');
|
||||
const { generatePlugins, run, postCheck, nodeCheck, promptForTs } = require('../util');
|
||||
const { getPortPromise } = require('portfinder');
|
||||
const chokidar = require('chokidar');
|
||||
const { uid } = require('@formily/shared');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
*
|
||||
@ -27,6 +31,25 @@ module.exports = (cli) => {
|
||||
.option('--inspect [port]')
|
||||
.allowUnknownOption()
|
||||
.action(async (opts) => {
|
||||
const watcher = chokidar.watch('./storage/plugins/**/*', {
|
||||
cwd: process.cwd(),
|
||||
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
|
||||
persistent: true,
|
||||
depth: 1, // 只监听第一层目录
|
||||
});
|
||||
|
||||
watcher
|
||||
.on('addDir', async (pathname) => {
|
||||
generatePlugins();
|
||||
const file = path.resolve(process.cwd(), 'storage/app.watch.ts');
|
||||
await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8');
|
||||
})
|
||||
.on('unlinkDir', async (pathname) => {
|
||||
generatePlugins();
|
||||
const file = path.resolve(process.cwd(), 'storage/app.watch.ts');
|
||||
await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8');
|
||||
});
|
||||
|
||||
promptForTs();
|
||||
const { SERVER_TSCONFIG_PATH } = process.env;
|
||||
process.env.IS_DEV_CMD = true;
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run, isDev, isProd, promptForTs } = require('../util');
|
||||
const { run, isDev, isProd, promptForTs, downloadPro } = require('../util');
|
||||
|
||||
/**
|
||||
*
|
||||
@ -20,10 +20,14 @@ module.exports = (cli) => {
|
||||
.allowUnknownOption()
|
||||
.option('-h, --help')
|
||||
.option('--ts-node-dev')
|
||||
.action((options) => {
|
||||
.action(async (options) => {
|
||||
const cmd = process.argv.slice(2)?.[0];
|
||||
if (cmd === 'install') {
|
||||
await downloadPro();
|
||||
}
|
||||
if (isDev()) {
|
||||
promptForTs();
|
||||
run('tsx', [
|
||||
await run('tsx', [
|
||||
'--tsconfig',
|
||||
SERVER_TSCONFIG_PATH,
|
||||
'-r',
|
||||
@ -32,7 +36,7 @@ module.exports = (cli) => {
|
||||
...process.argv.slice(2),
|
||||
]);
|
||||
} else if (isProd()) {
|
||||
run('node', [`${APP_PACKAGE_ROOT}/lib/index.js`, ...process.argv.slice(2)]);
|
||||
await run('node', [`${APP_PACKAGE_ROOT}/lib/index.js`, ...process.argv.slice(2)]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -31,6 +31,7 @@ module.exports = (cli) => {
|
||||
require('./umi')(cli);
|
||||
require('./upgrade')(cli);
|
||||
require('./postinstall')(cli);
|
||||
require('./pkg')(cli);
|
||||
if (isPackageValid('@umijs/utils')) {
|
||||
require('./create-plugin')(cli);
|
||||
}
|
||||
|
218
packages/core/cli/src/commands/pkg.js
Normal file
218
packages/core/cli/src/commands/pkg.js
Normal file
@ -0,0 +1,218 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const axios = require('axios');
|
||||
const fs = require('fs-extra');
|
||||
const zlib = require('zlib');
|
||||
const tar = require('tar');
|
||||
const path = require('path');
|
||||
const { createStoragePluginsSymlink } = require('@nocobase/utils/plugin-symlink');
|
||||
const chalk = require('chalk');
|
||||
|
||||
class Package {
|
||||
data;
|
||||
constructor(packageName, packageManager) {
|
||||
this.packageName = packageName;
|
||||
this.packageManager = packageManager;
|
||||
this.outputDir = path.resolve(process.cwd(), `storage/plugins/${this.packageName}`);
|
||||
}
|
||||
|
||||
get token() {
|
||||
return this.packageManager.getToken();
|
||||
}
|
||||
|
||||
url(path) {
|
||||
return this.packageManager.url(path);
|
||||
}
|
||||
|
||||
async mkdir() {
|
||||
if (await fs.exists(this.outputDir)) {
|
||||
await fs.rm(this.outputDir, { recursive: true, force: true });
|
||||
}
|
||||
await fs.mkdirp(this.outputDir);
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
try {
|
||||
const res = await axios.get(this.url(this.packageName), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
||||
this.data = res.data;
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
getTarball(version = 'latest') {
|
||||
if (this.data.versions[version]) {
|
||||
return [version, this.data.versions[version].dist.tarball];
|
||||
}
|
||||
|
||||
if (version.includes('beta')) {
|
||||
version = version.split('beta')[0] + 'beta';
|
||||
} else if (version.includes('alpha')) {
|
||||
const prefix = (version = version.split('alpha')[0]);
|
||||
version = Object.keys(this.data.versions)
|
||||
.filter((ver) => ver.startsWith(`${prefix}alpha`))
|
||||
.sort()
|
||||
.pop();
|
||||
}
|
||||
|
||||
if (version === 'latest') {
|
||||
version = this.data['dist-tags']['latest'];
|
||||
} else if (version === 'next') {
|
||||
version = this.data['dist-tags']['next'];
|
||||
}
|
||||
|
||||
if (!this.data.versions[version]) {
|
||||
console.log(chalk.redBright(`Download failed: ${this.packageName}@${version} package does not exist`));
|
||||
}
|
||||
|
||||
return [version, this.data.versions[version].dist.tarball];
|
||||
}
|
||||
|
||||
async isDevPackage() {
|
||||
let file = path.resolve(process.cwd(), 'packages/plugins', this.packageName, 'package.json');
|
||||
if (await fs.exists(file)) {
|
||||
return true;
|
||||
}
|
||||
file = path.resolve(process.cwd(), 'packages/pro-plugins', this.packageName, 'package.json');
|
||||
if (await fs.exists(file)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async download(options = {}) {
|
||||
if (await this.isDevPackage()) {
|
||||
console.log(chalk.yellowBright(`Skipped: ${this.packageName} is dev package`));
|
||||
return;
|
||||
}
|
||||
await this.getInfo();
|
||||
if (!this.data) {
|
||||
console.log(chalk.redBright(`Download failed: ${this.packageName} package does not exist`));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const [version, url] = this.getTarball(options.version);
|
||||
const response = await axios({
|
||||
url,
|
||||
responseType: 'stream',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
});
|
||||
await this.mkdir();
|
||||
await new Promise((resolve, reject) => {
|
||||
response.data
|
||||
.pipe(zlib.createGunzip()) // 解压 gzip
|
||||
.pipe(tar.extract({ cwd: this.outputDir, strip: 1 })) // 解压 tar
|
||||
.on('finish', resolve)
|
||||
.on('error', reject);
|
||||
});
|
||||
console.log(chalk.greenBright(`Download success: ${this.packageName}@${version}`));
|
||||
} catch (error) {
|
||||
console.log(chalk.redBright(`Download failed: ${this.packageName}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PackageManager {
|
||||
token;
|
||||
baseURL;
|
||||
|
||||
constructor({ baseURL }) {
|
||||
this.baseURL = baseURL;
|
||||
}
|
||||
|
||||
getToken() {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
getBaseURL() {
|
||||
return this.baseURL;
|
||||
}
|
||||
|
||||
url(path) {
|
||||
return this.baseURL + path;
|
||||
}
|
||||
|
||||
async login(credentials) {
|
||||
try {
|
||||
const res1 = await axios.post(`${this.baseURL}-/verdaccio/sec/login`, credentials, {
|
||||
responseType: 'json',
|
||||
});
|
||||
this.token = res1.data.token;
|
||||
} catch (error) {
|
||||
console.error(chalk.redBright(`Login failed: ${this.baseURL}`));
|
||||
}
|
||||
}
|
||||
|
||||
getPackage(packageName) {
|
||||
return new Package(packageName, this);
|
||||
}
|
||||
|
||||
async getProPackages() {
|
||||
const res = await axios.get(this.url('pro-packages'), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
||||
return res.data.data;
|
||||
}
|
||||
|
||||
async getPackages() {
|
||||
const pkgs = await this.getProPackages();
|
||||
return pkgs;
|
||||
}
|
||||
|
||||
async download(options = {}) {
|
||||
const { version } = options;
|
||||
if (!this.token) {
|
||||
return;
|
||||
}
|
||||
const pkgs = await this.getPackages();
|
||||
for (const pkg of pkgs) {
|
||||
await this.getPackage(pkg).download({ version });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command} cli
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
const pkg = cli.command('pkg');
|
||||
pkg
|
||||
.command('download-pro')
|
||||
.option('-V, --version [version]')
|
||||
.action(async () => {
|
||||
const { NOCOBASE_PKG_URL, NOCOBASE_PKG_USERNAME, NOCOBASE_PKG_PASSWORD } = process.env;
|
||||
if (!(NOCOBASE_PKG_URL && NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD)) {
|
||||
return;
|
||||
}
|
||||
const credentials = { username: NOCOBASE_PKG_USERNAME, password: NOCOBASE_PKG_PASSWORD };
|
||||
const pm = new PackageManager({ baseURL: NOCOBASE_PKG_URL });
|
||||
await pm.login(credentials);
|
||||
const file = path.resolve(__dirname, '../../package.json');
|
||||
const json = await fs.readJson(file);
|
||||
await pm.download({ version: json.version });
|
||||
await createStoragePluginsSymlink();
|
||||
});
|
||||
pkg.command('export-all').action(async () => {
|
||||
console.log('Todo...');
|
||||
});
|
||||
};
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run, isDev, isPackageValid, generatePlaywrightPath } = require('../util');
|
||||
const { run, isDev, isPackageValid, generatePlaywrightPath, generatePlugins } = require('../util');
|
||||
const { dirname, resolve } = require('path');
|
||||
const { existsSync, mkdirSync, readFileSync, appendFileSync } = require('fs');
|
||||
const { readFile, writeFile } = require('fs').promises;
|
||||
@ -41,7 +41,7 @@ module.exports = (cli) => {
|
||||
.option('--skip-umi')
|
||||
.action(async (options) => {
|
||||
writeToExclude();
|
||||
|
||||
generatePlugins();
|
||||
generatePlaywrightPath(true);
|
||||
await createStoragePluginsSymlink();
|
||||
if (!isDev()) {
|
||||
|
@ -8,10 +8,11 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { isDev, run, postCheck, runInstall, promptForTs } = require('../util');
|
||||
const { isDev, run, postCheck, downloadPro, promptForTs } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
const chalk = require('chalk');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
function deleteSockFiles() {
|
||||
const { SOCKET_PATH, PM2_HOME } = process.env;
|
||||
@ -38,6 +39,23 @@ module.exports = (cli) => {
|
||||
.option('--quickstart')
|
||||
.allowUnknownOption()
|
||||
.action(async (opts) => {
|
||||
if (opts.quickstart) {
|
||||
await downloadPro();
|
||||
}
|
||||
|
||||
const watcher = chokidar.watch('./storage/plugins/**/*', {
|
||||
cwd: process.cwd(),
|
||||
ignoreInitial: true,
|
||||
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
|
||||
persistent: true,
|
||||
depth: 1, // 只监听第一层目录
|
||||
});
|
||||
|
||||
watcher.on('addDir', async (pathname) => {
|
||||
console.log('pathname', pathname);
|
||||
await run('yarn', ['nocobase', 'pm2-restart']);
|
||||
});
|
||||
|
||||
if (opts.port) {
|
||||
process.env.APP_PORT = opts.port;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
const chalk = require('chalk');
|
||||
const { Command } = require('commander');
|
||||
const { resolve } = require('path');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, updateJsonFile, hasTsNode } = require('../util');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
|
||||
/**
|
||||
@ -29,15 +29,18 @@ module.exports = (cli) => {
|
||||
if (hasTsNode()) promptForTs();
|
||||
if (hasCorePackages()) {
|
||||
// await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
if (options.skipCodeUpdate) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
// await runAppCommand('upgrade');
|
||||
if (!hasTsNode()) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
@ -54,8 +57,9 @@ module.exports = (cli) => {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
if (pkg.version === stdout) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
rmAppDir();
|
||||
await rmAppDir();
|
||||
return;
|
||||
}
|
||||
const currentY = 1 * pkg.version.split('.')[1];
|
||||
@ -66,7 +70,8 @@ module.exports = (cli) => {
|
||||
await run('yarn', ['add', '@nocobase/cli', '@nocobase/devtools', '-W']);
|
||||
}
|
||||
await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
rmAppDir();
|
||||
await rmAppDir();
|
||||
});
|
||||
};
|
||||
|
@ -72,7 +72,7 @@ class PluginGenerator extends Generator {
|
||||
});
|
||||
this.log('');
|
||||
genTsConfigPaths();
|
||||
execa.sync('yarn', ['postinstall', '--skip-umi'], { shell: true, stdio: 'inherit' });
|
||||
execa.sync('yarn', ['postinstall'], { shell: true, stdio: 'inherit' });
|
||||
this.log(`The plugin folder is in ${chalk.green(`packages/plugins/${name}`)}`);
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +163,10 @@ exports.promptForTs = () => {
|
||||
console.log(chalk.green('WAIT: ') + 'TypeScript compiling...');
|
||||
};
|
||||
|
||||
exports.downloadPro = async () => {
|
||||
await exports.run('yarn', ['nocobase', 'pkg', 'download-pro']);
|
||||
};
|
||||
|
||||
exports.updateJsonFile = async (target, fn) => {
|
||||
const content = await readFile(target, 'utf-8');
|
||||
const json = JSON.parse(content);
|
||||
@ -416,3 +420,13 @@ exports.initEnv = function initEnv() {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
exports.generatePlugins = function () {
|
||||
try {
|
||||
require.resolve('@nocobase/devtools/umiConfig');
|
||||
const { generatePlugins } = require('@nocobase/devtools/umiConfig');
|
||||
generatePlugins();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/client",
|
||||
"version": "1.3.27-beta",
|
||||
"version": "1.4.0-alpha",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.mjs",
|
||||
@ -11,6 +11,7 @@
|
||||
"@ant-design/icons": "^5.1.4",
|
||||
"@ant-design/pro-layout": "^7.16.11",
|
||||
"@antv/g2plot": "^2.4.18",
|
||||
"@budibase/handlebars-helpers": "^0.13.2",
|
||||
"@ctrl/tinycolor": "^3.6.0",
|
||||
"@dnd-kit/core": "^5.0.1",
|
||||
"@dnd-kit/modifiers": "^6.0.0",
|
||||
@ -26,9 +27,9 @@
|
||||
"@formily/reactive-react": "^2.2.27",
|
||||
"@formily/shared": "^2.2.27",
|
||||
"@formily/validator": "^2.2.27",
|
||||
"@nocobase/evaluators": "1.3.27-beta",
|
||||
"@nocobase/sdk": "1.3.27-beta",
|
||||
"@nocobase/utils": "1.3.27-beta",
|
||||
"@nocobase/evaluators": "1.4.0-alpha",
|
||||
"@nocobase/sdk": "1.4.0-alpha",
|
||||
"@nocobase/utils": "1.4.0-alpha",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.12.8",
|
||||
"antd-style": "3.4.5",
|
||||
|
@ -103,6 +103,13 @@ export const useACLContext = () => {
|
||||
export const ACLActionParamsContext = createContext<any>({});
|
||||
ACLActionParamsContext.displayName = 'ACLActionParamsContext';
|
||||
|
||||
export const ACLCustomContext = createContext<any>({});
|
||||
ACLCustomContext.displayName = 'ACLCustomContext';
|
||||
|
||||
const useACLCustomContext = () => {
|
||||
return useContext(ACLCustomContext);
|
||||
};
|
||||
|
||||
export const useACLRolesCheck = () => {
|
||||
const ctx = useContext(ACLContext);
|
||||
const dataSourceName = useDataSourceKey();
|
||||
@ -218,9 +225,10 @@ export function useUIConfigurationPermissions(): { allowConfigUI: boolean } {
|
||||
|
||||
export const ACLCollectionProvider = (props) => {
|
||||
const { allowAll, parseAction } = useACLRoleContext();
|
||||
const { allowAll: customAllowAll } = useACLCustomContext();
|
||||
const app = useApp();
|
||||
const schema = useFieldSchema();
|
||||
if (allowAll || app.disableAcl) {
|
||||
if (allowAll || app.disableAcl || customAllowAll) {
|
||||
return props.children;
|
||||
}
|
||||
let actionPath = schema?.['x-acl-action'] || props.actionPath;
|
||||
|
@ -98,6 +98,9 @@ export class APIClient extends APIClientSDK {
|
||||
if (errs.find((error: { code?: string }) => error.code === 'ROLE_NOT_FOUND_ERR')) {
|
||||
this.auth.setRole(null);
|
||||
}
|
||||
if (errs.find((error: { code?: string }) => error.code === 'TOKEN_INVALID')) {
|
||||
this.auth.setToken(null);
|
||||
}
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
@ -130,9 +133,11 @@ export class APIClient extends APIClientSDK {
|
||||
}
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
async (error) => {
|
||||
if (this.silence) {
|
||||
throw error;
|
||||
console.error(error);
|
||||
return;
|
||||
// throw error;
|
||||
}
|
||||
const redirectTo = error?.response?.data?.redirectTo;
|
||||
if (redirectTo) {
|
||||
|
@ -12,10 +12,10 @@ import React, { FC, useMemo } from 'react';
|
||||
import { useApp } from '../../hooks';
|
||||
import { SchemaInitializerItems } from '../components';
|
||||
import { SchemaInitializerButton } from '../components/SchemaInitializerButton';
|
||||
import { withInitializer } from '../withInitializer';
|
||||
import { SchemaInitializerOptions } from '../types';
|
||||
import { SchemaInitializer } from '../SchemaInitializer';
|
||||
|
||||
import { SchemaInitializerOptions } from '../types';
|
||||
import { withInitializer } from '../withInitializer';
|
||||
import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider';
|
||||
const InitializerComponent: FC<SchemaInitializerOptions<any, any>> = React.memo((options) => {
|
||||
const Component: any = options.Component || SchemaInitializerButton;
|
||||
|
||||
@ -38,6 +38,18 @@ export function useSchemaInitializerRender<P1 = ButtonProps, P2 = {}>(
|
||||
options?: Omit<SchemaInitializerOptions<P1, P2>, 'name'>,
|
||||
) {
|
||||
const app = useApp();
|
||||
const { isMobile } = useOpenModeContext() || {};
|
||||
|
||||
// compatible with mobile
|
||||
// TODO: delete this code
|
||||
if (
|
||||
name === 'popup:common:addBlock' &&
|
||||
app.schemaInitializerManager.has('mobile:popup:common:addBlock') &&
|
||||
isMobile
|
||||
) {
|
||||
name = 'mobile:popup:common:addBlock';
|
||||
}
|
||||
|
||||
const initializer = useMemo(
|
||||
() => (typeof name === 'object' ? name : app.schemaInitializerManager.get<P1, P2>(name)),
|
||||
[app.schemaInitializerManager, name],
|
||||
|
@ -11,6 +11,7 @@ import { Field, GeneralField } from '@formily/core';
|
||||
import { RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||
import { Col, Row } from 'antd';
|
||||
import merge from 'deepmerge';
|
||||
import { isArray } from 'lodash';
|
||||
import template from 'lodash/template';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
@ -307,7 +308,15 @@ export const useFilterByTk = () => {
|
||||
const association = getCollectionField(assoc);
|
||||
return recordData?.[association.targetKey || 'id'];
|
||||
}
|
||||
return recordData?.[collection.filterTargetKey || 'id'];
|
||||
if (isArray(collection.filterTargetKey)) {
|
||||
const filterByTk = {};
|
||||
for (const key of collection.filterTargetKey) {
|
||||
filterByTk[key] = recordData?.[key];
|
||||
}
|
||||
return filterByTk;
|
||||
} else {
|
||||
return recordData?.[collection.filterTargetKey || 'id'];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,7 @@ import { FormContext, useField, useFieldSchema } from '@formily/react';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useCollectionManager_deprecated } from '../collection-manager';
|
||||
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
||||
import { useTableBlockParams } from '../modules/blocks/data-blocks/table';
|
||||
import { useTableBlockParams } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||
import { FixedBlockWrapper, SchemaComponentOptions } from '../schema-component';
|
||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||
import { useBlockHeightProps } from './hooks';
|
||||
|
@ -183,9 +183,10 @@ export const useTableFieldProps = () => {
|
||||
rowKey: (record: any) => {
|
||||
return field.value?.indexOf?.(record);
|
||||
},
|
||||
onRowSelectionChange(selectedRowKeys) {
|
||||
onRowSelectionChange(selectedRowKeys, selectedRowData) {
|
||||
ctx.field.data = ctx?.field?.data || {};
|
||||
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
||||
ctx.field.data.selectedRowData = selectedRowData;
|
||||
},
|
||||
onChange({ current, pageSize }) {
|
||||
ctx.service.run({ page: current, pageSize });
|
||||
|
@ -319,9 +319,10 @@ export const useTableSelectorProps = () => {
|
||||
dragSort: false,
|
||||
rowKey: ctx.rowKey || 'id',
|
||||
pagination: fieldSchema?.['x-component-props']?.pagination === false ? false : field.componentProps.pagination,
|
||||
onRowSelectionChange(selectedRowKeys, selectedRows) {
|
||||
onRowSelectionChange(selectedRowKeys, selectedRowData) {
|
||||
ctx.field.data = ctx?.field?.data || {};
|
||||
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
||||
ctx.field.data.selectedRowData = selectedRowData;
|
||||
},
|
||||
async onRowDragEnd({ from, to }) {
|
||||
await ctx.resource.move({
|
||||
|
@ -251,6 +251,13 @@ export const useCreateActionProps = () => {
|
||||
if (!onSuccess?.successMessage) {
|
||||
message.success(t('Saved successfully'));
|
||||
await resetFormCorrectly(form);
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
navigate(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
@ -1080,13 +1087,25 @@ export const useBulkDestroyActionProps = () => {
|
||||
const { field } = useBlockRequestContext();
|
||||
const { resource, service } = useBlockRequestContext();
|
||||
const { setSubmitted } = useActionContext();
|
||||
const collection = useCollection_deprecated();
|
||||
const { filterTargetKey } = collection;
|
||||
return {
|
||||
async onClick(e?, callBack?) {
|
||||
let filterByTk = field.data?.selectedRowKeys;
|
||||
if (Array.isArray(filterTargetKey)) {
|
||||
filterByTk = field.data.selectedRowData.map((v) => {
|
||||
const obj = {};
|
||||
filterTargetKey.map((j) => {
|
||||
obj[j] = v[j];
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
if (!field?.data?.selectedRowKeys?.length) {
|
||||
return;
|
||||
}
|
||||
await resource.destroy({
|
||||
filterByTk: field.data?.selectedRowKeys,
|
||||
filterByTk,
|
||||
});
|
||||
field.data.selectedRowKeys = [];
|
||||
const currentPage = service.params[0]?.page;
|
||||
@ -1098,7 +1117,7 @@ export const useBulkDestroyActionProps = () => {
|
||||
callBack?.();
|
||||
}
|
||||
setSubmitted?.(true);
|
||||
// service?.refresh?.();
|
||||
service?.refresh?.();
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -1295,6 +1314,50 @@ export const useAssociationFilterBlockProps = () => {
|
||||
parseVariableLoading,
|
||||
]);
|
||||
|
||||
const onSelected = useCallback(
|
||||
(value) => {
|
||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||
|
||||
getDataBlocks().forEach((block) => {
|
||||
const target = targets.find((target) => target.uid === block.uid);
|
||||
if (!target) return;
|
||||
|
||||
const key = `${uid}${fieldSchema.name}`;
|
||||
const param = block.service.params?.[0] || {};
|
||||
|
||||
if (!block.service.params?.[1]?.filters) {
|
||||
_.set(block.service.params, '[1].filters', {});
|
||||
}
|
||||
|
||||
// 保留原有的 filter
|
||||
const storedFilter = block.service.params[1].filters;
|
||||
|
||||
if (value.length) {
|
||||
storedFilter[key] = {
|
||||
[filterKey]: value,
|
||||
};
|
||||
} else {
|
||||
if (block.dataLoadingMode === 'manual') {
|
||||
return block.clearData();
|
||||
}
|
||||
delete storedFilter[key];
|
||||
}
|
||||
|
||||
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
|
||||
|
||||
return block.doFilter(
|
||||
{
|
||||
...param,
|
||||
page: 1,
|
||||
filter: mergedFilter,
|
||||
},
|
||||
{ filters: storedFilter },
|
||||
);
|
||||
});
|
||||
},
|
||||
[fieldSchema, filterKey, getDataBlocks],
|
||||
);
|
||||
|
||||
if (!collectionField) {
|
||||
return {};
|
||||
}
|
||||
@ -1336,41 +1399,6 @@ export const useAssociationFilterBlockProps = () => {
|
||||
};
|
||||
}
|
||||
|
||||
const onSelected = (value) => {
|
||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||
|
||||
getDataBlocks().forEach((block) => {
|
||||
const target = targets.find((target) => target.uid === block.uid);
|
||||
if (!target) return;
|
||||
|
||||
const key = `${uid}${fieldSchema.name}`;
|
||||
const param = block.service.params?.[0] || {};
|
||||
// 保留原有的 filter
|
||||
const storedFilter = block.service.params?.[1]?.filters || {};
|
||||
if (value.length) {
|
||||
storedFilter[key] = {
|
||||
[filterKey]: value,
|
||||
};
|
||||
} else {
|
||||
if (block.dataLoadingMode === 'manual') {
|
||||
return block.clearData();
|
||||
}
|
||||
delete storedFilter[key];
|
||||
}
|
||||
|
||||
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
|
||||
|
||||
return block.doFilter(
|
||||
{
|
||||
...param,
|
||||
page: 1,
|
||||
filter: mergedFilter,
|
||||
},
|
||||
{ filters: storedFilter },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
/** 渲染 Collapse 的列表数据 */
|
||||
list,
|
||||
|
@ -16,64 +16,23 @@ import { cloneDeep } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRequest } from '../../api-client';
|
||||
import { CollectionFieldInterface } from '../../data-source';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { useCollectionManager_deprecated } from '../hooks';
|
||||
import useDialect from '../hooks/useDialect';
|
||||
import { IField } from '../interfaces/types';
|
||||
import * as components from './components';
|
||||
import { useFieldInterfaceOptions } from './interfaces';
|
||||
|
||||
const getSchema = (schema: IField, record: any, compile) => {
|
||||
const getSchema = (schema: CollectionFieldInterface, record: any, compile) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
const properties = cloneDeep(schema.properties) as any;
|
||||
const properties = schema.getConfigureFormProperties();
|
||||
|
||||
if (schema.hasDefaultValue === true) {
|
||||
properties['defaultValue'] = cloneDeep(schema?.default?.uiSchema);
|
||||
properties.defaultValue.required = false;
|
||||
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
|
||||
properties['defaultValue']['x-decorator'] = 'FormItem';
|
||||
properties['defaultValue']['x-reactions'] = [
|
||||
{
|
||||
dependencies: [
|
||||
'uiSchema.x-component-props.gmt',
|
||||
'uiSchema.x-component-props.showTime',
|
||||
'uiSchema.x-component-props.dateFormat',
|
||||
'uiSchema.x-component-props.timeFormat',
|
||||
],
|
||||
fulfill: {
|
||||
state: {
|
||||
componentProps: {
|
||||
gmt: '{{$deps[0]}}',
|
||||
showTime: '{{$deps[1]}}',
|
||||
dateFormat: '{{$deps[2]}}',
|
||||
timeFormat: '{{$deps[3]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dependencies: ['primaryKey', 'unique', 'autoIncrement'],
|
||||
when: '{{$deps[0]||$deps[1]||$deps[2]}}',
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: true,
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
otherwise: {
|
||||
state: {
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
const initialValue: any = {
|
||||
name: `f_${uid()}`,
|
||||
...cloneDeep(schema.default),
|
||||
|
@ -64,6 +64,9 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
|
||||
description: `{{t( "If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.")}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
},
|
||||
'x-reactions': ['{{useAsyncDataSource(loadFilterTargetKeys)}}'],
|
||||
},
|
||||
footer: {
|
||||
|
@ -16,6 +16,7 @@ import set from 'lodash/set';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient, useRequest } from '../../api-client';
|
||||
import { CollectionFieldInterface } from '../../data-source';
|
||||
import { useCollectionParentRecordData } from '../../data-source/collection-record/CollectionRecordProvider';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContextProvider, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
@ -23,60 +24,23 @@ import { useResourceActionContext, useResourceContext } from '../ResourceActionP
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { useCollectionManager_deprecated } from '../hooks';
|
||||
import useDialect from '../hooks/useDialect';
|
||||
import { IField } from '../interfaces/types';
|
||||
import * as components from './components';
|
||||
|
||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||
const getSchema = (
|
||||
schema: CollectionFieldInterface,
|
||||
defaultValues: any,
|
||||
record: any,
|
||||
compile,
|
||||
getContainer,
|
||||
): ISchema => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
const properties = cloneDeep(schema.properties) as any;
|
||||
const properties = schema.getConfigureFormProperties();
|
||||
if (properties?.name) {
|
||||
properties.name['x-disabled'] = true;
|
||||
}
|
||||
|
||||
if (schema.hasDefaultValue === true) {
|
||||
properties['defaultValue'] = cloneDeep(schema.default.uiSchema) || {};
|
||||
properties.defaultValue.required = false;
|
||||
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
|
||||
properties['defaultValue']['x-decorator'] = 'FormItem';
|
||||
properties['defaultValue']['x-reactions'] = [
|
||||
{
|
||||
dependencies: [
|
||||
'uiSchema.x-component-props.gmt',
|
||||
'uiSchema.x-component-props.showTime',
|
||||
'uiSchema.x-component-props.dateFormat',
|
||||
'uiSchema.x-component-props.timeFormat',
|
||||
],
|
||||
fulfill: {
|
||||
state: {
|
||||
componentProps: {
|
||||
gmt: '{{$deps[0]}}',
|
||||
showTime: '{{$deps[1]}}',
|
||||
dateFormat: '{{$deps[2]}}',
|
||||
timeFormat: '{{$deps[3]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dependencies: ['primaryKey', 'unique', 'autoIncrement'],
|
||||
when: '{{$deps[0]||$deps[1]||$deps[2]}}',
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: true,
|
||||
value: undefined,
|
||||
},
|
||||
},
|
||||
otherwise: {
|
||||
state: {
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -92,7 +56,7 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
|
||||
return useRequest(
|
||||
() =>
|
||||
Promise.resolve({
|
||||
data: cloneDeep(omit(schema.default, ['uiSchema.rawTitle'])),
|
||||
data: cloneDeep(omit(defaultValues, ['uiSchema.rawTitle'])),
|
||||
}),
|
||||
options,
|
||||
);
|
||||
@ -230,15 +194,7 @@ export const EditFieldAction = (props) => {
|
||||
set(defaultValues.reverseField, 'name', `f_${uid()}`);
|
||||
set(defaultValues.reverseField, 'uiSchema.title', record.__parent?.title);
|
||||
}
|
||||
const schema = getSchema(
|
||||
{
|
||||
...interfaceConf,
|
||||
default: defaultValues,
|
||||
},
|
||||
record,
|
||||
compile,
|
||||
getContainer,
|
||||
);
|
||||
const schema = getSchema(interfaceConf, defaultValues, record, compile, getContainer);
|
||||
setSchema(schema);
|
||||
setVisible(true);
|
||||
}}
|
||||
|
@ -136,6 +136,6 @@ export const useResourceContext = () => {
|
||||
resource,
|
||||
collection,
|
||||
association,
|
||||
targetKey: association?.targetKey || collection?.targetKey || 'id',
|
||||
targetKey: association?.targetKey || collection?.filterTargetKey || collection?.targetKey || 'id',
|
||||
};
|
||||
};
|
||||
|
@ -52,6 +52,8 @@ import {
|
||||
UUIDFieldInterface,
|
||||
NanoidFieldInterface,
|
||||
UnixTimestampFieldInterface,
|
||||
DateFieldInterface,
|
||||
DatetimeNoTzFieldInterface,
|
||||
} from './interfaces';
|
||||
import {
|
||||
GeneralCollectionTemplate,
|
||||
@ -173,6 +175,8 @@ export class CollectionPlugin extends Plugin {
|
||||
UUIDFieldInterface,
|
||||
NanoidFieldInterface,
|
||||
UnixTimestampFieldInterface,
|
||||
DateFieldInterface,
|
||||
DatetimeNoTzFieldInterface,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ export class CreatedAtFieldInterface extends CollectionFieldInterface {
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
};
|
||||
availableTypes = ['date'];
|
||||
availableTypes = [];
|
||||
properties = {
|
||||
...defaultProps,
|
||||
...dateTimeProps,
|
||||
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { dateTimeProps, defaultProps, operators } from './properties';
|
||||
|
||||
export class DateFieldInterface extends CollectionFieldInterface {
|
||||
name = 'date';
|
||||
type = 'object';
|
||||
group = 'datetime';
|
||||
order = 3;
|
||||
title = '{{t("DateOnly")}}';
|
||||
sortable = true;
|
||||
default = {
|
||||
type: 'dateOnly',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
dateOnly: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
availableTypes = ['dateOnly'];
|
||||
hasDefaultValue = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
...dateTimeProps,
|
||||
'uiSchema.x-component-props.showTime': {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-visible': false,
|
||||
},
|
||||
};
|
||||
filterable = {
|
||||
operators: operators.datetime,
|
||||
};
|
||||
titleUsable = true;
|
||||
}
|
@ -15,23 +15,39 @@ export class DatetimeFieldInterface extends CollectionFieldInterface {
|
||||
type = 'object';
|
||||
group = 'datetime';
|
||||
order = 1;
|
||||
title = '{{t("Datetime")}}';
|
||||
title = '{{t("Datetime(with time zone)")}}';
|
||||
sortable = true;
|
||||
default = {
|
||||
type: 'date',
|
||||
defaultToCurrentTime: false,
|
||||
onUpdateToCurrentTime: false,
|
||||
timezone: true,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: false,
|
||||
utc: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
availableTypes = ['date', 'dateOnly', 'string'];
|
||||
availableTypes = ['date', 'string', 'datetime', 'datetimeTz'];
|
||||
hasDefaultValue = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
...dateTimeProps,
|
||||
defaultToCurrentTime: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Default value to current time")}}',
|
||||
},
|
||||
onUpdateToCurrentTime: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Automatically update timestamp on update")}}',
|
||||
},
|
||||
'uiSchema.x-component-props.gmt': {
|
||||
type: 'boolean',
|
||||
title: '{{t("GMT")}}',
|
||||
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { dateTimeProps, defaultProps, operators } from './properties';
|
||||
|
||||
export class DatetimeNoTzFieldInterface extends CollectionFieldInterface {
|
||||
name = 'datetimeNoTz';
|
||||
type = 'object';
|
||||
group = 'datetime';
|
||||
order = 2;
|
||||
title = '{{t("Datetime(without time zone)")}}';
|
||||
sortable = true;
|
||||
default = {
|
||||
type: 'datetimeNoTz',
|
||||
defaultToCurrentTime: false,
|
||||
onUpdateToCurrentTime: false,
|
||||
timezone: false,
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'DatePicker',
|
||||
'x-component-props': {
|
||||
showTime: false,
|
||||
utc: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
availableTypes = ['string', 'datetimeNoTz'];
|
||||
hasDefaultValue = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
...dateTimeProps,
|
||||
defaultToCurrentTime: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Default value to current server time")}}',
|
||||
},
|
||||
onUpdateToCurrentTime: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Automatically update timestamp to the current server time on update")}}',
|
||||
},
|
||||
'uiSchema.x-component-props.gmt': {
|
||||
type: 'boolean',
|
||||
title: '{{t("GMT")}}',
|
||||
'x-hidden': true,
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Use the same time zone (GMT) for all users")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
filterable = {
|
||||
operators: operators.datetime,
|
||||
};
|
||||
titleUsable = true;
|
||||
}
|
@ -46,3 +46,5 @@ export * from './sort';
|
||||
export * from './uuid';
|
||||
export * from './nanoid';
|
||||
export * from './unixTimestamp';
|
||||
export * from './dateOnly';
|
||||
export * from './datetimeNoTz';
|
||||
|
@ -10,7 +10,7 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { getUniqueKeyFromCollection } from './o2m';
|
||||
import { getUniqueKeyFromCollection } from './utils';
|
||||
import { defaultProps, relationshipType, reverseFieldProperties } from './properties';
|
||||
|
||||
export class M2MFieldInterface extends CollectionFieldInterface {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import { ISchema } from '@formily/react';
|
||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { getUniqueKeyFromCollection } from './o2m';
|
||||
import { getUniqueKeyFromCollection } from './utils';
|
||||
import { constraintsProps, relationshipType, reverseFieldProperties } from './properties';
|
||||
|
||||
export class M2OFieldInterface extends CollectionFieldInterface {
|
||||
|
@ -8,10 +8,9 @@
|
||||
*/
|
||||
|
||||
import { ISchema } from '@formily/react';
|
||||
import { Collection } from '../../data-source';
|
||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { constraintsProps, relationshipType, reverseFieldProperties } from './properties';
|
||||
|
||||
import { getUniqueKeyFromCollection } from './utils';
|
||||
export class O2MFieldInterface extends CollectionFieldInterface {
|
||||
name = 'o2m';
|
||||
type = 'object';
|
||||
@ -215,7 +214,3 @@ export class O2MFieldInterface extends CollectionFieldInterface {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function getUniqueKeyFromCollection(collection: Collection) {
|
||||
return collection?.filterTargetKey || collection?.getPrimaryKey() || 'id';
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import { ISchema } from '@formily/react';
|
||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { getUniqueKeyFromCollection } from './o2m';
|
||||
import { getUniqueKeyFromCollection } from './utils';
|
||||
import { constraintsProps, relationshipType, reverseFieldProperties } from './properties';
|
||||
|
||||
export class O2OFieldInterface extends CollectionFieldInterface {
|
||||
|
@ -10,6 +10,8 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { css } from '@emotion/css';
|
||||
import { DateFormatCom } from '../../../schema-component/antd/expiresRadio';
|
||||
export * as operators from './operators';
|
||||
|
||||
export const type: ISchema = {
|
||||
@ -225,26 +227,88 @@ export const reverseFieldProperties: Record<string, ISchema> = {
|
||||
};
|
||||
|
||||
export const dateTimeProps: { [key: string]: ISchema } = {
|
||||
'uiSchema.x-component-props.picker': {
|
||||
type: 'string',
|
||||
title: '{{t("Picker")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
default: 'date',
|
||||
enum: [
|
||||
{
|
||||
label: '{{t("Date")}}',
|
||||
value: 'date',
|
||||
},
|
||||
// {
|
||||
// label: '{{t("Week")}}',
|
||||
// value: 'week',
|
||||
// },
|
||||
{
|
||||
label: '{{t("Month")}}',
|
||||
value: 'month',
|
||||
},
|
||||
{
|
||||
label: '{{t("Quarter")}}',
|
||||
value: 'quarter',
|
||||
},
|
||||
{
|
||||
label: '{{t("Year")}}',
|
||||
value: 'year',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
'uiSchema.x-component-props.dateFormat': {
|
||||
type: 'string',
|
||||
title: '{{t("Date format")}}',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ExpiresRadio',
|
||||
'x-decorator-props': {},
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
.ant-radio-wrapper {
|
||||
display: flex;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
`,
|
||||
defaultValue: 'dddd',
|
||||
formats: ['MMMM Do YYYY', 'YYYY-MM-DD', 'MM/DD/YY', 'YYYY/MM/DD', 'DD/MM/YYYY'],
|
||||
},
|
||||
default: 'YYYY-MM-DD',
|
||||
enum: [
|
||||
{
|
||||
label: '{{t("Year/Month/Day")}}',
|
||||
value: 'YYYY/MM/DD',
|
||||
label: DateFormatCom({ format: 'MMMM Do YYYY' }),
|
||||
value: 'MMMM Do YYYY',
|
||||
},
|
||||
{
|
||||
label: '{{t("Year-Month-Day")}}',
|
||||
label: DateFormatCom({ format: 'YYYY-MM-DD' }),
|
||||
value: 'YYYY-MM-DD',
|
||||
},
|
||||
{
|
||||
label: '{{t("Day/Month/Year")}}',
|
||||
label: DateFormatCom({ format: 'MM/DD/YY' }),
|
||||
value: 'MM/DD/YY',
|
||||
},
|
||||
{
|
||||
label: DateFormatCom({ format: 'YYYY/MM/DD' }),
|
||||
value: 'YYYY/MM/DD',
|
||||
},
|
||||
{
|
||||
label: DateFormatCom({ format: 'DD/MM/YYYY' }),
|
||||
value: 'DD/MM/YYYY',
|
||||
},
|
||||
{
|
||||
label: 'custom',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
'x-reactions': {
|
||||
dependencies: ['uiSchema.x-component-props.picker'],
|
||||
fulfill: {
|
||||
state: {
|
||||
value: `{{ getPickerFormat($deps[0])}}`,
|
||||
componentProps: { picker: `{{$deps[0]}}` },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'uiSchema.x-component-props.showTime': {
|
||||
type: 'boolean',
|
||||
@ -253,10 +317,26 @@ export const dateTimeProps: { [key: string]: ISchema } = {
|
||||
'x-content': '{{t("Show time")}}',
|
||||
'x-reactions': [
|
||||
`{{(field) => {
|
||||
field.query('..[].timeFormat').take(f => {
|
||||
f.display = field.value ? 'visible' : 'none';
|
||||
});
|
||||
}}}`,
|
||||
field.query('..[].timeFormat').take(f => {
|
||||
f.display = field.value ? 'visible' : 'none';
|
||||
f.value='HH:mm:ss'
|
||||
});
|
||||
}}}`,
|
||||
{
|
||||
dependencies: ['uiSchema.x-component-props.picker'],
|
||||
when: '{{$deps[0]==="date"}}',
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
otherwise: {
|
||||
state: {
|
||||
hidden: true,
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
'uiSchema.x-component-props.timeFormat': {
|
||||
@ -275,6 +355,14 @@ export const dateTimeProps: { [key: string]: ISchema } = {
|
||||
value: 'HH:mm:ss',
|
||||
},
|
||||
],
|
||||
'x-reactions': {
|
||||
dependencies: ['uiSchema.x-component-props.showTime'],
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: `{{ !$deps[0] }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -60,13 +60,48 @@ export const object = [
|
||||
];
|
||||
|
||||
export const datetime = [
|
||||
{ label: "{{ t('is') }}", value: '$dateOn', selected: true },
|
||||
{ label: "{{ t('is not') }}", value: '$dateNotOn' },
|
||||
{ label: "{{ t('is before') }}", value: '$dateBefore' },
|
||||
{ label: "{{ t('is after') }}", value: '$dateAfter' },
|
||||
{ label: "{{ t('is on or after') }}", value: '$dateNotBefore' },
|
||||
{ label: "{{ t('is on or before') }}", value: '$dateNotAfter' },
|
||||
{ label: "{{ t('is between') }}", value: '$dateBetween', schema: { 'x-component': 'DatePicker.RangePicker' } },
|
||||
{
|
||||
label: "{{ t('is') }}",
|
||||
value: '$dateOn',
|
||||
selected: true,
|
||||
schema: { 'x-component': 'DatePicker.FilterWithPicker' },
|
||||
onlyFilterAction: true, //schema 仅在Filter.Action生效,筛选表单中不生效
|
||||
},
|
||||
{
|
||||
label: "{{ t('is not') }}",
|
||||
value: '$dateNotOn',
|
||||
schema: { 'x-component': 'DatePicker.FilterWithPicker' },
|
||||
onlyFilterAction: true,
|
||||
},
|
||||
{
|
||||
label: "{{ t('is before') }}",
|
||||
value: '$dateBefore',
|
||||
schema: { 'x-component': 'DatePicker.FilterWithPicker' },
|
||||
onlyFilterAction: true,
|
||||
},
|
||||
{
|
||||
label: "{{ t('is after') }}",
|
||||
value: '$dateAfter',
|
||||
schema: { 'x-component': 'DatePicker.FilterWithPicker' },
|
||||
onlyFilterAction: true,
|
||||
},
|
||||
{
|
||||
label: "{{ t('is on or after') }}",
|
||||
value: '$dateNotBefore',
|
||||
schema: { 'x-component': 'DatePicker.FilterWithPicker' },
|
||||
onlyFilterAction: true,
|
||||
},
|
||||
{
|
||||
label: "{{ t('is on or before') }}",
|
||||
value: '$dateNotAfter',
|
||||
schema: { 'x-component': 'DatePicker.FilterWithPicker' },
|
||||
onlyFilterAction: true,
|
||||
},
|
||||
{
|
||||
label: "{{ t('is between') }}",
|
||||
value: '$dateBetween',
|
||||
schema: { 'x-component': 'DatePicker.RangePicker' },
|
||||
},
|
||||
{ label: "{{ t('is empty') }}", value: '$empty', noValue: true },
|
||||
{ label: "{{ t('is not empty') }}", value: '$notEmpty', noValue: true },
|
||||
];
|
||||
@ -157,18 +192,18 @@ export const collection = [
|
||||
label: '{{t("is")}}',
|
||||
value: '$eq',
|
||||
selected: true,
|
||||
schema: { 'x-component': 'CollectionSelect' },
|
||||
schema: { 'x-component': 'DataSourceCollectionCascader' },
|
||||
},
|
||||
{
|
||||
label: '{{t("is not")}}',
|
||||
value: '$ne',
|
||||
schema: { 'x-component': 'CollectionSelect' },
|
||||
schema: { 'x-component': 'DataSourceCollectionCascader' },
|
||||
},
|
||||
{
|
||||
label: '{{t("is any of")}}',
|
||||
value: '$in',
|
||||
schema: {
|
||||
'x-component': 'CollectionSelect',
|
||||
'x-component': 'DataSourceCollectionCascader',
|
||||
'x-component-props': { mode: 'tags' },
|
||||
},
|
||||
},
|
||||
@ -176,7 +211,7 @@ export const collection = [
|
||||
label: '{{t("is none of")}}',
|
||||
value: '$notIn',
|
||||
schema: {
|
||||
'x-component': 'CollectionSelect',
|
||||
'x-component': 'DataSourceCollectionCascader',
|
||||
'x-component-props': { mode: 'tags' },
|
||||
},
|
||||
},
|
||||
|
@ -14,7 +14,7 @@ export class TimeFieldInterface extends CollectionFieldInterface {
|
||||
name = 'time';
|
||||
type = 'object';
|
||||
group = 'datetime';
|
||||
order = 2;
|
||||
order = 4;
|
||||
title = '{{t("Time")}}';
|
||||
sortable = true;
|
||||
default = {
|
||||
|
@ -8,31 +8,34 @@
|
||||
*/
|
||||
|
||||
import { CollectionFieldInterface } from '../../data-source/collection-field-interface/CollectionFieldInterface';
|
||||
import { dateTimeProps, defaultProps, operators } from './properties';
|
||||
|
||||
import { defaultProps, operators, dateTimeProps } from './properties';
|
||||
export class UnixTimestampFieldInterface extends CollectionFieldInterface {
|
||||
name = 'unixTimestamp';
|
||||
type = 'object';
|
||||
group = 'datetime';
|
||||
order = 1;
|
||||
order = 4;
|
||||
title = '{{t("Unix Timestamp")}}';
|
||||
sortable = true;
|
||||
default = {
|
||||
type: 'bigInt',
|
||||
type: 'unixTimestamp',
|
||||
accuracy: 'second',
|
||||
timezone: true,
|
||||
defaultToCurrentTime: false,
|
||||
onUpdateToCurrentTime: false,
|
||||
uiSchema: {
|
||||
type: 'number',
|
||||
'x-component': 'UnixTimestamp',
|
||||
'x-component-props': {
|
||||
accuracy: 'second',
|
||||
showTime: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
availableTypes = ['integer', 'bigInt'];
|
||||
hasDefaultValue = true;
|
||||
availableTypes = ['unixTimestamp'];
|
||||
hasDefaultValue = false;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
'uiSchema.x-component-props.accuracy': {
|
||||
...dateTimeProps,
|
||||
accuracy: {
|
||||
type: 'string',
|
||||
title: '{{t("Accuracy")}}',
|
||||
'x-component': 'Radio.Group',
|
||||
@ -43,9 +46,21 @@ export class UnixTimestampFieldInterface extends CollectionFieldInterface {
|
||||
{ value: 'second', label: '{{t("Second")}}' },
|
||||
],
|
||||
},
|
||||
defaultToCurrentTime: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Default value to current time")}}',
|
||||
},
|
||||
onUpdateToCurrentTime: {
|
||||
type: 'boolean',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Automatically update timestamp on update")}}',
|
||||
},
|
||||
};
|
||||
filterable = {
|
||||
operators: operators.number,
|
||||
operators: operators.datetime,
|
||||
};
|
||||
titleUsable = true;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export class UpdatedAtFieldInterface extends CollectionFieldInterface {
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
};
|
||||
availableTypes = ['date'];
|
||||
availableTypes = [];
|
||||
properties = {
|
||||
...defaultProps,
|
||||
...dateTimeProps,
|
||||
|
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 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 { Collection } from '../../../data-source';
|
||||
|
||||
export function getUniqueKeyFromCollection(collection: Collection) {
|
||||
if (collection?.filterTargetKey) {
|
||||
if (Array.isArray(collection.filterTargetKey)) {
|
||||
return collection?.filterTargetKey?.[0];
|
||||
}
|
||||
return collection?.filterTargetKey;
|
||||
}
|
||||
return collection?.getPrimaryKey() || 'id';
|
||||
}
|
@ -26,7 +26,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface {
|
||||
'x-validator': 'uuid',
|
||||
},
|
||||
};
|
||||
availableTypes = ['string', 'uid', 'uuid'];
|
||||
availableTypes = ['string', 'uuid'];
|
||||
properties = {
|
||||
'uiSchema.title': {
|
||||
type: 'string',
|
||||
|
@ -90,6 +90,9 @@ export class SqlCollectionTemplate extends CollectionTemplate {
|
||||
description: `{{t( "If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.")}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
},
|
||||
'x-reactions': ['{{useAsyncDataSource(loadFilterTargetKeys)}}'],
|
||||
},
|
||||
};
|
||||
|
@ -153,6 +153,9 @@ export class ViewCollectionTemplate extends CollectionTemplate {
|
||||
description: `{{t( "If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.")}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
multiple: true,
|
||||
},
|
||||
'x-reactions': ['{{useAsyncDataSource(loadFilterTargetKeys)}}'],
|
||||
},
|
||||
...getConfigurableProperties('category', 'description'),
|
||||
|
@ -8,9 +8,10 @@
|
||||
*/
|
||||
|
||||
import type { ISchema } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { CollectionFieldOptions } from '../collection';
|
||||
import { CollectionFieldInterfaceManager } from './CollectionFieldInterfaceManager';
|
||||
|
||||
import { defaultProps } from '../../collection-manager/interfaces/properties';
|
||||
export type CollectionFieldInterfaceFactory = new (
|
||||
collectionFieldInterfaceManager: CollectionFieldInterfaceManager,
|
||||
) => CollectionFieldInterface;
|
||||
@ -42,6 +43,7 @@ export abstract class CollectionFieldInterface {
|
||||
componentOptions?: CollectionFieldInterfaceComponentOption[];
|
||||
isAssociation?: boolean;
|
||||
operators?: any[];
|
||||
properties?: any;
|
||||
/**
|
||||
* - 如果该值为空,则在 Filter 组件中该字段会被过滤掉
|
||||
* - 如果该值为空,则不会在变量列表中看到该字段
|
||||
@ -82,4 +84,76 @@ export abstract class CollectionFieldInterface {
|
||||
}
|
||||
this.componentOptions.push(componentOption);
|
||||
}
|
||||
getConfigureFormProperties() {
|
||||
const defaultValueProps = this.hasDefaultValue ? this.getDefaultValueProperty() : {};
|
||||
return {
|
||||
...cloneDeep({ ...defaultProps, ...this?.properties }),
|
||||
...defaultValueProps,
|
||||
};
|
||||
}
|
||||
getDefaultValueProperty() {
|
||||
return {
|
||||
defaultValue: {
|
||||
...cloneDeep(this?.default?.uiSchema),
|
||||
...this?.properties?.uiSchema,
|
||||
required: false,
|
||||
title: '{{ t("Default value") }}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: [
|
||||
'uiSchema.x-component-props.gmt',
|
||||
'uiSchema.x-component-props.showTime',
|
||||
'uiSchema.x-component-props.dateFormat',
|
||||
'uiSchema.x-component-props.timeFormat',
|
||||
'uiSchema.x-component-props.picker',
|
||||
],
|
||||
fulfill: {
|
||||
state: {
|
||||
componentProps: {
|
||||
gmt: '{{$deps[0]}}',
|
||||
showTime: '{{$deps[1]}}',
|
||||
dateFormat: '{{$deps[2]}}',
|
||||
timeFormat: '{{$deps[3]}}',
|
||||
picker: '{{$deps[4]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// 当 picker 改变时,清空 defaultValue
|
||||
dependencies: ['uiSchema.x-component-props.picker'],
|
||||
fulfill: {
|
||||
state: {
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dependencies: ['primaryKey', 'unique', 'autoIncrement', 'defaultToCurrentTime'],
|
||||
when: '{{$deps[0]||$deps[1]||$deps[2]||$deps[3]}}',
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: true,
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
otherwise: {
|
||||
state: {
|
||||
hidden: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dependencies: ['uiSchema.enum'],
|
||||
fulfill: {
|
||||
state: {
|
||||
dataSource: '{{$deps[0]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export interface CollectionRecordOptions<DataType = {}, ParentDataType = {}> {
|
||||
parentRecord?: CollectionRecord<ParentDataType>;
|
||||
}
|
||||
|
||||
export class CollectionRecord<DataType = {}, ParentDataType = {}> {
|
||||
export class CollectionRecord<DataType = any, ParentDataType = {}> {
|
||||
public data?: DataType;
|
||||
public parentRecord?: CollectionRecord<ParentDataType>;
|
||||
public isNew?: boolean;
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import type { SchemaKey } from '@formily/json-schema';
|
||||
import qs from 'qs';
|
||||
import type { DataSource } from '../data-source';
|
||||
import type { CollectionFieldOptions, CollectionOptions, GetCollectionFieldPredicate } from './Collection';
|
||||
|
||||
@ -159,10 +160,23 @@ export class CollectionManager {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const getTargetKey = (collection: Collection) => collection.filterTargetKey || collection.getPrimaryKey() || 'id';
|
||||
|
||||
const buildFilterByTk = (targetKey: string | string[], record: Record<string, any>) => {
|
||||
if (Array.isArray(targetKey)) {
|
||||
const filterByTk = {};
|
||||
targetKey.forEach((key) => {
|
||||
filterByTk[key] = record[key];
|
||||
});
|
||||
return qs.stringify(filterByTk);
|
||||
} else {
|
||||
return record[targetKey];
|
||||
}
|
||||
};
|
||||
|
||||
if (collectionOrAssociation instanceof Collection) {
|
||||
const key = collectionOrAssociation.filterTargetKey || collectionOrAssociation.getPrimaryKey() || 'id';
|
||||
return collectionRecordOrAssociationRecord[key];
|
||||
const targetKey = getTargetKey(collectionOrAssociation);
|
||||
return buildFilterByTk(targetKey, collectionRecordOrAssociationRecord);
|
||||
}
|
||||
|
||||
if (collectionOrAssociation.includes('.')) {
|
||||
@ -186,9 +200,8 @@ export class CollectionManager {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const key = targetCollection?.filterTargetKey || targetCollection?.getPrimaryKey() || 'id';
|
||||
return collectionRecordOrAssociationRecord[key];
|
||||
const targetKey = getTargetKey(targetCollection);
|
||||
return buildFilterByTk(targetKey, collectionRecordOrAssociationRecord);
|
||||
}
|
||||
|
||||
getSourceKeyByAssociation(associationName: string) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import { IResource } from '@nocobase/sdk';
|
||||
import React, { FC, ReactNode, createContext, useContext, useMemo } from 'react';
|
||||
|
||||
import { isArray } from 'lodash';
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { useCollectionManager } from '../collection';
|
||||
import { CollectionRecord } from '../collection-record';
|
||||
@ -34,7 +34,16 @@ export const DataBlockResourceProvider: FC<{ children?: ReactNode }> = ({ childr
|
||||
if (association && parentRecord) {
|
||||
const sourceKey = cm.getSourceKeyByAssociation(association);
|
||||
const parentRecordData = parentRecord instanceof CollectionRecord ? parentRecord.data : parentRecord;
|
||||
return parentRecordData[sourceKey];
|
||||
if (isArray(sourceKey)) {
|
||||
const filterByTk = {};
|
||||
for (const key of sourceKey) {
|
||||
filterByTk[key] = parentRecordData?.[key];
|
||||
}
|
||||
|
||||
return encodeURIComponent(JSON.stringify(filterByTk));
|
||||
} else {
|
||||
return parentRecordData[sourceKey];
|
||||
}
|
||||
}
|
||||
}, [association, sourceId, parentRecord]);
|
||||
|
||||
|
42
packages/core/client/src/i18n/SwitchLanguage.tsx
Normal file
42
packages/core/client/src/i18n/SwitchLanguage.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 { TranslationOutlined } from '@ant-design/icons';
|
||||
import { Dropdown } from 'antd';
|
||||
import React from 'react';
|
||||
import { useAPIClient } from '../api-client';
|
||||
import languageCodes from '../locale';
|
||||
import { useSystemSettings } from '../system-settings';
|
||||
|
||||
export function SwitchLanguage() {
|
||||
const { data } = useSystemSettings();
|
||||
const api = useAPIClient();
|
||||
return (
|
||||
data?.data?.enabledLanguages.length > 1 && (
|
||||
<Dropdown
|
||||
menu={{
|
||||
onClick(info) {
|
||||
api.auth.setLocale(info.key);
|
||||
window.location.reload();
|
||||
},
|
||||
selectable: true,
|
||||
defaultSelectedKeys: [api.auth.locale],
|
||||
items: data?.data?.enabledLanguages?.map((code) => {
|
||||
return {
|
||||
key: code,
|
||||
label: languageCodes[code].label,
|
||||
};
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<TranslationOutlined style={{ fontSize: 24 }} />
|
||||
</Dropdown>
|
||||
)
|
||||
);
|
||||
}
|
@ -74,4 +74,9 @@ export { OpenModeProvider, useOpenModeContext } from './modules/popup/OpenModePr
|
||||
export { PopupContextProvider } from './modules/popup/PopupContextProvider';
|
||||
export { usePopupUtils } from './modules/popup/usePopupUtils';
|
||||
|
||||
export { SwitchLanguage } from './i18n/SwitchLanguage';
|
||||
export { VariablePopupRecordProvider } from './modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||
|
||||
export { useCurrentPopupRecord } from './modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||
|
||||
export { languageCodes } from './locale';
|
||||
|
@ -427,6 +427,7 @@
|
||||
"Option label": "Option label",
|
||||
"Color": "Color",
|
||||
"Background Color": "Background Color",
|
||||
"Text Align": "Text Align",
|
||||
"Add option": "Add option",
|
||||
"Related collection": "Related collection",
|
||||
"Allow linking to multiple records": "Allow linking to multiple records",
|
||||
@ -840,5 +841,9 @@
|
||||
"is none of": "is none of",
|
||||
"is any of": "is any of",
|
||||
"Plugin dependency version mismatch": "Plugin dependency version mismatch",
|
||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?"
|
||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?",
|
||||
"Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data",
|
||||
"Enable secondary confirmation": "Enable secondary confirmation",
|
||||
"Notification": "Notification",
|
||||
"Ellipsis overflow content": "Ellipsis overflow content"
|
||||
}
|
||||
|
@ -765,5 +765,6 @@
|
||||
"Expand All": "Expandir todo",
|
||||
"Clear default value": "Borrar valor por defecto",
|
||||
"Open in new window": "Abrir en una nueva ventana",
|
||||
"Sorry, the page you visited does not exist.": "Lo siento, la página que visitaste no existe."
|
||||
"Sorry, the page you visited does not exist.": "Lo siento, la página que visitaste no existe.",
|
||||
"Ellipsis overflow content": "Contenido de desbordamiento de elipsis"
|
||||
}
|
||||
|
@ -785,5 +785,6 @@
|
||||
"Expand All": "Tout déplier",
|
||||
"Clear default value": "Effacer la valeur par défaut",
|
||||
"Open in new window": "Ouvrir dans une nouvelle fenêtre",
|
||||
"Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas."
|
||||
"Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.",
|
||||
"Ellipsis overflow content": "Contenu de débordement avec ellipse"
|
||||
}
|
||||
|
@ -7,6 +7,15 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type LocaleOptions = {
|
||||
label: string;
|
||||
};
|
||||
@ -81,7 +90,7 @@ export const dayjsLocale = {
|
||||
'zh-TW': 'zh-tw',
|
||||
};
|
||||
|
||||
export default {
|
||||
export const languageCodes = {
|
||||
'ar-EG': { label: 'العربية' },
|
||||
'az-AZ': { label: 'Azərbaycan dili' },
|
||||
'bg-BG': { label: 'Български' },
|
||||
@ -150,3 +159,5 @@ export default {
|
||||
'zh-HK': { label: '繁體中文(香港)' },
|
||||
'zh-TW': { label: '繁體中文(台湾)' },
|
||||
};
|
||||
|
||||
export default languageCodes;
|
||||
|
@ -705,6 +705,7 @@
|
||||
"Clear default value": "デフォルト値をクリア",
|
||||
"Open in new window": "新しいウィンドウで開く",
|
||||
"Sorry, the page you visited does not exist.": "申し訳ありませんが、お探しのページは存在しません。",
|
||||
"Ellipsis overflow content": "省略記号で内容を省略",
|
||||
"NaN": "なし",
|
||||
"Settings": "設定",
|
||||
"Collection selector": "コレクションセレクタ",
|
||||
@ -1003,8 +1004,5 @@
|
||||
"Use simple pagination mode": "シンプルなページネーションモードを使用",
|
||||
"Set Template Engine": "テンプレートエンジンを設定",
|
||||
"Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "ページング時にテーブルレコードの総数取得をスキップして、読み込み速度を向上させます。データ量が多い場合にこのオプションの使用をお勧めします。",
|
||||
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "現在のユーザーにはUI設定の権限しかなく、コレクション「{{name}}」を閲覧する権限はありません。",
|
||||
"NaN": "なし",
|
||||
"true": "真",
|
||||
"false": "偽"
|
||||
}
|
||||
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "現在のユーザーにはUI設定の権限しかなく、コレクション「{{name}}」を閲覧する権限はありません。"
|
||||
}
|
||||
|
@ -876,5 +876,6 @@
|
||||
"Expand All": "모두 펼치기",
|
||||
"Clear default value": "기본값 지우기",
|
||||
"Open in new window": "새 창에서 열기",
|
||||
"Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다."
|
||||
"Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.",
|
||||
"Ellipsis overflow content": "생략 부호로 내용 줄임"
|
||||
}
|
||||
|
@ -742,5 +742,6 @@
|
||||
"Current popup record": "Registro pop-up atual",
|
||||
"Clear default value": "Limpar valor padrão",
|
||||
"Open in new window": "Abrir em nova janela",
|
||||
"Sorry, the page you visited does not exist.": "Desculpe, a página que você visitou não existe."
|
||||
"Sorry, the page you visited does not exist.": "Desculpe, a página que você visitou não existe.",
|
||||
"Ellipsis overflow content": "Conteúdo de transbordamento com reticências"
|
||||
}
|
||||
|
@ -579,5 +579,6 @@
|
||||
"Expand All": "Развернуть все",
|
||||
"Clear default value": "Очистить значение по умолчанию",
|
||||
"Open in new window": "Открыть в новом окне",
|
||||
"Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует."
|
||||
"Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует.",
|
||||
"Ellipsis overflow content": "Содержимое с многоточием при переполнении"
|
||||
}
|
||||
|
@ -577,5 +577,6 @@
|
||||
"Expand All": "Tümünü genişlet",
|
||||
"Clear default value": "Varsayılan değeri temizle",
|
||||
"Open in new window": "Yeni pencerede aç",
|
||||
"Sorry, the page you visited does not exist.": "Üzgünüz, ziyaret ettiğiniz sayfa mevcut değil."
|
||||
"Sorry, the page you visited does not exist.": "Üzgünüz, ziyaret ettiğiniz sayfa mevcut değil.",
|
||||
"Ellipsis overflow content": "Üç nokta ile taşan içerik"
|
||||
}
|
||||
|
@ -785,5 +785,6 @@
|
||||
"Expand All": "Розгорнути все",
|
||||
"Clear default value": "Очистити значення за замовчуванням",
|
||||
"Open in new window": "Відкрити в новому вікні",
|
||||
"Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує."
|
||||
"Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує.",
|
||||
"Ellipsis overflow content": "Вміст з багатокрапкою при переповненні"
|
||||
}
|
||||
|
@ -283,7 +283,7 @@
|
||||
"Checkbox group": "复选框",
|
||||
"China region": "中国行政区",
|
||||
"Date & Time": "日期 & 时间",
|
||||
"Datetime": "日期",
|
||||
"Datetime": "日期时间",
|
||||
"Relation": "关系类型",
|
||||
"Link to": "关联",
|
||||
"Link to description": "用于快速创建表关系,可兼容大多数普通场景。适合非开发人员使用。作为字段存在时,它是一个下拉选择用于选择目标数据表的数据。创建后,将同时在目标数据表中生成当前数据表的关联字段。",
|
||||
@ -448,6 +448,7 @@
|
||||
"Option label": "选项标签",
|
||||
"Color": "颜色",
|
||||
"Background Color": "背景颜色",
|
||||
"Text Align": "文本对齐",
|
||||
"Add option": "添加选项",
|
||||
"Related collection": "关系表",
|
||||
"Allow linking to multiple records": "允许关联多条记录",
|
||||
@ -901,6 +902,7 @@
|
||||
"Collections": "数据表",
|
||||
"Collection fields": "数据表字段",
|
||||
"Authentication": "用户认证",
|
||||
"Notification": "通知",
|
||||
"Logging and monitoring": "日志与监控",
|
||||
"Home page": "主页",
|
||||
"Handbook": "用户手册",
|
||||
@ -971,8 +973,32 @@
|
||||
"Use simple pagination mode": "使用简单分页模式",
|
||||
"Sorry, the page you visited does not exist.": "抱歉,你访问的页面不存在。",
|
||||
"Set Template Engine": "设置模板引擎",
|
||||
"Template engine": "模板引擎",
|
||||
"Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "在分页时跳过获取表记录总数,以加快加载速度,建议对有大量数据的数据表开启此选项",
|
||||
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "当前用户只有 UI 配置权限,但没有数据表 \"{{name}}\" 查看权限。",
|
||||
"Plugin dependency version mismatch": "插件依赖版本不一致",
|
||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "当前插件的依赖版本与应用的版本不一致,可能无法正常工作。您确定要继续激活插件吗?"
|
||||
"The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "当前插件的依赖版本与应用的版本不一致,可能无法正常工作。您确定要继续激活插件吗?",
|
||||
"Default value to current time": "设置字段默认值为当前时间",
|
||||
"Automatically update timestamp on update": "当记录更新时自动设置字段值为当前时间",
|
||||
"Default value to current server time": "设置字段默认值为当前服务端时间",
|
||||
"Automatically update timestamp to the current server time on update": "当记录更新时自动设置字段值为当前服务端时间",
|
||||
"Datetime(with time zone)": "日期时间(含时区)",
|
||||
"Datetime(without time zone)": "日期时间(不含时区)",
|
||||
"DateOnly": "仅日期",
|
||||
"Enable secondary confirmation": "启用二次确认",
|
||||
"Content": "内容",
|
||||
"Perform the Update record": "执行更新数据",
|
||||
"Are you sure you want to perform the Update record action?": "你确定执行更新数据操作吗?",
|
||||
"Perform the Custom request": "执行自定义请求",
|
||||
"Are you sure you want to perform the Custom request action": "你确定执行自定义请求操作吗?",
|
||||
"Perform the Refresh": "执行刷新",
|
||||
"Are you sure you want to perform the Refresh action?": "你确定执行刷新操作吗?",
|
||||
"Perform the Submit": "执行提交",
|
||||
"Are you sure you want to perform the Submit action?": "你确定执行提交操作吗?",
|
||||
"Perform the Trigger workflow": "执行触发工作流",
|
||||
"Are you sure you want to perform the Trigger workflow action?": "你确定执行触发工作流吗?",
|
||||
"Ellipsis overflow content": "省略超出长度的内容",
|
||||
"Picker": "选择器",
|
||||
"Quarter":"季度",
|
||||
"Switching the picker, the value and default value will be cleared":"切换选择器时,字段的值和默认值将会被清空"
|
||||
}
|
||||
|
@ -444,6 +444,7 @@
|
||||
"Option value": "選項值",
|
||||
"Option label": "選項標籤",
|
||||
"Color": "顏色",
|
||||
"Text Align": "文本對齊",
|
||||
"Add option": "新增選項",
|
||||
"Related collection": "關聯表",
|
||||
"Allow linking to multiple records": "允許關聯多條記錄",
|
||||
@ -874,5 +875,6 @@
|
||||
"Expand All": "展開全部",
|
||||
"Clear default value": "清除預設值",
|
||||
"Open in new window": "新窗口打開",
|
||||
"Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。"
|
||||
"Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。",
|
||||
"Ellipsis overflow content": "省略超出長度的內容"
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ test.describe('bulk-destroy', () => {
|
||||
await page.getByLabel('action-Action-Delete-destroy-').hover();
|
||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:bulkDelete-general').hover();
|
||||
await page.getByRole('menuitem', { name: 'Secondary confirmation' }).click();
|
||||
await page.getByLabel('Enable secondary confirmation').uncheck();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
await page.mouse.move(500, 0);
|
||||
|
||||
// 2. 选中所有行
|
||||
|
@ -52,10 +52,6 @@ export const customizeSaveRecordActionSettings = new SchemaSettings({
|
||||
{
|
||||
name: 'afterSuccessfulSubmission',
|
||||
Component: AfterSuccess,
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return isValid(fieldSchema?.['x-action-settings']?.onSuccess);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bindWorkflow',
|
||||
|
@ -28,6 +28,8 @@ import {
|
||||
} from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks/useCollectionState';
|
||||
import { SchemaSettingsModalItem } from '../../../schema-settings/SchemaSettings';
|
||||
import { useParentPopupRecord } from '../../variable/variablesProvider/VariablePopupRecordProvider';
|
||||
import { useDataBlockProps } from '../../../data-source';
|
||||
|
||||
const Tree = connect(
|
||||
AntdTree,
|
||||
@ -163,6 +165,10 @@ export const createSubmitActionSettings = new SchemaSettings({
|
||||
{
|
||||
name: 'saveMode',
|
||||
Component: SaveMode,
|
||||
useVisible() {
|
||||
const { type } = useDataBlockProps();
|
||||
return type !== 'publicForm';
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'assignFieldValues',
|
||||
@ -175,10 +181,6 @@ export const createSubmitActionSettings = new SchemaSettings({
|
||||
{
|
||||
name: 'afterSuccessfulSubmission',
|
||||
Component: AfterSuccess,
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return isValid(fieldSchema?.['x-action-settings']?.onSuccess);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'refreshDataBlockRequest',
|
||||
@ -188,6 +190,10 @@ export const createSubmitActionSettings = new SchemaSettings({
|
||||
isPopupAction: false,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const parentRecord = useParentPopupRecord();
|
||||
return !!parentRecord;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'remove',
|
||||
|
@ -71,10 +71,6 @@ export const updateSubmitActionSettings = new SchemaSettings({
|
||||
{
|
||||
name: 'afterSuccessfulSubmission',
|
||||
Component: AfterSuccess,
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return isValid(fieldSchema?.['x-action-settings']?.onSuccess);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'refreshDataBlockRequest',
|
||||
|
@ -163,11 +163,9 @@ test.describe('edit form block schema settings', () => {
|
||||
await page.getByLabel('action-Action.Link-Edit record-update-general-table-').click();
|
||||
await page.getByRole('spinbutton').fill('');
|
||||
await page.getByRole('spinbutton').fill('10');
|
||||
await expect(
|
||||
page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.formula-formula')
|
||||
.locator('.nb-read-pretty-input-number'),
|
||||
).toHaveText('11');
|
||||
await expect(page.getByLabel('block-item-CollectionField-general-form-general.formula-formula')).toHaveText(
|
||||
'formula:11',
|
||||
);
|
||||
await page.getByLabel('drawer-Action.Container-general-Edit record-mask').click();
|
||||
await expect(page.getByText('Unsaved changes')).toBeVisible();
|
||||
});
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { expect, commonFormViewPage, test } from '@nocobase/test/e2e';
|
||||
import { commonFormViewPage, expect, test } from '@nocobase/test/e2e';
|
||||
|
||||
test.describe('field schema settings', () => {
|
||||
test('linkage style color', async ({ page, mockPage, mockRecord }) => {
|
||||
@ -60,4 +60,32 @@ test.describe('field schema settings', () => {
|
||||
.locator('div.ant-formily-item-control-content-component');
|
||||
await expect(cell).toHaveCSS('background-color', 'rgb(163, 79, 204)');
|
||||
});
|
||||
|
||||
test('text align', async ({ page, mockPage, mockRecord }) => {
|
||||
const nocoPage = await mockPage(commonFormViewPage).waitForInit();
|
||||
await mockRecord('general', { singleLineText: 'asdfcedg' });
|
||||
await nocoPage.goto();
|
||||
await page.getByText('asdfcedg', { exact: true }).hover();
|
||||
await page.getByLabel('block-item-CollectionField-general-details-general.singleLineText-singleLineText').hover();
|
||||
|
||||
await page
|
||||
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-general-general.singleLineText', {
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Style' }).click();
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
await page.getByTestId('select-linkage-properties').click();
|
||||
await page.getByText('Text Align', { exact: true }).click();
|
||||
await page.getByTestId('select-single').click();
|
||||
await page.getByRole('option', { name: 'right' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
const cell = page
|
||||
.getByLabel('block-item-CollectionField-general-details-general.singleLineText-singleLineText')
|
||||
.locator('div.ant-formily-item-control-content-component');
|
||||
|
||||
await expect(cell).toHaveCSS('text-align', 'right');
|
||||
});
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ describe('FieldSettingsFormItem', () => {
|
||||
|
||||
describe('menu list', () => {
|
||||
describe('edit mode', () => {
|
||||
it('common field', async () => {
|
||||
it.skip('common field', async () => {
|
||||
await renderSettings(commonFieldOptions());
|
||||
|
||||
await checkSettings(
|
||||
@ -213,7 +213,7 @@ describe('FieldSettingsFormItem', () => {
|
||||
});
|
||||
|
||||
describe('read pretty mode', () => {
|
||||
it('common field', async () => {
|
||||
it.skip('common field', async () => {
|
||||
await renderReadPrettySettings(commonFieldOptions());
|
||||
|
||||
await checkSettings(
|
||||
@ -406,7 +406,7 @@ describe('FieldSettingsFormItem', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('Set default value', async () => {
|
||||
test.skip('Set default value', async () => {
|
||||
await renderSettings(commonFieldOptions());
|
||||
const newValue = 'new test';
|
||||
|
||||
@ -432,7 +432,7 @@ describe('FieldSettingsFormItem', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('Pattern', async () => {
|
||||
test.skip('Pattern', async () => {
|
||||
await renderSettings(associationFieldOptions());
|
||||
|
||||
await checkSettings([
|
||||
@ -464,7 +464,7 @@ describe('FieldSettingsFormItem', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('EditValidationRules', async () => {
|
||||
test.skip('EditValidationRules', async () => {
|
||||
await renderSingleSettings(commonFieldOptions(true));
|
||||
await checkSettings([
|
||||
{
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer';
|
||||
import { useDataBlockProps } from '../../../../data-source';
|
||||
|
||||
const commonOptions = {
|
||||
title: '{{t("Configure actions")}}',
|
||||
@ -25,6 +26,10 @@ const commonOptions = {
|
||||
name: 'customRequest',
|
||||
title: '{{t("Custom request")}}',
|
||||
Component: 'CustomRequestInitializer',
|
||||
useVisible() {
|
||||
const { type } = useDataBlockProps();
|
||||
return type !== 'publicForm';
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer';
|
||||
import { useCollection } from '../../../../data-source';
|
||||
import { useActionAvailable } from '../../useActionAvailable';
|
||||
const commonOptions = {
|
||||
title: "{{t('Configure actions')}}",
|
||||
@ -73,6 +72,14 @@ const commonOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customRequest',
|
||||
title: '{{t("Custom request")}}',
|
||||
Component: 'CustomRequestInitializer',
|
||||
schema: {
|
||||
'x-action': 'customize:table:request:global',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer';
|
||||
import { useCollection } from '../../../../data-source';
|
||||
import { useActionAvailable } from '../../useActionAvailable';
|
||||
|
||||
const commonOptions = {
|
||||
@ -74,6 +73,14 @@ const commonOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customRequest',
|
||||
title: '{{t("Custom request")}}',
|
||||
Component: 'CustomRequestInitializer',
|
||||
schema: {
|
||||
'x-action': 'customize:table:request:global',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -90,6 +90,14 @@ const commonOptions = {
|
||||
return collection.tree && treeTable;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customRequest',
|
||||
title: '{{t("Custom request")}}',
|
||||
Component: 'CustomRequestInitializer',
|
||||
schema: {
|
||||
'x-action': 'customize:table:request:global',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -57,4 +57,29 @@ test.describe('view', () => {
|
||||
const bgColor = await cell.evaluate((el) => getComputedStyle(el.parentElement).backgroundColor);
|
||||
expect(bgColor).toContain('163, 79, 204');
|
||||
});
|
||||
|
||||
test('text align', async ({ page, mockPage, mockRecord }) => {
|
||||
const nocoPage = await mockPage(oneTableBlockWithIntegerAndIDColumn).waitForInit();
|
||||
await mockRecord('general', { integer: '423' });
|
||||
await nocoPage.goto();
|
||||
await page.getByText('integer', { exact: true }).hover();
|
||||
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-general',
|
||||
})
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Style' }).click();
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
await page.getByTestId('select-linkage-properties').click();
|
||||
await page.getByText('Text Align', { exact: true }).click();
|
||||
await page.getByTestId('select-single').click();
|
||||
await page.getByRole('option', { name: 'right' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
const cell = page.getByRole('button', { name: '423' });
|
||||
const textAlign = await cell.evaluate((el) => getComputedStyle(el.parentElement).textAlign);
|
||||
expect(textAlign).toContain('right');
|
||||
});
|
||||
});
|
||||
|
@ -16,7 +16,6 @@ import { findFilterTargets } from '../../../../../block-provider/hooks';
|
||||
import { DataBlock, useFilterBlock } from '../../../../../filter-provider/FilterProvider';
|
||||
import { mergeFilter } from '../../../../../filter-provider/utils';
|
||||
import { removeNullCondition } from '../../../../../schema-component';
|
||||
import { useCollection } from '../../../../../data-source';
|
||||
|
||||
export const useTableBlockProps = () => {
|
||||
const field = useField<ArrayField>();
|
||||
@ -56,11 +55,12 @@ export const useTableBlockProps = () => {
|
||||
loading: ctx?.service?.loading,
|
||||
showIndex: ctx.showIndex,
|
||||
dragSort: ctx.dragSort && ctx.dragSortBy,
|
||||
rowKey: ctx.rowKey || 'id',
|
||||
rowKey: ctx.rowKey || fieldSchema?.['x-component-props']?.rowKey || 'id',
|
||||
pagination: fieldSchema?.['x-component-props']?.pagination === false ? false : field.componentProps.pagination,
|
||||
onRowSelectionChange: useCallback((selectedRowKeys) => {
|
||||
onRowSelectionChange: useCallback((selectedRowKeys, selectedRowData) => {
|
||||
ctx.field.data = ctx?.field?.data || {};
|
||||
ctx.field.data.selectedRowKeys = selectedRowKeys;
|
||||
ctx.field.data.selectedRowData = selectedRowData;
|
||||
ctx?.field?.onRowSelect?.(selectedRowKeys);
|
||||
}, []),
|
||||
onRowDragEnd: useCallback(
|
||||
|
@ -7,22 +7,22 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useApp, useSchemaToolbar } from '../../../../application';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { useCollectionManager_deprecated } from '../../../../collection-manager';
|
||||
import { useFieldComponentName } from '../../../../common/useFieldComponentName';
|
||||
import { useCollection } from '../../../../data-source';
|
||||
import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem';
|
||||
import { useDesignable } from '../../../../schema-component';
|
||||
import { useAssociationFieldContext } from '../../../../schema-component/antd/association-field/hooks';
|
||||
import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue';
|
||||
import { isPatternDisabled } from '../../../../schema-settings/isPatternDisabled';
|
||||
import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
|
||||
export const tableColumnSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:TableColumn',
|
||||
|
@ -13,11 +13,11 @@ import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { useCollection } from '../../../../data-source';
|
||||
import { useCollectionManager } from '../../../../data-source/collection/CollectionManagerProvider';
|
||||
import { useCompile, useDesignable } from '../../../../schema-component';
|
||||
import { SchemaSettingsDefaultSortingRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsDefaultSortingRules, SchemaSettingsDefaultValue } from '../../../../schema-settings';
|
||||
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
||||
import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem';
|
||||
|
||||
export const filterCollapseItemFieldSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:FilterCollapseItem',
|
||||
@ -135,6 +135,12 @@ export const filterCollapseItemFieldSettings = new SchemaSettings({
|
||||
},
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const collectionField = collection.getField(fieldSchema['name']);
|
||||
return !!collectionField?.target;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'setDefaultSortingRules',
|
||||
@ -150,6 +156,12 @@ export const filterCollapseItemFieldSettings = new SchemaSettings({
|
||||
name: collectionField?.target,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const collectionField = collection.getField(fieldSchema['name']);
|
||||
return !!collectionField?.target;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'titleField',
|
||||
@ -197,8 +209,20 @@ export const filterCollapseItemFieldSettings = new SchemaSettings({
|
||||
onChange: onTitleFieldChange,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const collectionField = collection.getField(fieldSchema['name']);
|
||||
return !!collectionField?.target;
|
||||
},
|
||||
},
|
||||
fieldComponentSettingsItem,
|
||||
{
|
||||
name: 'setDefaultValue',
|
||||
Component: SchemaSettingsDefaultValue,
|
||||
componentProps: {
|
||||
hideVariableButton: true,
|
||||
},
|
||||
} as any,
|
||||
];
|
||||
},
|
||||
},
|
||||
|
357
packages/core/client/src/modules/fields/__e2e__/ellipsis.test.ts
Normal file
357
packages/core/client/src/modules/fields/__e2e__/ellipsis.test.ts
Normal file
@ -0,0 +1,357 @@
|
||||
/**
|
||||
* 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 { expect, test } from '@nocobase/test/e2e';
|
||||
import { ellipsis } from './templates';
|
||||
|
||||
test.describe('ellipsis', () => {
|
||||
test('Input & Input.URL & Input.TextArea & Input.JSON & RichText & Markdown & MarkdownVditor', async ({
|
||||
page,
|
||||
mockPage,
|
||||
mockRecord,
|
||||
}) => {
|
||||
const nocoPage = await mockPage(ellipsis).waitForInit();
|
||||
await mockRecord('testEllipsis', {
|
||||
input: '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ这是一段很长的输入文本,用于测试省略功能。',
|
||||
inputURL: 'https://www.nocobase.com/docs/welcome/introduction/getting-started/installation/docker-compose',
|
||||
inputTextArea:
|
||||
'1234567890abcdefghijklmnopqrstuvwxyz用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果\n第二行文本,这里有更多的内容\n第三行文本,继续添加更多文字\n第四行文本,确保内容足够长\n第五行文本,用于测试多行文本的省略效果',
|
||||
inputJSON: `{
|
||||
"99999": "其他",
|
||||
"F3134": "软件销售",
|
||||
"I3007": "人工智能基础软件开发",
|
||||
"I3008": "人工智能应用软件开发",
|
||||
"I3014": "数字文化创意软件开发",
|
||||
"I3027": "信息技术咨询服务",
|
||||
"I3034": "计算机系统服务",
|
||||
"P1029": "业务培训(不含教育培训、职业技能培训等需取得许可的培训)"
|
||||
}`,
|
||||
richText:
|
||||
'用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果<h1>NocoBase简介</h1><p>1234567890abcdefghijklmnopqrstuvwxyz</p><p>这是第二段落,介绍NocoBase的主要特性</p><p>这是第三段落,讨论NocoBase的应用场景</p><ul><li>企业内部系统</li><li>工作流管理</li><li>数据分析平台</li></ul>',
|
||||
markdown:
|
||||
'用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果# NocoBase:开源无代码平台\n\n1234567890abcdefghijklmnopqrstuvwxyz\n\n## 为什么选择NocoBase?\n\n- 快速开发\n- 灵活定制\n- 开源免费\n\n### 核心功能\n\n1. 数据模型设计\n2. 界面配置\n3. 工作流引擎\n4. 权限管理\n\n> NocoBase让每个人都能轻松构建自己的软件系统',
|
||||
markdownVditor:
|
||||
'用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果用于测试多行文本的省略效果# Vditor:强大的Markdown编辑器\n\n1234567890abcdefghijklmnopqrstuvwxyz\n\n> Vditor是一个强大的Markdown编辑器,支持所见即所得、即时渲染和分屏预览等模式\n\n## 主要特性\n\n- 支持多种编辑模式\n- 丰富的快捷键\n- 自定义主题\n\n```js\nconsole.log("Vditor是NocoBase默认的Markdown编辑器");\n```\n\n更多信息请访问[Vditor官网](https://b3log.org/vditor/)',
|
||||
type: '1',
|
||||
});
|
||||
await nocoPage.goto();
|
||||
|
||||
// 1. Table -------------------------------------------------------------------------------------------------------
|
||||
await page.getByRole('button', { name: 'input', exact: true }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-testEllipsis' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByRole('button', { name: 'inputURL', exact: true }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-testEllipsis' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByRole('button', { name: 'inputTextArea', exact: true }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-testEllipsis' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByRole('button', { name: 'inputJSON', exact: true }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-testEllipsis' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByRole('button', { name: 'richText', exact: true }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-testEllipsis' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByRole('button', { name: 'markdown', exact: true }).hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-testEllipsis' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
// 2. Details -------------------------------------------------------------------------------------------------------
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-details-testEllipsis.input-input').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-details-testEllipsis.inputURL-inputURL').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-details-testEllipsis.inputTextArea-').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-details-testEllipsis.inputJSON-inputJSON').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-details-testEllipsis.richText-richText').hover();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.richText',
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-details-testEllipsis.markdown-markdown').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
// 3. List -------------------------------------------------------------------------------------------------------
|
||||
await page
|
||||
.getByLabel('block-item-CardItem-testEllipsis-list')
|
||||
.getByLabel('block-item-CollectionField-testEllipsis-list-testEllipsis.input-input')
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page
|
||||
.getByLabel('block-item-CardItem-testEllipsis-list')
|
||||
.getByLabel('block-item-CollectionField-testEllipsis-list-testEllipsis.inputURL-inputURL')
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page
|
||||
.getByLabel('block-item-CardItem-testEllipsis-list')
|
||||
.getByLabel('block-item-CollectionField-testEllipsis-list-testEllipsis.inputTextArea-')
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page
|
||||
.getByLabel('block-item-CardItem-testEllipsis-list')
|
||||
.getByLabel('block-item-CollectionField-testEllipsis-list-testEllipsis.inputJSON-inputJSON')
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page
|
||||
.getByLabel('block-item-CardItem-testEllipsis-list')
|
||||
.getByLabel('block-item-CollectionField-testEllipsis-list-testEllipsis.richText-richText')
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.richText',
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page
|
||||
.getByLabel('block-item-CardItem-testEllipsis-list')
|
||||
.getByLabel('block-item-CollectionField-testEllipsis-list-testEllipsis.markdown-markdown')
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
// 4. GridCard -------------------------------------------------------------------------------------------------------
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.input-input').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.inputURL-inputURL').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.inputURL-inputURL').hover();
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.inputURL',
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.inputTextArea-').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.inputJSON-').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.richText-richText').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-grid-card-testEllipsis.markdown-markdown').hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-' })
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
// 5. Kanban -------------------------------------------------------------------------------------------------------
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-kanban-testEllipsis.input-input').hover();
|
||||
await page
|
||||
.getByTestId('card-1')
|
||||
.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.input', {
|
||||
exact: true,
|
||||
})
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-kanban-testEllipsis.inputURL-inputURL').hover();
|
||||
await page
|
||||
.getByTestId('card-1')
|
||||
.getByLabel(
|
||||
'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.inputURL',
|
||||
{
|
||||
exact: true,
|
||||
},
|
||||
)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-kanban-testEllipsis.inputTextArea-').hover();
|
||||
await page
|
||||
.getByTestId('card-1')
|
||||
.getByLabel(
|
||||
'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.inputTextArea',
|
||||
{
|
||||
exact: true,
|
||||
},
|
||||
)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-kanban-testEllipsis.inputJSON-').hover();
|
||||
await page
|
||||
.getByTestId('card-1')
|
||||
.getByLabel(
|
||||
'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.inputJSON',
|
||||
{
|
||||
exact: true,
|
||||
},
|
||||
)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-kanban-testEllipsis.richText-richText').hover();
|
||||
await page
|
||||
.getByTestId('card-1')
|
||||
.getByLabel(
|
||||
'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.richText',
|
||||
{
|
||||
exact: true,
|
||||
},
|
||||
)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.getByLabel('block-item-CollectionField-testEllipsis-kanban-testEllipsis.markdown-markdown').hover();
|
||||
await page
|
||||
.getByTestId('card-1')
|
||||
.getByLabel(
|
||||
'designer-schema-settings-CollectionField-fieldSettings:FormItem-testEllipsis-testEllipsis.markdown',
|
||||
{
|
||||
exact: true,
|
||||
},
|
||||
)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch').check();
|
||||
await expect(page.getByRole('menuitem', { name: 'Ellipsis' }).getByRole('switch')).toBeChecked({ checked: true });
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -29,3 +29,21 @@ export const datePickerComponentFieldSettings = new SchemaSettings({
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const rangePickerPickerComponentFieldSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:DatePicker.RangePicker',
|
||||
items: [
|
||||
{
|
||||
name: 'dateDisplayFormat',
|
||||
Component: SchemaSettingsDateFormat as any,
|
||||
useComponentProps() {
|
||||
const schema = useFieldSchema();
|
||||
const { fieldSchema: tableColumnSchema } = useColumnSchema();
|
||||
const fieldSchema = tableColumnSchema || schema;
|
||||
return {
|
||||
fieldSchema,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { ellipsisSettingsItem } from '../Input/inputComponentSettings';
|
||||
|
||||
export const inputJSONSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:Input.JSON',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { ellipsisSettingsItem } from '../Input/inputComponentSettings';
|
||||
|
||||
export const inputTextAreaSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:Input.TextArea',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { ellipsisSettingsItem } from '../Input/inputComponentSettings';
|
||||
|
||||
export const inputURLSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:Input.URL',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 { useField, useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||
import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator';
|
||||
import { useDesignable } from '../../../../schema-component/hooks/useDesignable';
|
||||
|
||||
export const ellipsisSettingsItem: SchemaSettingsItemType = {
|
||||
name: 'ellipsis',
|
||||
type: 'switch',
|
||||
useComponentProps() {
|
||||
const { fieldSchema: tableFieldSchema, filedInstanceList } = useColumnSchema();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const formField = useField();
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const schema = tableFieldSchema || fieldSchema;
|
||||
const hidden = tableFieldSchema
|
||||
? filedInstanceList[0]
|
||||
? !filedInstanceList[0].readPretty
|
||||
: !tableFieldSchema['x-read-pretty']
|
||||
: !formField.readPretty;
|
||||
|
||||
return {
|
||||
title: t('Ellipsis overflow content'),
|
||||
checked: !!schema['x-component-props']?.ellipsis,
|
||||
hidden,
|
||||
onChange: async (checked) => {
|
||||
await dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': schema['x-uid'],
|
||||
'x-component-props': {
|
||||
...schema['x-component-props'],
|
||||
ellipsis: checked,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (tableFieldSchema && filedInstanceList) {
|
||||
filedInstanceList.forEach((fieldInstance) => {
|
||||
fieldInstance.componentProps.ellipsis = checked;
|
||||
});
|
||||
} else {
|
||||
formField.componentProps.ellipsis = checked;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const inputComponentSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:Input',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { ellipsisSettingsItem } from '../Input/inputComponentSettings';
|
||||
|
||||
export const markdownSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:Markdown',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { ellipsisSettingsItem } from '../Input/inputComponentSettings';
|
||||
|
||||
export const markdownVditorSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:MarkdownVditor',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { ellipsisSettingsItem } from '../Input/inputComponentSettings';
|
||||
|
||||
export const richTextSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:RichText',
|
||||
items: [ellipsisSettingsItem],
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user