From 1879fbeee3bab41d1ac41b6346375a783bdb1a7a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Jun 2021 09:56:46 +0100 Subject: [PATCH 1/8] Add support for confirmable action handling by client library --- .../client/src/components/ClientApp.svelte | 2 + .../src/components/ConfirmationDisplay.svelte | 15 ++++++ packages/client/src/store/auth.js | 31 +---------- packages/client/src/store/confirmation.js | 37 +++++++++++++ packages/client/src/store/index.js | 1 + packages/client/src/utils/buttonActions.js | 54 +++++++++++++------ 6 files changed, 94 insertions(+), 46 deletions(-) create mode 100644 packages/client/src/components/ConfirmationDisplay.svelte create mode 100644 packages/client/src/store/confirmation.js diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 5e0efe5451..c2bc0caaa3 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -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} + {#key $builderStore.selectedComponentId} {#if $builderStore.inBuilder} diff --git a/packages/client/src/components/ConfirmationDisplay.svelte b/packages/client/src/components/ConfirmationDisplay.svelte new file mode 100644 index 0000000000..cfb110167f --- /dev/null +++ b/packages/client/src/components/ConfirmationDisplay.svelte @@ -0,0 +1,15 @@ + + +{#if $confirmationStore.showConfirmation} + + + {$confirmationStore.text} + + +{/if} diff --git a/packages/client/src/store/auth.js b/packages/client/src/store/auth.js index 9e01a5648f..9829d2e350 100644 --- a/packages/client/src/store/auth.js +++ b/packages/client/src/store/auth.js @@ -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 }, } } diff --git a/packages/client/src/store/confirmation.js b/packages/client/src/store/confirmation.js new file mode 100644 index 0000000000..5a6ec11dd4 --- /dev/null +++ b/packages/client/src/store/confirmation.js @@ -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() diff --git a/packages/client/src/store/index.js b/packages/client/src/store/index.js index ebf89e14e3..9f2dbfdcfb 100644 --- a/packages/client/src/store/index.js +++ b/packages/client/src/store/index.js @@ -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" diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 4d2865d586..92912908cb 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -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 } } From 2003c69698d99b5d06b52d177255c0521c65b1e7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 21 Jun 2021 09:57:17 +0100 Subject: [PATCH 2/8] Update modal to support different events for hiding and cancelling, and support entry transitions for fixed modals --- packages/bbui/src/Modal/Modal.svelte | 24 +++++++++++++++------ packages/bbui/src/Modal/ModalContent.svelte | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/bbui/src/Modal/Modal.svelte b/packages/bbui/src/Modal/Modal.svelte index 12c12e3717..3ed9b93fa5 100644 --- a/packages/bbui/src/Modal/Modal.svelte +++ b/packages/bbui/src/Modal/Modal.svelte @@ -27,9 +27,17 @@ visible = false } + export function cancel() { + if (!visible) { + return + } + dispatch("cancel") + hide() + } + function handleKey(e) { if (visible && e.key === "Escape") { - hide() + cancel() } } @@ -41,7 +49,7 @@ } } - setContext(Context.Modal, { show, hide }) + setContext(Context.Modal, { show, hide, cancel }) @@ -56,15 +64,17 @@
-