Add support for confirmable action handling by client library

This commit is contained in:
Andrew Kingston 2021-06-21 09:56:46 +01:00
parent 064690c85a
commit 1879fbeee3
6 changed files with 94 additions and 46 deletions

View File

@ -3,6 +3,7 @@
import { setContext, onMount } from "svelte"
import Component from "./Component.svelte"
import NotificationDisplay from "./NotificationDisplay.svelte"
import ConfirmationDisplay from "./ConfirmationDisplay.svelte"
import Provider from "./Provider.svelte"
import SDK from "../sdk"
import {
@ -70,6 +71,7 @@
{/key}
</div>
<NotificationDisplay />
<ConfirmationDisplay />
<!-- Key block needs to be outside the if statement or it breaks -->
{#key $builderStore.selectedComponentId}
{#if $builderStore.inBuilder}

View File

@ -0,0 +1,15 @@
<script>
import { confirmationStore } from "../store"
import { Modal, ModalContent } from "@budibase/bbui"
</script>
{#if $confirmationStore.showConfirmation}
<Modal fixed on:cancel={confirmationStore.actions.cancel}>
<ModalContent
title="Confirm Action"
onConfirm={confirmationStore.actions.confirm}
>
{$confirmationStore.text}
</ModalContent>
</Modal>
{/if}

View File

@ -1,40 +1,11 @@
import * as API from "../api"
import { writable, get } from "svelte/store"
import { initialise } from "./initialise"
import { routeStore } from "./routes"
import { builderStore } from "./builder"
import { TableNames } from "../constants"
const createAuthStore = () => {
const store = writable(null)
const goToDefaultRoute = () => {
// Setting the active route forces an update of the active screen ID,
// even if we're on the same URL
routeStore.actions.setActiveRoute("/")
// Navigating updates the URL to reflect this route
routeStore.actions.navigate("/")
}
// Logs a user in
const logIn = async ({ email, password }) => {
const auth = await API.logIn({ email, password })
if (auth.success) {
await fetchUser()
await initialise()
goToDefaultRoute()
}
}
// Logs a user out
const logOut = async () => {
store.set(null)
window.document.cookie = `budibase:auth=; budibase:currentapp=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
await initialise()
goToDefaultRoute()
}
// Fetches the user object if someone is logged in and has reloaded the page
const fetchUser = async () => {
// Fetch the first user if inside the builder
@ -54,7 +25,7 @@ const createAuthStore = () => {
return {
subscribe: store.subscribe,
actions: { logIn, logOut, fetchUser },
actions: { fetchUser },
}
}

View File

@ -0,0 +1,37 @@
import { writable, get } from "svelte/store"
const initialState = {
showConfirmation: false,
text: null,
callback: null,
}
const createConfirmationStore = () => {
const store = writable(initialState)
const showConfirmation = (text, callback) => {
store.set({
showConfirmation: true,
text,
callback,
})
}
const confirm = async () => {
const state = get(store)
if (!state.showConfirmation || !state.callback) {
return
}
store.set(initialState)
await state.callback()
}
const cancel = () => {
store.set(initialState)
}
return {
subscribe: store.subscribe,
actions: { showConfirmation, confirm, cancel },
}
}
export const confirmationStore = createConfirmationStore()

View File

@ -4,6 +4,7 @@ export { routeStore } from "./routes"
export { screenStore } from "./screens"
export { builderStore } from "./builder"
export { dataSourceStore } from "./dataSource"
export { confirmationStore } from "./confirmation"
// Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context"

View File

@ -1,5 +1,5 @@
import { get } from "svelte/store"
import { routeStore, builderStore, authStore } from "../store"
import { routeStore, builderStore, confirmationStore } from "../store"
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
import { ActionTypes } from "../constants"
@ -68,15 +68,6 @@ const refreshDatasourceHandler = async (action, context) => {
)
}
const loginHandler = async action => {
const { email, password } = action.parameters
await authStore.actions.logIn({ email, password })
}
const logoutHandler = async () => {
await authStore.actions.logOut()
}
const handlerMap = {
["Save Row"]: saveRowHandler,
["Delete Row"]: deleteRowHandler,
@ -85,13 +76,19 @@ const handlerMap = {
["Trigger Automation"]: triggerAutomationHandler,
["Validate Form"]: validateFormHandler,
["Refresh Datasource"]: refreshDatasourceHandler,
["Log In"]: loginHandler,
["Log Out"]: logoutHandler,
}
const confirmTextMap = {
["Delete Row"]: "Are you sure you want to delete this row?",
["Save Row"]: "Are you sure you want to save this row?",
["Execute Query"]: "Are you sure you want to execute this query?",
["Trigger Automation"]: "Are you sure you want to trigger this automation?",
}
/**
* Parses an array of actions and returns a function which will execute the
* actions in the current context.
* A handler returning `false` is a flag to stop execution of handlers
*/
export const enrichButtonActions = (actions, context) => {
// Prevent button actions in the builder preview
@ -102,15 +99,40 @@ export const enrichButtonActions = (actions, context) => {
return async () => {
for (let i = 0; i < handlers.length; i++) {
try {
const result = await handlers[i](actions[i], context)
// A handler returning `false` is a flag to stop execution of handlers
if (result === false) {
const action = actions[i]
const callback = async () => handlers[i](action, context)
// If this action is confirmable, show confirmation and await a
// callback to execute further actions
if (action.parameters?.confirm) {
const defaultText = confirmTextMap[action["##eventHandlerType"]]
const confirmText = action.parameters?.confirmText || defaultText
confirmationStore.actions.showConfirmation(confirmText, async () => {
// When confirmed, execute this action immediately,
// then execute the rest of the actions in the chain
const result = await callback()
if (result !== false) {
const next = enrichButtonActions(actions.slice(i + 1), context)
await next()
}
})
// Stop enriching actions when encountering a confirmable actions,
// as the callback continues the action chain
return
}
// For non-confirmable actions, execute this immediately
else {
const result = await callback()
if (result === false) {
return
}
}
} catch (error) {
console.error("Error while executing button handler")
console.error(error)
// Stop executing on an error
// Stop executing further actions on error
return
}
}