Merge branch 'master' of github.com:Budibase/budibase into feature/sqs-table-cleanup
This commit is contained in:
commit
541e31633d
40 changed files with 285 additions and 192 deletions
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
|
|
|
@ -12,4 +12,5 @@ packages/pro/coverage
|
|||
packages/account-portal/packages/ui/build
|
||||
packages/account-portal/packages/ui/.routify
|
||||
packages/account-portal/packages/server/build
|
||||
packages/account-portal/packages/server/coverage
|
||||
**/*.ivm.bundle.js
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.23.12",
|
||||
"version": "2.24.1",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -83,7 +83,6 @@
|
|||
"dayjs": "^1.10.8",
|
||||
"easymde": "^2.16.1",
|
||||
"svelte-dnd-action": "^0.9.8",
|
||||
"svelte-flatpickr": "3.2.3",
|
||||
"svelte-portal": "^1.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
// These class names will never trigger a callback if clicked, no matter what
|
||||
const ignoredClasses = [
|
||||
".download-js-link",
|
||||
".flatpickr-calendar",
|
||||
".spectrum-Menu",
|
||||
".date-time-popover",
|
||||
]
|
||||
|
||||
// These class names will only trigger a callback when clicked if the registered
|
||||
// component is not nested inside them. For example, clicking inside a modal
|
||||
// will not close the modal, or clicking inside a popover will not close the
|
||||
// popover.
|
||||
const conditionallyIgnoredClasses = [
|
||||
".spectrum-Underlay",
|
||||
".drawer-wrapper",
|
||||
".spectrum-Popover",
|
||||
]
|
||||
let clickHandlers = []
|
||||
let candidateTarget
|
||||
|
||||
/**
|
||||
* Handle a body click event
|
||||
*/
|
||||
// Processes a "click outside" event and invokes callbacks if our source element
|
||||
// is valid
|
||||
const handleClick = event => {
|
||||
// Treat right clicks (context menu events) as normal clicks
|
||||
const eventType = event.type === "contextmenu" ? "click" : event.type
|
||||
|
||||
// Ignore click if this is an ignored class
|
||||
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
||||
return
|
||||
|
@ -30,11 +32,6 @@ const handleClick = event => {
|
|||
|
||||
// Process handlers
|
||||
clickHandlers.forEach(handler => {
|
||||
// Check that we're the right kind of click event
|
||||
if (handler.allowedType && eventType !== handler.allowedType) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the click isn't inside the target
|
||||
if (handler.element.contains(event.target)) {
|
||||
return
|
||||
|
@ -52,17 +49,43 @@ const handleClick = event => {
|
|||
handler.callback?.(event)
|
||||
})
|
||||
}
|
||||
document.documentElement.addEventListener("click", handleClick, true)
|
||||
document.documentElement.addEventListener("mousedown", handleClick, true)
|
||||
document.documentElement.addEventListener("contextmenu", handleClick, true)
|
||||
|
||||
// On mouse up we only trigger a "click outside" callback if we targetted the
|
||||
// same element that we did on mouse down. This fixes all sorts of issues where
|
||||
// we get annoying callbacks firing when we drag to select text.
|
||||
const handleMouseUp = e => {
|
||||
if (candidateTarget === e.target) {
|
||||
handleClick(e)
|
||||
}
|
||||
candidateTarget = null
|
||||
}
|
||||
|
||||
// On mouse down we store which element was targetted for comparison later
|
||||
const handleMouseDown = e => {
|
||||
// Only handle the primary mouse button here.
|
||||
// We handle context menu (right click) events in another handler.
|
||||
if (e.button !== 0) {
|
||||
return
|
||||
}
|
||||
candidateTarget = e.target
|
||||
|
||||
// Clear any previous listeners in case of multiple down events, and register
|
||||
// a single mouse up listener
|
||||
document.removeEventListener("mouseup", handleMouseUp)
|
||||
document.addEventListener("mouseup", handleMouseUp, true)
|
||||
}
|
||||
|
||||
// Global singleton listeners for our events
|
||||
document.addEventListener("mousedown", handleMouseDown)
|
||||
document.addEventListener("contextmenu", handleClick)
|
||||
|
||||
/**
|
||||
* Adds or updates a click handler
|
||||
*/
|
||||
const updateHandler = (id, element, anchor, callback, allowedType) => {
|
||||
const updateHandler = (id, element, anchor, callback) => {
|
||||
let existingHandler = clickHandlers.find(x => x.id === id)
|
||||
if (!existingHandler) {
|
||||
clickHandlers.push({ id, element, anchor, callback, allowedType })
|
||||
clickHandlers.push({ id, element, anchor, callback })
|
||||
} else {
|
||||
existingHandler.callback = callback
|
||||
}
|
||||
|
@ -89,8 +112,7 @@ export default (element, opts) => {
|
|||
const callback =
|
||||
newOpts?.callback || (typeof newOpts === "function" ? newOpts : null)
|
||||
const anchor = newOpts?.anchor || element
|
||||
const allowedType = newOpts?.allowedType || "click"
|
||||
updateHandler(id, element, anchor, callback, allowedType)
|
||||
updateHandler(id, element, anchor, callback)
|
||||
}
|
||||
update(opts)
|
||||
return {
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
import dayjs from "dayjs"
|
||||
import NumberInput from "./NumberInput.svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import isoWeek from "dayjs/plugin/isoWeek"
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
|
||||
export let value
|
||||
|
||||
|
@ -43,7 +46,7 @@
|
|||
return []
|
||||
}
|
||||
let monthEnd = monthStart.endOf("month")
|
||||
let calendarStart = monthStart.startOf("week")
|
||||
let calendarStart = monthStart.startOf("isoWeek")
|
||||
const numWeeks = Math.ceil((monthEnd.diff(calendarStart, "day") + 1) / 7)
|
||||
|
||||
let mondays = []
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
export let narrower = false
|
||||
export let noPadding = false
|
||||
|
||||
let sidePanelVisble = false
|
||||
let sidePanelVisible = false
|
||||
|
||||
setContext("side-panel", {
|
||||
open: () => (sidePanelVisble = true),
|
||||
close: () => (sidePanelVisble = false),
|
||||
open: () => (sidePanelVisible = true),
|
||||
close: () => (sidePanelVisible = false),
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -24,9 +24,9 @@
|
|||
</div>
|
||||
<div
|
||||
id="side-panel"
|
||||
class:visible={sidePanelVisble}
|
||||
class:visible={sidePanelVisible}
|
||||
use:clickOutside={() => {
|
||||
sidePanelVisble = false
|
||||
sidePanelVisible = false
|
||||
}}
|
||||
>
|
||||
<slot name="side-panel" />
|
||||
|
|
|
@ -154,7 +154,7 @@ export const parseDate = (value, { enableTime = true }) => {
|
|||
// schema flags
|
||||
export const stringifyDate = (
|
||||
value,
|
||||
{ enableTime = true, timeOnly = false, ignoreTimezones = false }
|
||||
{ enableTime = true, timeOnly = false, ignoreTimezones = false } = {}
|
||||
) => {
|
||||
if (!value) {
|
||||
return null
|
||||
|
@ -210,7 +210,7 @@ const localeDateFormat = new Intl.DateTimeFormat()
|
|||
// Formats a dayjs date according to schema flags
|
||||
export const getDateDisplayValue = (
|
||||
value,
|
||||
{ enableTime = true, timeOnly = false }
|
||||
{ enableTime = true, timeOnly = false } = {}
|
||||
) => {
|
||||
if (!value?.isValid()) {
|
||||
return ""
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<script>
|
||||
import { Body, Label, Input } from "@budibase/bbui"
|
||||
import { Body, Label } from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
|
||||
export let parameters
|
||||
export let bindings
|
||||
|
||||
onMount(() => {
|
||||
if (!parameters.confirm) {
|
||||
|
@ -15,11 +17,18 @@
|
|||
<Body size="S">Enter the message you wish to display to the user.</Body>
|
||||
<div class="params">
|
||||
<Label small>Title</Label>
|
||||
<Input placeholder="Prompt User" bind:value={parameters.customTitleText} />
|
||||
<DrawerBindableInput
|
||||
placeholder="Title"
|
||||
value={parameters.customTitleText}
|
||||
on:change={e => (parameters.customTitleText = e.detail)}
|
||||
{bindings}
|
||||
/>
|
||||
<Label small>Message</Label>
|
||||
<Input
|
||||
<DrawerBindableInput
|
||||
placeholder="Are you sure you want to continue?"
|
||||
bind:value={parameters.confirmText}
|
||||
value={parameters.confirmText}
|
||||
on:change={e => (parameters.confirmText = e.detail)}
|
||||
{bindings}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,26 +21,24 @@
|
|||
const currentStep = derived(multiStepStore, state => state.currentStep)
|
||||
const componentType = "@budibase/standard-components/multistepformblockstep"
|
||||
|
||||
setContext("multi-step-form-block", multiStepStore)
|
||||
|
||||
let cachedValue
|
||||
let cachedInstance = {}
|
||||
|
||||
$: if (!isEqual(cachedValue, value)) {
|
||||
cachedValue = value
|
||||
}
|
||||
|
||||
$: if (!isEqual(componentInstance, cachedInstance)) {
|
||||
cachedInstance = componentInstance
|
||||
}
|
||||
|
||||
setContext("multi-step-form-block", multiStepStore)
|
||||
|
||||
$: stepCount = cachedValue?.length || 0
|
||||
$: updateStore(stepCount)
|
||||
$: dataSource = getDatasourceForProvider($selectedScreen, cachedInstance)
|
||||
$: emitCurrentStep($currentStep)
|
||||
$: stepLabel = getStepLabel($multiStepStore)
|
||||
$: stepDef = getDefinition(stepLabel)
|
||||
$: stepSettings = cachedValue?.[$currentStep] || {}
|
||||
$: savedInstance = cachedValue?.[$currentStep] || {}
|
||||
$: defaults = Utils.buildMultiStepFormBlockDefaultProps({
|
||||
_id: cachedInstance._id,
|
||||
stepCount: $multiStepStore.stepCount,
|
||||
|
@ -48,14 +46,16 @@
|
|||
actionType: cachedInstance.actionType,
|
||||
dataSource: cachedInstance.dataSource,
|
||||
})
|
||||
// For backwards compatibility we need to sometimes manually set base
|
||||
// properties like _id and _component as we didn't used to save these
|
||||
$: stepInstance = {
|
||||
_id: Helpers.uuid(),
|
||||
_component: componentType,
|
||||
_id: savedInstance._id || Helpers.uuid(),
|
||||
_component: savedInstance._component || componentType,
|
||||
_instanceName: `Step ${currentStep + 1}`,
|
||||
title: stepSettings.title ?? defaults?.title,
|
||||
buttons: stepSettings.buttons || defaults?.buttons,
|
||||
fields: stepSettings.fields,
|
||||
desc: stepSettings.desc,
|
||||
title: savedInstance.title ?? defaults?.title,
|
||||
buttons: savedInstance.buttons || defaults?.buttons,
|
||||
fields: savedInstance.fields,
|
||||
desc: savedInstance.desc,
|
||||
|
||||
// Needed for field configuration
|
||||
dataSource,
|
||||
|
@ -92,7 +92,8 @@
|
|||
}
|
||||
|
||||
const addStep = () => {
|
||||
value = value.toSpliced($currentStep + 1, 0, {})
|
||||
const newInstance = componentStore.createInstance(componentType)
|
||||
value = value.toSpliced($currentStep + 1, 0, newInstance)
|
||||
dispatch("change", value)
|
||||
multiStepStore.update(state => ({
|
||||
...state,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
import { ActionButton, AbsTooltip } from "@budibase/bbui"
|
||||
|
||||
const multiStepStore = getContext("multi-step-form-block")
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -28,45 +28,49 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div class="step-actions">
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
icon="ChevronLeft"
|
||||
disabled={currentStep === 0}
|
||||
on:click={() => {
|
||||
stepAction("previousStep")
|
||||
}}
|
||||
tooltip={"Previous step"}
|
||||
/>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
disabled={currentStep === stepCount - 1}
|
||||
icon="ChevronRight"
|
||||
on:click={() => {
|
||||
stepAction("nextStep")
|
||||
}}
|
||||
tooltip={"Next step"}
|
||||
/>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
icon="Close"
|
||||
disabled={stepCount === 1}
|
||||
on:click={() => {
|
||||
stepAction("removeStep")
|
||||
}}
|
||||
tooltip={"Remove step"}
|
||||
/>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
icon="MultipleAdd"
|
||||
on:click={() => {
|
||||
stepAction("addStep")
|
||||
}}
|
||||
tooltip={"Add step"}
|
||||
/>
|
||||
<AbsTooltip text="Previous step" noWrap>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
icon="ChevronLeft"
|
||||
disabled={currentStep === 0}
|
||||
on:click={() => {
|
||||
stepAction("previousStep")
|
||||
}}
|
||||
/>
|
||||
</AbsTooltip>
|
||||
<AbsTooltip text="Next step" noWrap>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
disabled={currentStep === stepCount - 1}
|
||||
icon="ChevronRight"
|
||||
on:click={() => {
|
||||
stepAction("nextStep")
|
||||
}}
|
||||
/>
|
||||
</AbsTooltip>
|
||||
<AbsTooltip text="Remove step" noWrap>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
icon="Close"
|
||||
disabled={stepCount === 1}
|
||||
on:click={() => {
|
||||
stepAction("removeStep")
|
||||
}}
|
||||
/>
|
||||
</AbsTooltip>
|
||||
<AbsTooltip text="Add step" noWrap>
|
||||
<ActionButton
|
||||
size="S"
|
||||
secondary
|
||||
icon="MultipleAdd"
|
||||
on:click={() => {
|
||||
stepAction("addStep")
|
||||
}}
|
||||
/>
|
||||
</AbsTooltip>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => {
|
|||
return createComponent(
|
||||
"@budibase/standard-components/labelfield",
|
||||
{
|
||||
_id: column.field,
|
||||
_instanceName: column.field,
|
||||
active: column.active,
|
||||
field: column.field,
|
||||
|
|
|
@ -65,6 +65,7 @@ describe("getColumns", () => {
|
|||
it("returns the selected and unselected fields in the modern format, respecting the original order", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_id: "three",
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -73,6 +74,7 @@ describe("getColumns", () => {
|
|||
label: "three label",
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
_instanceName: "two",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -81,6 +83,7 @@ describe("getColumns", () => {
|
|||
label: "two label",
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
@ -91,6 +94,7 @@ describe("getColumns", () => {
|
|||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_id: "four",
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -115,6 +119,7 @@ describe("getColumns", () => {
|
|||
it("returns all columns, with non-hidden columns automatically selected", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_id: "two",
|
||||
_instanceName: "two",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -123,6 +128,7 @@ describe("getColumns", () => {
|
|||
label: "two",
|
||||
},
|
||||
{
|
||||
_id: "three",
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -131,6 +137,7 @@ describe("getColumns", () => {
|
|||
label: "three",
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
@ -141,6 +148,7 @@ describe("getColumns", () => {
|
|||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_id: "four",
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -173,6 +181,7 @@ describe("getColumns", () => {
|
|||
it("returns all columns, including those missing from the initial data", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_id: "three",
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -181,6 +190,7 @@ describe("getColumns", () => {
|
|||
label: "three label",
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
_instanceName: "two",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
@ -189,6 +199,7 @@ describe("getColumns", () => {
|
|||
label: "two",
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
@ -199,6 +210,7 @@ describe("getColumns", () => {
|
|||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_id: "four",
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -228,6 +240,7 @@ describe("getColumns", () => {
|
|||
it("returns all valid columns, excluding those that aren't valid for the schema", ctx => {
|
||||
expect(ctx.columns.sortable).toEqual([
|
||||
{
|
||||
_id: "three",
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -236,6 +249,7 @@ describe("getColumns", () => {
|
|||
label: "three label",
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
_instanceName: "two",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
@ -244,6 +258,7 @@ describe("getColumns", () => {
|
|||
label: "two",
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
_instanceName: "one",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
@ -254,6 +269,7 @@ describe("getColumns", () => {
|
|||
])
|
||||
|
||||
expect(ctx.columns.primary).toEqual({
|
||||
_id: "four",
|
||||
_instanceName: "four",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -318,6 +334,7 @@ describe("getColumns", () => {
|
|||
beforeEach(ctx => {
|
||||
ctx.updateSortable([
|
||||
{
|
||||
_id: "three",
|
||||
_instanceName: "three",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -326,6 +343,7 @@ describe("getColumns", () => {
|
|||
label: "three",
|
||||
},
|
||||
{
|
||||
_id: "one",
|
||||
_instanceName: "one",
|
||||
active: true,
|
||||
columnType: "foo",
|
||||
|
@ -334,6 +352,7 @@ describe("getColumns", () => {
|
|||
label: "one",
|
||||
},
|
||||
{
|
||||
_id: "two",
|
||||
_instanceName: "two",
|
||||
active: false,
|
||||
columnType: "foo",
|
||||
|
|
|
@ -25,6 +25,6 @@
|
|||
name="field"
|
||||
headings
|
||||
options={SchemaTypeOptionsExpanded}
|
||||
compare={(option, value) => option.type === value.type}
|
||||
compare={(option, value) => option.type === value?.type}
|
||||
/>
|
||||
{/key}
|
||||
|
|
|
@ -695,7 +695,7 @@
|
|||
menuItems={schemaMenuItems}
|
||||
showMenu={!schemaReadOnly}
|
||||
readOnly={schemaReadOnly}
|
||||
compare={(option, value) => option.type === value.type}
|
||||
compare={(option, value) => option.type === value?.type}
|
||||
/>
|
||||
</Tab>
|
||||
{/if}
|
||||
|
|
|
@ -253,6 +253,7 @@ export const SchemaTypeOptions = [
|
|||
{ label: "Number", value: FieldType.NUMBER },
|
||||
{ label: "Boolean", value: FieldType.BOOLEAN },
|
||||
{ label: "Datetime", value: FieldType.DATETIME },
|
||||
{ label: "JSON", value: FieldType.JSON },
|
||||
]
|
||||
|
||||
export const SchemaTypeOptionsExpanded = SchemaTypeOptions.map(el => ({
|
||||
|
|
|
@ -1106,50 +1106,51 @@ export const getAllStateVariables = () => {
|
|||
getAllAssets().forEach(asset => {
|
||||
findAllMatchingComponents(asset.props, component => {
|
||||
const settings = componentStore.getComponentSettings(component._component)
|
||||
const nestedTypes = [
|
||||
"buttonConfiguration",
|
||||
"fieldConfiguration",
|
||||
"stepConfiguration",
|
||||
]
|
||||
|
||||
// Extracts all event settings from a component instance.
|
||||
// Recurses into nested types to find all event-like settings at any
|
||||
// depth.
|
||||
const parseEventSettings = (settings, comp) => {
|
||||
if (!settings?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract top level event settings
|
||||
settings
|
||||
.filter(setting => setting.type === "event")
|
||||
.forEach(setting => {
|
||||
eventSettings.push(comp[setting.key])
|
||||
})
|
||||
}
|
||||
|
||||
const parseComponentSettings = (settings, component) => {
|
||||
// Parse the nested button configurations
|
||||
// Recurse into any nested instance types
|
||||
settings
|
||||
.filter(setting => setting.type === "buttonConfiguration")
|
||||
.filter(setting => nestedTypes.includes(setting.type))
|
||||
.forEach(setting => {
|
||||
const buttonConfig = component[setting.key]
|
||||
const instances = comp[setting.key]
|
||||
if (Array.isArray(instances) && instances.length) {
|
||||
instances.forEach(instance => {
|
||||
let type = instance?._component
|
||||
|
||||
if (Array.isArray(buttonConfig)) {
|
||||
buttonConfig.forEach(button => {
|
||||
const nestedSettings = componentStore.getComponentSettings(
|
||||
button._component
|
||||
)
|
||||
parseEventSettings(nestedSettings, button)
|
||||
// Backwards compatibility for multi-step from blocks which
|
||||
// didn't set a proper component type previously.
|
||||
if (setting.type === "stepConfiguration" && !type) {
|
||||
type = "@budibase/standard-components/multistepformblockstep"
|
||||
}
|
||||
|
||||
// Parsed nested component instances inside this setting
|
||||
const nestedSettings = componentStore.getComponentSettings(type)
|
||||
parseEventSettings(nestedSettings, instance)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
parseEventSettings(settings, component)
|
||||
}
|
||||
|
||||
// Parse the base component settings
|
||||
parseComponentSettings(settings, component)
|
||||
|
||||
// Parse step configuration
|
||||
const stepSetting = settings.find(
|
||||
setting => setting.type === "stepConfiguration"
|
||||
)
|
||||
const steps = stepSetting ? component[stepSetting.key] : []
|
||||
const stepDefinition = componentStore.getComponentSettings(
|
||||
"@budibase/standard-components/multistepformblockstep"
|
||||
)
|
||||
|
||||
steps?.forEach(step => {
|
||||
parseComponentSettings(stepDefinition, step)
|
||||
})
|
||||
parseEventSettings(settings, component)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -6717,7 +6717,20 @@
|
|||
"illegalChildren": ["section", "sidepanel"],
|
||||
"showEmptyState": false,
|
||||
"draggable": false,
|
||||
"info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action."
|
||||
"info": "Side panels are hidden by default. They will only be revealed when triggered by the 'Open Side Panel' action.",
|
||||
"settings": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "ignoreClicksOutside",
|
||||
"label": "Ignore clicks outside",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"key": "onClose",
|
||||
"label": "On close"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rowexplorer": {
|
||||
"block": true,
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
"screenfull": "^6.0.1",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-apexcharts": "^1.0.2",
|
||||
"svelte-flatpickr": "^3.3.4",
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -206,13 +206,6 @@
|
|||
/>
|
||||
{/key}
|
||||
|
||||
<!--
|
||||
Flatpickr needs to be inside the theme wrapper.
|
||||
It also needs its own container because otherwise it hijacks
|
||||
key events on the whole page. It is painful to work with.
|
||||
-->
|
||||
<div id="flatpickr-root" />
|
||||
|
||||
<!-- Modal container to ensure they sit on top -->
|
||||
<div class="modal-container" />
|
||||
|
||||
|
|
|
@ -60,16 +60,6 @@
|
|||
--spectrum-link-primary-m-text-color-hover: var(--primaryColorHover);
|
||||
}
|
||||
|
||||
/* Theme flatpickr */
|
||||
:global(.flatpickr-day.selected) {
|
||||
background: var(--primaryColor);
|
||||
border-color: var(--primaryColor);
|
||||
}
|
||||
:global(.flatpickr-day.selected:hover) {
|
||||
background: var(--primaryColorHover);
|
||||
border-color: var(--primaryColorHover);
|
||||
}
|
||||
|
||||
/* Custom scrollbars */
|
||||
:global(::-webkit-scrollbar) {
|
||||
width: 8px;
|
||||
|
|
|
@ -73,7 +73,10 @@
|
|||
$context.device.width,
|
||||
$context.device.height
|
||||
)
|
||||
$: autoCloseSidePanel = !$builderStore.inBuilder && $sidePanelStore.open
|
||||
$: autoCloseSidePanel =
|
||||
!$builderStore.inBuilder &&
|
||||
$sidePanelStore.open &&
|
||||
!$sidePanelStore.ignoreClicksOutside
|
||||
$: screenId = $builderStore.inBuilder
|
||||
? `${$builderStore.screen?._id}-screen`
|
||||
: "screen"
|
||||
|
@ -191,6 +194,11 @@
|
|||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const handleClickLink = () => {
|
||||
mobileOpen = false
|
||||
sidePanelStore.actions.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
@ -281,7 +289,7 @@
|
|||
url={navItem.url}
|
||||
subLinks={navItem.subLinks}
|
||||
internalLink={navItem.internalLink}
|
||||
on:clickLink={() => (mobileOpen = false)}
|
||||
on:clickLink={handleClickLink}
|
||||
leftNav={navigation === "Left"}
|
||||
{mobile}
|
||||
{navStateStore}
|
||||
|
@ -316,10 +324,7 @@
|
|||
<div
|
||||
id="side-panel-container"
|
||||
class:open={$sidePanelStore.open}
|
||||
use:clickOutside={{
|
||||
callback: autoCloseSidePanel ? sidePanelStore.actions.close : null,
|
||||
allowedType: "mousedown",
|
||||
}}
|
||||
use:clickOutside={autoCloseSidePanel ? sidePanelStore.actions.close : null}
|
||||
class:builder={$builderStore.inBuilder}
|
||||
>
|
||||
<div class="side-panel-header">
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
const { styleable, sidePanelStore, builderStore, dndIsDragging } =
|
||||
getContext("sdk")
|
||||
|
||||
export let onClose
|
||||
export let ignoreClicksOutside
|
||||
|
||||
// Automatically show and hide the side panel when inside the builder.
|
||||
// For some unknown reason, svelte reactivity breaks if we reference the
|
||||
// reactive variable "open" inside the following expression, or if we define
|
||||
|
@ -26,6 +29,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
// $: {
|
||||
|
||||
// }
|
||||
|
||||
// Derive visibility
|
||||
$: open = $sidePanelStore.contentId === $component.id
|
||||
|
||||
|
@ -36,10 +43,17 @@
|
|||
let renderKey = null
|
||||
$: {
|
||||
if (open) {
|
||||
sidePanelStore.actions.setIgnoreClicksOutside(ignoreClicksOutside)
|
||||
renderKey = Math.random()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSidePanelClose = async () => {
|
||||
if (onClose) {
|
||||
await onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const showInSidePanel = (el, visible) => {
|
||||
const update = visible => {
|
||||
const target = document.getElementById("side-panel-container")
|
||||
|
@ -51,6 +65,7 @@
|
|||
} else {
|
||||
if (target.contains(node)) {
|
||||
target.removeChild(node)
|
||||
handleSidePanelClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,6 @@
|
|||
readonly={fieldState.readonly}
|
||||
error={fieldState.error}
|
||||
id={fieldState.fieldId}
|
||||
appendTo={document.getElementById("flatpickr-root")}
|
||||
{enableTime}
|
||||
{timeOnly}
|
||||
{time24hr}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import flatpickr from "flatpickr"
|
||||
import dayjs from "dayjs"
|
||||
import { FieldTypes } from "../../../constants"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
|
||||
/**
|
||||
* Creates a validation function from a combination of schema-level constraints
|
||||
|
@ -81,7 +82,7 @@ export const createValidatorFromConstraints = (
|
|||
// Date constraint
|
||||
if (exists(schemaConstraints.datetime?.earliest)) {
|
||||
const limit = schemaConstraints.datetime.earliest
|
||||
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
|
||||
const limitString = Helpers.getDateDisplayValue(dayjs(limit))
|
||||
rules.push({
|
||||
type: "datetime",
|
||||
constraint: "minValue",
|
||||
|
@ -91,7 +92,7 @@ export const createValidatorFromConstraints = (
|
|||
}
|
||||
if (exists(schemaConstraints.datetime?.latest)) {
|
||||
const limit = schemaConstraints.datetime.latest
|
||||
const limitString = flatpickr.formatDate(new Date(limit), "F j Y, H:i")
|
||||
const limitString = Helpers.getDateDisplayValue(dayjs(limit))
|
||||
rules.push({
|
||||
type: "datetime",
|
||||
constraint: "maxValue",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { writable, derived } from "svelte/store"
|
|||
export const createSidePanelStore = () => {
|
||||
const initialState = {
|
||||
contentId: null,
|
||||
ignoreClicksOutside: true,
|
||||
}
|
||||
const store = writable(initialState)
|
||||
const derivedStore = derived(store, $store => {
|
||||
|
@ -32,11 +33,18 @@ export const createSidePanelStore = () => {
|
|||
}, 50)
|
||||
}
|
||||
|
||||
const setIgnoreClicksOutside = bool => {
|
||||
store.update(state => {
|
||||
state.ignoreClicksOutside = bool
|
||||
return state
|
||||
})
|
||||
}
|
||||
return {
|
||||
subscribe: derivedStore.subscribe,
|
||||
actions: {
|
||||
open,
|
||||
close,
|
||||
setIgnoreClicksOutside,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,6 +240,7 @@ const triggerAutomationHandler = async action => {
|
|||
const navigationHandler = action => {
|
||||
const { url, peek, externalNewTab } = action.parameters
|
||||
routeStore.actions.navigate(url, peek, externalNewTab)
|
||||
closeSidePanelHandler()
|
||||
}
|
||||
|
||||
const queryExecutionHandler = async action => {
|
||||
|
@ -541,16 +542,22 @@ export const enrichButtonActions = (actions, context) => {
|
|||
// then execute the rest of the actions in the chain
|
||||
const result = await callback()
|
||||
if (result !== false) {
|
||||
// Generate a new total context to pass into the next enrichment
|
||||
// Generate a new total context for the next enrichment
|
||||
buttonContext.push(result)
|
||||
const newContext = { ...context, actions: buttonContext }
|
||||
|
||||
// Enrich and call the next button action if there is more than one action remaining
|
||||
// Enrich and call the next button action if there is more
|
||||
// than one action remaining
|
||||
const next = enrichButtonActions(
|
||||
actions.slice(i + 1),
|
||||
newContext
|
||||
)
|
||||
resolve(typeof next === "function" ? await next() : true)
|
||||
if (typeof next === "function") {
|
||||
// Pass the event context back into the new action chain
|
||||
resolve(await next(eventContext))
|
||||
} else {
|
||||
resolve(true)
|
||||
}
|
||||
} else {
|
||||
resolve(false)
|
||||
}
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
const fieldSchema = schemaFields.find(x => x.name === filter.field)
|
||||
filter.type = fieldSchema?.type
|
||||
filter.subtype = fieldSchema?.subtype
|
||||
filter.formulaType = fieldSchema?.formulaType
|
||||
|
||||
// Update external type based on field
|
||||
filter.externalType = getSchema(filter)?.externalType
|
||||
|
|
|
@ -121,8 +121,14 @@
|
|||
|
||||
const onContextMenu = e => {
|
||||
e.preventDefault()
|
||||
ui.actions.blur()
|
||||
open = !open
|
||||
|
||||
// The timeout allows time for clickoutside to close other open popvers
|
||||
// before we show this one. Without the timeout, this popover closes again
|
||||
// before it's even visible as clickoutside closes it.
|
||||
setTimeout(() => {
|
||||
ui.actions.blur()
|
||||
open = !open
|
||||
}, 10)
|
||||
}
|
||||
|
||||
const sortAscending = () => {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
focusedCellAPI,
|
||||
focusedRowId,
|
||||
notifications,
|
||||
isDatasourcePlus,
|
||||
hasBudibaseIdentifiers,
|
||||
} = getContext("grid")
|
||||
|
||||
let anchor
|
||||
|
@ -82,7 +82,7 @@
|
|||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Copy"
|
||||
disabled={isNewRow || !$focusedRow?._id || !$isDatasourcePlus}
|
||||
disabled={isNewRow || !$focusedRow?._id || !$hasBudibaseIdentifiers}
|
||||
on:click={() => copyToClipboard($focusedRow?._id)}
|
||||
on:click={menu.actions.close}
|
||||
>
|
||||
|
@ -90,7 +90,7 @@
|
|||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Copy"
|
||||
disabled={isNewRow || !$focusedRow?._rev}
|
||||
disabled={isNewRow || !$focusedRow?._rev || !$hasBudibaseIdentifiers}
|
||||
on:click={() => copyToClipboard($focusedRow?._rev)}
|
||||
on:click={menu.actions.close}
|
||||
>
|
||||
|
|
|
@ -75,14 +75,18 @@ export const deriveStores = context => {
|
|||
}
|
||||
)
|
||||
|
||||
const isDatasourcePlus = derived(datasource, $datasource => {
|
||||
return ["table", "viewV2"].includes($datasource?.type)
|
||||
const hasBudibaseIdentifiers = derived(datasource, $datasource => {
|
||||
let type = $datasource?.type
|
||||
if (type === "provider") {
|
||||
type = $datasource.value?.datasource?.type
|
||||
}
|
||||
return ["table", "viewV2", "link"].includes(type)
|
||||
})
|
||||
|
||||
return {
|
||||
schema,
|
||||
enrichedSchema,
|
||||
isDatasourcePlus,
|
||||
hasBudibaseIdentifiers,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ export const createActions = context => {
|
|||
|
||||
const open = (cellId, e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
// Get DOM node for grid data wrapper to compute relative position to
|
||||
const gridNode = document.getElementById(gridID)
|
||||
|
|
|
@ -83,7 +83,7 @@ export const createActions = context => {
|
|||
error,
|
||||
notifications,
|
||||
fetch,
|
||||
isDatasourcePlus,
|
||||
hasBudibaseIdentifiers,
|
||||
refreshing,
|
||||
} = context
|
||||
const instanceLoaded = writable(false)
|
||||
|
@ -196,9 +196,16 @@ export const createActions = context => {
|
|||
// Handles validation errors from the rows API and updates local validation
|
||||
// state, storing error messages against relevant cells
|
||||
const handleValidationError = (rowId, error) => {
|
||||
let errorString
|
||||
if (typeof error === "string") {
|
||||
errorString = error
|
||||
} else if (typeof error?.message === "string") {
|
||||
errorString = error.message
|
||||
}
|
||||
|
||||
// If the server doesn't reply with a valid error, assume that the source
|
||||
// of the error is the focused cell's column
|
||||
if (!error?.json?.validationErrors && error?.message) {
|
||||
if (!error?.json?.validationErrors && errorString) {
|
||||
const focusedColumn = get(focusedCellId)?.split("-")[1]
|
||||
if (focusedColumn) {
|
||||
error = {
|
||||
|
@ -261,7 +268,7 @@ export const createActions = context => {
|
|||
focusedCellId.set(`${rowId}-${erroredColumns[0]}`)
|
||||
}
|
||||
} else {
|
||||
get(notifications).error(error?.message || "An unknown error occurred")
|
||||
get(notifications).error(errorString || "An unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,14 +465,14 @@ export const createActions = context => {
|
|||
}
|
||||
let rowsToAppend = []
|
||||
let newRow
|
||||
const $isDatasourcePlus = get(isDatasourcePlus)
|
||||
const $hasBudibaseIdentifiers = get(hasBudibaseIdentifiers)
|
||||
for (let i = 0; i < newRows.length; i++) {
|
||||
newRow = newRows[i]
|
||||
|
||||
// Ensure we have a unique _id.
|
||||
// This means generating one for non DS+, overwriting any that may already
|
||||
// exist as we cannot allow duplicates.
|
||||
if (!$isDatasourcePlus) {
|
||||
if (!$hasBudibaseIdentifiers) {
|
||||
newRow._id = Helpers.uuid()
|
||||
}
|
||||
|
||||
|
@ -510,7 +517,7 @@ export const createActions = context => {
|
|||
const cleanRow = row => {
|
||||
let clone = { ...row }
|
||||
delete clone.__idx
|
||||
if (!get(isDatasourcePlus)) {
|
||||
if (!get(hasBudibaseIdentifiers)) {
|
||||
delete clone._id
|
||||
}
|
||||
return clone
|
||||
|
|
|
@ -52,6 +52,8 @@ import {
|
|||
FetchAppPackageResponse,
|
||||
DuplicateAppRequest,
|
||||
DuplicateAppResponse,
|
||||
UpdateAppRequest,
|
||||
UpdateAppResponse,
|
||||
} from "@budibase/types"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||
import sdk from "../../sdk"
|
||||
|
@ -450,7 +452,7 @@ export async function create(ctx: UserCtx<CreateAppRequest, App>) {
|
|||
// This endpoint currently operates as a PATCH rather than a PUT
|
||||
// Thus name and url fields are handled only if present
|
||||
export async function update(
|
||||
ctx: UserCtx<{ name?: string; url?: string }, App>
|
||||
ctx: UserCtx<UpdateAppRequest, UpdateAppResponse>
|
||||
) {
|
||||
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
|
||||
// validation
|
||||
|
|
|
@ -129,6 +129,7 @@ export async function importToRows(
|
|||
for (let i = 0; i < data.length; i++) {
|
||||
let row = data[i]
|
||||
row._id = generateRowID(table._id!)
|
||||
row.type = "row"
|
||||
row.tableId = table._id
|
||||
|
||||
// We use a reference to table here and update it after input processing,
|
||||
|
|
|
@ -61,9 +61,7 @@ describe.each([
|
|||
}
|
||||
|
||||
async function createRows(rows: Record<string, any>[]) {
|
||||
for (const row of rows) {
|
||||
await config.api.row.save(table._id!, row)
|
||||
}
|
||||
await config.api.row.bulkImport(table._id!, { rows })
|
||||
}
|
||||
|
||||
class SearchAssertion {
|
||||
|
|
|
@ -131,11 +131,6 @@ export async function search(
|
|||
},
|
||||
relationships,
|
||||
}
|
||||
// make sure only rows returned
|
||||
request.filters!.equal = {
|
||||
...request.filters?.equal,
|
||||
type: "row",
|
||||
}
|
||||
|
||||
if (params.sort) {
|
||||
const sortField = table.schema[params.sort]
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
type FetchAppDefinitionResponse,
|
||||
type FetchAppPackageResponse,
|
||||
DuplicateAppResponse,
|
||||
UpdateAppRequest,
|
||||
UpdateAppResponse,
|
||||
} from "@budibase/types"
|
||||
import { Expectations, TestAPI } from "./base"
|
||||
import { AppStatus } from "../../../db/utils"
|
||||
|
@ -109,11 +111,11 @@ export class ApplicationAPI extends TestAPI {
|
|||
|
||||
update = async (
|
||||
appId: string,
|
||||
app: { name?: string; url?: string },
|
||||
app: UpdateAppRequest,
|
||||
expectations?: Expectations
|
||||
): Promise<App> => {
|
||||
): Promise<UpdateAppResponse> => {
|
||||
return await this._put<App>(`/api/applications/${appId}`, {
|
||||
fields: app,
|
||||
body: app,
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -44,3 +44,6 @@ export interface PublishResponse {
|
|||
status: string
|
||||
appUrl: string
|
||||
}
|
||||
|
||||
export interface UpdateAppRequest extends Partial<App> {}
|
||||
export interface UpdateAppResponse extends App {}
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -11441,11 +11441,6 @@ flat@^5.0.2:
|
|||
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
|
||||
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
|
||||
|
||||
flatpickr@^4.5.2:
|
||||
version "4.6.13"
|
||||
resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.13.tgz#8a029548187fd6e0d670908471e43abe9ad18d94"
|
||||
integrity sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==
|
||||
|
||||
flatted@^3.1.0:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
|
||||
|
@ -21082,20 +21077,6 @@ svelte-dnd-action@^0.9.8:
|
|||
postcss "^8.4.29"
|
||||
postcss-scss "^4.0.8"
|
||||
|
||||
svelte-flatpickr@3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.3.tgz#db5dd7ad832ef83262b45e09737955ad3d591fc8"
|
||||
integrity sha512-PNkqK4Napx8nTvCwkaUXdnKo8dISThaxEOK+szTUXcY6H0dQM0TSyuoMaVWY2yX7pM+PN5cpCQCcVe8YvTRFSw==
|
||||
dependencies:
|
||||
flatpickr "^4.5.2"
|
||||
|
||||
svelte-flatpickr@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.3.4.tgz#80b1ed2d6bc37df78b1660404e9326bfc0a206f4"
|
||||
integrity sha512-i+QqJRs8zPRKsxv8r2GIk1fsb8cI3ozn3/aHXtViAoNKLy0j4PV7OSWavgEZC1wlAa34qi2hMkUh+vg6qt2DRA==
|
||||
dependencies:
|
||||
flatpickr "^4.5.2"
|
||||
|
||||
svelte-hmr@^0.15.1:
|
||||
version "0.15.3"
|
||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.3.tgz#df54ccde9be3f091bf5f18fc4ef7b8eb6405fbe6"
|
||||
|
|
Loading…
Reference in a new issue