1
0
Fork 0
mirror of synced 2024-10-03 10:36:59 +13:00

Add basic inline searching and fix create first row popup

This commit is contained in:
Andrew Kingston 2023-10-12 11:58:25 +01:00
parent c610f2123f
commit 789bb528f4
4 changed files with 185 additions and 31 deletions

View file

@ -3,6 +3,7 @@
import GridCell from "./GridCell.svelte"
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
import { getColumnIcon } from "../lib/utils"
import { debounce } from "../../../utils/utils"
export let column
export let idx
@ -23,6 +24,8 @@
definition,
datasource,
schema,
focusedCellId,
filter,
} = getContext("grid")
const bannedDisplayColumnTypes = [
@ -32,12 +35,15 @@
"boolean",
"json",
]
const searchableTypes = ["string", "options", "number"]
let anchor
let open = false
let editIsOpen = false
let timeout
let popover
let searchValue
let input
$: sortedBy = column.name === $sort.column
$: canMoveLeft = orderable && idx > 0
@ -48,6 +54,9 @@
$: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
? "high-low"
: "Z-A"
$: searchable = searchableTypes.includes(column.schema.type)
$: searching = searchValue != null
$: debouncedUpdateFilter(searchValue)
const editColumn = async () => {
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))
</script>
<div
class="header-cell"
class:open
class:searchable
class:searching
style="flex: 0 0 {column.width}px;"
bind:this={anchor}
class:disabled={$isReordering || $isResizing}
@ -168,30 +211,48 @@
defaultHeight
center
>
<Icon
size="S"
name={getColumnIcon(column)}
color={`var(--spectrum-global-color-gray-600)`}
/>
{#if searching}
<input
bind:this={input}
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">
{column.label}
</div>
{#if sortedBy}
<div class="sort-indicator">
<Icon
size="S"
name={$sort.order === "descending" ? "SortOrderDown" : "SortOrderUp"}
color="var(--spectrum-global-color-gray-600)"
/>
{#if searching}
<div class="clear-icon" on:click={stopSearching}>
<Icon hoverable size="S" name="Close" />
</div>
{: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>
{/if}
<div class="more" on:click={() => (open = true)}>
<Icon
size="S"
name="MoreVertical"
color="var(--spectrum-global-color-gray-600)"
/>
</div>
</GridCell>
</div>
@ -289,6 +350,29 @@
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 {
flex: 1 1 auto;
width: 0;
@ -296,23 +380,45 @@
text-overflow: ellipsis;
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;
padding: 4px;
margin: 0 -4px;
}
.header-cell.open .more,
.header-cell:hover .more {
.header-cell.open .more-icon,
.header-cell:hover .more-icon {
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:hover .sort-indicator {
display: none;

View file

@ -27,8 +27,10 @@
rowVerticalInversionIndex,
columnHorizontalInversionIndex,
selectedRows,
loading,
loaded,
refreshing,
config,
filter,
} = getContext("grid")
let visible = false
@ -153,7 +155,7 @@
<!-- New row FAB -->
<TempTooltip
text="Click here to create your first row"
condition={hasNoRows && !$loading}
condition={hasNoRows && $loaded && !$filter?.length && !$refreshing}
type={TooltipType.Info}
>
{#if !visible && !selectedRowCount && $config.canAddRows}

View file

@ -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 => {
const { filter, initialFilter } = context

View file

@ -8,6 +8,7 @@ export const createStores = () => {
const rows = writable([])
const loading = writable(false)
const loaded = writable(false)
const refreshing = writable(false)
const rowChangeCache = writable({})
const inProgressChanges = writable({})
const hasNextPage = writable(false)
@ -53,6 +54,7 @@ export const createStores = () => {
fetch,
rowLookupMap,
loaded,
refreshing,
loading,
rowChangeCache,
inProgressChanges,
@ -82,6 +84,7 @@ export const createActions = context => {
notifications,
fetch,
isDatasourcePlus,
refreshing,
} = context
const instanceLoaded = writable(false)
@ -176,6 +179,9 @@ export const createActions = context => {
// Notify that we're loaded
loading.set(false)
}
// Update refreshing state
refreshing.set($fetch.loading)
})
fetch.set(newFetch)