Merge pull request #6309 from Budibase/builder-focus-updates

Builder focus updates
This commit is contained in:
Andrew Kingston 2022-06-15 11:07:46 +01:00 committed by GitHub
commit eb3a7b44b6
28 changed files with 152 additions and 307 deletions

View File

@ -1,8 +1,7 @@
<script> <script>
import "@spectrum-css/actionbutton/dist/index-vars.css" import "@spectrum-css/actionbutton/dist/index-vars.css"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const context = getContext("builderFocus")
export let quiet = false export let quiet = false
export let emphasized = false export let emphasized = false
@ -14,14 +13,6 @@
export let size = "M" export let size = "M"
export let active = false export let active = false
export let fullWidth = false export let fullWidth = false
export let autofocus = false
let focus = false
let actionButton
$: focus = autofocus && actionButton !== undefined
$: if (focus) {
actionButton.focus()
}
function longPress(element) { function longPress(element) {
if (!longPressable) return if (!longPressable) return
@ -46,7 +37,6 @@
<button <button
data-cy={dataCy} data-cy={dataCy}
bind:this={actionButton}
use:longPress use:longPress
class:spectrum-ActionButton--quiet={quiet} class:spectrum-ActionButton--quiet={quiet}
class:spectrum-ActionButton--emphasized={emphasized} class:spectrum-ActionButton--emphasized={emphasized}
@ -54,17 +44,9 @@
class:fullWidth class:fullWidth
class="spectrum-ActionButton spectrum-ActionButton--size{size}" class="spectrum-ActionButton spectrum-ActionButton--size{size}"
class:active class:active
class:is-focused={focus}
{disabled} {disabled}
on:longPress on:longPress
on:click|preventDefault on:click|preventDefault
on:focus={() => {
focus = true
}}
on:blur={() => {
focus = false
if (context) context.clear()
}}
> >
{#if longPressable} {#if longPressable}
<svg <svg
@ -98,10 +80,4 @@
.active svg { .active svg {
color: var(--spectrum-global-color-blue-600); color: var(--spectrum-global-color-blue-600);
} }
button.is-focused {
border-color: var(
--spectrum-textfield-m-border-color-down,
var(--spectrum-alias-border-color-mouse-focus)
);
}
</style> </style>

View File

@ -13,7 +13,6 @@
export let options = [] export let options = []
export let getOptionLabel = option => extractProperty(option, "label") export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value") export let getOptionValue = option => extractProperty(option, "value")
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -36,7 +35,6 @@
{options} {options}
{placeholder} {placeholder}
{readonly} {readonly}
{autofocus}
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}
on:change={onChange} on:change={onChange}

View File

@ -3,29 +3,21 @@
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"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher } from "svelte"
export let value = null export let value = null
export let id = null export let id = null
export let placeholder = "Choose an option or type" export let placeholder = "Choose an option or type"
export let disabled = false export let disabled = false
export let readonly = false export let readonly = false
export let autofocus = false
export let error = null export let error = null
export let options = [] export let options = []
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
const context = getContext("builderFocus")
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let open = false let open = false
let focus = false let focus = false
let comboInput
$: focus = autofocus && comboInput !== undefined
$: if (focus) {
comboInput.focus()
}
const selectOption = value => { const selectOption = value => {
dispatch("change", value) dispatch("change", value)
@ -57,16 +49,10 @@
class:is-focused={open || focus} class:is-focused={open || focus}
> >
<input <input
bind:this={comboInput}
{id} {id}
type="text" type="text"
on:focus={() => { on:focus={() => (focus = true)}
focus = true on:blur={() => (focus = false)}
}}
on:blur={() => {
focus = false
context.clear()
}}
on:change={onType} on:change={onType}
value={value || ""} value={value || ""}
placeholder={placeholder || ""} placeholder={placeholder || ""}

View File

@ -13,7 +13,6 @@
export let readonly = false export let readonly = false
export let autocomplete = false export let autocomplete = false
export let sort = false export let sort = false
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: selectedLookupMap = getSelectedLookupMap(value) $: selectedLookupMap = getSelectedLookupMap(value)
@ -86,5 +85,4 @@
{getOptionValue} {getOptionValue}
onSelectOption={toggleOption} onSelectOption={toggleOption}
{sort} {sort}
{autofocus}
/> />

View File

@ -3,7 +3,7 @@
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"
import { fly } from "svelte/transition" import { fly } from "svelte/transition"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher } from "svelte"
import clickOutside from "../../Actions/click_outside" import clickOutside from "../../Actions/click_outside"
import Search from "./Search.svelte" import Search from "./Search.svelte"
@ -26,18 +26,9 @@
export let autoWidth = false export let autoWidth = false
export let autocomplete = false export let autocomplete = false
export let sort = false export let sort = false
export let autofocus = false
const context = getContext("builderFocus")
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let searchTerm = null let searchTerm = null
let focus = false
let pickerButton
$: focus = autofocus && pickerButton !== undefined
$: if (focus) {
pickerButton.focus()
}
$: sortedOptions = getSortedOptions(options, getOptionLabel, sort) $: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
$: filteredOptions = getFilteredOptions( $: filteredOptions = getFilteredOptions(
@ -89,24 +80,7 @@
class:is-invalid={!!error} class:is-invalid={!!error}
class:is-open={open} class:is-open={open}
aria-haspopup="listbox" aria-haspopup="listbox"
class:is-focused={focus}
bind:this={pickerButton}
on:mousedown={onClick} on:mousedown={onClick}
on:keydown={e => {
var keycode = e.key
if (focus) {
if (keycode === "Enter") {
onClick(e)
}
}
}}
on:focus={() => {
focus = true
}}
on:blur={() => {
focus = false
if (context) context.clear()
}}
> >
{#if fieldIcon} {#if fieldIcon}
<span class="icon-Placeholder-Padding"> <span class="icon-Placeholder-Padding">

View File

@ -16,7 +16,6 @@
export let autoWidth = false export let autoWidth = false
export let autocomplete = false export let autocomplete = false
export let sort = false export let sort = false
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let open = false let open = false
@ -75,7 +74,6 @@
{fieldIcon} {fieldIcon}
{autocomplete} {autocomplete}
{sort} {sort}
{autofocus}
isPlaceholder={value == null || value === ""} isPlaceholder={value == null || value === ""}
placeholderOption={placeholder} placeholderOption={placeholder}
isOptionSelected={option => option === value} isOptionSelected={option => option === value}

View File

@ -14,7 +14,6 @@
export let getOptionLabel = option => option export let getOptionLabel = option => option
export let getOptionValue = option => option export let getOptionValue = option => option
export let sort = false export let sort = false
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -36,6 +35,5 @@
{getOptionValue} {getOptionValue}
on:change={onChange} on:change={onChange}
on:click on:click
{autofocus}
/> />
</Field> </Field>

View File

@ -18,7 +18,6 @@
export let autoWidth = false export let autoWidth = false
export let sort = false export let sort = false
export let tooltip = "" export let tooltip = ""
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => { const onChange = e => {
@ -45,7 +44,6 @@
{placeholder} {placeholder}
{autoWidth} {autoWidth}
{sort} {sort}
{autofocus}
{getOptionLabel} {getOptionLabel}
{getOptionValue} {getOptionValue}
{getOptionIcon} {getOptionIcon}

View File

@ -20,7 +20,6 @@ import analytics, { Events } from "analytics"
import { import {
findComponentType, findComponentType,
findComponentParent, findComponentParent,
findComponentPath,
findClosestMatchingComponent, findClosestMatchingComponent,
findAllMatchingComponents, findAllMatchingComponents,
findComponent, findComponent,
@ -61,6 +60,7 @@ const INITIAL_FRONTEND_STATE = {
theme: "", theme: "",
customTheme: {}, customTheme: {},
previewDevice: "desktop", previewDevice: "desktop",
highlightedSettingKey: null,
} }
export const getFrontendStore = () => { export const getFrontendStore = () => {
@ -423,21 +423,6 @@ export const getFrontendStore = () => {
store.update(state => { store.update(state => {
state.currentView = "component" state.currentView = "component"
state.selectedComponentId = componentInstance._id state.selectedComponentId = componentInstance._id
const focusSetting = resolveComponentFocus(
asset?.props,
componentInstance
)
if (focusSetting) {
state.builderFocus = [
{
key: focusSetting.key,
target: state.selectedComponentId,
location: "component_settings",
},
]
}
return state return state
}) })
@ -678,33 +663,14 @@ export const getFrontendStore = () => {
}, },
}, },
}, },
} settings: {
highlight: key => {
// Determine the initial focus for newly created components store.update(state => ({
// Take into account the fact that data providers should be ...state,
// skipped if they will be inherited from the path highlightedSettingKey: key,
const resolveComponentFocus = (asset_props, componentInstance) => { }))
const definition = store.actions.components.getDefinition( },
componentInstance._component },
)
let providerIdx = -1
let required = definition.settings.filter((s, idx) => {
if (s.type === "dataProvider") {
providerIdx = idx
}
return s.required
})
if (providerIdx > -1) {
const path = findComponentPath(asset_props, componentInstance._id)
const providers = path.filter(c =>
c._component?.endsWith("/dataprovider")
)
if (providers.length) {
required = required.splice(providerIdx, 1)
}
}
return required[0]
} }
return store return store

View File

@ -18,7 +18,6 @@
export let options export let options
export let allowJS = true export let allowJS = true
export let appendBindingsAsOptions = true export let appendBindingsAsOptions = true
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let bindingDrawer let bindingDrawer
@ -62,7 +61,6 @@
on:pick={e => onChange(e.detail, true)} on:pick={e => onChange(e.detail, true)}
{placeholder} {placeholder}
options={allOptions} options={allOptions}
{autofocus}
/> />
{#if !disabled} {#if !disabled}
<div <div

View File

@ -68,7 +68,7 @@
customTheme: $store.customTheme, customTheme: $store.customTheme,
previewDevice: $store.previewDevice, previewDevice: $store.previewDevice,
messagePassing: $store.clientFeatures.messagePassing, messagePassing: $store.clientFeatures.messagePassing,
isBudibaseEvent: true isBudibaseEvent: true,
} }
$: json = JSON.stringify(previewData) $: json = JSON.stringify(previewData)
@ -148,21 +148,6 @@
} }
}) })
const resolveFocus = (data) => {
if($store.builderFocus){
const comp = $store.builderFocus.reduce((acc, item)=>{
acc = item.target
return acc
}, "")
if(data.id !== comp){
store.update(state => {
delete state.builderFocus
return state
})
}
}
}
const handleBudibaseEvent = async event => { const handleBudibaseEvent = async event => {
const { type, data } = event.data || event.detail const { type, data } = event.data || event.detail
if (!type) { if (!type) {
@ -172,7 +157,6 @@
try { try {
if (type === "select-component" && data.id) { if (type === "select-component" && data.id) {
store.actions.components.select({ _id: data.id }) store.actions.components.select({ _id: data.id })
resolveFocus(data)
} else if (type === "update-prop") { } else if (type === "update-prop") {
await store.actions.components.updateProp(data.prop, data.value) await store.actions.components.updateProp(data.prop, data.value)
} else if (type === "delete-component" && data.id) { } else if (type === "delete-component" && data.id) {
@ -206,12 +190,18 @@
store.actions.components.copy(source, true) store.actions.components.copy(source, true)
await store.actions.components.paste(destination, data.mode) await store.actions.components.paste(destination, data.mode)
} }
} else if(type == "builder-focus") { } else if (type === "highlight-setting") {
store.update(state => ({ store.actions.settings.highlight(data.setting)
...state,
builderFocus : // Also scroll setting into view
[...data] const selector = `[data-cy="${data.setting}-prop-control"`
})) const element = document.querySelector(selector)?.parentElement
if (element) {
element.scrollIntoView({
behavior: "smooth",
block: "center",
})
}
} else { } else {
console.warn(`Client sent unknown event type: ${type}`) console.warn(`Client sent unknown event type: ${type}`)
} }

View File

@ -73,15 +73,6 @@
return true return true
} }
const isFocused = setting => {
if (!$store.builderFocus) {
return false
}
return (
setting.required === true && $store.builderFocus[0].key === setting.key
)
}
</script> </script>
{#each sections as section, idx (section.name)} {#each sections as section, idx (section.name)}
@ -102,10 +93,11 @@
control={getComponentForSettingType(setting.type)} control={getComponentForSettingType(setting.type)}
label={setting.label} label={setting.label}
key={setting.key} key={setting.key}
value={componentInstance[setting.key] ?? value={componentInstance[setting.key]}
componentInstance[setting.key]?.defaultValue} defaultValue={setting.defaultValue}
nested={setting.nested} nested={setting.nested}
onChange={val => updateProp(setting.key, val)} onChange={val => updateProp(setting.key, val)}
highlighted={$store.highlightedSettingKey === setting.key}
props={{ props={{
options: setting.options || [], options: setting.options || [],
placeholder: setting.placeholder || null, placeholder: setting.placeholder || null,
@ -116,7 +108,6 @@
{componentBindings} {componentBindings}
{componentInstance} {componentInstance}
{componentDefinition} {componentDefinition}
autofocus={isFocused(setting)}
/> />
{/if} {/if}
{/each} {/each}

View File

@ -6,7 +6,6 @@
import { createEventDispatcher, onMount } from "svelte" import { createEventDispatcher, onMount } from "svelte"
export let value export let value
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const getValue = component => `{{ literal ${makePropSafe(component._id)} }}` const getValue = component => `{{ literal ${makePropSafe(component._id)} }}`
@ -25,7 +24,6 @@
<Select <Select
{value} {value}
{autofocus}
placeholder={null} placeholder={null}
on:change on:change
options={providers} options={providers}

View File

@ -25,7 +25,6 @@
export let otherSources export let otherSources
export let showAllQueries export let showAllQueries
export let bindings = [] export let bindings = []
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const arrayTypes = ["attachment", "array"] const arrayTypes = ["attachment", "array"]
@ -159,7 +158,6 @@
value={text} value={text}
options={[text]} options={[text]}
on:click={dropdownRight.show} on:click={dropdownRight.show}
{autofocus}
/> />
{#if value?.type === "query"} {#if value?.type === "query"}
<i class="ri-settings-5-line" on:click={openQueryParamsDrawer} /> <i class="ri-settings-5-line" on:click={openQueryParamsDrawer} />

View File

@ -10,7 +10,6 @@
export let componentInstance = {} export let componentInstance = {}
export let value = "" export let value = ""
export let placeholder export let placeholder
export let autofocus = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($currentAsset, componentInstance) $: datasource = getDatasourceForProvider($currentAsset, componentInstance)
@ -38,10 +37,4 @@
} }
</script> </script>
<Select <Select {placeholder} value={boundValue} on:change={onChange} {options} />
{placeholder}
value={boundValue}
on:change={onChange}
{options}
{autofocus}
/>

View File

@ -10,7 +10,6 @@
export let componentInstance export let componentInstance
export let value export let value
export let type export let type
export let autofocus = false
$: form = findClosestMatchingComponent( $: form = findClosestMatchingComponent(
$currentAsset?.props, $currentAsset?.props,
@ -41,4 +40,4 @@
} }
</script> </script>
<Combobox on:change {value} {options} {autofocus} /> <Combobox on:change {value} {options} />

View File

@ -15,7 +15,6 @@
export let value = "" export let value = ""
export let maxIconsPerPage = 30 export let maxIconsPerPage = 30
export let autofocus = false
let searchTerm = "" let searchTerm = ""
let selectedLetter = "A" let selectedLetter = "A"
@ -118,7 +117,7 @@
</script> </script>
<div bind:this={buttonAnchor}> <div bind:this={buttonAnchor}>
<ActionButton on:click={dropdown.show} {autofocus}> <ActionButton on:click={dropdown.show}>
{displayValue} {displayValue}
</ActionButton> </ActionButton>
</div> </div>

View File

@ -10,7 +10,6 @@
export let componentInstance = {} export let componentInstance = {}
export let value = "" export let value = ""
export let placeholder export let placeholder
export let autofocus
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($currentAsset, componentInstance) $: datasource = getDatasourceForProvider($currentAsset, componentInstance)
@ -32,10 +31,4 @@
} }
</script> </script>
<Multiselect <Multiselect {placeholder} value={boundValue} on:change={setValue} {options} />
{placeholder}
value={boundValue}
on:change={setValue}
{options}
{autofocus}
/>

View File

@ -4,8 +4,8 @@
readableToRuntimeBinding, readableToRuntimeBinding,
runtimeToReadableBinding, runtimeToReadableBinding,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import { setContext } from "svelte"
import { store } from "builderStore" import { store } from "builderStore"
import { onDestroy } from "svelte"
export let label = "" export let label = ""
export let componentInstance = {} export let componentInstance = {}
@ -13,15 +13,17 @@
export let key = "" export let key = ""
export let type = "" export let type = ""
export let value = null export let value = null
export let defaultValue = null
export let props = {} export let props = {}
export let onChange = () => {} export let onChange = () => {}
export let bindings = [] export let bindings = []
export let componentBindings = [] export let componentBindings = []
export let nested = false export let nested = false
export let autofocus = false export let highlighted = false
$: nullishValue = value == null || value === ""
$: allBindings = getAllBindings(bindings, componentBindings, nested) $: allBindings = getAllBindings(bindings, componentBindings, nested)
$: safeValue = getSafeValue(value, props.defaultValue, allBindings) $: safeValue = getSafeValue(value, defaultValue, allBindings)
$: tempValue = safeValue $: tempValue = safeValue
$: replaceBindings = val => readableToRuntimeBinding(allBindings, val) $: replaceBindings = val => readableToRuntimeBinding(allBindings, val)
@ -64,30 +66,18 @@
: enriched : enriched
} }
setContext("builderFocus", { onDestroy(() => {
clear: () => { if (highlighted) {
if (!$store?.builderFocus) { store.actions.settings.highlight(null)
return
} }
store.update(state => {
const updatedFocus = $store?.builderFocus?.filter(focus => {
return (
focus.location === "component_settings" &&
focus.target !== componentInstance._id
)
})
if (updatedFocus?.length > 0) {
state.builderFocus = updatedFocus
} else {
delete state.builderFocus
}
return state
})
},
}) })
</script> </script>
<div class="property-control" data-cy={`setting-${key}`}> <div
class="property-control"
class:highlighted={highlighted && nullishValue}
data-cy={`setting-${key}`}
>
{#if type !== "boolean" && label} {#if type !== "boolean" && label}
<div class="label"> <div class="label">
<Label>{label}</Label> <Label>{label}</Label>
@ -107,7 +97,6 @@
{key} {key}
{type} {type}
{...props} {...props}
{autofocus}
/> />
</div> </div>
</div> </div>
@ -119,6 +108,14 @@
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
transition: background 130ms ease-out, border-color 130ms ease-out;
border-left: 4px solid transparent;
margin: -6px calc(-1 * var(--spacing-xl));
padding: 6px var(--spacing-xl) 6px calc(var(--spacing-xl) - 4px);
}
.property-control.highlighted {
background: var(--spectrum-global-color-gray-300);
border-color: var(--spectrum-global-color-blue-400);
} }
.label { .label {
padding-bottom: var(--spectrum-global-dimension-size-65); padding-bottom: var(--spectrum-global-dimension-size-65);

View File

@ -4,7 +4,6 @@
export let value export let value
export let bindings export let bindings
export let autofocus
$: urlOptions = $store.screens $: urlOptions = $store.screens
.map(screen => screen.routing?.route) .map(screen => screen.routing?.route)
@ -17,5 +16,4 @@
on:change on:change
options={urlOptions} options={urlOptions}
appendBindingsAsOptions={false} appendBindingsAsOptions={false}
{autofocus}
/> />

View File

@ -115,7 +115,6 @@
control={def.control} control={def.control}
label={def.label} label={def.label}
key={def.key} key={def.key}
error="asdasds"
value={deepGet($currentAsset, def.key)} value={deepGet($currentAsset, def.key)}
onChange={val => setAssetProps(def.key, val, def.parser, def.validate)} onChange={val => setAssetProps(def.key, val, def.parser, def.validate)}
{bindings} {bindings}

View File

@ -274,7 +274,8 @@
{ {
"type": "text", "type": "text",
"label": "Text", "label": "Text",
"key": "text" "key": "text",
"defaultValue": "New Button"
}, },
{ {
"type": "select", "type": "select",
@ -1154,15 +1155,13 @@
{ {
"type": "text", "type": "text",
"label": "Text", "label": "Text",
"key": "text", "key": "text"
"required": true
}, },
{ {
"type": "url", "type": "url",
"label": "URL", "label": "URL",
"key": "url", "key": "url",
"placeholder": "/screen", "placeholder": "/screen"
"required": true
}, },
{ {
"type": "boolean", "type": "boolean",

View File

@ -23,6 +23,7 @@
import Manifest from "manifest.json" import Manifest from "manifest.json"
import { getActiveConditions, reduceConditionActions } from "utils/conditions" import { getActiveConditions, reduceConditionActions } from "utils/conditions"
import Placeholder from "components/app/Placeholder.svelte" import Placeholder from "components/app/Placeholder.svelte"
import ComponentPlaceholder from "components/app/ComponentPlaceholder.svelte"
export let instance = {} export let instance = {}
export let isLayout = false export let isLayout = false
@ -81,6 +82,7 @@
let definition let definition
let settingsDefinition let settingsDefinition
let settingsDefinitionMap let settingsDefinitionMap
let missingRequiredSettings = false
// Set up initial state for each new component instance // Set up initial state for each new component instance
$: initialise(instance) $: initialise(instance)
@ -99,9 +101,10 @@
// Derive definition properties which can all be optional, so need to be // Derive definition properties which can all be optional, so need to be
// coerced to booleans // coerced to booleans
$: editable = !!definition?.editable
$: hasChildren = !!definition?.hasChildren $: hasChildren = !!definition?.hasChildren
$: showEmptyState = definition?.showEmptyState !== false $: showEmptyState = definition?.showEmptyState !== false
$: hasMissingRequiredSettings = missingRequiredSettings?.length > 0
$: editable = !!definition?.editable && !hasMissingRequiredSettings
// Interactive components can be selected, dragged and highlighted inside // Interactive components can be selected, dragged and highlighted inside
// the builder preview // the builder preview
@ -155,6 +158,7 @@
name, name,
editing, editing,
type: instance._component, type: instance._component,
missingRequiredSettings,
}) })
const initialise = instance => { const initialise = instance => {
@ -201,6 +205,27 @@
staticSettings = instanceSettings.staticSettings staticSettings = instanceSettings.staticSettings
dynamicSettings = instanceSettings.dynamicSettings dynamicSettings = instanceSettings.dynamicSettings
// Check if we have any missing required settings
missingRequiredSettings = settingsDefinition.filter(setting => {
let empty = instance[setting.key] == null || instance[setting.key] === ""
let missing = setting.required && empty
// Check if this setting depends on another, as it may not be required
if (setting.dependsOn) {
const dependsOnKey = setting.dependsOn.setting || setting.dependsOn
const dependsOnValue = setting.dependsOn.value
const realDependentValue = instance[dependsOnKey]
if (dependsOnValue == null && realDependentValue == null) {
return false
}
if (dependsOnValue !== realDependentValue) {
return false
}
}
return missing
})
// Force an initial enrichment of the new settings // Force an initial enrichment of the new settings
enrichComponentSettings(get(context), settingsDefinitionMap, { enrichComponentSettings(get(context), settingsDefinitionMap, {
force: true, force: true,
@ -414,6 +439,9 @@
data-id={id} data-id={id}
data-name={name} data-name={name}
> >
{#if hasMissingRequiredSettings}
<ComponentPlaceholder />
{:else}
<svelte:component this={constructor} bind:this={ref} {...initialSettings}> <svelte:component this={constructor} bind:this={ref} {...initialSettings}>
{#if children.length} {#if children.length}
{#each children as child (child._id)} {#each children as child (child._id)}
@ -425,6 +453,7 @@
<slot /> <slot />
{/if} {/if}
</svelte:component> </svelte:component>
{/if}
</div> </div>
{/if} {/if}

View File

@ -0,0 +1,44 @@
<script>
import { getContext } from "svelte"
import { builderStore } from "stores"
const { styleable } = getContext("sdk")
const component = getContext("component")
$: requiredSetting = $component.missingRequiredSettings?.[0]
</script>
{#if $builderStore.inBuilder && requiredSetting}
<div use:styleable={$component.styles}>
<div class="component-placeholder">
<span>
Add the <mark>{requiredSetting.label}</mark> setting to start using your
component -
</span>
<span
class="spectrum-Link"
on:click={() => {
builderStore.actions.highlightSetting(requiredSetting.key)
}}
>
Show me
</span>
</div>
</div>
{/if}
<style>
.component-placeholder {
color: var(--spectrum-global-color-gray-600);
font-size: var(--font-size-s);
padding: var(--spacing-xs);
}
.component-placeholder mark {
background-color: var(--spectrum-global-color-gray-400);
padding: 0 2px;
border-radius: 2px;
}
.component-placeholder .spectrum-Link {
cursor: pointer;
}
</style>

View File

@ -1,6 +1,5 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import Placeholder from "./Placeholder.svelte"
const { linkable, styleable, builderStore } = getContext("sdk") const { linkable, styleable, builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -80,11 +79,7 @@
{componentText} {componentText}
</div> </div>
{:else if $builderStore.inBuilder || componentText} {:else if $builderStore.inBuilder || componentText}
{#if !url && !text} {#if externalLink || openInNewTab}
<div use:styleable={{ ...$component.styles, empty: true }}>
<Placeholder />
</div>
{:else if externalLink || openInNewTab}
<a <a
{target} {target}
href={sanitizedUrl} href={sanitizedUrl}

View File

@ -1,67 +1,15 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import Manifest from "manifest.json"
import { builderStore } from "stores"
const { componentStore } = getContext("sdk") const { builderStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
export let text export let text
$: componentInstance = componentStore.actions.getComponentById($component.id)
const getComponentKey = compId => {
if (!compId) {
return
}
const prefix = "@budibase/standard-components/"
let componentKey = compId.replace(prefix, "")
return componentKey
}
const emptyFields = (definition, options) => {
if (!options) {
return []
}
return definition?.settings
? definition.settings.filter(setting => {
return (
setting.required &&
(!options[setting.key] || options[setting.key] == "")
)
})
: []
}
const definition = Manifest[getComponentKey($component.type)]
$: focus_setting = emptyFields(definition, componentInstance)[0]
</script> </script>
{#if $builderStore.inBuilder} {#if $builderStore.inBuilder}
<div class="placeholder_wrap">
{#if componentInstance && focus_setting}
<div> <div>
<span>
Add the <mark>{focus_setting?.label}</mark> setting to start using your
component &nbsp;
</span>
<span
class="showMe spectrum-Link"
on:click={() => {
builderStore.actions.setFocus([
{
location: "component_settings",
key: focus_setting.key,
target: $component.id,
},
])
}}
>
Show me
</span>
</div>
{:else}
{text || $component.name || "Placeholder"} {text || $component.name || "Placeholder"}
{/if}
</div> </div>
{/if} {/if}
@ -69,18 +17,5 @@
div { div {
color: var(--spectrum-global-color-gray-600); color: var(--spectrum-global-color-gray-600);
font-size: var(--font-size-s); font-size: var(--font-size-s);
padding: var(--spacing-xs);
}
:global(div.placeholder_wrap mark) {
background-color: var(--spectrum-global-color-gray-400);
padding: 0px 2px;
border-radius: 2px;
}
:global(div.placeholder_wrap .showMe) {
cursor: pointer;
}
:global(div.placeholder_wrap .showMe:hover) {
text-decoration: underline;
cursor: pointer;
} }
</style> </style>

View File

@ -76,9 +76,7 @@
{#if !formContext} {#if !formContext}
<Placeholder text="Form components need to be wrapped in a form" /> <Placeholder text="Form components need to be wrapped in a form" />
{:else if !fieldState} {:else if !fieldState}
{#if $builderStore.inBuilder}
<Placeholder /> <Placeholder />
{/if}
{:else if schemaType && schemaType !== type && type !== "options"} {:else if schemaType && schemaType !== type && type !== "options"}
<Placeholder <Placeholder
text="This Field setting is the wrong data type for this component" text="This Field setting is the wrong data type for this component"

View File

@ -68,8 +68,8 @@ const createBuilderStore = () => {
} }
store.update(state => ({ ...state, editMode: enabled })) store.update(state => ({ ...state, editMode: enabled }))
}, },
setFocus: data => { highlightSetting: setting => {
window.parent.postMessage({ type: "builder-focus", data }) dispatchEvent("highlight-setting", { setting })
}, },
} }
return { return {