Merge pull request #13327 from Budibase/cheeks-fixes

Various fixes and improvements
This commit is contained in:
Andrew Kingston 2024-03-22 12:57:25 +00:00 committed by GitHub
commit cc02d759d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 157 additions and 116 deletions

View File

@ -79,7 +79,8 @@ const removeHandler = id => {
export default (element, opts) => {
const id = Math.random()
const update = newOpts => {
const callback = newOpts?.callback || newOpts
const callback =
newOpts?.callback || (typeof newOpts === "function" ? newOpts : null)
const anchor = newOpts?.anchor || element
const allowedType = newOpts?.allowedType || "click"
updateHandler(id, element, anchor, callback, allowedType)

View File

@ -42,7 +42,6 @@
.main {
height: 100%;
overflow: auto;
overflow-x: hidden;
}
.padding .main {
padding: var(--spacing-xl);

View File

@ -12,6 +12,7 @@
export let schema
export let value
export let customRenderers = []
export let snippets
let renderer
const typeMap = {
@ -44,7 +45,7 @@
if (!template) {
return value
}
return processStringSync(template, { value })
return processStringSync(template, { value, snippets })
}
</script>

View File

@ -42,6 +42,7 @@
export let customPlaceholder = false
export let showHeaderBorder = true
export let placeholderText = "No rows found"
export let snippets = []
const dispatch = createEventDispatcher()
@ -425,6 +426,7 @@
<CellRenderer
{customRenderers}
{row}
{snippets}
schema={schema[field]}
value={deepGet(row, field)}
on:clickrelationship

View File

@ -313,7 +313,7 @@ export const bindingsToCompletions = (bindings, mode) => {
...bindingByCategory[catKey].reduce((acc, binding) => {
let displayType = binding.fieldSchema?.type || binding.display?.type
acc.push({
label: binding.display?.name || "NO NAME",
label: binding.display?.name || binding.readableBinding || "NO NAME",
info: completion => {
return buildBindingInfoNode(completion, binding)
},

View File

@ -8,6 +8,7 @@
export let allowJS = false
export let allowHelpers = true
export let autofocusEditor = false
export let context = null
$: enrichedBindings = enrichBindings(bindings)
@ -27,7 +28,7 @@
<BindingPanel
bindings={enrichedBindings}
context={$previewStore.selectedComponentContext}
context={{ ...$previewStore.selectedComponentContext, ...context }}
snippets={$snippets}
{value}
{allowJS}

View File

@ -7,10 +7,13 @@
Layout,
Label,
} from "@budibase/bbui"
import { themeStore } from "stores/builder"
import { themeStore, previewStore } from "stores/builder"
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
export let column
$: columnValue =
$previewStore.selectedComponentContext?.eventContext?.row?.[column.name]
</script>
<DrawerContent>
@ -41,6 +44,9 @@
icon: "TableColumnMerge",
},
]}
context={{
value: columnValue,
}}
/>
<Layout noPadding gap="XS">
<Label>Background color</Label>

View File

@ -14,7 +14,7 @@
{
"name": "Layout",
"icon": "ClassicGridView",
"children": ["container", "section", "grid", "sidepanel"]
"children": ["container", "section", "sidepanel"]
},
{
"name": "Data",

View File

@ -279,12 +279,10 @@ export class ComponentStore extends BudiStore {
else {
if (setting.type === "dataProvider") {
// Validate data provider exists, or else clear it
const treeId = parent?._id || component._id
const path = findComponentPath(screen?.props, treeId)
const providers = path.filter(component =>
component._component?.endsWith("/dataprovider")
const providers = findAllMatchingComponents(
screen?.props,
x => x._component === "@budibase/standard-components/dataprovider"
)
// Validate non-empty values
const valid = providers?.some(dp => value.includes?.(dp._id))
if (!valid) {
if (providers.length) {

View File

@ -7,12 +7,25 @@ export const INITIAL_HOVER_STATE = {
}
export class HoverStore extends BudiStore {
hoverTimeout
constructor() {
super({ ...INITIAL_HOVER_STATE })
this.hover = this.hover.bind(this)
}
hover(componentId, notifyClient = true) {
clearTimeout(this.hoverTimeout)
if (componentId) {
this.processHover(componentId, notifyClient)
} else {
this.hoverTimeout = setTimeout(() => {
this.processHover(componentId, notifyClient)
}, 10)
}
}
processHover(componentId, notifyClient) {
if (componentId === get(this.store).componentId) {
return
}

View File

@ -26,9 +26,12 @@
let schema
$: id = $component.id
$: selected = $component.selected
$: builderStep = $builderStore.metadata?.step
$: fetchSchema(dataSource)
$: enrichedSteps = enrichSteps(steps, schema, $component.id, $currentStep)
$: updateCurrentStep(enrichedSteps, $builderStore, $component)
$: enrichedSteps = enrichSteps(steps, schema, id)
$: updateCurrentStep(enrichedSteps, selected, builderStep)
// Provide additional data context for live binding eval
export const getAdditionalDataContext = () => {
@ -40,30 +43,22 @@
}
}
const updateCurrentStep = (steps, builderStore, component) => {
const { componentId, step } = builderStore.metadata || {}
// If we aren't in the builder or aren't selected then don't update the step
// context at all, allowing the normal form to take control.
if (
!component.selected ||
!builderStore.inBuilder ||
componentId !== component.id
) {
const updateCurrentStep = (steps, selected, builderStep) => {
// If we aren't selected in the builder then just allowing the normal form
// to take control.
if (!selected) {
return
}
// Ensure we have a valid step selected
let newStep = Math.min(step || 0, steps.length - 1)
// Sanity check
let newStep = Math.min(builderStep || 0, steps.length - 1)
newStep = Math.max(newStep, 0)
// Add 1 because the form component expects 1 indexed rather than 0 indexed
currentStep.set(newStep + 1)
}
const fetchSchema = async () => {
const fetchSchema = async dataSource => {
schema = (await fetchDatasourceSchema(dataSource)) || {}
}

View File

@ -34,6 +34,7 @@
$: formattedFields = convertOldFieldFormat(fields)
$: fieldsOrDefault = getDefaultFields(formattedFields, schema)
$: fetchSchema(dataSource)
$: id = $component.id
// We could simply spread $$props into the inner form and append our
// additions, but that would create svelte warnings about unused props and
// make maintenance in future more confusing as we typically always have a
@ -53,7 +54,7 @@
buttons:
buttons ||
Utils.buildFormBlockButtonConfig({
_id: $component.id,
_id: id,
showDeleteButton,
showSaveButton,
saveButtonLabel,

View File

@ -50,6 +50,7 @@
let schemaLoaded = false
$: deleteLabel = setDeleteLabel(sidePanelDeleteLabel, sidePanelShowDelete)
$: id = $component.id
$: isDSPlus = dataSource?.type === "table" || dataSource?.type === "viewV2"
$: fetchSchema(dataSource)
$: enrichSearchColumns(searchColumns, schema).then(
@ -279,7 +280,7 @@
dataSource,
buttonPosition: "top",
buttons: Utils.buildFormBlockButtonConfig({
_id: $component.id + "-form-edit",
_id: id + "-form-edit",
showDeleteButton: deleteLabel !== "",
showSaveButton: true,
saveButtonLabel: sidePanelSaveLabel || "Save",
@ -313,7 +314,7 @@
dataSource,
buttonPosition: "top",
buttons: Utils.buildFormBlockButtonConfig({
_id: $component.id + "-form-new",
_id: id + "-form-new",
showDeleteButton: false,
showSaveButton: true,
saveButtonLabel: "Save",

View File

@ -16,8 +16,15 @@
export let noRowsMessage
const component = getContext("component")
const { styleable, getAction, ActionTypes, rowSelectionStore } =
getContext("sdk")
const context = getContext("context")
const {
styleable,
getAction,
ActionTypes,
rowSelectionStore,
generateGoldenSample,
} = getContext("sdk")
const customColumnKey = `custom-${Math.random()}`
const customRenderers = [
{
@ -28,6 +35,7 @@
let selectedRows = []
$: snippets = $context.snippets
$: hasChildren = $component.children
$: loading = dataProvider?.loading ?? false
$: data = dataProvider?.rows || []
@ -61,6 +69,16 @@
selectedRows,
}
// Provide additional data context for live binding eval
export const getAdditionalDataContext = () => {
const goldenRow = generateGoldenSample(data)
return {
eventContext: {
row: goldenRow,
},
}
}
const getFields = (
schema,
customColumns,
@ -178,6 +196,7 @@
{quiet}
{compact}
{customRenderers}
{snippets}
allowSelectRows={allowSelectRows && table}
bind:selectedRows
allowEditRows={false}

View File

@ -6,36 +6,24 @@ export const getOptions = (
valueColumn,
customOptions
) => {
const isArray = fieldSchema?.type === "array"
// Take options from schema
if (optionsSource == null || optionsSource === "schema") {
return fieldSchema?.constraints?.inclusion ?? []
}
if (optionsSource === "provider" && isArray) {
let optionsSet = {}
dataProvider?.rows?.forEach(row => {
const value = row?.[valueColumn]
if (value != null) {
const label = row[labelColumn] || value
optionsSet[value] = { value, label }
}
})
return Object.values(optionsSet)
}
// Extract options from data provider
if (optionsSource === "provider" && valueColumn) {
let optionsSet = {}
let valueCache = {}
let options = []
dataProvider?.rows?.forEach(row => {
const value = row?.[valueColumn]
if (value != null) {
if (value != null && !valueCache[value]) {
valueCache[value] = true
const label = row[labelColumn] || value
optionsSet[value] = { value, label }
options.push({ value, label })
}
})
return Object.values(optionsSet)
return options
}
// Extract custom options

View File

@ -345,8 +345,7 @@
<IndicatorSet
componentId={$dndParent}
color="var(--spectrum-global-color-static-green-500)"
zIndex="930"
transition
zIndex={920}
prefix="Inside"
/>

View File

@ -1,10 +1,11 @@
<script>
import { onMount, onDestroy } from "svelte"
import IndicatorSet from "./IndicatorSet.svelte"
import { builderStore, dndIsDragging, hoverStore } from "stores"
import { dndIsDragging, hoverStore, builderStore } from "stores"
$: componentId = $hoverStore.hoveredComponentId
$: zIndex = componentId === $builderStore.selectedComponentId ? 900 : 920
$: selectedComponentId = $builderStore.selectedComponentId
$: selected = componentId === selectedComponentId
const onMouseOver = e => {
// Ignore if dragging
@ -45,7 +46,6 @@
<IndicatorSet
componentId={$dndIsDragging ? null : componentId}
color="var(--spectrum-global-color-static-blue-200)"
transition
{zIndex}
zIndex={selected ? 890 : 910}
allowResizeAnchors
/>

View File

@ -1,5 +1,4 @@
<script>
import { fade } from "svelte/transition"
import { Icon } from "@budibase/bbui"
export let top
@ -11,7 +10,6 @@
export let color
export let zIndex
export let componentId
export let transition = false
export let line = false
export let alignRight = false
export let showResizeAnchors = false
@ -31,10 +29,6 @@
</script>
<div
in:fade={{
delay: transition ? 100 : 0,
duration: transition ? 100 : 0,
}}
class="indicator"
class:flipped
class:line
@ -127,10 +121,6 @@
font-weight: 600;
}
/* Icon styles */
.label :global(.spectrum-Icon + .text) {
}
/* Anchor */
.anchor {
--size: 24px;

View File

@ -4,27 +4,39 @@
import { domDebounce } from "utils/domDebounce"
import { builderStore } from "stores"
export let componentId
export let color
export let transition
export let zIndex
export let componentId = null
export let color = null
export let zIndex = 900
export let prefix = null
export let allowResizeAnchors = false
let indicators = []
const errorColor = "var(--spectrum-global-color-static-red-600)"
const defaultState = () => ({
// Cached props
componentId,
color,
zIndex,
prefix,
allowResizeAnchors,
// Computed state
indicators: [],
text: null,
icon: null,
insideGrid: false,
error: false,
})
let interval
let text
let icon
let insideGrid = false
let errorState = false
$: visibleIndicators = indicators.filter(x => x.visible)
$: offset = $builderStore.inBuilder ? 0 : 2
let state = defaultState()
let nextState = null
let updating = false
let observers = []
let callbackCount = 0
let nextIndicators = []
$: visibleIndicators = state.indicators.filter(x => x.visible)
$: offset = $builderStore.inBuilder ? 0 : 2
$: $$props, debouncedUpdate()
const checkInsideGrid = id => {
const component = document.getElementsByClassName(id)[0]
@ -44,10 +56,10 @@
if (callbackCount >= observers.length) {
return
}
nextIndicators[idx].visible =
nextIndicators[idx].insideSidePanel || entries[0].isIntersecting
nextState.indicators[idx].visible =
nextState.indicators[idx].insideSidePanel || entries[0].isIntersecting
if (++callbackCount === observers.length) {
indicators = nextIndicators
state = nextState
updating = false
}
}
@ -59,7 +71,7 @@
// Sanity check
if (!componentId) {
indicators = []
state = defaultState()
return
}
@ -68,25 +80,25 @@
callbackCount = 0
observers.forEach(o => o.disconnect())
observers = []
nextIndicators = []
nextState = defaultState()
// Check if we're inside a grid
if (allowResizeAnchors) {
insideGrid = checkInsideGrid(componentId)
nextState.insideGrid = checkInsideGrid(componentId)
}
// Determine next set of indicators
const parents = document.getElementsByClassName(componentId)
if (parents.length) {
text = parents[0].dataset.name
if (prefix) {
text = `${prefix} ${text}`
nextState.text = parents[0].dataset.name
if (nextState.prefix) {
nextState.text = `${nextState.prefix} ${nextState.text}`
}
if (parents[0].dataset.icon) {
icon = parents[0].dataset.icon
nextState.icon = parents[0].dataset.icon
}
}
errorState = parents?.[0]?.classList.contains("error")
nextState.error = parents?.[0]?.classList.contains("error")
// Batch reads to minimize reflow
const scrollX = window.scrollX
@ -102,8 +114,9 @@
// If there aren't any nodes then reset
if (!children.length) {
indicators = []
state = defaultState()
updating = false
return
}
const device = document.getElementById("app-root")
@ -119,7 +132,7 @@
observers.push(observer)
const elBounds = child.getBoundingClientRect()
nextIndicators.push({
nextState.indicators.push({
top: elBounds.top + scrollY - deviceBounds.top - offset,
left: elBounds.left + scrollX - deviceBounds.left - offset,
width: elBounds.width + 4,
@ -144,20 +157,17 @@
})
</script>
{#key componentId}
{#each visibleIndicators as indicator, idx}
<Indicator
top={indicator.top}
left={indicator.left}
width={indicator.width}
height={indicator.height}
text={idx === 0 ? text : null}
icon={idx === 0 ? icon : null}
showResizeAnchors={allowResizeAnchors && insideGrid}
color={errorState ? "var(--spectrum-global-color-static-red-600)" : color}
{componentId}
{transition}
{zIndex}
text={idx === 0 ? state.text : null}
icon={idx === 0 ? state.icon : null}
showResizeAnchors={state.allowResizeAnchors && state.insideGrid}
color={state.error ? errorColor : state.color}
componentId={state.componentId}
zIndex={state.zIndex}
/>
{/each}
{/key}

View File

@ -10,7 +10,6 @@
<IndicatorSet
componentId={$builderStore.selectedComponentId}
{color}
zIndex="910"
transition
zIndex={900}
allowResizeAnchors
/>

View File

@ -98,7 +98,7 @@ const loadBudibase = async () => {
context: stringifiedContext,
})
} else if (type === "hover-component") {
hoverStore.actions.hoverComponent(data)
hoverStore.actions.hoverComponent(data, false)
} else if (type === "builder-meta") {
builderStore.actions.setMetadata(data)
}

View File

@ -34,6 +34,8 @@ import {
LuceneUtils,
Constants,
RowUtils,
memo,
derivedMemo,
} from "@budibase/frontend-core"
export default {
@ -71,6 +73,8 @@ export default {
makePropSafe,
createContextStore,
generateGoldenSample: RowUtils.generateGoldenSample,
memo,
derivedMemo,
// Components
Provider,

View File

@ -5,14 +5,28 @@ const createHoverStore = () => {
const store = writable({
hoveredComponentId: null,
})
let hoverTimeout
const hoverComponent = id => {
const hoverComponent = (id, notifyBuilder = true) => {
clearTimeout(hoverTimeout)
if (id) {
processHover(id, notifyBuilder)
} else {
hoverTimeout = setTimeout(() => {
processHover(id, notifyBuilder)
}, 10)
}
}
const processHover = (id, notifyBuilder = true) => {
if (id === get(store).hoveredComponentId) {
return
}
store.set({ hoveredComponentId: id })
if (notifyBuilder) {
eventStore.actions.dispatchEvent("hover-component", { id })
}
}
return {
...store,