diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 3f36f2f666..3186c40259 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5230,9 +5230,7 @@ "block": true, "name": "Grid block", "icon": "Table", - "styles": [ - "size" - ], + "styles": ["size"], "size": { "width": 600, "height": 400 @@ -5245,23 +5243,49 @@ "key": "table", "required": true }, + { + "type": "columns", + "label": "Columns", + "key": "columns", + "dependsOn": "table" + }, { "type": "filter", "label": "Filtering", - "key": "filter" + "key": "initialFilter" }, { "type": "field/sortable", "label": "Sort column", - "key": "sortColumn" + "key": "initialSortColumn" }, { "type": "select", "label": "Sort order", - "key": "sortOrder", + "key": "initialSortOrder", "options": ["Ascending", "Descending"], "defaultValue": "Ascending" }, + { + "type": "select", + "label": "Row height", + "key": "initialRowHeight", + "placeholder": "Default", + "options": [ + { + "label": "Small", + "value": 36 + }, + { + "label": "Medium", + "value": 64 + }, + { + "label": "Large", + "value": 92 + } + ] + }, { "type": "boolean", "label": "Add rows", diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 5267582bc8..bfc3a15d1c 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -9,9 +9,10 @@ export let allowEditRows = true export let allowDeleteRows = true export let stripeRows = false - export let filter = null - export let sortColumn = null - export let sortOrder = null + export let initialFilter = null + export let initialSortColumn = null + export let initialSortOrder = null + export let initialRowHeight = null const component = getContext("component") const { styleable, API, builderStore } = getContext("sdk") @@ -28,9 +29,10 @@ {allowEditRows} {allowDeleteRows} {stripeRows} - {filter} - {sortColumn} - {sortOrder} + {initialFilter} + {initialSortColumn} + {initialSortOrder} + {initialRowHeight} showControls={false} allowExpandRows={false} allowSchemaChanges={false} diff --git a/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte index 5ffd968d30..fb20cc50e5 100644 --- a/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/ColumnWidthButton.svelte @@ -33,22 +33,6 @@ selected: allLarge, }, ] - - const changeColumnWidth = async width => { - columns.update(state => { - state.forEach(column => { - column.width = width - }) - return state - }) - if ($stickyColumn) { - stickyColumn.update(state => ({ - ...state, - width, - })) - } - await columns.actions.saveChanges() - }
@@ -70,7 +54,7 @@ {#each sizeOptions as option} changeColumnWidth(option.size)} + on:click={() => columns.actions.changeAllColumnWidths(option.size)} selected={option.selected} > {option.label} diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index 9edd69d09d..211bb15917 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -29,7 +29,6 @@ GutterWidth, DefaultRowHeight, } from "../lib/constants" - import { memo } from "../../../utils" export let API = null export let tableId = null @@ -43,48 +42,26 @@ export let collaboration = true export let showAvatars = true export let showControls = true - export let filter = null - export let sortColumn = null - export let sortOrder = null + export let initialFilter = null + export let initialSortColumn = null + export let initialSortOrder = null + export let initialRowHeight = null // Unique identifier for DOM nodes inside this instance const rand = Math.random() - // Stores derived from props. - // We use memo here to ensure redundant store reactions don't fire and cause - // wasted API calls. - const tableIdStore = memo(tableId) - const schemaOverridesStore = memo(schemaOverrides) - const filterStore = memo(filter) - const sortStore = memo({ - column: sortColumn, - order: sortOrder, - }) - const config = memo({ - allowAddRows, - allowExpandRows, - allowEditRows, - allowDeleteRows, - allowSchemaChanges, - stripeRows, - showControls, - }) - // Build up context let context = { API: API || createAPIClient(), rand, - config, - tableId: tableIdStore, - schemaOverrides: schemaOverridesStore, - filter: filterStore, - sort: sortStore, + props: $$props, } context = { ...context, ...createEventManagers() } context = attachStores(context) // Reference some stores for local use const { + config, isResizing, isReordering, ui, @@ -95,22 +72,23 @@ gridFocused, } = context - // Keep prop-derived stores up to date - $: tableIdStore.set(tableId) - $: schemaOverridesStore.set(schemaOverrides) - $: filterStore.set(filter) - $: sortStore.set({ - column: sortColumn, - order: sortOrder, - }) + // Keep config store up to date with props $: config.set({ + tableId, + schemaOverrides, allowAddRows, - allowSchemaChanges, allowExpandRows, allowEditRows, allowDeleteRows, + allowSchemaChanges, stripeRows, + collaboration, + showAvatars, showControls, + initialFilter, + initialSortColumn, + initialSortOrder, + initialRowHeight, }) // Set context for children to consume diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index a0820e77bb..5f34de1434 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -56,6 +56,23 @@ export const deriveStores = context => { }) } + // Updates the width of all columns + const changeAllColumnWidths = async width => { + columns.update(state => { + return state.map(col => ({ + ...col, + width, + })) + }) + if (get(stickyColumn)) { + stickyColumn.update(state => ({ + ...state, + width, + })) + } + await saveChanges() + } + // Persists column changes by saving metadata against table schema const saveChanges = async () => { const $columns = get(columns) @@ -107,6 +124,7 @@ export const deriveStores = context => { saveChanges, saveTable, changePrimaryDisplay, + changeAllColumnWidths, }, }, } diff --git a/packages/frontend-core/src/components/grid/stores/config.js b/packages/frontend-core/src/components/grid/stores/config.js new file mode 100644 index 0000000000..3e8da81e2e --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/config.js @@ -0,0 +1,23 @@ +import { writable } from "svelte/store" +import { derivedMemo } from "../../../utils" + +export const createStores = context => { + const config = writable(context.props) + const getProp = prop => derivedMemo(config, $config => $config[prop]) + + // Derive and memoize some props so that we can react to them in isolation + const tableId = getProp("tableId") + const initialSortColumn = getProp("initialSortColumn") + const initialSortOrder = getProp("initialSortOrder") + const initialFilter = getProp("initialFilter") + const initialRowHeight = getProp("initialRowHeight") + + return { + config, + tableId, + initialSortColumn, + initialSortOrder, + initialFilter, + initialRowHeight, + } +} diff --git a/packages/frontend-core/src/components/grid/stores/filter.js b/packages/frontend-core/src/components/grid/stores/filter.js new file mode 100644 index 0000000000..1f01aaec40 --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/filter.js @@ -0,0 +1,20 @@ +import { writable } from "svelte/store" +import { derivedMemo } from "../../../utils" + +export const createStores = context => { + const { props } = context + + // Initialise to default props + const filter = writable(props.initialFilter) + + return { + filter, + } +} + +export const initialise = context => { + const { filter, initialFilter } = context + + // Reset filter when initial filter prop changes + initialFilter.subscribe(filter.set) +} diff --git a/packages/frontend-core/src/components/grid/stores/index.js b/packages/frontend-core/src/components/grid/stores/index.js index aa552a9ffe..588b568c8e 100644 --- a/packages/frontend-core/src/components/grid/stores/index.js +++ b/packages/frontend-core/src/components/grid/stores/index.js @@ -11,8 +11,14 @@ import * as Users from "./users" import * as Validation from "./validation" import * as Viewport from "./viewport" import * as Clipboard from "./clipboard" +import * as Config from "./config" +import * as Sort from "./sort" +import * as Filter from "./filter" const DependencyOrderedStores = [ + Config, + Sort, + Filter, Bounds, Scroll, Rows, diff --git a/packages/frontend-core/src/components/grid/stores/sort.js b/packages/frontend-core/src/components/grid/stores/sort.js new file mode 100644 index 0000000000..211124f640 --- /dev/null +++ b/packages/frontend-core/src/components/grid/stores/sort.js @@ -0,0 +1,27 @@ +import { writable } from "svelte/store" + +export const createStores = context => { + const { props } = context + + // Initialise to default props + const sort = writable({ + column: props.initialSortColumn, + order: props.initialSortOrder || "ascending", + }) + + return { + sort, + } +} + +export const initialise = context => { + const { sort, initialSortColumn, initialSortOrder } = context + + // Reset sort when initial sort props change + initialSortColumn.subscribe(newSortColumn => { + sort.update(state => ({ ...state, column: newSortColumn })) + }) + initialSortOrder.subscribe(newSortOrder => { + sort.update(state => ({ ...state, order: newSortOrder })) + }) +} diff --git a/packages/frontend-core/src/components/grid/stores/ui.js b/packages/frontend-core/src/components/grid/stores/ui.js index f5c0905320..168b0c8184 100644 --- a/packages/frontend-core/src/components/grid/stores/ui.js +++ b/packages/frontend-core/src/components/grid/stores/ui.js @@ -8,12 +8,13 @@ import { NewRowID, } from "../lib/constants" -export const createStores = () => { +export const createStores = context => { + const { props } = context const focusedCellId = writable(null) const focusedCellAPI = writable(null) const selectedRows = writable({}) const hoveredRowId = writable(null) - const rowHeight = writable(DefaultRowHeight) + const rowHeight = writable(props.initialRowHeight || DefaultRowHeight) const previousFocusedRowId = writable(null) const gridFocused = writable(false) const isDragging = writable(false) @@ -98,9 +99,9 @@ export const deriveStores = context => { // Derive the amount of content lines to show in cells depending on row height const contentLines = derived(rowHeight, $rowHeight => { - if ($rowHeight === LargeRowHeight) { + if ($rowHeight >= LargeRowHeight) { return 3 - } else if ($rowHeight === MediumRowHeight) { + } else if ($rowHeight >= MediumRowHeight) { return 2 } return 1 @@ -133,6 +134,7 @@ export const initialise = context => { hoveredRowId, table, rowHeight, + initialRowHeight, } = context // Ensure we clear invalid rows from state if they disappear @@ -189,4 +191,7 @@ export const initialise = context => { table.subscribe($table => { rowHeight.set($table?.rowHeight || DefaultRowHeight) }) + + // Reset row height when initial row height prop changes + initialRowHeight.subscribe(rowHeight.set) } diff --git a/packages/frontend-core/src/utils/index.js b/packages/frontend-core/src/utils/index.js index e3ec87a5e9..26e44410ba 100644 --- a/packages/frontend-core/src/utils/index.js +++ b/packages/frontend-core/src/utils/index.js @@ -3,5 +3,5 @@ export * as JSONUtils from "./json" export * as CookieUtils from "./cookies" export * as RoleUtils from "./roles" export * as Utils from "./utils" -export { memo } from "./memo" +export { memo, derivedMemo } from "./memo" export { createWebsocket } from "./websocket" diff --git a/packages/frontend-core/src/utils/memo.js b/packages/frontend-core/src/utils/memo.js index d46b264d1b..ba0e3f3490 100644 --- a/packages/frontend-core/src/utils/memo.js +++ b/packages/frontend-core/src/utils/memo.js @@ -1,4 +1,4 @@ -import { writable, get } from "svelte/store" +import { writable, get, derived } from "svelte/store" // A simple svelte store which deeply compares all changes and ensures that // subscribed children will only fire when a new value is actually set @@ -33,3 +33,11 @@ export const memo = initialValue => { }, } } + +// Enriched version of svelte's derived store which returns a memo +export const derivedMemo = (store, derivation) => { + const derivedStore = derived(store, derivation) + const memoStore = memo(get(derivedStore)) + derivedStore.subscribe(memoStore.set) + return memoStore +}