226 lines
5.8 KiB
JavaScript
226 lines
5.8 KiB
JavaScript
import { get, writable, derived } from "svelte/store"
|
|
import { cloneDeep } from "lodash/fp"
|
|
import { API } from "api"
|
|
import { SWITCHABLE_TYPES, FIELDS } from "constants/backend"
|
|
|
|
export function createTablesStore() {
|
|
const store = writable({
|
|
list: [],
|
|
selectedTableId: null,
|
|
})
|
|
const derivedStore = derived(store, $store => ({
|
|
...$store,
|
|
selected: $store.list?.find(table => table._id === $store.selectedTableId),
|
|
}))
|
|
|
|
const fetch = async () => {
|
|
const tables = await API.getTables()
|
|
store.update(state => ({
|
|
...state,
|
|
list: tables,
|
|
}))
|
|
}
|
|
|
|
const singleFetch = async tableId => {
|
|
const table = await API.getTable(tableId)
|
|
store.update(state => {
|
|
const list = []
|
|
// update the list, keep order accurate
|
|
for (let tbl of state.list) {
|
|
if (table._id === tbl._id) {
|
|
list.push(table)
|
|
} else {
|
|
list.push(tbl)
|
|
}
|
|
}
|
|
state.list = list
|
|
return state
|
|
})
|
|
}
|
|
|
|
const select = tableId => {
|
|
store.update(state => ({
|
|
...state,
|
|
selectedTableId: tableId,
|
|
}))
|
|
}
|
|
|
|
const save = async table => {
|
|
const updatedTable = cloneDeep(table)
|
|
const oldTable = get(store).list.filter(t => t._id === table._id)[0]
|
|
|
|
const fieldNames = []
|
|
// Update any renamed schema keys to reflect their names
|
|
for (let key of Object.keys(updatedTable.schema)) {
|
|
// If field name has been seen before remove it
|
|
if (fieldNames.indexOf(key.toLowerCase()) !== -1) {
|
|
delete updatedTable.schema[key]
|
|
continue
|
|
}
|
|
const field = updatedTable.schema[key]
|
|
const oldField = oldTable?.schema[key]
|
|
// If the type has changed then revert back to the old field
|
|
if (
|
|
oldField != null &&
|
|
oldField?.type !== field.type &&
|
|
SWITCHABLE_TYPES.indexOf(oldField?.type) === -1
|
|
) {
|
|
updatedTable.schema[key] = oldField
|
|
}
|
|
// Field has been renamed
|
|
if (field.name && field.name !== key) {
|
|
updatedTable.schema[field.name] = field
|
|
updatedTable._rename = { old: key, updated: field.name }
|
|
delete updatedTable.schema[key]
|
|
}
|
|
// Finally record this field has been used
|
|
fieldNames.push(key.toLowerCase())
|
|
}
|
|
|
|
const savedTable = await API.saveTable(updatedTable)
|
|
replaceTable(savedTable._id, savedTable)
|
|
select(savedTable._id)
|
|
// make sure tables up to date (related)
|
|
let newTableIds = []
|
|
for (let column of Object.values(updatedTable?.schema || {})) {
|
|
if (column.type === FIELDS.LINK.type) {
|
|
newTableIds.push(column.tableId)
|
|
}
|
|
}
|
|
|
|
let oldTableIds = []
|
|
for (let column of Object.values(oldTable?.schema || {})) {
|
|
if (column.type === FIELDS.LINK.type) {
|
|
oldTableIds.push(column.tableId)
|
|
}
|
|
}
|
|
|
|
const tableIdsToFetch = [...new Set([...newTableIds, ...oldTableIds])]
|
|
// too many tables to fetch, just get all
|
|
if (tableIdsToFetch.length > 3) {
|
|
await fetch()
|
|
} else {
|
|
await Promise.all(tableIdsToFetch.map(id => singleFetch(id)))
|
|
}
|
|
return savedTable
|
|
}
|
|
|
|
const deleteTable = async table => {
|
|
if (!table?._id) {
|
|
return
|
|
}
|
|
await API.deleteTable({
|
|
tableId: table._id,
|
|
tableRev: table._rev || "rev",
|
|
})
|
|
replaceTable(table._id, null)
|
|
}
|
|
|
|
const saveField = async ({
|
|
originalName,
|
|
field,
|
|
primaryDisplay = false,
|
|
indexes,
|
|
}) => {
|
|
let draft = cloneDeep(get(derivedStore).selected)
|
|
|
|
// delete the original if renaming
|
|
// need to handle if the column had no name, empty string
|
|
if (originalName != null && originalName !== field.name) {
|
|
delete draft.schema[originalName]
|
|
draft._rename = {
|
|
old: originalName,
|
|
updated: field.name,
|
|
}
|
|
}
|
|
|
|
// Optionally set display column
|
|
if (primaryDisplay) {
|
|
draft.primaryDisplay = field.name
|
|
} else if (draft.primaryDisplay === originalName) {
|
|
const fields = Object.keys(draft.schema)
|
|
// pick another display column randomly if unselecting
|
|
draft.primaryDisplay = fields.filter(
|
|
name => name !== originalName || name !== field
|
|
)[0]
|
|
}
|
|
if (indexes) {
|
|
draft.indexes = indexes
|
|
}
|
|
draft.schema = {
|
|
...draft.schema,
|
|
[field.name]: cloneDeep(field),
|
|
}
|
|
|
|
await save(draft)
|
|
}
|
|
|
|
const deleteField = async field => {
|
|
let draft = cloneDeep(get(derivedStore).selected)
|
|
delete draft.schema[field.name]
|
|
await save(draft)
|
|
}
|
|
|
|
// Handles external updates of tables
|
|
const replaceTable = (tableId, table) => {
|
|
if (!tableId) {
|
|
return
|
|
}
|
|
|
|
// Handle deletion
|
|
if (!table) {
|
|
store.update(state => ({
|
|
...state,
|
|
list: state.list.filter(x => x._id !== tableId),
|
|
}))
|
|
return
|
|
}
|
|
|
|
// Add new table
|
|
const index = get(store).list.findIndex(x => x._id === table._id)
|
|
if (index === -1) {
|
|
store.update(state => ({
|
|
...state,
|
|
list: [...state.list, table],
|
|
}))
|
|
}
|
|
|
|
// Update existing table
|
|
else if (table) {
|
|
// This function has to merge state as there discrepancies with the table
|
|
// API endpoints. The table list endpoint and get table endpoint use the
|
|
// "type" property to mean different things.
|
|
store.update(state => {
|
|
state.list[index] = {
|
|
...table,
|
|
type: state.list[index].type,
|
|
}
|
|
return state
|
|
})
|
|
}
|
|
}
|
|
|
|
const removeDatasourceTables = datasourceId => {
|
|
store.update(state => ({
|
|
...state,
|
|
list: state.list.filter(table => table.sourceId !== datasourceId),
|
|
}))
|
|
}
|
|
|
|
return {
|
|
...store,
|
|
subscribe: derivedStore.subscribe,
|
|
fetch,
|
|
init: fetch,
|
|
select,
|
|
save,
|
|
delete: deleteTable,
|
|
saveField,
|
|
deleteField,
|
|
replaceTable,
|
|
removeDatasourceTables,
|
|
}
|
|
}
|
|
|
|
export const tables = createTablesStore()
|