Fully integrates sheets with datasection and remove lots of old stuff

This commit is contained in:
Andrew Kingston 2023-03-07 11:40:32 +00:00
parent f516011182
commit ca92d520b3
17 changed files with 289 additions and 224 deletions

View File

@ -1,226 +1,52 @@
<script>
import { fade } from "svelte/transition"
import { tables } from "stores/backend"
import CreateRowButton from "./buttons/CreateRowButton.svelte"
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
import CreateViewButton from "./buttons/CreateViewButton.svelte"
import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte"
import ExportButton from "./buttons/ExportButton.svelte"
import ImportButton from "./buttons/ImportButton.svelte"
import EditRolesButton from "./buttons/EditRolesButton.svelte"
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
import TableFilterButton from "./buttons/TableFilterButton.svelte"
import Table from "./Table.svelte"
import { TableNames } from "constants"
import CreateEditRow from "./modals/CreateEditRow.svelte"
import {
Pagination,
Heading,
Body,
Modal,
Layout,
notifications,
} from "@budibase/bbui"
import { fetchData, Sheet } from "@budibase/frontend-core"
import { Sheet } from "@budibase/frontend-core"
import { API } from "api"
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
let createColumnModal
import SheetCreateColumnButton from "components/backend/DataTable/buttons/sheet/SheetCreateColumnButton.svelte"
import SheetCreateRowButton from "components/backend/DataTable/buttons/sheet/SheetCreateRowButton.svelte"
import SheetCreateViewButton from "components/backend/DataTable/buttons/sheet/SheetCreateViewButton.svelte"
import SheetImportButton from "components/backend/DataTable/buttons/sheet/SheetImportButton.svelte"
import SheetExportButton from "components/backend/DataTable/buttons/sheet/SheetExportButton.svelte"
import SheetFilterButton from "components/backend/DataTable/buttons/sheet/SheetFilterButton.svelte"
import SheetManageAccessButton from "components/backend/DataTable/buttons/sheet/SheetManageAccessButton.svelte"
import SheetRelationshipButton from "components/backend/DataTable/buttons/sheet/SheetRelationshipButton.svelte"
import SheetEditColumnModal from "components/backend/DataTable/modals/sheet/SheetEditColumnModal.svelte"
let hideAutocolumns = true
let filters
let hasRows = true
$: isUsersTable = $tables.selected?._id === TableNames.USERS
$: type = $tables.selected?.type
$: isInternal = type !== "external"
$: schema = $tables.selected?.schema
$: enrichedSchema = enrichSchema($tables.selected?.schema)
$: id = $tables.selected?._id
$: hasCols = checkHasCols(schema)
$: id, (filters = null)
let appliedFilter
let rawFilter
let appliedSort
let selectedRows = []
$: enrichedSchema,
() => {
appliedFilter = null
rawFilter = null
appliedSort = null
selectedRows = []
}
const enrichSchema = schema => {
let tempSchema = { ...schema }
tempSchema._id = {
type: "internal",
editable: false,
displayName: "ID",
autocolumn: true,
}
if (isInternal) {
tempSchema._rev = {
type: "internal",
editable: false,
displayName: "Revision",
autocolumn: true,
}
}
return tempSchema
}
const checkHasCols = schema => {
if (!schema || Object.keys(schema).length === 0) {
return false
}
let fields = Object.values(schema)
for (let field of fields) {
if (!field.autocolumn) {
return true
}
}
return false
}
// Fetch data whenever sorting option changes
const onSort = async e => {
const sort = {
sortColumn: e.detail.column,
sortOrder: e.detail.order,
}
appliedSort = { ...sort }
appliedSort.sortOrder = appliedSort.sortOrder.toLowerCase()
selectedRows = []
}
// Fetch data whenever filters change
const onFilter = e => {
filters = e.detail
appliedFilter = e.detail
}
$: isUsersTable = id === TableNames.USERS
$: isInternal = $tables.selected?.type !== "external"
</script>
<div class="wrapper">
<Sheet
tableId={$tables.selected?._id}
{API}
filter={filters}
on:add-column={createColumnModal.show}
>
<Sheet {API} tableId={id}>
<svelte:fragment slot="controls">
<CreateColumnButton
highlighted={!hasCols || !hasRows}
on:updatecolumns={null}
/>
<SheetCreateColumnButton />
{#if !isUsersTable}
<CreateRowButton
on:updaterows={null}
title="Create row"
modalContentComponent={CreateEditRow}
disabled={!hasCols}
highlighted={hasCols && !hasRows}
/>
<SheetCreateRowButton />
{/if}
{#if isInternal}
<CreateViewButton disabled={!hasCols || !hasRows} />
<SheetCreateViewButton />
{/if}
<ManageAccessButton resourceId={$tables.selected?._id} />
<SheetManageAccessButton />
{#if isUsersTable}
<EditRolesButton />
{/if}
{#if !isInternal}
<ExistingRelationshipButton
table={$tables.selected}
on:updatecolumns={null}
/>
<SheetRelationshipButton />
{/if}
<ImportButton
disabled={$tables.selected?._id === "ta_users"}
tableId={$tables.selected?._id}
on:importrows={null}
/>
<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}
{#if !isUsersTable}
<SheetImportButton />
{/if}
<SheetExportButton />
<SheetFilterButton />
<SheetEditColumnModal />
</svelte:fragment>
</Sheet>
</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 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>-->
<Modal bind:this={createColumnModal}>
<CreateEditColumn on:updatecolumns />
</Modal>
<style>
.wrapper {
flex: 1 1 auto;

View File

@ -6,6 +6,8 @@
export let disabled = false
let modal
export const show = () => modal?.show()
</script>
<ActionButton

View File

@ -7,15 +7,23 @@
export let table
const dispatch = createEventDispatcher()
$: datasource = findDatasource(table?._id)
$: plusTables = datasource?.plus
? Object.values(datasource?.entities || {})
: []
$: datasource = $datasources.list.find(
source => source._id === table?.sourceId
)
let modal
const findDatasource = tableId => {
return $datasources.list.find(datasource => {
return (
Object.values(datasource.entities || {}).find(entity => {
return entity._id === tableId
}) != null
)
})
}
async function saveRelationship() {
try {
// Create datasource
@ -28,7 +36,7 @@
}
</script>
{#if table.sourceId}
{#if datasource}
<div>
<ActionButton
icon="DataCorrelated"

View File

@ -0,0 +1,18 @@
<script>
import CreateColumnButton from "../CreateColumnButton.svelte"
import { getContext, onMount } from "svelte"
const { rows, columns, subscribe } = getContext("sheet")
let createColumnModal
$: highlighted = !$rows.length || !$columns.length
onMount(() => subscribe("add-column", createColumnModal.show))
</script>
<CreateColumnButton
{highlighted}
bind:this={createColumnModal}
on:updatecolumns={() => rows.actions.refreshSchema()}
/>

View File

@ -0,0 +1,17 @@
<script>
import CreateRowButton from "../CreateRowButton.svelte"
import CreateEditRow from "../../modals/CreateEditRow.svelte"
import { getContext } from "svelte"
const { rows, columns } = getContext("sheet")
$: hasCols = !!$columns.length
$: hasRows = !!$rows.length
</script>
<CreateRowButton
title="Create row"
modalContentComponent={CreateEditRow}
disabled={!hasCols}
highlighted={hasCols && !hasRows}
/>

View File

@ -0,0 +1,10 @@
<script>
import CreateViewButton from "../CreateViewButton.svelte"
import { getContext } from "svelte"
const { rows, columns } = getContext("sheet")
$: disabled = !$columns.length || !$rows.length
</script>
<CreateViewButton {disabled} />

View File

@ -0,0 +1,21 @@
<script>
import ExportButton from "../ExportButton.svelte"
import { getContext } from "svelte"
const { rows, columns, config, sort, selectedRows, filter } =
getContext("sheet")
$: disabled = !$rows.length || !$columns.length
$: selectedRowArray = Object.keys($selectedRows).map(id => ({ _id: id }))
</script>
<ExportButton
{disabled}
view={$config.tableId}
filters={$filter}
sorting={{
sortColumn: $sort.column,
sortOrder: $sort.order,
}}
selectedRows={selectedRowArray}
/>

View File

@ -0,0 +1,20 @@
<script>
import TableFilterButton from "../TableFilterButton.svelte"
import { getContext } from "svelte"
const { columns, config, filter, schema } = getContext("sheet")
const onFilter = e => {
filter.set(e.detail)
}
</script>
{#key $config.tableId}
<TableFilterButton
schema={$schema}
filters={$filter}
on:change={onFilter}
disabled={!$columns.length}
tableId={$config.tableId}
/>
{/key}

View File

@ -0,0 +1,12 @@
<script>
import ImportButton from "../ImportButton.svelte"
import { getContext } from "svelte"
const { rows, config } = getContext("sheet")
const refresh = () => {
rows.actions.refreshData()
}
</script>
<ImportButton tableId={$config.tableId} on:importrows={refresh} />

View File

@ -0,0 +1,8 @@
<script>
import ManageAccessButton from "../ManageAccessButton.svelte"
import { getContext } from "svelte"
const { config } = getContext("sheet")
</script>
<ManageAccessButton resourceId={$config.tableId} />

View File

@ -0,0 +1,13 @@
<script>
import ExistingRelationshipButton from "../ExistingRelationshipButton.svelte"
import { getContext } from "svelte"
const { table, rows } = getContext("sheet")
</script>
{#if $table}
<ExistingRelationshipButton
table={$table}
on:updatecolumns={() => rows.actions.refreshData()}
/>
{/if}

View File

@ -0,0 +1,29 @@
<script>
import { getContext, onMount } from "svelte"
import { Modal } from "@budibase/bbui"
import CreateEditColumn from "../CreateEditColumn.svelte"
const { rows, subscribe } = getContext("sheet")
let editableColumn
let editColumnModal
const updateColumns = () => {
rows.actions.refreshSchema()
}
const editColumn = column => {
editableColumn = column
editColumnModal.show()
}
onMount(() => subscribe("edit-column", editColumn))
</script>
<Modal bind:this={editColumnModal}>
<CreateEditColumn
field={editableColumn}
on:updatecolumns={updateColumns}
onClosed={editColumnModal.hide}
/>
</Modal>

View File

@ -1,6 +1,7 @@
<script>
import { setContext, createEventDispatcher, onMount } from "svelte"
import { setContext, onMount } from "svelte"
import { writable } from "svelte/store"
import { createEventManagers } from "./events"
import { createAPIClient } from "../../api"
import { createReorderStores } from "./stores/reorder"
import { createViewportStores } from "./stores/viewport"
@ -25,17 +26,14 @@
export let tableId
export let allowAddRows = true
export let allowSelectRows = true
export let filter
// Sheet constants
const cellHeight = 36
const rand = Math.random()
// State stores
const dispatch = createEventDispatcher()
const config = writable({
tableId,
filter,
allowAddRows,
allowSelectRows,
})
@ -47,8 +45,8 @@
rand,
cellHeight,
config,
dispatch,
}
context = { ...context, ...createEventManagers() }
context = { ...context, ...createRowsStore(context) }
context = { ...context, ...createColumnsStores(context) }
context = { ...context, ...createResizeStores(context) }
@ -65,7 +63,6 @@
// Keep config store up to date
$: config.set({
tableId,
filter,
allowAddRows,
allowSelectRows,
})
@ -73,6 +70,9 @@
// Set context for children to consume
setContext("sheet", context)
// Expose ability to retrieve context externally to allow sheet control
export const getContext = () => context
// Initialise websocket for multi-user
onMount(() => createWebsocket(context))
</script>

View File

@ -7,7 +7,7 @@
export let column
export let orderable = true
const { reorder, isReordering, isResizing, rand, sort, columns } =
const { reorder, isReordering, isResizing, rand, sort, columns, dispatch } =
getContext("sheet")
let anchor
@ -18,6 +18,11 @@
$: canMoveLeft = orderable && column.idx > 0
$: canMoveRight = orderable && column.idx < $columns.length - 1
const editColumn = () => {
dispatch("edit-column", column.schema)
open = false
}
const onMouseDown = e => {
if (e.button === 0 && orderable) {
timeout = setTimeout(() => {
@ -112,7 +117,7 @@
animate={false}
>
<Menu>
<MenuItem icon="Edit">Edit column</MenuItem>
<MenuItem icon="Edit" on:click={editColumn}>Edit column</MenuItem>
<MenuItem icon="SortOrderUp" on:click={sortAscending}>
Sort ascending
</MenuItem>
@ -125,7 +130,6 @@
<MenuItem disabled={!canMoveRight} icon="ArrowRight" on:click={moveRight}>
Move right
</MenuItem>
<MenuItem icon="Delete">Delete</MenuItem>
</Menu>
</Popover>

View File

@ -0,0 +1,29 @@
import { createEventDispatcher } from "svelte"
export const createEventManagers = () => {
const svelteDispatch = createEventDispatcher()
let subscribers = {}
// Dispatches a sheet event, notifying subscribers and also emitting a normal
// svelte event
const dispatch = (event, payload) => {
svelteDispatch(event, payload)
const subs = subscribers[event] || []
for (let i = 0; i < subs.length; i++) {
subs[i](payload)
}
}
// Subscribes to sheet events
const subscribe = (event, callback) => {
const subs = subscribers[event] || []
subscribers[event] = [...subs, callback]
// Return unsubscribe function
return () => {
subscribers[event] = subscribers[event].filter(cb => cb !== callback)
}
}
return { dispatch, subscribe }
}

View File

@ -6,10 +6,13 @@ import { notifications } from "@budibase/bbui"
export const createRowsStore = context => {
const { config, API } = context
const tableId = derived(config, $config => $config.tableId)
const filter = derived(config, $config => $config.filter)
const rows = writable([])
const schema = writable({})
const table = writable(null)
const filter = writable([])
const sort = writable({
column: null,
order: "ascending",
order: null,
})
// Flag for whether this is the first time loading our fetch
@ -18,9 +21,14 @@ export const createRowsStore = context => {
// Local cache of row IDs to speed up checking if a row exists
let rowCacheMap = {}
// Exported stores
const rows = writable([])
const schema = writable({})
// Reset everything when table ID changes
tableId.subscribe(() => {
filter.set([])
sort.set({
column: null,
order: null,
})
})
// Local stores for managing fetching data
const query = derived(filter, $filter => buildLuceneQuery($filter))
@ -60,16 +68,17 @@ export const createRowsStore = context => {
loaded = true
rowCacheMap = {}
rows.set([])
// Enrich primary display into schema
let newSchema = $$fetch.schema
const primaryDisplay = $$fetch.definition?.primaryDisplay
if (primaryDisplay && newSchema[primaryDisplay]) {
newSchema[primaryDisplay].primaryDisplay = true
}
schema.set(newSchema)
}
// Update schema and enrich primary display into schema
let newSchema = $$fetch.schema
const primaryDisplay = $$fetch.definition?.primaryDisplay
if (primaryDisplay && newSchema[primaryDisplay]) {
newSchema[primaryDisplay].primaryDisplay = true
}
schema.set(newSchema)
table.set($$fetch.definition)
// Process new rows
handleNewRows($$fetch.rows)
}
@ -143,6 +152,15 @@ export const createRowsStore = context => {
}
}
// Refreshes all data
const refreshData = () => {
filter.set([])
sort.set({
column: null,
order: null,
})
}
// Updates a value of a row
const updateRow = async (rowId, column, value) => {
const $rows = get(rows)
@ -233,6 +251,11 @@ export const createRowsStore = context => {
get(fetch)?.nextPage()
}
// Refreshes the schema of the data fetch subscription
const refreshSchema = async () => {
return await get(fetch)?.refreshDefinition()
}
return {
rows: {
...rows,
@ -242,9 +265,13 @@ export const createRowsStore = context => {
deleteRows,
loadNextPage,
refreshRow,
refreshData,
refreshSchema,
},
},
table,
schema,
sort,
filter,
}
}

View File

@ -368,6 +368,27 @@ export default class DataFetch {
}))
}
/**
* Refreshes the datasource's definition and schema
*/
async refreshDefinition() {
const { datasource } = this.options
// Fetch datasource definition and determine feature flags
const definition = await this.getDefinition(datasource)
// Fetch and enrich schema
let schema = this.getSchema(datasource, definition)
schema = this.enrichSchema(schema)
// Update state
this.store.update(state => ({
...state,
definition,
schema,
}))
}
/**
* Determines whether there is a next page of data based on the state of the
* store