Replace all manual API interaction with SDK
This commit is contained in:
parent
0f12f19619
commit
33e2ee427d
|
@ -11,7 +11,7 @@ let cache = {}
|
|||
* Makes a fully formatted URL based on the SDK configuration.
|
||||
*/
|
||||
const makeFullURL = path => {
|
||||
const { proto, domain, port } = get(configStore).config
|
||||
const { proto, domain, port } = get(configStore)
|
||||
let url = `/${path}`.replace("//", "/")
|
||||
return domain ? `${proto}://${domain}:${port}${url}` : url
|
||||
}
|
||||
|
@ -28,15 +28,17 @@ const handleError = error => {
|
|||
* Performs an API call to the server.
|
||||
* App ID header is always correctly set.
|
||||
*/
|
||||
const makeApiCall = async ({ method, url, body }) => {
|
||||
const makeApiCall = async ({ method, url, body, json = true }) => {
|
||||
try {
|
||||
const requestBody = json ? JSON.stringify(body) : body
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"x-budibase-app-id": getAppId(window.document.cookie),
|
||||
},
|
||||
body: body && JSON.stringify(body),
|
||||
body: requestBody,
|
||||
credentials: "same-origin",
|
||||
})
|
||||
switch (response.status) {
|
||||
|
@ -79,10 +81,11 @@ const makeCachedApiCall = async params => {
|
|||
/**
|
||||
* Constructs an API call function for a particular HTTP method.
|
||||
*/
|
||||
const requestApiCall = method => async ({ url, body, cache = false }) => {
|
||||
const requestApiCall = method => async params => {
|
||||
const { url, cache = false } = params
|
||||
const fullURL = makeFullURL(url)
|
||||
const params = { method, url: fullURL, body }
|
||||
return await (cache ? makeCachedApiCall : makeApiCall)(params)
|
||||
const enrichedParams = { ...params, method, url: fullURL }
|
||||
return await (cache ? makeCachedApiCall : makeApiCall)(enrichedParams)
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import api from "./api"
|
||||
|
||||
/**
|
||||
* Uploads an attachment to the server.
|
||||
*/
|
||||
export const uploadAttachment = async data => {
|
||||
return await api.post({
|
||||
url: "/api/attachments/upload",
|
||||
body: data,
|
||||
json: false,
|
||||
})
|
||||
}
|
|
@ -1,20 +1,21 @@
|
|||
import { fetchTableData, fetchTableDefinition } from "./tables"
|
||||
import { fetchTableData } from "./tables"
|
||||
import { fetchViewData } from "./views"
|
||||
import { fetchRelationshipData } from "./relationships"
|
||||
import { enrichRows } from "./rows"
|
||||
|
||||
/**
|
||||
* Fetches all rows for a particular Budibase data source.
|
||||
*/
|
||||
export const fetchDatasource = async datasource => {
|
||||
if (!datasource || !datasource.name) {
|
||||
if (!datasource || !datasource.type) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Fetch all rows in data source
|
||||
const { type, name, tableId } = datasource
|
||||
const { type, tableId } = datasource
|
||||
let rows = []
|
||||
if (type === "table") {
|
||||
rows = await fetchTableData(name)
|
||||
rows = await fetchTableData(tableId)
|
||||
} else if (type === "view") {
|
||||
rows = await fetchViewData(datasource)
|
||||
} else if (type === "link") {
|
||||
|
@ -22,37 +23,5 @@ export const fetchDatasource = async datasource => {
|
|||
}
|
||||
|
||||
// Enrich rows
|
||||
return await enrichDatasourceRows(rows, tableId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches data source rows which contain certain field types so that they can
|
||||
* be properly displayed.
|
||||
*/
|
||||
const enrichDatasourceRows = async (rows, tableId) => {
|
||||
if (rows && rows.length && tableId) {
|
||||
// Fetch table schema so we can check column types
|
||||
const tableDefinition = await fetchTableDefinition(tableId)
|
||||
const schema = tableDefinition && tableDefinition.schema
|
||||
if (schema) {
|
||||
const keys = Object.keys(schema)
|
||||
rows.forEach(row => {
|
||||
for (let key of keys) {
|
||||
const type = schema[key].type
|
||||
if (type === "link") {
|
||||
// Enrich row with the count of any relationship fields
|
||||
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
|
||||
} else if (type === "attachment") {
|
||||
// Enrich row with the first image URL for any attachment fields
|
||||
let url = null
|
||||
if (Array.isArray(row[key]) && row[key][0] != null) {
|
||||
url = row[key][0].url
|
||||
}
|
||||
row[`${key}_first`] = url
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return rows
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,7 @@
|
|||
export * from "./rows"
|
||||
export * from "./auth"
|
||||
export * from "./datasources"
|
||||
export * from "./tables"
|
||||
export * from "./attachments"
|
||||
export * from "./views"
|
||||
export * from "./relationships"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import api from "./api"
|
||||
import { enrichRows } from "./rows"
|
||||
|
||||
/**
|
||||
* Fetches related rows for a certain field of a certain row.
|
||||
|
@ -8,5 +9,6 @@ export const fetchRelationshipData = async ({ tableId, rowId, fieldName }) => {
|
|||
return []
|
||||
}
|
||||
const response = await api.get({ url: `/api/${tableId}/${rowId}/enrich` })
|
||||
return response[fieldName] || []
|
||||
const rows = response[fieldName] || []
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
import api from "./api"
|
||||
import { fetchTableDefinition } from "./tables"
|
||||
|
||||
/**
|
||||
* Fetches data about a certain row in a table.
|
||||
*/
|
||||
export const fetchRow = async ({ tableId, rowId }) => {
|
||||
const row = await api.get({
|
||||
url: `/api/${tableId}/rows/${rowId}`,
|
||||
})
|
||||
return await enrichRows([row], tableId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a row in a table.
|
||||
|
@ -31,6 +42,19 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes many rows from a table.
|
||||
*/
|
||||
export const deleteRows = async ({ tableId, rows }) => {
|
||||
return await api.post({
|
||||
url: `/api/${tableId}/rows`,
|
||||
body: {
|
||||
rows,
|
||||
type: "delete",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitises and parses column types when saving and updating rows.
|
||||
*/
|
||||
|
@ -68,3 +92,35 @@ const makeRowRequestBody = (parameters, state) => {
|
|||
|
||||
return body
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches rows which contain certain field types so that they can
|
||||
* be properly displayed.
|
||||
*/
|
||||
export const enrichRows = async (rows, tableId) => {
|
||||
if (rows && rows.length && tableId) {
|
||||
// Fetch table schema so we can check column types
|
||||
const tableDefinition = await fetchTableDefinition(tableId)
|
||||
const schema = tableDefinition && tableDefinition.schema
|
||||
if (schema) {
|
||||
const keys = Object.keys(schema)
|
||||
rows.forEach(row => {
|
||||
for (let key of keys) {
|
||||
const type = schema[key].type
|
||||
if (type === "link") {
|
||||
// Enrich row with the count of any relationship fields
|
||||
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
|
||||
} else if (type === "attachment") {
|
||||
// Enrich row with the first image URL for any attachment fields
|
||||
let url = null
|
||||
if (Array.isArray(row[key]) && row[key][0] != null) {
|
||||
url = row[key][0].url
|
||||
}
|
||||
row[`${key}_first`] = url
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import api from "./api"
|
||||
import { enrichRows } from "./rows"
|
||||
|
||||
/**
|
||||
* Fetches a table definition.
|
||||
|
@ -11,6 +12,7 @@ export const fetchTableDefinition = async tableId => {
|
|||
/**
|
||||
* Fetches all rows from a table.
|
||||
*/
|
||||
export const fetchTableData = async name => {
|
||||
return await api.get({ url: `/api/views/${name}` })
|
||||
export const fetchTableData = async tableId => {
|
||||
const rows = await api.get({ url: `/api/${tableId}/rows` })
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import api from "./api"
|
||||
import { enrichRows } from "./rows"
|
||||
|
||||
/**
|
||||
* Fetches all rows in a view.
|
||||
*/
|
||||
export const fetchViewData = async ({ name, field, groupBy, calculation }) => {
|
||||
export const fetchViewData = async ({
|
||||
name,
|
||||
field,
|
||||
groupBy,
|
||||
calculation,
|
||||
tableId,
|
||||
}) => {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (calculation) {
|
||||
|
@ -18,5 +25,6 @@ export const fetchViewData = async ({ name, field, groupBy, calculation }) => {
|
|||
? `/api/views/${name}?${params}`
|
||||
: `/api/views/${name}`
|
||||
|
||||
return await api.get({ url: QUERY_VIEW_URL })
|
||||
const rows = await api.get({ url: QUERY_VIEW_URL })
|
||||
return await enrichRows(rows, tableId)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { localStorageStore } from "../../../builder/src/builderStore/store/localStorage"
|
||||
import * as api from "../api"
|
||||
import { getAppId } from "../utils"
|
||||
|
||||
const initialState = {
|
||||
user: null,
|
||||
}
|
||||
const initialState = ""
|
||||
|
||||
export const createAuthStore = () => {
|
||||
const store = localStorageStore("bb-app-auth", initialState)
|
||||
const store = localStorageStore("budibase:token", initialState)
|
||||
|
||||
/**
|
||||
* Logs a user in.
|
||||
|
@ -14,18 +13,24 @@ export const createAuthStore = () => {
|
|||
const logIn = async ({ username, password }) => {
|
||||
const user = await api.logIn({ username, password })
|
||||
if (!user.error) {
|
||||
store.update(state => {
|
||||
state.user = user
|
||||
return state
|
||||
})
|
||||
store.set(user.token)
|
||||
}
|
||||
return !user.error
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a user out.
|
||||
*/
|
||||
const logOut = () => {
|
||||
store.update(() => initialState)
|
||||
store.set(initialState)
|
||||
|
||||
// Expire any cookies
|
||||
const appId = getAppId(window.document.cookie)
|
||||
if (appId) {
|
||||
for (let environment of ["local", "cloud"]) {
|
||||
window.document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.actions = {
|
||||
|
|
|
@ -26,7 +26,7 @@ export const createConfigStore = () => {
|
|||
* Rests the SDK configuration
|
||||
*/
|
||||
const reset = () => {
|
||||
store.update(() => initialState)
|
||||
store.set(initialState)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let _bb
|
||||
export let table
|
||||
export let layout = "list"
|
||||
|
||||
let headers = []
|
||||
let store = _bb.store
|
||||
|
||||
async function fetchData() {
|
||||
if (!table || !table.length) return
|
||||
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
|
||||
store.update(state => {
|
||||
state[table] = json
|
||||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Failed to fetch rows.", response)
|
||||
}
|
||||
}
|
||||
|
||||
$: data = $store[table] || []
|
||||
$: if (table) fetchData()
|
||||
|
||||
onMount(async () => {
|
||||
await fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<section class:grid={layout === 'grid'} class:list={layout === 'list'}>
|
||||
{#each data as data}
|
||||
<div class="data-card">
|
||||
<ul>
|
||||
{#each Object.keys(data) as key}
|
||||
<li>
|
||||
<span class="data-key">{key}:</span>
|
||||
<span class="data-value">{data[key]}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/each}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Inter;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 5px 0 5px 0;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.data-key {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +0,0 @@
|
|||
<script>
|
||||
export let _bb
|
||||
export let table
|
||||
|
||||
let searchValue = ""
|
||||
|
||||
function search() {
|
||||
const SEARCH_URL = _bb.api.get(``)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<input type="text" bind:value={searchValue} />
|
||||
<button on:click={search}>Search</button>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
<script>
|
||||
export let icon = ""
|
||||
export let fontSize = "1em"
|
||||
export let _bb
|
||||
|
||||
$: style = { fontSize }
|
||||
</script>
|
||||
|
||||
<i class={icon} {style} />
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { Label, Multiselect } from "@budibase/bbui"
|
||||
import api from "./api"
|
||||
import { fetchTableDefinition, fetchTableData } from "../../component-sdk"
|
||||
import { capitalise } from "./helpers"
|
||||
|
||||
export let schema = {}
|
||||
|
@ -16,22 +16,16 @@
|
|||
$: fetchRows(linkedTableId)
|
||||
$: fetchTable(linkedTableId)
|
||||
|
||||
async function fetchTable() {
|
||||
if (linkedTableId == null) {
|
||||
return
|
||||
async function fetchTable(id) {
|
||||
if (id != null) {
|
||||
linkedTable = await fetchTableDefinition(id)
|
||||
}
|
||||
const FETCH_TABLE_URL = `/api/tables/${linkedTableId}`
|
||||
const response = await api.get(FETCH_TABLE_URL)
|
||||
linkedTable = await response.json()
|
||||
}
|
||||
|
||||
async function fetchRows(linkedTableId) {
|
||||
if (linkedTableId == null) {
|
||||
return
|
||||
async function fetchRows(id) {
|
||||
if (id != null) {
|
||||
allRows = await fetchTableData(id)
|
||||
}
|
||||
const FETCH_ROWS_URL = `/api/${linkedTableId}/rows`
|
||||
const response = await api.get(FETCH_ROWS_URL)
|
||||
allRows = await response.json()
|
||||
}
|
||||
|
||||
function getPrettyName(row) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import fetchData from "./fetchData.js"
|
||||
import { fetchDatasource } from "../../component-sdk"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
||||
export let _bb
|
||||
|
@ -11,7 +11,7 @@
|
|||
|
||||
onMount(async () => {
|
||||
if (!isEmpty(datasource)) {
|
||||
const data = await fetchData(datasource, $store)
|
||||
const data = await fetchDatasource(datasource)
|
||||
_bb.attachChildren(target, {
|
||||
hydrate: false,
|
||||
context: data,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<script>
|
||||
import { authStore } from "../../component-sdk/src/store"
|
||||
|
||||
export let buttonText = "Log In"
|
||||
export let logo = ""
|
||||
export let title = ""
|
||||
export let buttonClass = ""
|
||||
export let inputClass = ""
|
||||
|
||||
export let _bb
|
||||
|
||||
let username = ""
|
||||
|
@ -21,19 +22,14 @@
|
|||
|
||||
const login = async () => {
|
||||
loading = true
|
||||
const response = await _bb.api.post("/api/authenticate", {
|
||||
username,
|
||||
password,
|
||||
})
|
||||
if (response.status === 200) {
|
||||
const json = await response.json()
|
||||
localStorage.setItem("budibase:token", json.token)
|
||||
// TODO: possibly do something with the user information in the response?
|
||||
|
||||
const success = await authStore.actions.logIn({ username, password })
|
||||
if (success) {
|
||||
location.reload()
|
||||
} else {
|
||||
loading = false
|
||||
error = true
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
<script>
|
||||
import { cssVars } from "./helpers"
|
||||
|
||||
export let navBarBackground = ""
|
||||
export let navBarBorder = ""
|
||||
export let navBarColor = ""
|
||||
export let selectedItemBackground = ""
|
||||
export let selectedItemColor = ""
|
||||
export let selectedItemBorder = ""
|
||||
export let itemHoverBackground = ""
|
||||
export let itemHoverColor = ""
|
||||
export let hideNavBar = false
|
||||
export let selectedItem = ""
|
||||
|
||||
export let _children
|
||||
export let _bb
|
||||
|
||||
let selectedIndex = -1
|
||||
let styleVars = {}
|
||||
let components = {}
|
||||
let componentElements = {}
|
||||
|
||||
const hasComponentElements = () =>
|
||||
Object.getOwnPropertyNames(componentElements).length > 0
|
||||
|
||||
$: {
|
||||
styleVars = {
|
||||
navBarBackground,
|
||||
navBarBorder,
|
||||
navBarColor,
|
||||
selectedItemBackground,
|
||||
selectedItemColor,
|
||||
selectedItemBorder,
|
||||
itemHoverBackground,
|
||||
itemHoverColor,
|
||||
}
|
||||
|
||||
if (_children && _children.length > 0 && hasComponentElements()) {
|
||||
const currentSelectedItem =
|
||||
selectedIndex > 0 ? _children[selectedIndex].title : ""
|
||||
if (selectedItem && currentSelectedItem !== selectedItem) {
|
||||
let i = 0
|
||||
for (let child of _children) {
|
||||
if (child.title === selectedItem) {
|
||||
onSelectItem(i)()
|
||||
}
|
||||
i++
|
||||
}
|
||||
} else if (!currentSelectedItem) {
|
||||
onSelectItem(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onSelectItem = index => () => {
|
||||
selectedIndex = index
|
||||
if (!components[index]) {
|
||||
const comp = _bb.attachChildren(componentElements[index])
|
||||
components[index] = comp
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="root" use:cssVars={styleVars}>
|
||||
{#if !hideNavBar}
|
||||
<div class="navbar">
|
||||
{#each _children as navItem, index}
|
||||
<div
|
||||
class="navitem"
|
||||
on:click={onSelectItem(index)}
|
||||
class:selected={selectedIndex === index}>
|
||||
{navItem.title}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#each _children as navItem, index}
|
||||
<div class="content" bind:this={componentElements[index]} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
grid-template-columns: [navbar] auto [content] 1fr;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
grid-column: navbar;
|
||||
background: var(--navBarBackground);
|
||||
border: var(--navBarBorder);
|
||||
color: var(--navBarColor);
|
||||
}
|
||||
|
||||
.navitem {
|
||||
padding: 10px 17px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navitem:hover {
|
||||
background: var(--itemHoverBackground);
|
||||
color: var(--itemHoverColor);
|
||||
}
|
||||
|
||||
.navitem.selected {
|
||||
background: var(--selectedItemBackground);
|
||||
border: var(--selectedItemBorder);
|
||||
color: var(--selectedItemColor);
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: content;
|
||||
}
|
||||
</style>
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import { authStore } from "../../component-sdk"
|
||||
|
||||
export let onLoad
|
||||
export let logoUrl
|
||||
export let _bb
|
||||
|
@ -18,14 +20,8 @@
|
|||
}
|
||||
|
||||
const logOut = () => {
|
||||
// TODO: not the best way to clear cookie, try to find better way
|
||||
const appId = location.pathname.split("/")[1]
|
||||
if (appId) {
|
||||
for (let environment of ["local", "cloud"]) {
|
||||
document.cookie = `budibase:${appId}:${environment}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;`
|
||||
}
|
||||
}
|
||||
location.href = `/${appId}`
|
||||
authStore.logOut()
|
||||
location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { fetchTableDefinition } from "../../component-sdk"
|
||||
|
||||
export let _bb
|
||||
export let table
|
||||
|
@ -12,15 +13,9 @@
|
|||
|
||||
let target
|
||||
|
||||
async function fetchTable(id) {
|
||||
const FETCH_TABLE_URL = `/api/tables/${id}`
|
||||
const response = await _bb.api.get(FETCH_TABLE_URL)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (table && typeof table === "string") {
|
||||
const tableObj = await fetchTable(table)
|
||||
const tableObj = await fetchTableDefinition(table)
|
||||
row.tableId = table
|
||||
row._table = tableObj
|
||||
_bb.attachChildren(target, {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import {
|
||||
fetchTableDefinition,
|
||||
fetchTableData,
|
||||
fetchRow,
|
||||
} from "../../component-sdk"
|
||||
|
||||
export let _bb
|
||||
export let table
|
||||
|
@ -8,63 +13,31 @@
|
|||
let store = _bb.store
|
||||
let target
|
||||
|
||||
async function fetchTable(id) {
|
||||
const FETCH_TABLE_URL = `/api/tables/${id}`
|
||||
const response = await _bb.api.get(FETCH_TABLE_URL)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async function fetchFirstRow() {
|
||||
const FETCH_ROWS_URL = `/api/views/all_${table}`
|
||||
const response = await _bb.api.get(FETCH_ROWS_URL)
|
||||
if (response.status === 200) {
|
||||
const allRows = await response.json()
|
||||
if (allRows.length > 0) return allRows[0]
|
||||
return { tableId: table }
|
||||
}
|
||||
const rows = await fetchTableData(table)
|
||||
return Array.isArray(rows) && rows.length ? rows[0] : { tableId: table }
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
const pathParts = window.location.pathname.split("/")
|
||||
|
||||
if (!table) {
|
||||
return
|
||||
}
|
||||
|
||||
const pathParts = window.location.pathname.split("/")
|
||||
const routeParamId = _bb.routeParams().id
|
||||
let row
|
||||
|
||||
// if srcdoc, then we assume this is the builder preview
|
||||
if (pathParts.length === 0 || pathParts[0] === "srcdoc") {
|
||||
if (table) row = await fetchFirstRow()
|
||||
} else if (_bb.routeParams().id) {
|
||||
const GET_ROW_URL = `/api/${table}/rows/${_bb.routeParams().id}`
|
||||
const response = await _bb.api.get(GET_ROW_URL)
|
||||
if (response.status === 200) {
|
||||
row = await response.json()
|
||||
if ((pathParts.length === 0 || pathParts[0] === "srcdoc") && table) {
|
||||
row = await fetchFirstRow()
|
||||
} else if (routeParamId) {
|
||||
row = await fetchRow({ tableId: table, rowId: routeParamId })
|
||||
} else {
|
||||
throw new Error("Failed to fetch row.", response)
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Row ID was not supplied to RowDetail")
|
||||
throw new Error("Row ID was not supplied to RowDetail")
|
||||
}
|
||||
|
||||
if (row) {
|
||||
// Fetch table schema so we can check for linked rows
|
||||
const tableObj = await fetchTable(row.tableId)
|
||||
for (let key of Object.keys(tableObj.schema)) {
|
||||
const type = tableObj.schema[key].type
|
||||
if (type === "link") {
|
||||
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
|
||||
} else if (type === "attachment") {
|
||||
let url = null
|
||||
if (Array.isArray(row[key]) && row[key][0] != null) {
|
||||
url = row[key][0].url
|
||||
}
|
||||
row[`${key}_first`] = url
|
||||
}
|
||||
}
|
||||
|
||||
row._table = tableObj
|
||||
|
||||
row._table = await fetchTableDefinition(row.tableId)
|
||||
_bb.attachChildren(target, {
|
||||
context: row,
|
||||
})
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<script>
|
||||
export let columns = []
|
||||
export let data = ""
|
||||
export let tableClass = ""
|
||||
export let theadClass = ""
|
||||
export let tbodyClass = ""
|
||||
export let trClass = ""
|
||||
export let thClass = ""
|
||||
export let onRowClick
|
||||
|
||||
export let _bb
|
||||
|
||||
const rowClickHandler = row => () => {
|
||||
// call currently only accepts one argument, so passing row does nothing
|
||||
// however, we do not expose this event anyway. I am leaving this
|
||||
// in for the future, as can and probably should hande this
|
||||
_bb.call("onRowClick", row)
|
||||
}
|
||||
|
||||
const cellValue = (colIndex, row) => {
|
||||
const val = _bb.getStateOrValue(_bb.props.columns[colIndex].value, row)
|
||||
return val
|
||||
}
|
||||
</script>
|
||||
|
||||
<table class={tableClass}>
|
||||
<thead class={theadClass}>
|
||||
<tr class={trClass}>
|
||||
{#each columns as col}
|
||||
<th class={thClass}>{col.title}</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class={tbodyClass}>
|
||||
{#if data}
|
||||
{#each data as row}
|
||||
<tr class={trClass} on:click={rowClickHandler(row)}>
|
||||
{#each columns as col, index}
|
||||
<th class={thClass}>{cellValue(index, row)}</th>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<style>
|
||||
.table-default {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
color: #212529;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table-default .thead-default .th-default {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-default .th-default {
|
||||
padding: 0.75rem;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dee2e6;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.th-default {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.table-default .tbody-default .tr-default:hover {
|
||||
color: #212529;
|
||||
background-color: rgba(0, 0, 0, 0.075);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -1,10 +0,0 @@
|
|||
<script>
|
||||
export let className = ""
|
||||
export let _bb
|
||||
|
||||
let thead
|
||||
|
||||
$: _bb.attachChildren(thead)
|
||||
</script>
|
||||
|
||||
<tbody bind:this={thead} class="className" />
|
|
@ -1,10 +0,0 @@
|
|||
<script>
|
||||
export let className = ""
|
||||
export let _bb
|
||||
|
||||
let thead
|
||||
|
||||
$: _bb.attachChildren(thead)
|
||||
</script>
|
||||
|
||||
<thead bind:this={thead} class="className" />
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { Dropzone } from "@budibase/bbui"
|
||||
import { uploadAttachment } from "../../../component-sdk"
|
||||
|
||||
const BYTES_IN_MB = 1000000
|
||||
|
||||
|
@ -17,16 +18,7 @@
|
|||
for (let i = 0; i < fileList.length; i++) {
|
||||
data.append("file", fileList[i])
|
||||
}
|
||||
|
||||
const response = await fetch("/api/attachments/upload", {
|
||||
method: "POST",
|
||||
body: data,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
return await uploadAttachment(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import fetchData, { fetchSchema } from "../fetchData"
|
||||
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
|
||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||
import ApexChart from "./ApexChart.svelte"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
import {
|
||||
closeColumn,
|
||||
dateColumn,
|
||||
highColumn,
|
||||
lowColumn,
|
||||
openColumn,
|
||||
} from "./CandleStickChart.svelte"
|
||||
|
||||
export let _bb
|
||||
export let title
|
||||
|
@ -41,8 +34,8 @@
|
|||
}
|
||||
|
||||
// Fetch, filter and sort data
|
||||
const schema = await fetchSchema(datasource.tableId)
|
||||
const result = await fetchData(datasource, $store)
|
||||
const schema = (await fetchTableDefinition(datasource.tableId)).schema
|
||||
const result = await fetchDatasource(datasource)
|
||||
const reducer = row => (valid, column) => valid && row[column] != null
|
||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||
const data = result
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import fetchData, { fetchSchema } from "../fetchData"
|
||||
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
|
||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||
import ApexChart from "./ApexChart.svelte"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
@ -32,8 +32,8 @@
|
|||
}
|
||||
|
||||
// Fetch, filter and sort data
|
||||
const schema = await fetchSchema(datasource.tableId)
|
||||
const result = await fetchData(datasource, $store)
|
||||
const schema = (await fetchTableDefinition(datasource.tableId)).schema
|
||||
const result = await fetchDatasource(datasource)
|
||||
const reducer = row => (valid, column) => valid && row[column] != null
|
||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||
const data = result
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import fetchData, { fetchSchema } from "../fetchData"
|
||||
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
|
||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||
import ApexChart from "./ApexChart.svelte"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
@ -40,8 +40,8 @@
|
|||
}
|
||||
|
||||
// Fetch, filter and sort data
|
||||
const schema = await fetchSchema(datasource.tableId)
|
||||
const result = await fetchData(datasource, $store)
|
||||
const schema = (await fetchTableDefinition(datasource.tableId)).schema
|
||||
const result = await fetchDatasource(datasource)
|
||||
const reducer = row => (valid, column) => valid && row[column] != null
|
||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||
const data = result
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import fetchData, { fetchSchema } from "../fetchData"
|
||||
import { fetchDatasource, fetchTableDefinition } from "../../../component-sdk"
|
||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||
import ApexChart from "./ApexChart.svelte"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
@ -30,8 +30,8 @@
|
|||
}
|
||||
|
||||
// Fetch, filter and sort data
|
||||
const schema = await fetchSchema(datasource.tableId)
|
||||
const result = await fetchData(datasource, $store)
|
||||
const schema = (await fetchTableDefinition(datasource.tableId)).schema
|
||||
const result = await fetchDatasource(datasource)
|
||||
const data = result
|
||||
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
|
||||
.slice(0, 20)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// These maps need to be set up to handle whatever types that are used in the tables.
|
||||
const setters = new Map([["number", number]])
|
||||
|
||||
import fetchData from "../fetchData.js"
|
||||
import * as SDK from "../../../component-sdk"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
|
@ -52,8 +52,8 @@
|
|||
|
||||
onMount(async () => {
|
||||
if (!isEmpty(datasource)) {
|
||||
data = await fetchData(datasource, $store)
|
||||
let schema = {}
|
||||
data = await SDK.fetchDatasource(datasource, $store)
|
||||
let schema
|
||||
|
||||
// Get schema for datasource
|
||||
// Views with "Calculate" applied provide their own schema.
|
||||
|
@ -61,9 +61,7 @@
|
|||
if (datasource.schema) {
|
||||
schema = datasource.schema
|
||||
} else {
|
||||
const jsonTable = await _bb.api.get(`/api/tables/${datasource.tableId}`)
|
||||
table = await jsonTable.json()
|
||||
schema = table.schema
|
||||
schema = (await SDK.fetchTableDefinition(datasource.tableId)).schema
|
||||
}
|
||||
|
||||
columnDefs = Object.keys(schema).map((key, i) => {
|
||||
|
@ -123,18 +121,12 @@
|
|||
}
|
||||
|
||||
const updateRow = async row => {
|
||||
const response = await _bb.api.patch(
|
||||
`/api/${row.tableId}/rows/${row._id}`,
|
||||
row
|
||||
)
|
||||
const json = await response.json()
|
||||
const schema = (await SDK.fetchTableDefinition(row.tableId)).schema
|
||||
await SDK.updateRow(schema, { data: row })
|
||||
}
|
||||
|
||||
const deleteRows = async () => {
|
||||
const response = await _bb.api.post(`/api/${datasource.name}/rows`, {
|
||||
rows: selectedRows,
|
||||
type: "delete",
|
||||
})
|
||||
await SDK.deleteRows({ rows: selectedRows, tableId: datasource.name })
|
||||
data = data.filter(row => !selectedRows.includes(row))
|
||||
selectedRows = []
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import api from "../../api"
|
||||
import { getTable } from "./tableCache"
|
||||
import {
|
||||
fetchTableDefinition,
|
||||
fetchRelationshipData,
|
||||
} from "../../../../component-sdk"
|
||||
|
||||
export let columnName
|
||||
export let row
|
||||
|
@ -16,7 +18,7 @@
|
|||
onMount(async () => {
|
||||
linkedRows = await fetchLinkedRowsData(row, columnName)
|
||||
if (linkedRows && linkedRows.length) {
|
||||
const table = await getTable(linkedRows[0].tableId)
|
||||
const table = await fetchTableDefinition(linkedRows[0].tableId)
|
||||
if (table && table.primaryDisplay) {
|
||||
displayColumn = table.primaryDisplay
|
||||
}
|
||||
|
@ -27,10 +29,11 @@
|
|||
if (!row || !row._id) {
|
||||
return []
|
||||
}
|
||||
const QUERY_URL = `/api/${row.tableId}/${row._id}/enrich`
|
||||
const response = await api.get(QUERY_URL)
|
||||
const enrichedRow = await response.json()
|
||||
return enrichedRow[columnName]
|
||||
return await fetchRelationshipData({
|
||||
tableId: row.tableId,
|
||||
rowId: row._id,
|
||||
fieldName: columnName,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import api from "../../api"
|
||||
|
||||
let cache = {}
|
||||
|
||||
async function fetchTable(id) {
|
||||
const FETCH_TABLE_URL = `/api/tables/${id}`
|
||||
const response = await api.get(FETCH_TABLE_URL)
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
export async function getTable(tableId) {
|
||||
if (!tableId) {
|
||||
return null
|
||||
}
|
||||
if (!cache[tableId]) {
|
||||
cache[tableId] = fetchTable(tableId)
|
||||
cache[tableId] = await cache[tableId]
|
||||
}
|
||||
return await cache[tableId]
|
||||
}
|
|
@ -13,9 +13,7 @@ export { default as Navigation } from "./Navigation.svelte"
|
|||
export { default as datagrid } from "./grid/Component.svelte"
|
||||
export { default as dataform } from "./DataForm.svelte"
|
||||
export { default as dataformwide } from "./DataFormWide.svelte"
|
||||
export { default as datalist } from "./DataList.svelte"
|
||||
export { default as list } from "./List.svelte"
|
||||
export { default as datasearch } from "./DataSearch.svelte"
|
||||
export { default as embed } from "./Embed.svelte"
|
||||
export { default as stackedlist } from "./StackedList.svelte"
|
||||
export { default as card } from "./Card.svelte"
|
||||
|
|
Loading…
Reference in New Issue