Merge branch 'master' into new-datepicker

This commit is contained in:
Andrew Kingston 2024-04-15 08:58:17 +01:00 committed by GitHub
commit bc5922e2ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 253 additions and 32 deletions

View File

@ -7,11 +7,11 @@
export let narrower = false export let narrower = false
export let noPadding = false export let noPadding = false
let sidePanelVisble = false let sidePanelVisible = false
setContext("side-panel", { setContext("side-panel", {
open: () => (sidePanelVisble = true), open: () => (sidePanelVisible = true),
close: () => (sidePanelVisble = false), close: () => (sidePanelVisible = false),
}) })
</script> </script>
@ -24,9 +24,9 @@
</div> </div>
<div <div
id="side-panel" id="side-panel"
class:visible={sidePanelVisble} class:visible={sidePanelVisible}
use:clickOutside={() => { use:clickOutside={() => {
sidePanelVisble = false sidePanelVisible = false
}} }}
> >
<slot name="side-panel" /> <slot name="side-panel" />

View File

@ -6723,7 +6723,21 @@
"illegalChildren": ["section", "sidepanel"], "illegalChildren": ["section", "sidepanel"],
"showEmptyState": false, "showEmptyState": false,
"draggable": 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.",
"sendEvents": true,
"settings": [
{
"type": "boolean",
"key": "clickOutsideToClose",
"label": "Click outside to close",
"defaultValue": true
},
{
"type": "event",
"key": "onSidePanelClose",
"label": "On side panel close"
}
]
}, },
"rowexplorer": { "rowexplorer": {
"block": true, "block": true,

View File

@ -73,7 +73,10 @@
$context.device.width, $context.device.width,
$context.device.height $context.device.height
) )
$: autoCloseSidePanel = !$builderStore.inBuilder && $sidePanelStore.open $: autoCloseSidePanel =
!$builderStore.inBuilder &&
$sidePanelStore.open &&
$sidePanelStore.clickOutsideToClose
$: screenId = $builderStore.inBuilder $: screenId = $builderStore.inBuilder
? `${$builderStore.screen?._id}-screen` ? `${$builderStore.screen?._id}-screen`
: "screen" : "screen"

View File

@ -5,6 +5,9 @@
const { styleable, sidePanelStore, builderStore, dndIsDragging } = const { styleable, sidePanelStore, builderStore, dndIsDragging } =
getContext("sdk") getContext("sdk")
export let sidePanelClose
export let clickOutsideToClose
// Automatically show and hide the side panel when inside the builder. // Automatically show and hide the side panel when inside the builder.
// For some unknown reason, svelte reactivity breaks if we reference the // For some unknown reason, svelte reactivity breaks if we reference the
// reactive variable "open" inside the following expression, or if we define // reactive variable "open" inside the following expression, or if we define
@ -26,6 +29,10 @@
} }
} }
$: {
sidePanelStore.actions.setSidepanelState(clickOutsideToClose)
}
// Derive visibility // Derive visibility
$: open = $sidePanelStore.contentId === $component.id $: open = $sidePanelStore.contentId === $component.id
@ -40,6 +47,12 @@
} }
} }
const handleSidePanelClose = async () => {
if (sidePanelClose) {
await sidePanelClose()
}
}
const showInSidePanel = (el, visible) => { const showInSidePanel = (el, visible) => {
const update = visible => { const update = visible => {
const target = document.getElementById("side-panel-container") const target = document.getElementById("side-panel-container")
@ -51,6 +64,7 @@
} else { } else {
if (target.contains(node)) { if (target.contains(node)) {
target.removeChild(node) target.removeChild(node)
handleSidePanelClose()
} }
} }
} }

View File

@ -3,6 +3,7 @@ import { writable, derived } from "svelte/store"
export const createSidePanelStore = () => { export const createSidePanelStore = () => {
const initialState = { const initialState = {
contentId: null, contentId: null,
clickOutsideToClose: true,
} }
const store = writable(initialState) const store = writable(initialState)
const derivedStore = derived(store, $store => { const derivedStore = derived(store, $store => {
@ -32,11 +33,19 @@ export const createSidePanelStore = () => {
}, 50) }, 50)
} }
const setSidepanelState = bool => {
clearTimeout(timeout)
store.update(state => {
state.clickOutsideToClose = bool
return state
})
}
return { return {
subscribe: derivedStore.subscribe, subscribe: derivedStore.subscribe,
actions: { actions: {
open, open,
close, close,
setSidepanelState,
}, },
} }
} }

View File

