Merge branch 'fix-oracle-parse' of github.com:Budibase/budibase into fix-oracle-parse
This commit is contained in:
commit
a30caa399b
|
@ -40,6 +40,15 @@
|
||||||
{
|
{
|
||||||
label: "Colors",
|
label: "Colors",
|
||||||
colors: [
|
colors: [
|
||||||
|
"red-100",
|
||||||
|
"orange-100",
|
||||||
|
"yellow-100",
|
||||||
|
"green-100",
|
||||||
|
"seafoam-100",
|
||||||
|
"blue-100",
|
||||||
|
"indigo-100",
|
||||||
|
"magenta-100",
|
||||||
|
|
||||||
"red-400",
|
"red-400",
|
||||||
"orange-400",
|
"orange-400",
|
||||||
"yellow-400",
|
"yellow-400",
|
||||||
|
@ -108,12 +117,17 @@
|
||||||
|
|
||||||
const getCheckColor = value => {
|
const getCheckColor = value => {
|
||||||
// Use dynamic color for theme grays
|
// Use dynamic color for theme grays
|
||||||
if (value?.includes("gray")) {
|
if (value?.includes("-gray-")) {
|
||||||
return /^.*(gray-(50|75|100|200|300|400|500))\)$/.test(value)
|
return /^.*(gray-(50|75|100|200|300|400|500))\)$/.test(value)
|
||||||
? "var(--spectrum-global-color-gray-900)"
|
? "var(--spectrum-global-color-gray-900)"
|
||||||
: "var(--spectrum-global-color-gray-50)"
|
: "var(--spectrum-global-color-gray-50)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use contrasating check for the dim colours
|
||||||
|
if (value?.includes("-100")) {
|
||||||
|
return "var(--spectrum-global-color-gray-900)"
|
||||||
|
}
|
||||||
|
|
||||||
// Use black check for static white
|
// Use black check for static white
|
||||||
if (value?.includes("static-black")) {
|
if (value?.includes("static-black")) {
|
||||||
return "var(--spectrum-global-color-static-gray-50)"
|
return "var(--spectrum-global-color-static-gray-50)"
|
||||||
|
|
|
@ -62,7 +62,9 @@
|
||||||
return placeholder || "Choose an option"
|
return placeholder || "Choose an option"
|
||||||
}
|
}
|
||||||
|
|
||||||
return getFieldAttribute(getOptionLabel, value, options)
|
return (
|
||||||
|
getFieldAttribute(getOptionLabel, value, options) || "Choose an option"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectOption = value => {
|
const selectOption = value => {
|
||||||
|
|
|
@ -111,17 +111,38 @@ a {
|
||||||
/* Custom theme additions */
|
/* Custom theme additions */
|
||||||
.spectrum--darkest {
|
.spectrum--darkest {
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.6);
|
--drop-shadow: rgba(0, 0, 0, 0.6);
|
||||||
--spectrum-global-color-blue-100: rgb(30, 36, 50);
|
--spectrum-global-color-red-100: #570000;
|
||||||
|
--spectrum-global-color-orange-100: #481801;
|
||||||
|
--spectrum-global-color-yellow-100: #352400;
|
||||||
|
--spectrum-global-color-green-100: #002f07;
|
||||||
|
--spectrum-global-color-seafoam-100: #122b2a;
|
||||||
|
--spectrum-global-color-blue-100: #002651;
|
||||||
|
--spectrum-global-color-indigo-100: #1a1d61;
|
||||||
|
--spectrum-global-color-magenta-100: #530329;
|
||||||
|
--translucent-grey: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
.spectrum--dark {
|
.spectrum--dark {
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.3);
|
--drop-shadow: rgba(0, 0, 0, 0.3);
|
||||||
--spectrum-global-color-blue-100: rgb(42, 47, 57);
|
--spectrum-global-color-red-100: #7b0000;
|
||||||
}
|
--spectrum-global-color-orange-100: #662500;
|
||||||
.spectrum--light {
|
--spectrum-global-color-yellow-100: #4c3600;
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.075);
|
--spectrum-global-color-green-100: #00450a;
|
||||||
--spectrum-global-color-blue-100: rgb(240, 245, 255);
|
--spectrum-global-color-seafoam-100: #12413f;
|
||||||
|
--spectrum-global-color-blue-100: #003877;
|
||||||
|
--spectrum-global-color-indigo-100: #282c8c;
|
||||||
|
--spectrum-global-color-magenta-100: #76003a;
|
||||||
|
--translucent-grey: rgba(255, 255, 255, 0.065);
|
||||||
}
|
}
|
||||||
|
.spectrum--light,
|
||||||
.spectrum--lightest {
|
.spectrum--lightest {
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.05);
|
--drop-shadow: rgba(0, 0, 0, 0.075);
|
||||||
--spectrum-global-color-blue-100: rgb(240, 244, 255);
|
--spectrum-global-color-red-100: #ffddd6;
|
||||||
|
--spectrum-global-color-orange-100: #ffdfad;
|
||||||
|
--spectrum-global-color-yellow-100: #fbf198;
|
||||||
|
--spectrum-global-color-green-100: #cef8e0;
|
||||||
|
--spectrum-global-color-seafoam-100: #cef7f3;
|
||||||
|
--spectrum-global-color-blue-100: #e0f2ff;
|
||||||
|
--spectrum-global-color-indigo-100: #edeeff;
|
||||||
|
--spectrum-global-color-magenta-100: #ffeaf1;
|
||||||
|
--translucent-grey: rgba(0, 0, 0, 0.085);
|
||||||
}
|
}
|
||||||
|
|
|
@ -682,7 +682,7 @@
|
||||||
{errors}
|
{errors}
|
||||||
/>
|
/>
|
||||||
{:else if editableColumn.type === FORMULA_TYPE}
|
{:else if editableColumn.type === FORMULA_TYPE}
|
||||||
{#if !table.sql}
|
{#if !externalTable}
|
||||||
<div class="split-label">
|
<div class="split-label">
|
||||||
<div class="label-length">
|
<div class="label-length">
|
||||||
<Label size="M">Formula Type</Label>
|
<Label size="M">Formula Type</Label>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
export let allowHBS = true
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
export let autofocusEditor = false
|
export let autofocusEditor = false
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
context={{ ...$previewStore.selectedComponentContext, ...context }}
|
context={{ ...$previewStore.selectedComponentContext, ...context }}
|
||||||
snippets={$snippets}
|
snippets={$snippets}
|
||||||
{value}
|
{value}
|
||||||
|
{allowHBS}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
{autofocusEditor}
|
{autofocusEditor}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let placeholder
|
export let placeholder
|
||||||
export let label
|
export let label
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
export let allowHBS = true
|
||||||
export let allowJS = true
|
export let allowJS = true
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
|
@ -100,6 +101,7 @@
|
||||||
value={readableValue}
|
value={readableValue}
|
||||||
on:change={event => (tempValue = event.detail)}
|
on:change={event => (tempValue = event.detail)}
|
||||||
{bindings}
|
{bindings}
|
||||||
|
{allowHBS}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
{context}
|
{context}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte
|
||||||
import FormStepConfiguration from "./controls/FormStepConfiguration.svelte"
|
import FormStepConfiguration from "./controls/FormStepConfiguration.svelte"
|
||||||
import FormStepControls from "./controls/FormStepControls.svelte"
|
import FormStepControls from "./controls/FormStepControls.svelte"
|
||||||
import PaywalledSetting from "./controls/PaywalledSetting.svelte"
|
import PaywalledSetting from "./controls/PaywalledSetting.svelte"
|
||||||
|
import TableConditionEditor from "./controls/TableConditionEditor.svelte"
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: DrawerBindableInput,
|
text: DrawerBindableInput,
|
||||||
|
@ -61,6 +62,7 @@ const componentMap = {
|
||||||
columns: ColumnEditor,
|
columns: ColumnEditor,
|
||||||
"columns/basic": BasicColumnEditor,
|
"columns/basic": BasicColumnEditor,
|
||||||
"columns/grid": GridColumnEditor,
|
"columns/grid": GridColumnEditor,
|
||||||
|
tableConditions: TableConditionEditor,
|
||||||
"field/sortable": SortableFieldSelect,
|
"field/sortable": SortableFieldSelect,
|
||||||
"field/string": FormFieldSelect,
|
"field/string": FormFieldSelect,
|
||||||
"field/number": FormFieldSelect,
|
"field/number": FormFieldSelect,
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
export let listItemKey
|
export let listItemKey
|
||||||
export let draggable = true
|
export let draggable = true
|
||||||
export let focus
|
export let focus
|
||||||
|
export let bindings = []
|
||||||
|
|
||||||
let zoneType = generate()
|
let zoneType = generate()
|
||||||
|
|
||||||
|
@ -126,6 +127,7 @@
|
||||||
anchor={anchors[draggableItem.id]}
|
anchor={anchors[draggableItem.id]}
|
||||||
item={draggableItem.item}
|
item={draggableItem.item}
|
||||||
{...listTypeProps}
|
{...listTypeProps}
|
||||||
|
{bindings}
|
||||||
on:change={onItemChanged}
|
on:change={onItemChanged}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
export let componentBindings
|
export let componentBindings
|
||||||
export let bindings
|
export let bindings
|
||||||
export let parseSettings
|
export let parseSettings
|
||||||
export let disabled
|
|
||||||
|
|
||||||
const draggable = getContext("draggable")
|
const draggable = getContext("draggable")
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
|
import { Constants } from "@budibase/frontend-core"
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
export let item
|
export let item
|
||||||
export let anchor
|
export let anchor
|
||||||
|
export let bindings
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -28,19 +31,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseSettings = settings => {
|
const parseSettings = settings => {
|
||||||
return settings
|
let columnSettings = settings
|
||||||
.filter(setting => setting.key !== "field")
|
.filter(setting => setting.key !== "field")
|
||||||
.map(setting => {
|
.map(setting => {
|
||||||
return { ...setting, nested: true }
|
return { ...setting, nested: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Filter out conditions for invalid types.
|
||||||
|
// Allow formulas as we have all the data already loaded in the table.
|
||||||
|
if (
|
||||||
|
Constants.BannedSearchTypes.includes(item.columnType) &&
|
||||||
|
item.columnType !== FieldType.FORMULA
|
||||||
|
) {
|
||||||
|
return columnSettings.filter(x => x.key !== "conditions")
|
||||||
|
}
|
||||||
|
return columnSettings
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="list-item-body">
|
<div class="list-item-body">
|
||||||
<div class="list-item-left">
|
<div class="list-item-left">
|
||||||
<EditComponentPopover
|
<EditComponentPopover
|
||||||
{anchor}
|
|
||||||
componentInstance={item}
|
componentInstance={item}
|
||||||
|
{bindings}
|
||||||
|
{anchor}
|
||||||
{parseSettings}
|
{parseSettings}
|
||||||
on:change
|
on:change
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let primaryDisplayColumnAnchor
|
let primaryDisplayColumnAnchor
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
items={columns.sortable}
|
items={columns.sortable}
|
||||||
listItemKey={"_id"}
|
listItemKey={"_id"}
|
||||||
listType={FieldSetting}
|
listType={FieldSetting}
|
||||||
|
{bindings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -68,6 +68,7 @@ const toGridFormat = draggableListColumns => {
|
||||||
field: entry.field,
|
field: entry.field,
|
||||||
active: entry.active,
|
active: entry.active,
|
||||||
width: entry.width,
|
width: entry.width,
|
||||||
|
conditions: entry.conditions,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
|
||||||
label: column.label,
|
label: column.label,
|
||||||
columnType: schema[column.field].type,
|
columnType: schema[column.field].type,
|
||||||
width: column.width,
|
width: column.width,
|
||||||
|
conditions: column.conditions,
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
@ -106,12 +108,12 @@ const getColumns = ({
|
||||||
createComponent,
|
createComponent,
|
||||||
schema
|
schema
|
||||||
)
|
)
|
||||||
const primary = draggableList.find(
|
const primary = draggableList
|
||||||
entry => entry.field === primaryDisplayColumnName
|
.filter(entry => entry.field === primaryDisplayColumnName)
|
||||||
)
|
.map(instance => ({ ...instance, schema }))[0]
|
||||||
const sortable = draggableList.filter(
|
const sortable = draggableList
|
||||||
entry => entry.field !== primaryDisplayColumnName
|
.filter(entry => entry.field !== primaryDisplayColumnName)
|
||||||
)
|
.map(instance => ({ ...instance, schema }))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
primary,
|
primary,
|
||||||
|
|
|
@ -72,6 +72,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "three",
|
field: "three",
|
||||||
label: "three label",
|
label: "three label",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "two",
|
_id: "two",
|
||||||
|
@ -81,6 +83,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "two",
|
field: "two",
|
||||||
label: "two label",
|
label: "two label",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "one",
|
_id: "one",
|
||||||
|
@ -90,6 +94,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "one",
|
field: "one",
|
||||||
label: "one",
|
label: "one",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -101,6 +107,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "four",
|
field: "four",
|
||||||
label: "four",
|
label: "four",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -126,6 +134,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "two",
|
field: "two",
|
||||||
label: "two",
|
label: "two",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "three",
|
_id: "three",
|
||||||
|
@ -135,6 +145,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "three",
|
field: "three",
|
||||||
label: "three",
|
label: "three",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "one",
|
_id: "one",
|
||||||
|
@ -144,6 +156,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "one",
|
field: "one",
|
||||||
label: "one",
|
label: "one",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -155,6 +169,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "four",
|
field: "four",
|
||||||
label: "four",
|
label: "four",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -188,6 +204,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "three",
|
field: "three",
|
||||||
label: "three label",
|
label: "three label",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "two",
|
_id: "two",
|
||||||
|
@ -197,6 +215,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "two",
|
field: "two",
|
||||||
label: "two",
|
label: "two",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "one",
|
_id: "one",
|
||||||
|
@ -206,6 +226,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "one",
|
field: "one",
|
||||||
label: "one",
|
label: "one",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -217,6 +239,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "four",
|
field: "four",
|
||||||
label: "four",
|
label: "four",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -247,6 +271,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "three",
|
field: "three",
|
||||||
label: "three label",
|
label: "three label",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "two",
|
_id: "two",
|
||||||
|
@ -256,6 +282,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "two",
|
field: "two",
|
||||||
label: "two",
|
label: "two",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
_id: "one",
|
_id: "one",
|
||||||
|
@ -265,6 +293,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "one",
|
field: "one",
|
||||||
label: "one",
|
label: "one",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -276,6 +306,8 @@ describe("getColumns", () => {
|
||||||
componentName: "@budibase/standard-components/labelfield",
|
componentName: "@budibase/standard-components/labelfield",
|
||||||
field: "four",
|
field: "four",
|
||||||
label: "four",
|
label: "four",
|
||||||
|
conditions: undefined,
|
||||||
|
schema: ctx.schema,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
ActionButton,
|
||||||
|
Drawer,
|
||||||
|
Button,
|
||||||
|
DrawerContent,
|
||||||
|
Layout,
|
||||||
|
Select,
|
||||||
|
Icon,
|
||||||
|
DatePicker,
|
||||||
|
Combobox,
|
||||||
|
Multiselect,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
|
import ColorPicker from "./ColorPicker.svelte"
|
||||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
import { QueryUtils, Constants, FilterUsers } from "@budibase/frontend-core"
|
||||||
|
import { generate } from "shortid"
|
||||||
|
import { FieldType, FormulaType } from "@budibase/types"
|
||||||
|
import { dndzone } from "svelte-dnd-action"
|
||||||
|
import { flip } from "svelte/animate"
|
||||||
|
|
||||||
|
export let componentInstance
|
||||||
|
export let bindings
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const flipDuration = 130
|
||||||
|
const targetOptions = [
|
||||||
|
{
|
||||||
|
label: "Cell",
|
||||||
|
value: "cell",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Row",
|
||||||
|
value: "row",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const conditionOptions = [
|
||||||
|
{
|
||||||
|
label: "Background color",
|
||||||
|
value: "backgroundColor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Text color",
|
||||||
|
value: "textColor",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let tempValue = []
|
||||||
|
let drawer
|
||||||
|
let dragDisabled = true
|
||||||
|
|
||||||
|
$: count = value?.length
|
||||||
|
$: conditionText = `${count || "No"} condition${count !== 1 ? "s" : ""} set`
|
||||||
|
$: type = componentInstance.columnType
|
||||||
|
$: valueTypeOptions = getValueTypeOptions(type)
|
||||||
|
$: hasValueOption = type !== FieldType.STRING
|
||||||
|
$: operatorOptions = QueryUtils.getValidOperatorsForType({
|
||||||
|
type,
|
||||||
|
|
||||||
|
// We can filter on any formula columns here since we already have the data
|
||||||
|
// on the page, so adding this ensures formula columns get operators
|
||||||
|
formulaType: FormulaType.STATIC,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getValueTypeOptions = type => {
|
||||||
|
let options = [
|
||||||
|
{
|
||||||
|
label: "Binding",
|
||||||
|
value: FieldType.STRING,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (type !== FieldType.STRING) {
|
||||||
|
options.push({
|
||||||
|
label: "Value",
|
||||||
|
value: type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
const openDrawer = () => {
|
||||||
|
tempValue = cloneDeep(value || [])
|
||||||
|
drawer.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
dispatch("change", tempValue)
|
||||||
|
drawer.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCondition = () => {
|
||||||
|
const condition = {
|
||||||
|
id: generate(),
|
||||||
|
target: targetOptions[0].value,
|
||||||
|
metadataKey: conditionOptions[0].value,
|
||||||
|
operator: operatorOptions[0]?.value,
|
||||||
|
valueType: FieldType.STRING,
|
||||||
|
}
|
||||||
|
tempValue = [...tempValue, condition]
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateCondition = condition => {
|
||||||
|
const dupe = { ...condition, id: generate() }
|
||||||
|
tempValue = [...tempValue, dupe]
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeCondition = condition => {
|
||||||
|
tempValue = tempValue.filter(c => c.id !== condition.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOperatorChange = (condition, newOperator) => {
|
||||||
|
const noValueOptions = [
|
||||||
|
Constants.OperatorOptions.Empty.value,
|
||||||
|
Constants.OperatorOptions.NotEmpty.value,
|
||||||
|
]
|
||||||
|
condition.noValue = noValueOptions.includes(newOperator)
|
||||||
|
if (condition.noValue) {
|
||||||
|
condition.referenceValue = null
|
||||||
|
condition.valueType = "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onValueTypeChange = condition => {
|
||||||
|
condition.referenceValue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConditions = e => {
|
||||||
|
tempValue = e.detail.items
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFinalize = e => {
|
||||||
|
updateConditions(e)
|
||||||
|
dragDisabled = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<Drawer
|
||||||
|
bind:this={drawer}
|
||||||
|
title="{componentInstance.field} conditions"
|
||||||
|
on:drawerShow
|
||||||
|
on:drawerHide
|
||||||
|
>
|
||||||
|
<Button cta slot="buttons" on:click={save}>Save</Button>
|
||||||
|
<DrawerContent slot="body">
|
||||||
|
<div class="container">
|
||||||
|
<Layout noPadding>
|
||||||
|
Update the appearance of cells and rows based on their value.
|
||||||
|
{#if tempValue.length}
|
||||||
|
<div
|
||||||
|
class="conditions"
|
||||||
|
use:dndzone={{
|
||||||
|
items: tempValue,
|
||||||
|
flipDurationMs: flipDuration,
|
||||||
|
dropTargetStyle: { outline: "none" },
|
||||||
|
dragDisabled,
|
||||||
|
}}
|
||||||
|
on:consider={updateConditions}
|
||||||
|
on:finalize={handleFinalize}
|
||||||
|
>
|
||||||
|
{#each tempValue as condition (condition.id)}
|
||||||
|
<div
|
||||||
|
class="condition"
|
||||||
|
class:update={condition.action === "update"}
|
||||||
|
class:with-value-option={hasValueOption}
|
||||||
|
animate:flip={{ duration: flipDuration }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="handle"
|
||||||
|
aria-label="drag-handle"
|
||||||
|
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
|
||||||
|
on:mousedown={() => (dragDisabled = false)}
|
||||||
|
>
|
||||||
|
<Icon name="DragHandle" size="XL" />
|
||||||
|
</div>
|
||||||
|
<span>Update</span>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
options={targetOptions}
|
||||||
|
bind:value={condition.target}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
options={conditionOptions}
|
||||||
|
bind:value={condition.metadataKey}
|
||||||
|
/>
|
||||||
|
<span>to</span>
|
||||||
|
<ColorPicker
|
||||||
|
value={condition.metadataValue}
|
||||||
|
on:change={e => (condition.metadataValue = e.detail)}
|
||||||
|
/>
|
||||||
|
<span>if value</span>
|
||||||
|
<Select
|
||||||
|
placeholder={null}
|
||||||
|
options={operatorOptions}
|
||||||
|
bind:value={condition.operator}
|
||||||
|
on:change={e => onOperatorChange(condition, e.detail)}
|
||||||
|
/>
|
||||||
|
{#if hasValueOption}
|
||||||
|
<Select
|
||||||
|
disabled={condition.noValue}
|
||||||
|
options={valueTypeOptions}
|
||||||
|
bind:value={condition.valueType}
|
||||||
|
placeholder={null}
|
||||||
|
on:change={() => onValueTypeChange(condition)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if type === FieldType.DATETIME && condition.valueType === type}
|
||||||
|
<DatePicker
|
||||||
|
placeholder="Value"
|
||||||
|
disabled={condition.noValue}
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
/>
|
||||||
|
{:else if type === FieldType.BOOLEAN && condition.valueType === type}
|
||||||
|
<Select
|
||||||
|
placeholder="Value"
|
||||||
|
disabled={condition.noValue}
|
||||||
|
options={["True", "False"]}
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
/>
|
||||||
|
{:else if (type === FieldType.OPTIONS || type === FieldType.ARRAY) && condition.valueType === type}
|
||||||
|
{#if condition.operator === Constants.OperatorOptions.In.value}
|
||||||
|
<Multiselect
|
||||||
|
disabled={condition.noValue}
|
||||||
|
options={componentInstance.schema?.[
|
||||||
|
componentInstance.field
|
||||||
|
]?.constraints?.inclusion || []}
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<Combobox
|
||||||
|
disabled={condition.noValue}
|
||||||
|
options={componentInstance.schema?.[
|
||||||
|
componentInstance.field
|
||||||
|
]?.constraints?.inclusion || []}
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{:else if (type === FieldType.BB_REFERENCE || type === FieldType.BB_REFERENCE_SINGLE) && condition.valueType === type}
|
||||||
|
<FilterUsers
|
||||||
|
bind:value={condition.referenceValue}
|
||||||
|
multiselect={[
|
||||||
|
Constants.OperatorOptions.In.value,
|
||||||
|
Constants.OperatorOptions.ContainsAny.value,
|
||||||
|
].includes(condition.operator)}
|
||||||
|
disabled={condition.noValue}
|
||||||
|
type={condition.valueType}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<DrawerBindableInput
|
||||||
|
{bindings}
|
||||||
|
placeholder="Value"
|
||||||
|
disabled={condition.noValue}
|
||||||
|
value={condition.referenceValue}
|
||||||
|
on:change={e => (condition.referenceValue = e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<Icon
|
||||||
|
name="Duplicate"
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
on:click={() => duplicateCondition(condition)}
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
name="Close"
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
on:click={() => removeCondition(condition)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<Button secondary icon="Add" on:click={addCondition}>
|
||||||
|
Add condition
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.conditions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.condition {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto 1fr 1fr auto auto auto 1fr 1fr auto auto;
|
||||||
|
align-items: center;
|
||||||
|
grid-column-gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.condition.with-value-option {
|
||||||
|
grid-template-columns: auto auto 1fr 1fr auto auto auto 1fr 1fr 1fr auto auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -29,7 +29,7 @@ import { JSONUtils, Constants } from "@budibase/frontend-core"
|
||||||
import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
|
import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json"
|
||||||
import { environment, licensing } from "stores/portal"
|
import { environment, licensing } from "stores/portal"
|
||||||
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
|
import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS, DB_TYPE_INTERNAL } from "constants/backend"
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
const { ContextScopes } = Constants
|
const { ContextScopes } = Constants
|
||||||
|
@ -991,7 +991,7 @@ export const getSchemaForDatasource = (asset, datasource, options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if we should add ID and rev to the schema
|
// Determine if we should add ID and rev to the schema
|
||||||
const isInternal = table && !table.sql
|
const isInternal = table && table?.sourceType === DB_TYPE_INTERNAL
|
||||||
const isDSPlus = ["table", "link", "viewV2"].includes(datasource.type)
|
const isDSPlus = ["table", "link", "viewV2"].includes(datasource.type)
|
||||||
|
|
||||||
// ID is part of the readable schema for all tables
|
// ID is part of the readable schema for all tables
|
||||||
|
|
|
@ -8,8 +8,11 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { componentStore } from "stores/builder"
|
import { componentStore } from "stores/builder"
|
||||||
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
import ConditionalUIDrawer from "./ConditionalUIDrawer.svelte"
|
||||||
|
import ComponentSettingsSection from "./ComponentSettingsSection.svelte"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let componentDefinition
|
||||||
|
export let componentBindings
|
||||||
export let bindings
|
export let bindings
|
||||||
|
|
||||||
let tempValue
|
let tempValue
|
||||||
|
@ -35,6 +38,19 @@
|
||||||
} set`
|
} set`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Load any general settings or sections tagged as "condition"
|
||||||
|
-->
|
||||||
|
<ComponentSettingsSection
|
||||||
|
{componentInstance}
|
||||||
|
{componentDefinition}
|
||||||
|
isScreen={false}
|
||||||
|
showInstanceName={false}
|
||||||
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
|
tag="condition"
|
||||||
|
/>
|
||||||
|
|
||||||
<DetailSummary name={"Conditions"} collapsible={false}>
|
<DetailSummary name={"Conditions"} collapsible={false}>
|
||||||
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
<ActionButton on:click={openDrawer}>{conditionText}</ActionButton>
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
|
|
|
@ -2879,6 +2879,11 @@
|
||||||
"placeholder": "Auto",
|
"placeholder": "Auto",
|
||||||
"min": 80,
|
"min": 80,
|
||||||
"max": 9999
|
"max": 9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tableConditions",
|
||||||
|
"label": "Conditions",
|
||||||
|
"key": "conditions"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -7303,7 +7308,6 @@
|
||||||
{
|
{
|
||||||
"type": "columns/grid",
|
"type": "columns/grid",
|
||||||
"key": "columns",
|
"key": "columns",
|
||||||
"nested": true,
|
|
||||||
"resetOn": "table"
|
"resetOn": "table"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
$: currentTheme = $context?.device?.theme
|
$: currentTheme = $context?.device?.theme
|
||||||
$: darkMode = !currentTheme?.includes("light")
|
$: darkMode = !currentTheme?.includes("light")
|
||||||
$: parsedColumns = getParsedColumns(columns)
|
$: parsedColumns = getParsedColumns(columns)
|
||||||
$: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
|
|
||||||
$: schemaOverrides = getSchemaOverrides(parsedColumns)
|
$: schemaOverrides = getSchemaOverrides(parsedColumns)
|
||||||
$: enrichedButtons = enrichButtons(buttons)
|
$: enrichedButtons = enrichButtons(buttons)
|
||||||
$: selectedRows = deriveSelectedRows(gridContext)
|
$: selectedRows = deriveSelectedRows(gridContext)
|
||||||
|
@ -62,7 +61,13 @@
|
||||||
const goldenRow = generateGoldenSample(rows)
|
const goldenRow = generateGoldenSample(rows)
|
||||||
const id = get(component).id
|
const id = get(component).id
|
||||||
return {
|
return {
|
||||||
|
// Not sure what this one is for...
|
||||||
[id]: goldenRow,
|
[id]: goldenRow,
|
||||||
|
|
||||||
|
// For row conditions context
|
||||||
|
row: goldenRow,
|
||||||
|
|
||||||
|
// For button action context
|
||||||
eventContext: {
|
eventContext: {
|
||||||
row: goldenRow,
|
row: goldenRow,
|
||||||
},
|
},
|
||||||
|
@ -91,6 +96,8 @@
|
||||||
overrides[column.field] = {
|
overrides[column.field] = {
|
||||||
displayName: column.label,
|
displayName: column.label,
|
||||||
order: idx,
|
order: idx,
|
||||||
|
conditions: column.conditions,
|
||||||
|
visible: !!column.active,
|
||||||
}
|
}
|
||||||
if (column.width) {
|
if (column.width) {
|
||||||
overrides[column.field].width = column.width
|
overrides[column.field].width = column.width
|
||||||
|
@ -163,7 +170,6 @@
|
||||||
{initialSortColumn}
|
{initialSortColumn}
|
||||||
{initialSortOrder}
|
{initialSortOrder}
|
||||||
{fixedRowHeight}
|
{fixedRowHeight}
|
||||||
{columnWhitelist}
|
|
||||||
{schemaOverrides}
|
{schemaOverrides}
|
||||||
canAddRows={allowAddRows}
|
canAddRows={allowAddRows}
|
||||||
canEditRows={allowEditRows}
|
canEditRows={allowEditRows}
|
||||||
|
@ -187,7 +193,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-200);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 410px;
|
height: 410px;
|
||||||
|
|
|
@ -130,6 +130,10 @@
|
||||||
on:mouseup={stopSelectionCallback}
|
on:mouseup={stopSelectionCallback}
|
||||||
on:click={handleClick}
|
on:click={handleClick}
|
||||||
width={column.width}
|
width={column.width}
|
||||||
|
metadata={{
|
||||||
|
...row.__metadata?.row,
|
||||||
|
...row.__metadata?.cell[column.name],
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={getCellRenderer(column)}
|
this={getCellRenderer(column)}
|
||||||
|
|
|
@ -11,13 +11,20 @@
|
||||||
export let center = false
|
export let center = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let hidden = false
|
export let hidden = false
|
||||||
|
export let metadata = null
|
||||||
|
|
||||||
$: style = getStyle(width, selectedUser)
|
$: style = getStyle(width, selectedUser, metadata)
|
||||||
|
|
||||||
const getStyle = (width, selectedUser) => {
|
const getStyle = (width, selectedUser, metadata) => {
|
||||||
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
|
let style = width === "auto" ? "width: auto;" : `flex: 0 0 ${width}px;`
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
style += `--user-color:${selectedUser.color};`
|
style += `--user-color :${selectedUser.color};`
|
||||||
|
}
|
||||||
|
if (metadata?.backgroundColor) {
|
||||||
|
style += `--cell-background: ${metadata.backgroundColor};`
|
||||||
|
}
|
||||||
|
if (metadata?.textColor) {
|
||||||
|
style += `--cell-font-color: ${metadata.textColor};`
|
||||||
}
|
}
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
|
@ -43,7 +50,7 @@
|
||||||
on:mouseup
|
on:mouseup
|
||||||
on:click
|
on:click
|
||||||
on:contextmenu
|
on:contextmenu
|
||||||
on:touchstart
|
on:touchstart|passive
|
||||||
on:touchend
|
on:touchend
|
||||||
on:touchcancel
|
on:touchcancel
|
||||||
on:mouseenter
|
on:mouseenter
|
||||||
|
@ -72,7 +79,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
color: var(--spectrum-global-color-gray-800);
|
color: var(--cell-font-color);
|
||||||
font-size: var(--cell-font-size);
|
font-size: var(--cell-font-size);
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
background: var(--cell-background);
|
background: var(--cell-background);
|
||||||
|
@ -94,9 +101,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cell border */
|
/* Cell border */
|
||||||
.cell.focused:after,
|
.cell.focused::after,
|
||||||
.cell.error:after,
|
.cell.error::after,
|
||||||
.cell.selected-other:not(.focused):after {
|
.cell.selected-other:not(.focused)::after {
|
||||||
content: " ";
|
content: " ";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -109,14 +116,30 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cell background overlay */
|
||||||
|
.cell.selected::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: calc(100% + 1px);
|
||||||
|
width: calc(100% + 1px);
|
||||||
|
opacity: 0.16;
|
||||||
|
background: var(--spectrum-global-color-blue-400);
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Cell border for cells with labels */
|
/* Cell border for cells with labels */
|
||||||
.cell.error:after {
|
.cell.error::after {
|
||||||
border-radius: 0 2px 2px 2px;
|
border-radius: 0 2px 2px 2px;
|
||||||
}
|
}
|
||||||
.cell.top.error:after {
|
.cell.top.error::after {
|
||||||
border-radius: 2px 2px 2px 0;
|
border-radius: 2px 2px 2px 0;
|
||||||
}
|
}
|
||||||
.cell.selected-other:not(.focused):after {
|
.cell.selected-other:not(.focused)::after {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,15 +174,10 @@
|
||||||
.cell.focused.readonly {
|
.cell.focused.readonly {
|
||||||
--cell-color: var(--spectrum-global-color-gray-600);
|
--cell-color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
.cell.highlighted:not(.focused):not(.selected),
|
||||||
.cell.highlighted:not(.focused),
|
|
||||||
.cell.focused.readonly {
|
.cell.focused.readonly {
|
||||||
--cell-background: var(--cell-background-hover);
|
--cell-background: var(--cell-background-hover);
|
||||||
}
|
}
|
||||||
.cell.selected.focused,
|
|
||||||
.cell.selected:not(.focused) {
|
|
||||||
--cell-background: var(--spectrum-global-color-blue-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Label for additional text */
|
/* Label for additional text */
|
||||||
.label {
|
.label {
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
selected={rowSelected}
|
selected={rowSelected}
|
||||||
{defaultHeight}
|
{defaultHeight}
|
||||||
rowIdx={row?.__idx}
|
rowIdx={row?.__idx}
|
||||||
|
metadata={row?.__metadata?.row}
|
||||||
>
|
>
|
||||||
<div class="gutter">
|
<div class="gutter">
|
||||||
{#if $$slots.default}
|
{#if $$slots.default}
|
||||||
|
@ -115,7 +116,7 @@
|
||||||
margin: 3px 0 0 0;
|
margin: 3px 0 0 0;
|
||||||
}
|
}
|
||||||
.number {
|
.number {
|
||||||
color: var(--spectrum-global-color-gray-500);
|
color: val(--cell-font-color, var(--spectrum-global-color-gray-500));
|
||||||
}
|
}
|
||||||
.checkbox.visible,
|
.checkbox.visible,
|
||||||
.number.visible {
|
.number.visible {
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
rowIdx={row.__idx}
|
rowIdx={row.__idx}
|
||||||
selected={rowSelected}
|
selected={rowSelected}
|
||||||
highlighted={rowHovered || rowFocused}
|
highlighted={rowHovered || rowFocused}
|
||||||
|
metadata={row.__metadata?.row}
|
||||||
>
|
>
|
||||||
<div class="buttons" class:offset={$showVScrollbar}>
|
<div class="buttons" class:offset={$showVScrollbar}>
|
||||||
{#each buttons as button}
|
{#each buttons as button}
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
export let API = null
|
export let API = null
|
||||||
export let datasource = null
|
export let datasource = null
|
||||||
export let schemaOverrides = null
|
export let schemaOverrides = null
|
||||||
export let columnWhitelist = null
|
|
||||||
export let canAddRows = true
|
export let canAddRows = true
|
||||||
export let canExpandRows = true
|
export let canExpandRows = true
|
||||||
export let canEditRows = true
|
export let canEditRows = true
|
||||||
|
@ -59,6 +58,7 @@
|
||||||
export let darkMode
|
export let darkMode
|
||||||
export let isCloud = null
|
export let isCloud = null
|
||||||
export let allowViewReadonlyColumns = false
|
export let allowViewReadonlyColumns = false
|
||||||
|
export let rowConditions = null
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const gridID = `grid-${Math.random().toString().slice(2)}`
|
const gridID = `grid-${Math.random().toString().slice(2)}`
|
||||||
|
@ -93,7 +93,6 @@
|
||||||
$: props.set({
|
$: props.set({
|
||||||
datasource,
|
datasource,
|
||||||
schemaOverrides,
|
schemaOverrides,
|
||||||
columnWhitelist,
|
|
||||||
canAddRows,
|
canAddRows,
|
||||||
canExpandRows,
|
canExpandRows,
|
||||||
canEditRows,
|
canEditRows,
|
||||||
|
@ -114,6 +113,8 @@
|
||||||
buttons,
|
buttons,
|
||||||
darkMode,
|
darkMode,
|
||||||
isCloud,
|
isCloud,
|
||||||
|
allowViewReadonlyColumns,
|
||||||
|
rowConditions,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Derive min height and make available in context
|
// Derive min height and make available in context
|
||||||
|
@ -231,6 +232,7 @@
|
||||||
--cell-spacing: 4px;
|
--cell-spacing: 4px;
|
||||||
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
--cell-border: 1px solid var(--spectrum-global-color-gray-200);
|
||||||
--cell-font-size: 14px;
|
--cell-font-size: 14px;
|
||||||
|
--cell-font-color: var(--spectrum-global-color-gray-800);
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -286,7 +288,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 2px solid var(--spectrum-global-color-gray-200);
|
border-bottom: var(--cell-border);
|
||||||
padding: var(--cell-padding);
|
padding: var(--cell-padding);
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
background: var(--grid-background-alt);
|
background: var(--grid-background-alt);
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
const generateStyle = (scrollLeft, scrollTop, rowHeight) => {
|
const generateStyle = (scrollLeft, scrollTop, rowHeight) => {
|
||||||
const offsetX = scrollHorizontally ? -1 * scrollLeft : 0
|
const offsetX = scrollHorizontally ? -1 * scrollLeft : 0
|
||||||
const offsetY = scrollVertically ? -1 * (scrollTop % rowHeight) : 0
|
const offsetY = scrollVertically ? -1 * (scrollTop % rowHeight) : 0
|
||||||
return `transform: translate3d(${offsetX}px, ${offsetY}px, 0);`
|
return `transform: translate(${offsetX}px, ${offsetY}px);`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles a mouse wheel event and updates scroll state
|
// Handles a mouse wheel event and updates scroll state
|
||||||
|
|
|
@ -162,7 +162,7 @@
|
||||||
|
|
||||||
/* Don't show borders between cells in the sticky column */
|
/* Don't show borders between cells in the sticky column */
|
||||||
.sticky-column :global(.cell:not(:last-child)) {
|
.sticky-column :global(.cell:not(:last-child)) {
|
||||||
border-right: none;
|
border-right-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -171,6 +171,9 @@
|
||||||
.header :global(.cell) {
|
.header :global(.cell) {
|
||||||
background: var(--grid-background-alt);
|
background: var(--grid-background-alt);
|
||||||
}
|
}
|
||||||
|
.header :global(.cell::before) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -144,6 +144,7 @@ export const initialise = context => {
|
||||||
visible: fieldSchema.visible ?? true,
|
visible: fieldSchema.visible ?? true,
|
||||||
readonly: fieldSchema.readonly,
|
readonly: fieldSchema.readonly,
|
||||||
order: fieldSchema.order ?? oldColumn?.order,
|
order: fieldSchema.order ?? oldColumn?.order,
|
||||||
|
conditions: fieldSchema.conditions,
|
||||||
}
|
}
|
||||||
// Override a few properties for primary display
|
// Override a few properties for primary display
|
||||||
if (field === primaryDisplay) {
|
if (field === primaryDisplay) {
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
import { writable, get } from "svelte/store"
|
||||||
|
import { derivedMemo, QueryUtils } from "../../../utils"
|
||||||
|
import { FieldType, EmptyFilterOption } from "@budibase/types"
|
||||||
|
|
||||||
|
export const createStores = () => {
|
||||||
|
const metadata = writable({})
|
||||||
|
return {
|
||||||
|
metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deriveStores = context => {
|
||||||
|
const { columns } = context
|
||||||
|
|
||||||
|
// Derive and memoize the cell conditions present in our columns so that we
|
||||||
|
// only recompute condition metadata when absolutely necessary
|
||||||
|
const conditions = derivedMemo(columns, $columns => {
|
||||||
|
let newConditions = []
|
||||||
|
for (let column of $columns) {
|
||||||
|
for (let condition of column.conditions || []) {
|
||||||
|
newConditions.push({
|
||||||
|
...condition,
|
||||||
|
column: column.name,
|
||||||
|
type: column.schema.type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newConditions
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
conditions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialise = context => {
|
||||||
|
const { metadata, conditions, rows } = context
|
||||||
|
|
||||||
|
// Recompute all metadata if conditions change
|
||||||
|
conditions.subscribe($conditions => {
|
||||||
|
let newMetadata = {}
|
||||||
|
if ($conditions?.length) {
|
||||||
|
for (let row of get(rows)) {
|
||||||
|
newMetadata[row._id] = evaluateConditions(row, $conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata.set(newMetadata)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Recompute metadata for specific rows when they change
|
||||||
|
rows.subscribe($rows => {
|
||||||
|
const $conditions = get(conditions)
|
||||||
|
if (!$conditions?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const $metadata = get(metadata)
|
||||||
|
let metadataUpdates = {}
|
||||||
|
for (let row of $rows) {
|
||||||
|
if (!row._rev || $metadata[row._id]?.version !== row._rev) {
|
||||||
|
metadataUpdates[row._id] = evaluateConditions(row, $conditions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(metadataUpdates).length) {
|
||||||
|
metadata.update(state => ({
|
||||||
|
...state,
|
||||||
|
...metadataUpdates,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const TypeCoercionMap = {
|
||||||
|
[FieldType.NUMBER]: parseFloat,
|
||||||
|
[FieldType.DATETIME]: val => {
|
||||||
|
if (val) {
|
||||||
|
return new Date(val).toISOString()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
[FieldType.BOOLEAN]: val => {
|
||||||
|
if (`${val}`.toLowerCase().trim() === "true") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (`${val}`.toLowerCase().trim() === "false") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluates an array of cell conditions against a certain row and returns the
|
||||||
|
// resultant metadata
|
||||||
|
const evaluateConditions = (row, conditions) => {
|
||||||
|
let metadata = {
|
||||||
|
version: row._rev,
|
||||||
|
row: {},
|
||||||
|
cell: {},
|
||||||
|
}
|
||||||
|
for (let condition of conditions) {
|
||||||
|
try {
|
||||||
|
let {
|
||||||
|
column,
|
||||||
|
type,
|
||||||
|
referenceValue,
|
||||||
|
operator,
|
||||||
|
metadataKey,
|
||||||
|
metadataValue,
|
||||||
|
target,
|
||||||
|
} = condition
|
||||||
|
let value = row[column]
|
||||||
|
|
||||||
|
// Coerce values into correct types for primitives
|
||||||
|
let coercedType = type
|
||||||
|
if (type === FieldType.FORMULA) {
|
||||||
|
// For formulas we want to ensure that the reference type matches the
|
||||||
|
// real type
|
||||||
|
if (value === true || value === false) {
|
||||||
|
coercedType = FieldType.BOOLEAN
|
||||||
|
} else if (typeof value === "number") {
|
||||||
|
coercedType = FieldType.NUMBER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const coerce = TypeCoercionMap[coercedType]
|
||||||
|
if (coerce) {
|
||||||
|
value = coerce(value)
|
||||||
|
referenceValue = coerce(referenceValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build lucene compatible condition expression
|
||||||
|
const luceneFilter = {
|
||||||
|
operator,
|
||||||
|
type,
|
||||||
|
field: "value",
|
||||||
|
value: referenceValue,
|
||||||
|
}
|
||||||
|
let query = QueryUtils.buildQuery([luceneFilter])
|
||||||
|
query.onEmptyFilter = EmptyFilterOption.RETURN_NONE
|
||||||
|
const result = QueryUtils.runQuery([{ value }], query)
|
||||||
|
if (result.length > 0) {
|
||||||
|
if (target === "row") {
|
||||||
|
metadata.row = {
|
||||||
|
...metadata.row,
|
||||||
|
[metadataKey]: metadataValue,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
metadata.cell[column] = {
|
||||||
|
...metadata.cell[column],
|
||||||
|
[metadataKey]: metadataValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Swallow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
|
@ -12,9 +12,9 @@ export const createStores = context => {
|
||||||
const initialFilter = getProp("initialFilter")
|
const initialFilter = getProp("initialFilter")
|
||||||
const fixedRowHeight = getProp("fixedRowHeight")
|
const fixedRowHeight = getProp("fixedRowHeight")
|
||||||
const schemaOverrides = getProp("schemaOverrides")
|
const schemaOverrides = getProp("schemaOverrides")
|
||||||
const columnWhitelist = getProp("columnWhitelist")
|
|
||||||
const notifySuccess = getProp("notifySuccess")
|
const notifySuccess = getProp("notifySuccess")
|
||||||
const notifyError = getProp("notifyError")
|
const notifyError = getProp("notifyError")
|
||||||
|
const rowConditions = getProp("rowConditions")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datasource,
|
datasource,
|
||||||
|
@ -23,9 +23,9 @@ export const createStores = context => {
|
||||||
initialFilter,
|
initialFilter,
|
||||||
fixedRowHeight,
|
fixedRowHeight,
|
||||||
schemaOverrides,
|
schemaOverrides,
|
||||||
columnWhitelist,
|
|
||||||
notifySuccess,
|
notifySuccess,
|
||||||
notifyError,
|
notifyError,
|
||||||
|
rowConditions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,8 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const {
|
const { API, definition, schemaOverrides, datasource, schemaMutations } =
|
||||||
API,
|
context
|
||||||
definition,
|
|
||||||
schemaOverrides,
|
|
||||||
columnWhitelist,
|
|
||||||
datasource,
|
|
||||||
schemaMutations,
|
|
||||||
} = context
|
|
||||||
|
|
||||||
const schema = derived(definition, $definition => {
|
const schema = derived(definition, $definition => {
|
||||||
let schema = getDatasourceSchema({
|
let schema = getDatasourceSchema({
|
||||||
|
@ -46,17 +40,13 @@ export const deriveStores = context => {
|
||||||
// Derives the total enriched schema, made up of the saved schema and any
|
// Derives the total enriched schema, made up of the saved schema and any
|
||||||
// prop and user overrides
|
// prop and user overrides
|
||||||
const enrichedSchema = derived(
|
const enrichedSchema = derived(
|
||||||
[schema, schemaOverrides, schemaMutations, columnWhitelist],
|
[schema, schemaOverrides, schemaMutations],
|
||||||
([$schema, $schemaOverrides, $schemaMutations, $columnWhitelist]) => {
|
([$schema, $schemaOverrides, $schemaMutations]) => {
|
||||||
if (!$schema) {
|
if (!$schema) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let enrichedSchema = {}
|
let enrichedSchema = {}
|
||||||
Object.keys($schema).forEach(field => {
|
Object.keys($schema).forEach(field => {
|
||||||
// Apply whitelist if provided
|
|
||||||
if ($columnWhitelist?.length && !$columnWhitelist.includes(field)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enrichedSchema[field] = {
|
enrichedSchema[field] = {
|
||||||
...$schema[field],
|
...$schema[field],
|
||||||
...$schemaOverrides?.[field],
|
...$schemaOverrides?.[field],
|
||||||
|
|
|
@ -20,6 +20,7 @@ import * as Table from "./datasources/table"
|
||||||
import * as ViewV2 from "./datasources/viewV2"
|
import * as ViewV2 from "./datasources/viewV2"
|
||||||
import * as NonPlus from "./datasources/nonPlus"
|
import * as NonPlus from "./datasources/nonPlus"
|
||||||
import * as Cache from "./cache"
|
import * as Cache from "./cache"
|
||||||
|
import * as Conditions from "./conditions"
|
||||||
|
|
||||||
const DependencyOrderedStores = [
|
const DependencyOrderedStores = [
|
||||||
Sort,
|
Sort,
|
||||||
|
@ -33,6 +34,7 @@ const DependencyOrderedStores = [
|
||||||
Scroll,
|
Scroll,
|
||||||
Validation,
|
Validation,
|
||||||
Rows,
|
Rows,
|
||||||
|
Conditions,
|
||||||
UI,
|
UI,
|
||||||
Resize,
|
Resize,
|
||||||
Viewport,
|
Viewport,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { getCellID, parseCellID } from "../lib/utils"
|
||||||
import { tick } from "svelte"
|
import { tick } from "svelte"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { sleep } from "../../../utils/utils"
|
import { sleep } from "../../../utils/utils"
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
|
@ -17,27 +18,6 @@ export const createStores = () => {
|
||||||
const error = writable(null)
|
const error = writable(null)
|
||||||
const fetch = writable(null)
|
const fetch = writable(null)
|
||||||
|
|
||||||
// Enrich rows with an index property and any pending changes
|
|
||||||
const enrichedRows = derived(
|
|
||||||
[rows, rowChangeCache],
|
|
||||||
([$rows, $rowChangeCache]) => {
|
|
||||||
return $rows.map((row, idx) => ({
|
|
||||||
...row,
|
|
||||||
...$rowChangeCache[row._id],
|
|
||||||
__idx: idx,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generate a lookup map to quick find a row by ID
|
|
||||||
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
|
||||||
let map = {}
|
|
||||||
for (let i = 0; i < $enrichedRows.length; i++) {
|
|
||||||
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mark loaded as true if we've ever stopped loading
|
// Mark loaded as true if we've ever stopped loading
|
||||||
let hasStartedLoading = false
|
let hasStartedLoading = false
|
||||||
loading.subscribe($loading => {
|
loading.subscribe($loading => {
|
||||||
|
@ -49,12 +29,8 @@ export const createStores = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rows: {
|
rows,
|
||||||
...rows,
|
|
||||||
subscribe: enrichedRows.subscribe,
|
|
||||||
},
|
|
||||||
fetch,
|
fetch,
|
||||||
rowLookupMap,
|
|
||||||
loaded,
|
loaded,
|
||||||
refreshing,
|
refreshing,
|
||||||
loading,
|
loading,
|
||||||
|
@ -65,6 +41,35 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const deriveStores = context => {
|
||||||
|
const { rows } = context
|
||||||
|
|
||||||
|
// Enrich rows with an index property and any pending changes
|
||||||
|
const enrichedRows = derived(rows, $rows => {
|
||||||
|
return $rows.map((row, idx) => ({
|
||||||
|
...row,
|
||||||
|
__idx: idx,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Generate a lookup map to quick find a row by ID
|
||||||
|
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
||||||
|
let map = {}
|
||||||
|
for (let i = 0; i < $enrichedRows.length; i++) {
|
||||||
|
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: {
|
||||||
|
...rows,
|
||||||
|
subscribe: enrichedRows.subscribe,
|
||||||
|
},
|
||||||
|
rowLookupMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = context => {
|
||||||
const {
|
const {
|
||||||
rows,
|
rows,
|
||||||
|
@ -367,7 +372,7 @@ export const createActions = context => {
|
||||||
// Get index of row to check if it exists
|
// Get index of row to check if it exists
|
||||||
const $rows = get(rows)
|
const $rows = get(rows)
|
||||||
const $rowLookupMap = get(rowLookupMap)
|
const $rowLookupMap = get(rowLookupMap)
|
||||||
const index = $rowLookupMap[id].__idx
|
const index = $rowLookupMap[id]?.__idx
|
||||||
|
|
||||||
// Process as either an update, addition or deletion
|
// Process as either an update, addition or deletion
|
||||||
if (row) {
|
if (row) {
|
||||||
|
@ -417,8 +422,21 @@ export const createActions = context => {
|
||||||
// valid pending change was made or not
|
// valid pending change was made or not
|
||||||
const stashRowChanges = (rowId, changes) => {
|
const stashRowChanges = (rowId, changes) => {
|
||||||
const $rowLookupMap = get(rowLookupMap)
|
const $rowLookupMap = get(rowLookupMap)
|
||||||
|
const $columnLookupMap = get(columnLookupMap)
|
||||||
const row = $rowLookupMap[rowId]
|
const row = $rowLookupMap[rowId]
|
||||||
|
|
||||||
|
// Coerce some values into the correct types
|
||||||
|
for (let column of Object.keys(changes || {})) {
|
||||||
|
const type = $columnLookupMap[column]?.schema?.type
|
||||||
|
|
||||||
|
// Stringify objects
|
||||||
|
if (type === FieldType.STRING || type == FieldType.LONGFORM) {
|
||||||
|
if (changes[column] != null && typeof changes[column] !== "string") {
|
||||||
|
changes[column] = JSON.stringify(changes[column])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check this is a valid change
|
// Check this is a valid change
|
||||||
if (!row || !changesAreValid(row, changes)) {
|
if (!row || !changesAreValid(row, changes)) {
|
||||||
return false
|
return false
|
||||||
|
@ -643,6 +661,7 @@ export const createActions = context => {
|
||||||
const cleanRow = row => {
|
const cleanRow = row => {
|
||||||
let clone = { ...row }
|
let clone = { ...row }
|
||||||
delete clone.__idx
|
delete clone.__idx
|
||||||
|
delete clone.__metadata
|
||||||
if (!get(hasBudibaseIdentifiers)) {
|
if (!get(hasBudibaseIdentifiers)) {
|
||||||
delete clone._id
|
delete clone._id
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ export const deriveStores = context => {
|
||||||
scrollLeft,
|
scrollLeft,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
rowChangeCache,
|
||||||
|
metadata,
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
// Derive visible rows
|
// Derive visible rows
|
||||||
|
@ -19,25 +21,31 @@ export const deriveStores = context => {
|
||||||
[scrollTop, rowHeight],
|
[scrollTop, rowHeight],
|
||||||
([$scrollTop, $rowHeight]) => {
|
([$scrollTop, $rowHeight]) => {
|
||||||
return Math.floor($scrollTop / $rowHeight)
|
return Math.floor($scrollTop / $rowHeight)
|
||||||
},
|
}
|
||||||
0
|
|
||||||
)
|
)
|
||||||
const visualRowCapacity = derived(
|
const visualRowCapacity = derived(
|
||||||
[height, rowHeight],
|
[height, rowHeight],
|
||||||
([$height, $rowHeight]) => {
|
([$height, $rowHeight]) => {
|
||||||
return Math.ceil($height / $rowHeight) + 1
|
return Math.ceil($height / $rowHeight) + 1
|
||||||
},
|
}
|
||||||
0
|
|
||||||
)
|
)
|
||||||
const renderedRows = derived(
|
const renderedRows = derived(
|
||||||
[rows, scrolledRowCount, visualRowCapacity],
|
[rows, scrolledRowCount, visualRowCapacity, rowChangeCache, metadata],
|
||||||
([$rows, $scrolledRowCount, $visualRowCapacity]) => {
|
([
|
||||||
return $rows.slice(
|
$rows,
|
||||||
$scrolledRowCount,
|
$scrolledRowCount,
|
||||||
$scrolledRowCount + $visualRowCapacity
|
$visualRowCapacity,
|
||||||
)
|
$rowChangeCache,
|
||||||
},
|
$metadata,
|
||||||
[]
|
]) => {
|
||||||
|
return $rows
|
||||||
|
.slice($scrolledRowCount, $scrolledRowCount + $visualRowCapacity)
|
||||||
|
.map(row => ({
|
||||||
|
...row,
|
||||||
|
...$rowChangeCache[row._id],
|
||||||
|
__metadata: $metadata[row._id],
|
||||||
|
}))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Derive visible columns
|
// Derive visible columns
|
||||||
|
|
|
@ -8,3 +8,4 @@ export { default as Updating } from "./Updating.svelte"
|
||||||
export { Grid } from "./grid"
|
export { Grid } from "./grid"
|
||||||
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
export { default as ClientAppSkeleton } from "./ClientAppSkeleton.svelte"
|
||||||
export { default as FilterBuilder } from "./FilterBuilder.svelte"
|
export { default as FilterBuilder } from "./FilterBuilder.svelte"
|
||||||
|
export { default as FilterUsers } from "./FilterUsers.svelte"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { FieldType, BBReferenceFieldSubType } from "@budibase/types"
|
||||||
|
|
||||||
export const BannedSearchTypes = [
|
export const BannedSearchTypes = [
|
||||||
FieldType.LINK,
|
FieldType.LINK,
|
||||||
|
FieldType.ATTACHMENT_SINGLE,
|
||||||
FieldType.ATTACHMENTS,
|
FieldType.ATTACHMENTS,
|
||||||
FieldType.FORMULA,
|
FieldType.FORMULA,
|
||||||
FieldType.JSON,
|
FieldType.JSON,
|
||||||
|
|
|
@ -16,5 +16,6 @@
|
||||||
/* Custom additions */
|
/* Custom additions */
|
||||||
--modal-background: var(--spectrum-global-color-gray-50);
|
--modal-background: var(--spectrum-global-color-gray-50);
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
--drop-shadow: rgba(0, 0, 0, 0.25) !important;
|
||||||
--spectrum-global-color-blue-100: rgba(36, 44, 64) !important;
|
--spectrum-global-color-blue-100: hsl(var(--hue), 48%, 24%) !important;
|
||||||
|
--translucent-grey: rgba(255, 255, 255, 0.075) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,5 +49,6 @@
|
||||||
/* Custom additions */
|
/* Custom additions */
|
||||||
--modal-background: var(--spectrum-global-color-gray-50);
|
--modal-background: var(--spectrum-global-color-gray-50);
|
||||||
--drop-shadow: rgba(0, 0, 0, 0.15) !important;
|
--drop-shadow: rgba(0, 0, 0, 0.15) !important;
|
||||||
--spectrum-global-color-blue-100: rgb(56, 65, 90) !important;
|
--spectrum-global-color-blue-100: hsl(213, 36%, 30%) !important;
|
||||||
|
--translucent-grey: hsla(213, 36%, 80%, 0.11) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ import {
|
||||||
} from "../../../integrations/tests/utils"
|
} from "../../../integrations/tests/utils"
|
||||||
import {
|
import {
|
||||||
db as dbCore,
|
db as dbCore,
|
||||||
|
context,
|
||||||
MAX_VALID_DATE,
|
MAX_VALID_DATE,
|
||||||
MIN_VALID_DATE,
|
MIN_VALID_DATE,
|
||||||
utils,
|
utils,
|
||||||
|
SQLITE_DESIGN_DOC_ID,
|
||||||
} from "@budibase/backend-core"
|
} from "@budibase/backend-core"
|
||||||
|
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
|
@ -2524,4 +2526,38 @@ describe.each([
|
||||||
}).toContainExactly([{ [" name"]: "foo" }])
|
}).toContainExactly([{ [" name"]: "foo" }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
isSqs &&
|
||||||
|
describe("duplicate columns", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
table = await createTable({
|
||||||
|
name: {
|
||||||
|
name: "name",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await context.doInAppContext(config.getAppId(), async () => {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const tableDoc = await db.get<Table>(table._id!)
|
||||||
|
tableDoc.schema.Name = {
|
||||||
|
name: "Name",
|
||||||
|
type: FieldType.STRING,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// remove the SQLite definitions so that they can be rebuilt as part of the search
|
||||||
|
const sqliteDoc = await db.get(SQLITE_DESIGN_DOC_ID)
|
||||||
|
await db.remove(sqliteDoc)
|
||||||
|
} catch (err) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await createRows([{ name: "foo", Name: "bar" }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should handle invalid duplicate column names", async () => {
|
||||||
|
await expectSearch({
|
||||||
|
query: {},
|
||||||
|
}).toContainExactly([{ name: "foo" }])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -49,6 +49,7 @@ import { dataFilters } from "@budibase/shared-core"
|
||||||
const builder = new sql.Sql(SqlClient.SQL_LITE)
|
const builder = new sql.Sql(SqlClient.SQL_LITE)
|
||||||
const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`)
|
const MISSING_COLUMN_REGEX = new RegExp(`no such column: .+`)
|
||||||
const MISSING_TABLE_REGX = new RegExp(`no such table: .+`)
|
const MISSING_TABLE_REGX = new RegExp(`no such table: .+`)
|
||||||
|
const DUPLICATE_COLUMN_REGEX = new RegExp(`duplicate column name: .+`)
|
||||||
|
|
||||||
function buildInternalFieldList(
|
function buildInternalFieldList(
|
||||||
table: Table,
|
table: Table,
|
||||||
|
@ -237,9 +238,11 @@ function resyncDefinitionsRequired(status: number, message: string) {
|
||||||
// pre data_ prefix on column names, need to resync
|
// pre data_ prefix on column names, need to resync
|
||||||
return (
|
return (
|
||||||
// there are tables missing - try a resync
|
// there are tables missing - try a resync
|
||||||
(status === 400 && message.match(MISSING_TABLE_REGX)) ||
|
(status === 400 && message?.match(MISSING_TABLE_REGX)) ||
|
||||||
// there are columns missing - try a resync
|
// there are columns missing - try a resync
|
||||||
(status === 400 && message.match(MISSING_COLUMN_REGEX)) ||
|
(status === 400 && message?.match(MISSING_COLUMN_REGEX)) ||
|
||||||
|
// duplicate column name in definitions - need to re-run definition sync
|
||||||
|
(status === 400 && message?.match(DUPLICATE_COLUMN_REGEX)) ||
|
||||||
// no design document found, needs a full sync
|
// no design document found, needs a full sync
|
||||||
(status === 404 && message?.includes(SQLITE_DESIGN_DOC_ID))
|
(status === 404 && message?.includes(SQLITE_DESIGN_DOC_ID))
|
||||||
)
|
)
|
||||||
|
|
|
@ -94,6 +94,9 @@ export function mapToUserColumn(key: string) {
|
||||||
function mapTable(table: Table): SQLiteTables {
|
function mapTable(table: Table): SQLiteTables {
|
||||||
const tables: SQLiteTables = {}
|
const tables: SQLiteTables = {}
|
||||||
const fields: Record<string, { field: string; type: SQLiteType }> = {}
|
const fields: Record<string, { field: string; type: SQLiteType }> = {}
|
||||||
|
// a list to make sure no duplicates - the fields are mapped by SQS with case sensitivity
|
||||||
|
// but need to make sure there are no duplicate columns
|
||||||
|
const usedColumns: string[] = []
|
||||||
for (let [key, column] of Object.entries(table.schema)) {
|
for (let [key, column] of Object.entries(table.schema)) {
|
||||||
// relationships should be handled differently
|
// relationships should be handled differently
|
||||||
if (column.type === FieldType.LINK) {
|
if (column.type === FieldType.LINK) {
|
||||||
|
@ -106,6 +109,12 @@ function mapTable(table: Table): SQLiteTables {
|
||||||
if (!FieldTypeMap[column.type]) {
|
if (!FieldTypeMap[column.type]) {
|
||||||
throw new Error(`Unable to map type "${column.type}" to SQLite type`)
|
throw new Error(`Unable to map type "${column.type}" to SQLite type`)
|
||||||
}
|
}
|
||||||
|
const lcKey = key.toLowerCase()
|
||||||
|
// ignore duplicates
|
||||||
|
if (usedColumns.includes(lcKey)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
usedColumns.push(lcKey)
|
||||||
fields[mapToUserColumn(key)] = {
|
fields[mapToUserColumn(key)] = {
|
||||||
field: key,
|
field: key,
|
||||||
type: FieldTypeMap[column.type],
|
type: FieldTypeMap[column.type],
|
||||||
|
|
Loading…
Reference in New Issue