Merge branch 'master' into bug/budi-7681-groups-list-infinite-reload-loop

This commit is contained in:
Michael Drury 2023-10-27 10:14:39 +01:00 committed by GitHub
commit fec5015422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 677 additions and 140 deletions

View File

@ -24,17 +24,23 @@
let selectedRows = [] let selectedRows = []
let customRenderers = [] let customRenderers = []
let parsedSchema = {}
$: if (schema) {
parsedSchema = Object.keys(schema).reduce((acc, key) => {
acc[key] =
typeof schema[key] === "string" ? { type: schema[key] } : schema[key]
if (!canBeSortColumn(acc[key].type)) {
acc[key].sortable = false
}
return acc
}, {})
}
$: selectedRows, dispatch("selectionUpdated", selectedRows) $: selectedRows, dispatch("selectionUpdated", selectedRows)
$: isUsersTable = tableId === TableNames.USERS $: isUsersTable = tableId === TableNames.USERS
$: data && resetSelectedRows() $: data && resetSelectedRows()
$: {
Object.values(schema || {}).forEach(col => {
if (!canBeSortColumn(col.type)) {
col.sortable = false
}
})
}
$: { $: {
if (isUsersTable) { if (isUsersTable) {
customRenderers = [ customRenderers = [
@ -44,24 +50,24 @@
}, },
] ]
UNEDITABLE_USER_FIELDS.forEach(field => { UNEDITABLE_USER_FIELDS.forEach(field => {
if (schema[field]) { if (parsedSchema[field]) {
schema[field].editable = false parsedSchema[field].editable = false
} }
}) })
if (schema.email) { if (parsedSchema.email) {
schema.email.displayName = "Email" parsedSchema.email.displayName = "Email"
} }
if (schema.roleId) { if (parsedSchema.roleId) {
schema.roleId.displayName = "Role" parsedSchema.roleId.displayName = "Role"
} }
if (schema.firstName) { if (parsedSchema.firstName) {
schema.firstName.displayName = "First Name" parsedSchema.firstName.displayName = "First Name"
} }
if (schema.lastName) { if (parsedSchema.lastName) {
schema.lastName.displayName = "Last Name" parsedSchema.lastName.displayName = "Last Name"
} }
if (schema.status) { if (parsedSchema.status) {
schema.status.displayName = "Status" parsedSchema.status.displayName = "Status"
} }
} }
} }
@ -97,7 +103,7 @@
<div class="table-wrapper"> <div class="table-wrapper">
<Table <Table
{data} {data}
{schema} schema={parsedSchema}
{loading} {loading}
{customRenderers} {customRenderers}
{rowCount} {rowCount}

View File

