diff --git a/lerna.json b/lerna.json index 7697b1a208..702efb014d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.79-alpha.4", + "version": "0.9.80-alpha.9", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 15aef51223..986e4da18d 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.79-alpha.4", + "version": "0.9.80-alpha.9", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index ecef7d6ef9..9be242d877 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.79-alpha.4", + "version": "0.9.80-alpha.9", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/bbui/src/Modal/Modal.svelte b/packages/bbui/src/Modal/Modal.svelte index 3ed9b93fa5..c93467bc75 100644 --- a/packages/bbui/src/Modal/Modal.svelte +++ b/packages/bbui/src/Modal/Modal.svelte @@ -115,7 +115,6 @@ } .spectrum-Modal { - background: var(--background); overflow: visible; max-height: none; margin: 40px 0; diff --git a/packages/bbui/src/Modal/ModalContent.svelte b/packages/bbui/src/Modal/ModalContent.svelte index dae9b9e8af..e6d4ab5f0f 100644 --- a/packages/bbui/src/Modal/ModalContent.svelte +++ b/packages/bbui/src/Modal/ModalContent.svelte @@ -88,7 +88,6 @@ display: grid; position: relative; gap: var(--spacing-xl); - color: var(--ink); } .spectrum-Dialog-content { @@ -106,13 +105,8 @@ position: absolute; top: 15px; right: 15px; - color: var(--ink); font-size: var(--font-size-m); } - .close-icon:hover { - color: var(--grey-6); - cursor: pointer; - } .close-icon :global(svg) { margin-right: 0; } diff --git a/packages/builder/package.json b/packages/builder/package.json index 5ffbb99338..b223f3a82a 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.79-alpha.4", + "version": "0.9.80-alpha.9", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.79-alpha.4", - "@budibase/client": "^0.9.79-alpha.4", + "@budibase/bbui": "^0.9.80-alpha.9", + "@budibase/client": "^0.9.80-alpha.9", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.79-alpha.4", + "@budibase/string-templates": "^0.9.80-alpha.9", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index b9cd134067..f84e1c8735 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -530,6 +530,11 @@ export const getFrontendStore = () => { selected._styles = { normal: {}, hover: {}, active: {} } await store.actions.preview.saveSelected() }, + updateConditions: async conditions => { + const selected = get(selectedComponent) + selected._conditions = conditions + await store.actions.preview.saveSelected() + }, updateProp: async (name, value) => { let component = get(selectedComponent) if (!name || !component) { diff --git a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte index 8b7417c41f..588a3a8486 100644 --- a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte @@ -6,8 +6,10 @@ import ErrorsBox from "components/common/ErrorsBox.svelte" import { roles } from "stores/backend" + const BASE_ROLE = { _id: "", inherits: "BASIC", permissionId: "Read/Write" } + let basePermissions = [] - let selectedRole = {} + let selectedRole = BASE_ROLE let errors = [] let builtInRoles = ["Admin", "Power", "Basic", "Public"] // Don't allow editing of public role @@ -15,6 +17,11 @@ $: selectedRoleId = selectedRole._id $: otherRoles = editableRoles.filter(role => role._id !== selectedRoleId) $: isCreating = selectedRoleId == null || selectedRoleId === "" + $: valid = + selectedRole.name && + selectedRole.inherits && + selectedRole.permissionId && + !builtInRoles.includes(selectedRole.name) const fetchBasePermissions = async () => { const permissionsResponse = await api.get("/api/permission/builtin") @@ -32,7 +39,7 @@ permissionId: role.permissionId ?? "", } } else { - selectedRole = { _id: "", inherits: "", permissionId: "" } + selectedRole = BASE_ROLE } errors = [] } @@ -88,6 +95,7 @@ title="Edit Roles" confirmText={isCreating ? "Create" : "Save"} onConfirm={saveRole} + disabled={!valid} > {#if errors.length} @@ -115,7 +123,7 @@ options={otherRoles} getOptionValue={role => role._id} getOptionLabel={role => role.name} - placeholder="None" + disabled={builtInRoles.includes(selectedRole.name)} /> + {#if condition.action === "update"} + + {/if} + {/if} +
IF
+ (condition.newValue = e.detail)} + /> + onValueTypeChange(condition, e.detail)} + /> + {#if ["string", "number"].includes(condition.valueType)} + (condition.referenceValue = e.detail)} + /> + {:else if condition.valueType === "datetime"} + + {:else if condition.valueType === "boolean"} + diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte index c1b5fa2f3b..40d579e9c9 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte @@ -70,7 +70,7 @@
- {#if type !== "boolean"} + {#if type !== "boolean" && label}
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js new file mode 100644 index 0000000000..8435971714 --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/componentSettings.js @@ -0,0 +1,54 @@ +import { Checkbox, Input, Select } from "@budibase/bbui" +import DataSourceSelect from "./DataSourceSelect.svelte" +import DataProviderSelect from "./DataProviderSelect.svelte" +import EventsEditor from "./EventsEditor" +import TableSelect from "./TableSelect.svelte" +import ColorPicker from "./ColorPicker.svelte" +import { IconSelect } from "./IconSelect" +import FieldSelect from "./FieldSelect.svelte" +import MultiFieldSelect from "./MultiFieldSelect.svelte" +import SchemaSelect from "./SchemaSelect.svelte" +import SectionSelect from "./SectionSelect.svelte" +import NavigationEditor from "./NavigationEditor/NavigationEditor.svelte" +import FilterEditor from "./FilterEditor/FilterEditor.svelte" +import URLSelect from "./URLSelect.svelte" +import StringFieldSelect from "./StringFieldSelect.svelte" +import NumberFieldSelect from "./NumberFieldSelect.svelte" +import OptionsFieldSelect from "./OptionsFieldSelect.svelte" +import BooleanFieldSelect from "./BooleanFieldSelect.svelte" +import LongFormFieldSelect from "./LongFormFieldSelect.svelte" +import DateTimeFieldSelect from "./DateTimeFieldSelect.svelte" +import AttachmentFieldSelect from "./AttachmentFieldSelect.svelte" +import RelationshipFieldSelect from "./RelationshipFieldSelect.svelte" + +const componentMap = { + text: Input, + select: Select, + dataSource: DataSourceSelect, + dataProvider: DataProviderSelect, + boolean: Checkbox, + number: Input, + event: EventsEditor, + table: TableSelect, + color: ColorPicker, + icon: IconSelect, + field: FieldSelect, + multifield: MultiFieldSelect, + schema: SchemaSelect, + section: SectionSelect, + navigation: NavigationEditor, + filter: FilterEditor, + url: URLSelect, + "field/string": StringFieldSelect, + "field/number": NumberFieldSelect, + "field/options": OptionsFieldSelect, + "field/boolean": BooleanFieldSelect, + "field/longform": LongFormFieldSelect, + "field/datetime": DateTimeFieldSelect, + "field/attachment": AttachmentFieldSelect, + "field/link": RelationshipFieldSelect, +} + +export const getComponentForSettingType = type => { + return componentMap[type] +} diff --git a/packages/builder/src/helpers/lucene.js b/packages/builder/src/helpers/lucene.js new file mode 100644 index 0000000000..43fb155e14 --- /dev/null +++ b/packages/builder/src/helpers/lucene.js @@ -0,0 +1,80 @@ +export const OperatorOptions = { + Equals: { + value: "equal", + label: "Equals", + }, + NotEquals: { + value: "notEqual", + label: "Not equals", + }, + Empty: { + value: "empty", + label: "Is empty", + }, + NotEmpty: { + value: "notEmpty", + label: "Is not empty", + }, + StartsWith: { + value: "string", + label: "Starts with", + }, + Like: { + value: "fuzzy", + label: "Like", + }, + MoreThan: { + value: "rangeLow", + label: "More than", + }, + LessThan: { + value: "rangeHigh", + label: "Less than", + }, +} + +export const getValidOperatorsForType = type => { + const Op = OperatorOptions + if (type === "string") { + return [ + Op.Equals, + Op.NotEquals, + Op.StartsWith, + Op.Like, + Op.Empty, + Op.NotEmpty, + ] + } else if (type === "number") { + return [ + Op.Equals, + Op.NotEquals, + Op.MoreThan, + Op.LessThan, + Op.Empty, + Op.NotEmpty, + ] + } else if (type === "options") { + return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] + } else if (type === "boolean") { + return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] + } else if (type === "longform") { + return [ + Op.Equals, + Op.NotEquals, + Op.StartsWith, + Op.Like, + Op.Empty, + Op.NotEmpty, + ] + } else if (type === "datetime") { + return [ + Op.Equals, + Op.NotEquals, + Op.MoreThan, + Op.LessThan, + Op.Empty, + Op.NotEmpty, + ] + } + return [] +} diff --git a/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte index feb15d00bb..d1aaeb0240 100644 --- a/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte @@ -58,5 +58,6 @@ align-items: stretch; gap: var(--spacing-l); background-color: var(--background); + overflow-y: auto; } diff --git a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte index 43360ddf51..c0f21bb554 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte @@ -19,7 +19,8 @@ import { fetchData } from "helpers" import { users, auth } from "stores/portal" - import TagsRenderer from "./_components/TagsTableRenderer.svelte" + import TagsRenderer from "./_components/RolesTagsTableRenderer.svelte" + import UpdateRolesModal from "./_components/UpdateRolesModal.svelte" import ForceResetPasswordModal from "./_components/ForceResetPasswordModal.svelte" @@ -36,7 +37,8 @@ $: defaultRoleId = $userFetch?.data?.builder?.global ? "ADMIN" : "BASIC" // Merge the Apps list and the roles response to get something that makes sense for the table $: appList = Object.keys($apps?.data).map(id => { - const role = $userFetch?.data?.roles?.[id] || defaultRoleId + const roleId = $userFetch?.data?.roles?.[id] || defaultRoleId + const role = $apps?.data?.[id].roles.find(role => role._id === roleId) return { ...$apps?.data?.[id], _id: id, diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/RolesTagsTableRenderer.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/RolesTagsTableRenderer.svelte new file mode 100644 index 0000000000..7e63045edd --- /dev/null +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/RolesTagsTableRenderer.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/TagsTableRenderer.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/TagsTableRenderer.svelte index eab0ccd19c..d2b56bbf33 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/TagsTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/TagsTableRenderer.svelte @@ -4,9 +4,9 @@ const displayLimit = 5 - $: roles = value?.filter(role => role != null) ?? [] - $: tags = roles.slice(0, displayLimit) - $: leftover = roles.length - tags.length + $: values = value?.filter(value => value != null) ?? [] + $: tags = values.slice(0, displayLimit) + $: leftover = values.length - tags.length
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte index 332be8e2d4..59045a1198 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/UpdateRolesModal.svelte @@ -10,8 +10,8 @@ const roles = app.roles let options = roles + .filter(role => role._id !== "PUBLIC") .map(role => ({ value: role._id, label: role.name })) - .filter(role => role.value !== "PUBLIC") let selectedRole = user?.roles?.[app?._id] async function updateUserRoles() { @@ -48,5 +48,7 @@ on:change {options} label="Role" + getOptionLabel={role => role.name} + getOptionValue={role => role._id} /> diff --git a/packages/cli/package.json b/packages/cli/package.json index 9e3ac8c9a1..fc3cb22f02 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.79-alpha.4", + "version": "0.9.80-alpha.9", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 40fb38ac78..54b2c53bd2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.79-alpha.4", + "version": "0.9.80-alpha.9", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -18,9 +18,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.79-alpha.4", - "@budibase/standard-components": "^0.9.79-alpha.4", - "@budibase/string-templates": "^0.9.79-alpha.4", + "@budibase/bbui": "^0.9.80-alpha.9", + "@budibase/standard-components": "^0.9.80-alpha.9", + "@budibase/string-templates": "^0.9.80-alpha.9", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 4440e83845..8c7437e523 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -8,11 +8,18 @@ import { hashString } from "../utils/hash" import Manifest from "@budibase/standard-components/manifest.json" import { Placeholder } from "@budibase/standard-components" + import { + getActiveConditions, + reduceConditionActions, + } from "../utils/conditions" export let instance = {} - // Props that will be passed to the component instance - let componentProps + // The enriched component settings + let enrichedSettings + + // Any prop overrides that need to be applied due to conditional UI + let conditionalSettings // Props are hashed when inside the builder preview and used as a key, so that // components fully remount whenever any props change @@ -28,6 +35,9 @@ let lastContextKey let lastInstanceKey + // Visibility flag used by conditional UI + let visible = true + // Get contexts const context = getContext("context") const insideScreenslot = !!getContext("screenslot") @@ -54,6 +64,8 @@ $builderStore.inBuilder && $builderStore.selectedComponentId === instance._id $: interactive = $builderStore.previewType === "layout" || insideScreenslot + $: evaluateConditions(enrichedSettings?._conditions) + $: componentSettings = { ...enrichedSettings, ...conditionalSettings } // Update component context $: componentStore.set({ @@ -62,14 +74,14 @@ styles: { ...instance._styles, id, empty, interactive }, empty, selected, - props: componentProps, + props: componentSettings, name, }) const getRawProps = instance => { let validProps = {} Object.entries(instance) - .filter(([name]) => !name.startsWith("_")) + .filter(([name]) => name === "_conditions" || !name.startsWith("_")) .forEach(([key, value]) => { validProps[key] = value }) @@ -123,34 +135,55 @@ return } let propsChanged = false - if (!componentProps) { - componentProps = {} + if (!enrichedSettings) { + enrichedSettings = {} propsChanged = true } Object.keys(enrichedProps).forEach(key => { - if (!propsAreSame(enrichedProps[key], componentProps[key])) { + if (!propsAreSame(enrichedProps[key], enrichedSettings[key])) { propsChanged = true - componentProps[key] = enrichedProps[key] + enrichedSettings[key] = enrichedProps[key] } }) // Update the hash if we're in the builder so we can fully remount this // component if (get(builderStore).inBuilder && propsChanged) { - propsHash = hashString(JSON.stringify(componentProps)) + propsHash = hashString(JSON.stringify(enrichedSettings)) } } + + const evaluateConditions = conditions => { + if (!conditions?.length) { + return + } + + // Default visible to false if there is a show condition + let nextVisible = !conditions.find(condition => condition.action === "show") + + // Execute conditions and determine settings and visibility changes + const activeConditions = getActiveConditions(conditions) + const result = reduceConditionActions(activeConditions) + if (result.visible != null) { + nextVisible = result.visible + } + + // Update state from condition results + conditionalSettings = result.settingUpdates + visible = nextVisible + } -
- {#key propsHash} - {#if constructor && componentProps} - +{#key propsHash} + {#if constructor && componentSettings && visible} +
+ {#if children.length} {#each children as child (child._id)} @@ -159,9 +192,9 @@ {/if} - {/if} - {/key} -
+
+ {/if} +{/key}