Merge branch 'master' into chore/cleanup_v2
This commit is contained in:
commit
4f3df1a58b
12 changed files with 887 additions and 44 deletions
|
@ -20,7 +20,7 @@ import ValidationEditor from "./controls/ValidationEditor/ValidationEditor.svelt
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte"
|
import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte"
|
||||||
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
|
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
|
||||||
import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte"
|
import GridColumnEditor from "./controls/GridColumnConfiguration/GridColumnConfiguration.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 ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte"
|
||||||
|
@ -28,6 +28,7 @@ import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: DrawerBindableInput,
|
text: DrawerBindableInput,
|
||||||
|
plainText: Input,
|
||||||
select: Select,
|
select: Select,
|
||||||
radio: RadioGroup,
|
radio: RadioGroup,
|
||||||
dataSource: DataSourceSelect,
|
dataSource: DataSourceSelect,
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<script>
|
|
||||||
import ColumnEditor from "./ColumnEditor.svelte"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ColumnEditor
|
|
||||||
{...$$props}
|
|
||||||
on:change
|
|
||||||
allowCellEditing={false}
|
|
||||||
allowReorder={false}
|
|
||||||
/>
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script>
|
||||||
|
import EditComponentPopover from "../EditComponentPopover.svelte"
|
||||||
|
import { FieldTypeToComponentMap } from "../FieldConfiguration/utils"
|
||||||
|
import { Toggle, Icon } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
export let item
|
||||||
|
export let anchor
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const onToggle = item => {
|
||||||
|
return e => {
|
||||||
|
item.active = e.detail
|
||||||
|
dispatch("change", { ...cloneDeep(item), active: e.detail })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseSettings = settings => {
|
||||||
|
return settings
|
||||||
|
.filter(setting => setting.key !== "field")
|
||||||
|
.map(setting => {
|
||||||
|
return { ...setting, nested: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIcon = () => {
|
||||||
|
const component = `@budibase/standard-components/${
|
||||||
|
FieldTypeToComponentMap[item.columnType]
|
||||||
|
}`
|
||||||
|
return store.actions.components.getDefinition(component).icon
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="list-item-body">
|
||||||
|
<div class="list-item-left">
|
||||||
|
<EditComponentPopover
|
||||||
|
{anchor}
|
||||||
|
componentInstance={item}
|
||||||
|
{parseSettings}
|
||||||
|
on:change
|
||||||
|
>
|
||||||
|
<div slot="header" class="type-icon">
|
||||||
|
<Icon name={getIcon()} />
|
||||||
|
<span>{item.field}</span>
|
||||||
|
</div>
|
||||||
|
</EditComponentPopover>
|
||||||
|
<div class="field-label">{item.label || item.field}</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-item-right">
|
||||||
|
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
|
||||||
|
</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-right :global(div.spectrum-Switch) {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.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>
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
getDatasourceForProvider,
|
||||||
|
getSchemaForDatasource,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import DraggableList from "../DraggableList/DraggableList.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import FieldSetting from "./FieldSetting.svelte"
|
||||||
|
import PrimaryColumnFieldSetting from "./PrimaryColumnFieldSetting.svelte"
|
||||||
|
import getColumns from "./getColumns.js"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
export let componentInstance
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
let primaryDisplayColumnAnchor
|
||||||
|
|
||||||
|
const handleChange = newValues => {
|
||||||
|
dispatch("change", newValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSchema = (asset, datasource) => {
|
||||||
|
const schema = getSchemaForDatasource(asset, datasource).schema
|
||||||
|
|
||||||
|
// Don't show ID and rev in tables
|
||||||
|
if (schema) {
|
||||||
|
delete schema._id
|
||||||
|
delete schema._rev
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
|
$: primaryDisplayColumnName = getSchemaForDatasource(
|
||||||
|
$currentAsset,
|
||||||
|
datasource
|
||||||
|
)?.table?.primaryDisplay
|
||||||
|
$: schema = getSchema(currentAsset, datasource)
|
||||||
|
$: columns = getColumns({
|
||||||
|
columns: value,
|
||||||
|
schema,
|
||||||
|
primaryDisplayColumnName,
|
||||||
|
onChange: handleChange,
|
||||||
|
createComponent: store.actions.components.createInstance,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if columns.primary}
|
||||||
|
<div class="sticky-item">
|
||||||
|
<div bind:this={primaryDisplayColumnAnchor} class="sticky-item-inner">
|
||||||
|
<div class="right-content">
|
||||||
|
<PrimaryColumnFieldSetting
|
||||||
|
anchor={primaryDisplayColumnAnchor}
|
||||||
|
item={columns.primary}
|
||||||
|
on:change={e => columns.update(e.detail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<DraggableList
|
||||||
|
on:change={e => columns.updateSortable(e.detail)}
|
||||||
|
on:itemChange={e => columns.update(e.detail)}
|
||||||
|
items={columns.sortable}
|
||||||
|
listItemKey={"_id"}
|
||||||
|
listType={FieldSetting}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.right-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 17.5px;
|
||||||
|
}
|
||||||
|
.sticky-item {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(
|
||||||
|
--spectrum-table-background-color,
|
||||||
|
var(--spectrum-global-color-gray-50)
|
||||||
|
);
|
||||||
|
border: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.sticky-item-inner {
|
||||||
|
background-color: var(
|
||||||
|
--spectrum-table-background-color,
|
||||||
|
var(--spectrum-global-color-gray-50)
|
||||||
|
);
|
||||||
|
transition: background-color ease-in-out 130ms;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid));
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
|
padding-right: var(--spacing-s);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,100 @@
|
||||||
|
<script>
|
||||||
|
import EditComponentPopover from "../EditComponentPopover.svelte"
|
||||||
|
import { Icon } from "@budibase/bbui"
|
||||||
|
import { setContext } from "svelte"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { FieldTypeToComponentMap } from "../FieldConfiguration/utils"
|
||||||
|
import { store } from "builderStore"
|
||||||
|
|
||||||
|
export let item
|
||||||
|
export let anchor
|
||||||
|
|
||||||
|
let draggableStore = writable({
|
||||||
|
selected: null,
|
||||||
|
actions: {
|
||||||
|
select: id => {
|
||||||
|
draggableStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
selected: id,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
setContext("draggable", draggableStore)
|
||||||
|
|
||||||
|
const parseSettings = settings => {
|
||||||
|
return settings
|
||||||
|
.filter(setting => setting.key !== "field")
|
||||||
|
.map(setting => {
|
||||||
|
return { ...setting, nested: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getIcon = item => {
|
||||||
|
const component = `@budibase/standard-components/${
|
||||||
|
FieldTypeToComponentMap[item.columnType]
|
||||||
|
}`
|
||||||
|
return store.actions.components.getDefinition(component)?.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
$: icon = getIcon(item)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="list-item-body">
|
||||||
|
<div class="list-item-left">
|
||||||
|
<EditComponentPopover
|
||||||
|
{anchor}
|
||||||
|
componentInstance={item}
|
||||||
|
{parseSettings}
|
||||||
|
on:change
|
||||||
|
>
|
||||||
|
<div slot="header" class="type-icon">
|
||||||
|
<Icon name={icon} />
|
||||||
|
<span>{item.field}</span>
|
||||||
|
</div>
|
||||||
|
</EditComponentPopover>
|
||||||
|
<div class="field-label">{item.label || item.field}</div>
|
||||||
|
</div>
|
||||||
|
<div title="The display column is always shown first" class="list-item-right">
|
||||||
|
<Icon name={"Info"} />
|
||||||
|
</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-right :global(svg) {
|
||||||
|
color: var(--grey-5);
|
||||||
|
padding: 7px 5px 7px 0;
|
||||||
|
}
|
||||||
|
.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>
|
|
@ -0,0 +1,129 @@
|
||||||
|
const modernize = columns => {
|
||||||
|
if (!columns) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
// If the first element has no active key then it's safe to assume all elements are in the old format
|
||||||
|
if (columns?.[0] && columns[0].active === undefined) {
|
||||||
|
return columns.map(column => ({
|
||||||
|
label: column.displayName,
|
||||||
|
field: column.name,
|
||||||
|
active: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeInvalidAddMissing = (
|
||||||
|
columns = [],
|
||||||
|
defaultColumns,
|
||||||
|
primaryDisplayColumnName
|
||||||
|
) => {
|
||||||
|
const defaultColumnNames = defaultColumns.map(column => column.field)
|
||||||
|
const columnNames = columns.map(column => column.field)
|
||||||
|
|
||||||
|
const validColumns = columns.filter(column =>
|
||||||
|
defaultColumnNames.includes(column.field)
|
||||||
|
)
|
||||||
|
let missingColumns = defaultColumns.filter(
|
||||||
|
defaultColumn => !columnNames.includes(defaultColumn.field)
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the user already has fields selected, any appended missing fields should be disabled by default
|
||||||
|
if (validColumns.length) {
|
||||||
|
missingColumns = missingColumns.map(field => ({ ...field, active: false }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedColumns = [...validColumns, ...missingColumns]
|
||||||
|
|
||||||
|
// Ensure the primary display column is always visible
|
||||||
|
const primaryDisplayIndex = combinedColumns.findIndex(
|
||||||
|
column => column.field === primaryDisplayColumnName
|
||||||
|
)
|
||||||
|
if (primaryDisplayIndex > -1) {
|
||||||
|
combinedColumns[primaryDisplayIndex].active = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return combinedColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefault = (schema = {}) => {
|
||||||
|
const defaultValues = Object.values(schema)
|
||||||
|
.filter(column => !column.nestedJSON)
|
||||||
|
.map(column => ({
|
||||||
|
label: column.name,
|
||||||
|
field: column.name,
|
||||||
|
active: column.visible ?? true,
|
||||||
|
order: column.visible ? column.order ?? -1 : Number.MAX_SAFE_INTEGER,
|
||||||
|
}))
|
||||||
|
|
||||||
|
defaultValues.sort((a, b) => a.order - b.order)
|
||||||
|
|
||||||
|
return defaultValues
|
||||||
|
}
|
||||||
|
|
||||||
|
const toGridFormat = draggableListColumns => {
|
||||||
|
return draggableListColumns.map(entry => ({
|
||||||
|
label: entry.label,
|
||||||
|
field: entry.field,
|
||||||
|
active: entry.active,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
|
||||||
|
return gridFormatColumns.map(column => {
|
||||||
|
return createComponent(
|
||||||
|
"@budibase/standard-components/labelfield",
|
||||||
|
{
|
||||||
|
_instanceName: column.field,
|
||||||
|
active: column.active,
|
||||||
|
field: column.field,
|
||||||
|
label: column.label,
|
||||||
|
columnType: schema[column.field].type,
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getColumns = ({
|
||||||
|
columns,
|
||||||
|
schema,
|
||||||
|
primaryDisplayColumnName,
|
||||||
|
onChange,
|
||||||
|
createComponent,
|
||||||
|
}) => {
|
||||||
|
const validatedColumns = removeInvalidAddMissing(
|
||||||
|
modernize(columns),
|
||||||
|
getDefault(schema),
|
||||||
|
primaryDisplayColumnName
|
||||||
|
)
|
||||||
|
const draggableList = toDraggableListFormat(
|
||||||
|
validatedColumns,
|
||||||
|
createComponent,
|
||||||
|
schema
|
||||||
|
)
|
||||||
|
const primary = draggableList.find(
|
||||||
|
entry => entry.field === primaryDisplayColumnName
|
||||||
|
)
|
||||||
|
const sortable = draggableList.filter(
|
||||||
|
entry => entry.field !== primaryDisplayColumnName
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary,
|
||||||
|
sortable,
|
||||||
|
updateSortable: newDraggableList => {
|
||||||
|
onChange(toGridFormat(newDraggableList.concat(primary)))
|
||||||
|
},
|
||||||
|
update: newEntry => {
|
||||||
|
const newDraggableList = draggableList.map(entry => {
|
||||||
|
return newEntry.field === entry.field ? newEntry : entry
|
||||||
|
})
|
||||||
|
|
||||||
|
onChange(toGridFormat(newDraggableList))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getColumns
|
|
@ -0,0 +1,374 @@
|
||||||
|
import { it, expect, describe, beforeEach, vi } from "vitest"
|
||||||
|
import getColumns from "./getColumns"
|
||||||
|
|
||||||
|
describe("getColumns", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
ctx.schema = {
|
||||||
|
one: { name: "one", visible: false, order: 0, type: "foo" },
|
||||||
|
two: { name: "two", visible: true, order: 1, type: "foo" },
|
||||||
|
three: { name: "three", visible: true, order: 2, type: "foo" },
|
||||||
|
four: { name: "four", visible: false, order: 3, type: "foo" },
|
||||||
|
five: {
|
||||||
|
name: "excluded",
|
||||||
|
visible: true,
|
||||||
|
order: 4,
|
||||||
|
type: "foo",
|
||||||
|
nestedJSON: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.primaryDisplayColumnName = "four"
|
||||||
|
ctx.onChange = vi.fn()
|
||||||
|
ctx.createComponent = (componentName, props) => {
|
||||||
|
return { componentName, ...props }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("nested json fields", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
ctx.columns = getColumns({
|
||||||
|
columns: null,
|
||||||
|
schema: ctx.schema,
|
||||||
|
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||||
|
onChange: ctx.onChange,
|
||||||
|
createComponent: ctx.createComponent,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not return nested json fields, as the grid cannot display them", ctx => {
|
||||||
|
expect(ctx.columns.sortable).not.toContainEqual({
|
||||||
|
name: "excluded",
|
||||||
|
visible: true,
|
||||||
|
order: 4,
|
||||||
|
type: "foo",
|
||||||
|
nestedJSON: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("using the old grid column format", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
const oldGridFormatColumns = [
|
||||||
|
{ displayName: "three label", name: "three" },
|
||||||
|
{ displayName: "two label", name: "two" },
|
||||||
|
]
|
||||||
|
|
||||||
|
ctx.columns = getColumns({
|
||||||
|
columns: oldGridFormatColumns,
|
||||||
|
schema: ctx.schema,
|
||||||
|
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||||
|
onChange: ctx.onChange,
|
||||||
|
createComponent: ctx.createComponent,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the selected and unselected fields in the modern format, respecting the original order", ctx => {
|
||||||
|
expect(ctx.columns.sortable).toEqual([
|
||||||
|
{
|
||||||
|
_instanceName: "three",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "three",
|
||||||
|
label: "three label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "two",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "two",
|
||||||
|
label: "two label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "one",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "one",
|
||||||
|
label: "one",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(ctx.columns.primary).toEqual({
|
||||||
|
_instanceName: "four",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "four",
|
||||||
|
label: "four",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("default columns", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
ctx.columns = getColumns({
|
||||||
|
columns: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
|
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||||
|
onChange: ctx.onChange,
|
||||||
|
createComponent: ctx.createComponent,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns all columns, with non-hidden columns automatically selected", ctx => {
|
||||||
|
expect(ctx.columns.sortable).toEqual([
|
||||||
|
{
|
||||||
|
_instanceName: "two",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "two",
|
||||||
|
label: "two",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "three",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "three",
|
||||||
|
label: "three",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "one",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "one",
|
||||||
|
label: "one",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(ctx.columns.primary).toEqual({
|
||||||
|
_instanceName: "four",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "four",
|
||||||
|
label: "four",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Unselected columns should be placed at the end", ctx => {
|
||||||
|
expect(ctx.columns.sortable[2].field).toEqual("one")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("missing columns", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
const gridFormatColumns = [
|
||||||
|
{ label: "three label", field: "three", active: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
ctx.columns = getColumns({
|
||||||
|
columns: gridFormatColumns,
|
||||||
|
schema: ctx.schema,
|
||||||
|
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||||
|
onChange: ctx.onChange,
|
||||||
|
createComponent: ctx.createComponent,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns all columns, including those missing from the initial data", ctx => {
|
||||||
|
expect(ctx.columns.sortable).toEqual([
|
||||||
|
{
|
||||||
|
_instanceName: "three",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "three",
|
||||||
|
label: "three label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "two",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "two",
|
||||||
|
label: "two",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "one",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "one",
|
||||||
|
label: "one",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(ctx.columns.primary).toEqual({
|
||||||
|
_instanceName: "four",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "four",
|
||||||
|
label: "four",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("invalid columns", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
const gridFormatColumns = [
|
||||||
|
{ label: "three label", field: "three", active: true },
|
||||||
|
{ label: "some nonsense", field: "some nonsense", active: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
ctx.columns = getColumns({
|
||||||
|
columns: gridFormatColumns,
|
||||||
|
schema: ctx.schema,
|
||||||
|
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||||
|
onChange: ctx.onChange,
|
||||||
|
createComponent: ctx.createComponent,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns all valid columns, excluding those that aren't valid for the schema", ctx => {
|
||||||
|
expect(ctx.columns.sortable).toEqual([
|
||||||
|
{
|
||||||
|
_instanceName: "three",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "three",
|
||||||
|
label: "three label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "two",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "two",
|
||||||
|
label: "two",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "one",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "one",
|
||||||
|
label: "one",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(ctx.columns.primary).toEqual({
|
||||||
|
_instanceName: "four",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "four",
|
||||||
|
label: "four",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("methods", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
const { update, updateSortable } = getColumns({
|
||||||
|
columns: [],
|
||||||
|
schema: ctx.schema,
|
||||||
|
primaryDisplayColumnName: ctx.primaryDisplayColumnName,
|
||||||
|
onChange: ctx.onChange,
|
||||||
|
createComponent: ctx.createComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.update = update
|
||||||
|
ctx.updateSortable = updateSortable
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
ctx.update({
|
||||||
|
field: "one",
|
||||||
|
label: "a new label",
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls the callback with the updated columns", ctx => {
|
||||||
|
expect(ctx.onChange).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ctx.onChange).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
field: "two",
|
||||||
|
label: "two",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "three",
|
||||||
|
label: "three",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "one",
|
||||||
|
label: "a new label",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "four",
|
||||||
|
label: "four",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("updateSortable", () => {
|
||||||
|
beforeEach(ctx => {
|
||||||
|
ctx.updateSortable([
|
||||||
|
{
|
||||||
|
_instanceName: "three",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "three",
|
||||||
|
label: "three",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "one",
|
||||||
|
active: true,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "one",
|
||||||
|
label: "one",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_instanceName: "two",
|
||||||
|
active: false,
|
||||||
|
columnType: "foo",
|
||||||
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
|
field: "two",
|
||||||
|
label: "two",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls the callback with the updated columns", ctx => {
|
||||||
|
expect(ctx.onChange).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ctx.onChange).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
field: "three",
|
||||||
|
label: "three",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "one",
|
||||||
|
label: "one",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "two",
|
||||||
|
label: "two",
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "four",
|
||||||
|
label: "four",
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -781,7 +781,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Divider />
|
<Divider noMargin />
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<Layout gap="L" noPadding>
|
<Layout gap="L" noPadding>
|
||||||
<div class="user-invite-form">
|
<div class="user-invite-form">
|
||||||
|
@ -808,9 +808,10 @@
|
||||||
: Constants.BudibaseRoleOptionsNew.filter(
|
: Constants.BudibaseRoleOptionsNew.filter(
|
||||||
option => option.value !== Constants.BudibaseRoles.Admin
|
option => option.value !== Constants.BudibaseRoles.Admin
|
||||||
)}
|
)}
|
||||||
label="Role"
|
label="Access"
|
||||||
/>
|
/>
|
||||||
{#if creationRoleType !== Constants.BudibaseRoles.Admin}
|
{#if creationRoleType !== Constants.BudibaseRoles.Admin}
|
||||||
|
<span class="role-wrap">
|
||||||
<RoleSelect
|
<RoleSelect
|
||||||
placeholder={false}
|
placeholder={false}
|
||||||
bind:value={creationAccessType}
|
bind:value={creationAccessType}
|
||||||
|
@ -821,6 +822,7 @@
|
||||||
align="right"
|
align="right"
|
||||||
fancySelect
|
fancySelect
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</FancyForm>
|
</FancyForm>
|
||||||
{#if creationRoleType === Constants.BudibaseRoles.Admin}
|
{#if creationRoleType === Constants.BudibaseRoles.Admin}
|
||||||
|
@ -847,6 +849,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.role-wrap :global(.fancy-field:not(:last-of-type)) {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.search :global(input) {
|
.search :global(input) {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
minWidth: "200px",
|
minWidth: "200px",
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
|
displayName: "Access",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: "1fr",
|
width: "1fr",
|
||||||
},
|
},
|
||||||
|
|
|
@ -2698,6 +2698,22 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"labelfield": {
|
||||||
|
"name": "Text Field",
|
||||||
|
"icon": "Text",
|
||||||
|
"editable": true,
|
||||||
|
"size": {
|
||||||
|
"width": 400,
|
||||||
|
"height": 32
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "plainText",
|
||||||
|
"label": "Label",
|
||||||
|
"key": "label"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"stringfield": {
|
"stringfield": {
|
||||||
"name": "Text Field",
|
"name": "Text Field",
|
||||||
"icon": "Text",
|
"icon": "Text",
|
||||||
|
@ -6308,19 +6324,6 @@
|
||||||
"key": "table",
|
"key": "table",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "columns/grid",
|
|
||||||
"label": "Columns",
|
|
||||||
"key": "columns",
|
|
||||||
"dependsOn": [
|
|
||||||
"table",
|
|
||||||
{
|
|
||||||
"setting": "table.type",
|
|
||||||
"value": "custom",
|
|
||||||
"invert": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "filter",
|
"type": "filter",
|
||||||
"label": "Filtering",
|
"label": "Filtering",
|
||||||
|
@ -6417,6 +6420,18 @@
|
||||||
"key": "stripeRows",
|
"key": "stripeRows",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Columns",
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "columns/grid",
|
||||||
|
"key": "columns",
|
||||||
|
"nested": true,
|
||||||
|
"resetOn": "table"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Buttons",
|
"name": "Buttons",
|
||||||
|
|
|
@ -19,6 +19,22 @@
|
||||||
export let onRowClick = null
|
export let onRowClick = null
|
||||||
export let buttons = null
|
export let buttons = null
|
||||||
|
|
||||||
|
// parses columns to fix older formats
|
||||||
|
const getParsedColumns = columns => {
|
||||||
|
// If the first element has an active key all elements should be in the new format
|
||||||
|
if (columns?.length && columns[0]?.active !== undefined) {
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns?.map(column => ({
|
||||||
|
label: column.displayName || column.name,
|
||||||
|
field: column.name,
|
||||||
|
active: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
$: parsedColumns = getParsedColumns(columns)
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
const {
|
const {
|
||||||
|
@ -33,16 +49,17 @@
|
||||||
|
|
||||||
let grid
|
let grid
|
||||||
|
|
||||||
$: columnWhitelist = columns?.map(col => col.name)
|
$: columnWhitelist = parsedColumns
|
||||||
$: schemaOverrides = getSchemaOverrides(columns)
|
?.filter(col => col.active)
|
||||||
|
?.map(col => col.field)
|
||||||
|
$: schemaOverrides = getSchemaOverrides(parsedColumns)
|
||||||
$: enrichedButtons = enrichButtons(buttons)
|
$: enrichedButtons = enrichButtons(buttons)
|
||||||
|
|
||||||
const getSchemaOverrides = columns => {
|
const getSchemaOverrides = columns => {
|
||||||
let overrides = {}
|
let overrides = {}
|
||||||
columns?.forEach(column => {
|
columns?.forEach(column => {
|
||||||
overrides[column.name] = {
|
overrides[column.field] = {
|
||||||
displayName: column.displayName || column.name,
|
displayName: column.label,
|
||||||
visible: true,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return overrides
|
return overrides
|
||||||
|
|
|
@ -55,11 +55,20 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Apply whitelist if specified
|
// Apply whitelist if specified
|
||||||
if ($columnWhitelist?.length) {
|
if ($columnWhitelist?.length) {
|
||||||
Object.keys(enrichedSchema).forEach(key => {
|
const sortedColumns = {}
|
||||||
if (!$columnWhitelist.includes(key)) {
|
|
||||||
delete enrichedSchema[key]
|
$columnWhitelist.forEach((columnKey, idx) => {
|
||||||
|
const enrichedColumn = enrichedSchema[columnKey]
|
||||||
|
if (enrichedColumn) {
|
||||||
|
sortedColumns[columnKey] = {
|
||||||
|
...enrichedColumn,
|
||||||
|
order: idx,
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return sortedColumns
|
||||||
}
|
}
|
||||||
|
|
||||||
return enrichedSchema
|
return enrichedSchema
|
||||||
|
|
Loading…
Reference in a new issue