Add column sorting and reordering via popover
This commit is contained in:
parent
f0ac9e9d9c
commit
9579c9c0d2
|
@ -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 @@
|
|||
<div
|
||||
class="sheet"
|
||||
class:is-resizing={$isResizing}
|
||||
class:is-reordering={$isReordering}
|
||||
style="--cell-height:{cellHeight}px;"
|
||||
id="sheet-{rand}"
|
||||
>
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -40,26 +61,34 @@
|
|||
style="flex: 0 0 {column.width}px;"
|
||||
bind:this={anchor}
|
||||
class:disabled={$isReordering || $isResizing}
|
||||
class:sorted={sortedBy}
|
||||
>
|
||||
<SheetCell
|
||||
reorderSource={$reorder.sourceColumn === column.name}
|
||||
reorderTarget={$reorder.targetColumn === column.name}
|
||||
on:mousedown={orderable ? startReordering : null}
|
||||
on:mouseup={orderable ? stopReordering : null}
|
||||
on:click={onClick}
|
||||
width={column.width}
|
||||
left={column.left}
|
||||
>
|
||||
<Icon
|
||||
size="S"
|
||||
name={getIconForField(column)}
|
||||
color="var(--spectrum-global-color-gray-600)"
|
||||
color={`var(--spectrum-global-color-gray-600)`}
|
||||
/>
|
||||
<div class="name">
|
||||
{column.name}
|
||||
</div>
|
||||
<div class="more">
|
||||
<Icon size="S" name="MoreVertical" />
|
||||
<div
|
||||
class="more"
|
||||
on:mousedown|stopPropagation
|
||||
on:click={() => (open = true)}
|
||||
>
|
||||
<Icon
|
||||
size="S"
|
||||
name="MoreVertical"
|
||||
color={`var(--spectrum-global-color-gray-600)`}
|
||||
/>
|
||||
</div>
|
||||
</SheetCell>
|
||||
</div>
|
||||
|
@ -67,19 +96,25 @@
|
|||
<Popover
|
||||
bind:open
|
||||
{anchor}
|
||||
align="left"
|
||||
align="right"
|
||||
offset={0}
|
||||
popoverTarget={document.getElementById(`sheet-${rand}`)}
|
||||
animate={false}
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem icon="Edit">Edit column</MenuItem>
|
||||
<MenuItem icon="SortOrderUp">Sort ascending</MenuItem>
|
||||
<MenuItem icon="SortOrderDown">Sort descending</MenuItem>
|
||||
{#if orderable}
|
||||
<MenuItem icon="ArrowLeft">Move left</MenuItem>
|
||||
<MenuItem icon="ArrowRight">Move right</MenuItem>
|
||||
{/if}
|
||||
<MenuItem icon="SortOrderUp" on:click={sortAscending}>
|
||||
Sort ascending
|
||||
</MenuItem>
|
||||
<MenuItem icon="SortOrderDown" on:click={sortDescending}>
|
||||
Sort descending
|
||||
</MenuItem>
|
||||
<MenuItem disabled={!canMoveLeft} icon="ArrowLeft" on:click={moveLeft}>
|
||||
Move left
|
||||
</MenuItem>
|
||||
<MenuItem disabled={!canMoveRight} icon="ArrowRight" on:click={moveRight}>
|
||||
Move right
|
||||
</MenuItem>
|
||||
<MenuItem icon="Delete">Delete</MenuItem>
|
||||
</Menu>
|
||||
</Popover>
|
||||
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue