diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte index 5ac70c93c8..1abddfe1ff 100644 --- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte @@ -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))
- + {#if searching} + focusedCellId.set(null)} + on:keydown={onInputKeyDown} + /> + {/if} + +
+ +
+
+ +
+
{column.label}
- {#if sortedBy} -
- + + {#if searching} +
+ +
+ {:else} + {#if sortedBy} +
+ +
+ {/if} +
(open = true)}> +
{/if} -
(open = true)}> - -
@@ -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; diff --git a/packages/frontend-core/src/components/grid/layout/NewRow.svelte b/packages/frontend-core/src/components/grid/layout/NewRow.svelte index aa9b6fa051..440e15ee0c 100644 --- a/packages/frontend-core/src/components/grid/layout/NewRow.svelte +++ b/packages/frontend-core/src/components/grid/layout/NewRow.svelte @@ -27,8 +27,10 @@ rowVerticalInversionIndex, columnHorizontalInversionIndex, selectedRows, - loading, + loaded, + refreshing, config, + filter, } = getContext("grid") let visible = false @@ -153,7 +155,7 @@ {#if !visible && !selectedRowCount && $config.canAddRows} diff --git a/packages/frontend-core/src/components/grid/stores/filter.js b/packages/frontend-core/src/components/grid/stores/filter.js index a59c98ccdd..a2de0ca2d0 100644 --- a/packages/frontend-core/src/components/grid/stores/filter.js +++ b/packages/frontend-core/src/components/grid/stores/filter.js @@ -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 diff --git a/packages/frontend-core/src/components/grid/stores/rows.js b/packages/frontend-core/src/components/grid/stores/rows.js index 49adb62936..98e64d7acb 100644 --- a/packages/frontend-core/src/components/grid/stores/rows.js +++ b/packages/frontend-core/src/components/grid/stores/rows.js @@ -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)