Migrate sheet to data section, improve reordering and reszing
This commit is contained in:
parent
e9b801e205
commit
fae24276f9
|
@ -21,7 +21,7 @@
|
||||||
Layout,
|
Layout,
|
||||||
notifications,
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { fetchData } from "@budibase/frontend-core"
|
import { fetchData, Sheet } from "@budibase/frontend-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
let hideAutocolumns = true
|
let hideAutocolumns = true
|
||||||
|
@ -153,112 +153,122 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Table
|
<Sheet tableId={$tables.selected?._id} {API} />
|
||||||
title={$tables.selected?.name}
|
|
||||||
schema={enrichedSchema}
|
|
||||||
{type}
|
|
||||||
tableId={id}
|
|
||||||
data={$fetch.rows}
|
|
||||||
bind:hideAutocolumns
|
|
||||||
loading={!$fetch.loaded}
|
|
||||||
on:sort={onSort}
|
|
||||||
allowEditing
|
|
||||||
disableSorting
|
|
||||||
on:updatecolumns={onUpdateColumns}
|
|
||||||
on:updaterows={onUpdateRows}
|
|
||||||
on:selectionUpdated={e => {
|
|
||||||
selectedRows = e.detail
|
|
||||||
}}
|
|
||||||
customPlaceholder
|
|
||||||
>
|
|
||||||
<div class="buttons">
|
|
||||||
<div class="left-buttons">
|
|
||||||
<CreateColumnButton
|
|
||||||
highlighted={$fetch.loaded && (!hasCols || !hasRows)}
|
|
||||||
on:updatecolumns={onUpdateColumns}
|
|
||||||
/>
|
|
||||||
{#if !isUsersTable}
|
|
||||||
<CreateRowButton
|
|
||||||
on:updaterows={onUpdateRows}
|
|
||||||
title={"Create row"}
|
|
||||||
modalContentComponent={CreateEditRow}
|
|
||||||
disabled={!hasCols}
|
|
||||||
highlighted={$fetch.loaded && hasCols && !hasRows}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if isInternal}
|
|
||||||
<CreateViewButton disabled={!hasCols || !hasRows} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="right-buttons">
|
|
||||||
<ManageAccessButton resourceId={$tables.selected?._id} />
|
|
||||||
{#if isUsersTable}
|
|
||||||
<EditRolesButton />
|
|
||||||
{/if}
|
|
||||||
{#if !isInternal}
|
|
||||||
<ExistingRelationshipButton
|
|
||||||
table={$tables.selected}
|
|
||||||
on:updatecolumns={onUpdateColumns}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<HideAutocolumnButton bind:hideAutocolumns />
|
|
||||||
<ImportButton
|
|
||||||
disabled={$tables.selected?._id === "ta_users"}
|
|
||||||
tableId={$tables.selected?._id}
|
|
||||||
on:importrows={onImportData}
|
|
||||||
/>
|
|
||||||
<ExportButton
|
|
||||||
disabled={!hasRows || !hasCols}
|
|
||||||
view={$tables.selected?._id}
|
|
||||||
filters={appliedFilter}
|
|
||||||
sorting={appliedSort}
|
|
||||||
{selectedRows}
|
|
||||||
/>
|
|
||||||
{#key id}
|
|
||||||
<TableFilterButton
|
|
||||||
{schema}
|
|
||||||
{filters}
|
|
||||||
on:change={onFilter}
|
|
||||||
disabled={!hasCols}
|
|
||||||
tableId={id}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div slot="placeholder">
|
|
||||||
<Layout gap="S">
|
|
||||||
{#if !hasCols}
|
|
||||||
<Heading>Let's create some columns</Heading>
|
|
||||||
<Body>
|
|
||||||
Start building out your table structure<br />
|
|
||||||
by adding some columns
|
|
||||||
</Body>
|
|
||||||
{:else}
|
|
||||||
<Heading>Now let's add a row</Heading>
|
|
||||||
<Body>
|
|
||||||
Add some data to your table<br />
|
|
||||||
by adding some rows
|
|
||||||
</Body>
|
|
||||||
{/if}
|
|
||||||
</Layout>
|
|
||||||
</div>
|
|
||||||
</Table>
|
|
||||||
{#key id}
|
|
||||||
<div in:fade={{ delay: 200, duration: 100 }}>
|
|
||||||
<div class="pagination">
|
|
||||||
<Pagination
|
|
||||||
page={$fetch.pageNumber + 1}
|
|
||||||
hasPrevPage={$fetch.hasPrevPage}
|
|
||||||
hasNextPage={$fetch.hasNextPage}
|
|
||||||
goToPrevPage={$fetch.loading ? null : fetch.prevPage}
|
|
||||||
goToNextPage={$fetch.loading ? null : fetch.nextPage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/key}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--<div>-->
|
||||||
|
<!-- <Table-->
|
||||||
|
<!-- title={$tables.selected?.name}-->
|
||||||
|
<!-- schema={enrichedSchema}-->
|
||||||
|
<!-- {type}-->
|
||||||
|
<!-- tableId={id}-->
|
||||||
|
<!-- data={$fetch.rows}-->
|
||||||
|
<!-- bind:hideAutocolumns-->
|
||||||
|
<!-- loading={!$fetch.loaded}-->
|
||||||
|
<!-- on:sort={onSort}-->
|
||||||
|
<!-- allowEditing-->
|
||||||
|
<!-- disableSorting-->
|
||||||
|
<!-- on:updatecolumns={onUpdateColumns}-->
|
||||||
|
<!-- on:updaterows={onUpdateRows}-->
|
||||||
|
<!-- on:selectionUpdated={e => {-->
|
||||||
|
<!-- selectedRows = e.detail-->
|
||||||
|
<!-- }}-->
|
||||||
|
<!-- customPlaceholder-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <div class="buttons">-->
|
||||||
|
<!-- <div class="left-buttons">-->
|
||||||
|
<!-- <CreateColumnButton-->
|
||||||
|
<!-- highlighted={$fetch.loaded && (!hasCols || !hasRows)}-->
|
||||||
|
<!-- on:updatecolumns={onUpdateColumns}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- {#if !isUsersTable}-->
|
||||||
|
<!-- <CreateRowButton-->
|
||||||
|
<!-- on:updaterows={onUpdateRows}-->
|
||||||
|
<!-- title={"Create row"}-->
|
||||||
|
<!-- modalContentComponent={CreateEditRow}-->
|
||||||
|
<!-- disabled={!hasCols}-->
|
||||||
|
<!-- highlighted={$fetch.loaded && hasCols && !hasRows}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- {#if isInternal}-->
|
||||||
|
<!-- <CreateViewButton disabled={!hasCols || !hasRows} />-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="right-buttons">-->
|
||||||
|
<!-- <ManageAccessButton resourceId={$tables.selected?._id} />-->
|
||||||
|
<!-- {#if isUsersTable}-->
|
||||||
|
<!-- <EditRolesButton />-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- {#if !isInternal}-->
|
||||||
|
<!-- <ExistingRelationshipButton-->
|
||||||
|
<!-- table={$tables.selected}-->
|
||||||
|
<!-- on:updatecolumns={onUpdateColumns}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- <HideAutocolumnButton bind:hideAutocolumns />-->
|
||||||
|
<!-- <ImportButton-->
|
||||||
|
<!-- disabled={$tables.selected?._id === "ta_users"}-->
|
||||||
|
<!-- tableId={$tables.selected?._id}-->
|
||||||
|
<!-- on:importrows={onImportData}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- <ExportButton-->
|
||||||
|
<!-- disabled={!hasRows || !hasCols}-->
|
||||||
|
<!-- view={$tables.selected?._id}-->
|
||||||
|
<!-- filters={appliedFilter}-->
|
||||||
|
<!-- sorting={appliedSort}-->
|
||||||
|
<!-- {selectedRows}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- {#key id}-->
|
||||||
|
<!-- <TableFilterButton-->
|
||||||
|
<!-- {schema}-->
|
||||||
|
<!-- {filters}-->
|
||||||
|
<!-- on:change={onFilter}-->
|
||||||
|
<!-- disabled={!hasCols}-->
|
||||||
|
<!-- tableId={id}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- {/key}-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div slot="placeholder">-->
|
||||||
|
<!-- <Layout gap="S">-->
|
||||||
|
<!-- {#if !hasCols}-->
|
||||||
|
<!-- <Heading>Let's create some columns</Heading>-->
|
||||||
|
<!-- <Body>-->
|
||||||
|
<!-- Start building out your table structure<br />-->
|
||||||
|
<!-- by adding some columns-->
|
||||||
|
<!-- </Body>-->
|
||||||
|
<!-- {:else}-->
|
||||||
|
<!-- <Heading>Now let's add a row</Heading>-->
|
||||||
|
<!-- <Body>-->
|
||||||
|
<!-- Add some data to your table<br />-->
|
||||||
|
<!-- by adding some rows-->
|
||||||
|
<!-- </Body>-->
|
||||||
|
<!-- {/if}-->
|
||||||
|
<!-- </Layout>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </Table>-->
|
||||||
|
<!-- {#key id}-->
|
||||||
|
<!-- <div in:fade={{ delay: 200, duration: 100 }}>-->
|
||||||
|
<!-- <div class="pagination">-->
|
||||||
|
<!-- <Pagination-->
|
||||||
|
<!-- page={$fetch.pageNumber + 1}-->
|
||||||
|
<!-- hasPrevPage={$fetch.hasPrevPage}-->
|
||||||
|
<!-- hasNextPage={$fetch.hasNextPage}-->
|
||||||
|
<!-- goToPrevPage={$fetch.loading ? null : fetch.prevPage}-->
|
||||||
|
<!-- goToNextPage={$fetch.loading ? null : fetch.nextPage}-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- {/key}-->
|
||||||
|
|
||||||
|
<!--</div>-->
|
||||||
<style>
|
<style>
|
||||||
|
div {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
margin: -28px -40px -40px -40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
.pagination {
|
.pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
|
|
||||||
const { reorderPlaceholder } = getContext("spreadsheet")
|
|
||||||
|
|
||||||
$: style = getStyle($reorderPlaceholder)
|
|
||||||
|
|
||||||
const getStyle = state => {
|
|
||||||
return (
|
|
||||||
`--x:${state.x}px;` +
|
|
||||||
`--width:${state.width}px;` +
|
|
||||||
`--height:${state.height}px;`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $reorderPlaceholder.x != null}
|
|
||||||
<div {style} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
height: min(calc(100% - 36px), var(--height));
|
|
||||||
width: var(--width);
|
|
||||||
left: var(--x);
|
|
||||||
position: absolute;
|
|
||||||
top: 36px;
|
|
||||||
background: var(--spectrum-global-color-blue-400);
|
|
||||||
opacity: 0.2;
|
|
||||||
z-index: 7;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,36 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
|
|
||||||
export let columnIdx
|
|
||||||
|
|
||||||
const { resize } = getContext("spreadsheet")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div on:mousedown={e => resize.actions.startResizing(columnIdx, e)} />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 16px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
div:after {
|
|
||||||
opacity: 0;
|
|
||||||
content: " ";
|
|
||||||
position: absolute;
|
|
||||||
width: 4px;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--spectrum-global-color-gray-600);
|
|
||||||
transition: opacity 130ms ease-out;
|
|
||||||
}
|
|
||||||
div:hover {
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
div:hover:after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<script>
|
|
||||||
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
|
|
||||||
const { selectedCellId, hoveredRowId } = getContext("spreadsheet")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<SpreadsheetCell
|
|
||||||
{...$$props}
|
|
||||||
spacer
|
|
||||||
on:click={() => ($selectedCellId = null)}
|
|
||||||
on:mouseenter={() => ($hoveredRowId = null)}
|
|
||||||
/>
|
|
|
@ -1,108 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { ActionButton } from "@budibase/bbui"
|
|
||||||
|
|
||||||
const {
|
|
||||||
selectedRows,
|
|
||||||
rows,
|
|
||||||
selectedCellId,
|
|
||||||
hoveredRowId,
|
|
||||||
tableId,
|
|
||||||
spreadsheetAPI,
|
|
||||||
} = getContext("spreadsheet")
|
|
||||||
const { API, confirmationStore } = getContext("sdk")
|
|
||||||
|
|
||||||
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
|
||||||
$: rowCount = $rows.length
|
|
||||||
|
|
||||||
const deleteRows = () => {
|
|
||||||
// Fetch full row objects to be deleted
|
|
||||||
const rowsToDelete = Object.entries($selectedRows)
|
|
||||||
.map(entry => {
|
|
||||||
if (entry[1] === true) {
|
|
||||||
return $rows.find(x => x._id === entry[0])
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(x => x != null)
|
|
||||||
|
|
||||||
// Deletion callback when confirmed
|
|
||||||
const performDeletion = async () => {
|
|
||||||
await API.deleteRows({
|
|
||||||
tableId: $tableId,
|
|
||||||
rows: rowsToDelete,
|
|
||||||
})
|
|
||||||
await spreadsheetAPI.refreshData()
|
|
||||||
|
|
||||||
// Refresh state
|
|
||||||
$selectedCellId = null
|
|
||||||
$hoveredRowId = null
|
|
||||||
$selectedRows = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show confirmation
|
|
||||||
confirmationStore.actions.showConfirmation(
|
|
||||||
"Delete rows",
|
|
||||||
`Are you sure you want to delete ${selectedRowCount} row${
|
|
||||||
selectedRowCount === 1 ? "" : "s"
|
|
||||||
}?`,
|
|
||||||
performDeletion
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<div class="buttons">
|
|
||||||
<ActionButton icon="Filter" size="S">Filter</ActionButton>
|
|
||||||
<ActionButton icon="Group" size="S">Group</ActionButton>
|
|
||||||
<ActionButton icon="SortOrderDown" size="S">Sort</ActionButton>
|
|
||||||
<ActionButton icon="VisibilityOff" size="S">Hide fields</ActionButton>
|
|
||||||
</div>
|
|
||||||
<div class="title">Sales Records</div>
|
|
||||||
<div class="delete">
|
|
||||||
{#if selectedRowCount}
|
|
||||||
<ActionButton icon="Delete" size="S" on:click={deleteRows}>
|
|
||||||
Delete {selectedRowCount} row{selectedRowCount === 1 ? "" : "s"}
|
|
||||||
</ActionButton>
|
|
||||||
{:else}
|
|
||||||
{rowCount} row{rowCount === 1 ? "" : "s"}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.controls {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto 1fr;
|
|
||||||
align-items: center;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0 12px;
|
|
||||||
background: var(--spectrum-global-color-gray-200);
|
|
||||||
gap: 8px;
|
|
||||||
border-bottom: 1px solid var(--spectrum-global-color-gray-400);
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--cell-spacing);
|
|
||||||
}
|
|
||||||
.delete {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--spectrum-global-color-gray-700);
|
|
||||||
}
|
|
||||||
.delete :global(.spectrum-ActionButton) {
|
|
||||||
color: var(--spectrum-global-color-red-600);
|
|
||||||
}
|
|
||||||
.delete :global(.spectrum-Icon) {
|
|
||||||
fill: var(--spectrum-global-color-red-600);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,17 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
|
|
||||||
const { selectedCellId, hoveredRowId } = getContext("spreadsheet")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
on:click={() => ($selectedCellId = null)}
|
|
||||||
on:mouseenter={() => ($hoveredRowId = null)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
grid-column: 1/-1;
|
|
||||||
height: 180px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1 +0,0 @@
|
||||||
export { default as spreadsheet } from "./Spreadsheet.svelte"
|
|
|
@ -1,2 +1,3 @@
|
||||||
export { default as SplitPage } from "./SplitPage.svelte"
|
export { default as SplitPage } from "./SplitPage.svelte"
|
||||||
export { default as TestimonialPage } from "./TestimonialPage.svelte"
|
export { default as TestimonialPage } from "./TestimonialPage.svelte"
|
||||||
|
export { default as Sheet } from "./sheet/Sheet.svelte"
|
||||||
|
|
|
@ -1,26 +1,16 @@
|
||||||
import { writable, get } from "svelte/store"
|
<script>
|
||||||
import { domDebounce } from "../../../../utils/domDebounce.js"
|
import { get } from "svelte/store"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
const MinColumnWidth = 100
|
const { columns, rand, visibleColumns } = getContext("spreadsheet")
|
||||||
|
const MinColumnWidth = 100
|
||||||
|
|
||||||
export const createResizeStore = context => {
|
let initialMouseX = null
|
||||||
const { columns, rand } = context
|
let initialWidth = null
|
||||||
const initialState = {
|
let columnIdx = null
|
||||||
initialMouseX: null,
|
|
||||||
initialWidth: null,
|
|
||||||
columnIdx: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
let initialWidth = 0
|
|
||||||
let width = 0
|
let width = 0
|
||||||
let left = 0
|
let left = 0
|
||||||
let initialMouseX = null
|
|
||||||
let sheet
|
|
||||||
let columnIdx = 0
|
|
||||||
let columnCount = 0
|
let columnCount = 0
|
||||||
let styles
|
|
||||||
|
|
||||||
const resize = writable(initialState)
|
|
||||||
|
|
||||||
const startResizing = (idx, e) => {
|
const startResizing = (idx, e) => {
|
||||||
const $columns = get(columns)
|
const $columns = get(columns)
|
||||||
|
@ -37,6 +27,7 @@ export const createResizeStore = context => {
|
||||||
// Add mouse event listeners to handle resizing
|
// Add mouse event listeners to handle resizing
|
||||||
document.addEventListener("mousemove", onResizeMouseMove)
|
document.addEventListener("mousemove", onResizeMouseMove)
|
||||||
document.addEventListener("mouseup", stopResizing)
|
document.addEventListener("mouseup", stopResizing)
|
||||||
|
document.getElementById(`sheet-${rand}`).classList.add("is-resizing")
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResizeMouseMove = e => {
|
const onResizeMouseMove = e => {
|
||||||
|
@ -68,7 +59,6 @@ export const createResizeStore = context => {
|
||||||
//
|
//
|
||||||
// sheet.style.cssText += newStyle
|
// sheet.style.cssText += newStyle
|
||||||
|
|
||||||
|
|
||||||
// let cells = sheet.querySelectorAll(`[data-col="${columnIdx}"]`)
|
// let cells = sheet.querySelectorAll(`[data-col="${columnIdx}"]`)
|
||||||
// let left
|
// let left
|
||||||
// cells.forEach(cell => {
|
// cells.forEach(cell => {
|
||||||
|
@ -93,8 +83,6 @@ export const createResizeStore = context => {
|
||||||
// offset += colWidth
|
// offset += colWidth
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
width = newWidth
|
width = newWidth
|
||||||
|
|
||||||
// Update width of column
|
// Update width of column
|
||||||
|
@ -113,15 +101,53 @@ export const createResizeStore = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopResizing = () => {
|
const stopResizing = () => {
|
||||||
resize.set(initialState)
|
columnIdx = null
|
||||||
document.removeEventListener("mousemove", onResizeMouseMove)
|
document.removeEventListener("mousemove", onResizeMouseMove)
|
||||||
document.removeEventListener("mouseup", stopResizing)
|
document.removeEventListener("mouseup", stopResizing)
|
||||||
|
document.getElementById(`sheet-${rand}`).classList.remove("is-resizing")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each $columns as col}
|
||||||
|
<div
|
||||||
|
class="resize-slider"
|
||||||
|
class:visible={columnIdx === col.idx}
|
||||||
|
on:mousedown={e => startResizing(col.idx, e)}
|
||||||
|
style="--left:{col.left + col.width}px;"
|
||||||
|
>
|
||||||
|
<div class="resize-indicator" />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.resize-slider {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--controls-height);
|
||||||
|
z-index: 6;
|
||||||
|
height: var(--cell-height);
|
||||||
|
left: var(--left);
|
||||||
|
opacity: 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.resize-slider:hover,
|
||||||
|
.resize-slider.visible {
|
||||||
|
cursor: col-resize;
|
||||||
|
opacity: 1;
|
||||||
|
height: calc(100% - var(--controls-height));
|
||||||
|
}
|
||||||
|
.resize-indicator {
|
||||||
|
margin-left: -1px;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--spectrum-global-color-blue-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
:global(.sheet.is-resizing *) {
|
||||||
...resize,
|
cursor: col-resize !important;
|
||||||
actions: {
|
|
||||||
startResizing,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
:global(.sheet.is-reordering .resize-slider) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,27 +1,24 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext } from "svelte"
|
import { setContext } from "svelte"
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
import { fetchData, LuceneUtils } from "@budibase/frontend-core"
|
import { fetchData } from "../../fetch/fetchData"
|
||||||
|
import { LuceneUtils } from "../../utils"
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { createReorderStores } from "./stores/reorder"
|
import { createReorderStores } from "./stores/reorder"
|
||||||
import { createResizeStore } from "./stores/resize"
|
import SpreadsheetHeader from "./SheetHeader.svelte"
|
||||||
import ReorderPlaceholder from "./ReorderPlaceholder.svelte"
|
import SpreadsheetBody from "./SheetBody.svelte"
|
||||||
import ResizeSlider from "./ResizeSlider.svelte"
|
import SpreadsheetCell from "./SheetCell.svelte"
|
||||||
import SpreadsheetHeader from "./SpreadsheetHeader.svelte"
|
import SpreadsheetRow from "./SheetRow.svelte"
|
||||||
import SpreadsheetBody from "./SpreadsheetBody.svelte"
|
import ResizeOverlay from "./ResizeOverlay.svelte"
|
||||||
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
|
||||||
import SpreadsheetRow from "./SpreadsheetRow.svelte"
|
|
||||||
|
|
||||||
export let table
|
export let tableId
|
||||||
export let filter
|
export let filter
|
||||||
export let sortColumn
|
export let sortColumn
|
||||||
export let sortOrder
|
export let sortOrder
|
||||||
|
export let API
|
||||||
const { styleable, API } = getContext("sdk")
|
|
||||||
const component = getContext("component")
|
|
||||||
|
|
||||||
// Sheet constants
|
// Sheet constants
|
||||||
const cellHeight = 32
|
const cellHeight = 36
|
||||||
const limit = 100
|
const limit = 100
|
||||||
const defaultWidth = 160
|
const defaultWidth = 160
|
||||||
const rand = Math.random()
|
const rand = Math.random()
|
||||||
|
@ -32,7 +29,6 @@
|
||||||
const hoveredRowId = writable(null)
|
const hoveredRowId = writable(null)
|
||||||
const selectedCellId = writable(null)
|
const selectedCellId = writable(null)
|
||||||
const selectedRows = writable({})
|
const selectedRows = writable({})
|
||||||
const tableId = writable(table?.tableId)
|
|
||||||
const changeCache = writable({})
|
const changeCache = writable({})
|
||||||
const newRows = writable([])
|
const newRows = writable([])
|
||||||
const visibleRows = writable([0, 0])
|
const visibleRows = writable([0, 0])
|
||||||
|
@ -50,6 +46,7 @@
|
||||||
|
|
||||||
// Build up spreadsheet context and additional stores
|
// Build up spreadsheet context and additional stores
|
||||||
const context = {
|
const context = {
|
||||||
|
API,
|
||||||
rand,
|
rand,
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
|
@ -66,11 +63,9 @@
|
||||||
scroll,
|
scroll,
|
||||||
}
|
}
|
||||||
const { reorder, reorderPlaceholder } = createReorderStores(context)
|
const { reorder, reorderPlaceholder } = createReorderStores(context)
|
||||||
const resize = createResizeStore(context)
|
|
||||||
|
|
||||||
$: tableId.set(table?.tableId)
|
|
||||||
$: query = LuceneUtils.buildLuceneQuery(filter)
|
$: query = LuceneUtils.buildLuceneQuery(filter)
|
||||||
$: fetch = createFetch(table)
|
$: fetch = createFetch(tableId)
|
||||||
$: fetch.update({
|
$: fetch.update({
|
||||||
sortColumn,
|
sortColumn,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
|
@ -83,10 +78,13 @@
|
||||||
$: updateSortedRows($fetch, $newRows)
|
$: updateSortedRows($fetch, $newRows)
|
||||||
$: renderedRows = $rows.slice($visibleRows[0], $visibleRows[1])
|
$: renderedRows = $rows.slice($visibleRows[0], $visibleRows[1])
|
||||||
|
|
||||||
const createFetch = datasource => {
|
const createFetch = tableId => {
|
||||||
return fetchData({
|
return fetchData({
|
||||||
API,
|
API,
|
||||||
datasource,
|
datasource: {
|
||||||
|
type: "table",
|
||||||
|
tableId,
|
||||||
|
},
|
||||||
options: {
|
options: {
|
||||||
sortColumn,
|
sortColumn,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
|
@ -99,23 +97,34 @@
|
||||||
|
|
||||||
// Generates the column array the first time the schema loads
|
// Generates the column array the first time the schema loads
|
||||||
const generateColumns = ({ schema, definition }) => {
|
const generateColumns = ({ schema, definition }) => {
|
||||||
if (!$columns.length && schema) {
|
if (!schema) {
|
||||||
let fields = Object.keys(schema || {})
|
$columns = []
|
||||||
const primaryDisplay = definition?.primaryDisplay
|
return
|
||||||
if (primaryDisplay) {
|
|
||||||
fields = [primaryDisplay, ...fields.filter(x => x !== primaryDisplay)]
|
|
||||||
}
|
|
||||||
$columns = fields.map((field, idx) => {
|
|
||||||
return {
|
|
||||||
idx,
|
|
||||||
name: field,
|
|
||||||
width: defaultWidth,
|
|
||||||
left: 40 + idx * defaultWidth,
|
|
||||||
schema: schema[field],
|
|
||||||
primaryDisplay: field === primaryDisplay,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
const currentColumns = $columns
|
||||||
|
|
||||||
|
// Get fields in new schema
|
||||||
|
let fields = Object.keys(schema || {})
|
||||||
|
const primaryDisplay = definition?.primaryDisplay
|
||||||
|
if (primaryDisplay) {
|
||||||
|
fields = [primaryDisplay, ...fields.filter(x => x !== primaryDisplay)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update columns, removing extraneous columns and adding missing ones
|
||||||
|
let offset = 40
|
||||||
|
$columns = fields.map((field, idx) => {
|
||||||
|
const existing = currentColumns.find(x => x.name === field)
|
||||||
|
const newCol = {
|
||||||
|
idx,
|
||||||
|
name: field,
|
||||||
|
width: existing?.width || defaultWidth,
|
||||||
|
left: offset,
|
||||||
|
schema: schema[field],
|
||||||
|
primaryDisplay: field === primaryDisplay,
|
||||||
|
}
|
||||||
|
offset += newCol.width
|
||||||
|
return newCol
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIconForField = field => {
|
const getIconForField = field => {
|
||||||
|
@ -174,7 +183,7 @@
|
||||||
|
|
||||||
const updateSortedRows = (unsortedRows, newRows) => {
|
const updateSortedRows = (unsortedRows, newRows) => {
|
||||||
let foo = unsortedRows.rows
|
let foo = unsortedRows.rows
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 0; i++) {
|
||||||
foo = foo.concat(foo.map(x => ({ ...x, _id: x._id + "x" })))
|
foo = foo.concat(foo.map(x => ({ ...x, _id: x._id + "x" })))
|
||||||
}
|
}
|
||||||
let sortedRows = foo.slice()
|
let sortedRows = foo.slice()
|
||||||
|
@ -197,110 +206,98 @@
|
||||||
...context,
|
...context,
|
||||||
reorder,
|
reorder,
|
||||||
reorderPlaceholder,
|
reorderPlaceholder,
|
||||||
resize,
|
|
||||||
spreadsheetAPI,
|
spreadsheetAPI,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div use:styleable={$component.styles}>
|
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
||||||
<div
|
<SpreadsheetHeader />
|
||||||
class="wrapper"
|
<SpreadsheetBody>
|
||||||
class:resize={$resize.columnIdx != null}
|
<div class="row">
|
||||||
style="--cell-height:{cellHeight}px;"
|
<!-- Field headers -->
|
||||||
id="sheet-{rand}"
|
<SpreadsheetCell header label on:click={selectAll} width="40" left="0">
|
||||||
>
|
<input
|
||||||
<SpreadsheetHeader />
|
type="checkbox"
|
||||||
<SpreadsheetBody>
|
checked={rowCount && selectedRowCount === rowCount}
|
||||||
<div class="row">
|
/>
|
||||||
<!-- Field headers -->
|
</SpreadsheetCell>
|
||||||
<SpreadsheetCell header label on:click={selectAll} width="40" left="0">
|
{#each $columns as field, fieldIdx}
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={rowCount && selectedRowCount === rowCount}
|
|
||||||
/>
|
|
||||||
</SpreadsheetCell>
|
|
||||||
{#each $columns as field, fieldIdx}
|
|
||||||
<SpreadsheetCell
|
|
||||||
header
|
|
||||||
sticky={fieldIdx === 0}
|
|
||||||
reorderSource={$reorder.columnIdx === fieldIdx}
|
|
||||||
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
|
||||||
on:mousedown={e => reorder.actions.startReordering(fieldIdx, e)}
|
|
||||||
width={field.width}
|
|
||||||
left={field.left}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
size="S"
|
|
||||||
name={getIconForField(field)}
|
|
||||||
color="var(--spectrum-global-color-gray-600)"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
{field.name}
|
|
||||||
</span>
|
|
||||||
<ResizeSlider columnIdx={fieldIdx} />
|
|
||||||
</SpreadsheetCell>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- All real rows -->
|
|
||||||
{#each renderedRows as row, rowIdx (row._id)}
|
|
||||||
<SpreadsheetRow {row} rowIdx={rowIdx + $visibleRows[0]} />
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<!-- New row placeholder -->
|
|
||||||
<div class="row new" style="--top:{($rows.length + 1) * cellHeight}px;">
|
|
||||||
<SpreadsheetCell
|
<SpreadsheetCell
|
||||||
label
|
header
|
||||||
on:click={addRow}
|
sticky={fieldIdx === 0}
|
||||||
on:mouseenter={() => ($hoveredRowId = "new")}
|
reorderSource={$reorder.columnIdx === fieldIdx}
|
||||||
rowHovered={$hoveredRowId === "new"}
|
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
||||||
width="40"
|
on:mousedown={e => reorder.actions.startReordering(fieldIdx, e)}
|
||||||
left="0"
|
width={field.width}
|
||||||
|
left={field.left}
|
||||||
>
|
>
|
||||||
<Icon hoverable name="Add" size="S" />
|
<Icon
|
||||||
</SpreadsheetCell>
|
size="S"
|
||||||
{#each $columns as field, fieldIdx}
|
name={getIconForField(field)}
|
||||||
<SpreadsheetCell
|
color="var(--spectrum-global-color-gray-600)"
|
||||||
sticky={fieldIdx === 0}
|
|
||||||
rowHovered={$hoveredRowId === "new"}
|
|
||||||
reorderSource={$reorder.columnIdx === fieldIdx}
|
|
||||||
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
|
||||||
on:click={() => addRow(field)}
|
|
||||||
on:mouseenter={() => ($hoveredRowId = "new")}
|
|
||||||
width={field.width}
|
|
||||||
left={field.left}
|
|
||||||
/>
|
/>
|
||||||
{/each}
|
<span>
|
||||||
</div>
|
{field.name}
|
||||||
</SpreadsheetBody>
|
</span>
|
||||||
|
</SpreadsheetCell>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Placeholder overlay for new column position -->
|
<!-- All real rows -->
|
||||||
<ReorderPlaceholder />
|
{#each renderedRows as row, rowIdx (row._id)}
|
||||||
</div>
|
<SpreadsheetRow {row} rowIdx={rowIdx + $visibleRows[0]} />
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<!-- New row placeholder -->
|
||||||
|
<div class="row new" style="--top:{($rows.length + 1) * cellHeight}px;">
|
||||||
|
<SpreadsheetCell
|
||||||
|
label
|
||||||
|
on:click={addRow}
|
||||||
|
on:mouseenter={() => ($hoveredRowId = "new")}
|
||||||
|
rowHovered={$hoveredRowId === "new"}
|
||||||
|
width="40"
|
||||||
|
left="0"
|
||||||
|
>
|
||||||
|
<Icon hoverable name="Add" size="S" />
|
||||||
|
</SpreadsheetCell>
|
||||||
|
{#each $columns as field, fieldIdx}
|
||||||
|
<SpreadsheetCell
|
||||||
|
sticky={fieldIdx === 0}
|
||||||
|
rowHovered={$hoveredRowId === "new"}
|
||||||
|
reorderSource={$reorder.columnIdx === fieldIdx}
|
||||||
|
reorderTarget={$reorder.swapColumnIdx === fieldIdx}
|
||||||
|
on:click={() => addRow(field)}
|
||||||
|
on:mouseenter={() => ($hoveredRowId = "new")}
|
||||||
|
width={field.width}
|
||||||
|
left={field.left}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</SpreadsheetBody>
|
||||||
|
<ResizeOverlay />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper {
|
.sheet {
|
||||||
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border: 1px solid var(--spectrum-global-color-gray-400);
|
|
||||||
border-radius: 4px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
/* Variables */
|
/* Variables */
|
||||||
--cell-background: var(--spectrum-global-color-gray-50);
|
--cell-background: var(--spectrum-global-color-gray-50);
|
||||||
--cell-background-hover: var(--spectrum-global-color-gray-100);
|
--cell-background-hover: var(--spectrum-global-color-gray-75);
|
||||||
--cell-padding: 8px;
|
--cell-padding: 10px;
|
||||||
--cell-spacing: 4px;
|
--cell-spacing: 4px;
|
||||||
--cell-font-size: 14px;
|
--cell-font-size: 14px;
|
||||||
|
--controls-height: 50px;
|
||||||
}
|
}
|
||||||
.wrapper.resize *:hover {
|
.sheet,
|
||||||
cursor: col-resize;
|
.sheet :global(*) {
|
||||||
}
|
box-sizing: border-box;
|
||||||
.wrapper::-webkit-scrollbar-track {
|
|
||||||
background: var(--cell-background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
@ -314,4 +311,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateY(var(--top));
|
transform: translateY(var(--top));
|
||||||
}
|
}
|
||||||
|
.row :global(> :last-child) {
|
||||||
|
border-right-width: 1px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { domDebounce } from "../../../utils/domDebounce"
|
import { Utils } from "../../utils"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const handleScroll = domDebounce(
|
const handleScroll = Utils.domDebounce(
|
||||||
({ left, top }) => {
|
({ left, top }) => {
|
||||||
// Only update local state when big changes occur
|
// Only update local state when big changes occur
|
||||||
if (Math.abs(top - scrollTop) > 100) {
|
if (Math.abs(top - scrollTop) > 100) {
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
class="spreadsheet"
|
class="sheet-body"
|
||||||
class:horizontally-scrolled={horizontallyScrolled}
|
class:horizontally-scrolled={horizontallyScrolled}
|
||||||
on:click|self={() => ($selectedCellId = null)}
|
on:click|self={() => ($selectedCellId = null)}
|
||||||
id={`sheet-${rand}-body`}
|
id={`sheet-${rand}-body`}
|
||||||
|
@ -131,12 +131,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.spreadsheet {
|
.sheet-body {
|
||||||
display: block;
|
display: block;
|
||||||
height: 800px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.sheet-body::-webkit-scrollbar-track {
|
||||||
|
background: var(--cell-background);
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
|
@ -10,10 +10,11 @@
|
||||||
export let reorderTarget = false
|
export let reorderTarget = false
|
||||||
export let left
|
export let left
|
||||||
export let width
|
export let width
|
||||||
|
export let column
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="cell"
|
class="cell col-{column}"
|
||||||
class:header
|
class:header
|
||||||
class:label
|
class:label
|
||||||
class:spacer
|
class:spacer
|
||||||
|
@ -37,7 +38,7 @@
|
||||||
.cell {
|
.cell {
|
||||||
height: var(--cell-height);
|
height: var(--cell-height);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(--spectrum-global-color-gray-300);
|
border-color: var(--spectrum-global-color-gray-200);
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-left-width: 1px;
|
border-left-width: 1px;
|
||||||
|
@ -67,16 +68,26 @@
|
||||||
.cell:hover {
|
.cell:hover {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.cell.row-selected {
|
.cell.row-selected:after {
|
||||||
background-color: rgb(224, 242, 255);
|
pointer-events: none;
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
opacity: 0.2;
|
||||||
|
background-color: var(--spectrum-global-color-blue-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header cells */
|
/* Header cells */
|
||||||
.cell.header {
|
.cell.header {
|
||||||
background: var(--spectrum-global-color-gray-200);
|
background: var(--background);
|
||||||
padding: 0 var(--cell-padding);
|
padding: 0 var(--cell-padding);
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
border-color: var(--spectrum-global-color-gray-400);
|
border-color: var(--spectrum-global-color-gray-200);
|
||||||
|
font-weight: 600;
|
||||||
|
gap: calc(2 * var(--cell-spacing));
|
||||||
}
|
}
|
||||||
.cell.header :global(span) {
|
.cell.header :global(span) {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
@ -90,7 +101,7 @@
|
||||||
.cell.sticky {
|
.cell.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-left-color: transparent;
|
border-left-width: 0;
|
||||||
transform: none;
|
transform: none;
|
||||||
left: 40px;
|
left: 40px;
|
||||||
}
|
}
|
||||||
|
@ -109,7 +120,16 @@
|
||||||
background: var(--spectrum-global-color-gray-200);
|
background: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
.cell.reorder-target {
|
.cell.reorder-target {
|
||||||
border-left-color: var(--spectrum-global-color-blue-400);
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.cell.reorder-target:before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
left: -2px;
|
||||||
|
background: var(--spectrum-global-color-blue-400);
|
||||||
|
width: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
height: calc(var(--cell-height) + 1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Label cells */
|
/* Label cells */
|
|
@ -0,0 +1,112 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { ActionButton, Modal, ModalContent } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedRows,
|
||||||
|
rows,
|
||||||
|
selectedCellId,
|
||||||
|
hoveredRowId,
|
||||||
|
tableId,
|
||||||
|
spreadsheetAPI,
|
||||||
|
API,
|
||||||
|
} = getContext("spreadsheet")
|
||||||
|
|
||||||
|
let modal
|
||||||
|
|
||||||
|
$: selectedRowCount = Object.values($selectedRows).filter(x => !!x).length
|
||||||
|
$: rowCount = $rows.length
|
||||||
|
$: rowsToDelete = Object.entries($selectedRows)
|
||||||
|
.map(entry => {
|
||||||
|
if (entry[1] === true) {
|
||||||
|
return $rows.find(x => x._id === entry[0])
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(x => x != null)
|
||||||
|
|
||||||
|
// Deletion callback when confirmed
|
||||||
|
const performDeletion = async () => {
|
||||||
|
await API.deleteRows({
|
||||||
|
tableId,
|
||||||
|
rows: rowsToDelete,
|
||||||
|
})
|
||||||
|
await spreadsheetAPI.refreshData()
|
||||||
|
|
||||||
|
// Refresh state
|
||||||
|
$selectedCellId = null
|
||||||
|
$hoveredRowId = null
|
||||||
|
$selectedRows = {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="buttons">
|
||||||
|
<ActionButton icon="Filter" quiet size="M">Filter</ActionButton>
|
||||||
|
<ActionButton icon="Group" quiet size="M">Group</ActionButton>
|
||||||
|
<ActionButton icon="SortOrderDown" quiet size="M">Sort</ActionButton>
|
||||||
|
<ActionButton icon="VisibilityOff" quiet size="M">Hide fields</ActionButton>
|
||||||
|
</div>
|
||||||
|
<div class="title" />
|
||||||
|
<div class="delete">
|
||||||
|
{#if selectedRowCount}
|
||||||
|
<ActionButton icon="Delete" size="S" on:click={modal.show}>
|
||||||
|
Delete {selectedRowCount} row{selectedRowCount === 1 ? "" : "s"}
|
||||||
|
</ActionButton>
|
||||||
|
{:else}
|
||||||
|
{rowCount} row{rowCount === 1 ? "" : "s"}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent
|
||||||
|
title="Add screens"
|
||||||
|
confirmText="Continue"
|
||||||
|
cancelText="Cancel"
|
||||||
|
onConfirm={performDeletion}
|
||||||
|
size="M"
|
||||||
|
>
|
||||||
|
Are you sure you want to delete {selectedRowCount}
|
||||||
|
row{selectedRowCount === 1 ? "" : "s"}?
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
height: var(--controls-height);
|
||||||
|
padding: 0 12px;
|
||||||
|
background: var(--background);
|
||||||
|
gap: 8px;
|
||||||
|
border-bottom: 2px solid var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(1 * var(--cell-spacing));
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
.delete {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--spectrum-global-color-gray-900);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.delete :global(.spectrum-ActionButton) {
|
||||||
|
color: var(--spectrum-global-color-red-600);
|
||||||
|
}
|
||||||
|
.delete :global(.spectrum-Icon) {
|
||||||
|
fill: var(--spectrum-global-color-red-600);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import SpreadsheetCell from "./SpreadsheetCell.svelte"
|
import SpreadsheetCell from "./SheetCell.svelte"
|
||||||
import OptionsCell from "./cells/OptionsCell.svelte"
|
import OptionsCell from "./cells/OptionsCell.svelte"
|
||||||
import DateCell from "./cells/DateCell.svelte"
|
import DateCell from "./cells/DateCell.svelte"
|
||||||
import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
import MultiSelectCell from "./cells/MultiSelectCell.svelte"
|
|
@ -165,10 +165,10 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.15);
|
max-height: calc(6 * var(--cell-height) - 1px);
|
||||||
max-height: 191px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
@ -180,12 +180,12 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--cell-spacing);
|
gap: var(--cell-spacing);
|
||||||
background-color: var(--cell-background);
|
background-color: var(--cell-background-hover);
|
||||||
}
|
}
|
||||||
.option:first-child {
|
.option:first-child {
|
||||||
flex: 0 0 calc(var(--cell-height) - 1px);
|
flex: 0 0 calc(var(--cell-height) - 1px);
|
||||||
}
|
}
|
||||||
.option:hover {
|
.option:hover {
|
||||||
background-color: var(--cell-background-hover);
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
import { get, writable } from "svelte/store"
|
import { get, writable } from "svelte/store"
|
||||||
|
|
||||||
export const createReorderStores = context => {
|
export const createReorderStores = context => {
|
||||||
const { columns, bounds, rows, scroll } = context
|
const { columns, bounds, rows, scroll, rand } = context
|
||||||
const reorderInitialState = {
|
const reorderInitialState = {
|
||||||
columnIdx: null,
|
columnIdx: null,
|
||||||
swapColumnIdx: null,
|
swapColumnIdx: null,
|
||||||
|
@ -61,6 +61,7 @@ export const createReorderStores = context => {
|
||||||
|
|
||||||
// Trigger a move event immediately so ensure a candidate column is chosen
|
// Trigger a move event immediately so ensure a candidate column is chosen
|
||||||
onReorderMouseMove(e)
|
onReorderMouseMove(e)
|
||||||
|
document.getElementById(`sheet-${rand}`).classList.add("is-reordering")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback when moving the mouse when reordering columns
|
// Callback when moving the mouse when reordering columns
|
||||||
|
@ -135,6 +136,7 @@ export const createReorderStores = context => {
|
||||||
// Remove event handlers
|
// Remove event handlers
|
||||||
document.removeEventListener("mousemove", onReorderMouseMove)
|
document.removeEventListener("mousemove", onReorderMouseMove)
|
||||||
document.removeEventListener("mouseup", stopReordering)
|
document.removeEventListener("mouseup", stopReordering)
|
||||||
|
document.getElementById(`sheet-${rand}`).classList.remove("is-reordering")
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
|
@ -86,3 +86,24 @@ export const throttle = (callback, minDelay = 1000) => {
|
||||||
}
|
}
|
||||||
return invoke
|
return invoke
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to debounce DOM activities using requestAnimationFrame
|
||||||
|
* @param callback the function to run
|
||||||
|
* @param extractParams
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export const domDebounce = (callback, extractParams = x => x) => {
|
||||||
|
let active = false
|
||||||
|
let lastParams
|
||||||
|
return (...params) => {
|
||||||
|
lastParams = extractParams(...params)
|
||||||
|
if (!active) {
|
||||||
|
active = true
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
callback(lastParams)
|
||||||
|
active = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue