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