1
0
Fork 0
mirror of synced 2024-10-04 12:03:31 +13:00

Merge pull request #10429 from Budibase/more-grid-tweaks

More grid improvements
This commit is contained in:
Andrew Kingston 2023-04-27 10:24:21 +01:00 committed by GitHub
commit 0769e332a6
11 changed files with 199 additions and 24 deletions

View file

@ -19,6 +19,7 @@
export let updateValue = rows.actions.updateValue export let updateValue = rows.actions.updateValue
export let invertX = false export let invertX = false
export let invertY = false export let invertY = false
export let contentLines = 1
const emptyError = writable(null) const emptyError = writable(null)
@ -84,5 +85,7 @@
{readonly} {readonly}
{invertY} {invertY}
{invertX} {invertX}
{contentLines}
/> />
<slot />
</GridCell> </GridCell>

View file

@ -117,6 +117,9 @@
.cell.error { .cell.error {
--cell-color: var(--spectrum-global-color-red-500); --cell-color: var(--spectrum-global-color-red-500);
} }
.cell.readonly {
--cell-color: var(--spectrum-global-color-gray-600);
}
.cell:not(.focused) { .cell:not(.focused) {
user-select: none; user-select: none;
} }

View file

@ -37,6 +37,8 @@
$: sortedBy = column.name === $sort.column $: sortedBy = column.name === $sort.column
$: canMoveLeft = orderable && idx > 0 $: canMoveLeft = orderable && idx > 0
$: canMoveRight = orderable && idx < $renderedColumns.length - 1 $: canMoveRight = orderable && idx < $renderedColumns.length - 1
$: ascendingLabel = column.schema?.type === "number" ? "low-high" : "A-Z"
$: descendingLabel = column.schema?.type === "number" ? "high-low" : "Z-A"
const editColumn = () => { const editColumn = () => {
dispatch("edit-column", column.schema) dispatch("edit-column", column.schema)
@ -179,14 +181,14 @@
on:click={sortAscending} on:click={sortAscending}
disabled={column.name === $sort.column && $sort.order === "ascending"} disabled={column.name === $sort.column && $sort.order === "ascending"}
> >
Sort A-Z Sort {ascendingLabel}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
icon="SortOrderDown" icon="SortOrderDown"
on:click={sortDescending} on:click={sortDescending}
disabled={column.name === $sort.column && $sort.order === "descending"} disabled={column.name === $sort.column && $sort.order === "descending"}
> >
Sort Z-A Sort {descendingLabel}
</MenuItem> </MenuItem>
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}> <MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
Move left Move left

View file

@ -12,6 +12,7 @@
export let api export let api
export let invertX = false export let invertX = false
export let invertY = false export let invertY = false
export let contentLines = 1
let isOpen = false let isOpen = false
let focusedOptionIdx = null let focusedOptionIdx = null
@ -86,7 +87,11 @@
class:open class:open
on:click|self={editable ? open : null} on:click|self={editable ? open : null}
> >
<div class="values" on:click={editable ? open : null}> <div
class="values"
class:wrap={contentLines > 1}
on:click={editable ? open : null}
>
{#each values as val} {#each values as val}
{@const color = getOptionColor(val)} {@const color = getOptionColor(val)}
{#if color} {#if color}
@ -160,6 +165,9 @@
grid-row-gap: var(--cell-padding); grid-row-gap: var(--cell-padding);
overflow: hidden; overflow: hidden;
padding: var(--cell-padding); padding: var(--cell-padding);
flex-wrap: nowrap;
}
.values.wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
.text { .text {

View file

@ -29,6 +29,7 @@
export let onChange export let onChange
export let invertX = false export let invertX = false
export let invertY = false export let invertY = false
export let contentLines = 1
const { API, dispatch } = getContext("grid") const { API, dispatch } = getContext("grid")
const color = getColor(0) const color = getColor(0)
@ -243,7 +244,11 @@
<div class="wrapper" class:editable class:focused style="--color:{color};"> <div class="wrapper" class:editable class:focused style="--color:{color};">
<div class="container"> <div class="container">
<div class="values" on:wheel={e => (focused ? e.stopPropagation() : null)}> <div
class="values"
class:wrap={editable || contentLines > 1}
on:wheel={e => (focused ? e.stopPropagation() : null)}
>
{#each value || [] as relationship, idx} {#each value || [] as relationship, idx}
{#if relationship.primaryDisplay} {#if relationship.primaryDisplay}
<div class="badge"> <div class="badge">
@ -376,6 +381,9 @@
grid-row-gap: var(--cell-padding); grid-row-gap: var(--cell-padding);
overflow: hidden; overflow: hidden;
padding: var(--cell-padding); padding: var(--cell-padding);
flex-wrap: nowrap;
}
.values.wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
.count { .count {

View file

@ -3,16 +3,13 @@
import { ActionButton, Popover, Select } from "@budibase/bbui" import { ActionButton, Popover, Select } from "@budibase/bbui"
const { sort, columns, stickyColumn } = getContext("grid") const { sort, columns, stickyColumn } = getContext("grid")
const orderOptions = [
{ label: "A-Z", value: "ascending" },
{ label: "Z-A", value: "descending" },
]
let open = false let open = false
let anchor let anchor
$: columnOptions = getColumnOptions($stickyColumn, $columns) $: columnOptions = getColumnOptions($stickyColumn, $columns)
$: checkValidSortColumn($sort.column, $stickyColumn, $columns) $: checkValidSortColumn($sort.column, $stickyColumn, $columns)
$: orderOptions = getOrderOptions($sort.column, columnOptions)
const getColumnOptions = (stickyColumn, columns) => { const getColumnOptions = (stickyColumn, columns) => {
let options = [] let options = []
@ -20,6 +17,7 @@
options.push({ options.push({
label: stickyColumn.label || stickyColumn.name, label: stickyColumn.label || stickyColumn.name,
value: stickyColumn.name, value: stickyColumn.name,
type: stickyColumn.schema?.type,
}) })
} }
return [ return [
@ -27,10 +25,25 @@
...columns.map(col => ({ ...columns.map(col => ({
label: col.label || col.name, label: col.label || col.name,
value: col.name, value: col.name,
type: col.schema?.type,
})), })),
] ]
} }
const getOrderOptions = (column, columnOptions) => {
const type = columnOptions.find(col => col.value === column)?.type
return [
{
label: type === "number" ? "Low-high" : "A-Z",
value: "ascending",
},
{
label: type === "number" ? "High-low" : "Z-A",
value: "descending",
},
]
}
const updateSortColumn = e => { const updateSortColumn = e => {
sort.update(state => ({ sort.update(state => ({
...state, ...state,

View file

@ -15,6 +15,7 @@
selectedCellMap, selectedCellMap,
focusedRow, focusedRow,
columnHorizontalInversionIndex, columnHorizontalInversionIndex,
contentLines,
} = getContext("grid") } = getContext("grid")
$: rowSelected = !!$selectedRows[row._id] $: rowSelected = !!$selectedRows[row._id]
@ -44,6 +45,7 @@
focused={$focusedCellId === cellId} focused={$focusedCellId === cellId}
selectedUser={$selectedCellMap[cellId]} selectedUser={$selectedCellMap[cellId]}
width={column.width} width={column.width}
contentLines={$contentLines}
/> />
{/each} {/each}
</div> </div>

View file

@ -0,0 +1,53 @@
<script>
export let keybind
export let padded = false
export let overlay = false
$: parsedKeys = parseKeys(keybind)
const parseKeys = keybind => {
return keybind?.split("+").map(key => {
if (key.toLowerCase() === "ctrl") {
return navigator.platform.startsWith("Mac") ? "⌘" : key
} else if (key.toLowerCase() === "enter") {
return "↵"
}
return key
})
}
</script>
<div class="keys" class:padded class:overlay>
{#each parsedKeys as key}
<div class="key">
{key}
</div>
{/each}
</div>
<style>
.keys {
display: flex;
flex-direction: row;
gap: 3px;
}
.keys.padded {
padding: var(--cell-padding);
}
.key {
padding: 2px 6px;
font-size: 12px;
font-weight: 600;
background-color: var(--spectrum-global-color-gray-200);
color: var(--spectrum-global-color-gray-700);
border-radius: 4px;
text-align: center;
display: grid;
place-items: center;
text-transform: uppercase;
}
.overlay .key {
background: rgba(255, 255, 255, 0.2);
color: #eee;
}
</style>

View file

@ -7,6 +7,7 @@
import { GutterWidth } from "../lib/constants" import { GutterWidth } from "../lib/constants"
import { NewRowID } from "../lib/constants" import { NewRowID } from "../lib/constants"
import GutterCell from "../cells/GutterCell.svelte" import GutterCell from "../cells/GutterCell.svelte"
import KeyboardShortcut from "./KeyboardShortcut.svelte"
const { const {
hoveredRowId, hoveredRowId,
@ -27,13 +28,14 @@
columnHorizontalInversionIndex, columnHorizontalInversionIndex,
} = getContext("grid") } = getContext("grid")
let visible = false
let isAdding = false let isAdding = false
let newRow = {} let newRow = {}
let offset = 0 let offset = 0
$: firstColumn = $stickyColumn || $renderedColumns[0] $: firstColumn = $stickyColumn || $renderedColumns[0]
$: width = GutterWidth + ($stickyColumn?.width || 0) $: width = GutterWidth + ($stickyColumn?.width || 0)
$: $tableId, (isAdding = false) $: $tableId, (visible = false)
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows) $: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
const shouldInvertY = (offset, inversionIndex, rows) => { const shouldInvertY = (offset, inversionIndex, rows) => {
@ -45,7 +47,8 @@
const addRow = async () => { const addRow = async () => {
// Blur the active cell and tick to let final value updates propagate // Blur the active cell and tick to let final value updates propagate
$focusedCellAPI?.blur() isAdding = true
$focusedCellId = null
await tick() await tick()
// Create row // Create row
@ -60,17 +63,19 @@
$focusedCellId = `${savedRow._id}-${firstColumn.name}` $focusedCellId = `${savedRow._id}-${firstColumn.name}`
} }
} }
isAdding = false
} }
const clear = () => { const clear = () => {
isAdding = false isAdding = false
visible = false
$focusedCellId = null $focusedCellId = null
$hoveredRowId = null $hoveredRowId = null
document.removeEventListener("keydown", handleKeyPress) document.removeEventListener("keydown", handleKeyPress)
} }
const startAdding = async () => { const startAdding = async () => {
if (isAdding) { if (visible) {
return return
} }
@ -95,7 +100,7 @@
// Update state and select initial cell // Update state and select initial cell
newRow = {} newRow = {}
isAdding = true visible = true
$hoveredRowId = NewRowID $hoveredRowId = NewRowID
if (firstColumn) { if (firstColumn) {
$focusedCellId = `${NewRowID}-${firstColumn.name}` $focusedCellId = `${NewRowID}-${firstColumn.name}`
@ -115,7 +120,7 @@
} }
const handleKeyPress = e => { const handleKeyPress = e => {
if (!isAdding) { if (!visible) {
return return
} }
if (e.key === "Escape") { if (e.key === "Escape") {
@ -137,7 +142,7 @@
</script> </script>
<!-- Only show new row functionality if we have any columns --> <!-- Only show new row functionality if we have any columns -->
{#if isAdding} {#if visible}
<div <div
class="container" class="container"
class:floating={offset > 0} class:floating={offset > 0}
@ -148,6 +153,9 @@
<div class="sticky-column" transition:fade={{ duration: 130 }}> <div class="sticky-column" transition:fade={{ duration: 130 }}>
<GutterCell on:expand={addViaModal} rowHovered> <GutterCell on:expand={addViaModal} rowHovered>
<Icon name="Add" color="var(--spectrum-global-color-gray-500)" /> <Icon name="Add" color="var(--spectrum-global-color-gray-500)" />
{#if isAdding}
<div in:fade={{ duration: 130 }} class="loading-overlay" />
{/if}
</GutterCell> </GutterCell>
{#if $stickyColumn} {#if $stickyColumn}
{@const cellId = `${NewRowID}-${$stickyColumn.name}`} {@const cellId = `${NewRowID}-${$stickyColumn.name}`}
@ -161,7 +169,14 @@
{updateValue} {updateValue}
rowIdx={0} rowIdx={0}
{invertY} {invertY}
/> >
{#if $stickyColumn?.schema?.autocolumn}
<div class="readonly-overlay">Can't edit auto column</div>
{/if}
{#if isAdding}
<div in:fade={{ duration: 130 }} class="loading-overlay" />
{/if}
</DataCell>
{/if} {/if}
</div> </div>
<div class="normal-columns" transition:fade={{ duration: 130 }}> <div class="normal-columns" transition:fade={{ duration: 130 }}>
@ -181,15 +196,32 @@
rowIdx={0} rowIdx={0}
invertX={columnIdx >= $columnHorizontalInversionIndex} invertX={columnIdx >= $columnHorizontalInversionIndex}
{invertY} {invertY}
/> >
{#if column?.schema?.autocolumn}
<div class="readonly-overlay">Can't edit auto column</div>
{/if}
{#if isAdding}
<div in:fade={{ duration: 130 }} class="loading-overlay" />
{/if}
</DataCell>
{/key} {/key}
{/each} {/each}
</div> </div>
</GridScrollWrapper> </GridScrollWrapper>
</div> </div>
<div class="buttons" transition:fade={{ duration: 130 }}> <div class="buttons" transition:fade={{ duration: 130 }}>
<Button size="M" cta on:click={addRow}>Save</Button> <Button size="M" cta on:click={addRow} disabled={isAdding}>
<Button size="M" secondary newStyles on:click={clear}>Cancel</Button> <div class="button-with-keys">
Save
<KeyboardShortcut overlay keybind="Ctrl+Enter" />
</div>
</Button>
<Button size="M" secondary newStyles on:click={clear}>
<div class="button-with-keys">
Cancel
<KeyboardShortcut overlay keybind="Esc" />
</div>
</Button>
</div> </div>
</div> </div>
{/if} {/if}
@ -240,6 +272,14 @@
top: calc(var(--row-height) + var(--offset) + 24px); top: calc(var(--row-height) + var(--offset) + 24px);
left: var(--gutter-width); left: var(--gutter-width);
} }
.button-with-keys {
display: flex;
gap: 6px;
align-items: center;
}
.button-with-keys :global(> div) {
padding-top: 2px;
}
/* Sticky column styles */ /* Sticky column styles */
.sticky-column { .sticky-column {
@ -262,4 +302,33 @@
width: 0; width: 0;
display: flex; display: flex;
} }
/* Readonly cell overlay */
.readonly-overlay {
position: absolute;
top: 0;
left: 0;
height: var(--row-height);
width: 100%;
padding: var(--cell-padding);
font-style: italic;
color: var(--spectrum-global-color-gray-600);
z-index: 1;
user-select: none;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* Overlay while row is being added */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
height: var(--row-height);
width: 100%;
z-index: 1;
background: var(--spectrum-global-color-gray-400);
opacity: 0.25;
}
</style> </style>

View file

@ -7,6 +7,7 @@
import HeaderCell from "../cells/HeaderCell.svelte" import HeaderCell from "../cells/HeaderCell.svelte"
import { GutterWidth, BlankRowID } from "../lib/constants" import { GutterWidth, BlankRowID } from "../lib/constants"
import GutterCell from "../cells/GutterCell.svelte" import GutterCell from "../cells/GutterCell.svelte"
import KeyboardShortcut from "./KeyboardShortcut.svelte"
const { const {
rows, rows,
@ -21,6 +22,7 @@
focusedRow, focusedRow,
scrollLeft, scrollLeft,
dispatch, dispatch,
contentLines,
} = getContext("grid") } = getContext("grid")
$: rowCount = $rows.length $: rowCount = $rows.length
@ -85,6 +87,7 @@
selectedUser={$selectedCellMap[cellId]} selectedUser={$selectedCellMap[cellId]}
width={$stickyColumn.width} width={$stickyColumn.width}
column={$stickyColumn} column={$stickyColumn}
contentLines={$contentLines}
/> />
{/if} {/if}
</div> </div>
@ -103,7 +106,9 @@
<GridCell <GridCell
width={$stickyColumn.width} width={$stickyColumn.width}
highlighted={$hoveredRowId === BlankRowID} highlighted={$hoveredRowId === BlankRowID}
/> >
<KeyboardShortcut padded keybind="Ctrl+Enter" />
</GridCell>
{/if} {/if}
</div> </div>
{/if} {/if}

View file

@ -15,8 +15,22 @@
selectedRows, selectedRows,
} = getContext("grid") } = getContext("grid")
const ignoredOriginSelectors = [
".spectrum-Modal",
"#builder-side-panel-container",
]
// Global key listener which intercepts all key events // Global key listener which intercepts all key events
const handleKeyDown = e => { const handleKeyDown = e => {
// Avoid processing events sourced from certain origins
if (e.target?.closest) {
for (let selector of ignoredOriginSelectors) {
if (e.target.closest(selector)) {
return
}
}
}
// If nothing selected avoid processing further key presses // If nothing selected avoid processing further key presses
if (!$focusedCellId) { if (!$focusedCellId) {
if (e.key === "Tab" || e.key?.startsWith("Arrow")) { if (e.key === "Tab" || e.key?.startsWith("Arrow")) {
@ -60,11 +74,6 @@
return return
} }
} }
// Avoid processing events sourced from modals
if (e.target?.closest?.(".spectrum-Modal")) {
return
}
e.preventDefault() e.preventDefault()
// Handle the key ourselves // Handle the key ourselves