add link for each individual setting
This commit is contained in:
parent
c199b681e3
commit
8cab28d5b6
|
@ -16,6 +16,7 @@
|
||||||
} from "@/dataBinding"
|
} from "@/dataBinding"
|
||||||
import { ActionButton, notifications } from "@budibase/bbui"
|
import { ActionButton, notifications } from "@budibase/bbui"
|
||||||
import { capitalise } from "@/helpers"
|
import { capitalise } from "@/helpers"
|
||||||
|
import { builderStore } from "@/stores/builder"
|
||||||
import TourWrap from "@/components/portal/onboarding/TourWrap.svelte"
|
import TourWrap from "@/components/portal/onboarding/TourWrap.svelte"
|
||||||
import { TOUR_STEP_KEYS } from "@/components/portal/onboarding/tours.js"
|
import { TOUR_STEP_KEYS } from "@/components/portal/onboarding/tours.js"
|
||||||
|
|
||||||
|
@ -55,6 +56,14 @@
|
||||||
$: id = $selectedComponent?._id
|
$: id = $selectedComponent?._id
|
||||||
$: id, (section = tabs[0])
|
$: id, (section = tabs[0])
|
||||||
$: componentName = getComponentName(componentInstance)
|
$: componentName = getComponentName(componentInstance)
|
||||||
|
|
||||||
|
$: highlightedSettings = $builderStore.highlightedSettings
|
||||||
|
$: if (highlightedSettings?.length) {
|
||||||
|
const settings = highlightedSettings.map(s => s.key)
|
||||||
|
if (settings.length === 1 && settings[0] === "_conditions") {
|
||||||
|
section = "conditions"
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $selectedComponent}
|
{#if $selectedComponent}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import { componentStore } from "@/stores/builder"
|
import { componentStore } from "@/stores/builder"
|
||||||
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
||||||
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
|
import { builderStore } from "@/stores/builder"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
|
@ -18,6 +19,10 @@
|
||||||
let tempValue
|
let tempValue
|
||||||
let drawer
|
let drawer
|
||||||
|
|
||||||
|
$: highlightCondition = $builderStore.highlightedSettings?.find(
|
||||||
|
setting => setting.key === "_conditions"
|
||||||
|
)
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
tempValue = JSON.parse(JSON.stringify(componentInstance?._conditions ?? []))
|
tempValue = JSON.parse(JSON.stringify(componentInstance?._conditions ?? []))
|
||||||
drawer.show()
|
drawer.show()
|
||||||
|
@ -52,7 +57,9 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DetailSummary name={"Conditions"} collapsible={false}>
|
<DetailSummary name={"Conditions"} collapsible={false}>
|
||||||
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
<div class:highlighted={highlightCondition}>
|
||||||
|
<ActionButton fullWidth on:click={openDrawer}>{conditionText}</ActionButton>
|
||||||
|
</div>
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
<Drawer bind:this={drawer} title="Conditions">
|
<Drawer bind:this={drawer} title="Conditions">
|
||||||
<svelte:fragment slot="description">
|
<svelte:fragment slot="description">
|
||||||
|
@ -61,3 +68,17 @@
|
||||||
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
<Button cta slot="buttons" on:click={() => save()}>Save</Button>
|
||||||
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
<ConditionalUIDrawer slot="body" bind:conditions={tempValue} {bindings} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.highlighted {
|
||||||
|
background: var(--spectrum-global-color-gray-300);
|
||||||
|
border-left: 4px solid var(--spectrum-semantic-informative-color-background);
|
||||||
|
transition: background 130ms ease-out, border-color 130ms ease-out;
|
||||||
|
margin-top: -3.5px;
|
||||||
|
margin-bottom: -3.5px;
|
||||||
|
padding-bottom: 3.5px;
|
||||||
|
padding-top: 3.5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Divider, Select, Heading } from "@budibase/bbui"
|
import { Select, Link } from "@budibase/bbui"
|
||||||
import { getAllStateVariables } from "@/dataBinding"
|
import { getAllStateVariables } from "@/dataBinding"
|
||||||
import {
|
import {
|
||||||
componentStore,
|
componentStore,
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
isJSBinding,
|
isJSBinding,
|
||||||
} from "@budibase/string-templates"
|
} from "@budibase/string-templates"
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
import CodeMirrorEditor from "@/components/common/CodeMirrorEditor.svelte"
|
import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
type ComponentUsingState = {
|
type ComponentUsingState = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -40,9 +40,8 @@
|
||||||
const previewContext = $previewStore.selectedComponentContext || {}
|
const previewContext = $previewStore.selectedComponentContext || {}
|
||||||
|
|
||||||
if (selectedKey && previewContext.state) {
|
if (selectedKey && previewContext.state) {
|
||||||
// It's unlikely value will ever be populated immediately as state is never populated automatically in preview
|
// It's unlikely value will ever be populated immediately as preview never has state values on load
|
||||||
const value = previewContext.state[selectedKey] ?? null
|
editorValue = previewContext.state[selectedKey] ?? null
|
||||||
editorValue = JSON.stringify(value, null, 2)
|
|
||||||
editorError = null
|
editorError = null
|
||||||
} else {
|
} else {
|
||||||
editorValue = ""
|
editorValue = ""
|
||||||
|
@ -50,10 +49,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findComponentsUpdatingState(
|
const findComponentsUpdatingState = (
|
||||||
component: any,
|
component: any,
|
||||||
stateKey: string
|
stateKey: string
|
||||||
): ComponentUsingState[] {
|
): ComponentUsingState[] => {
|
||||||
let foundComponents: ComponentUsingState[] = []
|
let foundComponents: ComponentUsingState[] = []
|
||||||
|
|
||||||
const eventHandlerProps = ["onClick", "onChange"]
|
const eventHandlerProps = ["onClick", "onChange"]
|
||||||
|
@ -87,39 +86,76 @@
|
||||||
return foundComponents
|
return foundComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
function findComponentsUsingState(
|
const findComponentsUsingState = (
|
||||||
component: any,
|
component: any,
|
||||||
stateKey: string
|
stateKey: string
|
||||||
): ComponentUsingState[] {
|
): ComponentUsingState[] => {
|
||||||
let componentsUsingState: ComponentUsingState[] = []
|
let componentsUsingState: ComponentUsingState[] = []
|
||||||
|
|
||||||
|
const processValue = (value: string): boolean => {
|
||||||
|
const bindings = findHBSBlocks(value).map(binding => {
|
||||||
|
const sanitizedBinding = binding.replace(/\\"/g, '"')
|
||||||
|
return isJSBinding(sanitizedBinding)
|
||||||
|
? decodeJSBinding(sanitizedBinding)
|
||||||
|
: sanitizedBinding
|
||||||
|
})
|
||||||
|
return bindings.join(" ").includes(stateKey)
|
||||||
|
}
|
||||||
|
|
||||||
const { _children, ...componentSettings } = component
|
const { _children, ...componentSettings } = component
|
||||||
let settingsWithState: string[] = []
|
|
||||||
|
|
||||||
|
// Check normal settings
|
||||||
for (const [setting, value] of Object.entries(componentSettings)) {
|
for (const [setting, value] of Object.entries(componentSettings)) {
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string" && processValue(value)) {
|
||||||
const bindings = findHBSBlocks(value).map(binding => {
|
componentsUsingState.push({
|
||||||
let sanitizedBinding = binding.replace(/\\"/g, '"')
|
id: component._id,
|
||||||
if (isJSBinding(sanitizedBinding)) {
|
name: `${component._instanceName} (${setting})`,
|
||||||
return decodeJSBinding(sanitizedBinding)
|
settings: [setting],
|
||||||
} else {
|
|
||||||
return sanitizedBinding
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
const bindingString = bindings.join(" ")
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (bindingString.includes(stateKey)) {
|
// Check conditions
|
||||||
settingsWithState.push(setting)
|
if (component._conditions?.length > 0) {
|
||||||
|
for (const condition of component._conditions) {
|
||||||
|
const conditionValues = [condition.referenceValue, condition.newValue]
|
||||||
|
if (
|
||||||
|
conditionValues.some(
|
||||||
|
value => typeof value === "string" && processValue(value)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
componentsUsingState.push({
|
||||||
|
id: component._id,
|
||||||
|
name: `${component._instanceName} (conditions)`,
|
||||||
|
settings: ["_conditions"],
|
||||||
|
})
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsWithState.length > 0) {
|
// Check styles
|
||||||
componentsUsingState.push({
|
if (component._styles) {
|
||||||
id: component._id,
|
const checkStyleObject = (obj: any) => {
|
||||||
name: component._instanceName,
|
for (const [, value] of Object.entries(obj)) {
|
||||||
settings: settingsWithState,
|
if (typeof value === "string" && processValue(value)) {
|
||||||
})
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasStateInStyles = Object.values(component._styles).some(styleObj =>
|
||||||
|
checkStyleObject(styleObj)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasStateInStyles) {
|
||||||
|
componentsUsingState.push({
|
||||||
|
id: component._id,
|
||||||
|
name: `${component._instanceName} (styles)`,
|
||||||
|
settings: ["_styles"],
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_children) {
|
if (_children) {
|
||||||
|
@ -134,7 +170,7 @@
|
||||||
return componentsUsingState
|
return componentsUsingState
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchComponents(stateKey: string) {
|
const searchComponents = (stateKey: string) => {
|
||||||
if (!stateKey || !$selectedScreen?.props) {
|
if (!stateKey || !$selectedScreen?.props) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -148,22 +184,21 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStateKeySelect(event: CustomEvent) {
|
const handleStateKeySelect = (key: CustomEvent) => {
|
||||||
selectedKey = event.detail
|
if (!key.detail) {
|
||||||
if (!selectedKey) {
|
|
||||||
throw new Error("No state key selected")
|
throw new Error("No state key selected")
|
||||||
}
|
}
|
||||||
searchComponents(selectedKey)
|
searchComponents(key.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickComponentLink(component: ComponentUsingState) {
|
const onClickComponentLink = (component: ComponentUsingState) => {
|
||||||
componentStore.select(component.id)
|
componentStore.select(component.id)
|
||||||
component.settings.forEach(setting => {
|
component.settings.forEach(setting => {
|
||||||
builderStore.highlightSetting(setting)
|
builderStore.highlightSetting(setting)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStateInspectorChange(e: CustomEvent) {
|
const handleStateInspectorChange = (e: CustomEvent) => {
|
||||||
if (!selectedKey || !$previewStore.selectedComponentContext) {
|
if (!selectedKey || !$previewStore.selectedComponentContext) {
|
||||||
throw new Error("No state key selected")
|
throw new Error("No state key selected")
|
||||||
}
|
}
|
||||||
|
@ -172,48 +207,40 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const stateUpdate = { [selectedKey]: e.detail }
|
||||||
const value = JSON.parse(e.detail)
|
editorError = null
|
||||||
const stateUpdate = { [selectedKey]: value }
|
|
||||||
editorError = null
|
|
||||||
|
|
||||||
previewStore.updateState(stateUpdate)
|
previewStore.updateState(stateUpdate)
|
||||||
previewStore.setSelectedComponentContext({
|
previewStore.setSelectedComponentContext({
|
||||||
...$previewStore.selectedComponentContext,
|
...$previewStore.selectedComponentContext,
|
||||||
state: stateUpdate,
|
state: stateUpdate,
|
||||||
})
|
})
|
||||||
previewStore.requestComponentContext()
|
previewStore.requestComponentContext()
|
||||||
} catch (err) {
|
|
||||||
editorError = "Invalid JSON value"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="state-panel">
|
<div class="state-panel">
|
||||||
<Heading size="S">State</Heading>
|
|
||||||
<div>Showing state variables for this screen</div>
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<Select
|
<Select
|
||||||
|
label="State variables"
|
||||||
value={selectedKey}
|
value={selectedKey}
|
||||||
placeholder="Type here..."
|
placeholder="Type here..."
|
||||||
options={keyOptions}
|
options={keyOptions}
|
||||||
on:change={handleStateKeySelect}
|
on:change={handleStateKeySelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<CodeMirrorEditor
|
<DrawerBindableInput
|
||||||
height={200}
|
|
||||||
value={editorValue}
|
value={editorValue}
|
||||||
resize="vertical"
|
title={`Set value for "${selectedKey}"`}
|
||||||
label="State Inspector"
|
placeholder="Enter a value"
|
||||||
|
label="Set temporary value for design preview"
|
||||||
on:change={handleStateInspectorChange}
|
on:change={handleStateInspectorChange}
|
||||||
/>
|
/>
|
||||||
{#if editorError}
|
{#if editorError}
|
||||||
<div class="error">{editorError}</div>
|
<div class="error">{editorError}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{#if componentsUsingState.length > 0}
|
{#if componentsUsingState.length > 0}
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
@ -221,7 +248,7 @@
|
||||||
{#each componentsUsingState as component, i}
|
{#each componentsUsingState as component, i}
|
||||||
{#if i > 0}{", "}{/if}
|
{#if i > 0}{", "}{/if}
|
||||||
<button
|
<button
|
||||||
class="component-link"
|
class="component-link updates-colour"
|
||||||
on:click={() => onClickComponentLink(component)}
|
on:click={() => onClickComponentLink(component)}
|
||||||
>
|
>
|
||||||
{component.name}
|
{component.name}
|
||||||
|
@ -235,7 +262,7 @@
|
||||||
{#each componentsUpdatingState as component, i}
|
{#each componentsUpdatingState as component, i}
|
||||||
{#if i > 0}{", "}{/if}
|
{#if i > 0}{", "}{/if}
|
||||||
<button
|
<button
|
||||||
class="component-link"
|
class="component-link controlled-by-colour"
|
||||||
on:click={() => onClickComponentLink(component)}
|
on:click={() => onClickComponentLink(component)}
|
||||||
>
|
>
|
||||||
{component.name}
|
{component.name}
|
||||||
|
@ -243,6 +270,16 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div style="opacity: 0.5; ">
|
||||||
|
<Link
|
||||||
|
href="https://docs.budibase.com/docs/app-state"
|
||||||
|
target="_blank"
|
||||||
|
size={"S"}
|
||||||
|
secondary
|
||||||
|
>
|
||||||
|
Learn more about State within Budibase.
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -258,7 +295,7 @@
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
color: var(--spectrum-global-color-gray-700);
|
color: var(--spectrum-global-color-gray-700);
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-50);
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
color: var(
|
color: var(
|
||||||
|
@ -268,6 +305,13 @@
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
margin-top: var(--spectrum-global-dimension-size-75);
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.updates-colour {
|
||||||
|
color: #8488fd;
|
||||||
|
}
|
||||||
|
.controlled-by-colour {
|
||||||
|
color: #e87400;
|
||||||
|
}
|
||||||
.component-link {
|
.component-link {
|
||||||
display: inline;
|
display: inline;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -275,7 +319,7 @@
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: var(--spectrum-global-color-white);
|
color: var(--spectrum-global-color-white);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-50);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.component-link:hover {
|
.component-link:hover {
|
||||||
|
|
Loading…
Reference in New Issue