Add client app state and button actions to control it
This commit is contained in:
parent
7c9f0b83ca
commit
a7be624d64
|
@ -18,7 +18,9 @@ export const getBindableProperties = (asset, componentId) => {
|
||||||
const userBindings = getUserBindings()
|
const userBindings = getUserBindings()
|
||||||
const urlBindings = getUrlBindings(asset)
|
const urlBindings = getUrlBindings(asset)
|
||||||
const deviceBindings = getDeviceBindings()
|
const deviceBindings = getDeviceBindings()
|
||||||
|
const stateBindings = getStateBindings()
|
||||||
return [
|
return [
|
||||||
|
...stateBindings,
|
||||||
...deviceBindings,
|
...deviceBindings,
|
||||||
...urlBindings,
|
...urlBindings,
|
||||||
...contextBindings,
|
...contextBindings,
|
||||||
|
@ -256,6 +258,19 @@ const getDeviceBindings = () => {
|
||||||
return bindings
|
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.
|
* Gets all bindable properties from URL parameters.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script>
|
||||||
|
import { Select, Label, Input } from "@budibase/bbui"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
|
export let parameters
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
|
const typeOptions = [
|
||||||
|
{
|
||||||
|
label: "Set value",
|
||||||
|
value: "set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Delete value",
|
||||||
|
value: "delete",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!parameters.type) {
|
||||||
|
parameters.type = "set"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Label small>Type</Label>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
bind:value={parameters.type}
|
||||||
|
options={typeOptions}
|
||||||
|
/>
|
||||||
|
<Label small>Key</Label>
|
||||||
|
<Input bind:value={parameters.key} />
|
||||||
|
{#if parameters.type === "set"}
|
||||||
|
<Label small>Value</Label>
|
||||||
|
<DrawerBindableInput
|
||||||
|
{bindings}
|
||||||
|
value={parameters.value}
|
||||||
|
on:change={e => (parameters.value = e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--spacing-l);
|
||||||
|
row-gap: var(--spacing-s);
|
||||||
|
grid-template-columns: 60px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -8,6 +8,7 @@ import LogOut from "./LogOut.svelte"
|
||||||
import ClearForm from "./ClearForm.svelte"
|
import ClearForm from "./ClearForm.svelte"
|
||||||
import CloseScreenModal from "./CloseScreenModal.svelte"
|
import CloseScreenModal from "./CloseScreenModal.svelte"
|
||||||
import ChangeFormStep from "./ChangeFormStep.svelte"
|
import ChangeFormStep from "./ChangeFormStep.svelte"
|
||||||
|
import UpdateStateStep from "./UpdateState.svelte"
|
||||||
|
|
||||||
// Defines which actions are available to configure in the front end.
|
// Defines which actions are available to configure in the front end.
|
||||||
// Unfortunately the "name" property is used as the identifier so please don't
|
// Unfortunately the "name" property is used as the identifier so please don't
|
||||||
|
@ -57,4 +58,8 @@ export default [
|
||||||
name: "Change Form Step",
|
name: "Change Form Step",
|
||||||
component: ChangeFormStep,
|
component: ChangeFormStep,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Update State",
|
||||||
|
component: UpdateStateStep,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
import ErrorSVG from "../../../builder/assets/error.svg"
|
import ErrorSVG from "../../../builder/assets/error.svg"
|
||||||
import UserBindingsProvider from "./UserBindingsProvider.svelte"
|
import UserBindingsProvider from "./UserBindingsProvider.svelte"
|
||||||
import DeviceBindingsProvider from "./DeviceBindingsProvider.svelte"
|
import DeviceBindingsProvider from "./DeviceBindingsProvider.svelte"
|
||||||
|
import StateBindingsProvider from "./StateBindingsProvider.svelte"
|
||||||
|
|
||||||
// Provide contexts
|
// Provide contexts
|
||||||
setContext("sdk", SDK)
|
setContext("sdk", SDK)
|
||||||
|
@ -85,28 +86,30 @@
|
||||||
{:else if $screenStore.activeLayout}
|
{:else if $screenStore.activeLayout}
|
||||||
<UserBindingsProvider>
|
<UserBindingsProvider>
|
||||||
<DeviceBindingsProvider>
|
<DeviceBindingsProvider>
|
||||||
<div id="app-root" class:preview={$builderStore.inBuilder}>
|
<StateBindingsProvider>
|
||||||
{#key $screenStore.activeLayout._id}
|
<div id="app-root" class:preview={$builderStore.inBuilder}>
|
||||||
<Component instance={$screenStore.activeLayout.props} />
|
{#key $screenStore.activeLayout._id}
|
||||||
|
<Component instance={$screenStore.activeLayout.props} />
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
<NotificationDisplay />
|
||||||
|
<ConfirmationDisplay />
|
||||||
|
<PeekScreenDisplay />
|
||||||
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
|
{#key $builderStore.selectedComponentId}
|
||||||
|
{#if $builderStore.inBuilder}
|
||||||
|
<SettingsBar />
|
||||||
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
<!--
|
||||||
<NotificationDisplay />
|
We don't want to key these by componentID as they control their own
|
||||||
<ConfirmationDisplay />
|
re-mounting to avoid flashes.
|
||||||
<PeekScreenDisplay />
|
-->
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
|
||||||
{#key $builderStore.selectedComponentId}
|
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
<SettingsBar />
|
<SelectionIndicator />
|
||||||
|
<HoverIndicator />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
</StateBindingsProvider>
|
||||||
<!--
|
|
||||||
We don't want to key these by componentID as they control their own
|
|
||||||
re-mounting to avoid flashes.
|
|
||||||
-->
|
|
||||||
{#if $builderStore.inBuilder}
|
|
||||||
<SelectionIndicator />
|
|
||||||
<HoverIndicator />
|
|
||||||
{/if}
|
|
||||||
</DeviceBindingsProvider>
|
</DeviceBindingsProvider>
|
||||||
</UserBindingsProvider>
|
</UserBindingsProvider>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import Provider from "./Provider.svelte"
|
||||||
|
import { stateStore } from "../store"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Provider key="state" data={$stateStore}>
|
||||||
|
<slot />
|
||||||
|
</Provider>
|
|
@ -7,6 +7,7 @@ export { builderStore } from "./builder"
|
||||||
export { dataSourceStore } from "./dataSource"
|
export { dataSourceStore } from "./dataSource"
|
||||||
export { confirmationStore } from "./confirmation"
|
export { confirmationStore } from "./confirmation"
|
||||||
export { peekStore } from "./peek"
|
export { peekStore } from "./peek"
|
||||||
|
export { stateStore } from "./state"
|
||||||
|
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
|
|
@ -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()
|
|
@ -5,6 +5,7 @@ import {
|
||||||
confirmationStore,
|
confirmationStore,
|
||||||
authStore,
|
authStore,
|
||||||
peekStore,
|
peekStore,
|
||||||
|
stateStore,
|
||||||
} from "../store"
|
} from "../store"
|
||||||
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
|
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
|
||||||
import { ActionTypes } from "../constants"
|
import { ActionTypes } from "../constants"
|
||||||
|
@ -122,6 +123,15 @@ const closeScreenModalHandler = () => {
|
||||||
window.dispatchEvent(new Event("close-screen-modal"))
|
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 = {
|
const handlerMap = {
|
||||||
["Save Row"]: saveRowHandler,
|
["Save Row"]: saveRowHandler,
|
||||||
["Delete Row"]: deleteRowHandler,
|
["Delete Row"]: deleteRowHandler,
|
||||||
|
@ -134,6 +144,7 @@ const handlerMap = {
|
||||||
["Clear Form"]: clearFormHandler,
|
["Clear Form"]: clearFormHandler,
|
||||||
["Close Screen Modal"]: closeScreenModalHandler,
|
["Close Screen Modal"]: closeScreenModalHandler,
|
||||||
["Change Form Step"]: changeFormStepHandler,
|
["Change Form Step"]: changeFormStepHandler,
|
||||||
|
["Update State"]: updateStateHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmTextMap = {
|
const confirmTextMap = {
|
||||||
|
|
Loading…
Reference in New Issue