Add sheet button to control column visibilty, improve sorting, improve disabled states
This commit is contained in:
parent
c573955998
commit
57c82c4a5d
17 changed files with 206 additions and 115 deletions
|
@ -1467,11 +1467,6 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sinonjs/commons" "^1.7.0"
|
"@sinonjs/commons" "^1.7.0"
|
||||||
|
|
||||||
"@socket.io/component-emitter@~3.1.0":
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
|
||||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
|
||||||
|
|
||||||
"@spectrum-css/accordion@^3.0.24":
|
"@spectrum-css/accordion@^3.0.24":
|
||||||
version "3.0.30"
|
version "3.0.30"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/accordion/-/accordion-3.0.30.tgz#0893a6db28bab984bf5adaf7e1ba194e741db615"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/accordion/-/accordion-3.0.30.tgz#0893a6db28bab984bf5adaf7e1ba194e741db615"
|
||||||
|
@ -2586,7 +2581,7 @@ dayjs@^1.10.4, dayjs@^1.11.2:
|
||||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
|
||||||
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
|
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
|
||||||
|
|
||||||
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
@ -2779,22 +2774,6 @@ end-of-stream@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
|
|
||||||
engine.io-client@~6.4.0:
|
|
||||||
version "6.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91"
|
|
||||||
integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==
|
|
||||||
dependencies:
|
|
||||||
"@socket.io/component-emitter" "~3.1.0"
|
|
||||||
debug "~4.3.1"
|
|
||||||
engine.io-parser "~5.0.3"
|
|
||||||
ws "~8.11.0"
|
|
||||||
xmlhttprequest-ssl "~2.0.0"
|
|
||||||
|
|
||||||
engine.io-parser@~5.0.3:
|
|
||||||
version "5.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45"
|
|
||||||
integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==
|
|
||||||
|
|
||||||
enquirer@^2.3.6:
|
enquirer@^2.3.6:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||||
|
@ -5917,24 +5896,6 @@ snapdragon@^0.8.1:
|
||||||
source-map-resolve "^0.5.0"
|
source-map-resolve "^0.5.0"
|
||||||
use "^3.1.0"
|
use "^3.1.0"
|
||||||
|
|
||||||
socket.io-client@^4.6.1:
|
|
||||||
version "4.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.6.1.tgz#80d97d5eb0feca448a0fb6d69a7b222d3d547eab"
|
|
||||||
integrity sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==
|
|
||||||
dependencies:
|
|
||||||
"@socket.io/component-emitter" "~3.1.0"
|
|
||||||
debug "~4.3.2"
|
|
||||||
engine.io-client "~6.4.0"
|
|
||||||
socket.io-parser "~4.2.1"
|
|
||||||
|
|
||||||
socket.io-parser@~4.2.1:
|
|
||||||
version "4.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
|
|
||||||
integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
|
|
||||||
dependencies:
|
|
||||||
"@socket.io/component-emitter" "~3.1.0"
|
|
||||||
debug "~4.3.1"
|
|
||||||
|
|
||||||
source-map-js@^1.0.2:
|
source-map-js@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
|
@ -6752,11 +6713,6 @@ ws@^7.4.6:
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
|
||||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||||
|
|
||||||
ws@~8.11.0:
|
|
||||||
version "8.11.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
|
|
||||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
|
||||||
|
|
||||||
xml-name-validator@^3.0.0:
|
xml-name-validator@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||||
|
@ -6767,11 +6723,6 @@ xmlchars@^2.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||||
|
|
||||||
xmlhttprequest-ssl@~2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
|
|
||||||
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
|
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const { dispatch } = getContext("sheet")
|
const { dispatch, columns } = getContext("sheet")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="add-component" on:click={() => dispatch("add-row")}>
|
{#if $columns.length}
|
||||||
<Icon size="XL" name="Add" />
|
<div class="add-component" on:click={() => dispatch("add-row")}>
|
||||||
</div>
|
<Icon size="XL" name="Add" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.add-component {
|
.add-component {
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
import HeaderCell from "./cells/HeaderCell.svelte"
|
import HeaderCell from "./cells/HeaderCell.svelte"
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
|
|
||||||
const { visibleColumns, dispatch, config } = getContext("sheet")
|
const { renderedColumns, dispatch, config } = getContext("sheet")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<SheetScrollWrapper scrollVertically={false} wheelInteractive={false}>
|
<SheetScrollWrapper scrollVertically={false} wheelInteractive={false}>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{#each $visibleColumns as column}
|
{#each $renderedColumns as column, idx}
|
||||||
<HeaderCell {column} />
|
<HeaderCell {column} {idx} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</SheetScrollWrapper>
|
</SheetScrollWrapper>
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
border-bottom: var(--cell-border);
|
border-bottom: var(--cell-border);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
height: var(--cell-height);
|
||||||
}
|
}
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
place-items: center;
|
place-items: center;
|
||||||
width: 46px;
|
width: 46px;
|
||||||
border-left: var(--cell-border);
|
border-left: var(--cell-border);
|
||||||
|
border-bottom: var(--cell-border);
|
||||||
}
|
}
|
||||||
.new-column:hover {
|
.new-column:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { getCellRenderer } from "./renderers"
|
import { getCellRenderer } from "./renderers"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
visibleColumns,
|
renderedColumns,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
rows,
|
rows,
|
||||||
selectedCellId,
|
selectedCellId,
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
</div>
|
</div>
|
||||||
<SheetScrollWrapper scrollVertically={false}>
|
<SheetScrollWrapper scrollVertically={false}>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{#each $visibleColumns as column}
|
{#each $renderedColumns as column}
|
||||||
{@const cellId = `new-${column.name}`}
|
{@const cellId = `new-${column.name}`}
|
||||||
<SheetCell
|
<SheetCell
|
||||||
width={column.width}
|
width={column.width}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
columns,
|
columns,
|
||||||
resize,
|
resize,
|
||||||
scroll,
|
scroll,
|
||||||
visibleColumns,
|
renderedColumns,
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
isReordering,
|
isReordering,
|
||||||
} = getContext("sheet")
|
} = getContext("sheet")
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
$: scrollLeft = $scroll.left
|
$: scrollLeft = $scroll.left
|
||||||
$: cutoff = scrollLeft + 40 + ($columns[0]?.width || 0)
|
$: cutoff = scrollLeft + 40 + ($columns[0]?.width || 0)
|
||||||
$: offset = 40 + ($stickyColumn?.width || 0)
|
$: offset = 40 + ($stickyColumn?.width || 0)
|
||||||
$: columnIdx = $resize.columnIdx
|
$: column = $resize.column
|
||||||
|
|
||||||
const getStyle = (column, offset, scrollLeft) => {
|
const getStyle = (column, offset, scrollLeft) => {
|
||||||
const left = offset + column.left + column.width - scrollLeft
|
const left = offset + column.left + column.width - scrollLeft
|
||||||
|
@ -25,17 +25,17 @@
|
||||||
{#if $stickyColumn}
|
{#if $stickyColumn}
|
||||||
<div
|
<div
|
||||||
class="resize-slider sticky"
|
class="resize-slider sticky"
|
||||||
class:visible={columnIdx === "sticky"}
|
class:visible={column === $stickyColumn.name}
|
||||||
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
|
on:mousedown={e => resize.actions.startResizing($stickyColumn, e)}
|
||||||
style="left:{40 + $stickyColumn.width}px;"
|
style="left:{40 + $stickyColumn.width}px;"
|
||||||
>
|
>
|
||||||
<div class="resize-indicator" />
|
<div class="resize-indicator" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $visibleColumns as column}
|
{#each $renderedColumns as column}
|
||||||
<div
|
<div
|
||||||
class="resize-slider"
|
class="resize-slider"
|
||||||
class:visible={columnIdx === column.idx}
|
class:visible={column === column.name}
|
||||||
on:mousedown={e => resize.actions.startResizing(column, e)}
|
on:mousedown={e => resize.actions.startResizing(column, e)}
|
||||||
style={getStyle(column, offset, scrollLeft)}
|
style={getStyle(column, offset, scrollLeft)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
import SheetScrollWrapper from "./SheetScrollWrapper.svelte"
|
||||||
import SheetRow from "./SheetRow.svelte"
|
import SheetRow from "./SheetRow.svelte"
|
||||||
|
|
||||||
const { bounds, visibleRows } = getContext("sheet")
|
const { bounds, renderedRows } = getContext("sheet")
|
||||||
|
|
||||||
let body
|
let body
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<div bind:this={body} class="sheet-body">
|
<div bind:this={body} class="sheet-body">
|
||||||
<SheetScrollWrapper>
|
<SheetScrollWrapper>
|
||||||
{#each $visibleRows as row, idx}
|
{#each $renderedRows as row, idx}
|
||||||
<SheetRow {row} {idx} />
|
<SheetRow {row} {idx} />
|
||||||
{/each}
|
{/each}
|
||||||
</SheetScrollWrapper>
|
</SheetScrollWrapper>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton } from "@budibase/bbui"
|
|
||||||
import SortButton from "./controls/SortButton.svelte"
|
import SortButton from "./controls/SortButton.svelte"
|
||||||
|
import HideColumnsButton from "./controls/HideColumnsButton.svelte"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="VisibilityOff" quiet size="M">Hide fields</ActionButton>
|
<HideColumnsButton />
|
||||||
<SortButton />
|
<SortButton />
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
reorder,
|
reorder,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
rows,
|
rows,
|
||||||
visibleColumns,
|
renderedColumns,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
selectedCellMap,
|
selectedCellMap,
|
||||||
selectedCellRow,
|
selectedCellRow,
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
on:mouseover={() => ($hoveredRowId = row._id)}
|
on:mouseover={() => ($hoveredRowId = row._id)}
|
||||||
on:mouseleave={() => ($hoveredRowId = null)}
|
on:mouseleave={() => ($hoveredRowId = null)}
|
||||||
>
|
>
|
||||||
{#each $visibleColumns as column (column.name)}
|
{#each $renderedColumns as column (column.name)}
|
||||||
{@const cellId = `${row._id}-${column.name}`}
|
{@const cellId = `${row._id}-${column.name}`}
|
||||||
<SheetCell
|
<SheetCell
|
||||||
rowSelected={rowSelected || containsSelectedCell}
|
rowSelected={rowSelected || containsSelectedCell}
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
cellHeight,
|
cellHeight,
|
||||||
scroll,
|
scroll,
|
||||||
bounds,
|
bounds,
|
||||||
columns,
|
|
||||||
visibleRows,
|
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
|
renderedRows,
|
||||||
|
renderedColumns,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
maxScrollTop,
|
maxScrollTop,
|
||||||
maxScrollLeft,
|
maxScrollLeft,
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
export let scrollHorizontally = true
|
export let scrollHorizontally = true
|
||||||
export let wheelInteractive = true
|
export let wheelInteractive = true
|
||||||
|
|
||||||
$: hiddenWidths = calculateHiddenWidths($visibleColumns)
|
$: hiddenWidths = calculateHiddenWidths($renderedColumns)
|
||||||
$: scrollLeft = $scroll.left
|
$: scrollLeft = $scroll.left
|
||||||
$: scrollTop = $scroll.top
|
$: scrollTop = $scroll.top
|
||||||
$: style = generateStyle($scroll, hiddenWidths)
|
$: style = generateStyle($scroll, hiddenWidths)
|
||||||
|
@ -31,12 +31,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates with total width of all columns currently not rendered
|
// Calculates with total width of all columns currently not rendered
|
||||||
const calculateHiddenWidths = visibleColumns => {
|
const calculateHiddenWidths = renderedColumns => {
|
||||||
const idx = visibleColumns[0]?.idx
|
const idx = $visibleColumns.findIndex(
|
||||||
|
col => col.name === renderedColumns[0]?.name
|
||||||
|
)
|
||||||
let width = 0
|
let width = 0
|
||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
for (let i = 0; i < idx; i++) {
|
for (let i = 0; i < idx; i++) {
|
||||||
width += $columns[i].width
|
width += $visibleColumns[i].width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return width
|
return width
|
||||||
|
@ -67,7 +69,7 @@
|
||||||
|
|
||||||
// Hover row under cursor
|
// Hover row under cursor
|
||||||
const y = clientY - $bounds.top + (newScrollTop % cellHeight)
|
const y = clientY - $bounds.top + (newScrollTop % cellHeight)
|
||||||
const hoveredRow = $visibleRows[Math.floor(y / cellHeight)]
|
const hoveredRow = $renderedRows[Math.floor(y / cellHeight)]
|
||||||
$hoveredRowId = hoveredRow?._id
|
$hoveredRowId = hoveredRow?._id
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
rows,
|
rows,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
visibleRows,
|
renderedRows,
|
||||||
selectedCellId,
|
selectedCellId,
|
||||||
hoveredRowId,
|
hoveredRowId,
|
||||||
scroll,
|
scroll,
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
|
|
||||||
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
<div class="content" on:mouseleave={() => ($hoveredRowId = null)}>
|
||||||
<SheetScrollWrapper scrollHorizontally={false}>
|
<SheetScrollWrapper scrollHorizontally={false}>
|
||||||
{#each $visibleRows as row, idx}
|
{#each $renderedRows as row, idx}
|
||||||
{@const rowSelected = !!$selectedRows[row._id]}
|
{@const rowSelected = !!$selectedRows[row._id]}
|
||||||
{@const rowHovered = $hoveredRowId === row._id}
|
{@const rowHovered = $hoveredRowId === row._id}
|
||||||
{@const containsSelectedRow = $selectedCellRow?._id === row._id}
|
{@const containsSelectedRow = $selectedCellRow?._id === row._id}
|
||||||
|
@ -161,13 +161,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
border-bottom: var(--cell-border);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.header :global(.cell) {
|
.header :global(.cell) {
|
||||||
background: var(--spectrum-global-color-gray-100);
|
background: var(--spectrum-global-color-gray-100);
|
||||||
border-bottom: none;
|
|
||||||
}
|
}
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { getIconForField } from "../utils"
|
import { getIconForField } from "../utils"
|
||||||
|
|
||||||
export let column
|
export let column
|
||||||
|
export let idx
|
||||||
export let orderable = true
|
export let orderable = true
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
isResizing,
|
isResizing,
|
||||||
rand,
|
rand,
|
||||||
sort,
|
sort,
|
||||||
columns,
|
renderedColumns,
|
||||||
dispatch,
|
dispatch,
|
||||||
config,
|
config,
|
||||||
} = getContext("sheet")
|
} = getContext("sheet")
|
||||||
|
@ -23,8 +24,8 @@
|
||||||
let timeout
|
let timeout
|
||||||
|
|
||||||
$: sortedBy = column.name === $sort.column
|
$: sortedBy = column.name === $sort.column
|
||||||
$: canMoveLeft = orderable && column.idx > 0
|
$: canMoveLeft = orderable && idx > 0
|
||||||
$: canMoveRight = orderable && column.idx < $columns.length - 1
|
$: canMoveRight = orderable && idx < $renderedColumns.length - 1
|
||||||
|
|
||||||
const editColumn = () => {
|
const editColumn = () => {
|
||||||
dispatch("edit-column", column.schema)
|
dispatch("edit-column", column.schema)
|
||||||
|
@ -147,7 +148,6 @@
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
padding: 0 var(--cell-padding);
|
padding: 0 var(--cell-padding);
|
||||||
gap: calc(2 * var(--cell-spacing));
|
gap: calc(2 * var(--cell-spacing));
|
||||||
border-bottom: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { ActionButton, Popover, Toggle } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const { columns } = getContext("sheet")
|
||||||
|
|
||||||
|
let open = false
|
||||||
|
let anchor
|
||||||
|
|
||||||
|
$: anyHidden = $columns.some(col => !col.visible)
|
||||||
|
|
||||||
|
const toggleVisibility = (column, visible) => {
|
||||||
|
columns.update(state => {
|
||||||
|
const index = state.findIndex(col => col.name === column.name)
|
||||||
|
state[index].visible = visible
|
||||||
|
return state.slice()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const showAll = () => {
|
||||||
|
columns.update(state => {
|
||||||
|
return state.map(col => ({
|
||||||
|
...col,
|
||||||
|
visible: true,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideAll = () => {
|
||||||
|
columns.update(state => {
|
||||||
|
return state.map(col => ({
|
||||||
|
...col,
|
||||||
|
visible: false,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<ActionButton
|
||||||
|
icon="VisibilityOff"
|
||||||
|
quiet
|
||||||
|
size="M"
|
||||||
|
on:click={() => (open = !open)}
|
||||||
|
selected={open || anyHidden}
|
||||||
|
disabled={!$columns.length}
|
||||||
|
>
|
||||||
|
Hide columns
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Popover bind:open {anchor} align="left">
|
||||||
|
<div class="content">
|
||||||
|
<div class="columns">
|
||||||
|
{#each $columns as column}
|
||||||
|
<Toggle
|
||||||
|
size="S"
|
||||||
|
value={column.visible}
|
||||||
|
on:change={e => toggleVisibility(column, e.detail)}
|
||||||
|
/>
|
||||||
|
<span>{column.name}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="buttons">
|
||||||
|
<ActionButton on:click={showAll}>Show all</ActionButton>
|
||||||
|
<ActionButton on:click={hideAll}>Hide all</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
padding: 12px 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.columns {
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { ActionButton, Popover, Select } from "@budibase/bbui"
|
import { ActionButton, Popover, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
const { sort, columns, stickyColumn } = getContext("sheet")
|
const { sort, visibleColumns, stickyColumn } = getContext("sheet")
|
||||||
const orderOptions = [
|
const orderOptions = [
|
||||||
{ label: "A-Z", value: "ascending" },
|
{ label: "A-Z", value: "ascending" },
|
||||||
{ label: "Z-A", value: "descending" },
|
{ label: "Z-A", value: "descending" },
|
||||||
|
@ -11,7 +11,8 @@
|
||||||
let open = false
|
let open = false
|
||||||
let anchor
|
let anchor
|
||||||
|
|
||||||
$: columnOptions = getColumnOptions($stickyColumn, $columns)
|
$: columnOptions = getColumnOptions($stickyColumn, $visibleColumns)
|
||||||
|
$: checkValidSortColumn($sort.column, $stickyColumn, $visibleColumns)
|
||||||
|
|
||||||
const getColumnOptions = (stickyColumn, columns) => {
|
const getColumnOptions = (stickyColumn, columns) => {
|
||||||
let options = []
|
let options = []
|
||||||
|
@ -20,6 +21,29 @@
|
||||||
}
|
}
|
||||||
return [...options, ...columns.map(col => col.name)]
|
return [...options, ...columns.map(col => col.name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we never have a sort column selected that is not visible
|
||||||
|
const checkValidSortColumn = (sortColumn, stickyColumn, visibleColumns) => {
|
||||||
|
if (!sortColumn) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
sortColumn !== stickyColumn?.name &&
|
||||||
|
!visibleColumns.some(col => col.name === sortColumn)
|
||||||
|
) {
|
||||||
|
if (stickyColumn) {
|
||||||
|
sort.update(state => ({
|
||||||
|
...state,
|
||||||
|
column: stickyColumn.name,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
sort.update(state => ({
|
||||||
|
...state,
|
||||||
|
column: visibleColumns[0]?.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
<div bind:this={anchor}>
|
||||||
|
@ -28,7 +52,8 @@
|
||||||
quiet
|
quiet
|
||||||
size="M"
|
size="M"
|
||||||
on:click={() => (open = !open)}
|
on:click={() => (open = !open)}
|
||||||
selected={!!$sort.order}
|
selected={!!$sort.column}
|
||||||
|
disabled={!$visibleColumns.length}
|
||||||
>
|
>
|
||||||
Sort
|
Sort
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
|
@ -10,17 +10,23 @@ export const createColumnsStores = context => {
|
||||||
// automatically calculated
|
// automatically calculated
|
||||||
const enrichedColumns = derived(columns, $columns => {
|
const enrichedColumns = derived(columns, $columns => {
|
||||||
let offset = 0
|
let offset = 0
|
||||||
return $columns.map((column, idx) => {
|
return $columns.map(column => {
|
||||||
const enriched = {
|
const enriched = {
|
||||||
...column,
|
...column,
|
||||||
idx,
|
|
||||||
left: offset,
|
left: offset,
|
||||||
}
|
}
|
||||||
offset += column.width
|
if (column.visible) {
|
||||||
|
offset += column.width
|
||||||
|
}
|
||||||
return enriched
|
return enriched
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Derived list of columns which have not been explicitly hidden
|
||||||
|
const visibleColumns = derived(enrichedColumns, $columns => {
|
||||||
|
return $columns.filter(col => col.visible)
|
||||||
|
})
|
||||||
|
|
||||||
// Merge new schema fields with existing schema in order to preserve widths
|
// Merge new schema fields with existing schema in order to preserve widths
|
||||||
schema.subscribe($schema => {
|
schema.subscribe($schema => {
|
||||||
const currentColumns = get(columns)
|
const currentColumns = get(columns)
|
||||||
|
@ -45,6 +51,7 @@ export const createColumnsStores = context => {
|
||||||
name: field,
|
name: field,
|
||||||
width: existing?.width || defaultWidth,
|
width: existing?.width || defaultWidth,
|
||||||
schema: $schema[field],
|
schema: $schema[field],
|
||||||
|
visible: existing?.visible ?? true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -75,5 +82,6 @@ export const createColumnsStores = context => {
|
||||||
subscribe: enrichedColumns.subscribe,
|
subscribe: enrichedColumns.subscribe,
|
||||||
},
|
},
|
||||||
stickyColumn,
|
stickyColumn,
|
||||||
|
visibleColumns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,13 @@ export const createResizeStores = context => {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
initialMouseX: null,
|
initialMouseX: null,
|
||||||
initialWidth: null,
|
initialWidth: null,
|
||||||
|
column: null,
|
||||||
columnIdx: null,
|
columnIdx: null,
|
||||||
width: 0,
|
width: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
}
|
}
|
||||||
const resize = writable(initialState)
|
const resize = writable(initialState)
|
||||||
const isResizing = derived(resize, $resize => $resize.columnIdx != null)
|
const isResizing = derived(resize, $resize => $resize.column != null)
|
||||||
const MinColumnWidth = 100
|
const MinColumnWidth = 100
|
||||||
|
|
||||||
// Starts resizing a certain column
|
// Starts resizing a certain column
|
||||||
|
@ -18,12 +19,20 @@ export const createResizeStores = context => {
|
||||||
// Prevent propagation to stop reordering triggering
|
// Prevent propagation to stop reordering triggering
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
|
// Find and cache index
|
||||||
|
let columnIdx = get(columns).findIndex(col => col.name === column.name)
|
||||||
|
if (columnIdx === -1) {
|
||||||
|
columnIdx = "sticky"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial store state
|
||||||
resize.set({
|
resize.set({
|
||||||
width: column.width,
|
width: column.width,
|
||||||
left: column.left,
|
left: column.left,
|
||||||
initialWidth: column.width,
|
initialWidth: column.width,
|
||||||
initialMouseX: e.clientX,
|
initialMouseX: e.clientX,
|
||||||
columnIdx: column.idx,
|
column: column.name,
|
||||||
|
columnIdx,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add mouse event listeners to handle resizing
|
// Add mouse event listeners to handle resizing
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const createScrollStores = context => {
|
||||||
const width = derived(bounds, $bounds => $bounds.width, 0)
|
const width = derived(bounds, $bounds => $bounds.width, 0)
|
||||||
const contentHeight = derived(
|
const contentHeight = derived(
|
||||||
rows,
|
rows,
|
||||||
$rows => ($rows.length + 1) * cellHeight + padding,
|
$rows => $rows.length * cellHeight + padding,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
const maxScrollTop = derived(
|
const maxScrollTop = derived(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { derived, get } from "svelte/store"
|
import { derived, get } from "svelte/store"
|
||||||
|
|
||||||
export const createViewportStores = context => {
|
export const createViewportStores = context => {
|
||||||
const { cellHeight, columns, rows, scroll, bounds } = context
|
const { cellHeight, visibleColumns, rows, scroll, bounds } = context
|
||||||
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
const scrollTop = derived(scroll, $scroll => $scroll.top, 0)
|
||||||
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
|
const scrollLeft = derived(scroll, $scroll => $scroll.left, 0)
|
||||||
|
|
||||||
|
@ -15,53 +15,59 @@ export const createViewportStores = context => {
|
||||||
const firstRowIdx = derived(scrollTop, $scrollTop => {
|
const firstRowIdx = derived(scrollTop, $scrollTop => {
|
||||||
return Math.floor($scrollTop / cellHeight)
|
return Math.floor($scrollTop / cellHeight)
|
||||||
})
|
})
|
||||||
const visibleRowCount = derived(height, $height => {
|
const renderedRowCount = derived(height, $height => {
|
||||||
return Math.ceil($height / cellHeight)
|
return Math.ceil($height / cellHeight)
|
||||||
})
|
})
|
||||||
const visibleRows = derived(
|
const renderedRows = derived(
|
||||||
[rows, firstRowIdx, visibleRowCount],
|
[rows, firstRowIdx, renderedRowCount],
|
||||||
([$rows, $firstRowIdx, $visibleRowCount]) => {
|
([$rows, $firstRowIdx, $visibleRowCount]) => {
|
||||||
return $rows.slice($firstRowIdx, $firstRowIdx + $visibleRowCount)
|
return $rows.slice($firstRowIdx, $firstRowIdx + $visibleRowCount)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Derive visible columns
|
// Derive visible columns
|
||||||
const visibleColumns = derived(
|
const renderedColumns = derived(
|
||||||
[columns, scrollLeft, width],
|
[visibleColumns, scrollLeft, width],
|
||||||
([$columns, $scrollLeft, $width]) => {
|
([$visibleColumns, $scrollLeft, $width]) => {
|
||||||
if (!$columns.length) {
|
if (!$visibleColumns.length) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let startColIdx = 0
|
let startColIdx = 0
|
||||||
let rightEdge = $columns[0].width
|
let rightEdge = $visibleColumns[0].width
|
||||||
while (rightEdge < $scrollLeft && startColIdx < $columns.length - 1) {
|
while (
|
||||||
|
rightEdge < $scrollLeft &&
|
||||||
|
startColIdx < $visibleColumns.length - 1
|
||||||
|
) {
|
||||||
startColIdx++
|
startColIdx++
|
||||||
rightEdge += $columns[startColIdx].width
|
rightEdge += $visibleColumns[startColIdx].width
|
||||||
}
|
}
|
||||||
let endColIdx = startColIdx + 1
|
let endColIdx = startColIdx + 1
|
||||||
let leftEdge = rightEdge
|
let leftEdge = rightEdge
|
||||||
while (leftEdge < $width + $scrollLeft && endColIdx < $columns.length) {
|
while (
|
||||||
leftEdge += $columns[endColIdx].width
|
leftEdge < $width + $scrollLeft &&
|
||||||
|
endColIdx < $visibleColumns.length
|
||||||
|
) {
|
||||||
|
leftEdge += $visibleColumns[endColIdx].width
|
||||||
endColIdx++
|
endColIdx++
|
||||||
}
|
}
|
||||||
const nextVisibleColumns = $columns.slice(startColIdx, endColIdx)
|
const nextRenderedColumns = $visibleColumns.slice(startColIdx, endColIdx)
|
||||||
|
|
||||||
// Cautiously shrink the number of rendered columns.
|
// Cautiously shrink the number of rendered columns.
|
||||||
// This is to avoid rapidly shrinking and growing the visible column count
|
// This is to avoid rapidly shrinking and growing the visible column count
|
||||||
// which results in remounting cells
|
// which results in remounting cells
|
||||||
const currentCount = get(visibleColumns).length
|
const currentCount = get(renderedColumns).length
|
||||||
if (currentCount === nextVisibleColumns.length + 1) {
|
if (currentCount === nextRenderedColumns.length + 1) {
|
||||||
return $columns.slice(startColIdx, endColIdx + 1)
|
return $visibleColumns.slice(startColIdx, endColIdx + 1)
|
||||||
} else {
|
} else {
|
||||||
return nextVisibleColumns
|
return nextRenderedColumns
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fetch next page when approaching end of data
|
// Fetch next page when approaching end of data
|
||||||
visibleRows.subscribe($visibleRows => {
|
renderedRows.subscribe($renderedRows => {
|
||||||
const lastVisible = $visibleRows[$visibleRows.length - 1]
|
const lastVisible = $renderedRows[$renderedRows.length - 1]
|
||||||
const $rows = get(rows)
|
const $rows = get(rows)
|
||||||
const lastRow = $rows[$rows.length - 1]
|
const lastRow = $rows[$rows.length - 1]
|
||||||
if (lastVisible && lastRow && lastVisible._id === lastRow._id) {
|
if (lastVisible && lastRow && lastVisible._id === lastRow._id) {
|
||||||
|
@ -69,5 +75,5 @@ export const createViewportStores = context => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { visibleRows, visibleColumns }
|
return { renderedRows, renderedColumns }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue