further changes to support persisting values when component changes

This commit is contained in:
Peter Clement 2025-02-12 12:10:53 +00:00
parent deeb1fc29f
commit af0c722af1
6 changed files with 90 additions and 33 deletions

View File

@ -1,37 +1,71 @@
<script> <script lang="ts">
import { onMount } from "svelte" import { onMount } from "svelte"
import { Input, Icon, Body, AbsTooltip } from "@budibase/bbui" import {
import { previewStore } from "@/stores/builder" Input,
Icon,
Body,
AbsTooltip,
TooltipPosition,
} from "@budibase/bbui"
import { previewStore, selectedScreen } from "@/stores/builder"
import { ComponentContext } from "@budibase/types"
export let baseRoute = "" export let baseRoute = ""
export let testValue = ""
$: routeParams = baseRoute.match(/:[a-zA-Z]+/g) || [] let testValue: string | undefined
$: placeholder = (() => {
// Helper function to extract the route parameters $: placeholder = getPlaceholder(baseRoute)
// e.g /employees/:id/:name becomes /employees/1/John for the placeholder $: baseInput = createBaseInput(baseRoute)
if (!routeParams.length) return "Add test values" $: updateTestValueFromContext($previewStore.selectedComponentContext)
const segments = baseRoute.split("/").slice(2) $: if ($selectedScreen) {
let paramCount = 1 testValue = ""
}
const getPlaceholder = (route: string) => {
const trimmed = route.replace(/\/$/, "")
if (trimmed.startsWith("/:")) {
return "1"
}
const segments = trimmed.split("/").slice(2)
let count = 1
return segments return segments
.map(segment => { .map(segment => (segment.startsWith(":") ? count++ : segment))
if (segment.startsWith(":")) {
return paramCount++
}
return segment
})
.join("/") .join("/")
})() }
$: { // This function is needed to repopulate the test value from componentContext
if ($previewStore.selectedComponentContext?.url?.testValue !== undefined) { // when a user navigates to another component and then back again
testValue = $previewStore.selectedComponentContext.url.testValue const updateTestValueFromContext = (context: ComponentContext | null) => {
if (context?.url && !testValue) {
const { wild, ...urlParams } = context.url
const queryParams = context.query
if (Object.values(urlParams).some(v => Boolean(v))) {
let value = baseRoute
.split("/")
.slice(2)
.map(segment =>
segment.startsWith(":")
? urlParams[segment.slice(1)] || ""
: segment
)
.join("/")
const qs = new URLSearchParams(queryParams).toString()
if (qs) {
value += `?${qs}`
}
testValue = value
}
} }
} }
const onVariableChange = e => { const createBaseInput = (baseRoute: string) => {
return baseRoute === "/" || baseRoute.split("/")[1]?.startsWith(":")
? "/"
: `/${baseRoute.split("/")[1]}/`
}
const onVariableChange = (e: CustomEvent) => {
previewStore.updateUrl({ route: baseRoute, testValue: e.detail }) previewStore.updateUrl({ route: baseRoute, testValue: e.detail })
previewStore.requestComponentContext()
} }
onMount(() => { onMount(() => {
@ -43,8 +77,8 @@
<div class="info"> <div class="info">
<Body size="XS">URL Variable Testing</Body> <Body size="XS">URL Variable Testing</Body>
<AbsTooltip <AbsTooltip
text="Test how your screen behaves with different URL parameters. Enter values in the format shown in the placeholder." text="Test how your screen behaves with different URL parameters. Enter values in the format shown in the placeholder below."
position={"bottom"} position={TooltipPosition.Top}
noWrap noWrap
> >
<div class="icon"> <div class="icon">
@ -54,7 +88,7 @@
</div> </div>
<div class="url-test-container"> <div class="url-test-container">
<div class="base-input"> <div class="base-input">
<Input disabled={true} value={`/${baseRoute.split("/")[1]}/`} /> <Input disabled={true} value={baseInput} />
</div> </div>
<div class="variable-input"> <div class="variable-input">
<Input <Input

View File

@ -19,7 +19,6 @@
$: bindings = getBindableProperties($selectedScreen, null) $: bindings = getBindableProperties($selectedScreen, null)
$: screenSettings = getScreenSettings($selectedScreen) $: screenSettings = getScreenSettings($selectedScreen)
let urlTestValue = ""
let errors = {} let errors = {}
@ -100,7 +99,6 @@
control: URLVariableTestInput, control: URLVariableTestInput,
props: { props: {
baseRoute: screen.routing?.route, baseRoute: screen.routing?.route,
testValue: urlTestValue,
}, },
}, },
] ]

View File

@ -1,9 +1,8 @@
import { get } from "svelte/store" import { get } from "svelte/store"
import { BudiStore } from "../BudiStore" import { BudiStore } from "../BudiStore"
import { PreviewDevice, ComponentContext } from "@budibase/types"
type PreviewDevice = "desktop" | "tablet" | "mobile"
type PreviewEventHandler = (name: string, payload?: any) => void type PreviewEventHandler = (name: string, payload?: any) => void
type ComponentContext = Record<string, any>
interface PreviewState { interface PreviewState {
previewDevice: PreviewDevice previewDevice: PreviewDevice

View File

@ -9,6 +9,7 @@ import {
componentStore, componentStore,
navigationStore, navigationStore,
selectedComponent, selectedComponent,
previewStore,
} from "@/stores/builder" } from "@/stores/builder"
import { createHistoryStore, HistoryStore } from "@/stores/builder/history" import { createHistoryStore, HistoryStore } from "@/stores/builder/history"
import { API } from "@/api" import { API } from "@/api"
@ -110,6 +111,9 @@ export class ScreenStore extends BudiStore<ScreenState> {
return return
} }
// When we select a screen, we want to clear the url binding test value
previewStore.updateUrl({ route: screen.routing.route, testValue: "" })
// Select new screen // Select new screen
this.update(state => { this.update(state => {
state.selectedScreenId = screen._id state.selectedScreenId = screen._id

View File

@ -120,15 +120,36 @@ const createRouteStore = () => {
return `${base}#${relativeURL}` return `${base}#${relativeURL}`
} }
const setTestUrlParams = (route: string, testValue: string) => { const setTestUrlParams = (route: string, testValue: string) => {
const routeSegments = route.split("/").slice(2) if (route === "/") {
const testSegments = testValue.split("/") return
}
const [pathPart, queryPart] = testValue.split("?")
const routeSegments = route.split("/").filter(Boolean)
// If first segment is a parameter (e.g. /:foo), include it in processing
const startIndex = routeSegments[0]?.startsWith(":") ? 0 : 1
const segments = routeSegments.slice(startIndex)
const testSegments = pathPart.split("/")
const params: Record<string, string> = {} const params: Record<string, string> = {}
routeSegments.forEach((segment, index) => { segments.forEach((segment, index) => {
if (segment.startsWith(":") && index < testSegments.length) { if (segment.startsWith(":") && index < testSegments.length) {
params[segment.slice(1)] = testSegments[index] params[segment.slice(1)] = testSegments[index]
} }
}) })
const queryParams: Record<string, string> = {}
if (queryPart) {
queryPart.split("&").forEach(param => {
const [key, value] = param.split("=")
if (key && value) {
queryParams[key] = value
}
})
}
setQueryParams({ ...queryParams })
store.update(state => ({ ...state, testUrlParams: params })) store.update(state => ({ ...state, testUrlParams: params }))
} }
return { return {

View File

@ -1 +1,2 @@
export type PreviewDevice = "desktop" | "tablet" | "mobile" export type PreviewDevice = "desktop" | "tablet" | "mobile"
export type ComponentContext = Record<string, any>