@ -8,6 +8,8 @@ import {
FieldType, FieldType,
RowSearchParams, RowSearchParams,
SearchFilters, SearchFilters,
SortOrder,
SortType,
Table, Table,
TableSchema, TableSchema,
} from "@budibase/types" } from "@budibase/types"
@ -62,7 +64,32 @@ describe.each([
class SearchAssertion { class SearchAssertion {
constructor(private readonly query: RowSearchParams) {} constructor(private readonly query: RowSearchParams) {}
async toFind(expectedRows: any[]) { // Asserts that the query returns rows matching exactly the set of rows
// passed in. The order of the rows matters. Rows returned in an order
// different to the one passed in will cause the assertion to fail. Extra
// rows returned by the query will also cause the assertion to fail.
async toMatchExactly(expectedRows: any[]) {
const { rows: foundRows } = await config.api.row.search(table._id!, {
...this.query,
tableId: table._id!,
})
// eslint-disable-next-line jest/no-standalone-expect
expect(foundRows).toHaveLength(expectedRows.length)
// eslint-disable-next-line jest/no-standalone-expect
expect(foundRows).toEqual(
expectedRows.map((expectedRow: any) =>
expect.objectContaining(
foundRows.find(foundRow => _.isMatch(foundRow, expectedRow))
)
)
)
}
// Asserts that the query returns rows matching exactly the set of rows
// passed in. The order of the rows is not important, but extra rows will
// cause the assertion to fail.
async toContainExactly(expectedRows: any[]) {
const { rows: foundRows } = await config.api.row.search(table._id!, { const { rows: foundRows } = await config.api.row.search(table._id!, {
...this.query, ...this.query,
tableId: table._id!, tableId: table._id!,
@ -82,8 +109,39 @@ describe.each([
) )
} }
// Asserts that the query returns rows matching the set of rows passed in.
// The order of the rows is not important. Extra rows will not cause the
// assertion to fail.
async toContain(expectedRows: any[]) {
const { rows: foundRows } = await config.api.row.search(table._id!, {
...this.query,
tableId: table._id!,
})
// eslint-disable-next-line jest/no-standalone-expect
expect(foundRows).toEqual(
expect.arrayContaining(
expectedRows.map((expectedRow: any) =>
expect.objectContaining(
foundRows.find(foundRow => _.isMatch(foundRow, expectedRow))
)
)
)
)
}
async toFindNothing() { async toFindNothing() {
await this.toFind([]) await this.toContainExactly([])
}
async toHaveLength(length: number) {
const { rows: foundRows } = await config.api.row.search(table._id!, {
...this.query,
tableId: table._id!,
})
// eslint-disable-next-line jest/no-standalone-expect
expect(foundRows).toHaveLength(length)
} }
} }
@ -105,28 +163,33 @@ describe.each([
describe("misc", () => { describe("misc", () => {
it("should return all if no query is passed", () => it("should return all if no query is passed", () =>
expectSearch({} as RowSearchParams).toFind([ expectSearch({} as RowSearchParams).toContainExactly([
{ name: "foo" }, { name: "foo" },
{ name: "bar" }, { name: "bar" },
])) ]))
it("should return all if empty query is passed", () => it("should return all if empty query is passed", () =>
expectQuery({}).toFind([{ name: "foo" }, { name: "bar" }])) expectQuery({}).toContainExactly([{ name: "foo" }, { name: "bar" }]))
it("should return all if onEmptyFilter is RETURN_ALL", () => it("should return all if onEmptyFilter is RETURN_ALL", () =>
expectQuery({ expectQuery({
onEmptyFilter: EmptyFilterOption.RETURN_ALL, onEmptyFilter: EmptyFilterOption.RETURN_ALL,
}).toFind([{ name: "foo" }, { name: "bar" }])) }).toContainExactly([{ name: "foo" }, { name: "bar" }]))
it("should return nothing if onEmptyFilter is RETURN_NONE", () => it("should return nothing if onEmptyFilter is RETURN_NONE", () =>
expectQuery({ expectQuery({
onEmptyFilter: EmptyFilterOption.RETURN_NONE, onEmptyFilter: EmptyFilterOption.RETURN_NONE,
}).toFindNothing()) }).toFindNothing())
it("should respect limit", () =>
expectSearch({ limit: 1, paginate: true, query: {} }).toHaveLength(1))
}) })
describe("equal", () => { describe("equal", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ equal: { name: "foo" } }).toFind([{ name: "foo" }])) expectQuery({ equal: { name: "foo" } }).toContainExactly([
{ name: "foo" },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ equal: { name: "none" } }).toFindNothing()) expectQuery({ equal: { name: "none" } }).toFindNothing())
@ -134,15 +197,21 @@ describe.each([
describe("notEqual", () => { describe("notEqual", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ notEqual: { name: "foo" } }).toFind([{ name: "bar" }])) expectQuery({ notEqual: { name: "foo" } }).toContainExactly([
{ name: "bar" },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ notEqual: { name: "bar" } }).toFind([{ name: "foo" }])) expectQuery({ notEqual: { name: "bar" } }).toContainExactly([
{ name: "foo" },
]))
}) })
describe("oneOf", () => { describe("oneOf", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ oneOf: { name: ["foo"] } }).toFind([{ name: "foo" }])) expectQuery({ oneOf: { name: ["foo"] } }).toContainExactly([
{ name: "foo" },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ oneOf: { name: ["none"] } }).toFindNothing()) expectQuery({ oneOf: { name: ["none"] } }).toFindNothing())
@ -150,11 +219,47 @@ describe.each([
describe("fuzzy", () => { describe("fuzzy", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ fuzzy: { name: "oo" } }).toFind([{ name: "foo" }])) expectQuery({ fuzzy: { name: "oo" } }).toContainExactly([
{ name: "foo" },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ fuzzy: { name: "none" } }).toFindNothing()) expectQuery({ fuzzy: { name: "none" } }).toFindNothing())
}) })
describe("sort", () => {
it("sorts ascending", () =>
expectSearch({
query: {},
sort: "name",
sortOrder: SortOrder.ASCENDING,
}).toMatchExactly([{ name: "bar" }, { name: "foo" }]))
it("sorts descending", () =>
expectSearch({
query: {},
sort: "name",
sortOrder: SortOrder.DESCENDING,
}).toMatchExactly([{ name: "foo" }, { name: "bar" }]))
describe("sortType STRING", () => {
it("sorts ascending", () =>
expectSearch({
query: {},
sort: "name",
sortType: SortType.STRING,
sortOrder: SortOrder.ASCENDING,
}).toMatchExactly([{ name: "bar" }, { name: "foo" }]))
it("sorts descending", () =>
expectSearch({
query: {},
sort: "name",
sortType: SortType.STRING,
sortOrder: SortOrder.DESCENDING,
}).toMatchExactly([{ name: "foo" }, { name: "bar" }]))
})
})
}) })
describe("numbers", () => { describe("numbers", () => {
@ -167,7 +272,7 @@ describe.each([
describe("equal", () => { describe("equal", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ equal: { age: 1 } }).toFind([{ age: 1 }])) expectQuery({ equal: { age: 1 } }).toContainExactly([{ age: 1 }]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ equal: { age: 2 } }).toFindNothing()) expectQuery({ equal: { age: 2 } }).toFindNothing())
@ -175,15 +280,15 @@ describe.each([
describe("notEqual", () => { describe("notEqual", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ notEqual: { age: 1 } }).toFind([{ age: 10 }])) expectQuery({ notEqual: { age: 1 } }).toContainExactly([{ age: 10 }]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ notEqual: { age: 10 } }).toFind([{ age: 1 }])) expectQuery({ notEqual: { age: 10 } }).toContainExactly([{ age: 1 }]))
}) })
describe("oneOf", () => { describe("oneOf", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ oneOf: { age: [1] } }).toFind([{ age: 1 }])) expectQuery({ oneOf: { age: [1] } }).toContainExactly([{ age: 1 }]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ oneOf: { age: [2] } }).toFindNothing()) expectQuery({ oneOf: { age: [2] } }).toFindNothing())
@ -193,17 +298,51 @@ describe.each([
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ expectQuery({
range: { age: { low: 1, high: 5 } }, range: { age: { low: 1, high: 5 } },
}).toFind([{ age: 1 }])) }).toContainExactly([{ age: 1 }]))
it("successfully finds multiple rows", () => it("successfully finds multiple rows", () =>
expectQuery({ expectQuery({
range: { age: { low: 1, high: 10 } }, range: { age: { low: 1, high: 10 } },
}).toFind([{ age: 1 }, { age: 10 }])) }).toContainExactly([{ age: 1 }, { age: 10 }]))
it("successfully finds a row with a high bound", () => it("successfully finds a row with a high bound", () =>
expectQuery({ expectQuery({
range: { age: { low: 5, high: 10 } }, range: { age: { low: 5, high: 10 } },
}).toFind([{ age: 10 }])) }).toContainExactly([{ age: 10 }]))
})
describe("sort", () => {
it("sorts ascending", () =>
expectSearch({
query: {},
sort: "age",
sortOrder: SortOrder.ASCENDING,
}).toMatchExactly([{ age: 1 }, { age: 10 }]))
it("sorts descending", () =>
expectSearch({
query: {},
sort: "age",
sortOrder: SortOrder.DESCENDING,
}).toMatchExactly([{ age: 10 }, { age: 1 }]))
})
describe("sortType NUMBER", () => {
it("sorts ascending", () =>
expectSearch({
query: {},
sort: "age",
sortType: SortType.NUMBER,
sortOrder: SortOrder.ASCENDING,
}).toMatchExactly([{ age: 1 }, { age: 10 }]))
it("sorts descending", () =>
expectSearch({
query: {},
sort: "age",
sortType: SortType.NUMBER,
sortOrder: SortOrder.DESCENDING,
}).toMatchExactly([{ age: 10 }, { age: 1 }]))
}) })
}) })
@ -223,7 +362,9 @@ describe.each([
describe("equal", () => { describe("equal", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ equal: { dob: JAN_1ST } }).toFind([{ dob: JAN_1ST }])) expectQuery({ equal: { dob: JAN_1ST } }).toContainExactly([
{ dob: JAN_1ST },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing()) expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing())
@ -231,15 +372,21 @@ describe.each([
describe("notEqual", () => { describe("notEqual", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ notEqual: { dob: JAN_1ST } }).toFind([{ dob: JAN_10TH }])) expectQuery({ notEqual: { dob: JAN_1ST } }).toContainExactly([
{ dob: JAN_10TH },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ notEqual: { dob: JAN_10TH } }).toFind([{ dob: JAN_1ST }])) expectQuery({ notEqual: { dob: JAN_10TH } }).toContainExactly([
{ dob: JAN_1ST },
]))
}) })
describe("oneOf", () => { describe("oneOf", () => {
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ oneOf: { dob: [JAN_1ST] } }).toFind([{ dob: JAN_1ST }])) expectQuery({ oneOf: { dob: [JAN_1ST] } }).toContainExactly([
{ dob: JAN_1ST },
]))
it("fails to find nonexistent row", () => it("fails to find nonexistent row", () =>
expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing()) expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing())
@ -249,17 +396,51 @@ describe.each([
it("successfully finds a row", () => it("successfully finds a row", () =>
expectQuery({ expectQuery({
range: { dob: { low: JAN_1ST, high: JAN_5TH } }, range: { dob: { low: JAN_1ST, high: JAN_5TH } },
}).toFind([{ dob: JAN_1ST }])) }).toContainExactly([{ dob: JAN_1ST }]))
it("successfully finds multiple rows", () => it("successfully finds multiple rows", () =>
expectQuery({ expectQuery({
range: { dob: { low: JAN_1ST, high: JAN_10TH } }, range: { dob: { low: JAN_1ST, high: JAN_10TH } },
}).toFind([{ dob: JAN_1ST }, { dob: JAN_10TH }])) }).toContainExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
it("successfully finds a row with a high bound", () => it("successfully finds a row with a high bound", () =>
expectQuery({ expectQuery({
range: { dob: { low: JAN_5TH, high: JAN_10TH } }, range: { dob: { low: JAN_5TH, high: JAN_10TH } },
}).toFind([{ dob: JAN_10TH }])) }).toContainExactly([{ dob: JAN_10TH }]))
})
describe("sort", () => {
it("sorts ascending", () =>
expectSearch({
query: {},
sort: "dob",
sortOrder: SortOrder.ASCENDING,
}).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
it("sorts descending", () =>
expectSearch({
query: {},
sort: "dob",
sortOrder: SortOrder.DESCENDING,
}).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }]))
describe("sortType STRING", () => {
it("sorts ascending", () =>
expectSearch({
query: {},
sort: "dob",
sortType: SortType.STRING,
sortOrder: SortOrder.ASCENDING,
}).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
it("sorts descending", () =>
expectSearch({
query: {},
sort: "dob",
sortType: SortType.STRING,
sortOrder: SortOrder.DESCENDING,
}).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }]))
})
}) })
}) })
}) })

View File

@ -132,7 +132,7 @@ export async function search(
type: "row", type: "row",
} }
if (params.sort && !params.sortType) { if (params.sort) {
const sortField = table.schema[params.sort] const sortField = table.schema[params.sort]
const sortType = const sortType =
sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING