From 69b840c919e37e4c68b0f8bfeeb2b4bea727f302 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 26 Jan 2021 08:55:44 +0000 Subject: [PATCH 01/86] Add initial work on new Form component, spectrum styles and new text field --- .../design/AppPreview/componentStructure.json | 5 +- packages/client/yarn.lock | 5 - packages/standard-components/manifest.json | 90 ++++++++++--- packages/standard-components/package.json | 8 ++ packages/standard-components/rollup.config.js | 2 + .../standard-components/src/DataForm.svelte | 5 - .../src/DataFormWide.svelte | 5 - .../standard-components/src/forms/Form.svelte | 123 ++++++++++++++++++ .../src/forms/Input.svelte | 67 ++++++++++ .../standard-components/src/forms/index.js | 2 + packages/standard-components/src/index.js | 15 ++- .../standard-components/src/spectrum-icons.js | 32 +++++ packages/standard-components/yarn.lock | 59 ++++++++- 13 files changed, 376 insertions(+), 42 deletions(-) delete mode 100644 packages/standard-components/src/DataForm.svelte delete mode 100644 packages/standard-components/src/DataFormWide.svelte create mode 100644 packages/standard-components/src/forms/Form.svelte create mode 100644 packages/standard-components/src/forms/Input.svelte create mode 100644 packages/standard-components/src/forms/index.js create mode 100644 packages/standard-components/src/spectrum-icons.js diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index bed95c607f..e0881821c7 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -7,8 +7,7 @@ "name": "Form", "icon": "ri-file-edit-line", "children": [ - "dataform", - "dataformwide", + "form", "input", "richtext", "datepicker" @@ -59,4 +58,4 @@ "newrow" ] } -] \ No newline at end of file +] diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 6660faa3eb..1a17d999c3 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -1362,11 +1362,6 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -mustache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.1.tgz#d99beb031701ad433338e7ea65e0489416c854a2" - integrity sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA== - nanoid@^2.1.0: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 901ea0df3c..c9c13c4dba 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -115,33 +115,54 @@ } ] }, - "dataform": { + "form": { "name": "Form", "icon": "ri-file-edit-line", - "styleable": true - }, - "dataformwide": { - "name": "Wide Form", - "icon": "ri-file-edit-line", - "styleable": true - }, - "input": { - "name": "Text Field", - "description": "A textfield component that allows the user to input text.", - "icon": "ri-edit-box-line", "styleable": true, - "bindable": true, + "hasChildren": true, + "dataProvider": true, + "datasourceSetting": "datasource", "settings": [ { - "type": "text", - "label": "Label", - "key": "label" + "type": "datasource", + "label": "Data", + "key": "datasource" }, { - "label": "Type", - "key": "type", - "defaultValue": "text", - "options": ["text", "password"] + "type": "select", + "label": "Theme", + "key": "theme", + "defaultValue": "spectrum--light", + "options": [ + { + "label": "Light", + "value": "spectrum--light" + }, + { + "label": "Dark", + "value": "spectrum--dark" + }, + { + "label": "Darkest", + "value": "spectrum--darkest" + } + ] + }, + { + "type": "select", + "label": "Size", + "key": "size", + "defaultValue": "spectrum--medium", + "options": [ + { + "label": "Medium", + "value": "spectrum--medium" + }, + { + "label": "Large", + "value": "spectrum--large" + } + ] } ] }, @@ -1102,5 +1123,34 @@ "defaultValue": true } ] + }, + "input": { + "name": "Text Field", + "description": "A textfield component that allows the user to input text.", + "icon": "ri-edit-box-line", + "styleable": true, + "bindable": true, + "settings": [ + { + "type": "text", + "label": "Field", + "key": "field" + }, + { + "type": "text", + "label": "Label", + "key": "label" + }, + { + "type": "text", + "label": "Placeholder", + "key": "placeholder" + }, + { + "type": "boolean", + "label": "Required", + "key": "required" + } + ] } } diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index b5b3a64888..7ae2b94b7f 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -22,6 +22,7 @@ "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-postcss": "^3.1.5", "rollup-plugin-svelte": "^6.1.1", + "rollup-plugin-svg": "^2.0.0", "rollup-plugin-terser": "^7.0.2", "sirv-cli": "^0.4.4", "svelte": "^3.30.0" @@ -33,10 +34,17 @@ "license": "MIT", "gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491", "dependencies": { + "@adobe/spectrum-css-workflow-icons": "^1.1.0", "@budibase/bbui": "^1.52.4", "@budibase/svelte-ag-grid": "^0.0.16", + "@spectrum-css/button": "^3.0.0-beta.6", + "@spectrum-css/icon": "^3.0.0-beta.2", + "@spectrum-css/page": "^3.0.0-beta.0", + "@spectrum-css/textfield": "^3.0.0-beta.6", + "@spectrum-css/vars": "^3.0.0-beta.2", "apexcharts": "^3.22.1", "flatpickr": "^4.6.6", + "loadicons": "^1.0.0", "lodash.debounce": "^4.0.8", "markdown-it": "^12.0.2", "quill": "^1.3.7", diff --git a/packages/standard-components/rollup.config.js b/packages/standard-components/rollup.config.js index dd8fd47b60..6d0e5fbd48 100644 --- a/packages/standard-components/rollup.config.js +++ b/packages/standard-components/rollup.config.js @@ -4,6 +4,7 @@ import svelte from "rollup-plugin-svelte" import postcss from "rollup-plugin-postcss" import json from "@rollup/plugin-json" import { terser } from "rollup-plugin-terser" +import svg from "rollup-plugin-svg" import builtins from "rollup-plugin-node-builtins" @@ -33,5 +34,6 @@ export default { }), commonjs(), json(), + svg(), ], } diff --git a/packages/standard-components/src/DataForm.svelte b/packages/standard-components/src/DataForm.svelte deleted file mode 100644 index 6307bc6237..0000000000 --- a/packages/standard-components/src/DataForm.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -
diff --git a/packages/standard-components/src/DataFormWide.svelte b/packages/standard-components/src/DataFormWide.svelte deleted file mode 100644 index 4edcf839b1..0000000000 --- a/packages/standard-components/src/DataFormWide.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte new file mode 100644 index 0000000000..19cc11bc83 --- /dev/null +++ b/packages/standard-components/src/forms/Form.svelte @@ -0,0 +1,123 @@ + + +
+ {#if loaded} + + {/if} +
+ + diff --git a/packages/standard-components/src/forms/Input.svelte b/packages/standard-components/src/forms/Input.svelte new file mode 100644 index 0000000000..30f2790e67 --- /dev/null +++ b/packages/standard-components/src/forms/Input.svelte @@ -0,0 +1,67 @@ + + +{#if !field} +
Add the Field setting to start using your component!
+{:else if !fieldState} +
Form components need to be wrapped in a Form.
+{:else} +
+ {#if label} + + {/if} +
+ {#if !$fieldState.valid} + + {/if} + +
+ {#if $fieldState.error} +
+ +
+ {/if} +
+{/if} + + diff --git a/packages/standard-components/src/forms/index.js b/packages/standard-components/src/forms/index.js new file mode 100644 index 0000000000..16e243a802 --- /dev/null +++ b/packages/standard-components/src/forms/index.js @@ -0,0 +1,2 @@ +export { default as form } from "./Form.svelte" +export { default as input } from "./Input.svelte" diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js index a856e60a14..b0fbf88017 100644 --- a/packages/standard-components/src/index.js +++ b/packages/standard-components/src/index.js @@ -1,17 +1,25 @@ import "@budibase/bbui/dist/bbui.css" import "flatpickr/dist/flatpickr.css" +import "@spectrum-css/vars/dist/spectrum-global.css" +import "@spectrum-css/vars/dist/spectrum-medium.css" +import "@spectrum-css/vars/dist/spectrum-large.css" +import "@spectrum-css/vars/dist/spectrum-light.css" +import "@spectrum-css/vars/dist/spectrum-dark.css" +import "@spectrum-css/vars/dist/spectrum-darkest.css" +import "@spectrum-css/page/dist/index-vars.css" +import "@spectrum-css/button/dist/index-vars.css" + +import { loadSpectrumIcons } from "./spectrum-icons" +loadSpectrumIcons() export { default as container } from "./Container.svelte" export { default as datagrid } from "./grid/Component.svelte" export { default as screenslot } from "./ScreenSlot.svelte" export { default as button } from "./Button.svelte" -export { default as input } from "./Input.svelte" export { default as richtext } from "./RichText.svelte" export { default as list } from "./List.svelte" export { default as stackedlist } from "./StackedList.svelte" export { default as card } from "./Card.svelte" -export { default as dataform } from "./DataForm.svelte" -export { default as dataformwide } from "./DataFormWide.svelte" export { default as datepicker } from "./DatePicker.svelte" export { default as text } from "./Text.svelte" export { default as login } from "./Login.svelte" @@ -26,3 +34,4 @@ export { default as cardstat } from "./CardStat.svelte" export { default as newrow } from "./NewRow.svelte" export { default as icon } from "./Icon.svelte" export * from "./charts" +export * from "./forms" diff --git a/packages/standard-components/src/spectrum-icons.js b/packages/standard-components/src/spectrum-icons.js new file mode 100644 index 0000000000..932838339b --- /dev/null +++ b/packages/standard-components/src/spectrum-icons.js @@ -0,0 +1,32 @@ +import "@spectrum-css/icon/dist/index-vars.css" +import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg" +import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg" + +export const loadSpectrumIcons = () => { + loadIconSet("Spectrum UI Icons", SpectrumUIIcons) + loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) +} + +const loadIconSet = (name, markup) => { + // Parse the SVG + const parser = new DOMParser() + try { + const doc = parser.parseFromString(markup, "image/svg+xml") + const svg = doc.firstChild + + // Check a real SVG was parsed + if (svg && svg.tagName === "svg") { + // Hide the element + svg.style.display = "none" + + // Insert it into the head + document.head.insertBefore(svg, null) + } else { + throw "Invalid tag type for SVG definition" + } + } catch (err) { + // Swallow error, but icons won't work + console.error(err) + console.error(`Failed to parse ${name}. Icons won't work.`) + } +} diff --git a/packages/standard-components/yarn.lock b/packages/standard-components/yarn.lock index 682c048276..732c50990a 100644 --- a/packages/standard-components/yarn.lock +++ b/packages/standard-components/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/spectrum-css-workflow-icons@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.1.0.tgz#79e97f86130e1a30b84c8524cebff93a600dbb8a" + integrity sha512-07ec4Pfr+W5II4a36nto3jShBZTbpe39lB977ULYN436UTQsnAdYupezBwFd7eEvOMUawgKfqnxyR2oPxp1SMQ== + "@babel/code-frame@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -127,6 +132,33 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@spectrum-css/button@^3.0.0-beta.6": + version "3.0.0-beta.6" + resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0-beta.6.tgz#007919d3e7a6692e506dc9addcd46aee6b203b1a" + integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg== + +"@spectrum-css/icon@^3.0.0-beta.2": + version "3.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.2.tgz#2dd7258ded74501b56e5fc42d0b6f0a3f4936aeb" + integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA== + +"@spectrum-css/page@^3.0.0-beta.0": + version "3.0.0-beta.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0-beta.0.tgz#885ea41b44861c5dc3aac904536f9e93c9109b58" + integrity sha512-+OD+l3aLisykxJnHfLkdkxMS1Uj1vKGYpKil7W0r5lSWU44eHyRgb8ZK5Vri1+sUO5SSf/CTybeVwtXME9wMLA== + dependencies: + "@spectrum-css/vars" "^3.0.0-beta.2" + +"@spectrum-css/textfield@^3.0.0-beta.6": + version "3.0.0-beta.6" + resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0-beta.6.tgz#30c044ceb403d6ea82d8046fb8f767f7fe455da6" + integrity sha512-U7P8C3Xx8h5X+r+dZu1qbxceIxBn7ZSmMvJyC7MPSPcU3EwdzCUepERNGX7NrQdcX91XSNlPUOF7hZUognBwhQ== + +"@spectrum-css/vars@^3.0.0-beta.2": + version "3.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.0-beta.2.tgz#f0b3a2db44aa57b1a82e47ab392c716a3056a157" + integrity sha512-HpcRDUkSjKVWUi7+jf6zp33YszXs3qFljaaNVTVOf0m0mqjWWXHxgLrvYlFFlHp5ITbNXds5Cb7EgiXCKmVIpA== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -979,6 +1011,11 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +estree-walker@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e" + integrity sha1-va/oCVOD2EFNXcLs9MkXO225QS4= + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -1557,6 +1594,11 @@ loader-utils@^1.1.0: emojis-list "^3.0.0" json5 "^1.0.1" +loadicons@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/loadicons/-/loadicons-1.0.0.tgz#79fd9b08ef2933988c94068cbd246ef3f21cbd04" + integrity sha512-KSywiudfuOK5sTdhNMM8hwRpMxZ5TbQlU4ZijMxUFwRW7jpxUmb9YJoLIzDn7+xuxeLzCZWBmLJS2JDjDWCpsw== + local-access@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.0.1.tgz#5121258146d64e869046c642ea4f1dd39ff942bb" @@ -1662,7 +1704,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -2456,6 +2498,13 @@ rollup-plugin-svelte@^6.1.1: rollup-pluginutils "^2.8.2" sourcemap-codec "^1.4.8" +rollup-plugin-svg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-svg/-/rollup-plugin-svg-2.0.0.tgz#ce11b55e915d5b2190328c4e6632bd6b4fe12ee9" + integrity sha512-DmE7dSQHo1SC5L2uH2qul3Mjyd5oV6U1aVVkyvTLX/mUsRink7f1b1zaIm+32GEBA6EHu8H/JJi3DdWqM53ySQ== + dependencies: + rollup-pluginutils "^1.3.1" + rollup-plugin-terser@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" @@ -2466,6 +2515,14 @@ rollup-plugin-terser@^7.0.2: serialize-javascript "^4.0.0" terser "^5.0.0" +rollup-pluginutils@^1.3.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408" + integrity sha1-HhVud4+UtyVb+hs9AXi+j1xVJAg= + dependencies: + estree-walker "^0.2.1" + minimatch "^3.0.2" + rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" From 365c503224d3fd788c1edb8985d3e137bea12015 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 26 Jan 2021 14:40:44 +0000 Subject: [PATCH 02/86] Add automatic schema validation to forms and add builder settings for specific field types --- .../builder/src/builderStore/storeUtils.js | 24 +++++- .../design/AppPreview/componentStructure.json | 4 +- .../PropertyControls/FormFieldSelect.svelte | 33 ++++++++ .../PropertyControls/NumberFieldSelect.svelte | 5 ++ .../PropertyControls/OptionSelect.svelte | 13 +++- .../OptionsFieldSelect.svelte | 5 ++ .../PropertyControls/StringFieldSelect.svelte | 5 ++ .../PropertiesPanel/SettingsView.svelte | 6 ++ packages/client/src/store/builder.js | 8 +- packages/client/src/utils/styleable.js | 14 ++-- packages/standard-components/manifest.json | 53 +++++++++++-- .../standard-components/src/forms/Form.svelte | 18 +++-- .../src/forms/NumberField.svelte | 5 ++ .../src/forms/OptionsField.svelte | 1 + .../src/forms/Placeholder.svelte | 17 ++++ .../{Input.svelte => StringField.svelte} | 9 ++- .../standard-components/src/forms/index.js | 4 +- .../src/forms/validation.js | 78 +++++++++++++++++++ 18 files changed, 271 insertions(+), 31 deletions(-) create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/NumberFieldSelect.svelte create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsFieldSelect.svelte create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/StringFieldSelect.svelte create mode 100644 packages/standard-components/src/forms/NumberField.svelte create mode 100644 packages/standard-components/src/forms/OptionsField.svelte create mode 100644 packages/standard-components/src/forms/Placeholder.svelte rename packages/standard-components/src/forms/{Input.svelte => StringField.svelte} (87%) create mode 100644 packages/standard-components/src/forms/validation.js diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js index 00f5a209a3..6d0f0beab0 100644 --- a/packages/builder/src/builderStore/storeUtils.js +++ b/packages/builder/src/builderStore/storeUtils.js @@ -59,8 +59,8 @@ export const findComponentPath = (rootComponent, id, path = []) => { } /** - * Recurses through the component tree and finds all components of a certain - * type. + * Recurses through the component tree and finds all components which match + * a certain selector */ export const findAllMatchingComponents = (rootComponent, selector) => { if (!rootComponent || !selector) { @@ -81,6 +81,26 @@ export const findAllMatchingComponents = (rootComponent, selector) => { return components.reverse() } +/** + * Finds the closes parent component which matches certain criteria + */ +export const findClosestMatchingComponent = ( + rootComponent, + componentId, + selector +) => { + if (!selector) { + return null + } + const componentPath = findComponentPath(rootComponent, componentId).reverse() + for (let component of componentPath) { + if (selector(component)) { + return component + } + } + return null +} + /** * Recurses through a component tree evaluating a matching function against * components until a match is found diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index e0881821c7..9193a566b5 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -8,7 +8,9 @@ "icon": "ri-file-edit-line", "children": [ "form", - "input", + "stringfield", + "numberfield", + "optionsfield", "richtext", "datepicker" ] diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte new file mode 100644 index 0000000000..ca40946bcb --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte @@ -0,0 +1,33 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/NumberFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/NumberFieldSelect.svelte new file mode 100644 index 0000000000..ce2569cf91 --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/NumberFieldSelect.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionSelect.svelte index 3ee839fbd0..c464ed84e0 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionSelect.svelte @@ -106,7 +106,9 @@ } $: displayLabel = - selectedOption && selectedOption.label ? selectedOption.label : value || "" + selectedOption && selectedOption.label + ? selectedOption.label + : value || "Choose option"
    +
  • handleClick(null)} + class:selected={value == null || value === ''}> + Choose option +
  • {#if isOptionsObject} {#each options as { value: v, label }}
  • handleClick(v)} class:selected={value === v}> {label}
  • @@ -142,7 +149,7 @@ {#each options as v}
  • handleClick(v)} class:selected={value === v}> {v}
  • diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsFieldSelect.svelte new file mode 100644 index 0000000000..526372e3d8 --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/OptionsFieldSelect.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/StringFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/StringFieldSelect.svelte new file mode 100644 index 0000000000..27815af91f --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/StringFieldSelect.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte index 6d3c0d07d3..da6ffc0f11 100644 --- a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte @@ -17,6 +17,9 @@ import DetailScreenSelect from "./PropertyControls/DetailScreenSelect.svelte" import { IconSelect } from "./PropertyControls/IconSelect" import ColorPicker from "./PropertyControls/ColorPicker.svelte" + import StringFieldSelect from "./PropertyControls/StringFieldSelect.svelte" + import NumberFieldSelect from "./PropertyControls/NumberFieldSelect.svelte" + import OptionsFieldSelect from "./PropertyControls/OptionsFieldSelect.svelte" export let componentDefinition = {} export let componentInstance = {} @@ -58,6 +61,9 @@ icon: IconSelect, field: TableViewFieldSelect, multifield: MultiTableViewFieldSelect, + "field/string": StringFieldSelect, + "field/number": NumberFieldSelect, + "field/options": OptionsFieldSelect, } const getControl = type => { diff --git a/packages/client/src/store/builder.js b/packages/client/src/store/builder.js index c7599e5254..295bb6ccc9 100644 --- a/packages/client/src/store/builder.js +++ b/packages/client/src/store/builder.js @@ -13,9 +13,11 @@ const createBuilderStore = () => { const store = writable(initialState) const actions = { selectComponent: id => { - window.dispatchEvent( - new CustomEvent("bb-select-component", { detail: id }) - ) + if (id) { + window.dispatchEvent( + new CustomEvent("bb-select-component", { detail: id }) + ) + } }, } return { diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index 3e934016f6..fbd3ccb053 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -9,7 +9,7 @@ const selectedComponentColor = "#4285f4" */ const buildStyleString = (styleObject, customStyles) => { let str = "" - Object.entries(styleObject).forEach(([style, value]) => { + Object.entries(styleObject || {}).forEach(([style, value]) => { if (style && value != null) { str += `${style}: ${value}; ` } @@ -60,14 +60,14 @@ export const styleable = (node, styles = {}) => { } // Creates event listeners and applies initial styles - const setupStyles = newStyles => { + const setupStyles = (newStyles = {}) => { const componentId = newStyles.id - const selectable = newStyles.allowSelection - const customStyles = newStyles.custom - const normalStyles = newStyles.normal + const selectable = !!newStyles.allowSelection + const customStyles = newStyles.custom || "" + const normalStyles = newStyles.normal || {} const hoverStyles = { ...normalStyles, - ...newStyles.hover, + ...(newStyles.hover || {}), } // Applies a style string to a DOM node, enriching it for the builder @@ -89,7 +89,7 @@ export const styleable = (node, styles = {}) => { // Handler to select a component in the builder when clicking it in the // builder preview selectComponent = event => { - builderStore.actions.selectComponent(newStyles.id) + builderStore.actions.selectComponent(componentId) return blockEvent(event) } diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index e66f467a00..a21a66992b 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -1121,7 +1121,7 @@ } ] }, - "input": { + "stringfield": { "name": "Text Field", "description": "A textfield component that allows the user to input text.", "icon": "ri-edit-box-line", @@ -1129,7 +1129,7 @@ "bindable": true, "settings": [ { - "type": "text", + "type": "field/string", "label": "Field", "key": "field" }, @@ -1142,11 +1142,54 @@ "type": "text", "label": "Placeholder", "key": "placeholder" + } + ] + }, + "numberfield": { + "name": "Number Field", + "description": "A textfield component that allows the user to input numbers.", + "icon": "ri-edit-box-line", + "styleable": true, + "bindable": true, + "settings": [ + { + "type": "field/number", + "label": "Field", + "key": "field" }, { - "type": "boolean", - "label": "Required", - "key": "required" + "type": "text", + "label": "Label", + "key": "label" + }, + { + "type": "text", + "label": "Placeholder", + "key": "placeholder" + } + ] + }, + "optionsfield": { + "name": "Options Picker", + "description": "A textfield component that allows the user to input text.", + "icon": "ri-edit-box-line", + "styleable": true, + "bindable": true, + "settings": [ + { + "type": "field/options", + "label": "Field", + "key": "field" + }, + { + "type": "text", + "label": "Label", + "key": "label" + }, + { + "type": "text", + "label": "Placeholder", + "key": "placeholder" } ] } diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte index 19cc11bc83..a4a2d46c86 100644 --- a/packages/standard-components/src/forms/Form.svelte +++ b/packages/standard-components/src/forms/Form.svelte @@ -1,6 +1,7 @@ + + diff --git a/packages/standard-components/src/forms/OptionsField.svelte b/packages/standard-components/src/forms/OptionsField.svelte new file mode 100644 index 0000000000..f728c685bb --- /dev/null +++ b/packages/standard-components/src/forms/OptionsField.svelte @@ -0,0 +1 @@ +Select diff --git a/packages/standard-components/src/forms/Placeholder.svelte b/packages/standard-components/src/forms/Placeholder.svelte new file mode 100644 index 0000000000..4c088e6038 --- /dev/null +++ b/packages/standard-components/src/forms/Placeholder.svelte @@ -0,0 +1,17 @@ + + +{#if $builderStore.inBuilder} +
    + +
    +{/if} diff --git a/packages/standard-components/src/forms/Input.svelte b/packages/standard-components/src/forms/StringField.svelte similarity index 87% rename from packages/standard-components/src/forms/Input.svelte rename to packages/standard-components/src/forms/StringField.svelte index 30f2790e67..b51b431e24 100644 --- a/packages/standard-components/src/forms/Input.svelte +++ b/packages/standard-components/src/forms/StringField.svelte @@ -2,11 +2,12 @@ import "@spectrum-css/textfield/dist/index-vars.css" import { Label } from "@budibase/bbui" import { getContext } from "svelte" + import Placeholder from "./Placeholder.svelte" export let field export let label export let placeholder - export let validate = value => (value ? null : "Required") + export let type = "text" const { styleable } = getContext("sdk") const component = getContext("component") @@ -23,9 +24,9 @@ {#if !field} -
    Add the Field setting to start using your component!
    + Add the Field setting to start using your component {:else if !fieldState} -
    Form components need to be wrapped in a Form.
    + Form components need to be wrapped in a Form {:else}
    {#if label} @@ -44,7 +45,7 @@ value={$fieldState.value || ''} placeholder={placeholder || ''} on:blur={onBlur} - type="text" + {type} class="spectrum-Textfield-input" />
    {#if $fieldState.error} diff --git a/packages/standard-components/src/forms/index.js b/packages/standard-components/src/forms/index.js index 16e243a802..5f83601526 100644 --- a/packages/standard-components/src/forms/index.js +++ b/packages/standard-components/src/forms/index.js @@ -1,2 +1,4 @@ export { default as form } from "./Form.svelte" -export { default as input } from "./Input.svelte" +export { default as stringfield } from "./StringField.svelte" +export { default as numberfield } from "./NumberField.svelte" +export { default as optionsfield } from "./OptionsField.svelte" diff --git a/packages/standard-components/src/forms/validation.js b/packages/standard-components/src/forms/validation.js new file mode 100644 index 0000000000..f07a0e755f --- /dev/null +++ b/packages/standard-components/src/forms/validation.js @@ -0,0 +1,78 @@ +export const createValidatorFromConstraints = (constraints, field, table) => { + let checks = [] + + if (constraints) { + // Required constraint + if ( + field === table?.primaryDisplay || + constraints.presence?.allowEmpty === false + ) { + checks.push(presenceConstraint) + } + + // String length constraint + if (constraints.length?.maximum) { + const length = constraints.length?.maximum + checks.push(lengthConstraint(length)) + } + + // Min / max number constraint + if (constraints.numericality?.greaterThanOrEqualTo != null) { + const min = constraints.numericality.greaterThanOrEqualTo + checks.push(numericalConstraint(x => x >= min, `Minimum value is ${min}`)) + } + if (constraints.numericality?.lessThanOrEqualTo != null) { + const max = constraints.numericality.lessThanOrEqualTo + checks.push(numericalConstraint(x => x <= max, `Maximum value is ${max}`)) + } + + // Inclusion constraint + if (constraints.inclusion !== undefined) { + const options = constraints.inclusion + checks.push(inclusionConstraint(options)) + } + } + + // Evaluate each constraint + return value => { + for (let check of checks) { + const error = check(value) + if (error) { + return error + } + } + return null + } +} + +const presenceConstraint = value => { + return value == null || value === "" ? "Required" : null +} + +const lengthConstraint = maxLength => value => { + if (value && value.length > maxLength) { + ;`Maximum ${maxLength} characters` + } + return null +} + +const numericalConstraint = (constraint, error) => value => { + if (isNaN(value)) { + return "Must be a number" + } + const number = parseFloat(value) + if (!constraint(number)) { + return error + } + return null +} + +const inclusionConstraint = (options = []) => value => { + if (value == null || value === "") { + return null + } + if (!options.includes(value)) { + return "Invalid value" + } + return null +} From 12f3b7f6fa23f9f9f131524c4a9e664870ebb42e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 27 Jan 2021 10:59:05 +0000 Subject: [PATCH 03/86] Add common SpectrumField component, add spectrum labels, add form label position customisation --- .../design/AppPreview/componentStructure.json | 1 + packages/client/src/utils/styleable.js | 1 + packages/standard-components/manifest.json | 125 ++++++++++-------- packages/standard-components/package.json | 6 + .../standard-components/src/forms/Form.svelte | 27 ++-- .../src/forms/OptionsField.svelte | 108 ++++++++++++++- .../src/forms/SpectrumField.svelte | 36 +++++ .../src/forms/StringField.svelte | 68 ++++++---- packages/standard-components/yarn.lock | 30 +++++ 9 files changed, 311 insertions(+), 91 deletions(-) create mode 100644 packages/standard-components/src/forms/SpectrumField.svelte diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index 9193a566b5..dcf29b2534 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -61,3 +61,4 @@ ] } ] + diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index fbd3ccb053..665a3dc92d 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -74,6 +74,7 @@ export const styleable = (node, styles = {}) => { // preview const applyStyles = styleString => { node.style = addBuilderPreviewStyles(styleString, componentId, selectable) + node.dataset.componentId = componentId } // Applies the "normal" style definition diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index a21a66992b..1cf887bbf2 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -114,57 +114,6 @@ } ] }, - "form": { - "name": "Form", - "icon": "ri-file-edit-line", - "styleable": true, - "hasChildren": true, - "dataProvider": true, - "datasourceSetting": "datasource", - "settings": [ - { - "type": "datasource", - "label": "Data", - "key": "datasource" - }, - { - "type": "select", - "label": "Theme", - "key": "theme", - "defaultValue": "spectrum--light", - "options": [ - { - "label": "Light", - "value": "spectrum--light" - }, - { - "label": "Dark", - "value": "spectrum--dark" - }, - { - "label": "Darkest", - "value": "spectrum--darkest" - } - ] - }, - { - "type": "select", - "label": "Size", - "key": "size", - "defaultValue": "spectrum--medium", - "options": [ - { - "label": "Medium", - "value": "spectrum--medium" - }, - { - "label": "Large", - "value": "spectrum--large" - } - ] - } - ] - }, "richtext": { "name": "Rich Text", "description": "A component that allows the user to enter long form text.", @@ -1121,6 +1070,77 @@ } ] }, + "form": { + "name": "Form", + "icon": "ri-file-edit-line", + "styleable": true, + "hasChildren": true, + "dataProvider": true, + "datasourceSetting": "datasource", + "settings": [ + { + "type": "datasource", + "label": "Data", + "key": "datasource" + }, + { + "type": "select", + "label": "Theme", + "key": "theme", + "defaultValue": "spectrum--light", + "options": [ + { + "label": "Light", + "value": "spectrum--light" + }, + { + "label": "Dark", + "value": "spectrum--dark" + }, + { + "label": "Darkest", + "value": "spectrum--darkest" + } + ] + }, + { + "type": "select", + "label": "Size", + "key": "size", + "defaultValue": "spectrum--medium", + "options": [ + { + "label": "Medium", + "value": "spectrum--medium" + }, + { + "label": "Large", + "value": "spectrum--large" + } + ] + }, + { + "type": "select", + "label": "Labels", + "key": "labelPosition", + "defaultValue": "left", + "options": [ + { + "label": "Left", + "value": "left" + }, + { + "label": "Right", + "value": "right" + }, + { + "label": "Above", + "value": "above" + } + ] + } + ] + }, "stringfield": { "name": "Text Field", "description": "A textfield component that allows the user to input text.", @@ -1189,7 +1209,8 @@ { "type": "text", "label": "Placeholder", - "key": "placeholder" + "key": "placeholder", + "placeholder": "Choose an option" } ] } diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 7ae2b94b7f..047cfa6aff 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -37,9 +37,15 @@ "@adobe/spectrum-css-workflow-icons": "^1.1.0", "@budibase/bbui": "^1.52.4", "@budibase/svelte-ag-grid": "^0.0.16", + "@spectrum-css/actionbutton": "^1.0.0-beta.1", "@spectrum-css/button": "^3.0.0-beta.6", + "@spectrum-css/fieldlabel": "^3.0.0-beta.7", "@spectrum-css/icon": "^3.0.0-beta.2", + "@spectrum-css/menu": "^3.0.0-beta.5", "@spectrum-css/page": "^3.0.0-beta.0", + "@spectrum-css/picker": "^1.0.0-beta.3", + "@spectrum-css/popover": "^3.0.0-beta.6", + "@spectrum-css/stepper": "^3.0.0-beta.7", "@spectrum-css/textfield": "^3.0.0-beta.6", "@spectrum-css/vars": "^3.0.0-beta.2", "apexcharts": "^3.22.1", diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte index a4a2d46c86..86568f18d5 100644 --- a/packages/standard-components/src/forms/Form.svelte +++ b/packages/standard-components/src/forms/Form.svelte @@ -1,4 +1,5 @@ + + + +
    +
      +
    • selectOption(null)}> + {placeholderText} + +
    • + {#each options as option} +
    • selectOption(option)}> + {option} + +
    • + {/each} +
    +
    + {#if $fieldState.error} +
    {$fieldState.error}
    + {/if} +
    + + diff --git a/packages/standard-components/src/forms/SpectrumField.svelte b/packages/standard-components/src/forms/SpectrumField.svelte new file mode 100644 index 0000000000..84f1a22ccb --- /dev/null +++ b/packages/standard-components/src/forms/SpectrumField.svelte @@ -0,0 +1,36 @@ + + +{#if !fieldId} + Add the Field setting to start using your component +{:else if !formContext} + Form components need to be wrapped in a Form +{:else} +
    + {#if label} + + {/if} +
    + +
    +
    +{/if} diff --git a/packages/standard-components/src/forms/StringField.svelte b/packages/standard-components/src/forms/StringField.svelte index b51b431e24..f75a5b1b6f 100644 --- a/packages/standard-components/src/forms/StringField.svelte +++ b/packages/standard-components/src/forms/StringField.svelte @@ -1,38 +1,34 @@ -{#if !field} - Add the Field setting to start using your component -{:else if !fieldState} - Form components need to be wrapped in a Form -{:else} -
    - {#if label} - - {/if} -
    + +
    +
    {#if !$fieldState.valid} {/if} + class="spectrum-Textfield-input" + class:spectrum-Stepper-input={numeric} />
    + {#if numeric} + + + + + {/if} {#if $fieldState.error} -
    - -
    +
    {$fieldState.error}
    {/if}
    -{/if} +
    diff --git a/packages/standard-components/yarn.lock b/packages/standard-components/yarn.lock index 732c50990a..a1a4a11af5 100644 --- a/packages/standard-components/yarn.lock +++ b/packages/standard-components/yarn.lock @@ -132,16 +132,31 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@spectrum-css/actionbutton@^1.0.0-beta.1": + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.0-beta.1.tgz#a6684cac108d4a9daefe0be6df8201d3c369a0d6" + integrity sha512-QbrPMTkbkmh+dEBP66TFXmF5z3qSde+BnLR5hnlo2XMvKvnblX2VJStEbQ+hTKuSZXCRFADXyXD5o0NOYDTByQ== + "@spectrum-css/button@^3.0.0-beta.6": version "3.0.0-beta.6" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0-beta.6.tgz#007919d3e7a6692e506dc9addcd46aee6b203b1a" integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg== +"@spectrum-css/fieldlabel@^3.0.0-beta.7": + version "3.0.0-beta.7" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.0-beta.7.tgz#f37797565e21b3609b8fbc2dafcea8ea41ffa114" + integrity sha512-0pseiPghqlOdALsRtidveWyt2YjfSXTZWDlSkcne/J0/QXBJOQH/7Qfy7TmROQZYRB2LqH1VzmE1zbvGwr5Aog== + "@spectrum-css/icon@^3.0.0-beta.2": version "3.0.0-beta.2" resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.2.tgz#2dd7258ded74501b56e5fc42d0b6f0a3f4936aeb" integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA== +"@spectrum-css/menu@^3.0.0-beta.5": + version "3.0.0-beta.5" + resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.0-beta.5.tgz#99d5ea7f6760b7a89d5d732f4e91b98dd3f82d74" + integrity sha512-jvPD5GbNdX31rdFBLxCG7KoUVGeeNYLzNXDpiGZsWme/djVTwitljgNe7bhVwCVlXZE7H20Ti/YrdafnE154Rw== + "@spectrum-css/page@^3.0.0-beta.0": version "3.0.0-beta.0" resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0-beta.0.tgz#885ea41b44861c5dc3aac904536f9e93c9109b58" @@ -149,6 +164,21 @@ dependencies: "@spectrum-css/vars" "^3.0.0-beta.2" +"@spectrum-css/picker@^1.0.0-beta.3": + version "1.0.0-beta.3" + resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.0-beta.3.tgz#476593597b5a9e0105397e4e39350869cf6e7965" + integrity sha512-jHzFnS5Frd3JSwZ6B8ymH/sVnNqAUBo9p93Zax4VHTUDsPTtTkvxj/Vxo4POmrJEL9v3qUB2Yk13rD2BSfEzLQ== + +"@spectrum-css/popover@^3.0.0-beta.6": + version "3.0.0-beta.6" + resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.0-beta.6.tgz#787611f020e091234e6ba7e946b0dbd0ed1a2fa2" + integrity sha512-dUJlwxoNpB6jOR0g/ywH2cPoUz2FVsL6xPfkm6BSsLp9ejhYy0/OFF4w0Q32Fu9qJDbWJ9qaoOlPpt7IjQ+/GQ== + +"@spectrum-css/stepper@^3.0.0-beta.7": + version "3.0.0-beta.7" + resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.0-beta.7.tgz#fc78435ce878c5e233af13e43ed2c3e8671a2bbc" + integrity sha512-TQL2OBcdEgbHBwehMGgqMuWdKZZQPGcBRV5FlF0TUdOT58lEqFAO43Gajqvyte1P23lNmnX8KuMwkRfQdn0RzA== + "@spectrum-css/textfield@^3.0.0-beta.6": version "3.0.0-beta.6" resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0-beta.6.tgz#30c044ceb403d6ea82d8046fb8f767f7fe455da6" From 6631fe2af86997ceb5023727d3fb19dda1cbe01e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 27 Jan 2021 15:52:12 +0000 Subject: [PATCH 04/86] Update builder preview to be interactive and improve builder preview experience --- .../design/AppPreview/iframeTemplate.html | 3 -- .../PropertiesPanel/SettingsView.svelte | 2 +- .../design/PropertiesPanel/componentStyles.js | 33 ------------ .../client/src/components/ClientApp.svelte | 11 ++-- .../client/src/components/Component.svelte | 45 ++++++++--------- packages/client/src/components/Router.svelte | 3 -- packages/client/src/utils/hash.js | 12 +++++ packages/client/src/utils/styleable.js | 50 +++++-------------- packages/standard-components/src/Login.svelte | 5 +- .../standard-components/src/Navigation.svelte | 5 +- .../src/forms/OptionsField.svelte | 12 ----- .../src/forms/SpectrumField.svelte | 16 +++++- .../src/forms/StringField.svelte | 50 ++----------------- 13 files changed, 81 insertions(+), 166 deletions(-) create mode 100644 packages/client/src/utils/hash.js diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.html b/packages/builder/src/components/design/AppPreview/iframeTemplate.html index 49df3b5c0b..166a978d01 100644 --- a/packages/builder/src/components/design/AppPreview/iframeTemplate.html +++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.html @@ -11,9 +11,6 @@ *, *:before, *:after { box-sizing: border-box; } - * { - pointer-events: none; - } {#if constructor && componentProps} - - {#if children.length} - {#each children as child (getChildKey(child._id))} - - {/each} - {/if} - + {#key propsHash} + + {#if children.length} + {#each children as child (child._id)} + + {/each} + {/if} + + {/key} {/if} diff --git a/packages/client/src/components/Router.svelte b/packages/client/src/components/Router.svelte index 5477d02f86..efa0e321aa 100644 --- a/packages/client/src/components/Router.svelte +++ b/packages/client/src/components/Router.svelte @@ -7,9 +7,6 @@ const { styleable } = getContext("sdk") const component = getContext("component") - // Set context flag so components know that we're now inside the screenslot - setContext("screenslot", true) - // Only wrap this as an array to take advantage of svelte keying, // to ensure the svelte-spa-router is fully remounted when route config // changes diff --git a/packages/client/src/utils/hash.js b/packages/client/src/utils/hash.js new file mode 100644 index 0000000000..2c42007e43 --- /dev/null +++ b/packages/client/src/utils/hash.js @@ -0,0 +1,12 @@ +export const hashString = str => { + if (!str) { + return 0 + } + let hash = 0 + for (let i = 0; i < str.length; i++) { + let char = str.charCodeAt(i) + hash = (hash << 5) - hash + char + hash = hash & hash // Convert to 32bit integer + } + return hash +} diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index 665a3dc92d..6cc387058a 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -1,9 +1,6 @@ import { get } from "svelte/store" import { builderStore } from "../store" -const selectedComponentWidth = 2 -const selectedComponentColor = "#4285f4" - /** * Helper to build a CSS string from a style object. */ @@ -23,24 +20,14 @@ const buildStyleString = (styleObject, customStyles) => { * events for any selectable components (overriding the blanket ban on pointer * events in the iframe HTML). */ -const addBuilderPreviewStyles = (styleString, componentId, selectable) => { - let str = styleString - - // Apply extra styles if we're in the builder preview - const state = get(builderStore) - if (state.inBuilder) { - // Allow pointer events and always enable cursor - if (selectable) { - str += ";pointer-events: all !important; cursor: pointer !important;" - } - - // Highlighted selected element - if (componentId === state.selectedComponentId) { - str += `;border: ${selectedComponentWidth}px solid ${selectedComponentColor} !important;` - } +const addBuilderPreviewStyles = (node, styleString, componentId) => { + if (componentId === get(builderStore).selectedComponentId) { + const style = window.getComputedStyle(node) + const property = style?.display === "table-row" ? "outline" : "border" + return styleString + `;${property}: 2px solid #4285f4 !important;` + } else { + return styleString } - - return str } /** @@ -52,17 +39,9 @@ export const styleable = (node, styles = {}) => { let applyHoverStyles let selectComponent - // Kill JS even bubbling - const blockEvent = event => { - event.preventDefault() - event.stopPropagation() - return false - } - // Creates event listeners and applies initial styles const setupStyles = (newStyles = {}) => { const componentId = newStyles.id - const selectable = !!newStyles.allowSelection const customStyles = newStyles.custom || "" const normalStyles = newStyles.normal || {} const hoverStyles = { @@ -70,10 +49,9 @@ export const styleable = (node, styles = {}) => { ...(newStyles.hover || {}), } - // Applies a style string to a DOM node, enriching it for the builder - // preview + // Applies a style string to a DOM node const applyStyles = styleString => { - node.style = addBuilderPreviewStyles(styleString, componentId, selectable) + node.style = addBuilderPreviewStyles(node, styleString, componentId) node.dataset.componentId = componentId } @@ -91,7 +69,9 @@ export const styleable = (node, styles = {}) => { // builder preview selectComponent = event => { builderStore.actions.selectComponent(componentId) - return blockEvent(event) + event.preventDefault() + event.stopPropagation() + return false } // Add listeners to toggle hover styles @@ -101,10 +81,6 @@ export const styleable = (node, styles = {}) => { // Add builder preview click listener if (get(builderStore).inBuilder) { node.addEventListener("click", selectComponent, false) - - // Kill other interaction events - node.addEventListener("mousedown", blockEvent) - node.addEventListener("mouseup", blockEvent) } // Apply initial normal styles @@ -119,8 +95,6 @@ export const styleable = (node, styles = {}) => { // Remove builder preview click listener if (get(builderStore).inBuilder) { node.removeEventListener("click", selectComponent) - node.removeEventListener("mousedown", blockEvent) - node.removeEventListener("mouseup", blockEvent) } } diff --git a/packages/standard-components/src/Login.svelte b/packages/standard-components/src/Login.svelte index 8a4af82320..1502b5c583 100644 --- a/packages/standard-components/src/Login.svelte +++ b/packages/standard-components/src/Login.svelte @@ -1,7 +1,7 @@ diff --git a/packages/standard-components/src/forms/OptionsField.svelte b/packages/standard-components/src/forms/OptionsField.svelte index 2796356027..77d48867f6 100644 --- a/packages/standard-components/src/forms/OptionsField.svelte +++ b/packages/standard-components/src/forms/OptionsField.svelte @@ -92,16 +92,4 @@ {/each}
- {#if $fieldState.error} -
{$fieldState.error}
- {/if} - - diff --git a/packages/standard-components/src/forms/SpectrumField.svelte b/packages/standard-components/src/forms/SpectrumField.svelte index 84f1a22ccb..4acda6e921 100644 --- a/packages/standard-components/src/forms/SpectrumField.svelte +++ b/packages/standard-components/src/forms/SpectrumField.svelte @@ -10,7 +10,7 @@ const component = getContext("component") const { labelPosition, formApi } = formContext || {} const formField = formApi?.registerField(field) ?? {} - const { fieldId } = formField + const { fieldId, fieldState } = formField $: labelPositionClass = labelPosition === "top" ? "" : `spectrum-FieldLabel--${labelPosition}` @@ -31,6 +31,20 @@ {/if}
+ {#if $fieldState.error} +
{$fieldState.error}
+ {/if}
{/if} + + diff --git a/packages/standard-components/src/forms/StringField.svelte b/packages/standard-components/src/forms/StringField.svelte index f75a5b1b6f..43ae0c1c13 100644 --- a/packages/standard-components/src/forms/StringField.svelte +++ b/packages/standard-components/src/forms/StringField.svelte @@ -1,7 +1,5 @@ -
-
+
+
{#if !$fieldState.valid} + class="spectrum-Textfield-input" />
- {#if numeric} - - - - - {/if} - {#if $fieldState.error} -
{$fieldState.error}
- {/if}
- - From e1f40ea066dd3cbbac2b4f2736fcb24b1f142d8e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 27 Jan 2021 18:25:57 +0000 Subject: [PATCH 05/86] Add FieldGroup component to allow easy mixing of fields and other content in forms --- .../design/AppPreview/componentStructure.json | 1 + packages/standard-components/manifest.json | 13 +++++-- .../src/forms/FieldGroup.svelte | 17 +++++++++ .../src/forms/FieldGroupFallback.svelte | 14 ++++++++ .../standard-components/src/forms/Form.svelte | 13 +++---- .../src/forms/OptionsField.svelte | 2 +- .../src/forms/SpectrumField.svelte | 35 +++++++++++-------- .../src/forms/StringField.svelte | 34 +++++++++--------- .../standard-components/src/forms/index.js | 1 + 9 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 packages/standard-components/src/forms/FieldGroup.svelte create mode 100644 packages/standard-components/src/forms/FieldGroupFallback.svelte diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index dcf29b2534..98f2ca1aa0 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -8,6 +8,7 @@ "icon": "ri-file-edit-line", "children": [ "form", + "fieldgroup", "stringfield", "numberfield", "optionsfield", diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 1cf887bbf2..ce628ee775 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -1118,12 +1118,21 @@ "value": "spectrum--large" } ] - }, + } + ] + }, + "fieldgroup": { + "name": "Field Group", + "description": "A group of fields in a form.", + "icon": "ri-edit-box-line", + "styleable": true, + "hasChildren": true, + "settings": [ { "type": "select", "label": "Labels", "key": "labelPosition", - "defaultValue": "left", + "defaultValue": "above", "options": [ { "label": "Left", diff --git a/packages/standard-components/src/forms/FieldGroup.svelte b/packages/standard-components/src/forms/FieldGroup.svelte new file mode 100644 index 0000000000..ec5979ed30 --- /dev/null +++ b/packages/standard-components/src/forms/FieldGroup.svelte @@ -0,0 +1,17 @@ + + +
+ + + +
diff --git a/packages/standard-components/src/forms/FieldGroupFallback.svelte b/packages/standard-components/src/forms/FieldGroupFallback.svelte new file mode 100644 index 0000000000..8fc22c9cc4 --- /dev/null +++ b/packages/standard-components/src/forms/FieldGroupFallback.svelte @@ -0,0 +1,14 @@ + + +{#if fieldGroupContext} + +{:else} +
+ +
+{/if} diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte index 86568f18d5..21e2ece4df 100644 --- a/packages/standard-components/src/forms/Form.svelte +++ b/packages/standard-components/src/forms/Form.svelte @@ -7,7 +7,6 @@ export let datasource export let theme export let size - export let labelPosition = "left" const { styleable, API } = getContext("sdk") const component = getContext("component") @@ -51,7 +50,7 @@ } // Provide both form API and state to children - setContext("form", { formApi, formState, labelPosition }) + setContext("form", { formApi, formState }) // Creates an API for a specific field const makeFieldApi = (field, validate) => { @@ -122,11 +121,7 @@ dir="ltr" use:styleable={$component.styles} class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}> -
- {#if loaded} - - {/if} - + {#if loaded} + + {/if}
diff --git a/packages/standard-components/src/forms/OptionsField.svelte b/packages/standard-components/src/forms/OptionsField.svelte index 77d48867f6..4c7f29f046 100644 --- a/packages/standard-components/src/forms/OptionsField.svelte +++ b/packages/standard-components/src/forms/OptionsField.svelte @@ -18,7 +18,7 @@ let open = false $: options = fieldSchema?.constraints?.inclusion ?? [] $: placeholderText = placeholder || "Choose an option" - $: isNull = $fieldState.value == null || $fieldState.value === "" + $: isNull = $fieldState?.value == null || $fieldState?.value === "" // Update value on blur only const selectOption = value => { diff --git a/packages/standard-components/src/forms/SpectrumField.svelte b/packages/standard-components/src/forms/SpectrumField.svelte index 4acda6e921..418b04371c 100644 --- a/packages/standard-components/src/forms/SpectrumField.svelte +++ b/packages/standard-components/src/forms/SpectrumField.svelte @@ -1,19 +1,22 @@ {#if !fieldId} @@ -21,21 +24,23 @@ {:else if !formContext} Form components need to be wrapped in a Form {:else} -
- {#if label} - - {/if} -
- - {#if $fieldState.error} -
{$fieldState.error}
+ +
+ {#if label} + {/if} +
+ + {#if $fieldState.error} +
{$fieldState.error}
+ {/if} +
-
+ {/if} diff --git a/packages/standard-components/src/forms/index.js b/packages/standard-components/src/forms/index.js index 52b7386005..5d0fc1d70c 100644 --- a/packages/standard-components/src/forms/index.js +++ b/packages/standard-components/src/forms/index.js @@ -4,3 +4,4 @@ export { default as stringfield } from "./StringField.svelte" export { default as numberfield } from "./NumberField.svelte" export { default as optionsfield } from "./OptionsField.svelte" export { default as booleanfield } from "./BooleanField.svelte" +export { default as longformfield } from "./LongFormField.svelte" diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js index b0fbf88017..8f0b38f2f1 100644 --- a/packages/standard-components/src/index.js +++ b/packages/standard-components/src/index.js @@ -16,7 +16,6 @@ export { default as container } from "./Container.svelte" export { default as datagrid } from "./grid/Component.svelte" export { default as screenslot } from "./ScreenSlot.svelte" export { default as button } from "./Button.svelte" -export { default as richtext } from "./RichText.svelte" export { default as list } from "./List.svelte" export { default as stackedlist } from "./StackedList.svelte" export { default as card } from "./Card.svelte" From e72850b84de137ba3c2343cbfc96cd84024da25f Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 28 Jan 2021 18:53:40 +0000 Subject: [PATCH 09/86] Integrate flatpickr with spectrum --- .../design/AppPreview/componentStructure.json | 2 +- .../DateTimeFieldSelect.svelte | 5 ++ .../PropertiesPanel/SettingsView.svelte | 2 + packages/standard-components/manifest.json | 36 +++++---- packages/standard-components/package.json | 1 + .../standard-components/src/DatePicker.svelte | 21 ----- .../src/forms/DateTimeField.svelte | 79 +++++++++++++++++++ .../src/forms/LongFormField.svelte | 2 - .../standard-components/src/forms/index.js | 1 + packages/standard-components/src/index.js | 2 - packages/standard-components/yarn.lock | 5 ++ 11 files changed, 116 insertions(+), 40 deletions(-) create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/DateTimeFieldSelect.svelte delete mode 100644 packages/standard-components/src/DatePicker.svelte create mode 100644 packages/standard-components/src/forms/DateTimeField.svelte diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index 96e45ed658..6ecdcc8657 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -14,7 +14,7 @@ "optionsfield", "booleanfield", "longformfield", - "datepicker" + "datetimefield" ] }, { diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DateTimeFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DateTimeFieldSelect.svelte new file mode 100644 index 0000000000..5c0ed8cb2f --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DateTimeFieldSelect.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte index 5575ac0b9f..a1be940553 100644 --- a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte @@ -22,6 +22,7 @@ import OptionsFieldSelect from "./PropertyControls/OptionsFieldSelect.svelte" import BooleanFieldSelect from "./PropertyControls/BooleanFieldSelect.svelte" import LongFormFieldSelect from "./PropertyControls/LongFormFieldSelect.svelte" + import DateTimeFieldSelect from "./PropertyControls/DateTimeFieldSelect.svelte" export let componentDefinition = {} export let componentInstance = {} @@ -68,6 +69,7 @@ "field/options": OptionsFieldSelect, "field/boolean": BooleanFieldSelect, "field/longform": LongFormFieldSelect, + "field/datetime": DateTimeFieldSelect, } const getControl = type => { diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index d0b81cdf13..4214346a7f 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -114,20 +114,6 @@ } ] }, - "datepicker": { - "name": "Date Picker", - "description": "A basic date picker component", - "icon": "ri-calendar-line", - "styleable": true, - "bindable": true, - "settings": [ - { - "type": "text", - "label": "Placeholder", - "key": "placeholder" - } - ] - }, "stackedlist": { "name": "Stacked List", "icon": "ri-archive-drawer-line", @@ -1257,5 +1243,27 @@ "placeholder": "Type something..." } ] + }, + "datetimefield": { + "name": "Date Picker", + "icon": "ri-calendar-line", + "styleable": true, + "settings": [ + { + "type": "field/datetime", + "label": "Field", + "key": "field" + }, + { + "type": "text", + "label": "Label", + "key": "label" + }, + { + "type": "text", + "label": "Placeholder", + "key": "placeholder" + } + ] } } diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 678e772c0f..eec67a7ff4 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -47,6 +47,7 @@ "@spectrum-css/checkbox": "^3.0.0-beta.6", "@spectrum-css/fieldlabel": "^3.0.0-beta.7", "@spectrum-css/icon": "^3.0.0-beta.2", + "@spectrum-css/inputgroup": "^3.0.0-beta.7", "@spectrum-css/menu": "^3.0.0-beta.5", "@spectrum-css/page": "^3.0.0-beta.0", "@spectrum-css/picker": "^1.0.0-beta.3", diff --git a/packages/standard-components/src/DatePicker.svelte b/packages/standard-components/src/DatePicker.svelte deleted file mode 100644 index 200a563516..0000000000 --- a/packages/standard-components/src/DatePicker.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - -
- -
diff --git a/packages/standard-components/src/forms/DateTimeField.svelte b/packages/standard-components/src/forms/DateTimeField.svelte new file mode 100644 index 0000000000..3245ce7662 --- /dev/null +++ b/packages/standard-components/src/forms/DateTimeField.svelte @@ -0,0 +1,79 @@ + + + + {#if fieldState} + +
+
+ +
+ +
+
+ {/if} +
+ + diff --git a/packages/standard-components/src/forms/LongFormField.svelte b/packages/standard-components/src/forms/LongFormField.svelte index 0b2b873760..a9cd13de4a 100644 --- a/packages/standard-components/src/forms/LongFormField.svelte +++ b/packages/standard-components/src/forms/LongFormField.svelte @@ -1,6 +1,5 @@ {#if fieldState}
+ value={$fieldState.value} />
-
-
    -
  • selectOption(null)}> - {placeholderText} - -
  • - {#each options as option} -
  • selectOption(option)}> - {option} - -
  • - {/each} -
-
+ option === $fieldState.value} + onSelectOption={selectOption} /> {/if}
diff --git a/packages/standard-components/src/forms/Picker.svelte b/packages/standard-components/src/forms/Picker.svelte new file mode 100644 index 0000000000..a4a3804939 --- /dev/null +++ b/packages/standard-components/src/forms/Picker.svelte @@ -0,0 +1,103 @@ + + +{#if fieldState} + + {#if open} +
(open = false)} /> +
+
    + {#if placeholderOption} +
  • onSelectOption(null)}> + {placeholderOption} + +
  • + {/if} + {#each options as option} +
  • onSelectOption(getOptionValue(option))}> + {getOptionLabel(option)} + +
  • + {/each} +
+
+ {/if} +{/if} + + diff --git a/packages/standard-components/src/forms/RelationshipField.svelte b/packages/standard-components/src/forms/RelationshipField.svelte new file mode 100644 index 0000000000..f015954575 --- /dev/null +++ b/packages/standard-components/src/forms/RelationshipField.svelte @@ -0,0 +1,76 @@ + + + + option._id} /> + diff --git a/packages/standard-components/src/forms/index.js b/packages/standard-components/src/forms/index.js index aa97bd61d4..3998424db3 100644 --- a/packages/standard-components/src/forms/index.js +++ b/packages/standard-components/src/forms/index.js @@ -7,3 +7,4 @@ export { default as booleanfield } from "./BooleanField.svelte" export { default as longformfield } from "./LongFormField.svelte" export { default as datetimefield } from "./DateTimeField.svelte" export { default as attachmentfield } from "./AttachmentField.svelte" +export { default as relationshipfield } from "./RelationshipField.svelte" From abd99f859aa72823855badeb3dc04ed0fb2bed6f Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 1 Feb 2021 13:23:18 +0000 Subject: [PATCH 18/86] Improve validation around loading options for relationship field --- .../src/forms/RelationshipField.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/standard-components/src/forms/RelationshipField.svelte b/packages/standard-components/src/forms/RelationshipField.svelte index f015954575..84842a02c3 100644 --- a/packages/standard-components/src/forms/RelationshipField.svelte +++ b/packages/standard-components/src/forms/RelationshipField.svelte @@ -25,13 +25,17 @@ const fetchTable = async id => { if (id != null) { - tableDefinition = await API.fetchTableDefinition(id) + const result = await API.fetchTableDefinition(id) + if (!result.error) { + tableDefinition = result + } } } const fetchRows = async id => { if (id != null) { - options = await API.fetchTableData(id) + const rows = await API.fetchTableData(id) + options = rows && !rows.error ? rows : [] } } From 260cecb5e5f02a28e134eb1e1d111ff3e729fe24 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 1 Feb 2021 14:10:38 +0000 Subject: [PATCH 19/86] Update form field ID's to always be safe --- .../standard-components/src/forms/Form.svelte | 5 ++- packages/standard-components/src/helpers.js | 32 +++++++++++++++++++ packages/standard-components/yarn.lock | 5 +++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte index c229f30df8..9edb749088 100644 --- a/packages/standard-components/src/forms/Form.svelte +++ b/packages/standard-components/src/forms/Form.svelte @@ -3,6 +3,7 @@ import { setContext, getContext, onMount } from "svelte" import { writable, get } from "svelte/store" import { createValidatorFromConstraints } from "./validation" + import { generateID } from "../helpers" export let datasource export let theme @@ -82,9 +83,7 @@ const makeFieldState = (field, defaultValue) => { return writable({ field, - fieldId: `${Math.random() - .toString(32) - .substr(2)}/${field}`, + fieldId: `id-${generateID()}-${field}`, value: initialValues[field] ?? defaultValue, error: null, valid: true, diff --git a/packages/standard-components/src/helpers.js b/packages/standard-components/src/helpers.js index 02f3229467..af98473ac9 100644 --- a/packages/standard-components/src/helpers.js +++ b/packages/standard-components/src/helpers.js @@ -31,3 +31,35 @@ export const cssVars = (node, props) => { }, } } + +/** + * Generates a short random ID. + * This is "nanoid" but rollup was derping attempting to bundle it, so the + * source has just been extracted manually since it's tiny. + */ +export const generateID = (size = 21) => { + let id = "" + let bytes = crypto.getRandomValues(new Uint8Array(size)) + + // A compact alternative for `for (var i = 0; i < step; i++)`. + while (size--) { + // It is incorrect to use bytes exceeding the alphabet size. + // The following mask reduces the random byte in the 0-255 value + // range to the 0-63 value range. Therefore, adding hacks, such + // as empty string fallback or magic numbers, is unneccessary because + // the bitmask trims bytes down to the alphabet size. + let byte = bytes[size] & 63 + if (byte < 36) { + // `0-9a-z` + id += byte.toString(36) + } else if (byte < 62) { + // `A-Z` + id += (byte - 26).toString(36).toUpperCase() + } else if (byte < 63) { + id += "_" + } else { + id += "-" + } + } + return id +} diff --git a/packages/standard-components/yarn.lock b/packages/standard-components/yarn.lock index 3e5d933b3b..fd9043e58f 100644 --- a/packages/standard-components/yarn.lock +++ b/packages/standard-components/yarn.lock @@ -1768,6 +1768,11 @@ mri@^1.1.0: resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ== +nanoid@^3.1.20: + version "3.1.20" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" + integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== + node-releases@^1.1.60: version "1.1.60" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" From 22cad32a2d359f0554b43c5c05a3f631451dacc9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 1 Feb 2021 14:10:55 +0000 Subject: [PATCH 20/86] Update date picker to fix horrible flatpickr event handling --- .../src/forms/DateTimeField.svelte | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/standard-components/src/forms/DateTimeField.svelte b/packages/standard-components/src/forms/DateTimeField.svelte index e4beda5d3e..591acededb 100644 --- a/packages/standard-components/src/forms/DateTimeField.svelte +++ b/packages/standard-components/src/forms/DateTimeField.svelte @@ -37,7 +37,9 @@ // duplicate input field. // We need to blur both because the focus styling does not get properly // applied. - const els = document.querySelectorAll(`#${$fieldState.id}-wrapper input`) + const els = document.querySelectorAll( + `#${$fieldState.fieldId}-wrapper input` + ) els.forEach(el => el.blur()) } @@ -51,9 +53,9 @@ on:close={onClose} options={flatpickrOptions} on:change={handleChange} - element={`#${$fieldState.id}-wrapper`}> + element={`#${$fieldState.fieldId}-wrapper`}>
-
+
+ {#if open} +
+ {/if} {/if} From 9214157ea46eb814b166275274b7819e556583c5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 1 Feb 2021 18:00:38 +0000 Subject: [PATCH 21/86] Fix long form field width --- packages/standard-components/src/forms/LongFormField.svelte | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/standard-components/src/forms/LongFormField.svelte b/packages/standard-components/src/forms/LongFormField.svelte index 8c475e7b28..81038c0355 100644 --- a/packages/standard-components/src/forms/LongFormField.svelte +++ b/packages/standard-components/src/forms/LongFormField.svelte @@ -50,6 +50,9 @@ div { background-color: white; } + div :global(> div) { + width: auto !important; + } div :global(.ql-snow.ql-toolbar:after, .ql-snow .ql-toolbar:after) { display: none; } From 9c0e417408a9f66525de7b38a61b4e811c869ac9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 1 Feb 2021 18:51:22 +0000 Subject: [PATCH 22/86] Add custom component actions. Simplify client context. Add form validation action --- .../builder/src/builderStore/dataBinding.js | 54 ++++++------ .../EventsEditor/EventEditor.svelte | 11 +-- .../EventsEditor/actions/ValidateForm.svelte | 39 ++++++++ .../EventsEditor/actions/index.js | 5 ++ .../client/src/components/ClientApp.svelte | 9 +- .../client/src/components/Component.svelte | 10 +-- .../client/src/components/DataProvider.svelte | 15 ---- .../client/src/components/Provider.svelte | 29 ++++++ packages/client/src/constants.js | 4 + packages/client/src/sdk.js | 8 +- packages/client/src/store/binding.js | 21 ----- packages/client/src/store/context.js | 34 +++++++ packages/client/src/store/data.js | 26 ------ packages/client/src/store/index.js | 5 +- packages/client/src/utils/buttonActions.js | 23 ++++- packages/client/src/utils/componentProps.js | 18 ++-- packages/standard-components/manifest.json | 17 ++-- packages/standard-components/src/Form.svelte | 6 +- packages/standard-components/src/Input.svelte | 14 --- packages/standard-components/src/List.svelte | 6 +- .../standard-components/src/NewRow.svelte | 6 +- .../standard-components/src/RowDetail.svelte | 6 +- .../src/charts/BarChart.svelte | 1 - .../src/charts/CandleStickChart.svelte | 1 - .../src/charts/LineChart.svelte | 1 - .../src/charts/PieChart.svelte | 1 - .../standard-components/src/forms/Form.svelte | 88 ++++++++++++------- .../src/grid/Component.svelte | 1 - 28 files changed, 264 insertions(+), 195 deletions(-) create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ValidateForm.svelte delete mode 100644 packages/client/src/components/DataProvider.svelte create mode 100644 packages/client/src/components/Provider.svelte delete mode 100644 packages/client/src/store/binding.js create mode 100644 packages/client/src/store/context.js delete mode 100644 packages/client/src/store/data.js delete mode 100644 packages/standard-components/src/Input.svelte diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 679a77ca2b..65241b7875 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -1,7 +1,7 @@ import { cloneDeep } from "lodash/fp" import { get } from "svelte/store" import { backendUiStore, store } from "builderStore" -import { findAllMatchingComponents, findComponentPath } from "./storeUtils" +import { findComponentPath } from "./storeUtils" import { TableNames } from "../constants" // Regex to match all instances of template strings @@ -11,9 +11,7 @@ const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g * Gets all bindable data context fields and instance fields. */ export const getBindableProperties = (rootComponent, componentId) => { - const contextBindings = getContextBindings(rootComponent, componentId) - const componentBindings = getComponentBindings(rootComponent) - return [...contextBindings, ...componentBindings] + return getContextBindings(rootComponent, componentId) } /** @@ -36,6 +34,30 @@ export const getDataProviderComponents = (rootComponent, componentId) => { }) } +/** + * Gets all data provider components above a component. + */ +export const getActionProviderComponents = ( + rootComponent, + componentId, + actionType +) => { + if (!rootComponent || !componentId) { + return [] + } + + // Get the component tree leading up to this component, ignoring the component + // itself + const path = findComponentPath(rootComponent, componentId) + path.pop() + + // Filter by only data provider components + return path.filter(component => { + const def = store.actions.components.getDefinition(component._component) + return def?.actions?.includes(actionType) + }) +} + /** * Gets a datasource object for a certain data provider component */ @@ -149,30 +171,6 @@ export const getContextBindings = (rootComponent, componentId) => { return contextBindings } -/** - * Gets all bindable components. These are form components which allow their - * values to be bound to. - */ -export const getComponentBindings = rootComponent => { - if (!rootComponent) { - return [] - } - const componentSelector = component => { - const type = component._component - const definition = store.actions.components.getDefinition(type) - return definition?.bindable - } - const components = findAllMatchingComponents(rootComponent, componentSelector) - return components.map(component => { - return { - type: "instance", - providerId: component._id, - runtimeBinding: `${component._id}`, - readableBinding: `${component._instanceName}`, - } - }) -} - /** * Gets a schema for a datasource object. */ diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte index 0e52aa8f76..7dd7fc5044 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/EventEditor.svelte @@ -1,15 +1,6 @@ + +
+ + + + {/each} + {/if} + +
+ + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js index 677c646728..e851bdb4be 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js @@ -3,6 +3,7 @@ import SaveRow from "./SaveRow.svelte" import DeleteRow from "./DeleteRow.svelte" import ExecuteQuery from "./ExecuteQuery.svelte" import TriggerAutomation from "./TriggerAutomation.svelte" +import ValidateForm from "./ValidateForm.svelte" // defines what actions are available, when adding a new one // the component is the setup panel for the action @@ -30,4 +31,8 @@ export default [ name: "Trigger Automation", component: TriggerAutomation, }, + { + name: "Validate Form", + component: ValidateForm, + }, ] diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 92a050a91e..cad74a3e63 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -4,12 +4,17 @@ import Component from "./Component.svelte" import NotificationDisplay from "./NotificationDisplay.svelte" import SDK from "../sdk" - import { createDataStore, initialise, screenStore, authStore } from "../store" + import { + createContextStore, + initialise, + screenStore, + authStore, + } from "../store" // Provide contexts setContext("sdk", SDK) setContext("component", writable({})) - setContext("data", createDataStore()) + setContext("context", createContextStore()) let loaded = false diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index c57a85b7e3..a9d80a3c5f 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -4,7 +4,7 @@ import * as ComponentLibrary from "@budibase/standard-components" import Router from "./Router.svelte" import { enrichProps, propsAreSame } from "../utils/componentProps" - import { authStore, bindingStore, builderStore } from "../store" + import { authStore, builderStore } from "../store" import { hashString } from "../utils/hash" export let definition = {} @@ -22,7 +22,7 @@ let latestUpdateTime // Get contexts - const dataContext = getContext("data") + const context = getContext("context") // Create component context const componentStore = writable({}) @@ -32,7 +32,7 @@ $: constructor = getComponentConstructor(definition._component) $: children = definition._children || [] $: id = definition._id - $: updateComponentProps(definition, $dataContext, $bindingStore, $authStore) + $: updateComponentProps(definition, $context, $authStore) $: styles = definition._styles // Update component context @@ -53,13 +53,13 @@ } // Enriches any string component props using handlebars - const updateComponentProps = async (definition, context, bindings, user) => { + const updateComponentProps = async (definition, context, user) => { // Record the timestamp so we can reference it after enrichment latestUpdateTime = Date.now() const enrichmentTime = latestUpdateTime // Enrich props with context - const enrichedProps = await enrichProps(definition, context, bindings, user) + const enrichedProps = await enrichProps(definition, context, user) // Abandon this update if a newer update has started if (enrichmentTime !== latestUpdateTime) { diff --git a/packages/client/src/components/DataProvider.svelte b/packages/client/src/components/DataProvider.svelte deleted file mode 100644 index 0e926b4973..0000000000 --- a/packages/client/src/components/DataProvider.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/packages/client/src/components/Provider.svelte b/packages/client/src/components/Provider.svelte new file mode 100644 index 0000000000..f1349796eb --- /dev/null +++ b/packages/client/src/components/Provider.svelte @@ -0,0 +1,29 @@ + + + diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index f5bdb4bb10..effb8f2449 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -1,3 +1,7 @@ export const TableNames = { USERS: "ta_users", } + +export const ActionTypes = { + ValidateForm: "ValidateForm", +} diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index b5efe1257c..1a3a4177a8 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -4,12 +4,12 @@ import { notificationStore, routeStore, screenStore, - bindingStore, builderStore, } from "./store" import { styleable } from "./utils/styleable" import { linkable } from "./utils/linkable" -import DataProvider from "./components/DataProvider.svelte" +import Provider from "./components/Provider.svelte" +import { ActionTypes } from "./constants" export default { API, @@ -20,6 +20,6 @@ export default { builderStore, styleable, linkable, - DataProvider, - setBindableValue: bindingStore.actions.setBindableValue, + Provider, + ActionTypes, } diff --git a/packages/client/src/store/binding.js b/packages/client/src/store/binding.js deleted file mode 100644 index e9ab28831d..0000000000 --- a/packages/client/src/store/binding.js +++ /dev/null @@ -1,21 +0,0 @@ -import { writable } from "svelte/store" - -const createBindingStore = () => { - const store = writable({}) - - const setBindableValue = (componentId, value) => { - store.update(state => { - if (componentId) { - state[componentId] = value - } - return state - }) - } - - return { - subscribe: store.subscribe, - actions: { setBindableValue }, - } -} - -export const bindingStore = createBindingStore() diff --git a/packages/client/src/store/context.js b/packages/client/src/store/context.js new file mode 100644 index 0000000000..a040c5bd20 --- /dev/null +++ b/packages/client/src/store/context.js @@ -0,0 +1,34 @@ +import { writable } from "svelte/store" +import { cloneDeep } from "lodash/fp" + +export const createContextStore = existingContext => { + const store = writable({ ...existingContext }) + + // Adds a data context layer to the tree + const provideData = (componentId, data) => { + store.update(state => { + if (componentId) { + state[componentId] = data + state[`${componentId}_draft`] = cloneDeep(data) + state.closestComponentId = componentId + } + return state + }) + } + + // Adds an action context layer to the tree + const provideAction = (componentId, actionType, callback) => { + store.update(state => { + if (actionType && componentId) { + state[`${componentId}_${actionType}`] = callback + } + return state + }) + } + + return { + subscribe: store.subscribe, + update: store.update, + actions: { provideData, provideAction }, + } +} diff --git a/packages/client/src/store/data.js b/packages/client/src/store/data.js deleted file mode 100644 index 5ff2b9b631..0000000000 --- a/packages/client/src/store/data.js +++ /dev/null @@ -1,26 +0,0 @@ -import { writable } from "svelte/store" -import { cloneDeep } from "lodash/fp" - -export const createDataStore = existingContext => { - const store = writable({ ...existingContext }) - - // Adds a context layer to the data context tree - const addContext = (row, componentId) => { - store.update(state => { - if (componentId) { - state[componentId] = row - state[`${componentId}_draft`] = cloneDeep(row) - state.closestComponentId = componentId - } - return state - }) - } - - return { - subscribe: store.subscribe, - update: store.update, - actions: { addContext }, - } -} - -export const dataStore = createDataStore() diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js index ae1a477c8f..575c5d98f2 100644 --- a/packages/client/src/store/index.js +++ b/packages/client/src/store/index.js @@ -3,10 +3,9 @@ export { notificationStore } from "./notification" export { routeStore } from "./routes" export { screenStore } from "./screens" export { builderStore } from "./builder" -export { bindingStore } from "./binding" -// Data stores are layered and duplicated, so it is not a singleton -export { createDataStore, dataStore } from "./data" +// Context stores are layered and duplicated, so it is not a singleton +export { createContextStore } from "./context" // Initialises an app by loading screens and routes export { initialise } from "./initialise" diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 178abd328d..e293223c5d 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -2,6 +2,7 @@ import { get } from "svelte/store" import { enrichDataBinding, enrichDataBindings } from "./enrichDataBinding" import { routeStore, builderStore } from "../store" import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api" +import { ActionTypes } from "../constants" const saveRowHandler = async (action, context) => { const { fields, providerId } = action.parameters @@ -59,12 +60,21 @@ const queryExecutionHandler = async (action, context) => { }) } +const validateFormHandler = async (action, context) => { + const { componentId } = action.parameters + const fn = context[`${componentId}_${ActionTypes.ValidateForm}`] + if (fn) { + return await fn() + } +} + const handlerMap = { ["Save Row"]: saveRowHandler, ["Delete Row"]: deleteRowHandler, ["Navigate To"]: navigationHandler, ["Execute Query"]: queryExecutionHandler, ["Trigger Automation"]: triggerAutomationHandler, + ["Validate Form"]: validateFormHandler, } /** @@ -79,7 +89,18 @@ export const enrichButtonActions = (actions, context) => { const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]]) return async () => { for (let i = 0; i < handlers.length; i++) { - await handlers[i](actions[i], context) + try { + const result = await handlers[i](actions[i], context) + // A handler returning `false` is a flag to stop execution of handlers + if (result === false) { + return + } + } catch (error) { + console.error("Error while executing button handler") + console.error(error) + // Stop executing on an error + return + } } } } diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js index fb421ca9fb..c4d2d668b1 100644 --- a/packages/client/src/utils/componentProps.js +++ b/packages/client/src/utils/componentProps.js @@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => { * Enriches component props. * Data bindings are enriched, and button actions are enriched. */ -export const enrichProps = async (props, dataContexts, dataBindings, user) => { +export const enrichProps = async (props, context, user) => { // Exclude all private props that start with an underscore let validProps = {} Object.entries(props) @@ -32,20 +32,22 @@ export const enrichProps = async (props, dataContexts, dataBindings, user) => { // Create context of all bindings and data contexts // Duplicate the closest context as "data" which the builder requires - const context = { - ...dataContexts, - ...dataBindings, + const totalContext = { + ...context, user, - data: dataContexts[dataContexts.closestComponentId], - data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`], + data: context[context.closestComponentId], + data_draft: context[`${context.closestComponentId}_draft`], } // Enrich all data bindings in top level props - let enrichedProps = await enrichDataBindings(validProps, context) + let enrichedProps = await enrichDataBindings(validProps, totalContext) // Enrich button actions if they exist if (props._component.endsWith("/button") && enrichedProps.onClick) { - enrichedProps.onClick = enrichButtonActions(enrichedProps.onClick, context) + enrichedProps.onClick = enrichButtonActions( + enrichedProps.onClick, + totalContext + ) } return enrichedProps diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 9733007bb9..0d5a61ad4c 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -1051,11 +1051,12 @@ }, "form": { "name": "Form", - "icon": "ri-file-edit-line", + "icon": "ri-file-text-line", "styleable": true, "hasChildren": true, "dataProvider": true, "datasourceSetting": "datasource", + "actions": ["ValidateForm"], "settings": [ { "type": "datasource", @@ -1102,7 +1103,7 @@ }, "fieldgroup": { "name": "Field Group", - "icon": "ri-edit-box-line", + "icon": "ri-layout-row-line", "styleable": true, "hasChildren": true, "settings": [ @@ -1130,7 +1131,7 @@ }, "stringfield": { "name": "Text Field", - "icon": "ri-edit-box-line", + "icon": "ri-t-box-line", "styleable": true, "settings": [ { @@ -1174,7 +1175,7 @@ }, "optionsfield": { "name": "Options Picker", - "icon": "ri-edit-box-line", + "icon": "ri-file-list-line", "styleable": true, "settings": [ { @@ -1197,7 +1198,7 @@ }, "booleanfield": { "name": "Checkbox", - "icon": "ri-edit-box-line", + "icon": "ri-checkbox-line", "styleable": true, "settings": [ { @@ -1219,7 +1220,7 @@ }, "longformfield": { "name": "Rich Text", - "icon": "ri-edit-box-line", + "icon": "ri-file-edit-line", "styleable": true, "settings": [ { @@ -1270,7 +1271,7 @@ }, "attachmentfield": { "name": "Attachment", - "icon": "ri-calendar-line", + "icon": "ri-image-edit-line", "styleable": true, "settings": [ { @@ -1287,7 +1288,7 @@ }, "relationshipfield": { "name": "Relationship Picker", - "icon": "ri-edit-box-line", + "icon": "ri-links-line", "styleable": true, "settings": [ { diff --git a/packages/standard-components/src/Form.svelte b/packages/standard-components/src/Form.svelte index 0da894b31e..bec99de553 100644 --- a/packages/standard-components/src/Form.svelte +++ b/packages/standard-components/src/Form.svelte @@ -14,7 +14,7 @@ const { styleable, API } = getContext("sdk") const component = getContext("component") - const dataContext = getContext("data") + const context = getContext("context") export let wide = false @@ -23,7 +23,7 @@ let fields = [] // Fetch info about the closest data context - $: getFormData($dataContext[$dataContext.closestComponentId]) + $: getFormData($context[$context.closestComponentId]) const getFormData = async context => { if (context) { @@ -32,7 +32,7 @@ fields = Object.keys(schema ?? {}) // Use the draft version for editing - row = $dataContext[`${$dataContext.closestComponentId}_draft`] + row = $context[`${$context.closestComponentId}_draft`] } } diff --git a/packages/standard-components/src/Input.svelte b/packages/standard-components/src/Input.svelte deleted file mode 100644 index 03dd6ea023..0000000000 --- a/packages/standard-components/src/Input.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte index 9ee59a79b7..ca484d75d3 100644 --- a/packages/standard-components/src/List.svelte +++ b/packages/standard-components/src/List.svelte @@ -2,7 +2,7 @@ import { getContext } from "svelte" import { isEmpty } from "lodash/fp" - const { API, styleable, DataProvider, builderStore } = getContext("sdk") + const { API, styleable, Provider, builderStore } = getContext("sdk") const component = getContext("component") export let datasource = [] @@ -26,9 +26,9 @@

Add some components too

{:else} {#each rows as row} - + - + {/each} {/if} {:else if loaded && $builderStore.inBuilder} diff --git a/packages/standard-components/src/NewRow.svelte b/packages/standard-components/src/NewRow.svelte index 68dcac0b11..9830c87015 100644 --- a/packages/standard-components/src/NewRow.svelte +++ b/packages/standard-components/src/NewRow.svelte @@ -1,14 +1,14 @@
- + - +
diff --git a/packages/standard-components/src/RowDetail.svelte b/packages/standard-components/src/RowDetail.svelte index 7f01341665..ed46fd6927 100644 --- a/packages/standard-components/src/RowDetail.svelte +++ b/packages/standard-components/src/RowDetail.svelte @@ -1,7 +1,7 @@ - +
{/if}
-
+ diff --git a/packages/standard-components/src/NewRow.svelte b/packages/standard-components/src/NewRow.svelte deleted file mode 100644 index 9830c87015..0000000000 --- a/packages/standard-components/src/NewRow.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -
- - - -
diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js index e100a0d6bd..a30e747363 100644 --- a/packages/standard-components/src/index.js +++ b/packages/standard-components/src/index.js @@ -28,7 +28,6 @@ export { default as image } from "./Image.svelte" export { default as embed } from "./Embed.svelte" export { default as cardhorizontal } from "./CardHorizontal.svelte" export { default as cardstat } from "./CardStat.svelte" -export { default as newrow } from "./NewRow.svelte" export { default as icon } from "./Icon.svelte" export * from "./charts" export * from "./forms" From 772fdfa83775fda0dbaaefcc85149838d4902920 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 2 Feb 2021 15:24:15 +0000 Subject: [PATCH 29/86] Remove unused vars and lint --- .../store/screenTemplates/utils/commonComponents.js | 8 -------- packages/client/src/store/context.js | 1 - 2 files changed, 9 deletions(-) diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index f0bd52d53c..f777c12334 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -2,14 +2,6 @@ import { get } from "svelte/store" import { Component } from "./Component" import { rowListUrl } from "../rowListScreen" import { backendUiStore } from "builderStore" -import StringFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/StringFieldSelect.svelte" -import NumberFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/NumberFieldSelect.svelte" -import OptionsFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/OptionsFieldSelect.svelte" -import BooleanFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/BooleanFieldSelect.svelte" -import LongFormFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/LongFormFieldSelect.svelte" -import DateTimeFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/DateTimeFieldSelect.svelte" -import AttachmentFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/AttachmentFieldSelect.svelte" -import RelationshipFieldSelect from "../../../../components/design/PropertiesPanel/PropertyControls/RelationshipFieldSelect.svelte" export function makeLinkComponent(tableName) { return new Component("@budibase/standard-components/link") diff --git a/packages/client/src/store/context.js b/packages/client/src/store/context.js index f6c07bf41d..63aaee3b14 100644 --- a/packages/client/src/store/context.js +++ b/packages/client/src/store/context.js @@ -1,5 +1,4 @@ import { writable } from "svelte/store" -import { cloneDeep } from "lodash/fp" export const createContextStore = existingContext => { const store = writable({ ...existingContext }) From aba4b4ea606f437c32b8348ce6c811bc4c631744 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 2 Feb 2021 15:38:43 +0000 Subject: [PATCH 30/86] Use select for selecting form to validate in button actions to fix issue showing component ID --- .../EventsEditor/actions/ValidateForm.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ValidateForm.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ValidateForm.svelte index 9c50bd8694..b0c8ff758f 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ValidateForm.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/ValidateForm.svelte @@ -1,5 +1,5 @@
From ed2ada720efb9389857d64e7a63d7c6554b00fc9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 2 Feb 2021 17:26:14 +0000 Subject: [PATCH 32/86] Add button to fieldgroups to automatically update from schema --- .../PropertiesPanel/SettingsView.svelte | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte index 154e175f60..948412b004 100644 --- a/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/SettingsView.svelte @@ -1,7 +1,10 @@
@@ -138,6 +152,10 @@ This component doesn't have any additional settings.
{/if} + + {#if componentDefinition?.component?.endsWith('/fieldgroup')} + + {/if}
From 64fb8f709ce78e92d871d2a85db2f2cf36ece6a5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 5 Feb 2021 10:26:38 +0000 Subject: [PATCH 55/86] Fix padding in builder setting icons to be more in line --- .../PropertiesPanel/PropertyControls/PropertyControl.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte index 6f78484339..353a69d509 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte @@ -138,7 +138,7 @@ align-items: center; display: flex; box-sizing: border-box; - padding-left: var(--spacing-xs); + padding-left: 7px; border-left: 1px solid var(--grey-4); background-color: var(--grey-2); border-top-right-radius: var(--border-radius-m); From 1fb4203b3a5665c6778d271b57166c32b59a7a57 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 5 Feb 2021 10:53:25 +0000 Subject: [PATCH 56/86] Adding validation to forms to ensure custom fields can't be the wrong type --- .../src/forms/AttachmentField.svelte | 12 +++++++++--- .../src/forms/BooleanField.svelte | 8 ++++---- .../src/forms/DateTimeField.svelte | 6 +++--- .../src/forms/{SpectrumField.svelte => Field.svelte} | 5 +++++ .../src/forms/LongFormField.svelte | 12 +++++++++--- .../src/forms/OptionsField.svelte | 12 +++++++++--- .../src/forms/RelationshipField.svelte | 7 ++++--- .../standard-components/src/forms/StringField.svelte | 12 ++++++++---- 8 files changed, 51 insertions(+), 23 deletions(-) rename packages/standard-components/src/forms/{SpectrumField.svelte => Field.svelte} (91%) diff --git a/packages/standard-components/src/forms/AttachmentField.svelte b/packages/standard-components/src/forms/AttachmentField.svelte index eebcc65f97..6cf16c109e 100644 --- a/packages/standard-components/src/forms/AttachmentField.svelte +++ b/packages/standard-components/src/forms/AttachmentField.svelte @@ -1,5 +1,5 @@ - + {#if mounted} {/if} - + diff --git a/packages/standard-components/src/forms/BooleanField.svelte b/packages/standard-components/src/forms/BooleanField.svelte index 279542e612..33360474df 100644 --- a/packages/standard-components/src/forms/BooleanField.svelte +++ b/packages/standard-components/src/forms/BooleanField.svelte @@ -1,7 +1,6 @@ - @@ -48,4 +48,4 @@
{/if} - + diff --git a/packages/standard-components/src/forms/DateTimeField.svelte b/packages/standard-components/src/forms/DateTimeField.svelte index 4924e5e383..e58c0ae6bb 100644 --- a/packages/standard-components/src/forms/DateTimeField.svelte +++ b/packages/standard-components/src/forms/DateTimeField.svelte @@ -1,6 +1,6 @@ - + {#if fieldState} {/if} {/if} - + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js index e851bdb4be..b267b8ab3e 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js @@ -4,6 +4,7 @@ import DeleteRow from "./DeleteRow.svelte" import ExecuteQuery from "./ExecuteQuery.svelte" import TriggerAutomation from "./TriggerAutomation.svelte" import ValidateForm from "./ValidateForm.svelte" +import RefreshDatasource from "./RefreshDatasource.svelte" // defines what actions are available, when adding a new one // the component is the setup panel for the action @@ -35,4 +36,8 @@ export default [ name: "Validate Form", component: ValidateForm, }, + { + name: "Refresh Datasource", + component: RefreshDatasource, + }, ] diff --git a/packages/client/src/components/Provider.svelte b/packages/client/src/components/Provider.svelte index 13dd70667b..8c50eeeb01 100644 --- a/packages/client/src/components/Provider.svelte +++ b/packages/client/src/components/Provider.svelte @@ -14,7 +14,7 @@ $: providerKey = key || $component.id // Add data context - $: newContext.actions.provideData(providerKey, data) + $: data !== undefined && newContext.actions.provideData(providerKey, data) // Add actions context $: { diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index effb8f2449..3aa302bec9 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -4,4 +4,5 @@ export const TableNames = { export const ActionTypes = { ValidateForm: "ValidateForm", + RefreshDatasource: "RefreshDatasource", } diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index b769b688d6..f96d18269d 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -50,14 +50,29 @@ const queryExecutionHandler = async action => { }) } -const validateFormHandler = async (action, context) => { - const { componentId } = action.parameters - const fn = context[`${componentId}_${ActionTypes.ValidateForm}`] +const executeActionHandler = async (context, componentId, actionType) => { + const fn = context[`${componentId}_${actionType}`] if (fn) { return await fn() } } +const validateFormHandler = async (action, context) => { + return await executeActionHandler( + context, + action.parameters.componentId, + ActionTypes.ValidateForm + ) +} + +const refreshDatasourceHandler = async (action, context) => { + return await executeActionHandler( + context, + action.parameters.componentId, + ActionTypes.RefreshDatasource + ) +} + const handlerMap = { ["Save Row"]: saveRowHandler, ["Delete Row"]: deleteRowHandler, @@ -65,6 +80,7 @@ const handlerMap = { ["Execute Query"]: queryExecutionHandler, ["Trigger Automation"]: triggerAutomationHandler, ["Validate Form"]: validateFormHandler, + ["Refresh Datasource"]: refreshDatasourceHandler, } /** diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index a57acf0114..5694e3c5b3 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -106,6 +106,7 @@ "styleable": true, "hasChildren": true, "dataProvider": true, + "actions": ["RefreshDatasource"], "settings": [ { "type": "datasource", diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte index 0c09e0b884..4ba10cc28c 100644 --- a/packages/standard-components/src/List.svelte +++ b/packages/standard-components/src/List.svelte @@ -2,7 +2,9 @@ import { getContext } from "svelte" import { isEmpty } from "lodash/fp" - const { API, styleable, Provider, builderStore } = getContext("sdk") + const { API, styleable, Provider, builderStore, ActionTypes } = getContext( + "sdk" + ) const component = getContext("component") export let datasource = [] @@ -18,25 +20,34 @@ } loaded = true } + + $: actions = [ + { + type: ActionTypes.RefreshDatasource, + callback: () => fetchData(datasource), + }, + ] -{#if rows.length > 0} -
- {#if $component.children === 0 && $builderStore.inBuilder} -

Add some components too

- {:else} - {#each rows as row} - - - - {/each} - {/if} -
-{:else if loaded && $builderStore.inBuilder} -
-

Feed me some data

-
-{/if} + + {#if rows.length > 0} +
+ {#if $component.children === 0 && $builderStore.inBuilder} +

Add some components too

+ {:else} + {#each rows as row} + + + + {/each} + {/if} +
+ {:else if loaded && $builderStore.inBuilder} +
+

Feed me some data

+
+ {/if} +
diff --git a/packages/standard-components/src/forms/DateTimeField.svelte b/packages/standard-components/src/forms/DateTimeField.svelte index e58c0ae6bb..1826c77724 100644 --- a/packages/standard-components/src/forms/DateTimeField.svelte +++ b/packages/standard-components/src/forms/DateTimeField.svelte @@ -3,6 +3,7 @@ import Field from "./Field.svelte" import "flatpickr/dist/flatpickr.css" import "@spectrum-css/inputgroup/dist/index-vars.css" + import { generateID } from "../helpers" export let field export let label @@ -14,8 +15,9 @@ let open = false let flatpickr + $: flatpickrId = `${$fieldState?.id}-${generateID()}-wrapper` $: flatpickrOptions = { - element: `#${$fieldState?.id}-wrapper`, + element: `#${flatpickrId}`, enableTime: enableTime || false, altInput: true, altFormat: enableTime ? "F j Y, H:i" : "F j, Y", @@ -46,9 +48,7 @@ // duplicate input field. // We need to blur both because the focus styling does not get properly // applied. - const els = document.querySelectorAll( - `#${$fieldState.fieldId}-wrapper input` - ) + const els = document.querySelectorAll(`#${flatpickrId} input`) els.forEach(el => el.blur()) } @@ -62,9 +62,9 @@ on:close={onClose} options={flatpickrOptions} on:change={handleChange} - element={`#${$fieldState.fieldId}-wrapper`}> + element={`#${flatpickrId}`}>
diff --git a/packages/standard-components/src/forms/FieldGroup.svelte b/packages/standard-components/src/forms/FieldGroup.svelte index 05bad90704..79e1ecad62 100644 --- a/packages/standard-components/src/forms/FieldGroup.svelte +++ b/packages/standard-components/src/forms/FieldGroup.svelte @@ -8,10 +8,20 @@ setContext("fieldGroup", { labelPosition }) -
+
+ + diff --git a/packages/standard-components/src/forms/Form.svelte b/packages/standard-components/src/forms/Form.svelte index d65c851f43..77008bd8b6 100644 --- a/packages/standard-components/src/forms/Form.svelte +++ b/packages/standard-components/src/forms/Form.svelte @@ -168,5 +168,6 @@ diff --git a/packages/standard-components/src/forms/LongFormField.svelte b/packages/standard-components/src/forms/LongFormField.svelte index 2d6f72874f..d22be1b6a9 100644 --- a/packages/standard-components/src/forms/LongFormField.svelte +++ b/packages/standard-components/src/forms/LongFormField.svelte @@ -65,4 +65,7 @@ div :global(.ql-snow .ql-formats:after) { display: none; } + div :global(.ql-editor p) { + word-break: break-all; + } diff --git a/packages/standard-components/src/forms/Picker.svelte b/packages/standard-components/src/forms/Picker.svelte index 8ab50a1b94..153d4f4ea7 100644 --- a/packages/standard-components/src/forms/Picker.svelte +++ b/packages/standard-components/src/forms/Picker.svelte @@ -100,7 +100,10 @@ } .spectrum-Popover { max-height: 240px; - width: var(--spectrum-global-dimension-size-2400); + width: 100%; z-index: 999; } + .spectrum-Picker { + width: 100%; + } diff --git a/packages/standard-components/src/forms/StringField.svelte b/packages/standard-components/src/forms/StringField.svelte index 65693418b1..416a037fb7 100644 --- a/packages/standard-components/src/forms/StringField.svelte +++ b/packages/standard-components/src/forms/StringField.svelte @@ -58,3 +58,9 @@
{/if} + + diff --git a/packages/standard-components/yarn.lock b/packages/standard-components/yarn.lock index 069baf04b2..8b0ca17cdc 100644 --- a/packages/standard-components/yarn.lock +++ b/packages/standard-components/yarn.lock @@ -44,10 +44,10 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@budibase/bbui@^1.58.4": - version "1.58.4" - resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.58.4.tgz#a74d66b3dd715b0a9861a0f86bc0b863fd8f1d44" - integrity sha512-1oEVt7zMREM594CAUIXqOtiuP4Sx4FbfgPBHTZ+t4RhFfbFqvU7yyakqPZM2LhTAmO5Rfa+c+dfFLh+y1++KaA== +"@budibase/bbui@^1.58.5": + version "1.58.5" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.58.5.tgz#c9ce712941760825c7774a1de77594e989db4561" + integrity sha512-0j1I7BetJ2GzB1BXKyvvlkuFphLmADJh2U/Ihubwxx5qUDY8REoVzLgAB4c24zt0CGVTF9VMmOoMLd0zD0QwdQ== dependencies: markdown-it "^12.0.2" quill "^1.3.7" From 2e9122ca8107cc9bb8405b511ca6ba384bd7b122 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 10 Feb 2021 19:42:56 +0000 Subject: [PATCH 81/86] Fix reactive store derivation overriding actions --- .../client/src/components/Provider.svelte | 12 ++----- packages/client/src/store/context.js | 31 ++++++++++++------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/client/src/components/Provider.svelte b/packages/client/src/components/Provider.svelte index 3c894f6723..6f56e62abe 100644 --- a/packages/client/src/components/Provider.svelte +++ b/packages/client/src/components/Provider.svelte @@ -11,17 +11,13 @@ // Clone and create new data context for this component tree const context = getContext("context") const component = getContext("component") - const newContext = createContextStore() + const newContext = createContextStore(context) setContext("context", newContext) - let initiated = false $: providerKey = key || $component.id // Add data context - $: { - newContext.actions.provideData(providerKey, $context, data) - initiated = true - } + $: newContext.actions.provideData(providerKey, data) // Instance ID is unique to each instance of a provider let instanceId @@ -56,6 +52,4 @@ }) -{#if initiated} - -{/if} + diff --git a/packages/client/src/store/context.js b/packages/client/src/store/context.js index 5725f94231..e9d307d4f3 100644 --- a/packages/client/src/store/context.js +++ b/packages/client/src/store/context.js @@ -1,20 +1,27 @@ -import { writable } from "svelte/store" +import { writable, derived } from "svelte/store" -export const createContextStore = () => { - const store = writable({}) +export const createContextStore = oldContext => { + const newContext = writable({}) + const contexts = oldContext ? [oldContext, newContext] : [newContext] + const totalContext = derived(contexts, $contexts => { + return $contexts.reduce((total, context) => ({ ...total, ...context }), {}) + }) // Adds a data context layer to the tree - const provideData = (providerId, context, data) => { - let newData = { ...context } - if (providerId && data !== undefined) { - newData[providerId] = data + const provideData = (providerId, data) => { + if (!providerId || data === undefined) { + return + } + newContext.update(state => { + state[providerId] = data // Keep track of the closest component ID so we can later hydrate a "data" prop. // This is only required for legacy bindings that used "data" rather than a // component ID. - newData.closestComponentId = providerId - } - store.set(newData) + state.closestComponentId = providerId + + return state + }) } // Adds an action context layer to the tree @@ -22,14 +29,14 @@ export const createContextStore = () => { if (!providerId || !actionType) { return } - store.update(state => { + newContext.update(state => { state[`${providerId}_${actionType}`] = callback return state }) } return { - subscribe: store.subscribe, + subscribe: totalContext.subscribe, actions: { provideData, provideAction }, } } From 24c3f3019f7ae364418c81556ab5e224f5af7960 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 11 Feb 2021 09:17:36 +0000 Subject: [PATCH 82/86] Fix automatic form generation from relationship and calculated view schemas --- .../builder/src/builderStore/dataBinding.js | 4 +- .../store/screenTemplates/newRowScreen.js | 5 ++- .../store/screenTemplates/rowDetailScreen.js | 5 ++- .../screenTemplates/utils/commonComponents.js | 37 ++++--------------- 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index b94d0d841a..abd8fdce85 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -211,7 +211,9 @@ export const getSchemaForDatasource = (datasource, isForm = false) => { schema = {} const params = table.parameters || [] params.forEach(param => { - schema[param.name] = { ...param, type: "string" } + if (param?.name) { + schema[param.name] = { ...param, type: "string" } + } }) } else { schema = cloneDeep(table.schema) diff --git a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js index 2ee537ac0c..aeac80e7c1 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/newRowScreen.js @@ -6,7 +6,7 @@ import { makeMainForm, makeTitleContainer, makeSaveButton, - makeTableFormComponents, + makeDatasourceFormComponents, } from "./utils/commonComponents" export default function(tables) { @@ -51,7 +51,8 @@ const createScreen = table => { }) // Add all form fields from this schema to the field group - makeTableFormComponents(table._id).forEach(component => { + const datasource = { type: "table", tableId: table._id } + makeDatasourceFormComponents(datasource).forEach(component => { fieldGroup.addChild(component) }) diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index 0ffb1de59f..0e48cf307e 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -7,9 +7,9 @@ import { makeBreadcrumbContainer, makeTitleContainer, makeSaveButton, - makeTableFormComponents, makeMainForm, spectrumColor, + makeDatasourceFormComponents, } from "./utils/commonComponents" export default function(tables) { @@ -109,7 +109,8 @@ const createScreen = table => { }) // Add all form fields from this schema to the field group - makeTableFormComponents(table._id).forEach(component => { + const datasource = { type: "table", tableId: table._id } + makeDatasourceFormComponents(datasource).forEach(component => { fieldGroup.addChild(component) }) diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 391d1b3c11..4c127fbe0b 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -1,7 +1,6 @@ -import { get } from "svelte/store" import { Component } from "./Component" import { rowListUrl } from "../rowListScreen" -import { backendUiStore } from "builderStore" +import { getSchemaForDatasource } from "../../../dataBinding" export function spectrumColor(number) { // Acorn throws a parsing error in this file if the word g-l-o-b-a-l is found @@ -174,37 +173,15 @@ const fieldTypeToComponentMap = { link: "relationshipfield", } -export function makeTableFormComponents(tableId) { - const tables = get(backendUiStore).tables - const schema = tables.find(table => table._id === tableId)?.schema ?? {} - return makeSchemaFormComponents(schema) -} - -export function makeQueryFormComponents(queryId) { - const queries = get(backendUiStore).queries - const params = queries.find(query => query._id === queryId)?.parameters ?? [] - let schema = {} - params.forEach(param => { - schema[param.name] = { ...param, type: "string" } - }) - return makeSchemaFormComponents(schema) -} - export function makeDatasourceFormComponents(datasource) { - if (!datasource) { - return [] - } - return datasource.type === "table" - ? makeTableFormComponents(datasource.tableId) - : makeQueryFormComponents(datasource._id) -} - -function makeSchemaFormComponents(schema) { + const { schema } = getSchemaForDatasource(datasource, true) let components = [] let fields = Object.keys(schema || {}) fields.forEach(field => { const fieldSchema = schema[field] - const componentType = fieldTypeToComponentMap[fieldSchema.type] + const fieldType = + typeof fieldSchema === "object" ? fieldSchema.type : fieldSchema + const componentType = fieldTypeToComponentMap[fieldType] const fullComponentType = `@budibase/standard-components/${componentType}` if (componentType) { const component = new Component(fullComponentType) @@ -214,10 +191,10 @@ function makeSchemaFormComponents(schema) { label: field, placeholder: field, }) - if (fieldSchema.type === "options") { + if (fieldType === "options") { component.customProps({ placeholder: "Choose an option " }) } - if (fieldSchema.type === "boolean") { + if (fieldType === "boolean") { component.customProps({ text: field, label: "" }) } components.push(component) From c625dcad11c950f6bdcf2df79b55d0d4fca39b10 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 11 Feb 2021 09:19:09 +0000 Subject: [PATCH 83/86] Shrink some settings names to fit in one line --- packages/standard-components/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/standard-components/manifest.json b/packages/standard-components/manifest.json index 5d7e111eca..a7f237a790 100644 --- a/packages/standard-components/manifest.json +++ b/packages/standard-components/manifest.json @@ -142,7 +142,7 @@ }, { "type": "number", - "label": "Rows Per Page", + "label": "Rows/Page", "defaultValue": 25, "key": "pageSize" }, From a16614782669596c573802a2b79f59e0f5c1ec24 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 11 Feb 2021 09:20:25 +0000 Subject: [PATCH 84/86] Update some empty state text --- packages/standard-components/src/List.svelte | 2 +- packages/standard-components/src/Search.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/standard-components/src/List.svelte b/packages/standard-components/src/List.svelte index 045208f06d..9923851abb 100644 --- a/packages/standard-components/src/List.svelte +++ b/packages/standard-components/src/List.svelte @@ -33,7 +33,7 @@
{#if rows.length > 0} {#if $component.children === 0 && $builderStore.inBuilder} -

Add some components to display

+

Add some components to display.

{:else} {#each rows as row} diff --git a/packages/standard-components/src/Search.svelte b/packages/standard-components/src/Search.svelte index affedccd2c..195479cdd9 100644 --- a/packages/standard-components/src/Search.svelte +++ b/packages/standard-components/src/Search.svelte @@ -107,7 +107,7 @@ {#if loaded} {#if rows.length > 0} {#if $component.children === 0 && $builderStore.inBuilder} -

Add some components to display

+

Add some components to display.

{:else} {#each rows as row} From 81e779c8b5e168f45f05c1567775d07879666972 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 11 Feb 2021 09:30:05 +0000 Subject: [PATCH 85/86] Add hot reloading to search results --- .../standard-components/src/Search.svelte | 146 ++++++++++-------- 1 file changed, 78 insertions(+), 68 deletions(-) diff --git a/packages/standard-components/src/Search.svelte b/packages/standard-components/src/Search.svelte index 195479cdd9..509205f8f1 100644 --- a/packages/standard-components/src/Search.svelte +++ b/packages/standard-components/src/Search.svelte @@ -1,6 +1,5 @@ -
-
- {#if schema} - {#each columns as field} -
- - {#if schema[field].type === 'options'} - - {:else if schema[field].type === 'datetime'} - - {:else if schema[field].type === 'boolean'} - - {:else if schema[field].type === 'number'} - - {:else if schema[field].type === 'string'} - - {/if} -
- {/each} - {/if} -
- - -
-
- {#if loaded} - {#if rows.length > 0} - {#if $component.children === 0 && $builderStore.inBuilder} -

Add some components to display.

- {:else} - {#each rows as row} - - - + +
+
+ {#if schema} + {#each columns as field} +
+ + {#if schema[field].type === 'options'} + + {:else if schema[field].type === 'datetime'} + + {:else if schema[field].type === 'boolean'} + + {:else if schema[field].type === 'number'} + + {:else if schema[field].type === 'string'} + + {/if} +
{/each} {/if} - {:else if noRowsMessage} -

{noRowsMessage}

- {/if} - {/if} - + {#if loaded} + {#if rows.length > 0} + {#if $component.children === 0 && $builderStore.inBuilder} +

Add some components to display.

+ {:else} + {#each rows as row} + + + + {/each} + {/if} + {:else if noRowsMessage} +

{noRowsMessage}

+ {/if} {/if} +
-
+