@ -20,6 +20,7 @@
let type = "internal" let type = "internal"
$: name = view.name $: name = view.name
$: schema = view.schema
$: calculation = view.calculation $: calculation = view.calculation
$: supportedFormats = Object.values(ROW_EXPORT_FORMATS).filter(key => { $: supportedFormats = Object.values(ROW_EXPORT_FORMATS).filter(key => {
@ -61,7 +62,7 @@
<Table <Table
title={decodeURI(name)} title={decodeURI(name)}
schema={view.schema} {schema}
tableId={view.tableId} tableId={view.tableId}
{data} {data}
{loading} {loading}

View File

@ -23,6 +23,7 @@ import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte" import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte"
import BarButtonList from "./controls/BarButtonList.svelte" import BarButtonList from "./controls/BarButtonList.svelte"
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte" import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte"
import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte" import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte"
const componentMap = { const componentMap = {
@ -48,6 +49,7 @@ const componentMap = {
"filter/relationship": RelationshipFilterEditor, "filter/relationship": RelationshipFilterEditor,
url: URLSelect, url: URLSelect,
fieldConfiguration: FieldConfiguration, fieldConfiguration: FieldConfiguration,
buttonConfiguration: ButtonConfiguration,
columns: ColumnEditor, columns: ColumnEditor,
"columns/basic": BasicColumnEditor, "columns/basic": BasicColumnEditor,
"columns/grid": GridColumnEditor, "columns/grid": GridColumnEditor,

View File

@ -0,0 +1,134 @@
<script>
import DraggableList from "../DraggableList/DraggableList.svelte"
import ButtonSetting from "./ButtonSetting.svelte"
import { createEventDispatcher } from "svelte"
import { store } from "builderStore"
import { Helpers } from "@budibase/bbui"
export let componentBindings
export let bindings
export let value
const dispatch = createEventDispatcher()
let focusItem
$: buttonList = sanitizeValue(value) || []
$: buttonCount = buttonList.length
$: itemProps = {
componentBindings: componentBindings || [],
bindings,
removeButton,
canRemove: buttonCount > 1,
}
const sanitizeValue = val => {
return val?.map(button => {
return button._component ? button : buildPseudoInstance(button)
})
}
const processItemUpdate = e => {
const updatedField = e.detail
const newButtonList = [...buttonList]
const fieldIdx = newButtonList.findIndex(pSetting => {
return pSetting._id === updatedField?._id
})
if (fieldIdx === -1) {
newButtonList.push(updatedField)
} else {
newButtonList[fieldIdx] = updatedField
}
dispatch("change", newButtonList)
}
const listUpdated = e => {
dispatch("change", [...e.detail])
}
const buildPseudoInstance = cfg => {
return store.actions.components.createInstance(
`@budibase/standard-components/button`,
{
_instanceName: Helpers.uuid(),
text: cfg.text,
type: cfg.type || "primary",
},
{}
)
}
const addButton = () => {
const newButton = buildPseudoInstance({
text: `Button ${buttonCount + 1}`,
})
dispatch("change", [...buttonList, newButton])
focusItem = newButton._id
}
const removeButton = id => {
dispatch(
"change",
buttonList.filter(button => button._id !== id)
)
}
</script>
<div class="button-configuration">
{#if buttonCount}
<DraggableList
on:change={listUpdated}
on:itemChange={processItemUpdate}
items={buttonList}
listItemKey={"_id"}
listType={ButtonSetting}
listTypeProps={itemProps}
focus={focusItem}
draggable={buttonCount > 1}
/>
<div class="list-footer" on:click={addButton}>
<div class="add-button">Add button</div>
</div>
{/if}
</div>
<style>
.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

@ -0,0 +1,64 @@
<script>
import EditComponentPopover from "../EditComponentPopover.svelte"
import { Icon } from "@budibase/bbui"
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
$: readableText = isJSBinding(item.text)
? "(JavaScript function)"
: runtimeToReadableBinding([...bindings, componentBindings], item.text)
</script>
<div class="list-item-body">
<div class="list-item-left">
<EditComponentPopover
{anchor}
componentInstance={item}
{componentBindings}
{bindings}
on:change
/>
<div class="field-label">{readableText || "Button"}</div>
</div>
<div class="list-item-right">
<Icon
disabled={!canRemove}
size="S"
name="Close"
hoverable
on:click={() => removeButton(item._id)}
/>
</div>
</div>
<style>
.field-label {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.list-item-body,
.list-item-left {
display: flex;
align-items: center;
gap: var(--spacing-m);
min-width: 0;
}
.list-item-body {
margin-top: 8px;
margin-bottom: 8px;
}
.list-item-right :global(div.spectrum-Switch) {
margin: 0px;
}
.list-item-body {
justify-content: space-between;
}
</style>

View File

@ -1,10 +1,10 @@
<script> <script>
import { Icon } from "@budibase/bbui"
import { dndzone } from "svelte-dnd-action" import { dndzone } from "svelte-dnd-action"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { generate } from "shortid" import { generate } from "shortid"
import { setContext } from "svelte" 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 items = []
export let showHandle = true export let showHandle = true
@ -12,6 +12,7 @@
export let listTypeProps = {} export let listTypeProps = {}
export let listItemKey export let listItemKey
export let draggable = true export let draggable = true
export let focus
let store = writable({ let store = writable({
selected: null, selected: null,
@ -27,6 +28,10 @@
setContext("draggable", store) setContext("draggable", store)
$: if (focus && store) {
get(store).actions.select(focus)
}
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const flipDurationMs = 150 const flipDurationMs = 150
@ -82,13 +87,16 @@
> >
{#each draggableItems as draggable (draggable.id)} {#each draggableItems as draggable (draggable.id)}
<li <li
on:mousedown={() => {
get(store).actions.select()
}}
bind:this={anchors[draggable.id]} bind:this={anchors[draggable.id]}
class:highlighted={draggable.id === $store.selected} class:highlighted={draggable.id === $store.selected}
> >
<div class="left-content"> <div class="left-content">
{#if showHandle} {#if showHandle}
<div class="handle" aria-label="drag-handle"> <div class="handle">
<Icon name="DragHandle" size="XL" /> <DragHandle />
</div> </div>
{/if} {/if}
</div> </div>
@ -142,8 +150,9 @@
border-top-right-radius: 4px; border-top-right-radius: 4px;
} }
.list-wrap > li:last-child { .list-wrap > li:last-child {
border-top-left-radius: var(--spectrum-table-regular-border-radius); border-bottom-left-radius: 4px;
border-top-right-radius: var(--spectrum-table-regular-border-radius); border-bottom-right-radius: 4px;
border-bottom: 0px;
} }
.right-content { .right-content {
flex: 1; flex: 1;
@ -153,4 +162,15 @@
padding-left: var(--spacing-s); padding-left: var(--spacing-s);
padding-right: 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> </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 { store } from "builderStore"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { createEventDispatcher } from "svelte" 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" import { getContext } from "svelte"
export let anchor export let anchor
export let field export let componentInstance
export let componentBindings export let componentBindings
export let bindings export let bindings
export let parseSettings
const draggable = getContext("draggable") const draggable = getContext("draggable")
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let popover let popover
let drawers = [] let drawers = []
let pseudoComponentInstance
let open = false 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() popover.hide()
} }
$: if (field) { // Open automatically if the component is marked as selected
pseudoComponentInstance = field $: if (!open && $draggable.selected === componentInstance._id && popover) {
popover.show()
open = true
} }
$: componentDef = store.actions.components.getDefinition( $: componentDef = store.actions.components.getDefinition(
pseudoComponentInstance._component componentInstance._component
) )
$: parsedComponentDef = processComponentDefinitionSettings(componentDef) $: parsedComponentDef = processComponentDefinitionSettings(componentDef)
@ -36,17 +40,16 @@
return {} return {}
} }
const clone = cloneDeep(componentDef) const clone = cloneDeep(componentDef)
const updatedSettings = clone.settings
.filter(setting => setting.key !== "field") if (typeof parseSettings === "function") {
.map(setting => { clone.settings = parseSettings(clone.settings)
return { ...setting, nested: true } }
})
clone.settings = updatedSettings
return clone return clone
} }
const updateSetting = async (setting, value) => { const updateSetting = async (setting, value) => {
const nestedComponentInstance = cloneDeep(pseudoComponentInstance) const nestedComponentInstance = cloneDeep(componentInstance)
const patchFn = store.actions.components.updateComponentSetting( const patchFn = store.actions.components.updateComponentSetting(
setting.key, setting.key,
@ -54,12 +57,26 @@
) )
patchFn(nestedComponentInstance) patchFn(nestedComponentInstance)
const update = { dispatch("change", nestedComponentInstance)
...nestedComponentInstance, }
active: pseudoComponentInstance.active,
const customPositionHandler = (anchorBounds, eleBounds, cfg) => {
let { left, top } = cfg
let percentageOffset = 30
// left-outside
left = anchorBounds.left - eleBounds.width - 18
// shift up from the anchor, if space allows
let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset
let defaultTop = anchorBounds.top - offsetPos
if (window.innerHeight - defaultTop < eleBounds.height) {
top = window.innerHeight - eleBounds.height - 5
} else {
top = anchorBounds.top - offsetPos
} }
dispatch("change", update) return { ...cfg, left, top }
} }
</script> </script>
@ -79,11 +96,11 @@
bind:this={popover} bind:this={popover}
on:open={() => { on:open={() => {
drawers = [] drawers = []
$draggable.actions.select(field._id) $draggable.actions.select(componentInstance._id)
}} }}
on:close={() => { on:close={() => {
open = false open = false
if ($draggable.selected == field._id) { if ($draggable.selected == componentInstance._id) {
$draggable.actions.select() $draggable.actions.select()
} }
}} }}
@ -92,33 +109,13 @@
showPopover={drawers.length == 0} showPopover={drawers.length == 0}
clickOutsideOverride={drawers.length > 0} clickOutsideOverride={drawers.length > 0}
maxHeight={600} maxHeight={600}
handlePostionUpdate={(anchorBounds, eleBounds, cfg) => { handlePostionUpdate={customPositionHandler}
let { left, top } = cfg
let percentageOffset = 30
// left-outside
left = anchorBounds.left - eleBounds.width - 18
// shift up from the anchor, if space allows
let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset
let defaultTop = anchorBounds.top - offsetPos
if (window.innerHeight - defaultTop < eleBounds.height) {
top = window.innerHeight - eleBounds.height - 5
} else {
top = anchorBounds.top - offsetPos
}
return { ...cfg, left, top }
}}
> >
<span class="popover-wrap"> <span class="popover-wrap">
<Layout noPadding noGap> <Layout noPadding noGap>
<div class="type-icon"> <slot name="header" />
<Icon name={parsedComponentDef.icon} />
<span>{field.field}</span>
</div>
<ComponentSettingsSection <ComponentSettingsSection
componentInstance={pseudoComponentInstance} {componentInstance}
componentDefinition={parsedComponentDef} componentDefinition={parsedComponentDef}
isScreen={false} isScreen={false}
onUpdateSetting={updateSetting} onUpdateSetting={updateSetting}
@ -141,20 +138,4 @@
.popover-wrap { .popover-wrap {
background-color: var(--spectrum-alias-background-color-primary); 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> </style>

View File

@ -7,7 +7,7 @@
getComponentBindableProperties, getComponentBindableProperties,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import { currentAsset } from "builderStore" import { currentAsset } from "builderStore"
import DraggableList from "../DraggableList.svelte" import DraggableList from "../DraggableList/DraggableList.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { store, selectedScreen } from "builderStore" import { store, selectedScreen } from "builderStore"
import FieldSetting from "./FieldSetting.svelte" import FieldSetting from "./FieldSetting.svelte"
@ -50,7 +50,7 @@
updateSanitsedFields(sanitisedValue) updateSanitsedFields(sanitisedValue)
unconfigured = buildUnconfiguredOptions(schema, sanitisedFields) unconfigured = buildUnconfiguredOptions(schema, sanitisedFields)
fieldList = [...sanitisedFields, ...unconfigured] fieldList = [...sanitisedFields, ...unconfigured]
.map(buildSudoInstance) .map(buildPseudoInstance)
.filter(x => x != null) .filter(x => x != null)
} }
@ -104,7 +104,7 @@
}) })
} }
const buildSudoInstance = instance => { const buildPseudoInstance = instance => {
if (instance._component) { if (instance._component) {
return instance return instance
} }

View File

@ -1,8 +1,11 @@
<script> <script>
import EditFieldPopover from "./EditFieldPopover.svelte" import EditComponentPopover from "../EditComponentPopover.svelte"
import { Toggle } from "@budibase/bbui" import { Toggle, Icon } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { cloneDeep } from "lodash/fp" 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 item
export let componentBindings export let componentBindings
@ -16,18 +19,43 @@
dispatch("change", { ...cloneDeep(item), active: e.detail }) 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> </script>
<div class="list-item-body"> <div class="list-item-body">
<div class="list-item-left"> <div class="list-item-left">
<EditFieldPopover <EditComponentPopover
{anchor} {anchor}
field={item} componentInstance={item}
{componentBindings} {componentBindings}
{bindings} {bindings}
{parseSettings}
on:change 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>
<div class="list-item-right"> <div class="list-item-right">
<Toggle on:change={onToggle(item)} text="" value={item.active} thin /> <Toggle on:change={onToggle(item)} text="" value={item.active} thin />
@ -53,4 +81,20 @@
.list-item-body { .list-item-body {
justify-content: space-between; 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> </style>

View File

@ -91,7 +91,12 @@
/> />
{/if} {/if}
{#if section == "styles"} {#if section == "styles"}
<DesignSection {componentInstance} {componentDefinition} {bindings} /> <DesignSection
{componentInstance}
{componentBindings}
{componentDefinition}
{bindings}
/>
<CustomStylesSection <CustomStylesSection
{componentInstance} {componentInstance}
{componentDefinition} {componentDefinition}

View File

@ -16,18 +16,32 @@
export let isScreen = false export let isScreen = false
export let onUpdateSetting export let onUpdateSetting
export let showSectionTitle = true export let showSectionTitle = true
export let tag
$: sections = getSections(componentInstance, componentDefinition, isScreen) $: sections = getSections(
componentInstance,
componentDefinition,
isScreen,
tag
)
const getSections = (instance, definition, isScreen) => { const getSections = (instance, definition, isScreen, tag) => {
const settings = definition?.settings ?? [] const settings = definition?.settings ?? []
const generalSettings = settings.filter(setting => !setting.section) const generalSettings = settings.filter(
const customSections = settings.filter(setting => setting.section) setting => !setting.section && setting.tag === tag
)
const customSections = settings.filter(
setting => setting.section && setting.tag === tag
)
let sections = [ let sections = [
{ ...(generalSettings?.length
name: "General", ? [
settings: generalSettings, {
}, name: "General",
settings: generalSettings,
},
]
: []),
...(customSections || []), ...(customSections || []),
] ]
@ -132,7 +146,7 @@
<div class="section-info"> <div class="section-info">
<InfoDisplay body={section.info} /> <InfoDisplay body={section.info} />
</div> </div>
{:else if idx === 0 && section.name === "General" && componentDefinition.info} {:else if idx === 0 && section.name === "General" && componentDefinition?.info && !tag}
<InfoDisplay <InfoDisplay
title={componentDefinition.name} title={componentDefinition.name}
body={componentDefinition.info} body={componentDefinition.info}
@ -181,7 +195,7 @@
</DetailSummary> </DetailSummary>
{/if} {/if}
{/each} {/each}
{#if componentDefinition?.block} {#if componentDefinition?.block && !tag}
<DetailSummary name="Eject" collapsible={false}> <DetailSummary name="Eject" collapsible={false}>
<EjectBlockButton /> <EjectBlockButton />
</DetailSummary> </DetailSummary>

View File

@ -1,10 +1,12 @@
<script> <script>
import StyleSection from "./StyleSection.svelte" import StyleSection from "./StyleSection.svelte"
import * as ComponentStyles from "./componentStyles" import * as ComponentStyles from "./componentStyles"
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
export let componentDefinition export let componentDefinition
export let componentInstance export let componentInstance
export let bindings export let bindings
export let componentBindings
const getStyles = def => { const getStyles = def => {
if (!def?.styles?.length) { if (!def?.styles?.length) {
@ -22,6 +24,19 @@
$: styles = getStyles(componentDefinition) $: styles = getStyles(componentDefinition)
</script> </script>
<!--
Load any general settings or sections tagged as "style"
-->
<ComponentSettingsSection
{componentInstance}
{componentDefinition}
isScreen={false}
showInstanceName={false}
{bindings}
{componentBindings}
tag="style"
/>
{#if styles?.length > 0} {#if styles?.length > 0}
{#each styles as style} {#each styles as style}
<StyleSection <StyleSection

View File

@ -36,6 +36,7 @@
"heading", "heading",
"text", "text",
"button", "button",
"buttongroup",
"tag", "tag",
"spectrumcard", "spectrumcard",
"cardstat", "cardstat",

View File

@ -258,6 +258,186 @@
"description": "Contains your app screens", "description": "Contains your app screens",
"static": true "static": true
}, },
"buttongroup": {
"name": "Button group",
"icon": "Button",
"hasChildren": false,
"settings": [
{
"section": true,
"name": "Buttons",
"settings": [
{
"type": "buttonConfiguration",
"key": "buttons",
"nested": true,
"defaultValue": [
{
"type": "cta",
"text": "Button 1"
},
{
"type": "primary",
"text": "Button 2"
}
]
}
]
},
{
"section": true,
"name": "Layout",
"settings": [
{
"type": "select",
"label": "Direction",
"key": "direction",
"showInBar": true,
"barStyle": "buttons",
"options": [
{
"label": "Column",
"value": "column",
"barIcon": "ViewColumn",
"barTitle": "Column layout"
},
{
"label": "Row",
"value": "row",
"barIcon": "ViewRow",
"barTitle": "Row layout"
}
],
"defaultValue": "row"
},
{
"type": "select",
"label": "Horiz. align",
"key": "hAlign",
"showInBar": true,
"barStyle": "buttons",
"options": [
{
"label": "Left",
"value": "left",
"barIcon": "AlignLeft",
"barTitle": "Align left"
},
{
"label": "Center",
"value": "center",
"barIcon": "AlignCenter",
"barTitle": "Align center"
},
{
"label": "Right",
"value": "right",
"barIcon": "AlignRight",
"barTitle": "Align right"
},
{
"label": "Stretch",
"value": "stretch",
"barIcon": "MoveLeftRight",
"barTitle": "Align stretched horizontally"
}
],
"defaultValue": "left"
},
{
"type": "select",
"label": "Vert. align",
"key": "vAlign",
"showInBar": true,
"barStyle": "buttons",
"options": [
{
"label": "Top",
"value": "top",
"barIcon": "AlignTop",
"barTitle": "Align top"
},
{
"label": "Middle",
"value": "middle",
"barIcon": "AlignMiddle",
"barTitle": "Align middle"
},
{
"label": "Bottom",
"value": "bottom",
"barIcon": "AlignBottom",
"barTitle": "Align bottom"
},
{
"label": "Stretch",
"value": "stretch",
"barIcon": "MoveUpDown",
"barTitle": "Align stretched vertically"
}
],
"defaultValue": "top"
},
{
"type": "select",
"label": "Size",
"key": "size",
"showInBar": true,
"barStyle": "buttons",
"options": [
{
"label": "Shrink",
"value": "shrink",
"barIcon": "Minimize",
"barTitle": "Shrink container"
},
{
"label": "Grow",
"value": "grow",
"barIcon": "Maximize",
"barTitle": "Grow container"
}
],
"defaultValue": "shrink"
},
{
"type": "select",
"label": "Gap",
"key": "gap",
"showInBar": true,
"barStyle": "picker",
"options": [
{
"label": "None",
"value": "N"
},
{
"label": "Small",
"value": "S"
},
{
"label": "Medium",
"value": "M"
},
{
"label": "Large",
"value": "L"
}
],
"defaultValue": "M"
},
{
"type": "boolean",
"label": "Wrap",
"key": "wrap",
"showInBar": true,
"barIcon": "ModernGridView",
"barTitle": "Wrap"
}
]
}
]
},
"button": { "button": {
"name": "Button", "name": "Button",
"description": "A basic html button that is ready for styling", "description": "A basic html button that is ready for styling",
@ -2409,7 +2589,6 @@
"key": "disabled", "key": "disabled",
"defaultValue": false "defaultValue": false
}, },
{ {
"type": "text", "type": "text",
"label": "Initial form step", "label": "Initial form step",
@ -5391,38 +5570,6 @@
"section": true, "section": true,
"name": "Fields", "name": "Fields",
"settings": [ "settings": [
{
"type": "select",
"label": "Align labels",
"key": "labelPosition",
"defaultValue": "left",
"options": [
{
"label": "Left",
"value": "left"
},
{
"label": "Above",
"value": "above"
}
]
},
{
"type": "select",
"label": "Size",
"key": "size",
"options": [
{
"label": "Medium",
"value": "spectrum--medium"
},
{
"label": "Large",
"value": "spectrum--large"
}
],
"defaultValue": "spectrum--medium"
},
{ {
"type": "fieldConfiguration", "type": "fieldConfiguration",
"key": "fields", "key": "fields",
@ -5442,6 +5589,40 @@
} }
} }
] ]
},
{
"tag": "style",
"type": "select",
"label": "Align labels",
"key": "labelPosition",
"defaultValue": "left",
"options": [
{
"label": "Left",
"value": "left"
},
{
"label": "Above",
"value": "above"
}
]
},
{
"tag": "style",
"type": "select",
"label": "Size",
"key": "size",
"options": [
{
"label": "Medium",
"value": "spectrum--medium"
},
{
"label": "Large",
"value": "spectrum--large"
}
],
"defaultValue": "spectrum--medium"
} }
], ],
"context": [ "context": [
@ -5743,4 +5924,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 }}
<BlockComponent
type="button"
props={{
text: text || "Button",
onClick,
type,
quiet,
disabled,
size,
}}
/>
{/each}
</BlockComponent>
</Block>

View File

@ -19,6 +19,7 @@ export { default as dataprovider } from "./DataProvider.svelte"
export { default as divider } from "./Divider.svelte" export { default as divider } from "./Divider.svelte"
export { default as screenslot } from "./ScreenSlot.svelte" export { default as screenslot } from "./ScreenSlot.svelte"
export { default as button } from "./Button.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 repeater } from "./Repeater.svelte"
export { default as text } from "./Text.svelte" export { default as text } from "./Text.svelte"
export { default as layout } from "./Layout.svelte" export { default as layout } from "./Layout.svelte"