Merge pull request #12049 from Budibase/grid-inline-searching
Inline searching for grid and grid block
This commit is contained in:
commit
91d6130f7b
|
@ -4,6 +4,8 @@
|
||||||
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
||||||
import GridCell from "./GridCell.svelte"
|
import GridCell from "./GridCell.svelte"
|
||||||
import { getColumnIcon } from "../lib/utils"
|
import { getColumnIcon } from "../lib/utils"
|
||||||
|
import { debounce } from "../../../utils/utils"
|
||||||
|
import { FieldType, FormulaTypes } from "@budibase/types"
|
||||||
|
|
||||||
export let column
|
export let column
|
||||||
export let idx
|
export let idx
|
||||||
|
@ -24,23 +26,69 @@
|
||||||
definition,
|
definition,
|
||||||
datasource,
|
datasource,
|
||||||
schema,
|
schema,
|
||||||
|
focusedCellId,
|
||||||
|
filter,
|
||||||
|
inlineFilters,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
|
const searchableTypes = [
|
||||||
|
FieldType.STRING,
|
||||||
|
FieldType.OPTIONS,
|
||||||
|
FieldType.NUMBER,
|
||||||
|
FieldType.BIGINT,
|
||||||
|
FieldType.ARRAY,
|
||||||
|
FieldType.LONGFORM,
|
||||||
|
]
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let open = false
|
let open = false
|
||||||
let editIsOpen = false
|
let editIsOpen = false
|
||||||
let timeout
|
let timeout
|
||||||
let popover
|
let popover
|
||||||
|
let searchValue
|
||||||
|
let input
|
||||||
|
|
||||||
$: sortedBy = column.name === $sort.column
|
$: sortedBy = column.name === $sort.column
|
||||||
$: canMoveLeft = orderable && idx > 0
|
$: canMoveLeft = orderable && idx > 0
|
||||||
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
||||||
$: ascendingLabel = ["number", "bigint"].includes(column.schema?.type)
|
$: sortingLabels = getSortingLabels(column.schema?.type)
|
||||||
? "low-high"
|
$: searchable = isColumnSearchable(column)
|
||||||
: "A-Z"
|
$: resetSearchValue(column.name)
|
||||||
$: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
|
$: searching = searchValue != null
|
||||||
? "high-low"
|
$: debouncedUpdateFilter(searchValue)
|
||||||
: "Z-A"
|
|
||||||
|
const getSortingLabels = type => {
|
||||||
|
switch (type) {
|
||||||
|
case FieldType.NUMBER:
|
||||||
|
case FieldType.BIGINT:
|
||||||
|
return {
|
||||||
|
ascending: "low-high",
|
||||||
|
descending: "high-low",
|
||||||
|
}
|
||||||
|
case FieldType.DATETIME:
|
||||||
|
return {
|
||||||
|
ascending: "old-new",
|
||||||
|
descending: "new-old",
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
ascending: "A-Z",
|
||||||
|
descending: "Z-A",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetSearchValue = name => {
|
||||||
|
searchValue = $inlineFilters?.find(x => x.id === `inline-${name}`)?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const isColumnSearchable = col => {
|
||||||
|
const { type, formulaType } = col.schema
|
||||||
|
return (
|
||||||
|
searchableTypes.includes(type) ||
|
||||||
|
(type === FieldType.FORMULA && formulaType === FormulaTypes.STATIC)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const editColumn = async () => {
|
const editColumn = async () => {
|
||||||
editIsOpen = true
|
editIsOpen = true
|
||||||
|
@ -141,12 +189,46 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startSearching = async () => {
|
||||||
|
$focusedCellId = null
|
||||||
|
searchValue = ""
|
||||||
|
await tick()
|
||||||
|
input?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInputKeyDown = e => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
updateFilter()
|
||||||
|
} else if (e.key === "Escape") {
|
||||||
|
input?.blur()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopSearching = () => {
|
||||||
|
searchValue = null
|
||||||
|
updateFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlurInput = () => {
|
||||||
|
if (searchValue === "") {
|
||||||
|
searchValue = null
|
||||||
|
}
|
||||||
|
updateFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFilter = () => {
|
||||||
|
filter.actions.addInlineFilter(column, searchValue)
|
||||||
|
}
|
||||||
|
const debouncedUpdateFilter = debounce(updateFilter, 250)
|
||||||
|
|
||||||
onMount(() => subscribe("close-edit-column", cancelEdit))
|
onMount(() => subscribe("close-edit-column", cancelEdit))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="header-cell"
|
class="header-cell"
|
||||||
class:open
|
class:open
|
||||||
|
class:searchable
|
||||||
|
class:searching
|
||||||
style="flex: 0 0 {column.width}px;"
|
style="flex: 0 0 {column.width}px;"
|
||||||
bind:this={anchor}
|
bind:this={anchor}
|
||||||
class:disabled={$isReordering || $isResizing}
|
class:disabled={$isReordering || $isResizing}
|
||||||
|
@ -161,30 +243,49 @@
|
||||||
defaultHeight
|
defaultHeight
|
||||||
center
|
center
|
||||||
>
|
>
|
||||||
<Icon
|
{#if searching}
|
||||||
size="S"
|
<input
|
||||||
name={getColumnIcon(column)}
|
bind:this={input}
|
||||||
color={`var(--spectrum-global-color-gray-600)`}
|
type="text"
|
||||||
/>
|
bind:value={searchValue}
|
||||||
|
on:blur={onBlurInput}
|
||||||
|
on:click={() => focusedCellId.set(null)}
|
||||||
|
on:keydown={onInputKeyDown}
|
||||||
|
data-grid-ignore
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="column-icon">
|
||||||
|
<Icon size="S" name={getColumnIcon(column)} />
|
||||||
|
</div>
|
||||||
|
<div class="search-icon" on:click={startSearching}>
|
||||||
|
<Icon hoverable size="S" name="Search" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="name">
|
<div class="name">
|
||||||
{column.label}
|
{column.label}
|
||||||
</div>
|
</div>
|
||||||
{#if sortedBy}
|
|
||||||
<div class="sort-indicator">
|
{#if searching}
|
||||||
<Icon
|
<div class="clear-icon" on:click={stopSearching}>
|
||||||
size="S"
|
<Icon hoverable size="S" name="Close" />
|
||||||
name={$sort.order === "descending" ? "SortOrderDown" : "SortOrderUp"}
|
</div>
|
||||||
color="var(--spectrum-global-color-gray-600)"
|
{:else}
|
||||||
/>
|
{#if sortedBy}
|
||||||
|
<div class="sort-indicator">
|
||||||
|
<Icon
|
||||||
|
hoverable
|
||||||
|
size="S"
|
||||||
|
name={$sort.order === "descending"
|
||||||
|
? "SortOrderDown"
|
||||||
|
: "SortOrderUp"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="more-icon" on:click={() => (open = true)}>
|
||||||
|
<Icon hoverable size="S" name="MoreVertical" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="more" on:click={() => (open = true)}>
|
|
||||||
<Icon
|
|
||||||
size="S"
|
|
||||||
name="MoreVertical"
|
|
||||||
color="var(--spectrum-global-color-gray-600)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</GridCell>
|
</GridCell>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -235,7 +336,7 @@
|
||||||
disabled={!canBeSortColumn(column.schema.type) ||
|
disabled={!canBeSortColumn(column.schema.type) ||
|
||||||
(column.name === $sort.column && $sort.order === "ascending")}
|
(column.name === $sort.column && $sort.order === "ascending")}
|
||||||
>
|
>
|
||||||
Sort {ascendingLabel}
|
Sort {sortingLabels.ascending}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="SortOrderDown"
|
icon="SortOrderDown"
|
||||||
|
@ -243,7 +344,7 @@
|
||||||
disabled={!canBeSortColumn(column.schema.type) ||
|
disabled={!canBeSortColumn(column.schema.type) ||
|
||||||
(column.name === $sort.column && $sort.order === "descending")}
|
(column.name === $sort.column && $sort.order === "descending")}
|
||||||
>
|
>
|
||||||
Sort {descendingLabel}
|
Sort {sortingLabels.descending}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
||||||
Move left
|
Move left
|
||||||
|
@ -283,6 +384,29 @@
|
||||||
background: var(--grid-background-alt);
|
background: var(--grid-background-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Icon colors */
|
||||||
|
.header-cell :global(.spectrum-Icon) {
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
}
|
||||||
|
.header-cell :global(.spectrum-Icon.hoverable:hover) {
|
||||||
|
color: var(--spectrum-global-color-gray-800) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search icon */
|
||||||
|
.search-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.header-cell.searchable:not(.open):hover .search-icon,
|
||||||
|
.header-cell.searchable.searching .search-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.header-cell.searchable:not(.open):hover .column-icon,
|
||||||
|
.header-cell.searchable.searching .column-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main center content */
|
||||||
.name {
|
.name {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -290,23 +414,45 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.header-cell.searching .name {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
display: none;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
outline: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--spectrum-global-color-gray-800);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 30px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
border: 1px solid var(--accent-color);
|
||||||
|
}
|
||||||
|
input:not(:focus) {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
.header-cell.searching input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.more {
|
/* Right icons */
|
||||||
|
.more-icon {
|
||||||
display: none;
|
display: none;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin: 0 -4px;
|
margin: 0 -4px;
|
||||||
}
|
}
|
||||||
.header-cell.open .more,
|
.header-cell.open .more-icon,
|
||||||
.header-cell:hover .more {
|
.header-cell:hover .more-icon {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.more:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.more:hover :global(.spectrum-Icon) {
|
|
||||||
color: var(--spectrum-global-color-gray-800) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-cell.open .sort-indicator,
|
.header-cell.open .sort-indicator,
|
||||||
.header-cell:hover .sort-indicator {
|
.header-cell:hover .sort-indicator {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -27,8 +27,10 @@
|
||||||
rowVerticalInversionIndex,
|
rowVerticalInversionIndex,
|
||||||
columnHorizontalInversionIndex,
|
columnHorizontalInversionIndex,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
loading,
|
loaded,
|
||||||
|
refreshing,
|
||||||
config,
|
config,
|
||||||
|
filter,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
let visible = false
|
let visible = false
|
||||||
|
@ -153,7 +155,7 @@
|
||||||
<!-- New row FAB -->
|
<!-- New row FAB -->
|
||||||
<TempTooltip
|
<TempTooltip
|
||||||
text="Click here to create your first row"
|
text="Click here to create your first row"
|
||||||
condition={hasNoRows && !$loading}
|
condition={hasNoRows && $loaded && !$filter?.length && !$refreshing}
|
||||||
type={TooltipType.Info}
|
type={TooltipType.Info}
|
||||||
>
|
>
|
||||||
{#if !visible && !selectedRowCount && $config.canAddRows}
|
{#if !visible && !selectedRowCount && $config.canAddRows}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
const ignoredOriginSelectors = [
|
const ignoredOriginSelectors = [
|
||||||
".spectrum-Modal",
|
".spectrum-Modal",
|
||||||
"#builder-side-panel-container",
|
"#builder-side-panel-container",
|
||||||
|
"[data-grid-ignore]",
|
||||||
]
|
]
|
||||||
|
|
||||||
// Global key listener which intercepts all key events
|
// Global key listener which intercepts all key events
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { derived, get, writable } from "svelte/store"
|
import { derived, get } from "svelte/store"
|
||||||
import { getDatasourceDefinition } from "../../../fetch"
|
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
||||||
|
import { memo } from "../../../utils"
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const definition = writable(null)
|
const definition = memo(null)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
definition,
|
definition,
|
||||||
|
@ -10,10 +11,15 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { definition, schemaOverrides, columnWhitelist, datasource } = context
|
const { API, definition, schemaOverrides, columnWhitelist, datasource } =
|
||||||
|
context
|
||||||
|
|
||||||
const schema = derived(definition, $definition => {
|
const schema = derived(definition, $definition => {
|
||||||
let schema = $definition?.schema
|
let schema = getDatasourceSchema({
|
||||||
|
API,
|
||||||
|
datasource: get(datasource),
|
||||||
|
definition: $definition,
|
||||||
|
})
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ export const initialise = context => {
|
||||||
datasource,
|
datasource,
|
||||||
sort,
|
sort,
|
||||||
filter,
|
filter,
|
||||||
|
inlineFilters,
|
||||||
|
allFilters,
|
||||||
nonPlus,
|
nonPlus,
|
||||||
initialFilter,
|
initialFilter,
|
||||||
initialSortColumn,
|
initialSortColumn,
|
||||||
|
@ -87,6 +89,7 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Wipe state
|
// Wipe state
|
||||||
filter.set(get(initialFilter))
|
filter.set(get(initialFilter))
|
||||||
|
inlineFilters.set([])
|
||||||
sort.set({
|
sort.set({
|
||||||
column: get(initialSortColumn),
|
column: get(initialSortColumn),
|
||||||
order: get(initialSortOrder) || "ascending",
|
order: get(initialSortOrder) || "ascending",
|
||||||
|
@ -94,14 +97,14 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Update fetch when filter changes
|
// Update fetch when filter changes
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
filter.subscribe($filter => {
|
allFilters.subscribe($allFilters => {
|
||||||
// Ensure we're updating the correct fetch
|
// Ensure we're updating the correct fetch
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch)
|
||||||
if (!isSameDatasource($fetch?.options?.datasource, $datasource)) {
|
if (!isSameDatasource($fetch?.options?.datasource, $datasource)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$fetch.update({
|
$fetch.update({
|
||||||
filter: $filter,
|
filter: $allFilters,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -71,6 +71,8 @@ export const initialise = context => {
|
||||||
datasource,
|
datasource,
|
||||||
fetch,
|
fetch,
|
||||||
filter,
|
filter,
|
||||||
|
inlineFilters,
|
||||||
|
allFilters,
|
||||||
sort,
|
sort,
|
||||||
table,
|
table,
|
||||||
initialFilter,
|
initialFilter,
|
||||||
|
@ -93,6 +95,7 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Wipe state
|
// Wipe state
|
||||||
filter.set(get(initialFilter))
|
filter.set(get(initialFilter))
|
||||||
|
inlineFilters.set([])
|
||||||
sort.set({
|
sort.set({
|
||||||
column: get(initialSortColumn),
|
column: get(initialSortColumn),
|
||||||
order: get(initialSortOrder) || "ascending",
|
order: get(initialSortOrder) || "ascending",
|
||||||
|
@ -100,14 +103,14 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Update fetch when filter changes
|
// Update fetch when filter changes
|
||||||
unsubscribers.push(
|
unsubscribers.push(
|
||||||
filter.subscribe($filter => {
|
allFilters.subscribe($allFilters => {
|
||||||
// Ensure we're updating the correct fetch
|
// Ensure we're updating the correct fetch
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch)
|
||||||
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$fetch.update({
|
$fetch.update({
|
||||||
filter: $filter,
|
filter: $allFilters,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -73,6 +73,8 @@ export const initialise = context => {
|
||||||
sort,
|
sort,
|
||||||
rows,
|
rows,
|
||||||
filter,
|
filter,
|
||||||
|
inlineFilters,
|
||||||
|
allFilters,
|
||||||
subscribe,
|
subscribe,
|
||||||
viewV2,
|
viewV2,
|
||||||
initialFilter,
|
initialFilter,
|
||||||
|
@ -97,6 +99,7 @@ export const initialise = context => {
|
||||||
|
|
||||||
// Reset state for new view
|
// Reset state for new view
|
||||||
filter.set(get(initialFilter))
|
filter.set(get(initialFilter))
|
||||||
|
inlineFilters.set([])
|
||||||
sort.set({
|
sort.set({
|
||||||
column: get(initialSortColumn),
|
column: get(initialSortColumn),
|
||||||
order: get(initialSortOrder) || "ascending",
|
order: get(initialSortOrder) || "ascending",
|
||||||
|
@ -143,21 +146,19 @@ export const initialise = context => {
|
||||||
order: $sort.order || "ascending",
|
order: $sort.order || "ascending",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await rows.actions.refreshData()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise just update the fetch
|
|
||||||
else {
|
// Also update the fetch to ensure the new sort is respected.
|
||||||
// Ensure we're updating the correct fetch
|
// Ensure we're updating the correct fetch.
|
||||||
const $fetch = get(fetch)
|
const $fetch = get(fetch)
|
||||||
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
$fetch.update({
|
|
||||||
sortOrder: $sort.order || "ascending",
|
|
||||||
sortColumn: $sort.column,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
$fetch.update({
|
||||||
|
sortOrder: $sort.order,
|
||||||
|
sortColumn: $sort.column,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -176,20 +177,25 @@ export const initialise = context => {
|
||||||
...$view,
|
...$view,
|
||||||
query: $filter,
|
query: $filter,
|
||||||
})
|
})
|
||||||
await rows.actions.refreshData()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise just update the fetch
|
})
|
||||||
else {
|
)
|
||||||
// Ensure we're updating the correct fetch
|
|
||||||
const $fetch = get(fetch)
|
// Keep fetch up to date with filters.
|
||||||
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
// If we're able to save filters against the view then we only need to apply
|
||||||
return
|
// inline filters to the fetch, as saved filters are applied server side.
|
||||||
}
|
// If we can't save filters, then all filters must be applied to the fetch.
|
||||||
$fetch.update({
|
unsubscribers.push(
|
||||||
filter: $filter,
|
allFilters.subscribe($allFilters => {
|
||||||
})
|
// Ensure we're updating the correct fetch
|
||||||
|
const $fetch = get(fetch)
|
||||||
|
if ($fetch?.options?.datasource?.tableId !== $datasource.tableId) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
$fetch.update({
|
||||||
|
filter: $allFilters,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,79 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get, derived } from "svelte/store"
|
||||||
|
import { FieldType } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = context => {
|
export const createStores = context => {
|
||||||
const { props } = context
|
const { props } = context
|
||||||
|
|
||||||
// Initialise to default props
|
// Initialise to default props
|
||||||
const filter = writable(get(props).initialFilter)
|
const filter = writable(get(props).initialFilter)
|
||||||
|
const inlineFilters = writable([])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filter,
|
filter,
|
||||||
|
inlineFilters,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deriveStores = context => {
|
||||||
|
const { filter, inlineFilters } = context
|
||||||
|
|
||||||
|
const allFilters = derived(
|
||||||
|
[filter, inlineFilters],
|
||||||
|
([$filter, $inlineFilters]) => {
|
||||||
|
return [...($filter || []), ...$inlineFilters]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
allFilters,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createActions = context => {
|
||||||
|
const { filter, inlineFilters } = context
|
||||||
|
|
||||||
|
const addInlineFilter = (column, value) => {
|
||||||
|
const filterId = `inline-${column.name}`
|
||||||
|
const type = column.schema.type
|
||||||
|
let inlineFilter = {
|
||||||
|
field: column.name,
|
||||||
|
id: filterId,
|
||||||
|
operator: "string",
|
||||||
|
valueType: "value",
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add overrides specific so the certain column type
|
||||||
|
if (type === FieldType.NUMBER) {
|
||||||
|
inlineFilter.value = parseFloat(value)
|
||||||
|
inlineFilter.operator = "equal"
|
||||||
|
} else if (type === FieldType.BIGINT) {
|
||||||
|
inlineFilter.operator = "equal"
|
||||||
|
} else if (type === FieldType.ARRAY) {
|
||||||
|
inlineFilter.operator = "contains"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this filter
|
||||||
|
inlineFilters.update($inlineFilters => {
|
||||||
|
// Remove any existing inline filter for this column
|
||||||
|
$inlineFilters = $inlineFilters?.filter(x => x.id !== filterId)
|
||||||
|
|
||||||
|
// Add new one if a value exists
|
||||||
|
if (value) {
|
||||||
|
$inlineFilters.push(inlineFilter)
|
||||||
|
}
|
||||||
|
return $inlineFilters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter: {
|
||||||
|
...filter,
|
||||||
|
actions: {
|
||||||
|
addInlineFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const createStores = () => {
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
const loading = writable(false)
|
const loading = writable(false)
|
||||||
const loaded = writable(false)
|
const loaded = writable(false)
|
||||||
|
const refreshing = writable(false)
|
||||||
const rowChangeCache = writable({})
|
const rowChangeCache = writable({})
|
||||||
const inProgressChanges = writable({})
|
const inProgressChanges = writable({})
|
||||||
const hasNextPage = writable(false)
|
const hasNextPage = writable(false)
|
||||||
|
@ -53,6 +54,7 @@ export const createStores = () => {
|
||||||
fetch,
|
fetch,
|
||||||
rowLookupMap,
|
rowLookupMap,
|
||||||
loaded,
|
loaded,
|
||||||
|
refreshing,
|
||||||
loading,
|
loading,
|
||||||
rowChangeCache,
|
rowChangeCache,
|
||||||
inProgressChanges,
|
inProgressChanges,
|
||||||
|
@ -66,7 +68,7 @@ export const createActions = context => {
|
||||||
rows,
|
rows,
|
||||||
rowLookupMap,
|
rowLookupMap,
|
||||||
definition,
|
definition,
|
||||||
filter,
|
allFilters,
|
||||||
loading,
|
loading,
|
||||||
sort,
|
sort,
|
||||||
datasource,
|
datasource,
|
||||||
|
@ -82,6 +84,7 @@ export const createActions = context => {
|
||||||
notifications,
|
notifications,
|
||||||
fetch,
|
fetch,
|
||||||
isDatasourcePlus,
|
isDatasourcePlus,
|
||||||
|
refreshing,
|
||||||
} = context
|
} = context
|
||||||
const instanceLoaded = writable(false)
|
const instanceLoaded = writable(false)
|
||||||
|
|
||||||
|
@ -108,7 +111,7 @@ export const createActions = context => {
|
||||||
// Tick to allow other reactive logic to update stores when datasource changes
|
// Tick to allow other reactive logic to update stores when datasource changes
|
||||||
// before proceeding. This allows us to wipe filters etc if needed.
|
// before proceeding. This allows us to wipe filters etc if needed.
|
||||||
await tick()
|
await tick()
|
||||||
const $filter = get(filter)
|
const $allFilters = get(allFilters)
|
||||||
const $sort = get(sort)
|
const $sort = get(sort)
|
||||||
|
|
||||||
// Determine how many rows to fetch per page
|
// Determine how many rows to fetch per page
|
||||||
|
@ -120,7 +123,7 @@ export const createActions = context => {
|
||||||
API,
|
API,
|
||||||
datasource: $datasource,
|
datasource: $datasource,
|
||||||
options: {
|
options: {
|
||||||
filter: $filter,
|
filter: $allFilters,
|
||||||
sortColumn: $sort.column,
|
sortColumn: $sort.column,
|
||||||
sortOrder: $sort.order,
|
sortOrder: $sort.order,
|
||||||
limit,
|
limit,
|
||||||
|
@ -176,6 +179,9 @@ export const createActions = context => {
|
||||||
// Notify that we're loaded
|
// Notify that we're loaded
|
||||||
loading.set(false)
|
loading.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update refreshing state
|
||||||
|
refreshing.set($fetch.loading)
|
||||||
})
|
})
|
||||||
|
|
||||||
fetch.set(newFetch)
|
fetch.set(newFetch)
|
||||||
|
|
|
@ -35,9 +35,28 @@ export default class ViewV2Fetch extends DataFetch {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData() {
|
async getData() {
|
||||||
const { datasource, limit, sortColumn, sortOrder, sortType, paginate } =
|
const {
|
||||||
this.options
|
datasource,
|
||||||
const { cursor, query } = get(this.store)
|
limit,
|
||||||
|
sortColumn,
|
||||||
|
sortOrder,
|
||||||
|
sortType,
|
||||||
|
paginate,
|
||||||
|
filter,
|
||||||
|
} = this.options
|
||||||
|
const { cursor, query, definition } = get(this.store)
|
||||||
|
|
||||||
|
// If sort/filter params are not defined, update options to store the
|
||||||
|
// params built in to this view. This ensures that we can accurately
|
||||||
|
// compare old and new params and skip a redundant API call.
|
||||||
|
if (!sortColumn && definition.sort?.field) {
|
||||||
|
this.options.sortColumn = definition.sort.field
|
||||||
|
this.options.sortOrder = definition.sort.order
|
||||||
|
}
|
||||||
|
if (!filter?.length && definition.query?.length) {
|
||||||
|
this.options.filter = definition.query
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await this.API.viewV2.fetch({
|
const res = await this.API.viewV2.fetch({
|
||||||
viewId: datasource.id,
|
viewId: datasource.id,
|
||||||
|
|
|
@ -32,12 +32,24 @@ export const fetchData = ({ API, datasource, options }) => {
|
||||||
return new Fetch({ API, datasource, ...options })
|
return new Fetch({ API, datasource, ...options })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the definition of any type of datasource
|
// Creates an empty fetch instance with no datasource configured, so no data
|
||||||
export const getDatasourceDefinition = async ({ API, datasource }) => {
|
// will initially be loaded
|
||||||
|
const createEmptyFetchInstance = ({ API, datasource }) => {
|
||||||
const handler = DataFetchMap[datasource?.type]
|
const handler = DataFetchMap[datasource?.type]
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const instance = new handler({ API })
|
return new handler({ API })
|
||||||
return await instance.getDefinition(datasource)
|
}
|
||||||
|
|
||||||
|
// Fetches the definition of any type of datasource
|
||||||
|
export const getDatasourceDefinition = async ({ API, datasource }) => {
|
||||||
|
const instance = createEmptyFetchInstance({ API, datasource })
|
||||||
|
return await instance?.getDefinition(datasource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches the schema of any type of datasource
|
||||||
|
export const getDatasourceSchema = ({ API, datasource, definition }) => {
|
||||||
|
const instance = createEmptyFetchInstance({ API, datasource })
|
||||||
|
return instance?.getSchema(datasource, definition)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue