Merge pull request #15365 from Budibase/budi-8980-create-state-panel
State explorer panel
This commit is contained in:
commit
5749543ab6
|
@ -11,6 +11,7 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import clickOutside from "../../Actions/click_outside"
|
import clickOutside from "../../Actions/click_outside"
|
||||||
import Popover from "../../Popover/Popover.svelte"
|
import Popover from "../../Popover/Popover.svelte"
|
||||||
|
import { PopoverAlignment } from "../../constants"
|
||||||
|
|
||||||
export let value: string | undefined = undefined
|
export let value: string | undefined = undefined
|
||||||
export let id: string | undefined = undefined
|
export let id: string | undefined = undefined
|
||||||
|
@ -97,11 +98,16 @@
|
||||||
<Popover
|
<Popover
|
||||||
{anchor}
|
{anchor}
|
||||||
{open}
|
{open}
|
||||||
align="left"
|
align={PopoverAlignment.Left}
|
||||||
on:close={() => (open = false)}
|
on:close={() => (open = false)}
|
||||||
useAnchorWidth
|
useAnchorWidth
|
||||||
>
|
>
|
||||||
<div class="popover-content" use:clickOutside={() => (open = false)}>
|
<div
|
||||||
|
class="popover-content"
|
||||||
|
use:clickOutside={() => {
|
||||||
|
open = false
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ul class="spectrum-Menu" role="listbox">
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
{#if options && Array.isArray(options)}
|
{#if options && Array.isArray(options)}
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
<script>
|
<script lang="ts" context="module">
|
||||||
|
type O = any
|
||||||
|
type V = any
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import "@spectrum-css/picker/dist/index-vars.css"
|
import "@spectrum-css/picker/dist/index-vars.css"
|
||||||
import "@spectrum-css/popover/dist/index-vars.css"
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
import "@spectrum-css/menu/dist/index-vars.css"
|
import "@spectrum-css/menu/dist/index-vars.css"
|
||||||
|
@ -11,43 +16,55 @@
|
||||||
import Tags from "../../Tags/Tags.svelte"
|
import Tags from "../../Tags/Tags.svelte"
|
||||||
import Tag from "../../Tags/Tag.svelte"
|
import Tag from "../../Tags/Tag.svelte"
|
||||||
import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte"
|
import ProgressCircle from "../../ProgressCircle/ProgressCircle.svelte"
|
||||||
|
import { PopoverAlignment } from "../../constants"
|
||||||
|
|
||||||
export let id = null
|
export let id: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let fieldText = ""
|
export let fieldText: string = ""
|
||||||
export let fieldIcon = ""
|
export let fieldIcon: string = ""
|
||||||
export let fieldColour = ""
|
export let fieldColour: string = ""
|
||||||
export let isPlaceholder = false
|
export let isPlaceholder: boolean = false
|
||||||
export let placeholderOption = null
|
export let placeholderOption: string | undefined | boolean = undefined
|
||||||
export let options = []
|
export let options: O[] = []
|
||||||
export let isOptionSelected = () => false
|
export let isOptionSelected = (option: O) => option as unknown as boolean
|
||||||
export let isOptionEnabled = () => true
|
export let isOptionEnabled = (option: O, _index?: number) =>
|
||||||
export let onSelectOption = () => {}
|
option as unknown as boolean
|
||||||
export let getOptionLabel = option => option
|
export let onSelectOption: (_value: V) => void = () => {}
|
||||||
export let getOptionValue = option => option
|
export let getOptionLabel = (option: O, _index?: number) => `${option}`
|
||||||
export let getOptionIcon = () => null
|
export let getOptionValue = (option: O, _index?: number) =>
|
||||||
|
option as unknown as V
|
||||||
|
export let getOptionIcon = (option: O, _index?: number) =>
|
||||||
|
option as unknown as O
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = (option: O, _index?: number) =>
|
||||||
export let getOptionSubtitle = () => null
|
option as unknown as O
|
||||||
export let open = false
|
export let getOptionSubtitle = (option: O, _index?: number) =>
|
||||||
export let readonly = false
|
option as unknown as O
|
||||||
export let quiet = false
|
export let open: boolean = false
|
||||||
export let autoWidth = false
|
export let readonly: boolean = false
|
||||||
export let autocomplete = false
|
export let quiet: boolean = false
|
||||||
export let sort = false
|
export let autoWidth: boolean | undefined = false
|
||||||
export let searchTerm = null
|
export let autocomplete: boolean = false
|
||||||
export let customPopoverHeight
|
export let sort: boolean = false
|
||||||
export let align = "left"
|
export let searchTerm: string | null = null
|
||||||
export let footer = null
|
export let customPopoverHeight: string | undefined = undefined
|
||||||
export let customAnchor = null
|
export let align: PopoverAlignment | undefined = PopoverAlignment.Left
|
||||||
export let loading
|
export let footer: string | undefined = undefined
|
||||||
export let onOptionMouseenter = () => {}
|
export let customAnchor: HTMLElement | undefined = undefined
|
||||||
export let onOptionMouseleave = () => {}
|
export let loading: boolean = false
|
||||||
|
export let onOptionMouseenter: (
|
||||||
|
_e: MouseEvent,
|
||||||
|
_option: any
|
||||||
|
) => void = () => {}
|
||||||
|
export let onOptionMouseleave: (
|
||||||
|
_e: MouseEvent,
|
||||||
|
_option: any
|
||||||
|
) => void = () => {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let button
|
let button: any
|
||||||
let component
|
let component: any
|
||||||
|
|
||||||
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
|
||||||
$: filteredOptions = getFilteredOptions(
|
$: filteredOptions = getFilteredOptions(
|
||||||
|
@ -56,7 +73,7 @@
|
||||||
getOptionLabel
|
getOptionLabel
|
||||||
)
|
)
|
||||||
|
|
||||||
const onClick = e => {
|
const onClick = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
dispatch("click")
|
dispatch("click")
|
||||||
|
@ -67,7 +84,11 @@
|
||||||
open = !open
|
open = !open
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSortedOptions = (options, getLabel, sort) => {
|
const getSortedOptions = (
|
||||||
|
options: any[],
|
||||||
|
getLabel: (_option: any) => string,
|
||||||
|
sort: boolean
|
||||||
|
) => {
|
||||||
if (!options?.length || !Array.isArray(options)) {
|
if (!options?.length || !Array.isArray(options)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -81,17 +102,21 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFilteredOptions = (options, term, getLabel) => {
|
const getFilteredOptions = (
|
||||||
|
options: any[],
|
||||||
|
term: string | null,
|
||||||
|
getLabel: (_option: any) => string
|
||||||
|
) => {
|
||||||
if (autocomplete && term) {
|
if (autocomplete && term) {
|
||||||
const lowerCaseTerm = term.toLowerCase()
|
const lowerCaseTerm = term.toLowerCase()
|
||||||
return options.filter(option => {
|
return options.filter((option: any) => {
|
||||||
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
const onScroll = e => {
|
const onScroll = (e: any) => {
|
||||||
const scrollPxThreshold = 100
|
const scrollPxThreshold = 100
|
||||||
const scrollPositionFromBottom =
|
const scrollPositionFromBottom =
|
||||||
e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop
|
e.target.scrollHeight - e.target.clientHeight - e.target.scrollTop
|
||||||
|
@ -151,18 +176,20 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<Popover
|
<Popover
|
||||||
anchor={customAnchor ? customAnchor : button}
|
anchor={customAnchor ? customAnchor : button}
|
||||||
align={align || "left"}
|
align={align || PopoverAlignment.Left}
|
||||||
{open}
|
{open}
|
||||||
on:close={() => (open = false)}
|
on:close={() => (open = false)}
|
||||||
useAnchorWidth={!autoWidth}
|
useAnchorWidth={!autoWidth}
|
||||||
maxWidth={autoWidth ? 400 : null}
|
maxWidth={autoWidth ? 400 : undefined}
|
||||||
customHeight={customPopoverHeight}
|
customHeight={customPopoverHeight}
|
||||||
maxHeight={360}
|
maxHeight={360}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="popover-content"
|
class="popover-content"
|
||||||
class:auto-width={autoWidth}
|
class:auto-width={autoWidth}
|
||||||
use:clickOutside={() => (open = false)}
|
use:clickOutside={() => {
|
||||||
|
open = false
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{#if autocomplete}
|
{#if autocomplete}
|
||||||
<Search
|
<Search
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/search/dist/index-vars.css"
|
import "@spectrum-css/search/dist/index-vars.css"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value: any = null
|
||||||
export let placeholder = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let id = null
|
export let id = null
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let inputRef
|
export let inputRef: HTMLElement | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let focus = false
|
let focus = false
|
||||||
|
|
||||||
const updateValue = value => {
|
const updateValue = (value: any) => {
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,19 +21,19 @@
|
||||||
focus = true
|
focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBlur = event => {
|
const onBlur = (event: any) => {
|
||||||
focus = false
|
focus = false
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInput = event => {
|
const onInput = (event: any) => {
|
||||||
if (!updateOnChange) {
|
if (!updateOnChange) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = (event: any) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,44 @@
|
||||||
<script>
|
<script lang="ts" context="module">
|
||||||
|
type O = any
|
||||||
|
type V = any
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import Picker from "./Picker.svelte"
|
import Picker from "./Picker.svelte"
|
||||||
|
import { PopoverAlignment } from "../../constants"
|
||||||
|
|
||||||
export let value = null
|
export let value: V | null = null
|
||||||
export let id = null
|
export let id: string | undefined = undefined
|
||||||
export let placeholder = "Choose an option"
|
export let placeholder: string | boolean = "Choose an option"
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let options = []
|
export let options: O[] = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = (option: O, _index?: number) => `${option}`
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = (option: O, _index?: number) =>
|
||||||
export let getOptionIcon = () => null
|
option as unknown as V
|
||||||
export let getOptionColour = () => null
|
export let getOptionIcon = (option: O, _index?: number) =>
|
||||||
export let getOptionSubtitle = () => null
|
option as unknown as string
|
||||||
export let compare = null
|
export let getOptionColour = (option: O, _index?: number) =>
|
||||||
|
option as unknown as string
|
||||||
|
export let getOptionSubtitle = (option: O, _index?: number) =>
|
||||||
|
option as unknown as string
|
||||||
|
export let compare = (option: O, value: V) => option === value
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled = (option: O, _index?: number) =>
|
||||||
export let readonly = false
|
option as unknown as boolean
|
||||||
export let quiet = false
|
export let readonly: boolean = false
|
||||||
export let autoWidth = false
|
export let quiet: boolean = false
|
||||||
export let autocomplete = false
|
export let autoWidth: boolean = false
|
||||||
export let sort = false
|
export let autocomplete: boolean = false
|
||||||
export let align
|
export let sort: boolean = false
|
||||||
export let footer = null
|
export let align: PopoverAlignment | undefined = PopoverAlignment.Left
|
||||||
export let open = false
|
export let footer: string | undefined = undefined
|
||||||
export let tag = null
|
export let open: boolean = false
|
||||||
export let searchTerm = null
|
export let searchTerm: string | undefined = undefined
|
||||||
export let loading
|
export let loading: boolean | undefined = undefined
|
||||||
export let onOptionMouseenter = () => {}
|
export let onOptionMouseenter = () => {}
|
||||||
export let onOptionMouseleave = () => {}
|
export let onOptionMouseleave = () => {}
|
||||||
|
export let customPopoverHeight: string | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -35,24 +46,28 @@
|
||||||
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
$: fieldIcon = getFieldAttribute(getOptionIcon, value, options)
|
||||||
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
$: fieldColour = getFieldAttribute(getOptionColour, value, options)
|
||||||
|
|
||||||
function compareOptionAndValue(option, value) {
|
function compareOptionAndValue(option: O, value: V) {
|
||||||
return typeof compare === "function"
|
return typeof compare === "function"
|
||||||
? compare(option, value)
|
? compare(option, value)
|
||||||
: option === value
|
: option === value
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldAttribute = (getAttribute, value, options) => {
|
const getFieldAttribute = (getAttribute: any, value: V[], options: O[]) => {
|
||||||
// Wait for options to load if there is a value but no options
|
// Wait for options to load if there is a value but no options
|
||||||
if (!options?.length) {
|
if (!options?.length) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
const index = options.findIndex((option, idx) =>
|
const index = options.findIndex((option: any, idx: number) =>
|
||||||
compareOptionAndValue(getOptionValue(option, idx), value)
|
compareOptionAndValue(getOptionValue(option, idx), value)
|
||||||
)
|
)
|
||||||
return index !== -1 ? getAttribute(options[index], index) : null
|
return index !== -1 ? getAttribute(options[index], index) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFieldText = (value, options, placeholder) => {
|
const getFieldText = (
|
||||||
|
value: any,
|
||||||
|
options: any,
|
||||||
|
placeholder: boolean | string
|
||||||
|
) => {
|
||||||
if (value == null || value === "") {
|
if (value == null || value === "") {
|
||||||
// Explicit false means use no placeholder and allow an empty fields
|
// Explicit false means use no placeholder and allow an empty fields
|
||||||
if (placeholder === false) {
|
if (placeholder === false) {
|
||||||
|
@ -67,7 +82,7 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectOption = value => {
|
const selectOption = (value: V) => {
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
@ -98,14 +113,14 @@
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{sort}
|
{sort}
|
||||||
{tag}
|
|
||||||
{onOptionMouseenter}
|
{onOptionMouseenter}
|
||||||
{onOptionMouseleave}
|
{onOptionMouseleave}
|
||||||
isPlaceholder={value == null || value === ""}
|
isPlaceholder={value == null || value === ""}
|
||||||
placeholderOption={placeholder === false
|
placeholderOption={placeholder === false
|
||||||
? null
|
? undefined
|
||||||
: placeholder || "Choose an option"}
|
: placeholder || "Choose an option"}
|
||||||
isOptionSelected={option => compareOptionAndValue(option, value)}
|
isOptionSelected={option => compareOptionAndValue(option, value)}
|
||||||
onSelectOption={selectOption}
|
onSelectOption={selectOption}
|
||||||
{loading}
|
{loading}
|
||||||
|
{customPopoverHeight}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
import { createEventDispatcher, onMount, tick } from "svelte"
|
import { createEventDispatcher, onMount, tick } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let align
|
export let align: "left" | "right" | "center" | undefined = undefined
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
export let autocomplete = null
|
export let autocomplete: boolean | undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let field
|
let field: any
|
||||||
let focus = false
|
let focus = false
|
||||||
|
|
||||||
const updateValue = newValue => {
|
const updateValue = (newValue: any) => {
|
||||||
if (readonly || disabled) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
focus = true
|
focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const onBlur = event => {
|
const onBlur = (event: any) => {
|
||||||
if (readonly || disabled) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -45,14 +45,14 @@
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInput = event => {
|
const onInput = (event: any) => {
|
||||||
if (readonly || !updateOnChange || disabled) {
|
if (readonly || !updateOnChange || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateValue(event.target.value)
|
updateValue(event.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateValueOnEnter = event => {
|
const updateValueOnEnter = (event: any) => {
|
||||||
if (readonly || disabled) {
|
if (readonly || disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -61,13 +61,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInputMode = type => {
|
const getInputMode = (type: any) => {
|
||||||
if (type === "bigint") {
|
if (type === "bigint") {
|
||||||
return "numeric"
|
return "numeric"
|
||||||
}
|
}
|
||||||
return type === "number" ? "decimal" : "text"
|
return type === "number" ? "decimal" : "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: autocompleteValue =
|
||||||
|
typeof autocomplete === "boolean"
|
||||||
|
? autocomplete
|
||||||
|
? "on"
|
||||||
|
: "off"
|
||||||
|
: undefined
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
focus = autofocus
|
focus = autofocus
|
||||||
|
@ -104,7 +111,7 @@
|
||||||
class="spectrum-Textfield-input"
|
class="spectrum-Textfield-input"
|
||||||
style={align ? `text-align: ${align};` : ""}
|
style={align ? `text-align: ${align};` : ""}
|
||||||
inputmode={getInputMode(type)}
|
inputmode={getInputMode(type)}
|
||||||
{autocomplete}
|
autocomplete={autocompleteValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
import FieldLabel from "./FieldLabel.svelte"
|
import FieldLabel from "./FieldLabel.svelte"
|
||||||
import Icon from "../Icon/Icon.svelte"
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
|
||||||
export let id = null
|
export let id: string | undefined = undefined
|
||||||
export let label = null
|
export let label: string | undefined = undefined
|
||||||
export let labelPosition = "above"
|
export let labelPosition: string = "above"
|
||||||
export let error = null
|
export let error: string | undefined = undefined
|
||||||
export let helpText = null
|
export let helpText: string | undefined = undefined
|
||||||
export let tooltip = ""
|
export let tooltip: string | undefined = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
|
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import TextField from "./Core/TextField.svelte"
|
import TextField from "./Core/TextField.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = null
|
export let value: any = undefined
|
||||||
export let label = null
|
export let label: string | undefined = undefined
|
||||||
export let labelPosition = "above"
|
export let labelPosition = "above"
|
||||||
export let placeholder = null
|
export let placeholder: string | undefined = undefined
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
export let error: string | undefined = undefined
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus: boolean | undefined = undefined
|
||||||
export let autocomplete
|
export let autocomplete: boolean | undefined = undefined
|
||||||
export let helpText = null
|
export let helpText: string | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = (e: any) => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@
|
||||||
<Field {helpText} {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<TextField
|
<TextField
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{value}
|
{value}
|
||||||
|
|
|
@ -1,44 +1,54 @@
|
||||||
<script>
|
<script lang="ts" context="module">
|
||||||
|
type O = any
|
||||||
|
type V = any
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import Select from "./Core/Select.svelte"
|
import Select from "./Core/Select.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { PopoverAlignment } from "../constants"
|
||||||
|
|
||||||
export let value = null
|
export let value: V | undefined = undefined
|
||||||
export let label = undefined
|
export let label: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let readonly = false
|
export let readonly: boolean = false
|
||||||
export let labelPosition = "above"
|
export let labelPosition: string = "above"
|
||||||
export let error = null
|
export let error: string | undefined = undefined
|
||||||
export let placeholder = "Choose an option"
|
export let placeholder: string | boolean = "Choose an option"
|
||||||
export let options = []
|
export let options: O[] = []
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = (option: O, _index?: number) =>
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
extractProperty(option, "label")
|
||||||
export let getOptionSubtitle = option => option?.subtitle
|
export let getOptionValue = (option: O, _index?: number) =>
|
||||||
export let getOptionIcon = option => option?.icon
|
extractProperty(option, "value")
|
||||||
export let getOptionColour = option => option?.colour
|
export let getOptionSubtitle = (option: O, _index?: number) =>
|
||||||
|
option?.subtitle
|
||||||
|
export let getOptionIcon = (option: O, _index?: number) => option?.icon
|
||||||
|
export let getOptionColour = (option: O, _index?: number) => option?.colour
|
||||||
export let useOptionIconImage = false
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled = undefined
|
export let isOptionEnabled:
|
||||||
export let quiet = false
|
| ((_option: O, _index?: number) => boolean)
|
||||||
export let autoWidth = false
|
| undefined = undefined
|
||||||
export let sort = false
|
export let quiet: boolean = false
|
||||||
export let tooltip = ""
|
export let autoWidth: boolean = false
|
||||||
export let autocomplete = false
|
export let sort: boolean = false
|
||||||
export let customPopoverHeight = undefined
|
export let tooltip: string | undefined = undefined
|
||||||
export let align = undefined
|
export let autocomplete: boolean = false
|
||||||
export let footer = null
|
export let customPopoverHeight: string | undefined = undefined
|
||||||
export let tag = null
|
export let align: PopoverAlignment | undefined = PopoverAlignment.Left
|
||||||
export let helpText = null
|
export let footer: string | undefined = undefined
|
||||||
export let compare = undefined
|
export let helpText: string | undefined = undefined
|
||||||
|
export let compare: any = undefined
|
||||||
export let onOptionMouseenter = () => {}
|
export let onOptionMouseenter = () => {}
|
||||||
export let onOptionMouseleave = () => {}
|
export let onOptionMouseleave = () => {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = (e: CustomEvent<any>) => {
|
||||||
value = e.detail
|
value = e.detail
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractProperty = (value, property) => {
|
const extractProperty = (value: any, property: any) => {
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
return value[property]
|
return value[property]
|
||||||
}
|
}
|
||||||
|
@ -49,7 +59,6 @@
|
||||||
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||||
<Select
|
<Select
|
||||||
{quiet}
|
{quiet}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{value}
|
{value}
|
||||||
|
@ -68,7 +77,6 @@
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{customPopoverHeight}
|
{customPopoverHeight}
|
||||||
{tag}
|
|
||||||
{compare}
|
{compare}
|
||||||
{onOptionMouseenter}
|
{onOptionMouseenter}
|
||||||
{onOptionMouseleave}
|
{onOptionMouseleave}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import "@spectrum-css/statuslight"
|
import "@spectrum-css/statuslight"
|
||||||
|
|
||||||
export let size = "M"
|
export let size: string = "M"
|
||||||
export let celery = false
|
export let celery: boolean = false
|
||||||
export let yellow = false
|
export let yellow: boolean = false
|
||||||
export let fuchsia = false
|
export let fuchsia: boolean = false
|
||||||
export let indigo = false
|
export let indigo: boolean = false
|
||||||
export let seafoam = false
|
export let seafoam: boolean = false
|
||||||
export let chartreuse = false
|
export let chartreuse: boolean = false
|
||||||
export let magenta = false
|
export let magenta: boolean = false
|
||||||
export let purple = false
|
export let purple: boolean = false
|
||||||
export let neutral = false
|
export let neutral: boolean = false
|
||||||
export let info = false
|
export let info: boolean = false
|
||||||
export let positive = false
|
export let positive: boolean = false
|
||||||
export let notice = false
|
export let notice: boolean = false
|
||||||
export let negative = false
|
export let negative: boolean = false
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let active = false
|
export let active: boolean = false
|
||||||
export let color = null
|
export let color: string | undefined = undefined
|
||||||
export let square = false
|
export let square: boolean = false
|
||||||
export let hoverable = false
|
export let hoverable: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
|
import { Icon, Input, Drawer, Button } from "@budibase/bbui"
|
||||||
import {
|
import {
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
|
@ -10,25 +10,25 @@
|
||||||
import { builderStore } from "@/stores/builder"
|
import { builderStore } from "@/stores/builder"
|
||||||
|
|
||||||
export let panel = ClientBindingPanel
|
export let panel = ClientBindingPanel
|
||||||
export let value = ""
|
export let value: any = ""
|
||||||
export let bindings = []
|
export let bindings: any[] = []
|
||||||
export let title
|
export let title: string | undefined = undefined
|
||||||
export let placeholder
|
export let placeholder: string | undefined = undefined
|
||||||
export let label
|
export let label: string | undefined = undefined
|
||||||
export let disabled = false
|
export let disabled: boolean = false
|
||||||
export let allowHBS = true
|
export let allowHBS: boolean = true
|
||||||
export let allowJS = true
|
export let allowJS: boolean = true
|
||||||
export let allowHelpers = true
|
export let allowHelpers: boolean = true
|
||||||
export let updateOnChange = true
|
export let updateOnChange: boolean = true
|
||||||
export let key
|
export let key: string | null = null
|
||||||
export let disableBindings = false
|
export let disableBindings: boolean = false
|
||||||
export let forceModal = false
|
export let forceModal: boolean = false
|
||||||
export let context = null
|
export let context = null
|
||||||
export let autocomplete
|
export let autocomplete: boolean | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let bindingDrawer
|
let bindingDrawer: any
|
||||||
let currentVal = value
|
let currentVal = value
|
||||||
|
|
||||||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
const saveBinding = () => {
|
const saveBinding = () => {
|
||||||
onChange(tempValue)
|
onChange(tempValue)
|
||||||
onBlur()
|
onBlur()
|
||||||
builderStore.propertyFocus()
|
builderStore.propertyFocus(null)
|
||||||
bindingDrawer.hide()
|
bindingDrawer.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
save: saveBinding,
|
save: saveBinding,
|
||||||
})
|
})
|
||||||
|
|
||||||
const onChange = value => {
|
const onChange = (value: any) => {
|
||||||
currentVal = readableToRuntimeBinding(bindings, value)
|
currentVal = readableToRuntimeBinding(bindings, value)
|
||||||
dispatch("change", currentVal)
|
dispatch("change", currentVal)
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,8 @@
|
||||||
dispatch("blur", currentVal)
|
dispatch("blur", currentVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDrawerHide = e => {
|
const onDrawerHide = (e: any) => {
|
||||||
builderStore.propertyFocus()
|
builderStore.propertyFocus(null)
|
||||||
dispatch("drawerHide", e.detail)
|
dispatch("drawerHide", e.detail)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "@/dataBinding"
|
} from "@/dataBinding"
|
||||||
import { builderStore } from "@/stores/builder"
|
import { builderStore } from "@/stores/builder"
|
||||||
import { onDestroy } from "svelte"
|
|
||||||
|
|
||||||
export let label = ""
|
export let label = ""
|
||||||
export let labelHidden = false
|
export let labelHidden = false
|
||||||
|
@ -32,7 +31,7 @@
|
||||||
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
$: safeValue = getSafeValue(value, defaultValue, allBindings)
|
||||||
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
|
||||||
|
|
||||||
$: if (!Array.isArray(value)) {
|
$: if (value) {
|
||||||
highlightType =
|
highlightType =
|
||||||
highlightedProp?.key === key ? `highlighted-${highlightedProp?.type}` : ""
|
highlightedProp?.key === key ? `highlighted-${highlightedProp?.type}` : ""
|
||||||
}
|
}
|
||||||
|
@ -75,12 +74,6 @@
|
||||||
? defaultValue
|
? defaultValue
|
||||||
: enriched
|
: enriched
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
if (highlightedProp) {
|
|
||||||
builderStore.highlightSetting(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1159,10 +1159,17 @@ export const buildFormSchema = (component, asset) => {
|
||||||
* Returns an array of the keys of any state variables which are set anywhere
|
* Returns an array of the keys of any state variables which are set anywhere
|
||||||
* in the app.
|
* in the app.
|
||||||
*/
|
*/
|
||||||
export const getAllStateVariables = () => {
|
export const getAllStateVariables = screen => {
|
||||||
// Find all button action settings in all components
|
let assets = []
|
||||||
|
if (screen) {
|
||||||
|
// only include state variables from a specific screen
|
||||||
|
assets.push(screen)
|
||||||
|
} else {
|
||||||
|
// otherwise include state variables from all screens
|
||||||
|
assets = getAllAssets()
|
||||||
|
}
|
||||||
let eventSettings = []
|
let eventSettings = []
|
||||||
getAllAssets().forEach(asset => {
|
assets.forEach(asset => {
|
||||||
findAllMatchingComponents(asset.props, component => {
|
findAllMatchingComponents(asset.props, component => {
|
||||||
const settings = componentStore.getComponentSettings(component._component)
|
const settings = componentStore.getComponentSettings(component._component)
|
||||||
const nestedTypes = [
|
const nestedTypes = [
|
||||||
|
@ -1214,11 +1221,17 @@ export const getAllStateVariables = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add on load settings from screens
|
// Add on load settings from screens
|
||||||
|
if (screen) {
|
||||||
|
if (screen.onLoad) {
|
||||||
|
eventSettings.push(screen.onLoad)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
get(screenStore).screens.forEach(screen => {
|
get(screenStore).screens.forEach(screen => {
|
||||||
if (screen.onLoad) {
|
if (screen.onLoad) {
|
||||||
eventSettings.push(screen.onLoad)
|
eventSettings.push(screen.onLoad)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Extract all state keys from any "update state" actions in each setting
|
// Extract all state keys from any "update state" actions in each setting
|
||||||
let bindingSet = new Set()
|
let bindingSet = new Set()
|
||||||
|
|
|
@ -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,17 @@
|
||||||
$: id = $selectedComponent?._id
|
$: id = $selectedComponent?._id
|
||||||
$: id, (section = tabs[0])
|
$: id, (section = tabs[0])
|
||||||
$: componentName = getComponentName(componentInstance)
|
$: componentName = getComponentName(componentInstance)
|
||||||
|
|
||||||
|
$: highlightedSetting = $builderStore.highlightedSetting
|
||||||
|
$: if (highlightedSetting) {
|
||||||
|
if (highlightedSetting.key === "_conditions") {
|
||||||
|
section = "conditions"
|
||||||
|
} else if (highlightedSetting.key === "_styles") {
|
||||||
|
section = "styles"
|
||||||
|
} else if (highlightedSetting.key === "_settings") {
|
||||||
|
section = "settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
</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,8 @@
|
||||||
let tempValue
|
let tempValue
|
||||||
let drawer
|
let drawer
|
||||||
|
|
||||||
|
$: highlighted = $builderStore.highlightedSetting?.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 +55,9 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DetailSummary name={"Conditions"} collapsible={false}>
|
<DetailSummary name={"Conditions"} collapsible={false}>
|
||||||
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
<div class:highlighted>
|
||||||
|
<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 +66,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>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
readableToRuntimeBinding,
|
readableToRuntimeBinding,
|
||||||
runtimeToReadableBinding,
|
runtimeToReadableBinding,
|
||||||
} from "@/dataBinding"
|
} from "@/dataBinding"
|
||||||
|
import { builderStore } from "@/stores/builder"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
export let componentDefinition
|
export let componentDefinition
|
||||||
|
@ -32,6 +33,8 @@
|
||||||
|
|
||||||
$: icon = componentDefinition?.icon
|
$: icon = componentDefinition?.icon
|
||||||
|
|
||||||
|
$: highlighted = $builderStore.highlightedSetting?.key === "_styles"
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
tempValue = runtimeToReadableBinding(
|
tempValue = runtimeToReadableBinding(
|
||||||
bindings,
|
bindings,
|
||||||
|
@ -55,7 +58,7 @@
|
||||||
name={`Custom CSS${componentInstance?._styles?.custom ? " *" : ""}`}
|
name={`Custom CSS${componentInstance?._styles?.custom ? " *" : ""}`}
|
||||||
collapsible={false}
|
collapsible={false}
|
||||||
>
|
>
|
||||||
<div>
|
<div class:highlighted>
|
||||||
<ActionButton on:click={openDrawer}>Edit custom CSS</ActionButton>
|
<ActionButton on:click={openDrawer}>Edit custom CSS</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
|
@ -97,4 +100,16 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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>
|
</style>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import ComponentList from "./ComponentList/index.svelte"
|
import ComponentList from "./ComponentList/index.svelte"
|
||||||
import { getHorizontalResizeActions } from "@/components/common/resizable"
|
import { getHorizontalResizeActions } from "@/components/common/resizable"
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
|
import StatePanel from "./StatePanel.svelte"
|
||||||
import BindingsPanel from "./BindingsPanel.svelte"
|
import BindingsPanel from "./BindingsPanel.svelte"
|
||||||
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
{:else if activeTab === Tabs.Bindings}
|
{:else if activeTab === Tabs.Bindings}
|
||||||
<BindingsPanel />
|
<BindingsPanel />
|
||||||
{:else if activeTab === Tabs.State}
|
{:else if activeTab === Tabs.State}
|
||||||
<div class="tab-content">State</div>
|
<div class="tab-content"><StatePanel /></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="divider">
|
<div class="divider">
|
||||||
|
|
|
@ -1,13 +1,342 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { ActionButton, Modal, ModalContent } from "@budibase/bbui"
|
import { onMount } from "svelte"
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
|
import type { Component } from "@budibase/types"
|
||||||
|
import { getAllStateVariables, getBindableProperties } from "@/dataBinding"
|
||||||
|
import {
|
||||||
|
componentStore,
|
||||||
|
selectedScreen,
|
||||||
|
builderStore,
|
||||||
|
previewStore,
|
||||||
|
} from "@/stores/builder"
|
||||||
|
import {
|
||||||
|
decodeJSBinding,
|
||||||
|
findHBSBlocks,
|
||||||
|
isJSBinding,
|
||||||
|
processStringSync,
|
||||||
|
} from "@budibase/string-templates"
|
||||||
|
import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
let modal
|
interface ComponentUsingState {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
settings: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
$: keyOptions = getAllStateVariables($selectedScreen)
|
||||||
|
$: bindings = getBindableProperties(
|
||||||
|
$selectedScreen,
|
||||||
|
$componentStore.selectedComponentId
|
||||||
|
)
|
||||||
|
|
||||||
|
let selectedKey: string | undefined = undefined
|
||||||
|
let componentsUsingState: ComponentUsingState[] = []
|
||||||
|
let componentsUpdatingState: ComponentUsingState[] = []
|
||||||
|
let editorValue: string = ""
|
||||||
|
let previousScreenId: string | undefined = undefined
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const screenChanged =
|
||||||
|
$selectedScreen && $selectedScreen._id !== previousScreenId
|
||||||
|
const previewContext = $previewStore.selectedComponentContext || {}
|
||||||
|
|
||||||
|
if (screenChanged) {
|
||||||
|
selectedKey = keyOptions[0]
|
||||||
|
componentsUsingState = []
|
||||||
|
componentsUpdatingState = []
|
||||||
|
editorValue = ""
|
||||||
|
previousScreenId = $selectedScreen._id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyOptions.length > 0 && !keyOptions.includes(selectedKey)) {
|
||||||
|
selectedKey = keyOptions[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedKey) {
|
||||||
|
searchComponents(selectedKey)
|
||||||
|
editorValue = previewContext.state?.[selectedKey] ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findComponentsUpdatingState = (
|
||||||
|
component: Component,
|
||||||
|
stateKey: string
|
||||||
|
): ComponentUsingState[] => {
|
||||||
|
let foundComponents: ComponentUsingState[] = []
|
||||||
|
|
||||||
|
const eventHandlerProps = [
|
||||||
|
"onClick",
|
||||||
|
"onChange",
|
||||||
|
"onRowClick",
|
||||||
|
"onChange",
|
||||||
|
"buttonOnClick",
|
||||||
|
]
|
||||||
|
|
||||||
|
eventHandlerProps.forEach(eventType => {
|
||||||
|
const handlers = component[eventType]
|
||||||
|
if (Array.isArray(handlers)) {
|
||||||
|
handlers.forEach(handler => {
|
||||||
|
if (
|
||||||
|
handler["##eventHandlerType"] === "Update State" &&
|
||||||
|
handler.parameters?.key === stateKey
|
||||||
|
) {
|
||||||
|
foundComponents.push({
|
||||||
|
id: component._id!,
|
||||||
|
name: component._instanceName,
|
||||||
|
settings: [eventType],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (component._children) {
|
||||||
|
for (let child of component._children) {
|
||||||
|
foundComponents = [
|
||||||
|
...foundComponents,
|
||||||
|
...findComponentsUpdatingState(child, stateKey),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundComponents
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasStateBinding = (value: string, stateKey: 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 getSettingsWithState = (component: any, stateKey: string): string[] => {
|
||||||
|
const settingsWithState: string[] = []
|
||||||
|
for (const [setting, value] of Object.entries(component)) {
|
||||||
|
if (typeof value === "string" && hasStateBinding(value, stateKey)) {
|
||||||
|
settingsWithState.push(setting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return settingsWithState
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkConditions = (conditions: any[], stateKey: string): boolean => {
|
||||||
|
return conditions.some(condition =>
|
||||||
|
[condition.referenceValue, condition.newValue].some(
|
||||||
|
value => typeof value === "string" && hasStateBinding(value, stateKey)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkStyles = (styles: any, stateKey: string): boolean => {
|
||||||
|
return (
|
||||||
|
typeof styles?.custom === "string" &&
|
||||||
|
hasStateBinding(styles.custom, stateKey)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const findComponentsUsingState = (
|
||||||
|
component: any,
|
||||||
|
stateKey: string
|
||||||
|
): ComponentUsingState[] => {
|
||||||
|
let componentsUsingState: ComponentUsingState[] = []
|
||||||
|
const { _children, _styles, _conditions, ...componentSettings } = component
|
||||||
|
|
||||||
|
const settingsWithState = getSettingsWithState(componentSettings, stateKey)
|
||||||
|
settingsWithState.forEach(setting => {
|
||||||
|
componentsUsingState.push({
|
||||||
|
id: component._id,
|
||||||
|
name: `${component._instanceName} (${setting})`,
|
||||||
|
settings: [setting],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (_conditions?.length > 0 && checkConditions(_conditions, stateKey)) {
|
||||||
|
componentsUsingState.push({
|
||||||
|
id: component._id,
|
||||||
|
name: `${component._instanceName} (conditions)`,
|
||||||
|
settings: ["_conditions"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_styles && checkStyles(_styles, stateKey)) {
|
||||||
|
componentsUsingState.push({
|
||||||
|
id: component._id,
|
||||||
|
name: `${component._instanceName} (styles)`,
|
||||||
|
settings: ["_styles"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_children) {
|
||||||
|
for (let child of _children) {
|
||||||
|
componentsUsingState = [
|
||||||
|
...componentsUsingState,
|
||||||
|
...findComponentsUsingState(child, stateKey),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentsUsingState
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchComponents = (stateKey: string | undefined) => {
|
||||||
|
if (!stateKey || !$selectedScreen?.props) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentStateUpdates = findComponentsUpdatingState(
|
||||||
|
$selectedScreen.props,
|
||||||
|
stateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
componentsUsingState = findComponentsUsingState(
|
||||||
|
$selectedScreen.props,
|
||||||
|
stateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
const screenStateUpdates =
|
||||||
|
$selectedScreen?.onLoad
|
||||||
|
?.filter(
|
||||||
|
(handler: any) =>
|
||||||
|
handler["##eventHandlerType"] === "Update State" &&
|
||||||
|
handler.parameters?.key === stateKey
|
||||||
|
)
|
||||||
|
.map(() => ({
|
||||||
|
id: $selectedScreen._id!,
|
||||||
|
name: "Screen onLoad",
|
||||||
|
settings: ["onLoad"],
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
componentsUpdatingState = [...componentStateUpdates, ...screenStateUpdates]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStateKeySelect = (key: CustomEvent) => {
|
||||||
|
if (!key.detail && keyOptions.length > 0) {
|
||||||
|
throw new Error("No state key selected")
|
||||||
|
}
|
||||||
|
searchComponents(key.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickComponentLink = (component: ComponentUsingState) => {
|
||||||
|
componentStore.select(component.id)
|
||||||
|
component.settings.forEach(setting => {
|
||||||
|
builderStore.highlightSetting(setting)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStateInspectorChange = (e: CustomEvent) => {
|
||||||
|
if (!selectedKey || !$previewStore.selectedComponentContext) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateUpdate = {
|
||||||
|
[selectedKey]: processStringSync(
|
||||||
|
e.detail,
|
||||||
|
$previewStore.selectedComponentContext
|
||||||
|
),
|
||||||
|
}
|
||||||
|
previewStore.updateState(stateUpdate)
|
||||||
|
editorValue = e.detail
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
previewStore.requestComponentContext()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton on:click={modal.show}>State</ActionButton>
|
<div class="state-panel">
|
||||||
|
<Select
|
||||||
|
label="State variables"
|
||||||
|
bind:value={selectedKey}
|
||||||
|
placeholder={keyOptions.length > 0 ? false : "No state variables found"}
|
||||||
|
options={keyOptions}
|
||||||
|
on:change={handleStateKeySelect}
|
||||||
|
/>
|
||||||
|
{#if selectedKey && keyOptions.length > 0}
|
||||||
|
<DrawerBindableInput
|
||||||
|
value={editorValue}
|
||||||
|
title={`Set value for "${selectedKey}"`}
|
||||||
|
placeholder="Enter a value"
|
||||||
|
label="Set temporary value for design preview"
|
||||||
|
on:change={e => handleStateInspectorChange(e)}
|
||||||
|
{bindings}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if componentsUsingState.length > 0}
|
||||||
|
<div class="section">
|
||||||
|
<span class="text">Updates</span>
|
||||||
|
<div class="updates-section">
|
||||||
|
{#each componentsUsingState as component}
|
||||||
|
<button
|
||||||
|
class="component-link updates-colour"
|
||||||
|
on:click={() => onClickComponentLink(component)}
|
||||||
|
>
|
||||||
|
{component.name}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if componentsUpdatingState.length > 0}
|
||||||
|
<div class="section">
|
||||||
|
<span class="text">Controlled by</span>
|
||||||
|
<div class="updates-section">
|
||||||
|
{#each componentsUpdatingState as component}
|
||||||
|
<button
|
||||||
|
class="component-link controlled-by-colour"
|
||||||
|
on:click={() => onClickComponentLink(component)}
|
||||||
|
>
|
||||||
|
{component.name}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<style>
|
||||||
<ModalContent title="State" showConfirmButton={false} cancelText="Close">
|
.state-panel {
|
||||||
Some awesome state content.
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
</ModalContent>
|
display: flex;
|
||||||
</Modal>
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
color: var(--spectrum-global-color-gray-700);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updates-colour {
|
||||||
|
color: var(--bb-indigo-light);
|
||||||
|
}
|
||||||
|
.controlled-by-colour {
|
||||||
|
color: var(--spectrum-global-color-orange-700);
|
||||||
|
}
|
||||||
|
.component-link {
|
||||||
|
display: inline-block;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.component-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.updates-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
previewStore,
|
previewStore,
|
||||||
tables,
|
tables,
|
||||||
componentTreeNodesStore,
|
componentTreeNodesStore,
|
||||||
|
builderStore,
|
||||||
} from "@/stores/builder"
|
} from "@/stores/builder"
|
||||||
import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding"
|
import { buildFormSchema, getSchemaForDatasource } from "@/dataBinding"
|
||||||
import {
|
import {
|
||||||
|
@ -736,10 +737,16 @@ export class ComponentStore extends BudiStore<ComponentState> {
|
||||||
*
|
*
|
||||||
* @param {string} componentId
|
* @param {string} componentId
|
||||||
*/
|
*/
|
||||||
select(componentId: string) {
|
select(id: string) {
|
||||||
this.update(state => {
|
this.update(state => {
|
||||||
state.selectedComponentId = componentId
|
// Only clear highlights if selecting a different component
|
||||||
return state
|
if (!id.includes(state.selectedComponentId!)) {
|
||||||
|
builderStore.highlightSetting()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedComponentId: id,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,10 @@ export class PreviewStore extends BudiStore<PreviewState> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateState(data: Record<string, any>) {
|
||||||
|
this.sendEvent("builder-state", data)
|
||||||
|
}
|
||||||
|
|
||||||
requestComponentContext() {
|
requestComponentContext() {
|
||||||
this.sendEvent("request-context")
|
this.sendEvent("request-context")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
dndStore,
|
dndStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
hoverStore,
|
hoverStore,
|
||||||
|
stateStore,
|
||||||
} from "./stores"
|
} from "./stores"
|
||||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
@ -104,6 +105,9 @@ const loadBudibase = async () => {
|
||||||
hoverStore.actions.hoverComponent(data, false)
|
hoverStore.actions.hoverComponent(data, false)
|
||||||
} else if (type === "builder-meta") {
|
} else if (type === "builder-meta") {
|
||||||
builderStore.actions.setMetadata(data)
|
builderStore.actions.setMetadata(data)
|
||||||
|
} else if (type === "builder-state") {
|
||||||
|
const [[key, value]] = Object.entries(data)
|
||||||
|
stateStore.actions.setValue(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ export interface Screen extends Document {
|
||||||
props: ScreenProps
|
props: ScreenProps
|
||||||
name?: string
|
name?: string
|
||||||
pluginAdded?: boolean
|
pluginAdded?: boolean
|
||||||
|
onLoad?: EventHandler[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScreenRoutesViewOutput extends Document {
|
export interface ScreenRoutesViewOutput extends Document {
|
||||||
|
@ -36,3 +37,14 @@ export type ScreenRoutingJson = Record<
|
||||||
subpaths: Record<string, any>
|
subpaths: Record<string, any>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export interface EventHandler {
|
||||||
|
parameters: {
|
||||||
|
key: string
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
persist: any | null
|
||||||
|
}
|
||||||
|
eventHandlerType: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue