diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index df403c0a22..43b8526e9e 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -5,7 +5,7 @@ version: "3" services: minio-service: container_name: budi-minio-dev - restart: always + restart: on-failure image: minio/minio volumes: - minio_data:/data @@ -23,7 +23,7 @@ services: proxy-service: container_name: budi-nginx-dev - restart: always + restart: on-failure image: nginx:latest volumes: - ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf @@ -38,7 +38,7 @@ services: couchdb-service: # platform: linux/amd64 container_name: budi-couchdb-dev - restart: always + restart: on-failure image: ibmcom/couchdb3 environment: - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} @@ -59,7 +59,7 @@ services: redis-service: container_name: budi-redis-dev - restart: always + restart: on-failure image: redis command: redis-server --requirepass ${REDIS_PASSWORD} ports: diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 41174a9d9d..c8b4ae8de9 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -393,18 +393,45 @@ const getUrlBindings = asset => { /** * Gets all bindable properties exposed in a button actions flow up until - * the specified action ID. + * the specified action ID, as well as context provided for the action + * setting as a whole by the component. */ -export const getButtonContextBindings = (actions, actionId) => { +export const getButtonContextBindings = ( + asset, + componentId, + settingKey, + actions, + actionId +) => { + let bindings = [] + + // Check if any context bindings are provided by the component for this + // setting + const component = findComponent(asset.props, componentId) + const settings = getComponentSettings(component?._component) + const eventSetting = settings.find(setting => setting.key === settingKey) + if (!eventSetting) { + return bindings + } + if (eventSetting.context?.length) { + eventSetting.context.forEach(contextEntry => { + bindings.push({ + readableBinding: contextEntry.label, + runtimeBinding: `${makePropSafe("eventContext")}.${makePropSafe( + contextEntry.key + )}`, + }) + }) + } + // Get the steps leading up to this value const index = actions?.findIndex(action => action.id === actionId) if (index == null || index === -1) { - return [] + return bindings } const prevActions = actions.slice(0, index) // Generate bindings for any steps which provide context - let bindings = [] prevActions.forEach((action, idx) => { const def = ActionDefinitions.actions.find( x => x.name === action["##eventHandlerType"] @@ -418,6 +445,7 @@ export const getButtonContextBindings = (actions, actionId) => { }) } }) + return bindings } diff --git a/packages/builder/src/builderStore/store/theme.js b/packages/builder/src/builderStore/store/theme.js index d4d7460ed2..bd3a149d63 100644 --- a/packages/builder/src/builderStore/store/theme.js +++ b/packages/builder/src/builderStore/store/theme.js @@ -2,9 +2,10 @@ import { createLocalStorageStore } from "@budibase/frontend-core" export const getThemeStore = () => { const themeElement = document.documentElement + const initialValue = { theme: "darkest", - options: ["lightest", "light", "dark", "darkest"], + options: ["lightest", "light", "dark", "darkest", "nord"], } const store = createLocalStorageStore("bb-theme", initialValue) @@ -21,6 +22,7 @@ export const getThemeStore = () => { `spectrum--${option}`, option === state.theme ) + themeElement.classList.add("spectrum--darkest") }) }) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionDrawer.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionDrawer.svelte index 8cf0f37f70..5b0ab4a6a3 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionDrawer.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionDrawer.svelte @@ -12,11 +12,13 @@ import { getAvailableActions } from "./index" import { generate } from "shortid" import { getButtonContextBindings } from "builderStore/dataBinding" + import { currentAsset, store } from "builderStore" const flipDurationMs = 150 const EVENT_TYPE_KEY = "##eventHandlerType" const actionTypes = getAvailableActions() + export let key export let actions export let bindings = [] @@ -24,6 +26,9 @@ // These are ephemeral bindings which only exist while executing actions $: buttonContextBindings = getButtonContextBindings( + $currentAsset, + $store.selectedComponentId, + key, actions, selectedAction?.id ) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionEditor.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionEditor.svelte index 6a0e94cd4c..550d982013 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionEditor.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/ButtonActionEditor.svelte @@ -8,6 +8,7 @@ const dispatch = createEventDispatcher() + export let key export let value = [] export let name export let bindings @@ -81,5 +82,6 @@ bind:actions={tmpValue} eventType={name} {bindings} + {key} /> diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte index 911688b30c..617b1c83ab 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte @@ -79,6 +79,7 @@ bindings={allBindings} name={key} text={label} + {key} {type} {...props} /> diff --git a/packages/client/manifest.json b/packages/client/manifest.json index be718ed271..b8fa824762 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2530,67 +2530,102 @@ "styles": ["size"], "editable": true, "draggable": false, - "illegalChildren": [ - "section" - ], + "illegalChildren": ["section"], "settings": [ { "type": "dataProvider", "label": "Provider", "key": "dataProvider" }, + { + "type": "field", + "label": "Latitude Key", + "key": "latitudeKey", + "dependsOn": "dataProvider" + }, + { + "type": "field", + "label": "Longitude Key", + "key": "longitudeKey", + "dependsOn": "dataProvider" + }, + { + "type": "field", + "label": "Title Key", + "key": "titleKey", + "dependsOn": "dataProvider" + }, + { + "type": "event", + "label": "On Click Marker", + "key": "onClickMarker", + "context": [ + { + "label": "Clicked marker", + "key": "marker" + } + ] + }, { "type": "boolean", - "label": "Enable Fullscreen", + "label": "Enable creating markers", + "key": "creationEnabled", + "defaultValue": false + }, + { + "type": "event", + "label": "On Create Marker", + "key": "onCreateMarker", + "dependsOn": "creationEnabled", + "context": [ + { + "label": "New marker latitude", + "key": "lat" + }, + { + "label": "New marker longitude", + "key": "lng" + } + ] + }, + { + "type": "boolean", + "label": "Enable fullscreen", "key": "fullScreenEnabled", "defaultValue": true }, { "type": "boolean", - "label": "Enable Location", + "label": "Enable location", "key": "locationEnabled", "defaultValue": true }, { "type": "boolean", - "label": "Enable Zoom", + "label": "Enable zoom", "key": "zoomEnabled", "defaultValue": true }, - { - "type": "number", - "label": "Zoom Level (0-100)", - "key": "zoomLevel", - "defaultValue": 72, - "max" : 100, - "min" : 0 - }, - { - "type": "field", - "label": "Latitude Key", - "key": "latitudeKey" - }, - { - "type": "field", - "label": "Longitude Key", - "key": "longitudeKey" - }, - { - "type": "field", - "label": "Title Key", - "key": "titleKey" - }, { "type": "text", "label": "Tile URL", "key": "tileURL", "defaultValue": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" }, + { "type": "text", - "label": "Default Location (lat,lng)", + "label": "Default Location (when empty)", "key": "defaultLocation", - "defaultValue": "51.5072,-0.1276" + "placeholder": "51.5072,-0.1276" + }, + { + "type": "number", + "label": "Default Location Zoom Level (0-100)", + "key": "zoomLevel", + "placeholder": 50, + "max": 100, + "min": 0 }, { "type": "text", diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index e4176587ee..d9af295108 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -1,6 +1,7 @@
diff --git a/packages/client/src/stores/confirmation.js b/packages/client/src/stores/confirmation.js index 497b021b04..bb9a54386f 100644 --- a/packages/client/src/stores/confirmation.js +++ b/packages/client/src/stores/confirmation.js @@ -4,30 +4,36 @@ const initialState = { showConfirmation: false, title: null, text: null, - callback: null, + onConfirm: null, + onCancel: null, } const createConfirmationStore = () => { const store = writable(initialState) - const showConfirmation = (title, text, callback) => { + const showConfirmation = (title, text, onConfirm, onCancel) => { store.set({ showConfirmation: true, title, text, - callback, + onConfirm, + onCancel, }) } const confirm = async () => { const state = get(store) - if (!state.showConfirmation || !state.callback) { + if (!state.showConfirmation || !state.onConfirm) { return } store.set(initialState) - await state.callback() + await state.onConfirm() } const cancel = () => { + const state = get(store) store.set(initialState) + if (state.onCancel) { + state.onCancel() + } } return { diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index f44e7d7453..ff649faf93 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -308,7 +308,7 @@ export const enrichButtonActions = (actions, context) => { let buttonContext = context.actions || [] const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]]) - return async () => { + return async eventContext => { for (let i = 0; i < handlers.length; i++) { try { // Skip any non-existent action definitions @@ -317,7 +317,11 @@ export const enrichButtonActions = (actions, context) => { } // Built total context for this action - const totalContext = { ...context, actions: buttonContext } + const totalContext = { + ...context, + actions: buttonContext, + eventContext, + } // Get and enrich this button action with the total context let action = actions[i] @@ -327,33 +331,36 @@ export const enrichButtonActions = (actions, 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( - action["##eventHandlerType"], - 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) { - // Generate a new total context to pass into the next enrichment - buttonContext.push(result) - const newContext = { ...context, actions: buttonContext } + return new Promise(resolve => { + const defaultText = confirmTextMap[action["##eventHandlerType"]] + const confirmText = action.parameters?.confirmText || defaultText + confirmationStore.actions.showConfirmation( + action["##eventHandlerType"], + 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) { + // Generate a new total context to pass into the next enrichment + buttonContext.push(result) + const newContext = { ...context, actions: buttonContext } - // Enrich and call the next button action - const next = enrichButtonActions( - actions.slice(i + 1), - newContext - ) - await next() + // Enrich and call the next button action + const next = enrichButtonActions( + actions.slice(i + 1), + newContext + ) + resolve(await next()) + } else { + resolve(false) + } + }, + () => { + resolve(false) } - } - ) - - // Stop enriching actions when encountering a confirmable action, - // as the callback continues the action chain - return + ) + }) } // For non-confirmable actions, execute the handler immediately diff --git a/packages/client/src/utils/componentProps.js b/packages/client/src/utils/componentProps.js index 0727b5943f..14760252a9 100644 --- a/packages/client/src/utils/componentProps.js +++ b/packages/client/src/utils/componentProps.js @@ -22,7 +22,7 @@ export const propsAreSame = (a, b) => { * Enriches component props. * Data bindings are enriched, and button actions are enriched. */ -export const enrichProps = (props, context) => { +export const enrichProps = (props, context, settingsDefinitionMap) => { // Create context of all bindings and data contexts // Duplicate the closest context as "data" which the builder requires const totalContext = { @@ -38,7 +38,7 @@ export const enrichProps = (props, context) => { let normalProps = { ...props } let actionProps = {} Object.keys(normalProps).forEach(prop => { - if (prop?.toLowerCase().includes("onclick")) { + if (settingsDefinitionMap?.[prop]?.type === "event") { actionProps[prop] = normalProps[prop] delete normalProps[prop] } @@ -61,7 +61,7 @@ export const enrichProps = (props, context) => { // Conditions if (enrichedProps._conditions?.length) { enrichedProps._conditions.forEach((condition, idx) => { - if (condition.setting?.toLowerCase().includes("onclick")) { + if (settingsDefinitionMap?.[condition.setting]?.type === "event") { // Use the original condition action value to enrich it to a button // action condition.settingValue = enrichButtonActions(