diff --git a/CHANGELOG.md b/CHANGELOG.md index 860abb0..27028e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - show error message on homepage in case there is a problem with api connection - new slider field type - new card layout for forms +- field logic ### Changed diff --git a/components/form/admin/field.card.tsx b/components/form/admin/field.card.tsx index 49b24a5..99ad9b3 100644 --- a/components/form/admin/field.card.tsx +++ b/components/form/admin/field.card.tsx @@ -1,10 +1,22 @@ -import { DeleteOutlined } from '@ant-design/icons/lib' -import { Button, Card, Checkbox, Form, Input, Popconfirm, Popover, Tag } from 'antd' +import { DeleteOutlined, PlusOutlined } from '@ant-design/icons/lib' +import { + Button, + Card, + Checkbox, + Form, + Input, + Popconfirm, + Popover, + Space, + Tag, + Tooltip, +} from 'antd' import { FormInstance } from 'antd/lib/form' import { FieldData } from 'rc-field-form/lib/interface' -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { FormFieldFragment } from '../../../graphql/fragment/form.fragment' +import { FormFieldFragment, FormFieldLogicFragment } from '../../../graphql/fragment/form.fragment' +import { LogicBlock } from './logic.block' import { adminTypes } from './types' import { TextType } from './types/text.type' @@ -47,12 +59,57 @@ export const FieldCard: React.FC = (props) => { return () => clearTimeout(id) }, [nextTitle]) + const addLogic = useCallback((add: (defaults: unknown) => void, index: number) => { + return ( + + + + + + ) + }, []) + return ( nextTitle} type={'inner'} extra={
+ + {() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const slug = form.getFieldValue(['form', 'fields', field.name as string, 'slug']) + + if (!slug) { + return null + } + + return Slug: {slug} + }} + = (props) => {
} - actions={[ remove(index)} />]} > @@ -122,6 +178,35 @@ export const FieldCard: React.FC = (props) => { + + + {(logic, { add, remove, move }) => { + const addAndMove = (index) => (defaults) => { + add(defaults) + move(fields.length, index) + } + + return ( +
+ {addLogic(addAndMove(0), 0)} + {logic.map((field, index) => ( +
+ + + + {addLogic(addAndMove(index + 1), index + 1)} +
+ ))} +
+ ) + }} +
) } diff --git a/components/form/admin/logic.block.tsx b/components/form/admin/logic.block.tsx new file mode 100644 index 0000000..95d3eb5 --- /dev/null +++ b/components/form/admin/logic.block.tsx @@ -0,0 +1,198 @@ +import { DeleteOutlined } from '@ant-design/icons' +import { Alert, Button, Checkbox, Form, Mentions, Popconfirm, Select } from 'antd' +import { FormInstance } from 'antd/lib/form' +import { FieldData } from 'rc-field-form/lib/interface' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { FormFieldFragment } from '../../../graphql/fragment/form.fragment' +import { useMath } from '../../use.math' + +interface Props { + form: FormInstance + fields: FormFieldFragment[] + field: FieldData + remove: (index: number) => void + index: number +} + +export const LogicBlock: React.FC = (props) => { + const { t } = useTranslation() + const { form, field, fields, remove, index } = props + const evaluator = useMath() + + return ( +
+ + + {fields.map((field) => ( + + {field.title} + + ))} + + + + + {(form) => { + try { + const defaults = {} + + fields.forEach((field) => { + defaults[`@${field.id}`] = field.value + + if (field.slug) { + defaults[`$${field.slug}`] = field.value + } + }) + + const result = evaluator( + form.getFieldValue([...form.prefixName, field.name as string, 'formula']), + defaults + ) + + return ( + + ) + } catch (e) { + return + } + }} + + + !/NEW/i.test(field.id)) + .map((field) => ({ + value: field.id, + label: field.title, + }))} + /> + + )} + + + + {(form) => ( + + )} + + + + {(form) => ( + + )} + + + + {(form) => ( + + )} + + + +
+ { + remove(index) + }} + > + + +
+
+
+ ) +} diff --git a/components/use.math.ts b/components/use.math.ts new file mode 100644 index 0000000..ac9c66a --- /dev/null +++ b/components/use.math.ts @@ -0,0 +1,29 @@ +import { all, create } from 'mathjs' +import { useState } from 'react' + +export const useMath = (): (( + expression: string, + values?: { [id: string]: string | number } +) => boolean) => { + const [math] = useState(create(all, {})) + + return (expression, values) => { + try { + let processed = expression + + Object.keys(values).forEach((key) => { + const r = new RegExp(key.replace('$', '\\$'), 'ig') + + if (r.test(processed)) { + processed = processed.replace(r, String(values[key])) + } + }) + + const result = math.evaluate(processed) + + return Boolean(result) + } catch (e) { + throw e + } + } +} diff --git a/locales/en/form.json b/locales/en/form.json index ce99762..8f74af7 100644 --- a/locales/en/form.json +++ b/locales/en/form.json @@ -56,6 +56,15 @@ }, "hooksTab": "WebHooks", "loading": "Loading Form", + "logic": { + "add": "Add Logic", + "action": { + "jumpTo": "Jump To", + "visible": "Visible", + "disable": "Disable", + "require": "Require" + } + }, "mange": "Edit Form \"{{title}}\"", "new": "New Form", "next": "Next", diff --git a/package.json b/package.json index d0e5def..20f4055 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "i18next": "^19.9.2", "i18next-browser-languagedetector": "^6.1.0", "isomorphic-fetch": "^3.0.0", + "mathjs": "^9.3.2", "next": "^10.2.0", "next-images": "^1.7.0", "next-redux-wrapper": "^6.0.2", @@ -47,6 +48,7 @@ "devDependencies": { "@types/axios": "^0.14.0", "@types/lifeomic__axios-fetch": "^1.5.0", + "@types/mathjs": "^6.0.12", "@types/node": "^14.14.43", "@types/node-fetch": "^2.5.10", "@types/react": "^17.0.4", diff --git a/yarn.lock b/yarn.lock index e30bf23..cb33551 100644 --- a/yarn.lock +++ b/yarn.lock @@ -409,6 +409,13 @@ "@types/node-fetch" "*" axios "^0.21.0" +"@types/mathjs@^6.0.12": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-6.0.12.tgz#1c2a60352852676e10936ce150b9500d36555973" + integrity sha512-bpKs8CDJ0aOiiJguywryE/U6Wre/uftJ89xhp4aCgF4oRb3Yug2VyZ87958gmSeq4WMsvWPMs2Q5TtFv+dJtaA== + dependencies: + decimal.js "^10.0.0" + "@types/node-fetch@*": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -1291,6 +1298,11 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= +complex.js@^2.0.11: + version "2.0.13" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.13.tgz#00cf7ba082565e164813b7bbbb0ced5d2aba172a" + integrity sha512-UEWd3G3/kd3lJmsdLsDh9qfinJlujL4hIFn3Vo4/G5eqehPsgCHf2CLhFs77tVkOp2stt/jbNit7Q1XFANFltA== + compress-commons@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.0.2.tgz#d6896be386e52f37610cef9e6fa5defc58c31bd7" @@ -1507,6 +1519,11 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" +decimal.js@^10.0.0, decimal.js@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1753,6 +1770,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-latex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -2110,6 +2132,11 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +fraction.js@^4.0.13: + version "4.1.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.0.tgz#229ec1cedc8c3c7e5d2d20688ba64f0a43af5830" + integrity sha512-o9lSKpK0TDqDwTL24Hxqi6I99s942l6TYkfl6WvGWgLOIFz/YonSGKfiSeMadoiNvTfqnfOa9mjb5SGVbBK9/w== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -2646,6 +2673,11 @@ isomorphic-fetch@^3.0.0: node-fetch "^2.6.1" whatwg-fetch "^3.4.1" +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= + jest-worker@27.0.0-next.5: version "27.0.0-next.5" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.0-next.5.tgz#5985ee29b12a4e191f4aae4bb73b97971d86ec28" @@ -2925,6 +2957,20 @@ material-colors@^1.2.1: resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== +mathjs@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.3.2.tgz#6523dd5c963d200ff1cea0ff7963b10521b82185" + integrity sha512-0YKSKAeN9OkbIQrxfxnBT4kk/KlH71piWOsvVvAasyRIj/Xd/zlpc5VP/aFxwr+llOq2F3f6booPEu2fWv3yjQ== + dependencies: + complex.js "^2.0.11" + decimal.js "^10.2.1" + escape-latex "^1.2.0" + fraction.js "^4.0.13" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^2.0.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -4272,6 +4318,11 @@ scroll-into-view-if-needed@^2.2.25: dependencies: compute-scroll-into-view "^1.0.14" +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + semver@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -4658,6 +4709,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tinycolor2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" @@ -4797,6 +4853,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +typed-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8" + integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA== + typescript@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"