WIP: split views into separate stores, fix datasource list, use unique data table for views V2

This commit is contained in:
Andrew Kingston 2023-07-26 13:23:31 +01:00
parent 25d993cb95
commit 150961fcf2
18 changed files with 206 additions and 101 deletions

View File

@ -26,6 +26,10 @@
$: id = $tables.selected?._id
$: isUsersTable = id === TableNames.USERS
$: isInternal = $tables.selected?.type !== "external"
$: datasource = {
type: "table",
tableId: id,
}
const handleGridTableUpdate = async e => {
tables.replaceTable(id, e.detail)
@ -43,7 +47,7 @@
<div class="wrapper">
<Grid
{API}
tableId={id}
{datasource}
allowAddRows={!isUsersTable}
allowDeleteRows={!isUsersTable}
schemaOverrides={isUsersTable ? userSchemaOverrides : null}

View File

@ -0,0 +1,50 @@
<script>
import { datasources, viewsV2 } from "stores/backend"
import { Grid } from "@budibase/frontend-core"
import { API } from "api"
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
import GridFilterButton from "components/backend/DataTable/buttons/grid/GridFilterButton.svelte"
import GridManageAccessButton from "components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte"
$: id = $viewsV2.selected?.id
$: datasource = {
type: "viewV2",
id,
tableId: id?.split("_").slice(0, -1).join("_"),
}
$: console.log(datasource)
const handleGridViewUpdate = async e => {
viewsV2.replace(id, e.detail)
}
</script>
<div class="wrapper">
<Grid
{API}
{datasource}
allowAddRows
allowDeleteRows
showAvatars={false}
on:updatedatasource={handleGridViewUpdate}
>
<svelte:fragment slot="filter">
<GridFilterButton />
</svelte:fragment>
<svelte:fragment slot="controls">
<GridManageAccessButton />
<GridCreateEditRowModal />
</svelte:fragment>
</Grid>
</div>
<style>
.wrapper {
flex: 1 1 auto;
margin: -28px -40px -40px -40px;
display: flex;
flex-direction: column;
background: var(--background);
overflow: hidden;
}
</style>

View File

@ -1,7 +1,14 @@
<script>
import { goto, isActive, params } from "@roxi/routify"
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
import { database, datasources, queries, tables, views } from "stores/backend"
import {
database,
datasources,
queries,
tables,
views,
viewsV2,
} from "stores/backend"
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
import NavItem from "components/common/NavItem.svelte"
@ -24,6 +31,7 @@
$tables,
$queries,
$views,
$viewsV2,
openDataSources
)
$: openDataSource = enrichedDataSources.find(x => x.open)
@ -41,6 +49,7 @@
tables,
queries,
views,
viewsV2,
openDataSources
) => {
if (!datasources?.list?.length) {
@ -57,7 +66,8 @@
isActive,
tables,
queries,
views
views,
viewsV2
)
const onlySource = datasources.list.length === 1
return {
@ -106,7 +116,8 @@
isActive,
tables,
queries,
views
views,
viewsV2
) => {
// Check for being on a datasource page
if (params.datasourceId === datasource._id) {
@ -152,10 +163,19 @@
// Check for a matching view
const selectedView = views.selected?.name
const table = options.find(table => {
const viewTable = options.find(table => {
return table.views?.[selectedView] != null
})
return table != null
if (viewTable) {
return true
}
// Check for a matching viewV2
const selectedViewV2 = viewsV2.selected?.name
const viewV2Table = options.find(table => {
return table.views?.[selectedViewV2] != null
})
return viewV2Table != null
}
</script>

View File

@ -1,5 +1,5 @@
<script>
import { tables, views, database } from "stores/backend"
import { tables, views, viewsV2, database } from "stores/backend"
import { TableNames } from "constants"
import EditTablePopover from "./popovers/EditTablePopover.svelte"
import EditViewPopover from "./popovers/EditViewPopover.svelte"
@ -38,11 +38,15 @@
{/if}
</NavItem>
{#each [...Object.entries(table.views || {})].sort() as [viewName, view], idx (idx)}
{@const viewSelected =
$isActive("./view") && $views.selected?.name === viewName}
{@const viewV2Selected =
$isActive("./view/v2") && $viewsV2.selected?.name === viewName}
<NavItem
indentLevel={2}
icon="Remove"
text={viewName}
selected={$isActive("./view") && $views.selected?.name === viewName}
selected={viewSelected || viewV2Selected}
on:click={() => {
if (view.version === 2) {
$goto(`./view/v2/${view.id}`)

View File

@ -51,7 +51,7 @@ export const syncURLToState = options => {
let cachedRedirect = get(routify.redirect)
let cachedPage = get(routify.page)
let previousParamsHash = null
let debug = false
let debug = true
const log = (...params) => debug && console.log(`[${urlParam}]`, ...params)
// Navigate to a certain URL

View File

@ -1,5 +1,5 @@
<script>
import TableDataTable from "components/backend/DataTable/DataTable.svelte"
import TableDataTable from "components/backend/DataTable/TableDataTable.svelte"
import { tables, database } from "stores/backend"
import { Banner } from "@budibase/bbui"

View File

@ -5,8 +5,8 @@
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: viewName = $views.selectedViewName
$: store.actions.websocket.selectResource(viewName)
$: name = $views.selectedViewName
$: store.actions.websocket.selectResource(name)
const stopSyncing = syncURLToState({
urlParam: "viewName",

View File

@ -1,23 +1,20 @@
<script>
import { views } from "stores/backend"
import { viewsV2 } from "stores/backend"
import { syncURLToState } from "helpers/urlStateSync"
import * as routify from "@roxi/routify"
import { onDestroy } from "svelte"
import { store } from "builderStore"
$: viewName = $views.selectedViewName
$: store.actions.websocket.selectResource(viewName)
$: id = $viewsV2.selectedViewId
$: store.actions.websocket.selectResource(id)
const stopSyncing = syncURLToState({
urlParam: "id",
stateKey: "selectedViewId",
validate: id => $views.list?.some(view => view.id === id),
update: id => {
const view = $views.list.find(v => v.id === id)
views.select(view.name)
},
validate: id => $viewsV2.list?.some(view => view.id === id),
update: viewsV2.select,
fallbackUrl: "../../",
store: views,
store: viewsV2,
routify,
decode: decodeURIComponent,
})

View File

@ -1,28 +1,5 @@
<script>
import { API } from "api"
import { Grid } from "@budibase/frontend-core"
import { views } from "stores/backend"
$: selectedView = $views.selected
import ViewV2DataTable from "components/backend/DataTable/ViewV2DataTable.svelte"
</script>
<div class="wrapper">
<Grid
{API}
tableId={selectedView?.id}
datasourceType="viewV2"
showAvatars={false}
showControls={false}
/>
</div>
<style>
.wrapper {
flex: 1 1 auto;
margin: -28px -40px -40px -40px;
display: flex;
flex-direction: column;
background: var(--background);
overflow: hidden;
}
</style>
<ViewV2DataTable />

View File

@ -1,6 +1,7 @@
export { database } from "./database"
export { tables } from "./tables"
export { views } from "./views"
export { viewsV2 } from "./viewsV2"
export { permissions } from "./permissions"
export { roles } from "./roles"
export { datasources, ImportTableError } from "./datasources"

View File

@ -9,7 +9,10 @@ export function createViewsStore() {
const derivedStore = derived([store, tables], ([$store, $tables]) => {
let list = []
$tables.list?.forEach(table => {
list = list.concat(Object.values(table?.views || {}))
const views = Object.values(table?.views || {}).filter(view => {
return view.version !== 2
})
list = list.concat(views)
})
return {
...$store,
@ -26,11 +29,7 @@ export function createViewsStore() {
}
const deleteView = async view => {
if (view.version === 2) {
await API.viewV2.delete(view.id)
} else {
await API.deleteView(view.name)
}
await API.deleteView(view.name)
// Update tables
tables.update(state => {
@ -40,20 +39,6 @@ export function createViewsStore() {
})
}
const create = async view => {
const savedViewResponse = await API.viewV2.create(view)
const savedView = savedViewResponse.data
// Update tables
tables.update(state => {
const table = state.list.find(table => table._id === view.tableId)
table.views[view.name] = savedView
return { ...state }
})
return savedView
}
const save = async view => {
const savedView = await API.saveView(view)
@ -74,7 +59,6 @@ export function createViewsStore() {
subscribe: derivedStore.subscribe,
select,
delete: deleteView,
create,
save,
}
}

View File

@ -0,0 +1,85 @@
import { writable, derived } from "svelte/store"
import { tables } from "./"
import { API } from "api"
export function createViewsV2Store() {
const store = writable({
selectedViewId: null,
})
const derivedStore = derived([store, tables], ([$store, $tables]) => {
let list = []
$tables.list?.forEach(table => {
const views = Object.values(table?.views || {}).filter(view => {
return view.version === 2
})
list = list.concat(views)
})
return {
...$store,
list,
selected: list.find(view => view.id === $store.selectedViewId),
}
})
const select = id => {
store.update(state => ({
...state,
selectedViewId: id,
}))
}
const deleteView = async view => {
await API.viewV2.delete(view.id)
// Update tables
tables.update(state => {
const table = state.list.find(table => table._id === view.tableId)
delete table.views[view.name]
return { ...state }
})
}
const create = async view => {
const savedViewResponse = await API.viewV2.create(view)
const savedView = savedViewResponse.data
// Update tables
tables.update(state => {
const table = state.list.find(table => table._id === view.tableId)
table.views[view.name] = savedView
return { ...state }
})
return savedView
}
const save = async view => {
// No dedicated save endpoint at this time
// const savedView = await API.saveView(view)
//
// // Update tables
// tables.update(state => {
// const table = state.list.find(table => table._id === view.tableId)
// if (table) {
// if (view.originalName) {
// delete table.views[view.originalName]
// }
// table.views[view.name] = savedView
// }
// return { ...state }
// })
}
const replace = (id, view) => {}
return {
subscribe: derivedStore.subscribe,
select,
delete: deleteView,
create,
save,
replace,
}
}
export const viewsV2 = createViewsV2Store()

View File

@ -28,8 +28,7 @@
} from "../lib/constants"
export let API = null
export let tableId = null
export let datasourceType = null
export let datasource = null
export let schemaOverrides = null
export let columnWhitelist = null
export let allowAddRows = true
@ -77,8 +76,7 @@
// Keep config store up to date with props
$: config.set({
tableId,
datasourceType,
datasource,
schemaOverrides,
columnWhitelist,
allowAddRows,

View File

@ -12,7 +12,7 @@
width,
config,
hasNonAutoColumn,
tableId,
datasource,
loading,
} = getContext("grid")
@ -33,7 +33,7 @@
</div>
</GridScrollWrapper>
{#if $config.allowSchemaChanges}
{#key $tableId}
{#key $datasource}
<TempTooltip
text="Click here to create your first column"
type={TooltipType.Info}

View File

@ -17,7 +17,7 @@
dispatch,
rows,
focusedCellAPI,
tableId,
datasource,
subscribe,
renderedRows,
renderedColumns,
@ -38,7 +38,7 @@
$: firstColumn = $stickyColumn || $renderedColumns[0]
$: width = GutterWidth + ($stickyColumn?.width || 0)
$: $tableId, (visible = false)
$: $datasource, (visible = false)
$: invertY = shouldInvertY(offset, $rowVerticalInversionIndex, $renderedRows)
$: selectedRowCount = Object.values($selectedRows).length
$: hasNoRows = !$rows.length

View File

@ -3,18 +3,18 @@ import { createWebsocket } from "../../../utils"
import { SocketEvent, GridSocketEvent } from "@budibase/shared-core"
export const createGridWebsocket = context => {
const { rows, tableId, users, focusedCellId, table, API } = context
const { rows, datasource, users, focusedCellId, table, API } = context
const socket = createWebsocket("/socket/grid")
const connectToTable = tableId => {
const connectToDatasource = datasource => {
if (!socket.connected) {
return
}
// Identify which table we are editing
const appId = API.getAppID()
socket.emit(
GridSocketEvent.SelectTable,
{ tableId, appId },
GridSocketEvent.SelectDatasource,
{ datasource, appId },
({ users: gridUsers }) => {
users.set(gridUsers)
}
@ -23,7 +23,7 @@ export const createGridWebsocket = context => {
// Built-in events
socket.on("connect", () => {
connectToTable(get(tableId))
connectToDatasource(get(datasource))
})
socket.on("connect_error", err => {
console.log("Failed to connect to grid websocket:", err.message)
@ -57,7 +57,7 @@ export const createGridWebsocket = context => {
})
// Change websocket connection when table changes
tableId.subscribe(connectToTable)
datasource.subscribe(connectToDatasource)
// Notify selected cell changes
focusedCellId.subscribe($focusedCellId => {

View File

@ -6,7 +6,7 @@ export const createStores = context => {
const getProp = prop => derivedMemo(config, $config => $config[prop])
// Derive and memoize some props so that we can react to them in isolation
const tableId = getProp("tableId")
const datasource = getProp("datasource")
const initialSortColumn = getProp("initialSortColumn")
const initialSortOrder = getProp("initialSortOrder")
const initialFilter = getProp("initialFilter")
@ -18,7 +18,7 @@ export const createStores = context => {
return {
config,
tableId,
datasource,
initialSortColumn,
initialSortOrder,
initialFilter,

View File

@ -59,7 +59,7 @@ export const deriveStores = context => {
filter,
loading,
sort,
tableId,
datasource,
API,
scroll,
validation,
@ -71,7 +71,7 @@ export const deriveStores = context => {
hasNextPage,
error,
notifications,
props,
config,
} = context
const instanceLoaded = writable(false)
const fetch = writable(null)
@ -95,7 +95,7 @@ export const deriveStores = context => {
// Reset everything when table ID changes
let unsubscribe = null
let lastResetKey = null
tableId.subscribe(async $tableId => {
datasource.subscribe(async $datasource => {
// Unsub from previous fetch if one exists
unsubscribe?.()
fetch.set(null)
@ -108,21 +108,6 @@ export const deriveStores = context => {
const $filter = get(filter)
const $sort = get(sort)
let datasource
if (props.datasourceType === "viewV2") {
const tableId = $tableId
datasource = {
type: props.datasourceType,
id: $tableId,
tableId: tableId.split("_").slice(0, -1).join("_"),
}
} else {
datasource = {
type: props.datasourceType,
tableId: $tableId,
}
}
// Create new fetch model
const newFetch = fetchData({
API,