Add basic inline searching and fix create first row popup
This commit is contained in:
parent
c610f2123f
commit
789bb528f4
|
@ -3,6 +3,7 @@
|
||||||
import GridCell from "./GridCell.svelte"
|
import GridCell from "./GridCell.svelte"
|
||||||
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
||||||
import { getColumnIcon } from "../lib/utils"
|
import { getColumnIcon } from "../lib/utils"
|
||||||
|
import { debounce } from "../../../utils/utils"
|
||||||
|
|
||||||
export let column
|
export let column
|
||||||
export let idx
|
export let idx
|
||||||
|
@ -23,6 +24,8 @@
|
||||||
definition,
|
definition,
|
||||||
datasource,
|
datasource,
|
||||||
schema,
|
schema,
|
||||||
|
focusedCellId,
|
||||||
|
filter,
|
||||||
} = getContext("grid")
|
} = getContext("grid")
|
||||||
|
|
||||||
const bannedDisplayColumnTypes = [
|
const bannedDisplayColumnTypes = [
|
||||||
|
@ -32,12 +35,15 @@
|
||||||
"boolean",
|
"boolean",
|
||||||
"json",
|
"json",
|
||||||
]
|
]
|
||||||
|
const searchableTypes = ["string", "options", "number"]
|
||||||
|
|
||||||
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
|
||||||
|
@ -48,6 +54,9 @@
|
||||||
$: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
|
$: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
|
||||||
? "high-low"
|
? "high-low"
|
||||||
: "Z-A"
|
: "Z-A"
|
||||||
|
$: searchable = searchableTypes.includes(column.schema.type)
|
||||||
|
$: searching = searchValue != null
|
||||||
|
$: debouncedUpdateFilter(searchValue)
|
||||||
|
|
||||||
const editColumn = async () => {
|
const editColumn = async () => {
|
||||||
editIsOpen = true
|
editIsOpen = true
|
||||||
|
@ -148,12 +157,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}
|
||||||
|
@ -168,30 +211,48 @@
|
||||||
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}
|
||||||
|
/>
|
||||||
|
{/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>
|
||||||
|
|
||||||
|
@ -289,6 +350,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;
|
||||||
|
@ -296,23 +380,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(--ink);
|
||||||
|
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}
|
||||||
|
|
|
@ -11,6 +11,46 @@ export const createStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createActions = context => {
|
||||||
|
const { filter } = context
|
||||||
|
|
||||||
|
const addInlineFilter = (column, value) => {
|
||||||
|
const filterId = `inline-${column}`
|
||||||
|
|
||||||
|
const inlineFilter = {
|
||||||
|
field: column.name,
|
||||||
|
id: filterId,
|
||||||
|
operator: "equal",
|
||||||
|
type: "string",
|
||||||
|
valueType: "value",
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.update($filter => {
|
||||||
|
// Remove any existing inline filter
|
||||||
|
if ($filter?.length) {
|
||||||
|
$filter = $filter?.filter(x => x.id !== filterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new one if a value exists
|
||||||
|
if (value) {
|
||||||
|
$filter = [...($filter || []), inlineFilter]
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filter
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter: {
|
||||||
|
...filter,
|
||||||
|
actions: {
|
||||||
|
addInlineFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = context => {
|
||||||
const { filter, initialFilter } = context
|
const { filter, initialFilter } = context
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue