Add/Remove button behaviour, binding label support and default text behaviour. Refactoring and renaming of EditComponentPopoever component

This commit is contained in:
Dean 2023-10-13 14:53:39 +01:00
parent 1ca9830baa
commit b8c87224f7
12 changed files with 281 additions and 151 deletions

View File

@ -1,5 +1,5 @@
<script>
import DraggableList from "../DraggableList.svelte"
import DraggableList from "../DraggableList/DraggableList.svelte"
import ButtonSetting from "./ButtonSetting.svelte"
import {
getBindableProperties,
@ -8,37 +8,47 @@
import { createEventDispatcher, onMount } from "svelte"
import { selectedScreen, store } from "builderStore"
import { isEqual, cloneDeep } from "lodash"
import { Helpers } from "@budibase/bbui"
export let componentInstance
export let value
$: bindings = getBindableProperties($selectedScreen, componentInstance._id)
const dispatch = createEventDispatcher()
let buttonList
let mounted
let cachedValue
let componentBindings = []
let focusItem
const updateState = value => {
return (value || []).map((button, idx) => {
if (!button._component) {
return buildPseudoInstance({ ...button, name: `Button ${idx + 1}` })
}
return button
})
}
const buildItemProps = buttonList => {
return {
componentBindings,
bindings,
removeButton,
canRemove: buttonList?.length > 1,
}
}
$: if (!isEqual(value, cachedValue) && mounted) {
cachedValue = value ? cloneDeep(value) : []
}
$: buttonList = updateState(cachedValue)
$: buttonCount = buttonList?.length
$: bindings = getBindableProperties($selectedScreen, componentInstance._id)
$: componentBindings = getComponentBindableProperties(
$selectedScreen,
componentInstance._id
)
$: if (!isEqual(value, cachedValue) && mounted) {
cachedValue = value
? cloneDeep(value)
: [
buildPseudoInstance({ name: `Button 1`, type: "cta" }),
buildPseudoInstance({ name: `Button 2` }),
]
}
const updateState = value => {
buttonList = value
}
$: updateState(cachedValue)
$: itemProps = buildItemProps(buttonList)
const processItemUpdate = e => {
const updatedField = e.detail
@ -53,7 +63,6 @@
} else {
parentButtonsUpdated[parentFieldIdx] = updatedField
}
console.log("On value update ", parentButtonsUpdated)
dispatch("change", parentButtonsUpdated)
}
@ -61,12 +70,11 @@
dispatch("change", [...e.detail])
}
// May not be necessary without
const buildPseudoInstance = cfg => {
const pseudoComponentInstance = store.actions.components.createInstance(
`@budibase/standard-components/button`,
{
_instanceName: cfg.name,
_instanceName: Helpers.uuid(),
text: cfg.name,
type: cfg.type || "primary",
},
@ -76,6 +84,21 @@
return pseudoComponentInstance
}
const addButton = () => {
const newButton = buildPseudoInstance({
name: `Button ${buttonList.length + 1}`,
})
dispatch("change", [...buttonList, newButton])
focusItem = newButton._id
}
const removeButton = id => {
dispatch(
"change",
buttonList.filter(button => button._id !== id)
)
}
onMount(() => {
mounted = true
})
@ -89,11 +112,14 @@
items={buttonList}
listItemKey={"_id"}
listType={ButtonSetting}
listTypeProps={{
componentBindings,
bindings,
}}
listTypeProps={itemProps}
focus={focusItem}
draggable={buttonCount > 1}
/>
<div class="list-footer" on:click={addButton}>
<div class="add-button">Add button</div>
</div>
{/if}
</div>
@ -101,4 +127,38 @@
.button-configuration :global(.spectrum-ActionButton) {
width: 100%;
}
.button-configuration :global(.list-wrap > li:last-child),
.button-configuration :global(.list-wrap) {
border-bottom-left-radius: unset;
border-bottom-right-radius: unset;
border-bottom: 0px;
}
.list-footer {
width: 100%;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
background-color: var(
--spectrum-table-background-color,
var(--spectrum-global-color-gray-50)
);
transition: background-color ease-in-out 130ms;
display: flex;
justify-content: center;
border: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
cursor: pointer;
}
.add-button {
margin: var(--spacing-s);
}
.list-footer:hover {
background-color: var(
--spectrum-table-row-background-color-hover,
var(--spectrum-alias-highlight-hover)
);
}
</style>

View File

@ -1,37 +1,40 @@
<script>
import EditFieldPopover from "../FieldConfiguration/EditFieldPopover.svelte"
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Icon } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
// import { cloneDeep } from "lodash/fp"
import { runtimeToReadableBinding } from "builderStore/dataBinding"
import { isJSBinding } from "@budibase/string-templates"
export let item
export let componentBindings
export let bindings
export let anchor
export let removeButton
export let canRemove
const dispatch = createEventDispatcher()
// const onToggle = item => {
// return e => {
// item.active = e.detail
// dispatch("change", { ...cloneDeep(item), active: e.detail })
// }
// }
$: readableText = isJSBinding(item.text)
? "(JavaScript function)"
: runtimeToReadableBinding([...bindings, componentBindings], item.text)
</script>
<div class="list-item-body">
<div class="list-item-left">
<EditFieldPopover
<EditComponentPopover
{anchor}
field={item}
componentInstance={item}
{componentBindings}
{bindings}
on:change
/>
<div class="field-label">{item.text}</div>
<div class="field-label">{readableText || "Button"}</div>
</div>
<div class="list-item-right">
<Icon size="S" name="Close" hoverable on:click={() => {console.log("REMOVE ME")}}
/>
<Icon
disabled={!canRemove}
size="S"
name="Close"
hoverable
on:click={() => removeButton(item._id)}
/>
</div>
</div>

View File

@ -1,10 +1,10 @@
<script>
import { Icon } from "@budibase/bbui"
import { dndzone } from "svelte-dnd-action"
import { createEventDispatcher } from "svelte"
import { generate } from "shortid"
import { setContext } from "svelte"
import { writable } from "svelte/store"
import { writable, get } from "svelte/store"
import DragHandle from "./drag-handle.svelte"
export let items = []
export let showHandle = true
@ -12,6 +12,7 @@
export let listTypeProps = {}
export let listItemKey
export let draggable = true
export let focus
let store = writable({
selected: null,
@ -27,6 +28,10 @@
setContext("draggable", store)
$: if (focus && store) {
get(store).actions.select(focus)
}
const dispatch = createEventDispatcher()
const flipDurationMs = 150
@ -82,13 +87,16 @@
>
{#each draggableItems as draggable (draggable.id)}
<li
on:mousedown={() => {
get(store).actions.select()
}}
bind:this={anchors[draggable.id]}
class:highlighted={draggable.id === $store.selected}
>
<div class="left-content">
{#if showHandle}
<div class="handle" aria-label="drag-handle">
<Icon name="DragHandle" size="XL" />
<div class="handle">
<DragHandle />
</div>
{/if}
</div>
@ -142,8 +150,9 @@
border-top-right-radius: 4px;
}
.list-wrap > li:last-child {
border-top-left-radius: var(--spectrum-table-regular-border-radius);
border-top-right-radius: var(--spectrum-table-regular-border-radius);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: 0px;
}
.right-content {
flex: 1;
@ -153,4 +162,15 @@
padding-left: var(--spacing-s);
padding-right: var(--spacing-s);
}
.handle {
display: flex;
height: var(--spectrum-global-dimension-size-150);
}
.handle :global(svg) {
fill: var(--spectrum-global-color-gray-500);
margin-right: var(--spacing-m);
margin-left: 2px;
width: var(--spectrum-global-dimension-size-65);
height: 100%;
}
</style>

View File

@ -0,0 +1,31 @@
<svg
class="drag-handle spectrum-Icon spectrum-Icon--sizeS"
focusable="false"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m1,11c0.55228,0 1,-0.4477 1,-1c0,-0.5523 -0.44772,-1 -1,-1c-0.55228,0 -1,0.4477 -1,1c0,0.5523 0.44772,1 1,1z"
/>
<path
d="m1,8c0.55228,0 1,-0.4477 1,-1c0,-0.55228 -0.44772,-1 -1,-1c-0.55228,0 -1,0.44772 -1,1c0,0.5523 0.44772,1 1,1z"
/>
<path
d="m1,5c0.55228,0 1,-0.44772 1,-1c0,-0.55228 -0.44772,-1 -1,-1c-0.55228,0 -1,0.44772 -1,1c0,0.55228 0.44772,1 1,1z"
/>
<path
d="m1,2c0.55228,0 1,-0.44772 1,-1c0,-0.55228 -0.44772,-1 -1,-1c-0.55228,0 -1,0.44772 -1,1c0,0.55228 0.44772,1 1,1z"
/>
<path
d="m4,11c0.5523,0 1,-0.4477 1,-1c0,-0.5523 -0.4477,-1 -1,-1c-0.55228,0 -1,0.4477 -1,1c0,0.5523 0.44772,1 1,1z"
/>
<path
d="m4,8c0.5523,0 1,-0.4477 1,-1c0,-0.55228 -0.4477,-1 -1,-1c-0.55228,0 -1,0.44772 -1,1c0,0.5523 0.44772,1 1,1z"
/>
<path
d="m4,5c0.5523,0 1,-0.44772 1,-1c0,-0.55228 -0.4477,-1 -1,-1c-0.55228,0 -1,0.44772 -1,1c0,0.55228 0.44772,1 1,1z"
/>
<path
d="m4,2c0.5523,0 1,-0.44772 1,-1c0,-0.55228 -0.4477,-1 -1,-1c-0.55228,0 -1,0.44772 -1,1c0,0.55228 0.44772,1 1,1z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -3,31 +3,35 @@
import { store } from "builderStore"
import { cloneDeep } from "lodash/fp"
import { createEventDispatcher } from "svelte"
import ComponentSettingsSection from "../../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte"
import ComponentSettingsSection from "../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte"
import { getContext } from "svelte"
export let anchor
export let field
export let componentInstance
export let componentBindings
export let bindings
export let parseSettings
const draggable = getContext("draggable")
const dispatch = createEventDispatcher()
let popover
let drawers = []
let pseudoComponentInstance
let open = false
$: if (open && $draggable.selected && $draggable.selected != field._id) {
// Auto hide the component when another item is selected
$: if (open && $draggable.selected != componentInstance._id) {
popover.hide()
}
$: if (field) {
pseudoComponentInstance = field
// Open automatically if the component is marked as selected
$: if (!open && $draggable.selected === componentInstance._id && popover) {
popover.show()
open = true
}
$: componentDef = store.actions.components.getDefinition(
pseudoComponentInstance._component
componentInstance._component
)
$: parsedComponentDef = processComponentDefinitionSettings(componentDef)
@ -36,17 +40,16 @@
return {}
}
const clone = cloneDeep(componentDef)
const updatedSettings = clone.settings
.filter(setting => setting.key !== "field")
.map(setting => {
return { ...setting, nested: true }
})
clone.settings = updatedSettings
if (typeof parseSettings === "function") {
clone.settings = parseSettings(clone.settings)
}
return clone
}
const updateSetting = async (setting, value) => {
const nestedComponentInstance = cloneDeep(pseudoComponentInstance)
const nestedComponentInstance = cloneDeep(componentInstance)
const patchFn = store.actions.components.updateComponentSetting(
setting.key,
@ -54,12 +57,7 @@
)
patchFn(nestedComponentInstance)
const update = {
...nestedComponentInstance,
active: pseudoComponentInstance.active,
}
dispatch("change", update)
dispatch("change", nestedComponentInstance)
}
const customPositionHandler = (anchorBounds, eleBounds, cfg) => {
@ -98,11 +96,11 @@
bind:this={popover}
on:open={() => {
drawers = []
$draggable.actions.select(field._id)
$draggable.actions.select(componentInstance._id)
}}
on:close={() => {
open = false
if ($draggable.selected == field._id) {
if ($draggable.selected == componentInstance._id) {
$draggable.actions.select()
}
}}
@ -115,12 +113,9 @@
>
<span class="popover-wrap">
<Layout noPadding noGap>
<div class="type-icon">
<Icon name={parsedComponentDef.icon} />
<span>{field.field}</span>
</div>
<slot name="header" />
<ComponentSettingsSection
componentInstance={pseudoComponentInstance}
{componentInstance}
componentDefinition={parsedComponentDef}
isScreen={false}
onUpdateSetting={updateSetting}
@ -143,20 +138,4 @@
.popover-wrap {
background-color: var(--spectrum-alias-background-color-primary);
}
.type-icon {
display: flex;
gap: var(--spacing-m);
margin: var(--spacing-xl);
margin-bottom: 0px;
height: var(--spectrum-alias-item-height-m);
padding: 0px var(--spectrum-alias-item-padding-m);
border-width: var(--spectrum-actionbutton-border-size);
border-radius: var(--spectrum-alias-border-radius-regular);
border: 1px solid
var(
--spectrum-actionbutton-m-border-color,
var(--spectrum-alias-border-color)
);
align-items: center;
}
</style>

View File

@ -7,7 +7,7 @@
getComponentBindableProperties,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import DraggableList from "../DraggableList.svelte"
import DraggableList from "../DraggableList/DraggableList.svelte"
import { createEventDispatcher } from "svelte"
import { store, selectedScreen } from "builderStore"
import FieldSetting from "./FieldSetting.svelte"

View File

@ -1,8 +1,11 @@
<script>
import EditFieldPopover from "./EditFieldPopover.svelte"
import { Toggle } from "@budibase/bbui"
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Toggle, Icon } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp"
import { store } from "builderStore"
import { runtimeToReadableBinding } from "builderStore/dataBinding"
import { isJSBinding } from "@budibase/string-templates"
export let item
export let componentBindings
@ -16,18 +19,43 @@
dispatch("change", { ...cloneDeep(item), active: e.detail })
}
}
const getReadableText = () => {
if (item.label) {
return isJSBinding(item.label)
? "(JavaScript function)"
: runtimeToReadableBinding([...bindings, componentBindings], item.label)
}
return item.field
}
const parseSettings = settings => {
return settings
.filter(setting => setting.key !== "field")
.map(setting => {
return { ...setting, nested: true }
})
}
$: readableText = getReadableText(item)
$: componentDef = store.actions.components.getDefinition(item._component)
</script>
<div class="list-item-body">
<div class="list-item-left">
<EditFieldPopover
<EditComponentPopover
{anchor}
field={item}
componentInstance={item}
{componentBindings}
{bindings}
{parseSettings}
on:change
/>
<div class="field-label">{item.label || item.field}</div>
>
<div slot="header" class="type-icon">
<Icon name={componentDef.icon} />
<span>{item.field}</span>
</div>
</EditComponentPopover>
<div class="field-label">{readableText}</div>
</div>
<div class="list-item-right">
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
@ -53,4 +81,20 @@
.list-item-body {
justify-content: space-between;
}
.type-icon {
display: flex;
gap: var(--spacing-m);
margin: var(--spacing-xl);
margin-bottom: 0px;
height: var(--spectrum-alias-item-height-m);
padding: 0px var(--spectrum-alias-item-padding-m);
border-width: var(--spectrum-actionbutton-border-size);
border-radius: var(--spectrum-alias-border-radius-regular);
border: 1px solid
var(
--spectrum-actionbutton-m-border-color,
var(--spectrum-alias-border-color)
);
align-items: center;
}
</style>

View File

@ -258,7 +258,6 @@
"description": "Contains your app screens",
"static": true
},
"buttongroup": {
"name": "Button group",
"icon": "Button",
@ -272,17 +271,14 @@
"type": "buttonConfiguration",
"key": "buttons",
"nested": true,
"defaultValue" : [{
"component" : "button",
"props" : {
"defaultValue": [
{
"type": "cta"
},
{
"type": "primary"
}
},{
"component" : "button",
"props" : {
"type" : "primary"
}
}]
]
}
]
},
@ -310,7 +306,7 @@
"barTitle": "Row layout"
}
],
"defaultValue": "column"
"defaultValue": "row"
},
{
"type": "select",
@ -440,7 +436,6 @@
}
]
},
"button": {
"name": "Button",
"description": "A basic html button that is ready for styling",
@ -2592,7 +2587,6 @@
"key": "disabled",
"defaultValue": false
},
{
"type": "text",
"label": "Initial form step",
@ -5875,4 +5869,4 @@
}
]
}
}
}

View File

@ -0,0 +1,37 @@
<script>
import BlockComponent from "../../BlockComponent.svelte"
import Block from "../../Block.svelte"
export let buttons = []
export let direction
export let hAlign
export let vAlign
export let gap = "S"
</script>
<Block>
<BlockComponent
type="container"
props={{
direction,
hAlign,
vAlign,
gap,
wrap: true,
}}
>
{#each buttons as { text, type, quiet, disabled, onClick, size }, idx}
<BlockComponent
type="button"
props={{
text: text || `Button ${idx + 1}`,
onClick,
type,
quiet,
disabled,
size,
}}
/>
{/each}
</BlockComponent>
</Block>

View File

@ -1,38 +0,0 @@
<script>
import { getContext } from "svelte"
import Container from "components/app/Container.svelte"
import BlockComponent from "../../BlockComponent.svelte"
import Block from "../../Block.svelte"
const { styleable } = getContext("sdk")
const component = getContext("component")
export let buttons = []
export let direction
export let hAlign
export let vAlign
export let gap = "S"
export let wrap
$: console.log("client buttons/wrap", buttons, wrap)
</script>
<!-- <div use:styleable={$component.styles}> -->
<Block>
<Container {direction} {hAlign} {vAlign} {gap} {wrap}>
{#each buttons as { text, type, quiet, disabled, onClick, size }}
<BlockComponent
type="button"
props={{
text,
onClick,
type,
quiet,
disabled,
size
}}
/>
{/each}
</Container>
</Block>
<!-- </div> -->

View File

@ -15,5 +15,4 @@ export { default as formstep } from "./FormStep.svelte"
export { default as jsonfield } from "./JSONField.svelte"
export { default as s3upload } from "./S3Upload.svelte"
export { default as codescanner } from "./CodeScannerField.svelte"
export { default as bbreferencefield } from "./BBReferenceField.svelte"
export { default as buttongroup } from "./ButtonGroup.svelte"
export { default as bbreferencefield } from "./BBReferenceField.svelte"

View File

@ -19,6 +19,7 @@ export { default as dataprovider } from "./DataProvider.svelte"
export { default as divider } from "./Divider.svelte"
export { default as screenslot } from "./ScreenSlot.svelte"
export { default as button } from "./Button.svelte"
export { default as buttongroup } from "./ButtonGroup.svelte"
export { default as repeater } from "./Repeater.svelte"
export { default as text } from "./Text.svelte"
export { default as layout } from "./Layout.svelte"