Add ability to eject blocks into raw components
This commit is contained in:
parent
d78bcc2b06
commit
02e5e66992
|
@ -575,6 +575,14 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
await store.actions.preview.saveSelected()
|
await store.actions.preview.saveSelected()
|
||||||
},
|
},
|
||||||
|
ejectBlock: async (id, definition) => {
|
||||||
|
const asset = get(currentAsset)
|
||||||
|
let parent = findComponentParent(asset.props, id)
|
||||||
|
const childIndex = parent._children.findIndex(x => x._id === id)
|
||||||
|
parent._children[childIndex] = definition
|
||||||
|
await store.actions.preview.saveSelected()
|
||||||
|
await store.actions.components.select(definition)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
links: {
|
links: {
|
||||||
save: async (url, title) => {
|
save: async (url, title) => {
|
||||||
|
|
|
@ -202,6 +202,9 @@
|
||||||
block: "center",
|
block: "center",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else if (type === "eject-block") {
|
||||||
|
const { id, definition } = data
|
||||||
|
await store.actions.components.ejectBlock(id, definition)
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Client sent unknown event type: ${type}`)
|
console.warn(`Client sent unknown event type: ${type}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
$: definition = store.actions.components.getDefinition(component?._component)
|
$: definition = store.actions.components.getDefinition(component?._component)
|
||||||
$: noChildrenAllowed = !component || !definition?.hasChildren
|
$: noChildrenAllowed = !component || !definition?.hasChildren
|
||||||
$: noPaste = !$store.componentToPaste
|
$: noPaste = !$store.componentToPaste
|
||||||
|
$: isBlock = definition?.block === true
|
||||||
|
|
||||||
// "editable" has been repurposed for inline text editing.
|
// "editable" has been repurposed for inline text editing.
|
||||||
// It remains here for legacy compatibility.
|
// It remains here for legacy compatibility.
|
||||||
|
@ -83,6 +84,8 @@
|
||||||
notifications.error("Error saving component")
|
notifications.error("Error saving component")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ejectBlock = () => {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showMenu}
|
{#if showMenu}
|
||||||
|
@ -93,6 +96,9 @@
|
||||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>
|
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>
|
||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
{#if isBlock}
|
||||||
|
<MenuItem icon="Delete" on:click={ejectBlock}>Eject block</MenuItem>
|
||||||
|
{/if}
|
||||||
<MenuItem noClose icon="ChevronUp" on:click={moveUpComponent}>
|
<MenuItem noClose icon="ChevronUp" on:click={moveUpComponent}>
|
||||||
Move up
|
Move up
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
@ -1,12 +1,65 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { getContext, setContext } from "svelte"
|
||||||
|
import { builderStore } from "../stores/builder.js"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
// We need to set a block context to know we're inside a block, but also
|
let structureLookupMap = {}
|
||||||
// to be able to reference the actual component ID of the block from
|
const registerBlockComponent = (id, order, parentId, instance) => {
|
||||||
// any depth
|
// Ensure child array exists
|
||||||
setContext("block", { id: $component.id })
|
if (!structureLookupMap[parentId]) {
|
||||||
|
structureLookupMap[parentId] = []
|
||||||
|
}
|
||||||
|
// Remove existing instance of this component in case props changed
|
||||||
|
structureLookupMap[parentId] = structureLookupMap[parentId].filter(
|
||||||
|
x => x.instance._id !== id
|
||||||
|
)
|
||||||
|
// Add new instance of this component
|
||||||
|
structureLookupMap[parentId].push({ order, instance })
|
||||||
|
}
|
||||||
|
|
||||||
|
const eject = () => {
|
||||||
|
// Start the new structure with the first top level component
|
||||||
|
let definition = structureLookupMap[$component.id][0].instance
|
||||||
|
attachChildren(definition, structureLookupMap)
|
||||||
|
builderStore.actions.ejectBlock($component.id, definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachChildren = (rootComponent, map) => {
|
||||||
|
let id = rootComponent._id
|
||||||
|
if (!map[id]?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort children by order
|
||||||
|
map[id].sort((a, b) => (a.order < b.order ? -1 : 1))
|
||||||
|
|
||||||
|
// Attach all children of this component
|
||||||
|
rootComponent._children = map[id].map(x => x.instance)
|
||||||
|
|
||||||
|
// Recurse for each child
|
||||||
|
rootComponent._children.forEach(child => {
|
||||||
|
attachChildren(child, map)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext("block", {
|
||||||
|
// We need to set a block context to know we're inside a block, but also
|
||||||
|
// to be able to reference the actual component ID of the block from
|
||||||
|
// any depth
|
||||||
|
id: $component.id,
|
||||||
|
|
||||||
|
// We register block components with their raw props so that we can eject
|
||||||
|
// blocks later on
|
||||||
|
registerComponent: registerBlockComponent,
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
|
{#if $component.selected}
|
||||||
|
<div>
|
||||||
|
<Button cta on:click={eject}>Eject block</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
export let props
|
export let props
|
||||||
export let styles
|
export let styles
|
||||||
export let context
|
export let context
|
||||||
|
export let order = 0
|
||||||
|
|
||||||
// ID is only exposed as a prop so that it can be bound to from parent
|
// ID is only exposed as a prop so that it can be bound to from parent
|
||||||
// block components
|
// block components
|
||||||
export let id
|
export let id
|
||||||
|
|
||||||
|
const component = getContext("component")
|
||||||
const block = getContext("block")
|
const block = getContext("block")
|
||||||
const rand = generate()
|
const rand = generate()
|
||||||
|
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
$: instance = {
|
$: instance = {
|
||||||
_component: `@budibase/standard-components/${type}`,
|
_component: `@budibase/standard-components/${type}`,
|
||||||
_id: id,
|
_id: id,
|
||||||
|
_instanceName: type,
|
||||||
_styles: {
|
_styles: {
|
||||||
normal: {
|
normal: {
|
||||||
...styles,
|
...styles,
|
||||||
|
@ -28,6 +31,7 @@
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
|
$: block.registerComponent(id, order, $component?.id, instance)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Component {instance} isBlock>
|
<Component {instance} isBlock>
|
||||||
|
|
|
@ -112,28 +112,52 @@
|
||||||
props={{ dataSource, disableValidation: true }}
|
props={{ dataSource, disableValidation: true }}
|
||||||
>
|
>
|
||||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||||
<div class="header" class:mobile={$context.device.mobile}>
|
<BlockComponent
|
||||||
<div class="title">
|
type="container"
|
||||||
<Heading>{title || ""}</Heading>
|
props={{
|
||||||
</div>
|
direction: "row",
|
||||||
<div class="controls">
|
hAlign: "stretch",
|
||||||
|
vAlign: "middle",
|
||||||
|
gap: "M",
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
"margin-bottom": "20px",
|
||||||
|
}}
|
||||||
|
order={0}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="heading"
|
||||||
|
props={{
|
||||||
|
text: title,
|
||||||
|
}}
|
||||||
|
order={0}
|
||||||
|
/>
|
||||||
|
<BlockComponent
|
||||||
|
type="container"
|
||||||
|
props={{
|
||||||
|
direction: "row",
|
||||||
|
hAlign: "right",
|
||||||
|
vAlign: "center",
|
||||||
|
gap: "M",
|
||||||
|
}}
|
||||||
|
order={1}
|
||||||
|
>
|
||||||
{#if enrichedSearchColumns?.length}
|
{#if enrichedSearchColumns?.length}
|
||||||
<div
|
{#each enrichedSearchColumns as column, idx}
|
||||||
class="search"
|
<BlockComponent
|
||||||
style="--cols:{enrichedSearchColumns?.length}"
|
type={column.componentType}
|
||||||
>
|
props={{
|
||||||
{#each enrichedSearchColumns as column}
|
field: column.name,
|
||||||
<BlockComponent
|
placeholder: column.name,
|
||||||
type={column.componentType}
|
text: column.name,
|
||||||
props={{
|
autoWidth: true,
|
||||||
field: column.name,
|
}}
|
||||||
placeholder: column.name,
|
styles={{
|
||||||
text: column.name,
|
width: "192px",
|
||||||
autoWidth: true,
|
}}
|
||||||
}}
|
order={idx}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if showTitleButton}
|
{#if showTitleButton}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
|
@ -143,10 +167,11 @@
|
||||||
text: titleButtonText,
|
text: titleButtonText,
|
||||||
type: "cta",
|
type: "cta",
|
||||||
}}
|
}}
|
||||||
|
order={3}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</BlockComponent>
|
||||||
</div>
|
</BlockComponent>
|
||||||
{/if}
|
{/if}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="dataprovider"
|
type="dataprovider"
|
||||||
|
@ -159,6 +184,7 @@
|
||||||
paginate,
|
paginate,
|
||||||
limit: rowCount,
|
limit: rowCount,
|
||||||
}}
|
}}
|
||||||
|
order={1}
|
||||||
>
|
>
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
type="table"
|
type="table"
|
||||||
|
@ -185,33 +211,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.title :global(.spectrum-Heading) {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
|
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,9 @@ const createBuilderStore = () => {
|
||||||
highlightSetting: setting => {
|
highlightSetting: setting => {
|
||||||
dispatchEvent("highlight-setting", { setting })
|
dispatchEvent("highlight-setting", { setting })
|
||||||
},
|
},
|
||||||
|
ejectBlock: (id, definition) => {
|
||||||
|
dispatchEvent("eject-block", { id, definition })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
|
|
Loading…
Reference in New Issue