1
0
Fork 0
mirror of synced 2024-10-04 03:54:37 +13:00

Update grid button text and refactor gutter cell into own component

This commit is contained in:
Andrew Kingston 2023-04-24 11:22:03 +01:00
parent 3bbf055401
commit 5155727b56
15 changed files with 199 additions and 372 deletions

View file

@ -16,7 +16,7 @@
</script> </script>
<ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}> <ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}>
Manage access Access
</ActionButton> </ActionButton>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<ManageAccessModal <ManageAccessModal

View file

@ -11,7 +11,7 @@
</script> </script>
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}> <ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
Create view Add view
</ActionButton> </ActionButton>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<CreateViewModal /> <CreateViewModal />

View file

@ -27,6 +27,12 @@
// Get the error for this cell if the row is focused // Get the error for this cell if the row is focused
$: error = getErrorStore(rowFocused, cellId) $: error = getErrorStore(rowFocused, cellId)
$: {
if ($error) {
console.log(cellId, $error)
}
}
// Determine if the cell is editable // Determine if the cell is editable
$: readonly = $: readonly =
column.schema.autocolumn || column.schema.autocolumn ||

View file

@ -0,0 +1,119 @@
<script>
import { GutterWidth } from "../lib/constants"
import { getContext } from "svelte"
import { Checkbox, Icon } from "@budibase/bbui"
import GridCell from "./GridCell.svelte"
import { createEventDispatcher } from "svelte"
export let row
export let rowFocused
export let rowHovered
export let rowSelected
export let disableExpand = false
export let disableNumber = false
const { config, dispatch, selectedRows } = getContext("grid")
const svelteDispatch = createEventDispatcher()
const select = () => {
svelteDispatch("select")
const id = row?._id
if (id) {
selectedRows.update(state => {
let newState = {
...state,
[id]: !state[id],
}
if (!newState[id]) {
delete newState[id]
}
return newState
})
}
}
const expand = () => {
svelteDispatch("expand")
if (row) {
dispatch("edit-row", row)
}
}
</script>
<GridCell
width={GutterWidth}
highlighted={rowFocused || rowHovered}
selected={rowSelected}
>
<div class="gutter">
{#if $$slots.default}
<slot />
{:else}
<div
on:click={select}
class="checkbox"
class:visible={$config.allowDeleteRows &&
(disableNumber || rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} />
</div>
{#if !disableNumber}
<div
class="number"
class:visible={!$config.allowDeleteRows ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}
</div>
{/if}
{/if}
{#if $config.allowExpandRows}
<div
class="expand"
class:visible={!disableExpand && (rowFocused || rowHovered)}
>
<Icon name="Maximize" hoverable size="S" on:click={expand} />
</div>
{/if}
</div>
</GridCell>
<style>
.gutter {
flex: 1 1 auto;
display: grid;
align-items: center;
padding: var(--cell-padding);
grid-template-columns: 1fr auto;
gap: var(--cell-spacing);
}
.checkbox,
.number {
display: none;
flex-direction: row;
justify-content: center;
align-items: center;
}
.checkbox :global(.spectrum-Checkbox) {
min-height: 0;
height: 20px;
}
.checkbox :global(.spectrum-Checkbox-box) {
margin: 3px 0 0 0;
}
.number {
color: var(--spectrum-global-color-gray-500);
}
.checkbox.visible,
.number.visible {
display: flex;
}
.expand {
opacity: 0;
pointer-events: none;
}
.expand.visible {
opacity: 1;
pointer-events: all;
}
</style>

View file

@ -12,5 +12,5 @@
on:click={() => dispatch("add-column")} on:click={() => dispatch("add-column")}
disabled={!$config.allowAddColumns} disabled={!$config.allowAddColumns}
> >
Create column Add column
</ActionButton> </ActionButton>

View file

@ -14,5 +14,5 @@
!$config.allowAddRows || !$config.allowAddRows ||
(!$columns.length && !$stickyColumn)} (!$columns.length && !$stickyColumn)}
> >
Create row Add row
</ActionButton> </ActionButton>

View file

@ -52,13 +52,13 @@
<div bind:this={anchor}> <div bind:this={anchor}>
<ActionButton <ActionButton
icon="ViewColumn" icon="MoveLeftRight"
quiet quiet
size="M" size="M"
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open} selected={open}
> >
Column width Width
</ActionButton> </ActionButton>
</div> </div>

View file

@ -48,7 +48,7 @@
selected={open || anyHidden} selected={open || anyHidden}
disabled={!$columns.length} disabled={!$columns.length}
> >
Hide columns Columns
</ActionButton> </ActionButton>
</div> </div>

View file

@ -36,13 +36,13 @@
<div bind:this={anchor}> <div bind:this={anchor}>
<ActionButton <ActionButton
icon="LineHeight" icon="MoveUpDown"
quiet quiet
size="M" size="M"
on:click={() => (open = !open)} on:click={() => (open = !open)}
selected={open} selected={open}
> >
Row height Height
</ActionButton> </ActionButton>
</div> </div>

View file

@ -2,11 +2,25 @@
import { getContext, onMount } from "svelte" import { getContext, onMount } from "svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte"
import GridRow from "./GridRow.svelte" import GridRow from "./GridRow.svelte"
import { BlankRowID } from "../lib/constants"
const { bounds, renderedRows, rowVerticalInversionIndex } = getContext("grid") const {
bounds,
renderedRows,
renderedColumns,
rowVerticalInversionIndex,
config,
hoveredRowId,
dispatch,
} = getContext("grid")
let body let body
$: renderColumnsWidth = $renderedColumns.reduce(
(total, col) => (total += col.width),
0
)
onMount(() => { onMount(() => {
// Observe and record the height of the body // Observe and record the height of the body
const observer = new ResizeObserver(() => { const observer = new ResizeObserver(() => {
@ -24,6 +38,16 @@
{#each $renderedRows as row, idx} {#each $renderedRows as row, idx}
<GridRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} /> <GridRow {row} {idx} invertY={idx >= $rowVerticalInversionIndex} />
{/each} {/each}
{#if $config.allowAddRows}
<div
class="blank"
class:highlighted={$hoveredRowId === BlankRowID}
style="width:{renderColumnsWidth}px"
on:mouseenter={() => ($hoveredRowId = BlankRowID)}
on:mouseleave={() => ($hoveredRowId = null)}
on:click={() => dispatch("add-row-inline", true)}
/>
{/if}
</GridScrollWrapper> </GridScrollWrapper>
</div> </div>
@ -35,4 +59,15 @@
overflow: hidden; overflow: hidden;
flex: 1 1 auto; flex: 1 1 auto;
} }
.blank {
height: var(--row-height);
background: var(--cell-background);
border-bottom: var(--cell-border);
border-right: var(--cell-border);
position: absolute;
}
.blank.highlighted {
background: var(--cell-background-hover);
cursor: pointer;
}
</style> </style>

View file

@ -1,228 +0,0 @@
<script>
import GridCell from "../cells/GridCell.svelte"
import { getContext, onMount, tick } from "svelte"
import { Icon, Button } from "@budibase/bbui"
import GridScrollWrapper from "./GridScrollWrapper.svelte"
import DataCell from "../cells/DataCell.svelte"
import { fade } from "svelte/transition"
import { GutterWidth } from "../lib/constants"
export let animate = false
export let rowIdx = 0
const {
hoveredRowId,
focusedCellId,
stickyColumn,
config,
dispatch,
rows,
focusedCellAPI,
tableId,
subscribe,
renderedColumns,
focusedRow,
reorder,
} = getContext("grid")
let isAdding = false
let newRow = { _id: `new${rowIdx}` }
let touched = false
$: rowId = `new${rowIdx}`
$: firstColumn = $stickyColumn || $renderedColumns[0]
$: width = GutterWidth + ($stickyColumn?.width || 0)
$: $tableId, (isAdding = false)
$: rowHovered = $hoveredRowId === rowId
$: rowFocused = $focusedRow?._id === rowId
$: reorderSource = $reorder.sourceColumn
const addRow = async () => {
// Create row
const savedRow = await rows.actions.addRow(newRow, rowIdx)
if (savedRow) {
// Select the first cell if possible
if (firstColumn) {
$focusedCellId = `${savedRow._id}-${firstColumn.name}`
}
// Reset state
isAdding = false
newRow = {}
}
}
const cancel = () => {
newRow = { _id: rowId }
isAdding = false
$focusedCellId = null
$hoveredRowId = null
}
const startAdding = async () => {
isAdding = true
$hoveredRowId = rowId
if (firstColumn) {
$focusedCellId = `${rowId}-${firstColumn.name}`
// Also focus the cell if it is a text-like cell
if (["string", "number"].includes(firstColumn.schema.type)) {
await tick()
$focusedCellAPI?.focus()
}
}
}
const updateValue = (rowId, columnName, val) => {
touched = true
newRow[columnName] = val
}
const addViaModal = () => {
isAdding = false
dispatch("add-row")
}
const handleKeyPress = e => {
if (!isAdding) {
return
}
if (e.key === "Escape") {
cancel()
}
}
onMount(() => subscribe("add-row-inline", startAdding))
onMount(() => {
document.addEventListener("keydown", handleKeyPress)
return () => {
document.removeEventListener("keydown", handleKeyPress)
}
})
</script>
<!-- Only show new row functionality if we have any columns -->
<div
class="container"
transition:fade={{ duration: 130 }}
on:focus
on:mouseenter={() => ($hoveredRowId = rowId)}
on:mouseleave={() => ($hoveredRowId = null)}
>
<div class="sticky-column" style="flex: 0 0 {width}px">
<GridCell width={GutterWidth} highlighted={rowHovered || rowFocused}>
<div class="gutter">
<div class="number">
<Icon name="Add" />
</div>
{#if $config.allowExpandRows}
<div class="expand" class:visible={rowFocused || rowHovered}>
<Icon name="Maximize" size="S" hoverable on:click={addViaModal} />
</div>
{/if}
</div>
</GridCell>
{#if $stickyColumn}
{@const cellId = `${rowId}-${$stickyColumn.name}`}
<DataCell
{cellId}
{rowFocused}
highlighted={rowHovered || rowFocused}
column={$stickyColumn}
row={newRow}
focused={$focusedCellId === cellId}
width={$stickyColumn.width}
{updateValue}
rowIdx={0}
/>
{/if}
</div>
<GridScrollWrapper scrollHorizontally wheelInteractive>
<div class="row">
{#each $renderedColumns as column}
{@const cellId = `${rowId}-${column.name}`}
{#key cellId}
<DataCell
{cellId}
{column}
{updateValue}
{rowFocused}
highlighted={rowHovered ||
rowFocused ||
reorderSource === column.name}
row={newRow}
focused={$focusedCellId === cellId}
width={column.width}
rowIdx={0}
/>
{/key}
{/each}
</div>
</GridScrollWrapper>
{#if Object.keys(newRow || {}).length > 1}
<div class="buttons" in:fade={{ duration: 130 }}>
<Button size="M" cta on:click={addRow}>Save</Button>
<Button size="M" secondary newStyles on:click={cancel}>Cancel</Button>
</div>
{/if}
</div>
<style>
.container {
display: flex;
flex-direction: row;
align-items: stretch;
}
/* Floating buttons which sit on top of the underlay but below the sticky column */
.buttons {
display: flex;
flex-direction: row;
gap: 8px;
pointer-events: all;
z-index: 3;
position: absolute;
top: calc(var(--row-height) + 24px);
left: var(--gutter-width);
}
/* Sticky column styles */
.sticky-column {
display: flex;
z-index: 4;
position: relative;
align-self: flex-start;
}
.sticky-column :global(.cell:not(:last-child)) {
border-right: none;
}
.gutter {
flex: 1 1 auto;
display: grid;
align-items: center;
padding: var(--cell-padding);
grid-template-columns: 1fr auto;
gap: var(--cell-spacing);
}
.number {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
color: var(--spectrum-global-color-gray-500);
}
.expand {
opacity: 0;
pointer-events: none;
}
.expand.visible {
opacity: 1;
pointer-events: all;
}
/* Normal column styles */
.row {
width: 0;
display: flex;
}
</style>

View file

@ -1,25 +0,0 @@
<script>
import NewRow from "./NewRow.svelte"
import { getContext } from "svelte"
import { DefaultRowHeight } from "../lib/constants"
const { rows, renderedRows, scrollTop, rowHeight } = getContext("grid")
$: top =
$renderedRows.length * $rowHeight -
($scrollTop % $rowHeight) +
DefaultRowHeight
</script>
<div class="new-row-bottom" style="top:{top}px;">
<NewRow rowIdx={$rows.length} />
</div>
<style>
.new-row-bottom {
position: absolute;
left: 0;
width: 100%;
height: 100px;
}
</style>

View file

@ -1,11 +1,12 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { Checkbox, Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import GridCell from "../cells/GridCell.svelte" import GridCell from "../cells/GridCell.svelte"
import DataCell from "../cells/DataCell.svelte" import DataCell from "../cells/DataCell.svelte"
import GridScrollWrapper from "./GridScrollWrapper.svelte" import GridScrollWrapper from "./GridScrollWrapper.svelte"
import HeaderCell from "../cells/HeaderCell.svelte" import HeaderCell from "../cells/HeaderCell.svelte"
import { GutterWidth } from "../lib/constants" import { GutterWidth, BlankRowID } from "../lib/constants"
import GutterCell from "../cells/GutterCell.svelte"
const { const {
rows, rows,
@ -17,8 +18,8 @@
config, config,
selectedCellMap, selectedCellMap,
focusedRow, focusedRow,
dispatch,
scrollLeft, scrollLeft,
dispatch,
} = getContext("grid") } = getContext("grid")
$: rowCount = $rows.length $: rowCount = $rows.length
@ -37,19 +38,6 @@
$selectedRows = allRows $selectedRows = allRows
} }
} }
const selectRow = id => {
selectedRows.update(state => {
let newState = {
...state,
[id]: !state[id],
}
if (!newState[id]) {
delete newState[id]
}
return newState
})
}
</script> </script>
<div <div
@ -58,26 +46,7 @@
class:scrolled={$scrollLeft > 0} class:scrolled={$scrollLeft > 0}
> >
<div class="header row"> <div class="header row">
<GridCell width={GutterWidth} defaultHeight center> <GutterCell disableExpand disableNumber on:select={selectAll} />
<div class="gutter">
<div class="checkbox visible">
{#if $config.allowDeleteRows}
<div on:click={selectAll}>
<Checkbox
value={rowCount && selectedRowCount === rowCount}
disabled={!$renderedRows.length}
/>
</div>
{/if}
</div>
{#if $config.allowExpandRows}
<div class="expand">
<Icon name="Maximize" size="S" />
</div>
{/if}
</div>
</GridCell>
{#if $stickyColumn} {#if $stickyColumn}
<HeaderCell column={$stickyColumn} orderable={false} idx="sticky" /> <HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
{/if} {/if}
@ -95,41 +64,7 @@
on:mouseenter={() => ($hoveredRowId = row._id)} on:mouseenter={() => ($hoveredRowId = row._id)}
on:mouseleave={() => ($hoveredRowId = null)} on:mouseleave={() => ($hoveredRowId = null)}
> >
<GridCell <GutterCell {row} {rowFocused} {rowHovered} {rowSelected} />
width={GutterWidth}
highlighted={rowFocused || rowHovered}
selected={rowSelected}
>
<div class="gutter">
<div
on:click={() => selectRow(row._id)}
class="checkbox"
class:visible={$config.allowDeleteRows &&
(rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} />
</div>
<div
class="number"
class:visible={!$config.allowDeleteRows ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}
</div>
{#if $config.allowExpandRows}
<div class="expand" class:visible={rowFocused || rowHovered}>
<Icon
name="Maximize"
hoverable
size="S"
on:click={() => {
dispatch("edit-row", row)
}}
/>
</div>
{/if}
</div>
</GridCell>
{#if $stickyColumn} {#if $stickyColumn}
<DataCell <DataCell
{row} {row}
@ -146,6 +81,24 @@
{/if} {/if}
</div> </div>
{/each} {/each}
{#if $config.allowAddRows}
<div
class="row new"
on:mouseenter={() => ($hoveredRowId = BlankRowID)}
on:mouseleave={() => ($hoveredRowId = null)}
on:click={() => dispatch("add-row-inline", true)}
>
<GutterCell disableExpand rowHovered={$hoveredRowId === BlankRowID}>
<Icon name="Add" />
</GutterCell>
{#if $stickyColumn}
<GridCell
width={$stickyColumn.width}
highlighted={$hoveredRowId === BlankRowID}
/>
{/if}
</div>
{/if}
</GridScrollWrapper> </GridScrollWrapper>
</div> </div>
</div> </div>
@ -203,43 +156,7 @@
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
} }
.row.new :global(*:hover) {
/* Styles for gutter */ cursor: pointer;
.gutter {
flex: 1 1 auto;
display: grid;
align-items: center;
padding: var(--cell-padding);
grid-template-columns: 1fr auto;
gap: var(--cell-spacing);
}
.checkbox,
.number {
display: none;
flex-direction: row;
justify-content: center;
align-items: center;
}
.checkbox :global(.spectrum-Checkbox) {
min-height: 0;
height: 20px;
}
.checkbox :global(.spectrum-Checkbox-box) {
margin: 3px 0 0 0;
}
.number {
color: var(--spectrum-global-color-gray-500);
}
.checkbox.visible,
.number.visible {
display: flex;
}
.expand {
opacity: 0;
pointer-events: none;
}
.expand.visible {
opacity: 1;
pointer-events: all;
} }
</style> </style>

View file

@ -10,3 +10,4 @@ export const MediumRowHeight = 64
export const LargeRowHeight = 92 export const LargeRowHeight = 92
export const DefaultRowHeight = SmallRowHeight export const DefaultRowHeight = SmallRowHeight
export const NewRowID = "new" export const NewRowID = "new"
export const BlankRowID = "blank"

View file

@ -75,6 +75,8 @@ export const deriveStores = context => {
leftEdge += $visibleColumns[endColIdx].width leftEdge += $visibleColumns[endColIdx].width
endColIdx++ endColIdx++
} }
// Render an additional column on either side to account for
// debounce column updates based on scroll position
const next = $visibleColumns.slice( const next = $visibleColumns.slice(
Math.max(0, startColIdx - 1), Math.max(0, startColIdx - 1),
endColIdx + 1 endColIdx + 1