From 9579c9c0d2690362d1574c8cc53365d429ea51f2 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 7 Mar 2023 08:15:27 +0000 Subject: [PATCH] Add column sorting and reordering via popover --- .../src/components/sheet/Sheet.svelte | 6 +- .../components/sheet/cells/HeaderCell.svelte | 98 +++++++++++++------ .../src/components/sheet/stores/columns.js | 31 ++++-- .../src/components/sheet/stores/reorder.js | 44 ++++++--- .../src/components/sheet/stores/resize.js | 5 - .../src/components/sheet/stores/rows.js | 11 ++- .../src/components/sheet/utils.js | 2 +- 7 files changed, 136 insertions(+), 61 deletions(-) diff --git a/packages/frontend-core/src/components/sheet/Sheet.svelte b/packages/frontend-core/src/components/sheet/Sheet.svelte index cd0b2316c0..553bafd2ad 100644 --- a/packages/frontend-core/src/components/sheet/Sheet.svelte +++ b/packages/frontend-core/src/components/sheet/Sheet.svelte @@ -60,7 +60,7 @@ context = { ...context, ...createUserStores(context) } // Reference some stores for local use - const isResizing = context.isResizing + const { isResizing, isReordering } = context // Keep config store up to date $: config.set({ @@ -80,6 +80,7 @@
@@ -129,6 +130,9 @@ .sheet.is-resizing :global(*) { cursor: col-resize !important; } + .sheet.is-reordering :global(*) { + cursor: grabbing !important; + } .sheet-data { flex: 1 1 auto; diff --git a/packages/frontend-core/src/components/sheet/cells/HeaderCell.svelte b/packages/frontend-core/src/components/sheet/cells/HeaderCell.svelte index 770aa91d34..2780d73a9c 100644 --- a/packages/frontend-core/src/components/sheet/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/sheet/cells/HeaderCell.svelte @@ -7,30 +7,51 @@ export let column export let orderable = true - const { reorder, isReordering, isResizing, rand } = getContext("sheet") + const { reorder, isReordering, isResizing, rand, sort, columns } = + getContext("sheet") - let timeout let anchor let open = false - let isClick = true + let timeout + + $: sortedBy = column.name === $sort.column + $: canMoveLeft = orderable && column.idx > 0 + $: canMoveRight = orderable && column.idx < $columns.length - 1 const startReordering = e => { - isClick = true timeout = setTimeout(() => { - isClick = false reorder.actions.startReordering(column.name, e) - }, 250) + }, 200) } const stopReordering = () => { clearTimeout(timeout) } - const onClick = () => { - if (isClick) { - stopReordering() - open = true - } + const sortAscending = () => { + sort.set({ + column: column.name, + order: "ascending", + }) + open = false + } + + const sortDescending = () => { + sort.set({ + column: column.name, + order: "descending", + }) + open = false + } + + const moveLeft = () => { + reorder.actions.moveColumnLeft(column.name) + open = false + } + + const moveRight = () => { + reorder.actions.moveColumnRight(column.name) + open = false } @@ -40,26 +61,34 @@ style="flex: 0 0 {column.width}px;" bind:this={anchor} class:disabled={$isReordering || $isResizing} + class:sorted={sortedBy} >
{column.name}
-
- +
(open = true)} + > +
@@ -67,19 +96,25 @@ Edit column - Sort ascending - Sort descending - {#if orderable} - Move left - Move right - {/if} + + Sort ascending + + + Sort descending + + + Move left + + + Move right + Delete @@ -88,17 +123,15 @@ .header-cell { display: flex; } - .header-cell:not(.disabled):hover :global(.cell), - .header-cell:not(.disabled).open :global(.cell) { - cursor: pointer; - background: var(--spectrum-global-color-gray-200); - } + .header-cell :global(.cell) { background: var(--background); padding: 0 var(--cell-padding); gap: calc(2 * var(--cell-spacing)); border-bottom: none; } + .header-cell.sorted :global(.cell) { + } .name { flex: 1 1 auto; @@ -109,10 +142,13 @@ } .more { - display: none; + padding: 4px; + margin: 0 -4px; } - .header-cell:not(.disabled):hover .more, - .header-cell:not(.disabled).open .more { - display: block; + .more:hover { + cursor: pointer; + } + .more:hover :global(.spectrum-Icon) { + color: var(--spectrum-global-color-gray-800) !important; } diff --git a/packages/frontend-core/src/components/sheet/stores/columns.js b/packages/frontend-core/src/components/sheet/stores/columns.js index f500f7f2f5..b648f7dd32 100644 --- a/packages/frontend-core/src/components/sheet/stores/columns.js +++ b/packages/frontend-core/src/components/sheet/stores/columns.js @@ -1,4 +1,4 @@ -import { get, writable } from "svelte/store" +import { derived, get, writable } from "svelte/store" export const createColumnsStores = context => { const { schema } = context @@ -6,6 +6,21 @@ export const createColumnsStores = context => { const columns = writable([]) const stickyColumn = writable(null) + // Derive an enriched version of columns with left offsets and indexes + // automatically calculated + const enrichedColumns = derived(columns, $columns => { + let offset = 0 + return $columns.map((column, idx) => { + const enriched = { + ...column, + idx, + left: offset, + } + offset += column.width + return enriched + }) + }) + // Merge new schema fields with existing schema in order to preserve widths schema.subscribe($schema => { const currentColumns = get(columns) @@ -23,19 +38,14 @@ export const createColumnsStores = context => { }) // Update columns, removing extraneous columns and adding missing ones - let offset = 0 columns.set( - fields.map((field, idx) => { + fields.map(field => { const existing = currentColumns.find(x => x.name === field) - const newCol = { - idx, + return { name: field, width: existing?.width || defaultWidth, - left: offset, schema: $schema[field], } - offset += newCol.width - return newCol }) ) }) @@ -60,7 +70,10 @@ export const createColumnsStores = context => { }) return { - columns, + columns: { + ...columns, + subscribe: enrichedColumns.subscribe, + }, stickyColumn, } } diff --git a/packages/frontend-core/src/components/sheet/stores/reorder.js b/packages/frontend-core/src/components/sheet/stores/reorder.js index b0978de599..d547e14f69 100644 --- a/packages/frontend-core/src/components/sheet/stores/reorder.js +++ b/packages/frontend-core/src/components/sheet/stores/reorder.js @@ -77,27 +77,19 @@ export const createReorderStores = context => { // Callback when stopping reordering columns const stopReordering = () => { // Swap position of columns - const $columns = get(columns) let { sourceColumn, targetColumn } = get(reorder) + const $columns = get(columns) let sourceIdx = $columns.findIndex(x => x.name === sourceColumn) let targetIdx = $columns.findIndex(x => x.name === targetColumn) targetIdx++ + console.log(sourceIdx, targetIdx) columns.update(state => { const removed = state.splice(sourceIdx, 1) if (--targetIdx < sourceIdx) { targetIdx++ } state.splice(targetIdx, 0, removed[0]) - let offset = 0 - return state.map((col, idx) => { - const newCol = { - ...col, - idx, - left: offset, - } - offset += col.width - return newCol - }) + return state.slice() }) // Reset state @@ -108,12 +100,42 @@ export const createReorderStores = context => { document.removeEventListener("mouseup", stopReordering) } + const moveColumnLeft = column => { + const $columns = get(columns) + const sourceIdx = $columns.findIndex(x => x.name === column) + if (sourceIdx === 0) { + return + } + columns.update(state => { + let tmp = state[sourceIdx] + state[sourceIdx] = state[sourceIdx - 1] + state[sourceIdx - 1] = tmp + return state.slice() + }) + } + + const moveColumnRight = column => { + const $columns = get(columns) + const sourceIdx = $columns.findIndex(x => x.name === column) + if (sourceIdx === $columns.length - 1) { + return + } + columns.update(state => { + let tmp = state[sourceIdx] + state[sourceIdx] = state[sourceIdx + 1] + state[sourceIdx + 1] = tmp + return state.slice() + }) + } + return { reorder: { ...reorder, actions: { startReordering, stopReordering, + moveColumnLeft, + moveColumnRight, }, }, isReordering, diff --git a/packages/frontend-core/src/components/sheet/stores/resize.js b/packages/frontend-core/src/components/sheet/stores/resize.js index 6ffb51b886..0ca16dac13 100644 --- a/packages/frontend-core/src/components/sheet/stores/resize.js +++ b/packages/frontend-core/src/components/sheet/stores/resize.js @@ -51,11 +51,6 @@ export const createResizeStores = context => { } else { columns.update(state => { state[columnIdx].width = newWidth - let offset = state[columnIdx].left + newWidth - for (let i = columnIdx + 1; i < state.length; i++) { - state[i].left = offset - offset += state[i].width - } return [...state] }) } diff --git a/packages/frontend-core/src/components/sheet/stores/rows.js b/packages/frontend-core/src/components/sheet/stores/rows.js index 0b7bd01368..e5e8235c36 100644 --- a/packages/frontend-core/src/components/sheet/stores/rows.js +++ b/packages/frontend-core/src/components/sheet/stores/rows.js @@ -7,6 +7,10 @@ export const createRowsStore = context => { const { config, API } = context const tableId = derived(config, $config => $config.tableId) const filter = derived(config, $config => $config.filter) + const sort = writable({ + column: null, + order: "ascending", + }) // Flag for whether this is the first time loading our fetch let loaded = false @@ -20,7 +24,7 @@ export const createRowsStore = context => { // Local stores for managing fetching data const query = derived(filter, $filter => buildLuceneQuery($filter)) - const fetch = derived([tableId, query], ([$tableId, $query]) => { + const fetch = derived([tableId, query, sort], ([$tableId, $query, $sort]) => { if (!$tableId) { return null } @@ -35,8 +39,8 @@ export const createRowsStore = context => { tableId: $tableId, }, options: { - sortColumn: null, - sortOrder: null, + sortColumn: $sort.column, + sortOrder: $sort.order, query: $query, limit: 100, paginate: true, @@ -241,5 +245,6 @@ export const createRowsStore = context => { }, }, schema, + sort, } } diff --git a/packages/frontend-core/src/components/sheet/utils.js b/packages/frontend-core/src/components/sheet/utils.js index 996465a6ca..21f8d4a6e6 100644 --- a/packages/frontend-core/src/components/sheet/utils.js +++ b/packages/frontend-core/src/components/sheet/utils.js @@ -8,7 +8,7 @@ export const getColor = (idx, opacity = 0.3) => { export const getIconForField = field => { const type = field.schema.type if (type === "options") { - return "ChevronDown" + return "Dropdown" } else if (type === "datetime") { return "Date" }