From 0cbfa0a52177cce6b5400107a639d97de0b4e7a9 Mon Sep 17 00:00:00 2001 From: chenos Date: Sat, 19 Nov 2022 18:15:42 +0800 Subject: [PATCH] Revert "refactor: formula plugin (#1082)" This reverts commit 0469b8c44d4c89c403bd8bde68053c769159952e. --- .../client/src/plugins/excel-formula-field.ts | 1 - .../client/src/plugins/math-formula-field.ts | 1 - .../CollectionManagerProvider.tsx | 6 +- .../Configuration/AddFieldAction.tsx | 24 +- .../components/CollectionFieldInterface.tsx | 16 -- .../Configuration/interfaces.tsx | 36 ++- .../Configuration/schemas/collectionFields.ts | 4 +- .../hooks/useCollectionManager.ts | 2 +- .../client/src/collection-manager/index.tsx | 6 +- .../collection-manager/interfaces/formula.ts} | 26 +- .../collection-manager/interfaces/index.ts | 4 +- .../collection-manager/interfaces/integer.ts | 7 +- .../src/collection-manager/interfaces/json.ts | 6 +- .../interfaces/properties/index.ts | 1 - .../interfaces/sequence.tsx | 12 +- .../core/client/src/formula/Expression.tsx | 224 ------------------ packages/core/client/src/formula/Result.tsx | 53 ----- .../core/client/src/formula/demos/demo2.tsx | 51 ---- packages/core/client/src/formula/index.md | 10 - packages/core/client/src/formula/index.tsx | 9 - packages/core/client/src/index.tsx | 1 - .../antd/formula-input/Compute.tsx | 36 +++ .../antd/formula-input/FormulaInput.tsx | 121 ++++++++++ .../antd/formula-input/index.ts | 6 + .../client/src/schema-component/antd/index.ts | 3 +- .../__tests__/fields/formula-field.test.ts | 69 ++++++ packages/core/database/src/fields/field.ts | 4 +- .../database/src/fields/formula-field.ts} | 32 +-- packages/core/database/src/fields/index.ts | 7 +- .../plugins/excel-formula-field/client.d.ts | 4 - .../plugins/excel-formula-field/client.js | 30 --- .../plugins/excel-formula-field/package.json | 12 - .../plugins/excel-formula-field/server.d.ts | 4 - .../plugins/excel-formula-field/server.js | 30 --- .../src/client/ExcelFormula/index.ts | 20 -- .../src/client/excel-formula.ts | 56 ----- .../excel-formula-field/src/client/index.tsx | 17 -- .../plugins/excel-formula-field/src/index.ts | 1 - .../src/server/excel-formula-field.ts | 108 --------- .../excel-formula-field/src/server/index.ts | 1 - .../excel-formula-field/src/server/plugin.ts | 24 -- .../excel-formula-field/src/utils/evaluate.ts | 9 - .../plugins/math-formula-field/client.d.ts | 4 - packages/plugins/math-formula-field/client.js | 30 --- .../plugins/math-formula-field/package.json | 12 - .../plugins/math-formula-field/server.d.ts | 4 - packages/plugins/math-formula-field/server.js | 30 --- .../src/client/MathFormula/index.tsx | 20 -- .../math-formula-field/src/client/index.tsx | 17 -- .../plugins/math-formula-field/src/index.ts | 1 - .../server/__tests__/formula-field.test.ts | 90 ------- .../math-formula-field/src/server/index.ts | 1 - .../math-formula-field/src/server/plugin.ts | 24 -- .../math-formula-field/src/utils/evaluate.ts | 8 - packages/presets/nocobase/package.json | 2 - packages/presets/nocobase/src/index.ts | 2 - 56 files changed, 318 insertions(+), 1021 deletions(-) delete mode 100644 packages/app/client/src/plugins/excel-formula-field.ts delete mode 100644 packages/app/client/src/plugins/math-formula-field.ts delete mode 100644 packages/core/client/src/collection-manager/Configuration/components/CollectionFieldInterface.tsx rename packages/{plugins/math-formula-field/src/client/math-formula.ts => core/client/src/collection-manager/interfaces/formula.ts} (66%) delete mode 100644 packages/core/client/src/formula/Expression.tsx delete mode 100644 packages/core/client/src/formula/Result.tsx delete mode 100644 packages/core/client/src/formula/demos/demo2.tsx delete mode 100644 packages/core/client/src/formula/index.md delete mode 100644 packages/core/client/src/formula/index.tsx create mode 100644 packages/core/client/src/schema-component/antd/formula-input/Compute.tsx create mode 100644 packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx create mode 100644 packages/core/client/src/schema-component/antd/formula-input/index.ts create mode 100644 packages/core/database/src/__tests__/fields/formula-field.test.ts rename packages/{plugins/math-formula-field/src/server/math-formula-field.ts => core/database/src/fields/formula-field.ts} (76%) delete mode 100755 packages/plugins/excel-formula-field/client.d.ts delete mode 100755 packages/plugins/excel-formula-field/client.js delete mode 100644 packages/plugins/excel-formula-field/package.json delete mode 100755 packages/plugins/excel-formula-field/server.d.ts delete mode 100755 packages/plugins/excel-formula-field/server.js delete mode 100644 packages/plugins/excel-formula-field/src/client/ExcelFormula/index.ts delete mode 100644 packages/plugins/excel-formula-field/src/client/excel-formula.ts delete mode 100644 packages/plugins/excel-formula-field/src/client/index.tsx delete mode 100644 packages/plugins/excel-formula-field/src/index.ts delete mode 100644 packages/plugins/excel-formula-field/src/server/excel-formula-field.ts delete mode 100644 packages/plugins/excel-formula-field/src/server/index.ts delete mode 100644 packages/plugins/excel-formula-field/src/server/plugin.ts delete mode 100644 packages/plugins/excel-formula-field/src/utils/evaluate.ts delete mode 100755 packages/plugins/math-formula-field/client.d.ts delete mode 100755 packages/plugins/math-formula-field/client.js delete mode 100644 packages/plugins/math-formula-field/package.json delete mode 100755 packages/plugins/math-formula-field/server.d.ts delete mode 100755 packages/plugins/math-formula-field/server.js delete mode 100644 packages/plugins/math-formula-field/src/client/MathFormula/index.tsx delete mode 100644 packages/plugins/math-formula-field/src/client/index.tsx delete mode 100644 packages/plugins/math-formula-field/src/index.ts delete mode 100644 packages/plugins/math-formula-field/src/server/__tests__/formula-field.test.ts delete mode 100644 packages/plugins/math-formula-field/src/server/index.ts delete mode 100644 packages/plugins/math-formula-field/src/server/plugin.ts delete mode 100644 packages/plugins/math-formula-field/src/utils/evaluate.ts diff --git a/packages/app/client/src/plugins/excel-formula-field.ts b/packages/app/client/src/plugins/excel-formula-field.ts deleted file mode 100644 index e7bcc5fe0..000000000 --- a/packages/app/client/src/plugins/excel-formula-field.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@nocobase/plugin-excel-formula-field/client'; \ No newline at end of file diff --git a/packages/app/client/src/plugins/math-formula-field.ts b/packages/app/client/src/plugins/math-formula-field.ts deleted file mode 100644 index 9869c7c28..000000000 --- a/packages/app/client/src/plugins/math-formula-field.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@nocobase/plugin-math-formula-field/client'; \ No newline at end of file diff --git a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx index f802b323e..19d75d68e 100644 --- a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx +++ b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx @@ -1,5 +1,5 @@ import { Spin } from 'antd'; -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { useAPIClient, useRequest } from '../api-client'; import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider'; import { CollectionManagerContext } from './context'; @@ -8,13 +8,11 @@ import { CollectionManagerOptions } from './types'; export const CollectionManagerProvider: React.FC = (props) => { const { service, interfaces, collections = [], refreshCM } = props; - const ctx = useContext(CollectionManagerContext); return ( { +const getSchema = (schema: IField, record: any, compile): ISchema => { if (!schema) { return; } @@ -24,7 +24,7 @@ const getSchema = (schema: IField, record: any, compile) => { const properties = cloneDeep(schema.properties) as any; if (schema.hasDefaultValue === true) { - properties['defaultValue'] = cloneDeep(schema?.default?.uiSchema); + properties['defaultValue'] = cloneDeep(schema.default.uiSchema); properties['defaultValue']['title'] = compile('{{ t("Default value") }}'); properties['defaultValue']['x-decorator'] = 'FormItem'; } @@ -108,9 +108,9 @@ export const useCollectionFieldFormValues = () => { } delete values.autoCreateReverseField; return values; - }, - }; -}; + } + } +} const useCreateCollectionField = () => { const form = useForm(); @@ -143,7 +143,7 @@ export const AddCollectionField = (props) => { }; export const AddFieldAction = (props) => { - const { scope, getContainer, item: record, children, trigger, align } = props; + const { scope, getContainer, item: record, children,trigger ,align} = props; const { getInterface } = useCollectionManager(); const [visible, setVisible] = useState(false); const [schema, setSchema] = useState({}); @@ -164,13 +164,11 @@ export const AddFieldAction = (props) => { }} onClick={(info) => { const schema = getSchema(getInterface(info.key), record, compile); - if (schema) { - setSchema(schema); - setVisible(true); - } + setSchema(schema); + setVisible(true); }} > - {getOptions().map((option) => { + {options.map((option) => { return ( option.children.length > 0 && ( diff --git a/packages/core/client/src/collection-manager/Configuration/components/CollectionFieldInterface.tsx b/packages/core/client/src/collection-manager/Configuration/components/CollectionFieldInterface.tsx deleted file mode 100644 index ec9b4e6de..000000000 --- a/packages/core/client/src/collection-manager/Configuration/components/CollectionFieldInterface.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { observer } from '@formily/react'; -import { Tag } from 'antd'; -import React from 'react'; -import { useCompile } from '../../../schema-component'; -import { useCollectionManager } from '../../hooks'; - -export const CollectionFieldInterface = observer((props: any) => { - const { value } = props; - const { getInterface } = useCollectionManager(); - const compile = useCompile(); - const schema = getInterface(value); - - if (!schema) return null; - - return {compile(schema.title)}; -}); diff --git a/packages/core/client/src/collection-manager/Configuration/interfaces.tsx b/packages/core/client/src/collection-manager/Configuration/interfaces.tsx index 4e6622baa..373e6e3b8 100644 --- a/packages/core/client/src/collection-manager/Configuration/interfaces.tsx +++ b/packages/core/client/src/collection-manager/Configuration/interfaces.tsx @@ -31,23 +31,19 @@ registerGroupLabel('advanced', '{{t("Advanced type")}}'); registerGroupLabel('systemInfo', '{{t("System info")}}'); registerGroupLabel('others', '{{t("Others")}}'); -export const getOptions = () => { - return Object.keys(groupLabels).map((groupName) => { - return { - label: groupLabels[groupName], - children: Object.keys(fields[groupName] || {}) - .map((type) => { - const field = fields[groupName][type]; - return { - value: type, - label: field.title, - name: type, - ...fields[groupName][type], - }; - }) - .sort((a, b) => a.order - b.order), - }; - }); -}; - -export const options = getOptions(); +export const options = Object.keys(groupLabels).map((groupName) => { + return { + label: groupLabels[groupName], + children: Object.keys(fields[groupName] || {}) + .map((type) => { + const field = fields[groupName][type]; + return { + value: type, + label: field.title, + name: type, + ...fields[groupName][type], + }; + }) + .sort((a, b) => a.order - b.order), + }; +}); diff --git a/packages/core/client/src/collection-manager/Configuration/schemas/collectionFields.ts b/packages/core/client/src/collection-manager/Configuration/schemas/collectionFields.ts index 06c690cd4..695d697e7 100644 --- a/packages/core/client/src/collection-manager/Configuration/schemas/collectionFields.ts +++ b/packages/core/client/src/collection-manager/Configuration/schemas/collectionFields.ts @@ -1,6 +1,5 @@ import { ISchema } from '@formily/react'; import { CollectionOptions } from '../../types'; -import { CollectionFieldInterface } from '../components/CollectionFieldInterface'; import { options } from '../interfaces'; const collection: CollectionOptions = { @@ -156,10 +155,9 @@ export const collectionFieldSchema: ISchema = { type: 'void', 'x-decorator': 'Table.Column.Decorator', 'x-component': 'Table.Column', - title: '{{t("Field interface")}}', properties: { interface: { - 'x-component': CollectionFieldInterface, + 'x-component': 'CollectionField', 'x-read-pretty': true, }, }, diff --git a/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts b/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts index 823a1ef2a..6cb344467 100644 --- a/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts +++ b/packages/core/client/src/collection-manager/hooks/useCollectionManager.ts @@ -106,7 +106,7 @@ export const useCollectionManager = () => { if (!fieldNames?.length) { return; } - let cName: any = collectionName; + let cName = collectionName; let collectionField; while (cName && fieldNames.length > 0) { const fileName = fieldNames.shift(); diff --git a/packages/core/client/src/collection-manager/index.tsx b/packages/core/client/src/collection-manager/index.tsx index 37ec7fd6c..33f33e83a 100644 --- a/packages/core/client/src/collection-manager/index.tsx +++ b/packages/core/client/src/collection-manager/index.tsx @@ -5,12 +5,10 @@ export * from './CollectionManagerProvider'; export * from './CollectionManagerSchemaComponentProvider'; export * from './CollectionManagerShortcut'; export * from './CollectionProvider'; -export * from './Configuration'; -export { registerField, registerGroupLabel } from './Configuration/interfaces'; export * from './context'; export * from './hooks'; -export * as interfacesProperties from './interfaces/properties'; -export * from './interfaces/types'; export * from './ResourceActionProvider'; export * from './types'; +export * from './Configuration'; + diff --git a/packages/plugins/math-formula-field/src/client/math-formula.ts b/packages/core/client/src/collection-manager/interfaces/formula.ts similarity index 66% rename from packages/plugins/math-formula-field/src/client/math-formula.ts rename to packages/core/client/src/collection-manager/interfaces/formula.ts index 765fa2e64..99e41a26c 100644 --- a/packages/plugins/math-formula-field/src/client/math-formula.ts +++ b/packages/core/client/src/collection-manager/interfaces/formula.ts @@ -1,22 +1,22 @@ -import { IField, interfacesProperties } from '@nocobase/client'; -const { defaultProps, operators } = interfacesProperties; +import { defaultProps, operators } from './properties'; +import { IField } from './types'; -export const mathFormula: IField = { - name: 'mathFormula', +export const formula: IField = { + name: 'formula', type: 'object', group: 'advanced', order: 1, - title: '{{t("Math formula")}}', - description: '{{t("Compute a value based on the other fields using mathjs")}}', + title: '{{t("Formula")}}', + description: '{{t("Formula description")}}', sortable: true, default: { - type: 'mathFormula', + type: 'formula', // name, uiSchema: { type: 'number', // title, - 'x-disabled': true, - 'x-component': 'MathFormula.Result', + "x-disabled": true, + 'x-component': 'Formula.Result', 'x-component-props': { stringMode: true, step: '1', @@ -25,16 +25,16 @@ export const mathFormula: IField = { }, properties: { ...defaultProps, - expression: { + 'expression': { type: 'string', title: '{{t("Expression")}}', required: true, description: '{{t("Input +, -, *, /, ( ) to calculate, input @ to open field variables.")}}', - 'x-component': 'MathFormula.Expression', + 'x-component': 'Formula.Expression', 'x-decorator': 'FormItem', 'x-component-props': { - supports: ['number', 'percent', 'integer'], - useCurrentFields: '{{ useCurrentFields }}', + 'supports': ['number', 'percent', 'integer'], + 'useCurrentFields': '{{ useCurrentFields }}' }, }, 'uiSchema.x-component-props.step': { diff --git a/packages/core/client/src/collection-manager/interfaces/index.ts b/packages/core/client/src/collection-manager/interfaces/index.ts index bef505911..f2aa1e806 100644 --- a/packages/core/client/src/collection-manager/interfaces/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/index.ts @@ -6,6 +6,8 @@ export * from './createdAt'; export * from './createdBy'; export * from './datetime'; export * from './email'; +export * from './formula'; +export * from './sequence'; export * from './icon'; export * from './id'; export * from './input'; @@ -25,10 +27,8 @@ export * from './phone'; export * from './radioGroup'; export * from './richText'; export * from './select'; -export * from './sequence'; export * from './subTable'; export * from './textarea'; export * from './time'; export * from './updatedAt'; export * from './updatedBy'; - diff --git a/packages/core/client/src/collection-manager/interfaces/integer.ts b/packages/core/client/src/collection-manager/interfaces/integer.ts index 792bef3c9..b8b84c816 100644 --- a/packages/core/client/src/collection-manager/interfaces/integer.ts +++ b/packages/core/client/src/collection-manager/interfaces/integer.ts @@ -1,7 +1,8 @@ -import { registerValidateFormats } from '@formily/core'; -import { i18n } from '../../i18n'; import { defaultProps, operators, unique } from './properties'; import { IField } from './types'; +import { i18n } from '../../i18n'; +import { registerValidateFormats } from '@formily/core'; +import { ISchema } from '@formily/react'; registerValidateFormats({ odd: /^-?\d*[13579]$/, @@ -16,7 +17,7 @@ export const integer: IField = { title: '{{t("Integer")}}', sortable: true, default: { - type: 'bigInt', + type: 'integer', // name, uiSchema: { type: 'number', diff --git a/packages/core/client/src/collection-manager/interfaces/json.ts b/packages/core/client/src/collection-manager/interfaces/json.ts index 5943d80a0..9b3ccbce5 100644 --- a/packages/core/client/src/collection-manager/interfaces/json.ts +++ b/packages/core/client/src/collection-manager/interfaces/json.ts @@ -1,6 +1,6 @@ -import { registerValidateRules } from '@formily/core'; -import { defaultProps } from './properties'; +import { defaultProps, operators, unique } from './properties'; import { IField } from './types'; +import { registerValidateRules } from '@formily/core'; registerValidateRules({ json(value) { @@ -20,7 +20,7 @@ export const json: IField = { name: 'json', type: 'object', group: 'advanced', - order: 4, + order: 3, title: '{{t("JSON")}}', sortable: true, default: { diff --git a/packages/core/client/src/collection-manager/interfaces/properties/index.ts b/packages/core/client/src/collection-manager/interfaces/properties/index.ts index dbc2722d4..dd522a6ac 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/index.ts @@ -15,7 +15,6 @@ export const type: ISchema = { { label: 'String', value: 'string' }, { label: 'Text', value: 'text' }, { label: 'Integer', value: 'integer' }, - { label: 'BigInteger', value: 'bigInt' }, { label: 'Float', value: 'float' }, { label: 'Double', value: 'double' }, { label: 'Decimal', value: 'decimal' }, diff --git a/packages/core/client/src/collection-manager/interfaces/sequence.tsx b/packages/core/client/src/collection-manager/interfaces/sequence.tsx index 23cd3ece3..167509194 100644 --- a/packages/core/client/src/collection-manager/interfaces/sequence.tsx +++ b/packages/core/client/src/collection-manager/interfaces/sequence.tsx @@ -1,14 +1,14 @@ -import { css } from '@emotion/css'; -import { ArrayTable, FormButtonGroup, FormDrawer, FormLayout, Submit } from '@formily/antd'; +import React, { useContext } from 'react'; +import { Button, Select } from 'antd'; import { onFieldValueChange } from '@formily/core'; import { SchemaOptionsContext, useForm, useFormEffects } from '@formily/react'; -import { Button, Select } from 'antd'; -import React, { useContext } from 'react'; +import { ArrayTable, FormButtonGroup, FormDrawer, FormLayout, Submit } from '@formily/antd'; import { useTranslation } from 'react-i18next'; +import { css } from '@emotion/css'; import { Cron, SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component'; -import { defaultProps, operators, unique } from './properties'; import { IField } from './types'; +import { defaultProps, operators, unique } from './properties'; function RuleTypeSelect(props) { const compile = useCompile(); @@ -252,7 +252,7 @@ export const sequence: IField = { name: 'sequence', type: 'object', group: 'advanced', - order: 3, + order: 2, title: '{{t("Sequence")}}', sortable: true, default: { diff --git a/packages/core/client/src/formula/Expression.tsx b/packages/core/client/src/formula/Expression.tsx deleted file mode 100644 index a19af9e02..000000000 --- a/packages/core/client/src/formula/Expression.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { css } from '@emotion/css'; -import { Field, onFormSubmitValidateStart } from '@formily/core'; -import { useField, useFormEffects } from '@formily/react'; -import { Dropdown, Menu } from 'antd'; -import React, { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -function pasteHtml(html, selectPastedContent = false) { - var sel, range; - if (window.getSelection) { - // IE9 and non-IE - sel = window.getSelection(); - if (sel.getRangeAt && sel.rangeCount) { - range = sel.getRangeAt(0); - range.deleteContents(); - - // Range.createContextualFragment() would be useful here but is - // only relatively recently standardized and is not supported in - // some browsers (IE9, for one) - var el = document.createElement('div'); - el.innerHTML = html; - var frag = document.createDocumentFragment(), - node, - lastNode; - while ((node = el.firstChild)) { - lastNode = frag.appendChild(node); - } - var firstNode = frag.firstChild; - range.insertNode(frag); - - // Preserve the selection - if (lastNode) { - range = range.cloneRange(); - range.setStartAfter(lastNode); - if (selectPastedContent) { - range.setStartBefore(firstNode); - } else { - range.collapse(true); - } - sel.removeAllRanges(); - sel.addRange(range); - } - } - } -} - -const getValue = (el) => { - const values: any[] = []; - for (const node of el.childNodes) { - if (node.nodeName === 'SPAN') { - values.push(`{{${node['dataset']['key']}}}`); - } else { - values.push(node.textContent?.trim?.()); - } - } - const text = values.join(' ')?.replace(/\s+/g, ' ').trim(); - return text; -}; - -const renderExp = (exp: string, scope = {}) => { - return exp.replace(/{{([^}]+)}}/g, (_, i) => { - return scope[i.trim()] || ''; - }); -}; - -export const Expression = (props) => { - const { evaluate, value, supports, useCurrentFields } = props; - const field = useField(); - const { t } = useTranslation(); - const fields = useCurrentFields(); - const inputRef = useRef(); - const [changed, setChanged] = useState(false); - - const onChange = (value) => { - setChanged(true); - props.onChange(value); - }; - - const numColumns = new Map(); - const scope = {}; - fields - .filter((field) => supports.includes(field.interface)) - .forEach((field) => { - numColumns.set(field.name, field.uiSchema.title); - scope[field.name] = 1; - }); - const keys = Array.from(numColumns.keys()); - const [html, setHtml] = useState(() => { - const scope = {}; - for (const key of keys) { - const val = numColumns.get(key); - scope[ - key - ] = ` ${val} `; - } - return renderExp(value || '', scope); - }); - useEffect(() => { - if (changed) { - return; - } - const scope = {}; - for (const key of keys) { - const val = numColumns.get(key); - scope[ - key - ] = ` ${val} `; - } - const val = renderExp(value || '', scope); - setHtml(val); - }, [value]); - const menu = ( - - {keys.length > 0 ? ( - keys.map((key) => ( - - - - )) - ) : ( - - {t('No available fields')} - - )} - - ); - - useFormEffects(() => { - onFormSubmitValidateStart(() => { - try { - evaluate(field.value, scope); - field.feedbacks = []; - } catch (e) { - console.error(field.value, scope, (e as Error).message); - field.setFeedback({ - type: 'error', - code: 'FormulaError', - messages: [t('Formula error.')], - }); - } - }); - }); - - console.log('value, html', value, html); - - return ( - -
{ - const text = getValue(e.currentTarget); - if (e.key === 'Backspace') { - if (text && keys.map((k) => `{{${k}}}`).includes(text)) { - inputRef.current.innerHTML = ' '; - } - } - }} - onKeyUp={(e) => { - const text = getValue(e.currentTarget); - if (e.key === 'Backspace') { - // pasteHtml(' '); - } - console.log('onChange', text); - onChange(text); - }} - onClick={(e) => { - const text = getValue(e.currentTarget); - onChange(text); - console.log('onChange', text); - }} - onBlur={(e) => { - const text = getValue(e.currentTarget); - console.log('onChange', text); - onChange(text); - }} - onInput={(e) => { - const text = getValue(e.currentTarget); - console.log('onChange', text); - onChange(text); - }} - className={'ant-input'} - style={{ display: 'block' }} - ref={inputRef as any} - contentEditable - dangerouslySetInnerHTML={{ __html: html }} - /> - - ); -}; - -export default Expression; diff --git a/packages/core/client/src/formula/Result.tsx b/packages/core/client/src/formula/Result.tsx deleted file mode 100644 index e5cd17f1c..000000000 --- a/packages/core/client/src/formula/Result.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { onFormValuesChange } from '@formily/core'; -import { useField, useFieldSchema, useForm, useFormEffects } from '@formily/react'; -import { toFixedByStep } from '@nocobase/utils/client'; -import cloneDeep from 'lodash/cloneDeep'; -import * as math from 'mathjs'; -import { isNumber } from 'mathjs'; -import React, { useState } from 'react'; -import { useCollection } from '../collection-manager'; - -const ReadPretty = (props) => { - if (props?.options?.dataType !== 'string') { - return
{toFixedByStep(props.value, props.step)}
; - } - return
{props.value}
; -}; - -const Input = (props) => { - const { evaluate, options } = props; - const { dataType, expression } = options; - const form = useForm(); - const val = () => { - const scope = cloneDeep(form.values); - try { - let result = evaluate(expression, scope); - result = isNumber(result) && Number.isFinite(result) ? math.round(result, 9) : result; - return result; - } catch (error) { - return null; - } - }; - const [value, setVal] = useState(() => { - return val(); - }); - useFormEffects(() => { - onFormValuesChange(() => { - setVal(val()); - }); - }); - if (dataType !== 'string') { - return
{toFixedByStep(value, props.step)}
; - } - return
{value}
; -}; - -export const Result = (props: any) => { - const field = useField(); - const { getField } = useCollection(); - const fieldSchema = useFieldSchema(); - const options = getField(fieldSchema.name as string); - return field.readPretty ? : ; -}; - -export default Result; diff --git a/packages/core/client/src/formula/demos/demo2.tsx b/packages/core/client/src/formula/demos/demo2.tsx deleted file mode 100644 index acab9073e..000000000 --- a/packages/core/client/src/formula/demos/demo2.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/** - * title: Formula - */ -import { connect } from '@formily/react'; -import { Formula, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; -import React from 'react'; - -const Expression = connect(Formula.Expression); - -const schema = { - type: 'object', - properties: { - input: { - type: 'string', - default: '{{f1 }} + {{ f2}}', - 'x-component': 'Expression', - 'x-component-props': { - evaluate: (exp, scope) => { - return 1; - }, - supports: ['number'], - useCurrentFields: () => { - return [ - { - name: 'f1', - interface: 'number', - uiSchema: { - title: 'F1', - }, - }, - { - name: 'f2', - interface: 'number', - uiSchema: { - title: 'F2', - }, - }, - ]; - }, - }, - }, - }, -}; - -export default () => { - return ( - - - - ); -}; diff --git a/packages/core/client/src/formula/index.md b/packages/core/client/src/formula/index.md deleted file mode 100644 index 5efb7dfdf..000000000 --- a/packages/core/client/src/formula/index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -nav: - path: /client -group: - path: /client ---- - -# Formula - - diff --git a/packages/core/client/src/formula/index.tsx b/packages/core/client/src/formula/index.tsx deleted file mode 100644 index 9a59d66df..000000000 --- a/packages/core/client/src/formula/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import Expression from './Expression'; -import Result from './Result'; - -export const Formula = () => null; - -Formula.Result = Result; -Formula.Expression = Expression; - -export default Formula; diff --git a/packages/core/client/src/index.tsx b/packages/core/client/src/index.tsx index e76fdf3b7..e069e8236 100644 --- a/packages/core/client/src/index.tsx +++ b/packages/core/client/src/index.tsx @@ -11,7 +11,6 @@ export * from './board'; export * from './china-region'; export * from './collection-manager'; export * from './document-title'; -export * from './formula'; export * from './i18n'; export * from './icon'; export * from './plugin-manager'; diff --git a/packages/core/client/src/schema-component/antd/formula-input/Compute.tsx b/packages/core/client/src/schema-component/antd/formula-input/Compute.tsx new file mode 100644 index 000000000..e69293d7a --- /dev/null +++ b/packages/core/client/src/schema-component/antd/formula-input/Compute.tsx @@ -0,0 +1,36 @@ +import { onFormValuesChange } from '@formily/core'; +import { connect, mapReadPretty, useFieldSchema, useFormEffects } from '@formily/react'; +import { InputNumber } from 'antd'; +import _ from 'lodash'; +import * as math from 'mathjs'; +import React from 'react'; +import { useCollection } from '../../../collection-manager/hooks'; +import { ReadPretty } from '../input-number/ReadPretty'; + +const AntdCompute = (props) => { + const { onChange, ...others } = props; + const { getField } = useCollection(); + const fieldSchema = useFieldSchema(); + const options = getField(fieldSchema.name); + const { expression } = options; + + useFormEffects(() => { + onFormValuesChange((form) => { + const scope = _.cloneDeep(form.values); + let result; + try { + result = math.evaluate(expression, scope); + result = Number.isFinite(result) ? math.round(result, 9) : null; + } catch{} + if (onChange) { + onChange(result); + } + }); + }); + + return ; +}; + +export const Compute = connect(AntdCompute, mapReadPretty(ReadPretty)); + +export default Compute; diff --git a/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx b/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx new file mode 100644 index 000000000..94c38f649 --- /dev/null +++ b/packages/core/client/src/schema-component/antd/formula-input/FormulaInput.tsx @@ -0,0 +1,121 @@ +import { Field, onFormSubmitValidateStart } from '@formily/core'; +import { connect, mapProps, useField, useFormEffects } from '@formily/react'; +import { Menu, Dropdown } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; +import ContentEditable from 'react-contenteditable'; +import { useTranslation } from 'react-i18next'; +import * as math from 'mathjs'; + +const AntdFormulaInput = (props) => { + const { value, onChange, supports, useCurrentFields } = props; + const field = useField(); + const { t } = useTranslation(); + const fields = useCurrentFields(); + + const inputRef = useRef(); + const [dropdownVisible, setDropdownVisible] = useState(false); + const [html, setHtml] = useState(null); + + const numColumns = new Map(); + const scope = {}; + fields + .filter((field) => supports.includes(field.interface)) + .forEach((field) => { + numColumns.set(field.name, field.uiSchema.title); + scope[field.name] = 1; + }); + const keys = Array.from(numColumns.keys()); + + useEffect(() => { + if (value) { + let newHtml = value; + numColumns.forEach((value, key) => { + newHtml = newHtml.replaceAll( + key, + ``, + ); + }); + newHtml = `${newHtml}`; // set extra span for cursor focus on last position + setHtml(newHtml); + } else { + setHtml(''); + } + }, [value]); + + const menu = ( + { + const replaceFormula = field.value.replace('@', args.key); + if (onChange && replaceFormula != field.value) { + onChange(replaceFormula); + } + setDropdownVisible(false); + (inputRef.current as any).focus(); + }} + > + {keys.map((key) => ( + {numColumns.get(key)} + ))} + + ); + + const handleChange = (e) => { + if (onChange) { + if (e.currentTarget.textContent == '') { + onChange(null); + } else { + onChange(e.currentTarget.textContent); + } + } + }; + + const handleKeyDown = (e) => { + const { key } = e; + switch (key) { + case 'Enter': + e.preventDefault(); + break; + case '@': + case 'Process': + setDropdownVisible(true); + break; + default: + setDropdownVisible(false); + break; + } + }; + + useFormEffects(() => { + onFormSubmitValidateStart(() => { + try { + math.evaluate(field.value, scope); + field.feedbacks = []; + } catch (e) { + console.error(field.value, scope, (e as Error).message); + field.setFeedback({ + type: 'error', + code: 'FormulaError', + messages: [t('Formula error.')], + }); + } + }); + }); + + return ( + + + + ); +}; + +export const FormulaInput = connect(AntdFormulaInput, mapProps({})); + +export default FormulaInput; diff --git a/packages/core/client/src/schema-component/antd/formula-input/index.ts b/packages/core/client/src/schema-component/antd/formula-input/index.ts new file mode 100644 index 000000000..19f071dcb --- /dev/null +++ b/packages/core/client/src/schema-component/antd/formula-input/index.ts @@ -0,0 +1,6 @@ +import { Compute } from './Compute'; +import { FormulaInput } from './FormulaInput'; + +export const Formula: any = () => null; +Formula.Expression = FormulaInput; +Formula.Result = Compute; \ No newline at end of file diff --git a/packages/core/client/src/schema-component/antd/index.ts b/packages/core/client/src/schema-component/antd/index.ts index 67af5a630..ca5790054 100644 --- a/packages/core/client/src/schema-component/antd/index.ts +++ b/packages/core/client/src/schema-component/antd/index.ts @@ -4,13 +4,14 @@ export * from './calendar'; export * from './card-item'; export * from './cascader'; export * from './checkbox'; -export * from './color-select'; export * from './cron'; +export * from './color-select'; export * from './date-picker'; export * from './filter'; export * from './form'; export * from './form-item'; export * from './form-v2'; +export * from './formula-input'; export * from './g2plot'; export * from './grid'; export * from './icon-picker'; diff --git a/packages/core/database/src/__tests__/fields/formula-field.test.ts b/packages/core/database/src/__tests__/fields/formula-field.test.ts new file mode 100644 index 000000000..92f561fb2 --- /dev/null +++ b/packages/core/database/src/__tests__/fields/formula-field.test.ts @@ -0,0 +1,69 @@ +import { mockDatabase } from '..'; +import { Database } from '../../database'; +import { FormulaField } from '../../fields'; + +describe('formula field', () => { + let db: Database; + + beforeEach(async () => { + db = mockDatabase(); + }); + + afterEach(async () => { + await db.close(); + }); + + it('add formula field with old table, already has data.', async () => { + const Test = db.collection({ + name: 'tests', + fields: [ + { type: 'float', name: 'price' }, + { type: 'float', name: 'count' }, + ], + }); + + await db.sync(); + + const test = await Test.model.create({ + price: '1.2', + count: '2', + }); + + const expression = 'price*count'; + const field = Test.addField('sum', { type: 'formula', expression }); + + await field.sync({}); + + const updatedTest = await Test.model.findByPk(test.id); + const sum = updatedTest.get('sum'); + + const sumField = Test.getField('sum'); + expect(sum).toEqual(sumField.caculate(expression, updatedTest.toJSON())); + }); + + it('auto set formula field with create or update data', async () => { + const expression = 'price*count'; + const Test = db.collection({ + name: 'tests', + fields: [ + { type: 'float', name: 'price' }, + { type: 'float', name: 'count' }, + { name: 'sum', type: 'formula', expression }, + ], + }); + + await db.sync(); + + const test = await Test.model.create({ + price: '1.2', + count: '2', + }); + + const sumField = Test.getField('sum'); + expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON())); + + test.set('count', '6'); + await test.save(); + expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON())); + }); +}); diff --git a/packages/core/database/src/fields/field.ts b/packages/core/database/src/fields/field.ts index e9ebe238c..a5ffd1d31 100644 --- a/packages/core/database/src/fields/field.ts +++ b/packages/core/database/src/fields/field.ts @@ -5,12 +5,12 @@ import { ModelIndexesOptions, QueryInterfaceOptions, SyncOptions, - Transactionable + Transactionable, } from 'sequelize'; import { Collection } from '../collection'; import { Database } from '../database'; -import { InheritedCollection } from '../inherited-collection'; import { ModelEventTypes } from '../types'; +import { InheritedCollection } from '../inherited-collection'; export interface FieldContext { database: Database; diff --git a/packages/plugins/math-formula-field/src/server/math-formula-field.ts b/packages/core/database/src/fields/formula-field.ts similarity index 76% rename from packages/plugins/math-formula-field/src/server/math-formula-field.ts rename to packages/core/database/src/fields/formula-field.ts index 1f94936db..db56bc7f2 100644 --- a/packages/plugins/math-formula-field/src/server/math-formula-field.ts +++ b/packages/core/database/src/fields/formula-field.ts @@ -1,17 +1,16 @@ -import { BaseFieldOptions, Field } from '@nocobase/database'; -import * as math from 'mathjs'; import { DataTypes } from 'sequelize'; -import { evaluate } from '../utils/evaluate'; +import { BaseColumnFieldOptions, Field } from './field'; +import * as math from 'mathjs'; -export class MathFormulaField extends Field { +export class FormulaField extends Field { get dataType() { - return DataTypes.DOUBLE; + return DataTypes.FLOAT; } - calculate(expression, scope) { - let result: any = null; + caculate(expression, scope) { + let result = null; try { - result = evaluate(expression, scope); + result = math.evaluate(expression, scope); result = math.round(result, 9); } catch {} return result; @@ -27,7 +26,7 @@ export class MathFormulaField extends Field { for (const record of records) { const scope = record.toJSON(); - const result = this.calculate(expression, scope); + const result = this.caculate(expression, scope); if (result) { await record.update( { @@ -43,12 +42,12 @@ export class MathFormulaField extends Field { } }; - calculateField = async (instance) => { + caculateField = async (instance) => { const { expression, name } = this.options; const scope = instance.toJSON(); let result; try { - result = evaluate(expression, scope); + result = math.evaluate(expression, scope); result = math.round(result, 9); } catch {} if (result === 0 || result) { @@ -68,7 +67,7 @@ export class MathFormulaField extends Field { for (const record of records) { const scope = record.toJSON(); - const result = this.calculate(expression, scope); + const result = this.caculate(expression, scope); await record.update( { [name]: result, @@ -88,19 +87,20 @@ export class MathFormulaField extends Field { this.on('afterSync', this.initFieldData); // TODO: should not depends on fields table (which is defined by other plugin) this.database.on('fields.afterUpdate', this.updateFieldData); - this.on('beforeSave', this.calculateField); + this.on('beforeSave', this.caculateField); } unbind() { super.unbind(); - this.off('beforeSave', this.calculateField); + this.off('beforeSave', this.caculateField); // TODO: should not depends on fields table this.database.off('fields.afterUpdate', this.updateFieldData); this.off('afterSync', this.initFieldData); } } -export interface MathFormulaFieldOptions extends BaseFieldOptions { - type: 'mathFormula'; +export interface FormulaFieldOptions extends BaseColumnFieldOptions { + type: 'formula'; + expression: string; } diff --git a/packages/core/database/src/fields/index.ts b/packages/core/database/src/fields/index.ts index 82029bd2c..24e9a728a 100644 --- a/packages/core/database/src/fields/index.ts +++ b/packages/core/database/src/fields/index.ts @@ -17,7 +17,6 @@ import { } from './number-field'; import { PasswordFieldOptions } from './password-field'; import { RadioFieldOptions } from './radio-field'; -import { SequenceFieldOptions } from './sequence-field'; import { SortFieldOptions } from './sort-field'; import { StringFieldOptions } from './string-field'; import { TextFieldOptions } from './text-field'; @@ -25,6 +24,8 @@ import { TimeFieldOptions } from './time-field'; import { UidFieldOptions } from './uid-field'; import { UUIDFieldOptions } from './uuid-field'; import { VirtualFieldOptions } from './virtual-field'; +import { FormulaFieldOptions } from './formula-field'; +import { SequenceFieldOptions } from './sequence-field'; export * from './array-field'; export * from './belongs-to-field'; @@ -40,7 +41,6 @@ export * from './number-field'; export * from './password-field'; export * from './radio-field'; export * from './relation-field'; -export { SequenceField } from './sequence-field'; export * from './sort-field'; export * from './string-field'; export * from './text-field'; @@ -48,6 +48,8 @@ export * from './time-field'; export * from './uid-field'; export * from './uuid-field'; export * from './virtual-field'; +export * from './formula-field'; +export { SequenceField } from './sequence-field'; export type FieldOptions = | BaseFieldOptions @@ -64,6 +66,7 @@ export type FieldOptions = | SortFieldOptions | TextFieldOptions | VirtualFieldOptions + | FormulaFieldOptions | ArrayFieldOptions | TimeFieldOptions | DateFieldOptions diff --git a/packages/plugins/excel-formula-field/client.d.ts b/packages/plugins/excel-formula-field/client.d.ts deleted file mode 100755 index 765db9222..000000000 --- a/packages/plugins/excel-formula-field/client.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// @ts-nocheck -export * from './lib/client'; -export { default } from './lib/client'; - diff --git a/packages/plugins/excel-formula-field/client.js b/packages/plugins/excel-formula-field/client.js deleted file mode 100755 index 238820257..000000000 --- a/packages/plugins/excel-formula-field/client.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - -var _index = _interopRequireWildcard(require("./lib/client")); - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var _exportNames = {}; -Object.defineProperty(exports, "default", { - enumerable: true, - get: function get() { - return _index.default; - } -}); - -Object.keys(_index).forEach(function (key) { - if (key === "default" || key === "__esModule") return; - if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; - if (key in exports && exports[key] === _index[key]) return; - Object.defineProperty(exports, key, { - enumerable: true, - get: function get() { - return _index[key]; - } - }); -}); diff --git a/packages/plugins/excel-formula-field/package.json b/packages/plugins/excel-formula-field/package.json deleted file mode 100644 index e0b1f9d98..000000000 --- a/packages/plugins/excel-formula-field/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@nocobase/plugin-excel-formula-field", - "version": "0.8.0-alpha.13", - "main": "lib/server/index.js", - "dependencies": { - "@formulajs/formulajs": "^3.1.7" - }, - "devDependencies": { - "@nocobase/server": "0.8.0-alpha.13", - "@nocobase/test": "0.8.0-alpha.13" - } -} diff --git a/packages/plugins/excel-formula-field/server.d.ts b/packages/plugins/excel-formula-field/server.d.ts deleted file mode 100755 index e70edb928..000000000 --- a/packages/plugins/excel-formula-field/server.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// @ts-nocheck -export * from './lib/server'; -export { default } from './lib/server'; - diff --git a/packages/plugins/excel-formula-field/server.js b/packages/plugins/excel-formula-field/server.js deleted file mode 100755 index d06a7eb92..000000000 --- a/packages/plugins/excel-formula-field/server.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - -var _index = _interopRequireWildcard(require("./lib/server")); - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var _exportNames = {}; -Object.defineProperty(exports, "default", { - enumerable: true, - get: function get() { - return _index.default; - } -}); - -Object.keys(_index).forEach(function (key) { - if (key === "default" || key === "__esModule") return; - if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; - if (key in exports && exports[key] === _index[key]) return; - Object.defineProperty(exports, key, { - enumerable: true, - get: function get() { - return _index[key]; - } - }); -}); diff --git a/packages/plugins/excel-formula-field/src/client/ExcelFormula/index.ts b/packages/plugins/excel-formula-field/src/client/ExcelFormula/index.ts deleted file mode 100644 index b1db4d848..000000000 --- a/packages/plugins/excel-formula-field/src/client/ExcelFormula/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { connect, observer } from '@formily/react'; -import { Formula } from '@nocobase/client'; -import React from 'react'; -import { evaluate } from '../../utils/evaluate'; - -export const ExcelFormula: any = () => null; - -ExcelFormula.Result = observer((props) => - React.createElement(Formula.Result, { - ...props, - evaluate, - }), -); - -ExcelFormula.Expression = connect((props) => { - return React.createElement(Formula.Expression, { - ...props, - evaluate, - }); -}); diff --git a/packages/plugins/excel-formula-field/src/client/excel-formula.ts b/packages/plugins/excel-formula-field/src/client/excel-formula.ts deleted file mode 100644 index db9dc7ed8..000000000 --- a/packages/plugins/excel-formula-field/src/client/excel-formula.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { IField, interfacesProperties } from '@nocobase/client'; -const { defaultProps, operators } = interfacesProperties; - -export const excelFormula: IField = { - name: 'excelFormula', - type: 'object', - group: 'advanced', - order: 2, - title: '{{t("Excel formula")}}', - description: '{{t("Compute a value based on the other fields using excel formula functions")}}', - sortable: true, - default: { - type: 'excelFormula', - // name, - uiSchema: { - type: 'string', - // title, - 'x-disabled': true, - 'x-component': 'ExcelFormula.Result', - 'x-component-props': { - stringMode: true, - step: '1', - }, - }, - }, - properties: { - ...defaultProps, - dataType: { - type: 'string', - title: '{{t("Data type")}}', - 'x-component': 'Select', - 'x-decorator': 'FormItem', - default: 'number', - 'x-disabled': '{{ !createOnly }}', - enum: [ - { value: 'string', label: '{{t("String")}}' }, - { value: 'number', label: '{{t("Number")}}' }, - ], - }, - expression: { - type: 'string', - title: '{{t("Expression")}}', - required: true, - description: '{{t("Input @ to open field variables.")}}', - 'x-component': 'ExcelFormula.Expression', - 'x-decorator': 'FormItem', - 'x-component-props': { - supports: ['number', 'percent', 'integer', 'string'], - useCurrentFields: '{{ useCurrentFields }}', - }, - }, - }, - filterable: { - operators: operators.string, - }, -}; diff --git a/packages/plugins/excel-formula-field/src/client/index.tsx b/packages/plugins/excel-formula-field/src/client/index.tsx deleted file mode 100644 index b79379acb..000000000 --- a/packages/plugins/excel-formula-field/src/client/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CollectionManagerContext, registerField, SchemaComponentOptions } from '@nocobase/client'; -import React, { useContext } from 'react'; -import { excelFormula } from './excel-formula'; -import { ExcelFormula } from './ExcelFormula'; - -registerField(excelFormula.group, 'excelFormula', excelFormula); - -export default React.memo((props) => { - const ctx = useContext(CollectionManagerContext); - return ( - - - {props.children} - - - ); -}); diff --git a/packages/plugins/excel-formula-field/src/index.ts b/packages/plugins/excel-formula-field/src/index.ts deleted file mode 100644 index 7ddad5814..000000000 --- a/packages/plugins/excel-formula-field/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './server'; diff --git a/packages/plugins/excel-formula-field/src/server/excel-formula-field.ts b/packages/plugins/excel-formula-field/src/server/excel-formula-field.ts deleted file mode 100644 index 57e8e7502..000000000 --- a/packages/plugins/excel-formula-field/src/server/excel-formula-field.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { BaseFieldOptions, Field } from '@nocobase/database'; -import { DataTypes } from 'sequelize'; -import { evaluate } from '../utils/evaluate'; - -export class ExcelFormulaField extends Field { - get dataType() { - const { dataType } = this.options; - return dataType === 'string' ? DataTypes.STRING : DataTypes.DOUBLE; - } - - calculate(expression, scope) { - try { - return evaluate(expression, scope); - } catch {} - return null; - } - - initFieldData = async ({ transaction }) => { - const { expression, name } = this.options; - - const records = await this.collection.repository.find({ - order: [this.collection.model.primaryKeyAttribute], - transaction, - }); - - for (const record of records) { - const scope = record.toJSON(); - const result = this.calculate(expression, scope); - if (result) { - await record.update( - { - [name]: result, - }, - { - transaction, - silent: true, - hooks: false, - }, - ); - } - } - }; - - calculateField = async (instance) => { - const { expression, name } = this.options; - const scope = instance.toJSON(); - let result = this.calculate(expression, scope); - if (result) { - instance.set(name, result); - } - }; - - updateFieldData = async (instance, { transaction }) => { - if (this.collection.name === instance.collectionName && instance.name === this.options.name) { - this.options = Object.assign(this.options, instance.options); - const { name, expression } = this.options; - - const records = await this.collection.repository.find({ - order: [this.collection.model.primaryKeyAttribute], - transaction, - }); - - for (const record of records) { - const scope = record.toJSON(); - const result = this.calculate(expression, scope); - await record.update( - { - [name]: result, - }, - { - transaction, - silent: true, - hooks: false, - }, - ); - } - } - }; - - bind() { - super.bind(); - this.on('afterSync', this.initFieldData); - // TODO: should not depends on fields table (which is defined by other plugin) - this.database.on('fields.afterUpdate', this.updateFieldData); - this.on('beforeSave', this.calculateField); - } - - unbind() { - super.unbind(); - this.off('beforeSave', this.calculateField); - // TODO: should not depends on fields table - this.database.off('fields.afterUpdate', this.updateFieldData); - this.off('afterSync', this.initFieldData); - } - - toSequelize() { - const opts = super.toSequelize(); - delete opts.dataType; - return opts; - } - -} - -export interface ExcelFormulaFieldOptions extends BaseFieldOptions { - type: 'excelFormula'; - dataType?: 'number' | 'string'; - expression: string; -} diff --git a/packages/plugins/excel-formula-field/src/server/index.ts b/packages/plugins/excel-formula-field/src/server/index.ts deleted file mode 100644 index b68aea57f..000000000 --- a/packages/plugins/excel-formula-field/src/server/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './plugin'; diff --git a/packages/plugins/excel-formula-field/src/server/plugin.ts b/packages/plugins/excel-formula-field/src/server/plugin.ts deleted file mode 100644 index 75ed8247c..000000000 --- a/packages/plugins/excel-formula-field/src/server/plugin.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { InstallOptions, Plugin } from '@nocobase/server'; -import { ExcelFormulaField } from './excel-formula-field'; - -export class ExcelFormulaFieldPlugin extends Plugin { - afterAdd() {} - - beforeLoad() { - this.db.registerFieldTypes({ - excelFormula: ExcelFormulaField, - }); - } - - async load() {} - - async install(options?: InstallOptions) {} - - async afterEnable() {} - - async afterDisable() {} - - async remove() {} -} - -export default ExcelFormulaFieldPlugin; diff --git a/packages/plugins/excel-formula-field/src/utils/evaluate.ts b/packages/plugins/excel-formula-field/src/utils/evaluate.ts deleted file mode 100644 index cac69d182..000000000 --- a/packages/plugins/excel-formula-field/src/utils/evaluate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as formulajs from '@formulajs/formulajs'; - -export function evaluate(exp: string, scope = {}) { - const expression = exp.replace(/{{([^}]+)}}/g, (match, i) => { - return scope[i.trim()] || ''; - }); - const fn = new Function(...Object.keys(formulajs), ...Object.keys(scope), `return ${expression}`); - return fn(...Object.values(formulajs), ...Object.values(scope)); -} diff --git a/packages/plugins/math-formula-field/client.d.ts b/packages/plugins/math-formula-field/client.d.ts deleted file mode 100755 index 765db9222..000000000 --- a/packages/plugins/math-formula-field/client.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// @ts-nocheck -export * from './lib/client'; -export { default } from './lib/client'; - diff --git a/packages/plugins/math-formula-field/client.js b/packages/plugins/math-formula-field/client.js deleted file mode 100755 index 238820257..000000000 --- a/packages/plugins/math-formula-field/client.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - -var _index = _interopRequireWildcard(require("./lib/client")); - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var _exportNames = {}; -Object.defineProperty(exports, "default", { - enumerable: true, - get: function get() { - return _index.default; - } -}); - -Object.keys(_index).forEach(function (key) { - if (key === "default" || key === "__esModule") return; - if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; - if (key in exports && exports[key] === _index[key]) return; - Object.defineProperty(exports, key, { - enumerable: true, - get: function get() { - return _index[key]; - } - }); -}); diff --git a/packages/plugins/math-formula-field/package.json b/packages/plugins/math-formula-field/package.json deleted file mode 100644 index 84eb0c6e5..000000000 --- a/packages/plugins/math-formula-field/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@nocobase/plugin-math-formula-field", - "version": "0.8.0-alpha.13", - "main": "lib/server/index.js", - "dependencies": { - "mathjs": "^10.6.0" - }, - "devDependencies": { - "@nocobase/server": "0.8.0-alpha.13", - "@nocobase/test": "0.8.0-alpha.13" - } -} diff --git a/packages/plugins/math-formula-field/server.d.ts b/packages/plugins/math-formula-field/server.d.ts deleted file mode 100755 index e70edb928..000000000 --- a/packages/plugins/math-formula-field/server.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// @ts-nocheck -export * from './lib/server'; -export { default } from './lib/server'; - diff --git a/packages/plugins/math-formula-field/server.js b/packages/plugins/math-formula-field/server.js deleted file mode 100755 index d06a7eb92..000000000 --- a/packages/plugins/math-formula-field/server.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } - -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } - -var _index = _interopRequireWildcard(require("./lib/server")); - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var _exportNames = {}; -Object.defineProperty(exports, "default", { - enumerable: true, - get: function get() { - return _index.default; - } -}); - -Object.keys(_index).forEach(function (key) { - if (key === "default" || key === "__esModule") return; - if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; - if (key in exports && exports[key] === _index[key]) return; - Object.defineProperty(exports, key, { - enumerable: true, - get: function get() { - return _index[key]; - } - }); -}); diff --git a/packages/plugins/math-formula-field/src/client/MathFormula/index.tsx b/packages/plugins/math-formula-field/src/client/MathFormula/index.tsx deleted file mode 100644 index 9d62ae7b9..000000000 --- a/packages/plugins/math-formula-field/src/client/MathFormula/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { connect, observer } from '@formily/react'; -import { Formula } from '@nocobase/client'; -import React from 'react'; -import { evaluate } from '../../utils/evaluate'; - -export const MathFormula: any = () => null; - -MathFormula.Result = observer((props) => - React.createElement(Formula.Result, { - ...props, - evaluate, - }), -); - -MathFormula.Expression = connect((props) => { - return React.createElement(Formula.Expression, { - ...props, - evaluate, - }); -}); diff --git a/packages/plugins/math-formula-field/src/client/index.tsx b/packages/plugins/math-formula-field/src/client/index.tsx deleted file mode 100644 index fb1607224..000000000 --- a/packages/plugins/math-formula-field/src/client/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { CollectionManagerContext, registerField, SchemaComponentOptions } from '@nocobase/client'; -import React, { useContext } from 'react'; -import { mathFormula } from './math-formula'; -import { MathFormula } from './MathFormula'; - -registerField(mathFormula.group, 'mathFormula', mathFormula); - -export default React.memo((props) => { - const ctx = useContext(CollectionManagerContext); - return ( - - - {props.children} - - - ); -}); diff --git a/packages/plugins/math-formula-field/src/index.ts b/packages/plugins/math-formula-field/src/index.ts deleted file mode 100644 index 7ddad5814..000000000 --- a/packages/plugins/math-formula-field/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './server'; diff --git a/packages/plugins/math-formula-field/src/server/__tests__/formula-field.test.ts b/packages/plugins/math-formula-field/src/server/__tests__/formula-field.test.ts deleted file mode 100644 index 8be8b5302..000000000 --- a/packages/plugins/math-formula-field/src/server/__tests__/formula-field.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Database, mockDatabase } from '@nocobase/database'; -import { MathFormulaField } from '../math-formula-field'; - -describe('formula field', () => { - let db: Database; - - beforeEach(async () => { - db = mockDatabase(); - db.registerFieldTypes({ - mathFormula: MathFormulaField, - }); - }); - - afterEach(async () => { - await db.close(); - }); - - it('auto set formula field with create or update data', async () => { - const expression = 'price*count'; - const Test = db.collection({ - name: 'tests', - fields: [ - { type: 'float', name: 'price' }, - { type: 'float', name: 'count' }, - { name: 'sum', type: 'mathFormula', expression }, - ], - }); - - await db.sync(); - - const test = await Test.model.create({ - price: '1.2', - count: '2', - }); - - const sumField = Test.getField('sum'); - expect(test.get('sum')).toEqual(2.4); - - test.set('count', '6'); - await test.save(); - expect(test.get('sum')).toEqual(7.2); - }); - - it('auto set formula field with create or update data', async () => { - const expression = '{{price}}*{{count}}'; - const Test = db.collection({ - name: 'tests', - fields: [ - { type: 'float', name: 'price' }, - { type: 'float', name: 'count' }, - { name: 'sum', type: 'mathFormula', expression }, - ], - }); - - await db.sync(); - - const test = await Test.model.create({ - price: '1.2', - count: '2', - }); - - const sumField = Test.getField('sum'); - expect(test.get('sum')).toEqual(2.4); - - test.set('count', '6'); - await test.save(); - expect(test.get('sum')).toEqual(7.2); - }); - - it('1.22+2=3.22', async () => { - const expression = '{{a}}+{{b}}'; - const Test = db.collection({ - name: 'tests', - fields: [ - { type: 'float', name: 'a' }, - { type: 'float', name: 'b' }, - { name: 'sum', type: 'mathFormula', expression }, - ], - }); - - await db.sync(); - - const test = await Test.model.create({ - a: '2', - b: '1.22', - }); - - expect(test.get('sum')).toEqual(3.22); - }); -}); diff --git a/packages/plugins/math-formula-field/src/server/index.ts b/packages/plugins/math-formula-field/src/server/index.ts deleted file mode 100644 index b68aea57f..000000000 --- a/packages/plugins/math-formula-field/src/server/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './plugin'; diff --git a/packages/plugins/math-formula-field/src/server/plugin.ts b/packages/plugins/math-formula-field/src/server/plugin.ts deleted file mode 100644 index c150c2290..000000000 --- a/packages/plugins/math-formula-field/src/server/plugin.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { InstallOptions, Plugin } from '@nocobase/server'; -import { MathFormulaField } from './math-formula-field'; - -export class MathFormulaFieldPlugin extends Plugin { - afterAdd() {} - - beforeLoad() { - this.db.registerFieldTypes({ - mathFormula: MathFormulaField, - }); - } - - async load() {} - - async install(options?: InstallOptions) {} - - async afterEnable() {} - - async afterDisable() {} - - async remove() {} -} - -export default MathFormulaFieldPlugin; diff --git a/packages/plugins/math-formula-field/src/utils/evaluate.ts b/packages/plugins/math-formula-field/src/utils/evaluate.ts deleted file mode 100644 index 71c0eee14..000000000 --- a/packages/plugins/math-formula-field/src/utils/evaluate.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as math from 'mathjs'; - -export function evaluate(exp: string, scope = {}) { - const expression = exp.replace(/{{([^}]+)}}/g, (_, i) => { - return scope[i.trim()] || ''; - }); - return math.evaluate(expression, scope); -} diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 9be5a2198..8be388f0a 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -22,8 +22,6 @@ "@nocobase/plugin-import": "0.8.0-alpha.13", "@nocobase/plugin-sample-hello": "0.8.0-alpha.13", "@nocobase/plugin-system-settings": "0.8.0-alpha.13", - "@nocobase/plugin-math-formula-field": "0.8.0-alpha.13", - "@nocobase/plugin-excel-formula-field": "0.8.0-alpha.13", "@nocobase/plugin-ui-routes-storage": "0.8.0-alpha.13", "@nocobase/plugin-ui-schema-storage": "0.8.0-alpha.13", "@nocobase/plugin-users": "0.8.0-alpha.13", diff --git a/packages/presets/nocobase/src/index.ts b/packages/presets/nocobase/src/index.ts index feb24edf9..8d63e6ade 100644 --- a/packages/presets/nocobase/src/index.ts +++ b/packages/presets/nocobase/src/index.ts @@ -19,8 +19,6 @@ export class PresetNocoBase extends Plugin { 'export', 'import', 'audit-logs', - 'math-formula-field', - 'excel-formula-field', ]; await this.app.pm.add(plugins, { enabled: true,