From d90123e8de29a40ebfc15542ded58701de722da7 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 8 Feb 2024 11:05:45 +0000 Subject: [PATCH 01/13] Wip --- .../NewScreen/CreateScreenModal.svelte | 83 +++++++++++++++---- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index a9d64afd19..ab8ccecf6e 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -2,6 +2,7 @@ import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte" import DatasourceModal from "./DatasourceModal.svelte" import ScreenRoleModal from "./ScreenRoleModal.svelte" + import FormTypeModal from "./FormTypeModal.svelte" import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" import { Modal, notifications } from "@budibase/bbui" import { store } from "builderStore" @@ -19,12 +20,17 @@ let screenDetailsModal let datasourceModal let screenAccessRoleModal + let formTypeModal // Cache variables for workflow let screenAccessRole = Roles.BASIC + let selectedTemplates = null + + let selectedDatasources = null let blankScreenUrl = null let screenMode = null + let formType = null // Creates an array of screens, checking and sanitising their URLs const createScreens = async ({ screens, screenAccessRole }) => { @@ -56,20 +62,21 @@ screen.routing.roleId = screenAccessRole // Create the screen - const response = await store.actions.screens.save(screen) - screenId = response._id + // const response = await store.actions.screens.save(screen) + // screenId = response._id // Add link in layout. We only ever actually create 1 screen now, even // for autoscreens, so it's always safe to do this. - await store.actions.links.save( - screen.routing.route, - capitalise(screen.routing.route.split("/")[1]) - ) + // await store.actions.links.save( + // screen.routing.route, + // capitalise(screen.routing.route.split("/")[1]) + // ) + console.log(screen) } // Go to new screen - $goto(`./${screenId}`) - store.actions.screens.select(screenId) + //$goto(`./${screenId}`) + //store.actions.screens.select(screenId) } catch (error) { console.log(error) notifications.error("Error creating screens") @@ -103,13 +110,15 @@ // Handler for NewScreenModal export const show = newMode => { mode = newMode - selectedTemplates = null + // selectedTemplates = null + selectedDatasources = null blankScreenUrl = null screenMode = mode pendingScreen = null screenAccessRole = Roles.BASIC + formType = null - if (mode === "table" || mode === "grid") { + if (mode === "table" || mode === "grid" || mode === "form") { datasourceModal.show() } else if (mode === "blank") { let templates = getTemplates($tables.list) @@ -124,8 +133,9 @@ } // Handler for DatasourceModal confirmation, move to screen access select - const confirmScreenDatasources = async ({ templates }) => { - selectedTemplates = templates + const confirmScreenDatasources = async ({ datasources }) => { + selectedDatasources = datasources + console.log("confirmScreenDatasources ", datasources) screenAccessRoleModal.show() } @@ -136,6 +146,14 @@ screenTemplate.autoTableId = template.resourceId return screenTemplate }) + console.log("selectedTemplates ", selectedTemplates) + /* + + id : "ROW_LIST_TEMPLATE" + name : "Employees - List" + resourceId : "ta_bb_employee" + + */ await createScreens({ screens, screenAccessRole }) } @@ -175,8 +193,14 @@ datasourceModal.show() } } + window.test = () => { + formTypeModal.show() + } + { + if (screenMode === "form") { + formTypeModal.show() + } else { + confirmScreenCreation() + } + }} bind:screenAccessRole + onCancel={roleSelectBack} screenUrl={blankScreenUrl} + confirmText={screenMode === "form" ? "Confirm" : "Done"} /> @@ -200,3 +231,27 @@ initialUrl={blankScreenUrl} /> + + { + console.log("hide") + //formType = null + }} +> + { + console.log("test confirm") + }} + onCancel={() => { + console.log("cancel") + formTypeModal.hide() + screenAccessRoleModal.show() + }} + on:select={e => { + console.log("form type selection ", e.detail) + formType = e.detail + }} + type={formType} + /> + From e9e5281e820589f82b091db479b5b72dc2b94db8 Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 14 Feb 2024 12:11:24 +0000 Subject: [PATCH 02/13] Initial commit form screen flow and tour refactor --- .../builder/src/builderStore/dataBinding.js | 2 +- .../src/builderStore/store/frontend.js | 6 +- .../store/screenTemplates/formScreen.js | 43 ++++++ .../store/screenTemplates/index.js | 7 +- .../src/components/deploy/AppActions.svelte | 9 +- .../ButtonConfiguration/ButtonSetting.svelte | 2 +- .../EditComponentPopover.svelte | 22 +-- .../controls/EditComponentPopover/index.js | 18 +++ .../FieldConfiguration/FieldSetting.svelte | 2 +- .../controls/FormStepConfiguration.svelte | 4 +- .../FieldSetting.svelte | 2 +- .../PrimaryColumnFieldSetting.svelte | 2 +- .../settings/controls/PropertyControl.svelte | 25 +++- .../portal/onboarding/TourPopover.svelte | 3 +- .../portal/onboarding/TourWrap.svelte | 29 ++-- .../steps/NewViewUpdateFormRowId.svelte | 17 +++ .../portal/onboarding/steps/index.js | 1 + .../src/components/portal/onboarding/tours.js | 119 +++++++++++++-- .../builder/app/[application]/_layout.svelte | 2 +- .../Component/ComponentSettingsPanel.svelte | 35 ++++- .../Component/ComponentSettingsSection.svelte | 4 +- .../[screenId]/_components/AppPreview.svelte | 2 +- .../NewScreen/CreateScreenModal.svelte | 137 +++++++++++------- .../NewScreen/DatasourceModal.svelte | 30 ++-- .../NewScreen/FormTypeModal.svelte | 78 ++++++++++ .../NewScreen/ScreenRoleModal.svelte | 3 +- .../NewScreen/{ => images}/blank.png | Bin .../_components/NewScreen/images/form.png | Bin 0 -> 22892 bytes .../NewScreen/{ => images}/grid.png | Bin .../NewScreen/{ => images}/table.png | Bin .../design/_components/NewScreen/index.svelte | 17 ++- packages/types/src/api/web/auth.ts | 1 + packages/types/src/documents/global/user.ts | 1 + .../worker/src/api/routes/validation/users.ts | 1 + 34 files changed, 478 insertions(+), 146 deletions(-) create mode 100644 packages/builder/src/builderStore/store/screenTemplates/formScreen.js rename packages/builder/src/components/design/settings/controls/{ => EditComponentPopover}/EditComponentPopover.svelte (79%) create mode 100644 packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js create mode 100644 packages/builder/src/components/portal/onboarding/steps/NewViewUpdateFormRowId.svelte create mode 100644 packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte rename packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/{ => images}/blank.png (100%) create mode 100644 packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/form.png rename packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/{ => images}/grid.png (100%) rename packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/{ => images}/table.png (100%) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index edea3b9ec7..9cb7b3311b 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -1131,7 +1131,7 @@ export const getAllStateVariables = () => { "@budibase/standard-components/multistepformblockstep" ) - steps.forEach(step => { + steps?.forEach(step => { parseComponentSettings(stepDefinition, step) }) }) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 55208bb97e..456f0658fc 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -75,7 +75,7 @@ const INITIAL_FRONTEND_STATE = { theme: "", customTheme: {}, previewDevice: "desktop", - highlightedSettingKey: null, + highlightedSetting: null, propertyFocus: null, builderSidePanel: false, hasLock: true, @@ -1460,10 +1460,10 @@ export const getFrontendStore = () => { }, }, settings: { - highlight: key => { + highlight: (key, type) => { store.update(state => ({ ...state, - highlightedSettingKey: key, + highlightedSetting: { key, type: type || "info" }, })) }, propertyFocus: key => { diff --git a/packages/builder/src/builderStore/store/screenTemplates/formScreen.js b/packages/builder/src/builderStore/store/screenTemplates/formScreen.js new file mode 100644 index 0000000000..8ce46cd002 --- /dev/null +++ b/packages/builder/src/builderStore/store/screenTemplates/formScreen.js @@ -0,0 +1,43 @@ +import { Screen } from "./utils/Screen" +import { Component } from "./utils/Component" +import sanitizeUrl from "./utils/sanitizeUrl" + +export const FORM_TEMPLATE = "FORM_TEMPLATE" +export const formUrl = datasource => sanitizeUrl(`/${datasource.label}-form`) + +// Mode not really necessary +export default function (datasources, config) { + if (!Array.isArray(datasources)) { + return [] + } + return datasources.map(datasource => { + return { + name: `${datasource.label} - Form`, + create: () => createScreen(datasource, config), + id: FORM_TEMPLATE, + resourceId: datasource.resourceId, + } + }) +} + +const generateMultistepFormBlock = (dataSource, { actionType } = {}) => { + const multistepFormBlock = new Component( + "@budibase/standard-components/multistepformblock" + ) + multistepFormBlock + .customProps({ + actionType, + dataSource, + steps: [{}], + }) + .instanceName(`${dataSource.label} - Multistep Form block`) + return multistepFormBlock +} + +const createScreen = (datasource, config) => { + return new Screen() + .route(formUrl(datasource)) + .instanceName(`${datasource.label} - Form`) + .addChild(generateMultistepFormBlock(datasource, config)) + .json() +} diff --git a/packages/builder/src/builderStore/store/screenTemplates/index.js b/packages/builder/src/builderStore/store/screenTemplates/index.js index 3ff42fdec6..fff31cc070 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/index.js +++ b/packages/builder/src/builderStore/store/screenTemplates/index.js @@ -1,7 +1,11 @@ import rowListScreen from "./rowListScreen" import createFromScratchScreen from "./createFromScratchScreen" +import formScreen from "./formScreen" -const allTemplates = datasources => [...rowListScreen(datasources)] +const allTemplates = datasources => [ + ...rowListScreen(datasources), + ...formScreen(datasources), +] // Allows us to apply common behaviour to all create() functions const createTemplateOverride = template => () => { @@ -19,6 +23,7 @@ export default datasources => { }) const fromScratch = enrichTemplate(createFromScratchScreen) const tableTemplates = allTemplates(datasources).map(enrichTemplate) + return [ fromScratch, ...tableTemplates.sort((templateA, templateB) => { diff --git a/packages/builder/src/components/deploy/AppActions.svelte b/packages/builder/src/components/deploy/AppActions.svelte index 7d14fd0e87..bf59c3a230 100644 --- a/packages/builder/src/components/deploy/AppActions.svelte +++ b/packages/builder/src/components/deploy/AppActions.svelte @@ -156,9 +156,10 @@ {/if}
@@ -204,7 +205,7 @@
- + Publish - import EditComponentPopover from "../EditComponentPopover.svelte" + import EditComponentPopover from "../EditComponentPopover/EditComponentPopover.svelte" import { Icon } from "@budibase/bbui" import { runtimeToReadableBinding } from "builderStore/dataBinding" import { isJSBinding } from "@budibase/string-templates" diff --git a/packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte b/packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte similarity index 79% rename from packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte rename to packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte index 04bb925873..af535a00f0 100644 --- a/packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte @@ -3,7 +3,8 @@ import { store } from "builderStore" import { cloneDeep } from "lodash/fp" import { createEventDispatcher, getContext } from "svelte" - import ComponentSettingsSection from "../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte" + import { customPositionHandler } from "." + import ComponentSettingsSection from "../../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte" export let anchor export let componentInstance @@ -59,25 +60,6 @@ dispatch("change", nestedComponentInstance) } - - const customPositionHandler = (anchorBounds, eleBounds, cfg) => { - let { left, top } = cfg - let percentageOffset = 30 - // left-outside - left = anchorBounds.left - eleBounds.width - 18 - - // shift up from the anchor, if space allows - let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset - let defaultTop = anchorBounds.top - offsetPos - - if (window.innerHeight - defaultTop < eleBounds.height) { - top = window.innerHeight - eleBounds.height - 5 - } else { - top = anchorBounds.top - offsetPos - } - - return { ...cfg, left, top } - } { + let { left, top } = cfg + let percentageOffset = 30 + // left-outside + left = anchorBounds.left - eleBounds.width - 18 + + // shift up from the anchor, if space allows + let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset + let defaultTop = anchorBounds.top - offsetPos + + if (window.innerHeight - defaultTop < eleBounds.height) { + top = window.innerHeight - eleBounds.height - 5 + } else { + top = anchorBounds.top - offsetPos + } + + return { ...cfg, left, top } +} diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte index 8c40c455c8..94ce698ff1 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte @@ -1,5 +1,5 @@
{#if label && !labelHidden} @@ -115,6 +120,16 @@
diff --git a/packages/builder/src/components/portal/onboarding/steps/index.js b/packages/builder/src/components/portal/onboarding/steps/index.js index 8e27748f36..6694ce97a7 100644 --- a/packages/builder/src/components/portal/onboarding/steps/index.js +++ b/packages/builder/src/components/portal/onboarding/steps/index.js @@ -1,3 +1,4 @@ export { default as OnboardingData } from "./OnboardingData.svelte" export { default as OnboardingDesign } from "./OnboardingDesign.svelte" export { default as OnboardingPublish } from "./OnboardingPublish.svelte" +export { default as NewViewUpdateFormRowId } from "./NewViewUpdateFormRowId.svelte" diff --git a/packages/builder/src/components/portal/onboarding/tours.js b/packages/builder/src/components/portal/onboarding/tours.js index 55fd4c4a9b..fdc00bf32d 100644 --- a/packages/builder/src/components/portal/onboarding/tours.js +++ b/packages/builder/src/components/portal/onboarding/tours.js @@ -2,8 +2,14 @@ import { get } from "svelte/store" import { store } from "builderStore" import { auth } from "stores/portal" import analytics from "analytics" -import { OnboardingData, OnboardingDesign, OnboardingPublish } from "./steps" +import { + OnboardingData, + OnboardingDesign, + OnboardingPublish, + NewViewUpdateFormRowId, +} from "./steps" import { API } from "api" +import { customPositionHandler } from "components/design/settings/controls/EditComponentPopover" const ONBOARDING_EVENT_PREFIX = "onboarding" @@ -14,11 +20,26 @@ export const TOUR_STEP_KEYS = { BUILDER_USER_MANAGEMENT: "builder-user-management", BUILDER_AUTOMATION_SECTION: "builder-automation-section", FEATURE_USER_MANAGEMENT: "feature-user-management", + BUILDER_FORM_CREATE_STEPS: "builder-form-create-steps", + BUILDER_FORM_VIEW_UPDATE_STEPS: "builder-form-view-update-steps", + BUILDER_FORM_ROW_ID: "builder-form-row-id", } export const TOUR_KEYS = { TOUR_BUILDER_ONBOARDING: "builder-onboarding", FEATURE_ONBOARDING: "feature-onboarding", + BUILDER_FORM_CREATE: "builder-form-create", + BUILDER_FORM_VIEW_UPDATE: "builder-form-view-update", +} + +const resetTourState = () => { + store.update(state => ({ + ...state, + tourNodes: undefined, + tourKey: undefined, + tourKeyStep: undefined, + onboarding: false, + })) } const endUserOnboarding = async ({ skipped = false } = {}) => { @@ -37,13 +58,7 @@ const endUserOnboarding = async ({ skipped = false } = {}) => { // Update the cached user await auth.getSelf() - store.update(state => ({ - ...state, - tourNodes: undefined, - tourKey: undefined, - tourKeyStep: undefined, - onboarding: false, - })) + resetTourState() } catch (e) { console.error("Onboarding failed", e) return false @@ -52,9 +67,28 @@ const endUserOnboarding = async ({ skipped = false } = {}) => { } } -const tourEvent = eventKey => { +const endTour = async ({ key, skipped = false } = {}) => { + const { tours = {} } = get(auth).user + tours[key] = new Date().toISOString() + + await API.updateSelf({ + tours, + }) + + if (skipped) { + tourEvent(key, skipped) + } + + // Update the cached user + await auth.getSelf() + + resetTourState() +} + +const tourEvent = (eventKey, skipped) => { analytics.captureEvent(`${ONBOARDING_EVENT_PREFIX}:${eventKey}`, { eventSource: EventSource.PORTAL, + skipped, }) } @@ -135,7 +169,74 @@ const getTours = () => { }, ], }, + [TOUR_KEYS.BUILDER_FORM_CREATE]: { + steps: [ + { + id: TOUR_STEP_KEYS.BUILDER_FORM_CREATE_STEPS, + title: "Add multiple steps", + body: `When faced with a sizable form, consider implementing a multi-step + approach to enhance user experience. Breaking the form into multiple steps + can significantly improve usability by making the process more digestible for your users.`, + query: "#steps-prop-control-wrap", + onComplete: () => { + store.actions.settings.highlight() + endTour({ key: TOUR_KEYS.BUILDER_FORM_CREATE }) + }, + onLoad: () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_FORM_CREATE_STEPS) + store.actions.settings.highlight("steps", "info") + }, + positionHandler: customPositionHandler, + align: "left-outside", + }, + ], + }, + [TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE]: { + steps: [ + { + id: TOUR_STEP_KEYS.BUILDER_FORM_ROW_ID, + title: "Add row ID to update a row", + layout: NewViewUpdateFormRowId, + query: "#rowId-prop-control-wrap", + onLoad: () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_FORM_ROW_ID) + store.actions.settings.highlight("rowId", "info") + }, + positionHandler: customPositionHandler, + align: "left-outside", + }, + { + id: TOUR_STEP_KEYS.BUILDER_FORM_VIEW_UPDATE_STEPS, + title: "Add multiple steps", + body: `When faced with a sizable form, consider implementing a multi-step + approach to enhance user experience. Breaking the form into multiple steps + can significantly improve usability by making the process more digestible for your users.`, + query: "#steps-prop-control-wrap", + onComplete: () => { + store.actions.settings.highlight() + endTour({ key: TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE }) + }, + onLoad: () => { + tourEvent(TOUR_STEP_KEYS.BUILDER_FORM_VIEW_UPDATE_STEPS) + store.actions.settings.highlight("steps", "info") + }, + positionHandler: customPositionHandler, + align: "left-outside", + }, + ], + onSkip: async () => { + store.actions.settings.highlight() + endTour({ key: TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE, skipped: true }) + }, + }, } } export const TOURS = getTours() +export const TOURSBYSTEP = Object.keys(TOURS).reduce((acc, tour) => { + TOURS[tour].steps.forEach(element => { + acc[element.id] = element + acc[element.id]["tour"] = tour + }) + return acc +}, {}) diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 1df2a90250..5a6e9c941e 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -151,7 +151,7 @@
{#each $layout.children as { path, title }} - + import Panel from "components/design/Panel.svelte" import { store, selectedComponent, selectedScreen } from "builderStore" + import { auth } from "stores/portal" import { getComponentName } from "builderStore/componentUtils" import ComponentSettingsSection from "./ComponentSettingsSection.svelte" import DesignSection from "./DesignSection.svelte" import CustomStylesSection from "./CustomStylesSection.svelte" import ConditionalUISection from "./ConditionalUISection.svelte" import { notifications, ActionButton } from "@budibase/bbui" + import TourWrap from "components/portal/onboarding/TourWrap.svelte" + import { + TOUR_STEP_KEYS, + TOUR_KEYS, + } from "components/portal/onboarding/tours.js" import { getBindableProperties, @@ -14,6 +20,12 @@ } from "builderStore/dataBinding" import { capitalise } from "helpers" + const { + BUILDER_FORM_CREATE_STEPS, + BUILDER_FORM_VIEW_UPDATE_STEPS, + BUILDER_FORM_ROW_ID, + } = TOUR_STEP_KEYS + const onUpdateName = async value => { try { await store.actions.components.updateSetting("_instanceName", value) @@ -43,7 +55,6 @@ $: id = $selectedComponent?._id $: id, (section = tabs[0]) - $: componentName = getComponentName(componentInstance) @@ -89,13 +100,21 @@
{#if section == "settings"} - + + + {/if} {#if section == "styles"} updateSetting(setting, val)} - highlighted={$store.highlightedSettingKey === setting.key} + highlighted={$store.highlightedSetting?.key === setting.key + ? $store.highlightedSetting + : null} propertyFocus={$store.propertyFocus === setting.key} info={setting.info} disableBindings={setting.disableBindings} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 011980bbe2..c9dc4f8982 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -161,7 +161,7 @@ } else if (type === "request-add-component") { toggleAddComponent() } else if (type === "highlight-setting") { - store.actions.settings.highlight(data.setting) + store.actions.settings.highlight(data.setting, "error") // Also scroll setting into view const selector = `#${data.setting}-prop-control` diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index 2a2459949d..a61e7551e7 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -4,14 +4,18 @@ import ScreenRoleModal from "./ScreenRoleModal.svelte" import FormTypeModal from "./FormTypeModal.svelte" import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" + import rowListScreen from "builderStore/store/screenTemplates/rowListScreen" + import formScreen from "builderStore/store/screenTemplates/formScreen" import { Modal, notifications } from "@budibase/bbui" import { store } from "builderStore" import { get } from "svelte/store" import getTemplates from "builderStore/store/screenTemplates" import { tables } from "stores/backend" + import { auth } from "stores/portal" import { Roles } from "constants/backend" import { capitalise } from "helpers" import { goto } from "@roxi/routify" + import { TOUR_KEYS } from "components/portal/onboarding/tours.js" let mode let pendingScreen @@ -25,7 +29,8 @@ // Cache variables for workflow let screenAccessRole = Roles.BASIC - let selectedTemplates = null + let templates = null + let screens = null let selectedDatasources = null let blankScreenUrl = null @@ -40,6 +45,7 @@ try { let screenId + let createdScreens = [] for (let screen of screens) { // Check we aren't clashing with an existing URL @@ -62,21 +68,19 @@ screen.routing.roleId = screenAccessRole // Create the screen - // const response = await store.actions.screens.save(screen) - // screenId = response._id + const response = await store.actions.screens.save(screen) + screenId = response._id + createdScreens.push(response) // Add link in layout. We only ever actually create 1 screen now, even // for autoscreens, so it's always safe to do this. - // await store.actions.links.save( - // screen.routing.route, - // capitalise(screen.routing.route.split("/")[1]) - // ) - console.log(screen) + await store.actions.links.save( + screen.routing.route, + capitalise(screen.routing.route.split("/")[1]) + ) } - // Go to new screen - //$goto(`./${screenId}`) - //store.actions.screens.select(screenId) + return createdScreens } catch (error) { console.error(error) notifications.error("Error creating screens") @@ -110,7 +114,8 @@ // Handler for NewScreenModal export const show = newMode => { mode = newMode - // selectedTemplates = null + templates = null + screens = null selectedDatasources = null blankScreenUrl = null screenMode = mode @@ -135,26 +140,24 @@ // Handler for DatasourceModal confirmation, move to screen access select const confirmScreenDatasources = async ({ datasources }) => { selectedDatasources = datasources - console.log("confirmScreenDatasources ", datasources) - screenAccessRoleModal.show() + if (screenMode === "form") { + formTypeModal.show() + } else { + screenAccessRoleModal.show() + } } // Handler for Datasource Screen Creation const completeDatasourceScreenCreation = async () => { - const screens = selectedTemplates.map(template => { + templates = rowListScreen(selectedDatasources) + + const screens = templates.map(template => { let screenTemplate = template.create() screenTemplate.autoTableId = template.resourceId return screenTemplate }) - console.log("selectedTemplates ", selectedTemplates) - /* - - id : "ROW_LIST_TEMPLATE" - name : "Employees - List" - resourceId : "ta_bb_employee" - - */ - await createScreens({ screens, screenAccessRole }) + const createdScreens = await createScreens({ screens, screenAccessRole }) + loadNewScreen(createdScreens) } const confirmScreenBlank = async ({ screenUrl }) => { @@ -171,7 +174,55 @@ return } pendingScreen.routing.route = screenUrl - await createScreens({ screens: [pendingScreen], screenAccessRole }) + const createdScreens = await createScreens({ + screens: [pendingScreen], + screenAccessRole, + }) + loadNewScreen(createdScreens) + } + + const onConfirmFormType = () => { + screenAccessRoleModal.show() + } + + const loadNewScreen = createdScreens => { + const lastScreen = createdScreens.slice(-1) + + // Go to new screen + $goto(`./${lastScreen._id}`) + store.actions.screens.select(lastScreen._id) + } + + const confirmFormScreenCreation = async () => { + templates = formScreen(selectedDatasources, { actionType: formType }) + screens = templates.map(template => { + let screenTemplate = template.create() + return screenTemplate + }) + const createdScreens = await createScreens({ screens, screenAccessRole }) + const lastScreen = createdScreens?.slice(-1)?.pop() + const mainComponent = lastScreen?.props?._children?.[0]._id + + if (formType === "Update" || formType === "Create") { + const associatedTour = + formType === "Update" + ? TOUR_KEYS.BUILDER_FORM_VIEW_UPDATE + : TOUR_KEYS.BUILDER_FORM_CREATE + + const tourRequired = !$auth?.user?.tours?.[associatedTour] + if (tourRequired) { + store.update(state => ({ + ...state, + tourStepKey: null, + tourNodes: null, + tourKey: associatedTour, + })) + } + } + + // Go to new screen + $goto(`./${lastScreen._id}/${mainComponent}`) + store.actions.screens.select(lastScreen._id) } // Submit screen config for creation. @@ -181,6 +232,8 @@ screenUrl: blankScreenUrl, screenAccessRole, }) + } else if (screenMode === "form") { + confirmFormScreenCreation() } else { completeDatasourceScreenCreation() } @@ -193,30 +246,16 @@ datasourceModal.show() } } - window.test = () => { - formTypeModal.show() - } - - + { - if (screenMode === "form") { - formTypeModal.show() - } else { - confirmScreenCreation() - } + confirmScreenCreation() }} bind:screenAccessRole onCancel={roleSelectBack} @@ -232,24 +271,14 @@ /> - { - console.log("hide") - //formType = null - }} -> + { - console.log("test confirm") - }} + onConfirm={onConfirmFormType} onCancel={() => { - console.log("cancel") formTypeModal.hide() - screenAccessRoleModal.show() + datasourceModal.show() }} on:select={e => { - console.log("form type selection ", e.detail) formType = e.detail }} type={formType} diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte index 731c60a406..4348c17312 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte @@ -4,37 +4,33 @@ import ICONS from "components/backend/DatasourceNavigator/icons" import { IntegrationNames } from "constants" import { onMount } from "svelte" - import rowListScreen from "builderStore/store/screenTemplates/rowListScreen" import DatasourceTemplateRow from "./DatasourceTemplateRow.svelte" - export let mode export let onCancel export let onConfirm - export let initialScreens = [] - let selectedScreens = [...initialScreens] + let selectedSources = [] $: filteredSources = $datasources.list?.filter(datasource => { return datasource.source !== IntegrationNames.REST && datasource["entities"] }) const toggleSelection = datasource => { - const { resourceId } = datasource - if (selectedScreens.find(s => s.resourceId === resourceId)) { - selectedScreens = selectedScreens.filter( - screen => screen.resourceId !== resourceId + const exists = selectedSources.find( + d => d.resourceId === datasource.resourceId + ) + if (exists) { + selectedSources = selectedSources.filter( + d => d.resourceId === datasource.resourceId ) } else { - selectedScreens = [ - ...selectedScreens, - rowListScreen([datasource], mode)[0], - ] + selectedSources = [...selectedSources, datasource] } } const confirmDatasourceSelection = async () => { await onConfirm({ - templates: selectedScreens, + datasources: selectedSources, }) } @@ -54,7 +50,7 @@ cancelText="Back" onConfirm={confirmDatasourceSelection} {onCancel} - disabled={!selectedScreens.length} + disabled={!selectedSources.length} size="L" > @@ -85,8 +81,8 @@ resourceId: table._id, type: "table", }} - {@const selected = selectedScreens.find( - screen => screen.resourceId === tableDS.resourceId + {@const selected = selectedSources.find( + datasource => datasource.resourceId === tableDS.resourceId )} toggleSelection(tableDS)} @@ -103,7 +99,7 @@ tableId: view.tableId, type: "viewV2", }} - {@const selected = selectedScreens.find( + {@const selected = selectedSources.find( x => x.resourceId === viewDS.resourceId )} + import { ModalContent, Layout, Body, Label } from "@budibase/bbui" + import { createEventDispatcher } from "svelte" + + export let onCancel = () => {} + export let onConfirm = () => {} + export let type + + const dispatch = createEventDispatcher() + + + + + + +
{ + dispatch("select", "Create") + }} + > + Create a new row + For capturing and storing new data from your users +
+
{ + dispatch("select", "Update") + }} + > + Update an existing row + For viewing and updating existing data +
+
{ + dispatch("select", "View") + }} + > + View an existing row + For a read only view of your data +
+
+
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/ScreenRoleModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/ScreenRoleModal.svelte index 5d73b7961c..9363523a63 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/ScreenRoleModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/ScreenRoleModal.svelte @@ -10,6 +10,7 @@ export let onCancel export let screenUrl export let screenAccessRole + export let confirmText = "Done" let error @@ -41,7 +42,7 @@ lgxV!H3f7O5)=RcfUcw{s|5f6=U*RqB=9T8YjjKa^-qL@jEshoj0}~# zr<<*Vvkd^ilp35WrZ_A~HfpXdmr5Wd5-SK_t>&wVJ#G|5ql45gU5mkx6(W@$O*cBK z>CP^A+ic+D?yhi6(g@1%Z~mvfUSg5V`uw5 z_DnyziAg7)4d%xUU~~c6YKAGZqBavA^oHlYf{fE2^|Jn5Z~L{!J;1uEb^+bp9Vvm3 zIdF4tvp2u@DX?O38`BEQv&do_{`PfOz?N?nWC1V#{fc@k(_cBr?uv$9001iPe-99l zlSlN*MD$itl|$SIp`+0V3J&)(0RU70C0QvQ|Fx6g%_KG_PhHtU9{;hHaVR@H$qov- z^e)98@P^?ovp;=5AQ`&zL4@wj4(N|nm`$R~>Rf`@U;i<$-<1*Zo0}r6tTanT=6brz zr*SFrt?GV0wo6&r&6cBt%|oG256@OSfdO~h`W!ETC_H{VJUpFE%c0rXQBh*K>y4Ag zgtph6w%53R4+mZ=(^JRGwr-&wj(`4Ms4FBazl7WeMRUG9UQC5b^po?Lgh=i=PcEw~Hik>|)bVUmUK6No}f4{n|EPQgF{<7`dZ)^t)nTGXm6hEn(>vOkO zW)=3EJ>cFxUc=0Se$Q|F_VW>*06fedvi^G`P0keIwUaQbR{@u z^IhT1_-e@3$-Z#GEez@5{>#a_?X~gm>rJGGq?q8Z8-3h7$q|@+ql~hbzv<7-U;18H zx&)Im1vtcub9)m1(dL+N8(OP-tJCn@au3p%#q%rp>fneWtABL%zS&hZM`hpuy(fWT zamRNj$bys?y~mrz-uWLP=RLlQ(PgVRA^r`fW-SWpz9od=ovr*wE*?DNG!jqhV2yn7 zLB4@Ymr9=$@BaoJkNqZF5gr(C(Tjz4#PiIgz`*V5(Z?f!LjB7M~QD;uCsx(wZG z{*Sl-5>i1`#g!Ho)6Yh?={E#E2p#Hw@d*bGu?zWw_WF|kQ)H7wBUm3RH)ZgI|FCuj z+G~-ut{o(-7#Ylh5F)nmmD0y_%)Hfl;d|vHL$B!nw4;}~N>m>1IAl`uxc(wReV$#T z7Br`fBoWrB4n5-23!O5_JF#~?t|>Ds4Xc(QA$_GlH%T$8h->0Pvp+1p#wC3YIKds{ zRA(4@x?l+(Z^5Tb#6g|$vHxfbQ}c!dsTo0IJ4IT8F}&bc{IHSB3L{UTxYY4U6dmGC zmU$ofCH=DKzA*nQ`Rb$jd}T@RoB!*^LrhEMa%pYRpAcN_e*AnRpZMg$bA95rqPFsH zIhr{J)VM#9{-O;PufG&S(C+M4q7F{}nLD-7`S|zW+OH_UFA)B{U1PuK80=!VUc>sr zDDY714Kmk`A^J}X(gz&-1E)W;#-2;;h??8n^G%dK<5yFZ6a~lv{|UqIJO11C`uIx2 z_9h5#${|NUZP4psI`-mAos9B7?Zk<=R6S10(w2XypdWmh40-n#@xgH?i}vhBvGn%w ze=-nGsSQsbId;;n;A{j6Kb-dktpuVKN|*i9Az4cJObEf{gYy zwM}%1NJ?-^ln_Vb-~QCWtN;6&ePeLB+3c)p{Jxpx_k)%9h)VyH1dZ_|^kt3p#~!Xi zVS~re|E*A3zU`<2sU?XB+s0}3o7VYN4)T9`3%2pDWtZa*gn+coH+jfC9{#hC|DXw*3LFEFD28Er7# zMQ>!0z^+1+{8>6O@%r?r0$lY!M7IaKYf=``}Zx16<^PA zSI-h#orq`Puw=eCP_Eoy~dX%4$37GUaPWs7Y>*|S!suPnoae+G)}%u^{=-*N`$}9 z??}S9mpE@F5B#~wXFI;}VtW1Hjefm%^4xPzA)i;<6}^IhzPO$FO(OOIPtbquxt{Un zK-Sl$w9Vz%=q$w7cPcEbEbznH!;0uzw{eF7zJWl?{oYl?=tZ&C`z^!^wqO0p*TDk2 zy@!3B*-zg?xgG{z$O<2lp!ZkoKW1Xu{rLk!PV>F5)5;`{08w{!o}a!I8+@NsVkA#! zWyrH9zC-LcvsiJ^OPWGPTnTeHx}xQ161&YjF8mwc@jv-lp$2)CcY=kx3$MP$W%3ZPzx0nFSFan)c}IYccF!w zC{UD;U~>3(I+FOZV^m0zq7wQUg&a#=L5%56FZrR6YvWM2pks0>mM%MCrU0Q2Yg|G{ zhPgvbHnwemGL^L>8c7tG+xXmmRUrf~Y>vP9i9(#T4~R*alnx;1^Dj6UYU?Kq%Y_Ze z9(VWl0U-7pCIycm zyD%*rfHD9?>i#Y!0MCzY4ajIF>GV9C(I_gaV3lg}1#!N`M7_Ji;-dQIv>h(Un4ahW zT))F}@_WhV{j23M_q-O&d6hJffs_@U-2i9*Q#uKsvp^_i^R3Bq0fdNL8(D`76^2ZH zqQA{2`5k$9=H}?Is42$#(2yF-+6zErT=E&O0Din;U0;ZkY8F8OX(zHyId_Yopb;td z49_Y?PTrS*Dylj=WdM@fpq;?B*$X{LU-hCqpE5g;V{-T{`DhcY4UtQAa>uX?HB&dx z>SRn0qxeCk%TFwe#%L;|H8O9B{$+sNL}V0=5E|BXbJ(qL{gNA{u-L0frf8GSehXIY z3TM%0S#bcBQ^kS&wqGs^1rgJHF~+>9u&-&p@c6z=dY{hQuzfvtF-JLr<-#$%6;Ab3 z$KbRQ7&OEQG{D0WU>BV^Krp)9O6@zCnmdJx8xh!n z+07n{6l59(qMl(r{30xmgI0uyc5TZu4v;!f23tZek_Hz}x~`&HZSKvgSUHhJ)VC+9 zNB3_ww4zxqv8C;rZ+o>X#zM?gMBI8?=+NX)sH{VARWzdKzBx-UgT$y8V`C{kT^_j~uC)@^KV(LgY)Dt9KvJwImKf62W=0DcgIzbZZ!{m{ISbVsA-_%Xl``wbF&S%P#%K3LJYy(@Q6 zeA%-)Vjx;@dzVv$og&>RJA=X&mZntLWuL4RtM)Ocm%Ar1;};QT#cVGyqAbfQ+w>5n z+;&yjxWY6NQkFoH{;5+2h6s*m0U6YT4q?XQG#peMpD(qe4;K=?xbbQc&G&pFAsi-G+NycQ82P@_Z#2dQibP9g}HBq$p$6q%uvlHeZ%U&#JyZ z@ZA?GjrUL$r^cD4ApK}cMblxgB-~x|SJ#elT>S$XJX9$$FAj>KVw}ysEbz}+3FL6b-^eeLAduO0( z#^p)tgahCPKEqpXcE?;laz=?60yR6;k1hFss~jzRp36SGoRa<6GNm+sn1B3xyorc; zxM*9NWC~W<`({EZlmFhV{!~VKu{X(p2m(`yM-^ra_(BaKPCe8eOH7>sB1-=pM_1}m+ps6r0 zGn)1$Y{hi%le8X1me%6UtUtEkmNUnwp++oxMYVzY9f=0dOT?q7DN4cpHuEbXUu^&A z;pIlp@ABBHVPj_WVLEmtS8!wFuieBM{K~mvniik^B+O6vyMO$OWTr3^u$JACWpiE3sye2SCTn=&mo;-yrIx&Fli`ZKS zw=@S`Zng1Oe8Ktj&F*NrWlu1W;M;i3`$0P+l?p)6N)^(U2y9Gaz820AH5|RFy_&DM ziUAg)1&G!Z!UYXv${ofz=ZTT=bjf^V>&lvq%@CX)t`XIFbE(XK^Q2+Z((>EBvF~9* z_QOv)n7<8LQp{56*RlwzfeGSl4F-1-onBndE7hI_z(Hc_;%88C5_0$jf(N;4ur8Z! zRfK`=UXxNKMx_syRSVVmm`#v2lH|M84id{=y|KV{=k!|vzJ(x>rY)}4@_U|Hs0TxP z&&xbkd@=vzVkRW+^R7znBzd@lLD1SfFWvj-HTUCA1c-&nA;N3myWKP<71?KXFi&dAs7Gj>r_9Rh~ zrTQveQsyV$G%R_YU#bl(I72Tt!*qO7th@K>mR-WoZ2`agF4)mTxB&dBhd9E7T7RnY z%kXzS5kG=GgACc)&&rsHZcv+)k7QqwmNJz@mMY^ay;d z*+<~q18^QVcf_qF_nORVkS*TQ8A{h=k}RFdwOr8RgPZB_iC|pP@;UEajzbyoX4-%W zDbL!2(ZDDw`Vxh#GD~ zYQ_W|p-Nw3dl7hT=idHU=|0?)@eH8zvSWyxIW7byQyC~Axlp=#PydR;qbj|nyAr64 zV)xiF6ByimUQ_e-&kg6%hnG6Eqt)196dPAzCG(<0Pxwn37|NMA+uqM!B|QBT_S;K# zo<>%WAt}x&pEIrBN;}?qkRaG}pY{F2Dl@I;55@y5w0t4EEME-G{3^610uYbSbe`64 zBSG~CVg|RK+L>}<+rOuc@XC(LLC0edVP8@`N9Yu1LiM1{eYs`ZyKIUHjHa1mJ%;?u za+oH*U91bqAi<$`l6n4dGnPN+DpKQ8vPnF?CW4Vc->s;2n9InP`0xkKv#VTA8M-hm z+!@Z*aC`YF-Q?4uUQ$xdry0TSUGH_?6;S#*WwIv3VH*D(e=C_&)YuF#hNJ}7s#cD9 z31N-5<@<4C=5$lVmK?wl?!FN@KjlgN(Jl7AfpmsH5MHBqVTds$K1ZY5B=XhIEvWlG)8aSzWtp;s3 zkjXjckAqozPSFqz^dB^06M;%OI2jK+yMBKWIM|F4m-0!10pm3WcGWk_ZR zx~;Vleu}q~l#p@o1@2S$YrVnJd3@4@D+uB zNv!lO$|_ENC{68sZ0q+{In-(5sl;~@3u8UTgoIhqSfL%0u46l0$=q zIyCE-#DoGSEz_pC)UW=>(4K$H3MCIOtZv15y3DuL`H6s#J*Az$yaAH?9;7o}2mJ znDlq_!V{DEgyFP{;lNG?V=OQ`c2*OQ-%0f@eljNRA>>R{y4V8iXD>KkENNJ+_y$T( zes0j%`fZ;Pj>k~PS(|>RFRv@<+W6M;XGr(UFL@PHpwU250Ro8q>-*usMn+{q9>XXI z>wM~jrk;j87<)`MO9t+Y@fMDaSo1< zn7m7Dwjs&DAD;xM<Ukb4D+_L@m zmw$c0`=UF|UK)oFp8|#ndO;{NSsG;OjZ`@q#}usKDkxDE@gV^Z*+##r4%cZQHoyL4 z=#*+zfnY@54b(H*;01zp7abtH4w1al60zoH&1siH!)g#HVQW(DglHdlMI+=xiXI5g zF7DJLAZ$nuPRc<`43l&`CLiQIcEITsfY$k4j%Wxg0)*h&raSX7NG130A zjq{N8HKI?oDQUj>HZCzA!x?NGNlGdvY{i?>K{1c>Kld6rU=H7@N zm%*ml;_@S+>IR{Lmcd`dQ~AoECGni;3SP@V!5zTG#2eENFPQ0J0Czi>xQWutNB{Xp zWVacO2wMc!j6wq5dx))6#+{5-{AzoT$HPMtr`pN99)9D3l7zFaA0Zmz-D&bBh06FT zq6>KwwnMmbtHNrOIvTVSHZ)9yZ>ky-YkDygFIM7?Dd{Ez7+8rZ|o zsmR$?aQ%y`r(-K>EK;=`(&1Bwyffo=OSq1|4?kVRi?St>Un3Y>J!I;T&^h@IRB@az3{F}GKG+z>($z>RK8&V% zSj4j^?i0rt4M~&G2X1$a`_V=Z!!aC<2zLw~N-GBkU6Dsrug|j$v!~#FSxvoAo3{#V zIAJ~l>et2Hio4JF{erv~`z}Ux;uFOT3_Q=y6e?P{%&N*(ez@C-ZzlsBb9=1nq@5Yl zIhfl0dPf&OxX&jlCa)1ZJ5<2j?#6~8nqmt-?wq}a4@=riO66wGt{Q3RdFH19*}@Nt zK%C|?W69wmUmz|mL-87s?7k8&C)<31_sWp(wd|skTiJhfyVzF1)j#nD8>K3}7d9iz zpN5XdLp>;9%@-#4@8xS(Z8bF0NK%~P#8a-#RxzaHf=r#}SD**u^4cZ-gZWIhQgS>r zv^Fr)11#8+9Ah4GTDi8d9!cL-@=MgJU Yvyx9Zl9yZ6P*V{!Tpauu`1b++LgmxO zWa0D5ZvQhPmP(UkcM1*P+vs3{X*H~)q$MgOKzUnJ?y)j!gwVAaMA-1QHV00koIsZ; zj#Zwe1`8tF&X1(V0X>i4Q6q#R^iZ&;GziwHUC;i_{-BW5d#LgRs%)&t_pWEbDCp7s zez)l}GU2ss1482(6x`0T*^1H9(o9_@6L@}x2ut7S+m>^bpoYb<5RK9dfc7)Eg)9>h z@C;M!Jd9LOy`?yrBrp`eK2+ttK&d|HShRoLyZ6(2cvoBa_DIv=BA^jCnU{4ijzQak zsvA9aVj?-Z6gS!}W{gy(QvPS<0qURvoKrP$xaSu7Q$jmt2t@vZG;OvY>;#|IVP%ar z>qje&MTJ!yjTDQ4*d9&AuW=d}m%%l2N1!!w$@Mr8Tnl>N-a+v3rmjgzd5r79Rd;7M zK5*X=eN7eP%-ipoY?DPimA*@hnQn5k=BPd8)1DO0AObF~U6 zxyu%v_LM`5K#=0%b5W6(PGExcpN9L3(?iY>AXhg6-FteQu3^)7!&NtfhnBu{R|wjR zfgG2=u2TkBb*Kj2&lbtYIKhc<8$ZtbvGQ1TgRdun#>L=GL+6O{Yr1 zLM`;rdv3f{sTFga!XraW6`f}cd|yr08Jp9c%v88t6qEM7RJ+L%!94(bw|P}bfEt|B zyjMQy^~EIg0$Kz}vHw?4+3|U1a7(R1%dMY#`WBbp>b+I9Jafp9s=wqNs?~ zNEw7@=agzfLW)E!0obk75sbMXwZhFN|{0bYZFJTTu&Ywj&HjeR>l{5un+W6)spwaZS z=V-DVti`wW7;FXLHg~*{vPLaI{zg@rG*u`^?PD+Kh28w@Z;i%N6UYh`QL@dZv~c z%QxR{sqt%GCt_5P^oY$YSe&F%wz*0Aln7>0$_}U{mRf>sIl?i9B`rejTYn!u+Y&#T zq}ViaukUjsNvCRojY4XCB5Qw$?9f-zjSl5VEjDfTiar$ZOkX|^de40lkktnD&IocJ zo=b687{I5~<;b%rR4a;W&wiFY{G1A^)Qh8I>b!-JCXn}H)=uwE_y}CU(OOKo`eX9z z=+;#sZ&}PSRLsd##hM+c+q8;Xoncg1bq;7NpVnDAh6AL$)x?qLi=uW5sY-6x{YxB( z9-Q26X1ia5a@Fl0bJ){y;~>+U-?zREwG&&6RX=4N2Zto#xrp)n!*UZFZuTS==~=!E z6+j}iY;;JU8d8-_9x#PG)UzGlS`DjErf0Iu>u-7L6VY`q9L1djv>(GRBVW6`hMICW z@9@fT6TAW^B<{9H6}eJqFoy29T6|W6^sf6V2Ejm(XFxjHx_)qSM_O>G+a>i)^GJ85 zqz)R%&OP3^D5|SwhwY{R8L^7p^yP;g<*OLnlI_htJXZf?BPSi=er|Vnk$g7Oha{`a zPw1S-FJtJamz{HN9ok-&HkfcXcO~s9VLJH3OZws*Xl=->$hCv0IyD01h0?*XIyek?dH5X#uKs3kP8#hHkw#KAZ60NX)s=dx zvvLx^ad=z}s2f3^hARB#TcSSuVGARQ*Y1k9VnQz2FI#YX$afyauaN}POzDFtG$MT0 zeUQUey8u>rv6r)xk<7NkRDgV@MgG(J!}Dil`+l#<&p=I^XH?Jd$rJ$I$#C56?k8hS zU%?+O-PbNyO>lex@cAnj#tn$gHdN(0G9u~(*3kiO=n?fT^;j%K`eDDY4@L$~e67w# zRK{JQmDbc+O>D9*dW2>sozh!)_-4G@$Tjy(DhB7i3afOuZO)6n;hzvS;Z@f&gf2{G zY7DKZqJQ4N>|$z?^x$ACZH8$orz*^`#~x8fYR5gNi20S%y}1kK@oUjwjaSE42{`xw zy&sd#SNCcLZD5cAB4#nj+BUwGqJ^FgTx+Fs6P>K?7^j5`snbnMUS#8H#gTo=)f*XZ zvp2-%TF#+ab%N(#;r7S| zTR8xv@=oRN95(UyH-s6y$VzXso1Cc6j{tWb{!n~U?bd(G7-y5}K zGY=Gn6hrE1>Km#*xv-oxe_#QCr}(p^S>VD;co z$xvK(GCga*4&0=nt$vvG9yD87JDizA6F?v}SA8_~oj|l?F6*7Qc(wQYUA8T{w>XzQ z)PNKgX&i+XS=|7MdzyZ+CJM`Ct4@S@X31{xySlb_(`FcZ*<|Fs{Hda#y2-9j_)PRI zF?NzQdW zAjmtYu7;W2VG!_PYP@46DN5XYZcwE^%MUwPVF$Da6{B%BjD|?G=5vBH|9tt$1WGhD zR=g?^s-4W!2+*rNgle!nBGuB5pfQ0!@I+OTLw83hDV=~qA*Q&vH4K$HZ{6w6<{Q9+=y%a>cgV2X6IBoc-zIHl{W zFH^dq^!l-o5b4wmCxOY;+dBj$&K|Z+lqsNRu|KpWCi&enn|VBGKg!~6qf-$?&GcUp z0JeZ9*eMw5pfM5uu6kN0wob90IiP*>4p}no7f7E-9*i})p-cFbK{N0jmCFD9I7h>+ zAmQ|ql80_{J$C#+f5{HrTTYt6yYMH!Wiw>gfk9L`h`2$307N*jLI6>h&U>qOWU7ka ze-hlZ_WMD%yy6W~Q$>4)JMXs;{UxDR%jYcX7{?hYPPflY@pcGi$)tIIWy1XI1KZr0 zpzauUyQ;k2u$RG z2)Ou-IRLEwq5XGlIX?w>T+_ZP1XsJOdspBM$y$8HGGqK?a zwS)%y9_8~o-IIW}o-I>HVHJfY8e}ClzkrgLkil79DRdijlAX-Co`N?273msc8(wi! z<03OPWS-awzZDSv_?e-PTFrGB{6jNvUV*NnorBI0Pgw#%lRZ1G5^)e?F}BN#B7a^D z%C5SHUCp-=zB437o6kI~wQogs>=HG=3iV^d`=IGOeZ}I;J1RFpz$@oHR}^L7j7_uV zxbc4LoVo_CBKez!@p#!`Gd~97I%)Xw=gB$QLJK1>Nk!fXbMakv&2SCi__IGxCPGJo znT-q$lJrh5LCTu^uh{CJ@=tT}pEyQirF;lDyi{yI0|TPxka;yFZc42LAonoIwJ({~ z199Gd?=6N_&rdNFHtxl?I;-xlgh!bvy`}WP;r1lz29NvdQ!n=l!EMPXWomH-Ec_Hj zBnFCa!5YeIM_;4e`+Vb6yplB~t!@8IO*g49z-?3+Fg<-sf@6dcL)N@9sLxfV*&%%n z58pKAf%7pyt>67oZLHqb9vC(9pEf)>n3OA1C9?8JG)ETlcc-7v(>Vd2zV-wD&TyDu z>(8@x_G@Z@C;f^VQdRx;7_x*6XuVIo->mo;9;ES40jcnGf|%Q@f_BN|PCiEuJ@in0 zZqwJYL~?k=qu%|hD zJZ#>9$}|ekFm`Z3MfgZoc3ps3If}dnZT@dB&B&YyI!>%AeValI5iz`YCNi#*GCJ8d z&xE?FB-4`RZK=r{xp9$EdlJ zTLzGtdhH!S8kI9GSjW zzoWaqhiKV;u;lirPJI?dG2a^2hDb!cn6hi;bV|TR2JQUmnh7+TK zgy9|Mse-LTXEBKkh*PFEdJO&cxE+=%0RhU9-~Wxr^_y8HK`Q&#_i= z3xM$t2YyLZ9vz%%>iHCak7>UMbw1(ksnE z!_2zrqJC&P+2U1k*1WNVHHzweFK17AD3wG;5kFL5dc!~4Dtl0-n-y0p(3C| zKsMg)-Q(zX&ePosD=40c8_&l3GctqqbcV=h6ZjyV@;}fTcw5=ttO?QiS9``psKIx4 zwS&p0g=G9X$xt46LgF8@!20oX^0EZkmNGfpqs#4TWt{6}zcBk1QA`Dt)Ij8=-#9XC zA{f@V_myR{e%n;R96xi3j#}M*%v>#e*=9&NJZ-1%&4adgeUCG_RIxemj3O`4NfziG zNCM?Dfs$EvC|<+SInl)N$?cmt4A%ubmgk~%RIDT5z&_O|en|n-SI>26^y!EMK~GG> z^S{El#ouls!;s&hiFhWwMMxcO%?_664C-)buZUI6xi=xLZ1G>}JTghaT`f&}AHil_ zG6q(mMAXqFL2T}l8-#x+>6sGfOL~S&&PuBrh{ERFn2()l07gF#wzst#Wm+7S+TG*D z*v*NH;G?a_Bw24a%C?IKDRk5$1~Xv92^<$<+8QOKv#lS(&dcv?yj@3ivGH_2 zlw`HNJ9taJ@+Ls{Iv^aP&7ikNg0+3d>t>h=-&QEAW@8Z^tLq)-B<99e#3RWa}0O6o)pST{+ize+(h2#FYQ$Pyv&nX|$Xq z)(^W3jQA}F%Kkypy_}#y68X^N(k>_>KAZlwetRfE{ZNpVYNg2`;BN}IVJw0c03{`1K!8{`$~^VWM+vdfe@ebaE%~j=u&0rjE^SG zL4y6`wSTrFRtY(kk8-(VRTNA${ z&4W3jgo=nmP`!g7qW0CGkjpL$)V!GN#L8GSW91cm28dC66X3s=&|W-+N|4g4f94#- z*O<5-UC?`yKN0FgR9_cCqx)=2{{Ifoo(TUE2{pDRS;v zxG4Wsh2gc4+tW|WOBdgh3>YzcN*}&WxXka4S;Zcu0)=-jv^m{3YzEE6hZi9kvD8Kp zqcNmZEirmxu`kDG%&_vH$w{sx>F~IM-{eq4f)T9?R~t1Azf5u7x>|2l(V)YUuo0*t zFSD!J-^jMpWT?k4RUbMV>I49#<ZiW=QR4M7a_0AVh&Zyy7bNl^d6?t33{Rw{;Dlfm z!JGu1*Qz1IAE_cuDG`E>wZ!riZj4-{>J>_tq=5doktYw~n*4I)s=uQP&25d6Sn|}- z9qhKOW^%>(A@^9f@0k3pf5NYR&W40nb9QsHM_J^_3$n=zV>e5Ac=sOp8>WDFBU%iiN4zb}}#}`WEKYI#4sKoiyoZF;D z%YyhGMS6i@7^y#SUWO$5hL+~;NvQ}+IBw`mXz9?0H&ve-bS)iGZfm9yQJod3<4rLH z-aI*0;`cPo2^|*y@Wzsbx3k}uelV!*p4$;ESQKfJV(88?mP$~%+QNNpgM1Jo{X+xw zVBg&oXmgt$F4Hh}7*LGkMDfYdVd{tDt2m*a=RbKATvsj@)=*)gS5Qf8pv~i$p*M4F z*31*f?y;w&A?~fsM!(FnHR{=fW7HuhO`rHyabe45%7SjTl^eX3*X##y@$^v5^MVsP1Vfl?`2ozbgWD%qcsH$7;ep{Fd? zn{6dIYT{`wlnyL*`T-GHydQe1RUOAl{ePRH)%@p}<1@QwC+HtE-#Xa5DZQ6I{&JkZ z?CXiR;beSVo4%M0(6*kIh_`<5(+0YO4^5qSq*@0U&`Fu_q(W5W!t7qa*f{?8zdwOadjVE!A5c z*)7(YH8Ru2hV{LK#QMlmS`YAJ|M+>62l&e`m!p$l_v6eSDzkG{)AcrJ&lgh=n9Z|! zNVUsckr|Di@%cNtogq|CQj|G|M1!0&m`=l#*aVII7~$vaisM3(m=NyWnWT#x=EnyW z5mM6NPAVeCVX9QH3n5wkX?s`ZE(WV5{FiM6d&b`nLAjpm2KLDg+RIh*0=XtaYF#IkF1OWt5t!oVw_6edFH4=dcuY)0+Vp2X zg>t62vTJ^%(f^UYC_JUx_lgSq>H23%ed>9?e>vH&?i80BK-aeZc{%{`xAJw=6pd~O zWWZ$nviEt@`uaZt`>Pt*5Ni&vY}VR_)?69T5<`J04QF zlGtBg?>j(d^*ybUpd-rb0 zgPO0@jNthLU>+voL8@XSi<5HRD}XU3rOfSrzM-DX+#aGCpuCEz+}9Z>F$(7U@%6yq zFWDt&sh-YwRFYS5f2A=mCI)$Od3sJ>TohzeOa}2@*8)s~Av(@e{xjq?E`ulAfN1nu z`uV6o9%qDL!E*1Tn9)zr)?LY#=#8*;p0L8`7FgbNAR(X^gO896t3hR`KxH43fHR`} z^po}uaTz!XD~9ScuDKLLFpZ1(YtA1`C0c%?v#+CxP?ivmNnJ$|M}t{~W;WD5usGxb zOw#%(*LJC~q;sMigYvx<10-;r&|F0`V%AwwF}lC#TeOrn4y?u44aj!je9M+q1t_T5 z0?te6gxAl^Sw%gPXOtgVuSE8V;k~mvVZtbJ--njWlIekY#@Bk&CBGvLoXd%~%qM+< zF-#0mcK%8Xf`@2D0@Sn4~i>Z zKKRrt_Y_}}by3hm@Z_{Lj{Gb>&}Aw$jrN-ZFW;gQngflFez8ykQW5wkpR&rha(C)* z^Cg3uGh5Q!BGT+@k`Qd)CbL{dtQNc`&>XOY4=So+R6j(qBFe}#>}PrMGC(i_;R-)5 z3b=G8Fnmp6l!cw|Z1MX0h@?`L?q^e`Bq3>0H>~YOE1Mb`0^p9O7zg#`kls473boFh zYb);z6~WWz*SugDaAMhB@~PjTY!=3%Kq8nal$hJ^_kkUSU5FJS`|`PX-RK3STcN1` zkFyHp@I3+L+^=vw-Jn8ht7bi{icemb4?ayFU6hss0Nw+anqk`5jsi;_ML&pVL`|9b zsRKlqv1CV%kvQc`rvgT>-!yTzudwudgWRBzp1*NZH@O=`Bn$()4;Q_!E7=TTP_H6qO*s>lDGF9xFexab$Pex!=A-ru zRm;C@p)oK=0P*_P>9m=JLt|-=48R!kbr?%;k=(T`v1-F6Q@+)22=BSs z$9=_v)eSB!4r09z9XF_4bhaBs4m-q(2=5n-%*YMY!HHS`@;u=+z!7;^b?8znMGtq1 z?E%BsAQhMYPsn`zr}`S~SuGq#K2GXKRXpauJQl>hcZZPSdln1Y4q*|fho(UoS#fmu z8|<XCjr+d<-O7g6~0q(bBVbx7T+U9!BOgTU21i)E5PpYcUD~ z&26UXXKAL)&T*g_zPSA1-|`qO;WGhIJLdx9@e zC7edygp-tatQg^sXoMROp*85VjAJ-sDf*q6GO~@qe?}9LkMfwj_%Cumt5~4*2Aia1 zJSdm%rt`fz6pZxb-l(Bk+!L+RFxX@mmlJQGGb@0Ac1d^bCFvFon?BeFPw5s2&39KJ z!6pDLT&>i)dHtmfXJoH%)yB!)cB&5cD0U>49$EO5QlC&p2#FVK17<< z=3R_OJ#MDgw-VRip8!oAFYe)V!@aAe7+V6zwweZFy&Ps`%ZZTjpF*t4)uHAGUbdIsehzpODe}l>X~UKR#4ZNXkd=j9-zo2h~;>5`arn(-F*B=n&16 z1y!w@;^grB;wb#v(ZA^9!~`symg$rEqn|80qXOTWITcb70>*itvE(}?Ejxa64V|Vb zqmh+99jqCj9rQvWk?D;YLmG{275ZvvgGP_W8*qh{KH|a-H1oh*JI;y<4qMCP?F5HZ zLJ;nPLsh$CpA+i_MqWLsW|7UUe~WFCFsSb1Uh~_tIo)A2ETocSV0h2<>L0qQ=1-FK zQ_6I4=8SM1gbOwM(3I6mdEjN+Fd8h$;>p+tGXtpZ%a%#1x%&BVpCPHRMrYI1ZEg6_ z34=btE`$kUpP#d9asx>oOTwN8u;VnpUc3vOr!L?TD=!4);cjz`+?@SP&O-wYeL#in z1e)W&PQ3^{gdY4Ts=`_n$b=@8?JRK>o}}x6aF}!}Rk|#~^sBMzp(Kg1R;yBc)FW~2 zUd9AZF;eM!6I%9XLU(@NH{h+Trf%~OUCDG2rU4Y6C28K|v9Iu0fET`9o5CTs=xFRz z78VB2ivZ>mu_ji~p$pE8c1y>6(zB6{l+&uU9oGHd!d*GEl(c;jwPl#*xBTI#b**vS+@NTy;i_k-Iunz z;lh`NV~J;=*%4LtU&n2lbUs7kC&d1Qd#@6}37amQ!Kv}fp?uR|Z()ZwLq_to(tG~a z*BB5pJmVNrQYOM8|Il2cCaa8B0L62pL@M5q^(v;k?uY%{qd5R^rnUmlLtu=i*et0P#>S&(H zu)_VUCIS5p4DiTr+zD*j^E(DZgheXOptUN8?^m{02`4$Ho)$FzetU7$%Msvti#)RROW{bC}|5gMAm;wzb+ z1+yb}*~Tc?Lgxw2vH6IQqL;MsCAPz#x!MsFHClR1L?|=~S8Am&-1MdvHTYA?z*FMULl#vpt~sF7jpY6aX4oqCbMy zn&mCh*ya=9+Kr<2C%W+Q;2AW$3~@u!oo5ZBV+(_}Zw4)-=^f--<;3o>W}P=1E9LC}YpP9y`zt=$v|Y zZHchz)5Sz5S-IP%LWRy`8WV|^VOTSCF~n)?uU0~&jx(fx)jaDfb*^o^R*bwEE2m>B zF%X~*5k^q#lwNF#%D@abh{tLs{j#(2EpyGY7cT@m71d5p_wnU?p3!;Ys>I&Um%7;yp` z(fDFlYZM7uKu0xm+4e6Jpg9OP*VV1)VKDJ4WF%tc6YjM@1Ck|vrn$y|l&chCOQ)bz z!MwObA28T1H-ij5=^lZ-8;->bXsRpAbdbfY@1GeB-)D*+5*)9DAzJ957?3zswVX5u z!p%45ikOAlFiWG`3P#BR>*vymN^tUW;^cxmOwUeSKMM3TUdaKf@keDcKb7T(y$4?s zoo_s?=T|il7&t+EgpZURZ9iN3~U8yhWRBlqSZ*N;x=0l|ymCD+d-WGkvrD}hIha@Oa2n32~ zO&0g#_CW!vyv{NK6o82>Ce-8>X3abS*)A*8^tMvL4hTG^sd2#caRD{Niq=ITfp$=gA6XlZU&|^g6&p-st&h?AQ>EkqGyFN z^et?vdD#)g@GSXL?m7PB9@QvAd0wK?jE%U20N;icMF?DUdr6GzG!3y5}@&afu_<%F1&=tWT)o4GRIB z8t{w_n0{;^74{=jaqA!%Msmz0?N)2{NkMfXH3t%b1hakt_EGl^s1cE%KvMBus28|C z*XJcFuASK~SY}~Odea*astT$O1(|4nI=B`pN@Qd6ODLj13~LDDSy%z*=_?&D9TQSV zvN$$yF#DV)GjRnK6a~>Y>Mk_n6RA+ynGozC8%xJX+cNuNo&YHdgwlOdbV1RB6cv~; z%pNhy-8uaTs4>A82=-3EQO)qU(eqsQg>ohPbrA5ylFJwyFnv5$3KCE=BRz*>tg$M#UU^sM}SnD)(ODgtcEPhpTkpZkS5RF3pi%k)C z83?s9+3?zRsc5DJ#+bl%0Cm9hQP6a`%CB+^g`*3Cc~Jmw1e;!HCE2eBGay+mN>Wsg zKt*-CeqbzI*d?^F4v%N%LxGzYdiq@be-<>Sw%w!y>b}Z#N7ViZM zq=TaarVqy>#U;>OKD~{!cTZ<5=RtE<% zj$vRO_sCzydy2=}gHtq?02z(UVdSE5$^*LM5l zF2)It4Vd1->mHo2HI6*M=0KyE6Q$I4-l92*jy?6VbTLNHc z?dR%!apSibnD1Qeoe)xi<)66evZBO$JfJ#Y`dHX1GP~3n0oDVD69ab0PH3U4Gs!6? zOr>+HHbY)}o8Cl@krmb;_nuQ%ePEB5W;5*iko}Q|QlwV2(J<^+oZ^z57X_lhsxPVS zY^o?r&>tNeF#XsB6-L#SGu}<%=qeD=p4Ayis8YL(!`?>NS?diWU@x+*ZlARLypjJM zB(r7&+7s~F_uRfMWfV?cDB_|#>!R9@bwIk%7cB$)k$bmgmQ7H4(co-lmT7?O6PsBX z8!-L&NGe3CrJ}&;XP3mryQ~;1t%f516(@}ts20RyDL|rqq0JWLoU#S1#Hp59a5$tq z)+$lay`ap$5J`ES56Nu2I0UtfVBqJE*u!ZFhb1F9WUvrjLN71{~kEaXu4Z zN1#>?@fMtCTLCNV6GB#s*yN>KTq*{|G8@2Dq3}dBw+t0&siNiqf*+8zfB}heUkq2~ zO0#NLqACJl&9wHFlfS9hE)z5p-{&mI_F<)W4yH3=_PtRyWs8bxQq^3)KS!hQx(YB5 zu$WO&D3TYPCoO!z0@>N%)^*0sw85zIgZpE+ldVN!#zCq~MMDvo79S(3aJxR&>@&r4 zN`q%Zq17Sguc?1!Chqi@4w#M=;E49dnCe~zpfklC1bPnGZyZqJVVrcTwqtnG%bw*! zpoql56M+JiEjT}q=a6c&+7c>$p}?;C4$KHWqKJvT23AtA+9!AGGa(TK16TWE74m3oXkZB)u;%b@RC=*96coy6A{@q(S^i)8-L@$*ONuqC(tgHOG;a4aeIdQu zvcrbK677XhE)XvHjv>=}u8sPX6@gTA&lJlh71mjOx1e8-T4drva+OHNu$`RKNFgE? z9B7o^*u5~*v8$OQkB=P&bOyqHBo%oBmYcoo%w&)57*erJG?QMKsz*V5UuIa%wRpjl zL}sf8VHLXxA|N3$AY;j_q2RTR~{+)>=sDl>TXT2Mj zhGa6uX9}%N79gVP-4o(Spf20ft<)Hd4VZphJS9rksRs4U+sd>kAW&mc&P;zO(4Acm z=@GE*=Y}Fyteig^%FQsPnnAtorl=L2h;gwDie^X|P@10VrcWjY4XG~g!RL4-`+$HE z4j5+xLXwq_4VXRxF|B_dq@V%ofDuz5TL+YLhS2n$mHM}ilwe2uF?5m!v9k9}^pvI_ zdEF`P=E;goxZ9Uwrt4gyJ!w^UHb_a!AQ)--`-Q$|YGfSSqiTS)D)N6~W@&7|^kI+{ z%8>0S-^w_&6G8ZukxmUzvtyuwbifb}I`wVA3g6BO{n8ZJe60kW2)8ktJl6AT@# zIX${8__c5&r63V&6V-$cRjI228bxZ}#Wf#{y%R8vX^pu(OGU*bPB2SW{*Lqm6q|hc zw_7jmmtMZezU6(dOQfzDoz);y=)2!La`LQKRFC1->nRgV?r6$!FYPDmG}~H{Q~3;% z0toxyHWS5=w0?Lt_C zfdK9jQh*J>RDGS&m^8I|+KGTJ1c4D|Et&$fu3uZ0#S{Y{>RylC8cZk6dut%=CbLM9 zXz|iBW3(71g8&(|$uN%L&|JslJ3-(@BjlpbCKwZtg!n9Uz`)K?RmZ`J*@85)!c6p0 z1I*@HTK#D9J~Gcp8UTT;h?_PhjT%#j{ze7hvFn2Ar>gz2tAi-X8XN$hlrAJ&5K8AL zf=%ZbV>b>f)ek08%CXueQ5`eHy{_U~$~Xq3)B~}6H>nUNsN65XN>x#f28`Okry_w1 zmBvR$rbUQAy8WDM0PjufQLE|kplTFFF_plOVY*8A`Jo}os1LOI z?2B|gO9)p7nP>Nx0Y14Hi~w&cXjl-!r`A0CZ?`jH6qy}{@XT40T|vRwLZ$#R(gD_c z0n@Q}0nut6BHP@*`?s$Yu5?bl8Pn#Q6r=|h{zk*PgJg}VvvB}`ZnOvl1$yV_4n~xn za!p z0n?{hhHIOf$-1XPFz-Y}OciP%oz>)TY3G`?AOyM9tE7x8aE-_5XyH&FtGIB@UC z#|>o{F7-?;2XnRGyU+4^oU>qk>*^{-KCk2 z8@*>4gnE)(bxPzvLtMM;GY(jJE8ZYTH&XL6jlciY9F)wgAeCeUW6R~F7$4dX@4kE}T{dTwb1Sf;Q8 zvIELkzwI$Q$DF1p4~(H>`>D}0x7fNWZ9y;~sOmbe^xU$uRR>UqnLZ|xqB)>qO6&~y z9H*z?)(oRfi&)Ew?rv!Nq$`d2)%P#^{p$2@&}*tPds45rO}heRsvxr>l62%kw#Jxe z#)xO2drz3)#n(Wx&FBa~i2~RV*KTaU^n+7U!ghiN3_Gg8m`U~SkITrmg>Vf@n$bHZ z6u3ABw#2A=XMHc56Yd*=+uJxXR@D4i|35KP2D zfIG|59Z3=PK|o~puxd97c%i5Fj%~?+NacQ4pw{he50H!fR*shA5t*l=10z&1Nz?FTHn0i&cNe-HB&ttuFr6ctq z7d$Y6 z0HdV+qQLn`SUjU6TYR4tZR73j^A2D;Mrk0JGhq+EQ82^1IU^;eXR%$}}sYcLik%(+_DqAI+@40UYVxEOy0}qBxT3&4D6(AdV4{Txo7GwmfacGo~ zLhw_S-0=NyGAJB-2QZCE^WGFsQ%BmQRngQAe8Swpptcu=O`-G`3M!a%7Cp~ZVWQ5p zbx7+RWOF@Jp}Kd%*&NscU?R9jqMJZNKMTr`^olJLvqDXn6&ge!3WXK`%T<-QoXqkB zkag zs*JuQFr7PeD=dvwApxc;P}%cp*ukNu_o5lU-BT>SQ#8dkd>R1H+iYTrQ1o46&qS0p ztx}rKwaRo#RISV*C~l*kspP!O^MZn@mGz>4ay47S;#nA^quqNS@#(&!fZ^mAF(mk{0-Y{v^ zRzFwh$)wVv&F76}k8Dt=f?9Cdhm|T?)Sklx$V<$&=|^@S?7U+z9lIrnmJZN}7ag$* zR)I!14`P{=QWNW7jdV82?_CHgO4hIy3Pc0N&G>y)kcvMOo`{!y(*1e7V8876IyNh! z_M}#88)5+#!M3G}SC-i=fFuQ^Cj6cWJe2Wsy_mxQN0bwUs7w zFb=>r8irgtC2O3(<_-y60LL5*SXPR#e+wV7o~ zNlb=D0^_83Zu(8{223Z$xi*4Qb3Q>@OnG5K1f=d_wyZCe(+j;CwgguIwlWjz&Pkjn zx~BU$WeJ`7kUCqH*=s}<(Pfbq=nti27g^S}st*kI)%Txq!=fQl_Ca`#PyRc^dWQ=Y z5RC^PFnuU08f-)yM4L<(F;^2ulw+{zDc}#HO9@wk;34*tk__7HbPsMnpEUivZL;6Y zFm5z~$8c6OTW3nGijaj2VFX(HdFFXRDQgD2gPW1N(l*Anqfo(b{d~#Cpq0rkgTrH& z1=F!x0;{SEnnlO$K55|?_Ah#w9wiH*N(Zz*=MHF;wvwejSkm#QMbJztn$a?5SV&W` zjMC_6csyikCW>NI>kjtPJN;-%xeG8@BSjUKTULZQrvWk@Y%j8;zBHA*_Xeh4%`)5v zc`Y5r)_MBE`x69P@?Fj-fIt8}x~r3S1CE0k^sUJJRaCG&Mzep6f*R_^3vEuz#brz; z1O!S@sGCvh3yRtXczEk1{A; z%|-~BqYh);hne0bm_FNAQ-X90JdK30c?|;l7e=a9OkR8nBr2y*FcO7fFzo{=J7$YU zYR?qNku7N^6YIDSETTGwud#F#ocRKqmH@dpu_P8BQhTRXJ>DEFl!CV6Gf`<)gNaRT zg}H+@4IA->dt;_!S0khrA&y-OvOr=r+83kaV6}e*h5<=;006dy-cGb{EQ+9)EPnxP z_uq?=pghX#nL@5mo|hndl)L$)mJAE;K#RQR%aV>v;3;H|V35(wI!;xQ#>L(jn2y~Z zV7+?}9k8;By`VnDEIQu&%&jj1VWx>VnvE&$8KZ!`a&(2GkB;_8g-umZKmgVufd^Yl zGs(WUlHekD&-d|?Zi+h&kx(E5}_^vMrSU(@e{qmX5WRDrr9YS#sL*s;EmUbGB z&=ypF1`TE>ncW%K?AfK`NiZ%B>1J4d+G5?@#}MvaC;TC0eL~C%fWTug^?tzA-TpYj zzCTNGMf>(|n*E(`CxE&Tg;NDVD5V4#6ci~3*9sjDQ)EH_4S5bf;iN$_TDFoEfF=^c zp!W9y2->?X%hjp6ycG+lSzPt}2T+-EQmn%J%z%^ import { Body } from "@budibase/bbui" import CreationPage from "components/common/CreationPage.svelte" - import blankImage from "./blank.png" - import tableImage from "./table.png" - import gridImage from "./grid.png" + import blankImage from "./images/blank.png" + import tableImage from "./images/table.png" + import gridImage from "./images/grid.png" + import formImage from "./images/form.png" //optimized example import CreateScreenModal from "./CreateScreenModal.svelte" import { store } from "builderStore" @@ -54,6 +55,16 @@ View and manipulate rows on a grid
+ +
createScreenModal.show("form")}> +
+ +
+
+ Form + Capture data from your users +
+
diff --git a/packages/types/src/api/web/auth.ts b/packages/types/src/api/web/auth.ts index 46b1e8cec9..5ff0c3c1f5 100644 --- a/packages/types/src/api/web/auth.ts +++ b/packages/types/src/api/web/auth.ts @@ -18,6 +18,7 @@ export interface UpdateSelfRequest { password?: string forceResetPassword?: boolean onboardedAt?: string + tours?: Record } export interface UpdateSelfResponse { diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index 337855787f..ddb1e39c64 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -55,6 +55,7 @@ export interface User extends Document { dayPassRecordedAt?: string userGroups?: string[] onboardedAt?: string + tours?: Record scimInfo?: { isSync: true } & Record ssoId?: string } diff --git a/packages/worker/src/api/routes/validation/users.ts b/packages/worker/src/api/routes/validation/users.ts index dfc1e6fbbf..7b95de0f59 100644 --- a/packages/worker/src/api/routes/validation/users.ts +++ b/packages/worker/src/api/routes/validation/users.ts @@ -26,6 +26,7 @@ export const buildSelfSaveValidation = () => { firstName: OPTIONAL_STRING, lastName: OPTIONAL_STRING, onboardedAt: Joi.string().optional(), + tours: Joi.object().optional(), } return auth.joiValidator.body(Joi.object(schema).required().unknown(false)) } From b0cd3d4d03206c11d97821e2c860a2f22076a4c9 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Feb 2024 15:23:13 +0000 Subject: [PATCH 03/13] Refactored tours. Tours will end if a TourWrap is removed from screen. --- .../portal/onboarding/TourPopover.svelte | 19 ++++---- .../portal/onboarding/TourWrap.svelte | 44 +++++++++++++------ .../src/components/portal/onboarding/tours.js | 20 ++++++--- .../builder/app/[application]/_layout.svelte | 2 +- .../NewScreen/CreateScreenModal.svelte | 25 +++++------ .../design/_components/NewScreen/index.svelte | 2 +- .../builder/src/stores/builder/builder.js | 15 +++++-- .../src/stores/builder/tests/builder.test.js | 34 ++++++++++++-- 8 files changed, 109 insertions(+), 52 deletions(-) diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte index 1140708593..e319d3bee4 100644 --- a/packages/builder/src/components/portal/onboarding/TourPopover.svelte +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -1,6 +1,6 @@ {#if tourKey} @@ -100,6 +96,7 @@ dismissible={false} offset={15} handlePostionUpdate={tourStep?.positionHandler} + customZindex={3} >
diff --git a/packages/builder/src/components/portal/onboarding/TourWrap.svelte b/packages/builder/src/components/portal/onboarding/TourWrap.svelte index 9be6255f52..779a84f463 100644 --- a/packages/builder/src/components/portal/onboarding/TourWrap.svelte +++ b/packages/builder/src/components/portal/onboarding/TourWrap.svelte @@ -1,44 +1,62 @@ diff --git a/packages/builder/src/components/portal/onboarding/tours.js b/packages/builder/src/components/portal/onboarding/tours.js index 894f9c7894..f5e34518cb 100644 --- a/packages/builder/src/components/portal/onboarding/tours.js +++ b/packages/builder/src/components/portal/onboarding/tours.js @@ -32,14 +32,18 @@ export const TOUR_KEYS = { BUILDER_FORM_VIEW_UPDATE: "builder-form-view-update", } +export const getCurrentStepIdx = (steps, tourStepKey) => { + if (!steps?.length) { + return + } + if (steps?.length && !tourStepKey) { + return 0 + } + return steps.findIndex(step => step.id === tourStepKey) +} + const resetTourState = () => { - builderStore.update(state => ({ - ...state, - tourNodes: undefined, - tourKey: undefined, - tourKeyStep: undefined, - onboarding: false, - })) + builderStore.setTour() } const endUserOnboarding = async ({ skipped = false } = {}) => { @@ -58,6 +62,7 @@ const endUserOnboarding = async ({ skipped = false } = {}) => { // Update the cached user await auth.getSelf() + builderStore.endBuilderOnboarding() resetTourState() } catch (e) { console.error("Onboarding failed", e) @@ -222,6 +227,7 @@ const getTours = () => { }, positionHandler: customPositionHandler, align: "left-outside", + scrollIntoView: true, }, ], onSkip: async () => { diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 474c17ffb7..f786fad017 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -95,7 +95,7 @@ const release_date = new Date("2023-03-01T00:00:00.000Z") const onboarded = new Date($auth.user?.onboardedAt) if (onboarded < release_date) { - builderStore.startTour(TOUR_KEYS.FEATURE_ONBOARDING) + builderStore.setTour(TOUR_KEYS.FEATURE_ONBOARDING) } } } diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index d263b6b983..c2a7a364e5 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -155,7 +155,7 @@ // Handler for Datasource Screen Creation const completeDatasourceScreenCreation = async () => { - templates = rowListScreen(selectedDatasources) + templates = rowListScreen(selectedDatasources, mode) const screens = templates.map(template => { let screenTemplate = template.create() @@ -192,10 +192,17 @@ } const loadNewScreen = createdScreens => { - const lastScreen = createdScreens.slice(-1) + const lastScreen = createdScreens.slice(-1)[0] // Go to new screen - $goto(`./${lastScreen._id}`) + if (lastScreen?.props?._children.length) { + // Focus on the main component for the streen type + const mainComponent = lastScreen?.props?._children?.[0]._id + $goto(`./${lastScreen._id}/${mainComponent}`) + } else { + $goto(`./${lastScreen._id}`) + } + screenStore.select(lastScreen._id) } @@ -206,8 +213,6 @@ return screenTemplate }) const createdScreens = await createScreens({ screens, screenAccessRole }) - const lastScreen = createdScreens?.slice(-1)?.pop() - const mainComponent = lastScreen?.props?._children?.[0]._id if (formType === "Update" || formType === "Create") { const associatedTour = @@ -217,18 +222,12 @@ const tourRequired = !$auth?.user?.tours?.[associatedTour] if (tourRequired) { - builderStore.update(state => ({ - ...state, - tourStepKey: null, - tourNodes: null, - tourKey: associatedTour, - })) + builderStore.setTour(associatedTour) } } // Go to new screen - $goto(`./${lastScreen._id}/${mainComponent}`) - screenStore.select(lastScreen._id) + loadNewScreen(createdScreens) } // Submit screen config for creation. diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte index 6c3637a248..ff3b0beee9 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte @@ -4,7 +4,7 @@ import blankImage from "./images/blank.png" import tableImage from "./images/table.png" import gridImage from "./images/grid.png" - import formImage from "./images/form.png" //optimized example + import formImage from "./images/form.png" import CreateScreenModal from "./CreateScreenModal.svelte" import { screenStore } from "stores/builder" diff --git a/packages/builder/src/stores/builder/builder.js b/packages/builder/src/stores/builder/builder.js index 22b663af35..19253d2688 100644 --- a/packages/builder/src/stores/builder/builder.js +++ b/packages/builder/src/stores/builder/builder.js @@ -7,7 +7,7 @@ import { TOUR_KEYS } from "components/portal/onboarding/tours.js" export const INITIAL_BUILDER_STATE = { previousTopNavPath: {}, - highlightedSettingKey: null, + highlightedSetting: null, propertyFocus: null, builderSidePanel: false, onboarding: false, @@ -61,7 +61,7 @@ export class BuilderStore extends BudiStore { highlightSetting(key, type) { this.update(state => ({ ...state, - highlightedSetting: { key, type: type || "info" }, + highlightedSetting: key ? { key, type: type || "info" } : null, })) } @@ -135,9 +135,18 @@ export class BuilderStore extends BudiStore { })) } - startTour(tourKey) { + endBuilderOnboarding() { this.update(state => ({ ...state, + onboarding: false, + })) + } + + setTour(tourKey) { + this.update(state => ({ + ...state, + tourStepKey: null, + tourNodes: null, tourKey: tourKey, })) } diff --git a/packages/builder/src/stores/builder/tests/builder.test.js b/packages/builder/src/stores/builder/tests/builder.test.js index 7aac2489db..e6f52689aa 100644 --- a/packages/builder/src/stores/builder/tests/builder.test.js +++ b/packages/builder/src/stores/builder/tests/builder.test.js @@ -88,14 +88,42 @@ describe("Builder store", () => { ) }) - it("Sync a highlighted setting key to state", ctx => { - expect(ctx.test.store.highlightedSettingKey).toBeNull() + it("Sync a highlighted setting key to state. Default to info type", ctx => { + expect(ctx.test.store.highlightedSetting).toBeNull() ctx.test.builderStore.highlightSetting("testing") expect(ctx.test.store).toStrictEqual({ ...INITIAL_BUILDER_STATE, - highlightedSettingKey: "testing", + highlightedSetting: { + key: "testing", + type: "info", + }, + }) + }) + + it("Sync a highlighted setting key to state. Use provided type", ctx => { + expect(ctx.test.store.highlightedSetting).toBeNull() + + ctx.test.builderStore.highlightSetting("testing", "error") + + expect(ctx.test.store).toStrictEqual({ + ...INITIAL_BUILDER_STATE, + highlightedSetting: { + key: "testing", + type: "error", + }, + }) + }) + + it("Sync a highlighted setting key to state. Unset when no value is passed", ctx => { + expect(ctx.test.store.highlightedSetting).toBeNull() + + ctx.test.builderStore.highlightSetting("testing", "error") + ctx.test.builderStore.highlightSetting() + + expect(ctx.test.store).toStrictEqual({ + ...INITIAL_BUILDER_STATE, }) }) From 9da5467bfe5444e87a20022a9eb6a20d53de1a3f Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Feb 2024 15:23:52 +0000 Subject: [PATCH 04/13] Remove unnecessary reset function --- .../builder/src/components/portal/onboarding/tours.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/components/portal/onboarding/tours.js b/packages/builder/src/components/portal/onboarding/tours.js index f5e34518cb..5cf6735970 100644 --- a/packages/builder/src/components/portal/onboarding/tours.js +++ b/packages/builder/src/components/portal/onboarding/tours.js @@ -42,10 +42,6 @@ export const getCurrentStepIdx = (steps, tourStepKey) => { return steps.findIndex(step => step.id === tourStepKey) } -const resetTourState = () => { - builderStore.setTour() -} - const endUserOnboarding = async ({ skipped = false } = {}) => { // Mark the users onboarding as complete // Clear all tour related state @@ -63,7 +59,7 @@ const endUserOnboarding = async ({ skipped = false } = {}) => { await auth.getSelf() builderStore.endBuilderOnboarding() - resetTourState() + builderStore.setTour() } catch (e) { console.error("Onboarding failed", e) return false @@ -87,7 +83,8 @@ const endTour = async ({ key, skipped = false } = {}) => { // Update the cached user await auth.getSelf() - resetTourState() + // Reset tour state + builderStore.setTour() } const tourEvent = (eventKey, skipped) => { From 86c6922bf4f3cedaef16e61569000d40c1beb326 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Feb 2024 15:25:07 +0000 Subject: [PATCH 05/13] Added in init flag to ensure that analytic clients only init once --- packages/builder/src/analytics/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/analytics/index.js b/packages/builder/src/analytics/index.js index 6bb10acdb5..3a80a05d7f 100644 --- a/packages/builder/src/analytics/index.js +++ b/packages/builder/src/analytics/index.js @@ -9,13 +9,17 @@ const intercom = new IntercomClient(process.env.INTERCOM_TOKEN) class AnalyticsHub { constructor() { this.clients = [posthog, intercom] + this.initialised = false } async activate() { // Check analytics are enabled const analyticsStatus = await API.getAnalyticsStatus() - if (analyticsStatus.enabled) { - this.clients.forEach(client => client.init()) + if (analyticsStatus.enabled && !this.initialised) { + this.clients.forEach(client => { + client.init() + }) + this.initialised = true } } From ddc51edee77793de33579e7d7446ce8cbdebc08c Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Feb 2024 15:44:54 +0000 Subject: [PATCH 06/13] Fix to ensure the skip flag is also reset in the tour popover --- .../builder/src/components/portal/onboarding/TourPopover.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte index e319d3bee4..6dd7fa96ee 100644 --- a/packages/builder/src/components/portal/onboarding/TourPopover.svelte +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -26,6 +26,7 @@ tourStep = null popoverAnchor = null popover = null + skipping = false return } if (!tourSteps?.length) { From 7895292705fcb7197e7f846d070931fac761bc13 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Feb 2024 16:16:30 +0000 Subject: [PATCH 07/13] Add offset to custom positioning. Reusing existing prop --- packages/bbui/src/Actions/position_dropdown.js | 5 ++++- .../EditComponentPopover/EditComponentPopover.svelte | 1 + .../design/settings/controls/EditComponentPopover/index.js | 4 ++-- .../src/components/portal/onboarding/TourPopover.svelte | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index cc169eac09..d259b9197a 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -35,7 +35,10 @@ export default function positionDropdown(element, opts) { } if (typeof customUpdate === "function") { - styles = customUpdate(anchorBounds, elementBounds, styles) + styles = customUpdate(anchorBounds, elementBounds, { + ...styles, + offset: opts.offset, + }) } else { // Determine vertical styles if (align === "right-outside") { diff --git a/packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte b/packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte index 5bbbfa283c..39e4bc2ada 100644 --- a/packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/EditComponentPopover/EditComponentPopover.svelte @@ -86,6 +86,7 @@ showPopover={drawers.length === 0} clickOutsideOverride={drawers.length > 0} maxHeight={600} + offset={18} handlePostionUpdate={customPositionHandler} > diff --git a/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js b/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js index a32a1cd821..2dc3f60185 100644 --- a/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js +++ b/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js @@ -1,8 +1,8 @@ export const customPositionHandler = (anchorBounds, eleBounds, cfg) => { - let { left, top } = cfg + let { left, top, offset } = cfg let percentageOffset = 30 // left-outside - left = anchorBounds.left - eleBounds.width - 18 + left = anchorBounds.left - eleBounds.width - (offset || 5) // shift up from the anchor, if space allows let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte index 6dd7fa96ee..732ee40e11 100644 --- a/packages/builder/src/components/portal/onboarding/TourPopover.svelte +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -95,7 +95,7 @@ anchor={popoverAnchor} maxWidth={300} dismissible={false} - offset={15} + offset={12} handlePostionUpdate={tourStep?.positionHandler} customZindex={3} > From 08d5a6174a1aa0850d1bb75f14107c22c3adf330 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 15 Feb 2024 16:24:34 +0000 Subject: [PATCH 08/13] Lint --- .../components/design/settings/controls/PropertyControl.svelte | 1 - .../design/_components/NewScreen/CreateScreenModal.svelte | 2 -- .../design/_components/NewScreen/FormTypeModal.svelte | 3 +-- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index 3bfb7e9086..4cbf29e3ae 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -26,7 +26,6 @@ export let disableBindings = false export let wide - $: nullishValue = value == null || value === "" $: allBindings = getAllBindings(bindings, componentBindings, nested) $: safeValue = getSafeValue(value, defaultValue, allBindings) $: replaceBindings = val => readableToRuntimeBinding(allBindings, val) diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index c2a7a364e5..8c1a11289d 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -48,7 +48,6 @@ } try { - let screenId let createdScreens = [] for (let screen of screens) { @@ -73,7 +72,6 @@ // Create the screen const response = await screenStore.save(screen) - screenId = response._id createdScreens.push(response) // Add link in layout. We only ever actually create 1 screen now, even diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte index cc0ffaea49..856552dec2 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte @@ -1,5 +1,5 @@
{#if label && !labelHidden} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index b55be0728a..c7f8094084 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -177,9 +177,6 @@ defaultValue={setting.defaultValue} nested={setting.nested} onChange={val => updateSetting(setting, val)} - highlighted={$builderStore.highlightedSetting?.key === setting.key - ? $builderStore.highlightedSetting - : null} propertyFocus={$builderStore.propertyFocus === setting.key} info={setting.info} disableBindings={setting.disableBindings} From 30f0e3459f1ec83af56f992a14c3507813d1dfa7 Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 6 Mar 2024 10:29:43 +0000 Subject: [PATCH 13/13] PR feedback --- .../portal/onboarding/steps/NewFormSteps.svelte | 9 +++++++++ .../src/components/portal/onboarding/steps/index.js | 1 + .../builder/src/components/portal/onboarding/tours.js | 9 +++------ 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 packages/builder/src/components/portal/onboarding/steps/NewFormSteps.svelte diff --git a/packages/builder/src/components/portal/onboarding/steps/NewFormSteps.svelte b/packages/builder/src/components/portal/onboarding/steps/NewFormSteps.svelte new file mode 100644 index 0000000000..7c3679f9e8 --- /dev/null +++ b/packages/builder/src/components/portal/onboarding/steps/NewFormSteps.svelte @@ -0,0 +1,9 @@ +
+ When faced with a sizable form, consider implementing a multi-step approach to + enhance user experience. + +

+ Breaking the form into multiple steps can significantly improve usability by + making the process more digestible for your users. +

+
diff --git a/packages/builder/src/components/portal/onboarding/steps/index.js b/packages/builder/src/components/portal/onboarding/steps/index.js index 6694ce97a7..e15d191652 100644 --- a/packages/builder/src/components/portal/onboarding/steps/index.js +++ b/packages/builder/src/components/portal/onboarding/steps/index.js @@ -2,3 +2,4 @@ export { default as OnboardingData } from "./OnboardingData.svelte" export { default as OnboardingDesign } from "./OnboardingDesign.svelte" export { default as OnboardingPublish } from "./OnboardingPublish.svelte" export { default as NewViewUpdateFormRowId } from "./NewViewUpdateFormRowId.svelte" +export { default as NewFormSteps } from "./NewFormSteps.svelte" diff --git a/packages/builder/src/components/portal/onboarding/tours.js b/packages/builder/src/components/portal/onboarding/tours.js index 5cf6735970..fab97cdd03 100644 --- a/packages/builder/src/components/portal/onboarding/tours.js +++ b/packages/builder/src/components/portal/onboarding/tours.js @@ -7,6 +7,7 @@ import { OnboardingDesign, OnboardingPublish, NewViewUpdateFormRowId, + NewFormSteps, } from "./steps" import { API } from "api" import { customPositionHandler } from "components/design/settings/controls/EditComponentPopover" @@ -176,9 +177,7 @@ const getTours = () => { { id: TOUR_STEP_KEYS.BUILDER_FORM_CREATE_STEPS, title: "Add multiple steps", - body: `When faced with a sizable form, consider implementing a multi-step - approach to enhance user experience. Breaking the form into multiple steps - can significantly improve usability by making the process more digestible for your users.`, + layout: NewFormSteps, query: "#steps-prop-control-wrap", onComplete: () => { builderStore.highlightSetting() @@ -210,9 +209,7 @@ const getTours = () => { { id: TOUR_STEP_KEYS.BUILDER_FORM_VIEW_UPDATE_STEPS, title: "Add multiple steps", - body: `When faced with a sizable form, consider implementing a multi-step - approach to enhance user experience. Breaking the form into multiple steps - can significantly improve usability by making the process more digestible for your users.`, + layout: NewFormSteps, query: "#steps-prop-control-wrap", onComplete: () => { builderStore.highlightSetting()