Add placeholders and empty states automatically as required to any empty client components
This commit is contained in:
parent
c6564858b2
commit
3b085d9ac3
|
@ -65,7 +65,7 @@
|
|||
>
|
||||
<Provider key="user" data={$authStore} {actions}>
|
||||
<div id="app-root">
|
||||
<Component definition={$screenStore.activeLayout.props} />
|
||||
<Component instance={$screenStore.activeLayout.props} />
|
||||
</div>
|
||||
<NotificationDisplay />
|
||||
<!-- Key block needs to be outside the if statement or it breaks -->
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
import { enrichProps, propsAreSame } from "../utils/componentProps"
|
||||
import { builderStore } from "../store"
|
||||
import { hashString } from "../utils/hash"
|
||||
import Manifest from "@budibase/standard-components/manifest.json"
|
||||
|
||||
export let definition = {}
|
||||
export let instance = {}
|
||||
|
||||
// Props that will be passed to the component instance
|
||||
let componentProps
|
||||
|
@ -28,26 +29,29 @@
|
|||
const componentStore = writable({})
|
||||
setContext("component", componentStore)
|
||||
|
||||
// Extract component definition info
|
||||
$: constructor = getComponentConstructor(definition._component)
|
||||
$: children = definition._children || []
|
||||
$: id = definition._id
|
||||
$: name = definition._instanceName
|
||||
$: updateComponentProps(definition, $context)
|
||||
$: styles = definition._styles
|
||||
$: transition = definition._transition
|
||||
// Extract component instance info
|
||||
$: constructor = getComponentConstructor(instance._component)
|
||||
$: definition = getComponentDefinition(instance._component)
|
||||
$: children = instance._children || []
|
||||
$: id = instance._id
|
||||
$: name = instance._instanceName
|
||||
$: empty =
|
||||
!children.length && definition?.hasChildren && $builderStore.inBuilder
|
||||
$: updateComponentProps(instance, $context)
|
||||
$: selected =
|
||||
$builderStore.inBuilder &&
|
||||
$builderStore.selectedComponentId === definition._id
|
||||
$builderStore.selectedComponentId === instance._id
|
||||
|
||||
// Update component context
|
||||
$: componentStore.set({
|
||||
id,
|
||||
children: children.length,
|
||||
styles: { ...styles, id },
|
||||
transition,
|
||||
styles: { ...instance._styles, id, empty },
|
||||
empty,
|
||||
transition: instance._transition,
|
||||
selected,
|
||||
props: componentProps,
|
||||
name,
|
||||
})
|
||||
|
||||
// Gets the component constructor for the specified component
|
||||
|
@ -60,14 +64,20 @@
|
|||
return ComponentLibrary[name]
|
||||
}
|
||||
|
||||
const getComponentDefinition = component => {
|
||||
const prefix = "@budibase/standard-components/"
|
||||
const type = component?.replace(prefix, "")
|
||||
return type ? Manifest[type] : null
|
||||
}
|
||||
|
||||
// Enriches any string component props using handlebars
|
||||
const updateComponentProps = (definition, context) => {
|
||||
const updateComponentProps = (instance, context) => {
|
||||
// Record the timestamp so we can reference it after enrichment
|
||||
latestUpdateTime = Date.now()
|
||||
const enrichmentTime = latestUpdateTime
|
||||
|
||||
// Enrich props with context
|
||||
const enrichedProps = enrichProps(definition, context)
|
||||
const enrichedProps = enrichProps(instance, context)
|
||||
|
||||
// Abandon this update if a newer update has started
|
||||
if (enrichmentTime !== latestUpdateTime) {
|
||||
|
@ -100,14 +110,21 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class={id} data-type="component" data-id={id} data-name={name}>
|
||||
<div
|
||||
class={`component ${id}`}
|
||||
data-type="component"
|
||||
data-id={id}
|
||||
data-name={name}
|
||||
>
|
||||
{#key propsHash}
|
||||
{#if constructor && componentProps}
|
||||
<svelte:component this={constructor} {...componentProps}>
|
||||
{#if children.length}
|
||||
{#each children as child (child._id)}
|
||||
<svelte:self definition={child} />
|
||||
<svelte:self instance={child} />
|
||||
{/each}
|
||||
{:else if empty}
|
||||
<div class="placeholder">{name}</div>
|
||||
{/if}
|
||||
</svelte:component>
|
||||
{/if}
|
||||
|
@ -115,7 +132,11 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
.component {
|
||||
display: contents;
|
||||
}
|
||||
.placeholder {
|
||||
color: #888;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
<!-- Ensure to fully remount when screen changes -->
|
||||
{#key screenDefinition?._id}
|
||||
<Provider key="url" data={params}>
|
||||
<Component definition={screenDefinition} />
|
||||
<Component instance={screenDefinition} />
|
||||
</Provider>
|
||||
{/key}
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
|
||||
onMount(() => {
|
||||
document.addEventListener("mouseover", onMouseOver)
|
||||
document.addEventListener("mouseleave", onMouseLeave)
|
||||
window.addEventListener("mouseleave", onMouseLeave)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
document.removeEventListener("mouseover", onMouseOver)
|
||||
document.removeEventListener("mouseleave", onMouseLeave)
|
||||
window.removeEventListener("mouseleave", onMouseLeave)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { writable, derived } from "svelte/store"
|
||||
import manifest from "@budibase/standard-components/manifest.json"
|
||||
import Manifest from "@budibase/standard-components/manifest.json"
|
||||
|
||||
const dispatchEvent = (type, data) => {
|
||||
window.dispatchEvent(
|
||||
|
@ -46,7 +46,7 @@ const createBuilderStore = () => {
|
|||
const component = findComponentById(asset?.props, selectedComponentId)
|
||||
const prefix = "@budibase/standard-components/"
|
||||
const type = component?._component?.replace(prefix, "")
|
||||
const definition = type ? manifest[type] : null
|
||||
const definition = type ? Manifest[type] : null
|
||||
return {
|
||||
...$state,
|
||||
selectedComponent: component,
|
||||
|
|
|
@ -35,6 +35,11 @@ export const styleable = (node, styles = {}) => {
|
|||
|
||||
// Applies a style string to a DOM node
|
||||
const applyStyles = styleString => {
|
||||
// Apply empty border if required
|
||||
if (newStyles.empty) {
|
||||
styleString += "border: 2px dashed rgba(0, 0, 0, 0.25);"
|
||||
}
|
||||
|
||||
node.style = styleString
|
||||
node.dataset.componentId = componentId
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
export let vAlign
|
||||
export let size
|
||||
|
||||
let element
|
||||
|
||||
$: directionClass = direction ? `valid-container direction-${direction}` : ""
|
||||
$: hAlignClass = hAlign ? `hAlign-${hAlign}` : ""
|
||||
$: vAlignClass = vAlign ? `vAlign-${vAlign}` : ""
|
||||
|
@ -19,23 +17,13 @@
|
|||
|
||||
<div
|
||||
class={[directionClass, hAlignClass, vAlignClass, sizeClass].join(" ")}
|
||||
class:empty={!$component.children && $builderStore.inBuilder}
|
||||
class:selected={$component.selected}
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}
|
||||
bind:this={element}
|
||||
>
|
||||
{#if !$component.children && $builderStore.inBuilder}
|
||||
<div class="placeholder">Add some content</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.empty {
|
||||
border: 2px dashed rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.valid-container {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
|
@ -96,12 +84,4 @@
|
|||
.direction-column.hAlign-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.selected {
|
||||
position: relative;
|
||||
}
|
||||
.placeholder {
|
||||
padding: 20px;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
luceneSort,
|
||||
luceneLimit,
|
||||
} from "./lucene"
|
||||
import Placeholder from "./Placeholder.svelte"
|
||||
import Container from "./Container.svelte"
|
||||
|
||||
export let dataSource
|
||||
export let filter
|
||||
|
@ -230,8 +232,12 @@
|
|||
<div class="loading">
|
||||
<ProgressCircle />
|
||||
</div>
|
||||
{:else}
|
||||
{#if !$component.children}
|
||||
<Placeholder />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
{#if paginate && internalTable}
|
||||
<div class="pagination">
|
||||
<Pagination
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
export let text = $component.name || "Placeholder"
|
||||
</script>
|
||||
|
||||
{#if $builderStore.inBuilder}
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
div {
|
||||
padding: 20px;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import Placeholder from "./Placeholder.svelte"
|
||||
|
||||
export let dataProvider
|
||||
export let noRowsMessage
|
||||
|
@ -13,35 +14,15 @@
|
|||
</script>
|
||||
|
||||
<div use:styleable={$component.styles}>
|
||||
{#if rows.length > 0}
|
||||
{#if $component.children === 0 && $builderStore.inBuilder}
|
||||
<p><i class="ri-image-line" />Add some components to display.</p>
|
||||
{:else}
|
||||
{#if $component.empty}
|
||||
<Placeholder />
|
||||
{:else if rows.length > 0}
|
||||
{#each rows as row}
|
||||
<Provider data={row}>
|
||||
<slot />
|
||||
</Provider>
|
||||
{/each}
|
||||
{/if}
|
||||
{:else if loaded && noRowsMessage}
|
||||
<p><i class="ri-list-check-2" />{noRowsMessage}</p>
|
||||
<Placeholder text={noRowsMessage} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
p {
|
||||
margin: 0 var(--spacing-m);
|
||||
background-color: var(--grey-2);
|
||||
color: var(--grey-6);
|
||||
font-size: var(--font-size-s);
|
||||
padding: var(--spacing-l);
|
||||
border-radius: var(--border-radius-s);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
p i {
|
||||
margin-bottom: var(--spacing-m);
|
||||
font-size: 1.5rem;
|
||||
color: var(--grey-5);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
export let imageUrl = ""
|
||||
export let heading = ""
|
||||
export let subheading = ""
|
||||
export let destinationUrl = ""
|
||||
export let destinationUrl = "/"
|
||||
|
||||
$: showImage = !!imageUrl
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { chart } from "svelte-apexcharts"
|
||||
import Placeholder from "../Placeholder.svelte"
|
||||
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
@ -11,8 +12,8 @@
|
|||
{#if options}
|
||||
<div use:chart={options} use:styleable={$component.styles} />
|
||||
{:else if $builderStore.inBuilder}
|
||||
<div class="placeholder" use:styleable={$component.styles}>
|
||||
Use the settings panel to build your chart.
|
||||
<div use:styleable={{ ...$component.styles, empty: true }}>
|
||||
<Placeholder text="Use the settings panel to build your chart" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -24,7 +25,4 @@
|
|||
div :global(.apexcharts-yaxis-label, .apexcharts-xaxis-label) {
|
||||
fill: #aaa;
|
||||
}
|
||||
div.placeholder {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,30 +8,6 @@ export const capitalise = string => {
|
|||
return string.substring(0, 1).toUpperCase() + string.substring(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Svelte action to set CSS variables on a DOM node.
|
||||
*
|
||||
* @param node
|
||||
* @param props
|
||||
*/
|
||||
export const cssVars = (node, props) => {
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
node.style.setProperty(`--${key}`, value)
|
||||
})
|
||||
|
||||
return {
|
||||
update(new_props) {
|
||||
Object.entries(new_props).forEach(([key, value]) => {
|
||||
node.style.setProperty(`--${key}`, value)
|
||||
delete props[key]
|
||||
})
|
||||
|
||||
Object.keys(props).forEach(name => node.style.removeProperty(`--${name}`))
|
||||
props = new_props
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a short random ID.
|
||||
* This is "nanoid" but rollup was derping attempting to bundle it, so the
|
||||
|
|
|
@ -10,6 +10,10 @@ import "@spectrum-css/page/dist/index-vars.css"
|
|||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
||||
loadSpectrumIcons()
|
||||
|
||||
// Non user-facing components
|
||||
export { default as Placeholder } from "./Placeholder.svelte"
|
||||
|
||||
// User facing components
|
||||
export { default as container } from "./Container.svelte"
|
||||
export { default as dataprovider } from "./DataProvider.svelte"
|
||||
export { default as screenslot } from "./ScreenSlot.svelte"
|
||||
|
|
Loading…
Reference in New Issue