Add support for confirmable action handling by client library
This commit is contained in:
parent
17d17bf45b
commit
312674bfa7
|
@ -3,6 +3,7 @@
|
||||||
import { setContext, onMount } from "svelte"
|
import { setContext, onMount } from "svelte"
|
||||||
import Component from "./Component.svelte"
|
import Component from "./Component.svelte"
|
||||||
import NotificationDisplay from "./NotificationDisplay.svelte"
|
import NotificationDisplay from "./NotificationDisplay.svelte"
|
||||||
|
import ConfirmationDisplay from "./ConfirmationDisplay.svelte"
|
||||||
import Provider from "./Provider.svelte"
|
import Provider from "./Provider.svelte"
|
||||||
import SDK from "../sdk"
|
import SDK from "../sdk"
|
||||||
import {
|
import {
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<NotificationDisplay />
|
<NotificationDisplay />
|
||||||
|
<ConfirmationDisplay />
|
||||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||||
{#key $builderStore.selectedComponentId}
|
{#key $builderStore.selectedComponentId}
|
||||||
{#if $builderStore.inBuilder}
|
{#if $builderStore.inBuilder}
|
||||||
|
|
|
@ -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}
|
|
@ -1,40 +1,11 @@
|
||||||
import * as API from "../api"
|
import * as API from "../api"
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { initialise } from "./initialise"
|
|
||||||
import { routeStore } from "./routes"
|
|
||||||
import { builderStore } from "./builder"
|
import { builderStore } from "./builder"
|
||||||
import { TableNames } from "../constants"
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
const createAuthStore = () => {
|
const createAuthStore = () => {
|
||||||
const store = writable(null)
|
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
|
// Fetches the user object if someone is logged in and has reloaded the page
|
||||||
const fetchUser = async () => {
|
const fetchUser = async () => {
|
||||||
// Fetch the first user if inside the builder
|
// Fetch the first user if inside the builder
|
||||||
|
@ -54,7 +25,7 @@ const createAuthStore = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe: store.subscribe,
|
subscribe: store.subscribe,
|
||||||
actions: { logIn, logOut, fetchUser },
|
actions: { fetchUser },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -4,6 +4,7 @@ export { routeStore } from "./routes"
|
||||||
export { screenStore } from "./screens"
|
export { screenStore } from "./screens"
|
||||||
export { builderStore } from "./builder"
|
export { builderStore } from "./builder"
|
||||||
export { dataSourceStore } from "./dataSource"
|
export { dataSourceStore } from "./dataSource"
|
||||||
|
export { confirmationStore } from "./confirmation"
|
||||||
|
|
||||||
// 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"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { get } from "svelte/store"
|
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 { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
|
||||||
import { ActionTypes } from "../constants"
|
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 = {
|
const handlerMap = {
|
||||||
["Save Row"]: saveRowHandler,
|
["Save Row"]: saveRowHandler,
|
||||||
["Delete Row"]: deleteRowHandler,
|
["Delete Row"]: deleteRowHandler,
|
||||||
|
@ -85,13 +76,19 @@ const handlerMap = {
|
||||||
["Trigger Automation"]: triggerAutomationHandler,
|
["Trigger Automation"]: triggerAutomationHandler,
|
||||||
["Validate Form"]: validateFormHandler,
|
["Validate Form"]: validateFormHandler,
|
||||||
["Refresh Datasource"]: refreshDatasourceHandler,
|
["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
|
* Parses an array of actions and returns a function which will execute the
|
||||||
* actions in the current context.
|
* actions in the current context.
|
||||||
|
* A handler returning `false` is a flag to stop execution of handlers
|
||||||
*/
|
*/
|
||||||
export const enrichButtonActions = (actions, context) => {
|
export const enrichButtonActions = (actions, context) => {
|
||||||
// Prevent button actions in the builder preview
|
// Prevent button actions in the builder preview
|
||||||
|
@ -102,15 +99,40 @@ export const enrichButtonActions = (actions, context) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
for (let i = 0; i < handlers.length; i++) {
|
for (let i = 0; i < handlers.length; i++) {
|
||||||
try {
|
try {
|
||||||
const result = await handlers[i](actions[i], context)
|
const action = actions[i]
|
||||||
// A handler returning `false` is a flag to stop execution of handlers
|
const callback = async () => handlers[i](action, context)
|
||||||
if (result === false) {
|
|
||||||
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For non-confirmable actions, execute this immediately
|
||||||
|
else {
|
||||||
|
const result = await callback()
|
||||||
|
if (result === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error while executing button handler")
|
console.error("Error while executing button handler")
|
||||||
console.error(error)
|
console.error(error)
|
||||||
// Stop executing on an error
|
// Stop executing further actions on error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue