From a7be624d6448ef9ce3b6d295fb96cd0d1b4a5aba Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 26 Aug 2021 11:28:44 +0100 Subject: [PATCH 1/5] Add client app state and button actions to control it --- .../builder/src/builderStore/dataBinding.js | 15 +++++ .../EventsEditor/actions/UpdateState.svelte | 56 +++++++++++++++++++ .../EventsEditor/actions/index.js | 5 ++ .../client/src/components/ClientApp.svelte | 41 +++++++------- .../components/StateBindingsProvider.svelte | 8 +++ packages/client/src/store/index.js | 1 + packages/client/src/store/state.js | 25 +++++++++ packages/client/src/utils/buttonActions.js | 11 ++++ 8 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte create mode 100644 packages/client/src/components/StateBindingsProvider.svelte create mode 100644 packages/client/src/store/state.js diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 00eaaf0249..f6e861137b 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -18,7 +18,9 @@ export const getBindableProperties = (asset, componentId) => { const userBindings = getUserBindings() const urlBindings = getUrlBindings(asset) const deviceBindings = getDeviceBindings() + const stateBindings = getStateBindings() return [ + ...stateBindings, ...deviceBindings, ...urlBindings, ...contextBindings, @@ -256,6 +258,19 @@ const getDeviceBindings = () => { return bindings } +/** + * Gets all state bindings that are globally available. + */ +const getStateBindings = () => { + return [ + { + type: "context", + runtimeBinding: makePropSafe("state"), + readableBinding: `State`, + }, + ] +} + /** * Gets all bindable properties from URL parameters. */ diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte new file mode 100644 index 0000000000..c85ad70289 --- /dev/null +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte @@ -0,0 +1,56 @@ + + +
+ + + {#if parameters.type === "set"} + + (parameters.value = e.detail)} + /> + {/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 9cf2461b77..6c285939ac 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 @@ -8,6 +8,7 @@ import LogOut from "./LogOut.svelte" import ClearForm from "./ClearForm.svelte" import CloseScreenModal from "./CloseScreenModal.svelte" import ChangeFormStep from "./ChangeFormStep.svelte" +import UpdateStateStep from "./UpdateState.svelte" // Defines which actions are available to configure in the front end. // Unfortunately the "name" property is used as the identifier so please don't @@ -57,4 +58,8 @@ export default [ name: "Change Form Step", component: ChangeFormStep, }, + { + name: "Update State", + component: UpdateStateStep, + }, ] diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 11347821a9..d46199dbdd 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -22,6 +22,7 @@ import ErrorSVG from "../../../builder/assets/error.svg" import UserBindingsProvider from "./UserBindingsProvider.svelte" import DeviceBindingsProvider from "./DeviceBindingsProvider.svelte" + import StateBindingsProvider from "./StateBindingsProvider.svelte" // Provide contexts setContext("sdk", SDK) @@ -85,28 +86,30 @@ {:else if $screenStore.activeLayout} -
- {#key $screenStore.activeLayout._id} - + +
+ {#key $screenStore.activeLayout._id} + + {/key} +
+ + + + + {#key $builderStore.selectedComponentId} + {#if $builderStore.inBuilder} + + {/if} {/key} -
- - - - - {#key $builderStore.selectedComponentId} + {#if $builderStore.inBuilder} - + + {/if} - {/key} - - {#if $builderStore.inBuilder} - - - {/if} +
{/if} diff --git a/packages/client/src/components/StateBindingsProvider.svelte b/packages/client/src/components/StateBindingsProvider.svelte new file mode 100644 index 0000000000..b2c049ff31 --- /dev/null +++ b/packages/client/src/components/StateBindingsProvider.svelte @@ -0,0 +1,8 @@ + + + + + diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js index 43de7bb6da..6c99e3f453 100644 --- a/packages/client/src/store/index.js +++ b/packages/client/src/store/index.js @@ -7,6 +7,7 @@ export { builderStore } from "./builder" export { dataSourceStore } from "./dataSource" export { confirmationStore } from "./confirmation" export { peekStore } from "./peek" +export { stateStore } from "./state" // Context stores are layered and duplicated, so it is not a singleton export { createContextStore } from "./context" diff --git a/packages/client/src/store/state.js b/packages/client/src/store/state.js new file mode 100644 index 0000000000..c32264914b --- /dev/null +++ b/packages/client/src/store/state.js @@ -0,0 +1,25 @@ +import { writable } from "svelte/store" + +const createStateStore = () => { + const store = writable({}) + + const setValue = (key, value) => { + store.update(state => { + state[key] = value + return state + }) + } + const deleteValue = key => { + store.update(state => { + delete state[key] + return state + }) + } + + return { + subscribe: store.subscribe, + actions: { setValue, deleteValue }, + } +} + +export const stateStore = createStateStore() diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index e6be6ec03d..11c177d4f6 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -5,6 +5,7 @@ import { confirmationStore, authStore, peekStore, + stateStore, } from "../store" import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api" import { ActionTypes } from "../constants" @@ -122,6 +123,15 @@ const closeScreenModalHandler = () => { window.dispatchEvent(new Event("close-screen-modal")) } +const updateStateHandler = action => { + const { type, key, value } = action.parameters + if (type === "set") { + stateStore.actions.setValue(key, value) + } else if (type === "delete") { + stateStore.actions.deleteValue(key) + } +} + const handlerMap = { ["Save Row"]: saveRowHandler, ["Delete Row"]: deleteRowHandler, @@ -134,6 +144,7 @@ const handlerMap = { ["Clear Form"]: clearFormHandler, ["Close Screen Modal"]: closeScreenModalHandler, ["Change Form Step"]: changeFormStepHandler, + ["Update State"]: updateStateHandler, } const confirmTextMap = { From 21029b80ba40b5512e025c79f196dc78f635b855 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 26 Aug 2021 16:46:25 +0100 Subject: [PATCH 2/5] Automatically determine which state keys are available --- .../builder/src/builderStore/dataBinding.js | 65 ++++++++++++++++--- .../EventsEditor/actions/UpdateState.svelte | 6 +- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index f6e861137b..887695d41e 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -1,6 +1,10 @@ import { cloneDeep } from "lodash/fp" import { get } from "svelte/store" -import { findComponent, findComponentPath } from "./storeUtils" +import { + findComponent, + findComponentPath, + findAllMatchingComponents, +} from "./storeUtils" import { store } from "builderStore" import { tables as tablesStore, queries as queriesStores } from "stores/backend" import { makePropSafe } from "@budibase/string-templates" @@ -262,13 +266,12 @@ const getDeviceBindings = () => { * Gets all state bindings that are globally available. */ const getStateBindings = () => { - return [ - { - type: "context", - runtimeBinding: makePropSafe("state"), - readableBinding: `State`, - }, - ] + const safeState = makePropSafe("state") + return getAllStateVariables().map(key => ({ + type: "context", + runtimeBinding: `${safeState}.${makePropSafe(key)}`, + readableBinding: `State.${key}`, + })) } /** @@ -473,3 +476,49 @@ export function runtimeToReadableBinding(bindableProperties, textWithBindings) { "readableBinding" ) } + +/** + * Returns an array of the keys of any state variables which are set anywhere + * in the app. + */ +export const getAllStateVariables = () => { + let allComponents = [] + + // Find all onClick settings in all layouts + get(store).layouts.forEach(layout => { + const components = findAllMatchingComponents( + layout.props, + c => c.onClick != null + ) + allComponents = allComponents.concat(components || []) + }) + + // Find all onClick settings in all screens + get(store).screens.forEach(screen => { + const components = findAllMatchingComponents( + screen.props, + c => c.onClick != null + ) + allComponents = allComponents.concat(components || []) + }) + + // Add state bindings for all state actions + let bindingSet = new Set() + allComponents.forEach(component => { + if (!Array.isArray(component.onClick)) { + return + } + component.onClick.forEach(action => { + if ( + action["##eventHandlerType"] === "Update State" && + action.parameters?.type === "set" && + action.parameters?.key && + action.parameters?.value + ) { + bindingSet.add(action.parameters.key) + } + }) + }) + + return Array.from(bindingSet) +} diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte index c85ad70289..c9a0c2119e 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte @@ -1,11 +1,13 @@