Merge pull request #13327 from Budibase/cheeks-fixes
Various fixes and improvements
This commit is contained in:
commit
cc02d759d5
|
@ -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)
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
.main {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.padding .main {
|
||||
padding: var(--spacing-xl);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{
|
||||
"name": "Layout",
|
||||
"icon": "ClassicGridView",
|
||||
"children": ["container", "section", "grid", "sidepanel"]
|
||||
"children": ["container", "section", "sidepanel"]
|
||||
},
|
||||
{
|
||||
"name": "Data",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)) || {}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -345,8 +345,7 @@
|
|||
<IndicatorSet
|
||||
componentId={$dndParent}
|
||||
color="var(--spectrum-global-color-static-green-500)"
|
||||
zIndex="930"
|
||||
transition
|
||||
zIndex={920}
|
||||
prefix="Inside"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
{#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}
|
||||
{/each}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<IndicatorSet
|
||||
componentId={$builderStore.selectedComponentId}
|
||||
{color}
|
||||
zIndex="910"
|
||||
transition
|
||||
zIndex={900}
|
||||
allowResizeAnchors
|
||||
/>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue