Merge pull request #1338 from Budibase/datasource-refactor
Datasource Refactor + Spectrum Table
This commit is contained in:
commit
6851f59eea
|
@ -20,7 +20,7 @@ context("Create Bindings", () => {
|
||||||
cy.get("[data-cy=setting-text] input")
|
cy.get("[data-cy=setting-text] input")
|
||||||
.type("{{}{{}{{} Current User._id {}}{}}")
|
.type("{{}{{}{{} Current User._id {}}{}}")
|
||||||
.blur()
|
.blur()
|
||||||
cy.getComponent(componentId).should("have.text", "{{{ user._id }}")
|
cy.getComponent(componentId).should("have.text", "{{{ [user].[_id] }}")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ context("Create Components", () => {
|
||||||
it("should create a form and reset to match schema", () => {
|
it("should create a form and reset to match schema", () => {
|
||||||
cy.addComponent("Form", "Form").then(() => {
|
cy.addComponent("Form", "Form").then(() => {
|
||||||
cy.get("[data-cy=Settings]").click()
|
cy.get("[data-cy=Settings]").click()
|
||||||
cy.get("[data-cy=setting-datasource]")
|
cy.get("[data-cy=setting-dataSource]")
|
||||||
.contains("Choose option")
|
.contains("Choose option")
|
||||||
.click()
|
.click()
|
||||||
cy.get(".dropdown")
|
cy.get(".dropdown")
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
import { findComponent, findComponentPath } from "./storeUtils"
|
||||||
import { store } from "builderStore"
|
import { store } from "builderStore"
|
||||||
import {
|
import {
|
||||||
tables as tablesStore,
|
tables as tablesStore,
|
||||||
queries as queriesStores,
|
queries as queriesStores,
|
||||||
} from "stores/backend/"
|
} from "stores/backend/"
|
||||||
import { findComponentPath } from "./storeUtils"
|
|
||||||
import { makePropSafe } from "@budibase/string-templates"
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
import { TableNames } from "../constants"
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export const getDataProviderComponents = (asset, componentId) => {
|
||||||
// Filter by only data provider components
|
// Filter by only data provider components
|
||||||
return path.filter(component => {
|
return path.filter(component => {
|
||||||
const def = store.actions.components.getDefinition(component._component)
|
const def = store.actions.components.getDefinition(component._component)
|
||||||
return def?.dataProvider
|
return def?.context != null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,14 +66,25 @@ export const getActionProviderComponents = (asset, componentId, actionType) => {
|
||||||
/**
|
/**
|
||||||
* Gets a datasource object for a certain data provider component
|
* Gets a datasource object for a certain data provider component
|
||||||
*/
|
*/
|
||||||
export const getDatasourceForProvider = component => {
|
export const getDatasourceForProvider = (asset, component) => {
|
||||||
const def = store.actions.components.getDefinition(component?._component)
|
const def = store.actions.components.getDefinition(component?._component)
|
||||||
if (!def) {
|
if (!def) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this component has a dataProvider setting, go up the stack and use it
|
||||||
|
const dataProviderSetting = def.settings.find(setting => {
|
||||||
|
return setting.type === "dataProvider"
|
||||||
|
})
|
||||||
|
if (dataProviderSetting) {
|
||||||
|
const settingValue = component[dataProviderSetting.key]
|
||||||
|
const providerId = extractLiteralHandlebarsID(settingValue)
|
||||||
|
const provider = findComponent(asset.props, providerId)
|
||||||
|
return getDatasourceForProvider(asset, provider)
|
||||||
|
}
|
||||||
|
|
||||||
// Extract datasource from component instance
|
// Extract datasource from component instance
|
||||||
const validSettingTypes = ["datasource", "table", "schema"]
|
const validSettingTypes = ["dataSource", "table", "schema"]
|
||||||
const datasourceSetting = def.settings.find(setting => {
|
const datasourceSetting = def.settings.find(setting => {
|
||||||
return validSettingTypes.includes(setting.type)
|
return validSettingTypes.includes(setting.type)
|
||||||
})
|
})
|
||||||
|
@ -105,53 +116,68 @@ const getContextBindings = (asset, componentId) => {
|
||||||
|
|
||||||
// Create bindings for each data provider
|
// Create bindings for each data provider
|
||||||
dataProviders.forEach(component => {
|
dataProviders.forEach(component => {
|
||||||
const isForm = component._component.endsWith("/form")
|
const def = store.actions.components.getDefinition(component._component)
|
||||||
const datasource = getDatasourceForProvider(component)
|
const contextDefinition = def.context
|
||||||
let tableName, schema
|
let schema
|
||||||
|
let readablePrefix
|
||||||
|
|
||||||
// Forms are an edge case which do not need table schemas
|
if (contextDefinition.type === "form") {
|
||||||
if (isForm) {
|
// Forms do not need table schemas
|
||||||
|
// Their schemas are built from their component field names
|
||||||
schema = buildFormSchema(component)
|
schema = buildFormSchema(component)
|
||||||
tableName = "Fields"
|
readablePrefix = "Fields"
|
||||||
} else {
|
} else if (contextDefinition.type === "static") {
|
||||||
|
// Static contexts are fully defined by the components
|
||||||
|
schema = {}
|
||||||
|
const values = contextDefinition.values || []
|
||||||
|
values.forEach(value => {
|
||||||
|
schema[value.key] = { name: value.label, type: "string" }
|
||||||
|
})
|
||||||
|
} else if (contextDefinition.type === "schema") {
|
||||||
|
// Schema contexts are generated dynamically depending on their data
|
||||||
|
const datasource = getDatasourceForProvider(asset, component)
|
||||||
if (!datasource) {
|
if (!datasource) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const info = getSchemaForDatasource(datasource)
|
||||||
// Get schema and table for the datasource
|
|
||||||
const info = getSchemaForDatasource(datasource, isForm)
|
|
||||||
schema = info.schema
|
schema = info.schema
|
||||||
tableName = info.table?.name
|
readablePrefix = info.table?.name
|
||||||
|
|
||||||
// Add _id and _rev fields for certain types
|
|
||||||
if (schema && ["table", "link"].includes(datasource.type)) {
|
|
||||||
schema["_id"] = { type: "string" }
|
|
||||||
schema["_rev"] = { type: "string" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!schema || !tableName) {
|
if (!schema) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = Object.keys(schema).sort()
|
const keys = Object.keys(schema).sort()
|
||||||
|
|
||||||
// Create bindable properties for each schema field
|
// Create bindable properties for each schema field
|
||||||
|
const safeComponentId = makePropSafe(component._id)
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
// Replace certain bindings with a new property to help display components
|
|
||||||
|
// Make safe runtime binding and replace certain bindings with a
|
||||||
|
// new property to help display components
|
||||||
let runtimeBoundKey = key
|
let runtimeBoundKey = key
|
||||||
if (fieldSchema.type === "link") {
|
if (fieldSchema.type === "link") {
|
||||||
runtimeBoundKey = `${key}_text`
|
runtimeBoundKey = `${key}_text`
|
||||||
} else if (fieldSchema.type === "attachment") {
|
} else if (fieldSchema.type === "attachment") {
|
||||||
runtimeBoundKey = `${key}_first`
|
runtimeBoundKey = `${key}_first`
|
||||||
}
|
}
|
||||||
|
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
||||||
|
runtimeBoundKey
|
||||||
|
)}`
|
||||||
|
|
||||||
|
// Optionally use a prefix with readable bindings
|
||||||
|
let readableBinding = component._instanceName
|
||||||
|
if (readablePrefix) {
|
||||||
|
readableBinding += `.${readablePrefix}`
|
||||||
|
}
|
||||||
|
readableBinding += `.${fieldSchema.name || key}`
|
||||||
|
|
||||||
|
// Create the binding object
|
||||||
bindings.push({
|
bindings.push({
|
||||||
type: "context",
|
type: "context",
|
||||||
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
|
runtimeBinding,
|
||||||
runtimeBoundKey
|
readableBinding,
|
||||||
)}`,
|
|
||||||
readableBinding: `${component._instanceName}.${tableName}.${key}`,
|
|
||||||
// Field schema and provider are required to construct relationship
|
// Field schema and provider are required to construct relationship
|
||||||
// datasource options, based on bindable properties
|
// datasource options, based on bindable properties
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
|
@ -168,14 +194,12 @@ const getContextBindings = (asset, componentId) => {
|
||||||
*/
|
*/
|
||||||
const getUserBindings = () => {
|
const getUserBindings = () => {
|
||||||
let bindings = []
|
let bindings = []
|
||||||
const tables = get(tablesStore).list
|
const { schema } = getSchemaForDatasource({
|
||||||
const userTable = tables.find(table => table._id === TableNames.USERS)
|
type: "table",
|
||||||
const schema = {
|
tableId: TableNames.USERS,
|
||||||
...userTable.schema,
|
})
|
||||||
_id: { type: "string" },
|
|
||||||
_rev: { type: "string" },
|
|
||||||
}
|
|
||||||
const keys = Object.keys(schema).sort()
|
const keys = Object.keys(schema).sort()
|
||||||
|
const safeUser = makePropSafe("user")
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const fieldSchema = schema[key]
|
const fieldSchema = schema[key]
|
||||||
// Replace certain bindings with a new property to help display components
|
// Replace certain bindings with a new property to help display components
|
||||||
|
@ -188,7 +212,7 @@ const getUserBindings = () => {
|
||||||
|
|
||||||
bindings.push({
|
bindings.push({
|
||||||
type: "context",
|
type: "context",
|
||||||
runtimeBinding: `user.${runtimeBoundKey}`,
|
runtimeBinding: `${safeUser}.${makePropSafe(runtimeBoundKey)}`,
|
||||||
readableBinding: `Current User.${key}`,
|
readableBinding: `Current User.${key}`,
|
||||||
// Field schema and provider are required to construct relationship
|
// Field schema and provider are required to construct relationship
|
||||||
// datasource options, based on bindable properties
|
// datasource options, based on bindable properties
|
||||||
|
@ -212,9 +236,10 @@ const getUrlBindings = asset => {
|
||||||
params.push(part.replace(/:/g, "").replace(/\?/g, ""))
|
params.push(part.replace(/:/g, "").replace(/\?/g, ""))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const safeURL = makePropSafe("url")
|
||||||
return params.map(param => ({
|
return params.map(param => ({
|
||||||
type: "context",
|
type: "context",
|
||||||
runtimeBinding: `url.${param}`,
|
runtimeBinding: `${safeURL}.${makePropSafe(param)}`,
|
||||||
readableBinding: `URL.${param}`,
|
readableBinding: `URL.${param}`,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -236,15 +261,6 @@ export const getSchemaForDatasource = (datasource, isForm = false) => {
|
||||||
if (table) {
|
if (table) {
|
||||||
if (type === "view") {
|
if (type === "view") {
|
||||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||||
|
|
||||||
// Some calc views don't include a "name" property inside the schema
|
|
||||||
if (schema) {
|
|
||||||
Object.keys(schema).forEach(field => {
|
|
||||||
if (!schema[field].name) {
|
|
||||||
schema[field].name = field
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (type === "query" && isForm) {
|
} else if (type === "query" && isForm) {
|
||||||
schema = {}
|
schema = {}
|
||||||
const params = table.parameters || []
|
const params = table.parameters || []
|
||||||
|
@ -257,6 +273,21 @@ export const getSchemaForDatasource = (datasource, isForm = false) => {
|
||||||
schema = cloneDeep(table.schema)
|
schema = cloneDeep(table.schema)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add _id and _rev fields for certain types
|
||||||
|
if (schema && !isForm && ["table", "link"].includes(datasource.type)) {
|
||||||
|
schema["_id"] = { type: "string" }
|
||||||
|
schema["_rev"] = { type: "string" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure there are "name" properties for all fields
|
||||||
|
if (schema) {
|
||||||
|
Object.keys(schema).forEach(field => {
|
||||||
|
if (!schema[field].name) {
|
||||||
|
schema[field].name = field
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { schema, table }
|
return { schema, table }
|
||||||
}
|
}
|
||||||
|
@ -277,7 +308,7 @@ const buildFormSchema = component => {
|
||||||
if (fieldSetting && component.field) {
|
if (fieldSetting && component.field) {
|
||||||
const type = fieldSetting.type.split("field/")[1]
|
const type = fieldSetting.type.split("field/")[1]
|
||||||
if (type) {
|
if (type) {
|
||||||
schema[component.field] = { name: component.field, type }
|
schema[component.field] = { type }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
component._children?.forEach(child => {
|
component._children?.forEach(child => {
|
||||||
|
@ -330,6 +361,14 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a component ID from a handlebars expression setting of
|
||||||
|
* {{ literal [componentId] }}
|
||||||
|
*/
|
||||||
|
function extractLiteralHandlebarsID(value) {
|
||||||
|
return value?.match(/{{\s*literal[\s[]+([a-fA-F0-9]+)[\s\]]*}}/)?.[1]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a readable data binding into a runtime data binding
|
* Converts a readable data binding into a runtime data binding
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -37,7 +37,7 @@ const createScreen = table => {
|
||||||
.customProps({
|
.customProps({
|
||||||
theme: "spectrum--lightest",
|
theme: "spectrum--lightest",
|
||||||
size: "spectrum--medium",
|
size: "spectrum--medium",
|
||||||
datasource: {
|
dataSource: {
|
||||||
label: table.name,
|
label: table.name,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default function(tables) {
|
||||||
export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE"
|
export const ROW_DETAIL_TEMPLATE = "ROW_DETAIL_TEMPLATE"
|
||||||
export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`)
|
export const rowDetailUrl = table => sanitizeUrl(`/${table.name}/:id`)
|
||||||
|
|
||||||
function generateTitleContainer(table, title, formId) {
|
function generateTitleContainer(table, title, formId, repeaterId) {
|
||||||
// have to override style for this, its missing margin
|
// have to override style for this, its missing margin
|
||||||
const saveButton = makeSaveButton(table, formId).normalStyle({
|
const saveButton = makeSaveButton(table, formId).normalStyle({
|
||||||
background: "#000000",
|
background: "#000000",
|
||||||
|
@ -61,10 +61,9 @@ function generateTitleContainer(table, title, formId) {
|
||||||
onClick: [
|
onClick: [
|
||||||
{
|
{
|
||||||
parameters: {
|
parameters: {
|
||||||
providerId: formId,
|
|
||||||
rowId: `{{ ${makePropSafe(formId)}._id }}`,
|
|
||||||
revId: `{{ ${makePropSafe(formId)}._rev }}`,
|
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
|
rowId: `{{ ${makePropSafe(repeaterId)}.${makePropSafe("_id")} }}`,
|
||||||
|
revId: `{{ ${makePropSafe(repeaterId)}.${makePropSafe("_rev")} }}`,
|
||||||
},
|
},
|
||||||
"##eventHandlerType": "Delete Row",
|
"##eventHandlerType": "Delete Row",
|
||||||
},
|
},
|
||||||
|
@ -84,18 +83,33 @@ function generateTitleContainer(table, title, formId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createScreen = table => {
|
const createScreen = table => {
|
||||||
const screen = new Screen()
|
const provider = new Component("@budibase/standard-components/dataprovider")
|
||||||
.component("@budibase/standard-components/rowdetail")
|
.instanceName(`Data Provider`)
|
||||||
.table(table._id)
|
.customProps({
|
||||||
.instanceName(`${table.name} - Detail`)
|
dataSource: {
|
||||||
.route(rowDetailUrl(table))
|
label: table.name,
|
||||||
|
name: `all_${table._id}`,
|
||||||
|
tableId: table._id,
|
||||||
|
type: "table",
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
_id: `{{ ${makePropSafe("url")}.${makePropSafe("id")} }}`,
|
||||||
|
},
|
||||||
|
limit: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const repeater = new Component("@budibase/standard-components/repeater")
|
||||||
|
.instanceName("Repeater")
|
||||||
|
.customProps({
|
||||||
|
dataProvider: `{{ literal ${makePropSafe(provider._json._id)} }}`,
|
||||||
|
})
|
||||||
|
|
||||||
const form = makeMainForm()
|
const form = makeMainForm()
|
||||||
.instanceName("Form")
|
.instanceName("Form")
|
||||||
.customProps({
|
.customProps({
|
||||||
theme: "spectrum--lightest",
|
theme: "spectrum--lightest",
|
||||||
size: "spectrum--medium",
|
size: "spectrum--medium",
|
||||||
datasource: {
|
dataSource: {
|
||||||
label: table.name,
|
label: table.name,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
|
@ -116,14 +130,24 @@ const createScreen = table => {
|
||||||
|
|
||||||
// Add all children to the form
|
// Add all children to the form
|
||||||
const formId = form._json._id
|
const formId = form._json._id
|
||||||
const rowDetailId = screen._json.props._id
|
const repeaterId = repeater._json._id
|
||||||
const heading = table.primaryDisplay
|
const heading = table.primaryDisplay
|
||||||
? `{{ ${makePropSafe(rowDetailId)}.${makePropSafe(table.primaryDisplay)} }}`
|
? `{{ ${makePropSafe(repeaterId)}.${makePropSafe(table.primaryDisplay)} }}`
|
||||||
: null
|
: null
|
||||||
form
|
form
|
||||||
.addChild(makeBreadcrumbContainer(table.name, heading || "Edit"))
|
.addChild(makeBreadcrumbContainer(table.name, heading || "Edit"))
|
||||||
.addChild(generateTitleContainer(table, heading || "Edit Row", formId))
|
.addChild(
|
||||||
|
generateTitleContainer(table, heading || "Edit Row", formId, repeaterId)
|
||||||
|
)
|
||||||
.addChild(fieldGroup)
|
.addChild(fieldGroup)
|
||||||
|
|
||||||
return screen.addChild(form).json()
|
repeater.addChild(form)
|
||||||
|
provider.addChild(repeater)
|
||||||
|
|
||||||
|
return new Screen()
|
||||||
|
.component("@budibase/standard-components/container")
|
||||||
|
.instanceName(`${table.name} - Detail`)
|
||||||
|
.route(rowDetailUrl(table))
|
||||||
|
.addChild(provider)
|
||||||
|
.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import sanitizeUrl from "./utils/sanitizeUrl"
|
||||||
import { newRowUrl } from "./newRowScreen"
|
import { newRowUrl } from "./newRowScreen"
|
||||||
import { Screen } from "./utils/Screen"
|
import { Screen } from "./utils/Screen"
|
||||||
import { Component } from "./utils/Component"
|
import { Component } from "./utils/Component"
|
||||||
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
|
|
||||||
export default function(tables) {
|
export default function(tables) {
|
||||||
return tables.map(table => {
|
return tables.map(table => {
|
||||||
|
@ -70,21 +71,56 @@ function generateTitleContainer(table) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createScreen = table => {
|
const createScreen = table => {
|
||||||
const datagrid = new Component("@budibase/standard-components/datagrid")
|
const provider = new Component("@budibase/standard-components/dataprovider")
|
||||||
|
.instanceName(`Data Provider`)
|
||||||
.customProps({
|
.customProps({
|
||||||
datasource: {
|
dataSource: {
|
||||||
label: table.name,
|
label: table.name,
|
||||||
name: `all_${table._id}`,
|
name: `all_${table._id}`,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
type: "table",
|
type: "table",
|
||||||
},
|
},
|
||||||
editable: false,
|
|
||||||
theme: "alpine",
|
|
||||||
height: "540",
|
|
||||||
pagination: true,
|
|
||||||
detailUrl: `${rowListUrl(table)}/:id`,
|
|
||||||
})
|
})
|
||||||
.instanceName("Grid")
|
|
||||||
|
const spectrumTable = new Component("@budibase/standard-components/table")
|
||||||
|
.customProps({
|
||||||
|
dataProvider: `{{ literal ${makePropSafe(provider._json._id)} }}`,
|
||||||
|
theme: "spectrum--lightest",
|
||||||
|
showAutoColumns: false,
|
||||||
|
quiet: false,
|
||||||
|
size: "spectrum--medium",
|
||||||
|
rowCount: 8,
|
||||||
|
})
|
||||||
|
.instanceName(`${table.name} Table`)
|
||||||
|
|
||||||
|
const safeTableId = makePropSafe(spectrumTable._json._id)
|
||||||
|
const safeRowId = makePropSafe("_id")
|
||||||
|
const viewButton = new Component("@budibase/standard-components/button")
|
||||||
|
.customProps({
|
||||||
|
text: "View",
|
||||||
|
onClick: [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
url: `${rowListUrl(table)}/{{ ${safeTableId}.${safeRowId} }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.instanceName("View Button")
|
||||||
|
.normalStyle({
|
||||||
|
background: "transparent",
|
||||||
|
"font-family": "Inter, sans-serif",
|
||||||
|
"font-weight": "500",
|
||||||
|
color: "#888",
|
||||||
|
"border-width": "0",
|
||||||
|
})
|
||||||
|
.hoverStyle({
|
||||||
|
color: "#4285f4",
|
||||||
|
})
|
||||||
|
|
||||||
|
spectrumTable.addChild(viewButton)
|
||||||
|
provider.addChild(spectrumTable)
|
||||||
|
|
||||||
const mainContainer = new Component("@budibase/standard-components/container")
|
const mainContainer = new Component("@budibase/standard-components/container")
|
||||||
.normalStyle({
|
.normalStyle({
|
||||||
|
@ -105,14 +141,12 @@ const createScreen = table => {
|
||||||
.type("div")
|
.type("div")
|
||||||
.instanceName("Container")
|
.instanceName("Container")
|
||||||
.addChild(generateTitleContainer(table))
|
.addChild(generateTitleContainer(table))
|
||||||
.addChild(datagrid)
|
.addChild(provider)
|
||||||
|
|
||||||
return new Screen()
|
return new Screen()
|
||||||
.component("@budibase/standard-components/container")
|
.component("@budibase/standard-components/container")
|
||||||
.mainType("div")
|
|
||||||
.route(rowListUrl(table))
|
.route(rowListUrl(table))
|
||||||
.instanceName(`${table.name} - List`)
|
.instanceName(`${table.name} - List`)
|
||||||
.name("")
|
|
||||||
.addChild(mainContainer)
|
.addChild(mainContainer)
|
||||||
.json()
|
.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ export function makeSaveButton(table, formId) {
|
||||||
{
|
{
|
||||||
parameters: {
|
parameters: {
|
||||||
providerId: formId,
|
providerId: formId,
|
||||||
|
tableId: table._id,
|
||||||
},
|
},
|
||||||
"##eventHandlerType": "Save Row",
|
"##eventHandlerType": "Save Row",
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
[
|
[
|
||||||
"container",
|
"container",
|
||||||
"datagrid",
|
"dataprovider",
|
||||||
"list",
|
"table",
|
||||||
|
"repeater",
|
||||||
"button",
|
"button",
|
||||||
"search",
|
"search",
|
||||||
{
|
{
|
||||||
|
@ -62,8 +63,7 @@
|
||||||
"children": [
|
"children": [
|
||||||
"screenslot",
|
"screenslot",
|
||||||
"navigation",
|
"navigation",
|
||||||
"login",
|
"login"
|
||||||
"rowdetail"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<script>
|
||||||
|
import { Select } from "@budibase/bbui"
|
||||||
|
import { makePropSafe } from "@budibase/string-templates"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { findComponentPath } from "builderStore/storeUtils"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
$: path = findComponentPath($currentAsset.props, $store.selectedComponentId)
|
||||||
|
$: providers = path.filter(
|
||||||
|
component =>
|
||||||
|
component._component === "@budibase/standard-components/dataprovider"
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select thin secondary {value} on:change>
|
||||||
|
<option value="">Choose option</option>
|
||||||
|
{#if providers}
|
||||||
|
{#each providers as component}
|
||||||
|
<option value={`{{ literal ${makePropSafe(component._id)} }}`}>
|
||||||
|
{component._instanceName}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Select>
|
|
@ -1,50 +1,38 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import {
|
import { tables } from "stores/backend"
|
||||||
getDataProviderComponents,
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
getDatasourceForProvider,
|
import DrawerBindableInput from "components/common/DrawerBindableInput.svelte"
|
||||||
getSchemaForDatasource,
|
|
||||||
} from "builderStore/dataBinding"
|
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
|
|
||||||
$: dataProviderComponents = getDataProviderComponents(
|
$: tableOptions = $tables || []
|
||||||
$currentAsset,
|
$: bindings = getBindableProperties($currentAsset, $store.selectedComponentId)
|
||||||
$store.selectedComponentId
|
|
||||||
)
|
|
||||||
$: {
|
|
||||||
// Automatically set rev and table ID based on row ID
|
|
||||||
if (parameters.providerId) {
|
|
||||||
parameters.rowId = `{{ ${parameters.providerId}._id }}`
|
|
||||||
parameters.revId = `{{ ${parameters.providerId}._rev }}`
|
|
||||||
const providerComponent = dataProviderComponents.find(
|
|
||||||
provider => provider._id === parameters.providerId
|
|
||||||
)
|
|
||||||
const datasource = getDatasourceForProvider(providerComponent)
|
|
||||||
const { table } = getSchemaForDatasource(datasource)
|
|
||||||
if (table) {
|
|
||||||
parameters.tableId = table._id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#if dataProviderComponents.length === 0}
|
<Label small>Table</Label>
|
||||||
<div class="cannot-use">
|
<Select thin secondary bind:value={parameters.tableId}>
|
||||||
Delete row can only be used within a component that provides data, such as
|
<option value="" />
|
||||||
a List
|
{#each tableOptions as table}
|
||||||
</div>
|
<option value={table._id}>{table.name}</option>
|
||||||
{:else}
|
{/each}
|
||||||
<Label small>Datasource</Label>
|
</Select>
|
||||||
<Select thin secondary bind:value={parameters.providerId}>
|
|
||||||
<option value="" />
|
<Label small>Row ID</Label>
|
||||||
{#each dataProviderComponents as provider}
|
<DrawerBindableInput
|
||||||
<option value={provider._id}>{provider._instanceName}</option>
|
{bindings}
|
||||||
{/each}
|
title="Row ID to delete"
|
||||||
</Select>
|
value={parameters.rowId}
|
||||||
{/if}
|
on:change={value => (parameters.rowId = value.detail)} />
|
||||||
|
|
||||||
|
<Label small>Row Rev</Label>
|
||||||
|
<DrawerBindableInput
|
||||||
|
{bindings}
|
||||||
|
title="Row rev to delete"
|
||||||
|
value={parameters.revId}
|
||||||
|
on:change={value => (parameters.revId = value.detail)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -54,11 +42,7 @@
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
.cannot-use {
|
|
||||||
color: var(--red);
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
margin: auto;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label, Spacer } from "@budibase/bbui"
|
import { Select, Label, Spacer } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
import { datasources, integrations, queries } from 'stores/backend/'
|
import { datasources, integrations, queries } from "stores/backend/"
|
||||||
import { getBindableProperties } from "builderStore/dataBinding"
|
import { getBindableProperties } from "builderStore/dataBinding"
|
||||||
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte"
|
||||||
import IntegrationQueryEditor from "components/integration/index.svelte"
|
import IntegrationQueryEditor from "components/integration/index.svelte"
|
||||||
|
@ -18,9 +18,8 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
function fetchQueryDefinition(query) {
|
function fetchQueryDefinition(query) {
|
||||||
const source = $datasources.list.find(
|
const source = $datasources.list.find(ds => ds._id === query.datasourceId)
|
||||||
ds => ds._id === query.datasourceId
|
.source
|
||||||
).source
|
|
||||||
return $integrations[source].query[query.queryVerb]
|
return $integrations[source].query[query.queryVerb]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -30,7 +30,9 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
grid-template-columns: auto 1fr auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
<div class="root">This action doesn't require any additional settings.</div>
|
<script>
|
||||||
|
import { Body } from "@budibase/bbui"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<Body small grey>This action doesn't require any additional settings.</Body>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
font-size: var(--font-size-s);
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(> div) {
|
.root :global(> div) {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { Select, Label } from "@budibase/bbui"
|
import { Select, Label, Body } from "@budibase/bbui"
|
||||||
import { store, currentAsset } from "builderStore"
|
import { store, currentAsset } from "builderStore"
|
||||||
|
import { tables } from "stores/backend"
|
||||||
import {
|
import {
|
||||||
getDataProviderComponents,
|
getDataProviderComponents,
|
||||||
getDatasourceForProvider,
|
|
||||||
getSchemaForDatasource,
|
getSchemaForDatasource,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import SaveFields from "./SaveFields.svelte"
|
import SaveFields from "./SaveFields.svelte"
|
||||||
|
@ -14,14 +14,11 @@
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
$store.selectedComponentId
|
$store.selectedComponentId
|
||||||
)
|
)
|
||||||
$: providerComponent = dataProviderComponents.find(
|
$: schemaFields = getSchemaFields(parameters?.tableId)
|
||||||
provider => provider._id === parameters.providerId
|
$: tableOptions = $tables || []
|
||||||
)
|
|
||||||
$: schemaFields = getSchemaFields(providerComponent)
|
|
||||||
|
|
||||||
const getSchemaFields = component => {
|
const getSchemaFields = tableId => {
|
||||||
const datasource = getDatasourceForProvider(component)
|
const { schema } = getSchemaForDatasource({ type: "table", tableId })
|
||||||
const { schema } = getSchemaForDatasource(datasource)
|
|
||||||
return Object.values(schema || {})
|
return Object.values(schema || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,31 +28,48 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
{#if !dataProviderComponents.length}
|
<Body small grey>
|
||||||
<div class="cannot-use">
|
Choosing a Data Source will automatically use the data it provides, but it's
|
||||||
Save Row can only be used within a component that provides data, such as a
|
optional.<br />
|
||||||
Repeater
|
You can always add or override fields manually.
|
||||||
</div>
|
</Body>
|
||||||
{:else}
|
<div class="fields">
|
||||||
<Label small>Datasource</Label>
|
<Label small>Data Source</Label>
|
||||||
<Select thin secondary bind:value={parameters.providerId}>
|
<Select thin secondary bind:value={parameters.providerId}>
|
||||||
<option value="" />
|
<option value="">None</option>
|
||||||
{#each dataProviderComponents as provider}
|
{#each dataProviderComponents as provider}
|
||||||
<option value={provider._id}>{provider._instanceName}</option>
|
<option value={provider._id}>{provider._instanceName}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{#if parameters.providerId}
|
<Label small>Table</Label>
|
||||||
|
<Select thin secondary bind:value={parameters.tableId}>
|
||||||
|
<option value="" />
|
||||||
|
{#each tableOptions as table}
|
||||||
|
<option value={table._id}>{table.name}</option>
|
||||||
|
{/each}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{#if parameters.tableId}
|
||||||
<SaveFields
|
<SaveFields
|
||||||
parameterFields={parameters.fields}
|
parameterFields={parameters.fields}
|
||||||
{schemaFields}
|
{schemaFields}
|
||||||
on:change={onFieldsChanged} />
|
on:change={onFieldsChanged} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root :global(p) {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
row-gap: var(--spacing-s);
|
row-gap: var(--spacing-s);
|
||||||
|
@ -63,14 +77,9 @@
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(> div:nth-child(2)) {
|
.fields :global(> div:nth-child(2)),
|
||||||
|
.fields :global(> div:nth-child(4)) {
|
||||||
grid-column-start: 2;
|
grid-column-start: 2;
|
||||||
grid-column-end: 6;
|
grid-column-end: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cannot-use {
|
|
||||||
color: var(--red);
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -101,6 +101,11 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.root {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.fields {
|
.fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
column-gap: var(--spacing-l);
|
column-gap: var(--spacing-l);
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(> div) {
|
.root :global(> div) {
|
||||||
|
|
|
@ -5,19 +5,21 @@
|
||||||
getDatasourceForProvider,
|
getDatasourceForProvider,
|
||||||
getSchemaForDatasource,
|
getSchemaForDatasource,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
|
import { currentAsset } from "builderStore"
|
||||||
|
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
export let multiselect = false
|
export let multiselect = false
|
||||||
|
export let placeholder
|
||||||
|
|
||||||
$: datasource = getDatasourceForProvider(componentInstance)
|
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
$: schema = getSchemaForDatasource(datasource).schema
|
$: schema = getSchemaForDatasource(datasource).schema
|
||||||
$: options = Object.keys(schema || {})
|
$: options = Object.keys(schema || {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if multiselect}
|
{#if multiselect}
|
||||||
<MultiOptionSelect {value} {onChange} {options} />
|
<MultiOptionSelect {value} {onChange} {options} {placeholder} />
|
||||||
{:else}
|
{:else}
|
||||||
<OptionSelect {value} {onChange} {options} />
|
<OptionSelect {value} {onChange} {options} {placeholder} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
getSchemaForDatasource,
|
getSchemaForDatasource,
|
||||||
} from "builderStore/dataBinding"
|
} from "builderStore/dataBinding"
|
||||||
import SaveFields from "./EventsEditor/actions/SaveFields.svelte"
|
import SaveFields from "./EventsEditor/actions/SaveFields.svelte"
|
||||||
|
import { currentAsset } from "builderStore"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@
|
||||||
$: schemaFields = getSchemaFields(componentInstance)
|
$: schemaFields = getSchemaFields(componentInstance)
|
||||||
|
|
||||||
const getSchemaFields = component => {
|
const getSchemaFields = component => {
|
||||||
const datasource = getDatasourceForProvider(component)
|
const datasource = getDatasourceForProvider($currentAsset, component)
|
||||||
const { schema } = getSchemaForDatasource(datasource)
|
const { schema } = getSchemaForDatasource(datasource)
|
||||||
return Object.values(schema || {})
|
return Object.values(schema || {})
|
||||||
}
|
}
|
||||||
|
@ -65,6 +66,8 @@
|
||||||
.root {
|
.root {
|
||||||
padding: var(--spacing-l);
|
padding: var(--spacing-l);
|
||||||
min-height: calc(40vh - 2 * var(--spacing-l));
|
min-height: calc(40vh - 2 * var(--spacing-l));
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fields {
|
.fields {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
componentInstance._id,
|
componentInstance._id,
|
||||||
component => component._component === "@budibase/standard-components/form"
|
component => component._component === "@budibase/standard-components/form"
|
||||||
)
|
)
|
||||||
$: datasource = getDatasourceForProvider(form)
|
$: datasource = getDatasourceForProvider($currentAsset, form)
|
||||||
$: schema = getSchemaForDatasource(datasource, true).schema
|
$: schema = getSchemaForDatasource(datasource, true).schema
|
||||||
$: options = getOptions(schema, type)
|
$: options = getOptions(schema, type)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
export let options = []
|
export let options = []
|
||||||
export let value = []
|
export let value = []
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
|
export let placeholder
|
||||||
|
|
||||||
let boundValue = getValidOptions(value, options)
|
let boundValue = getValidOptions(value, options)
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
align="right"
|
align="right"
|
||||||
extraThin
|
extraThin
|
||||||
secondary
|
secondary
|
||||||
|
{placeholder}
|
||||||
value={boundValue}
|
value={boundValue}
|
||||||
on:change={setValue}>
|
on:change={setValue}>
|
||||||
{#each options as option}
|
{#each options as option}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let styleBindingProperty
|
export let styleBindingProperty
|
||||||
export let onChange = () => {}
|
export let onChange = () => {}
|
||||||
|
export let placeholder
|
||||||
|
|
||||||
let open = null
|
let open = null
|
||||||
let rotate = ""
|
let rotate = ""
|
||||||
|
@ -108,7 +109,7 @@
|
||||||
$: displayLabel =
|
$: displayLabel =
|
||||||
selectedOption && selectedOption.label
|
selectedOption && selectedOption.label
|
||||||
? selectedOption.label
|
? selectedOption.label
|
||||||
: value || "Choose option"
|
: value || placeholder || "Choose option"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import DatasourceSelect from "./DatasourceSelect.svelte"
|
import DataSourceSelect from "./DataSourceSelect.svelte"
|
||||||
|
|
||||||
const otherSources = [{ name: "Custom", label: "Custom" }]
|
const otherSources = [{ name: "Custom", label: "Custom" }]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatasourceSelect on:change {...$$props} showAllQueries={true} {otherSources} />
|
<DataSourceSelect on:change {...$$props} showAllQueries={true} {otherSources} />
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
import OptionSelect from "./PropertyControls/OptionSelect.svelte"
|
import OptionSelect from "./PropertyControls/OptionSelect.svelte"
|
||||||
import Checkbox from "./PropertyControls/Checkbox.svelte"
|
import Checkbox from "./PropertyControls/Checkbox.svelte"
|
||||||
import TableSelect from "./PropertyControls/TableSelect.svelte"
|
import TableSelect from "./PropertyControls/TableSelect.svelte"
|
||||||
import DatasourceSelect from "./PropertyControls/DatasourceSelect.svelte"
|
import DataSourceSelect from "./PropertyControls/DataSourceSelect.svelte"
|
||||||
|
import DataProviderSelect from "./PropertyControls/DataProviderSelect.svelte"
|
||||||
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
|
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
|
||||||
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
||||||
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
||||||
|
@ -61,7 +62,8 @@
|
||||||
const controlMap = {
|
const controlMap = {
|
||||||
text: Input,
|
text: Input,
|
||||||
select: OptionSelect,
|
select: OptionSelect,
|
||||||
datasource: DatasourceSelect,
|
dataSource: DataSourceSelect,
|
||||||
|
dataProvider: DataProviderSelect,
|
||||||
detailScreen: DetailScreenSelect,
|
detailScreen: DetailScreenSelect,
|
||||||
boolean: Checkbox,
|
boolean: Checkbox,
|
||||||
number: Input,
|
number: Input,
|
||||||
|
@ -108,8 +110,8 @@
|
||||||
componentInstance._id,
|
componentInstance._id,
|
||||||
component => component._component.endsWith("/form")
|
component => component._component.endsWith("/form")
|
||||||
)
|
)
|
||||||
const datasource = form?.datasource
|
const dataSource = form?.dataSource
|
||||||
const fields = makeDatasourceFormComponents(datasource)
|
const fields = makeDatasourceFormComponents(dataSource)
|
||||||
onChange(
|
onChange(
|
||||||
"_children",
|
"_children",
|
||||||
fields.map(field => field.json())
|
fields.map(field => field.json())
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { writable, get as svelteGet } from "svelte/store"
|
import { writable, get as svelteGet } from "svelte/store"
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import {
|
import { store, automationStore, hostingStore } from "builderStore"
|
||||||
store,
|
|
||||||
automationStore,
|
|
||||||
hostingStore,
|
|
||||||
} from "builderStore"
|
|
||||||
import { string, object } from "yup"
|
import { string, object } from "yup"
|
||||||
import api, { get } from "builderStore/api"
|
import api, { get } from "builderStore/api"
|
||||||
import Form from "@svelteschool/svelte-forms"
|
import Form from "@svelteschool/svelte-forms"
|
||||||
|
@ -24,7 +20,6 @@
|
||||||
|
|
||||||
export let template
|
export let template
|
||||||
|
|
||||||
|
|
||||||
const infoValidation = {
|
const infoValidation = {
|
||||||
applicationName: string().required("Your application must have a name."),
|
applicationName: string().required("Your application must have a name."),
|
||||||
}
|
}
|
||||||
|
@ -149,7 +144,6 @@
|
||||||
)
|
)
|
||||||
const pkg = await applicationPkg.json()
|
const pkg = await applicationPkg.json()
|
||||||
if (applicationPkg.ok) {
|
if (applicationPkg.ok) {
|
||||||
// backendUiStore.actions.reset()
|
|
||||||
await store.actions.initialise(pkg)
|
await store.actions.initialise(pkg)
|
||||||
await automationStore.actions.fetch()
|
await automationStore.actions.fetch()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { store, automationStore } from "builderStore"
|
import { store, automationStore } from "builderStore"
|
||||||
import { roles } from 'stores/backend/'
|
import { roles } from "stores/backend/"
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button } from "@budibase/bbui"
|
||||||
import SettingsLink from "components/settings/Link.svelte"
|
import SettingsLink from "components/settings/Link.svelte"
|
||||||
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
|
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
|
||||||
|
@ -18,7 +18,6 @@
|
||||||
const pkg = await res.json()
|
const pkg = await res.json()
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
// backendUiStore.actions.reset()
|
|
||||||
await store.actions.initialise(pkg)
|
await store.actions.initialise(pkg)
|
||||||
await automationStore.actions.fetch()
|
await automationStore.actions.fetch()
|
||||||
await roles.fetch()
|
await roles.fetch()
|
||||||
|
|
|
@ -7,31 +7,31 @@ import { executeQuery } from "./queries"
|
||||||
/**
|
/**
|
||||||
* Fetches all rows for a particular Budibase data source.
|
* Fetches all rows for a particular Budibase data source.
|
||||||
*/
|
*/
|
||||||
export const fetchDatasource = async datasource => {
|
export const fetchDatasource = async dataSource => {
|
||||||
if (!datasource || !datasource.type) {
|
if (!dataSource || !dataSource.type) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all rows in data source
|
// Fetch all rows in data source
|
||||||
const { type, tableId, fieldName } = datasource
|
const { type, tableId, fieldName } = dataSource
|
||||||
let rows = []
|
let rows = []
|
||||||
if (type === "table") {
|
if (type === "table") {
|
||||||
rows = await fetchTableData(tableId)
|
rows = await fetchTableData(tableId)
|
||||||
} else if (type === "view") {
|
} else if (type === "view") {
|
||||||
rows = await fetchViewData(datasource)
|
rows = await fetchViewData(dataSource)
|
||||||
} else if (type === "query") {
|
} else if (type === "query") {
|
||||||
// Set the default query params
|
// Set the default query params
|
||||||
let parameters = cloneDeep(datasource.queryParams || {})
|
let parameters = cloneDeep(dataSource.queryParams || {})
|
||||||
for (let param of datasource.parameters) {
|
for (let param of dataSource.parameters) {
|
||||||
if (!parameters[param.name]) {
|
if (!parameters[param.name]) {
|
||||||
parameters[param.name] = param.default
|
parameters[param.name] = param.default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rows = await executeQuery({ queryId: datasource._id, parameters })
|
rows = await executeQuery({ queryId: dataSource._id, parameters })
|
||||||
} else if (type === "link") {
|
} else if (type === "link") {
|
||||||
rows = await fetchRelationshipData({
|
rows = await fetchRelationshipData({
|
||||||
rowId: datasource.rowId,
|
rowId: dataSource.rowId,
|
||||||
tableId: datasource.rowTableId,
|
tableId: dataSource.rowTableId,
|
||||||
fieldName,
|
fieldName,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { notificationStore, datasourceStore } from "../store"
|
import { notificationStore, dataSourceStore } from "../store"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,7 +20,7 @@ export const executeQuery = async ({ queryId, parameters }) => {
|
||||||
notificationStore.danger("An error has occurred")
|
notificationStore.danger("An error has occurred")
|
||||||
} else if (!query.readable) {
|
} else if (!query.readable) {
|
||||||
notificationStore.success("Query executed successfully")
|
notificationStore.success("Query executed successfully")
|
||||||
datasourceStore.actions.invalidateDatasource(query.datasourceId)
|
dataSourceStore.actions.invalidateDataSource(query.datasourceId)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { notificationStore, datasourceStore } from "../store"
|
import { notificationStore, dataSourceStore } from "../store"
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
import { fetchTableDefinition } from "./tables"
|
import { fetchTableDefinition } from "./tables"
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ export const saveRow = async row => {
|
||||||
: notificationStore.success("Row saved")
|
: notificationStore.success("Row saved")
|
||||||
|
|
||||||
// Refresh related datasources
|
// Refresh related datasources
|
||||||
datasourceStore.actions.invalidateDatasource(row.tableId)
|
dataSourceStore.actions.invalidateDataSource(row.tableId)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export const updateRow = async row => {
|
||||||
: notificationStore.success("Row updated")
|
: notificationStore.success("Row updated")
|
||||||
|
|
||||||
// Refresh related datasources
|
// Refresh related datasources
|
||||||
datasourceStore.actions.invalidateDatasource(row.tableId)
|
dataSourceStore.actions.invalidateDataSource(row.tableId)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
|
||||||
: notificationStore.success("Row deleted")
|
: notificationStore.success("Row deleted")
|
||||||
|
|
||||||
// Refresh related datasources
|
// Refresh related datasources
|
||||||
datasourceStore.actions.invalidateDatasource(tableId)
|
dataSourceStore.actions.invalidateDataSource(tableId)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ export const deleteRows = async ({ tableId, rows }) => {
|
||||||
: notificationStore.success(`${rows.length} row(s) deleted`)
|
: notificationStore.success(`${rows.length} row(s) deleted`)
|
||||||
|
|
||||||
// Refresh related datasources
|
// Refresh related datasources
|
||||||
datasourceStore.actions.invalidateDatasource(tableId)
|
dataSourceStore.actions.invalidateDataSource(tableId)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
{
|
{
|
||||||
type: ActionTypes.RefreshDatasource,
|
type: ActionTypes.RefreshDatasource,
|
||||||
callback: () => authStore.actions.fetchUser(),
|
callback: () => authStore.actions.fetchUser(),
|
||||||
metadata: { datasource: { type: "table", tableId: TableNames.USERS } },
|
metadata: { dataSource: { type: "table", tableId: TableNames.USERS } },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, setContext, onMount } from "svelte"
|
import { getContext, setContext, onMount } from "svelte"
|
||||||
import { datasourceStore, createContextStore } from "../store"
|
import { dataSourceStore, createContextStore } from "../store"
|
||||||
import { ActionTypes } from "../constants"
|
import { ActionTypes } from "../constants"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
@ -31,9 +31,9 @@
|
||||||
// Register any "refresh datasource" actions with a singleton store
|
// Register any "refresh datasource" actions with a singleton store
|
||||||
// so we can easily refresh data at all levels for any datasource
|
// so we can easily refresh data at all levels for any datasource
|
||||||
if (type === ActionTypes.RefreshDatasource) {
|
if (type === ActionTypes.RefreshDatasource) {
|
||||||
const { datasource } = metadata || {}
|
const { dataSource } = metadata || {}
|
||||||
datasourceStore.actions.registerDatasource(
|
dataSourceStore.actions.registerDataSource(
|
||||||
datasource,
|
dataSource,
|
||||||
instanceId,
|
instanceId,
|
||||||
callback
|
callback
|
||||||
)
|
)
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
instanceId = generate()
|
instanceId = generate()
|
||||||
|
|
||||||
// Unregister all datasource instances when unmounting this provider
|
// Unregister all datasource instances when unmounting this provider
|
||||||
return () => datasourceStore.actions.unregisterInstance(instanceId)
|
return () => dataSourceStore.actions.unregisterInstance(instanceId)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { writable, get } from "svelte/store"
|
||||||
|
import { notificationStore } from "./notification"
|
||||||
|
|
||||||
|
export const createDataSourceStore = () => {
|
||||||
|
const store = writable([])
|
||||||
|
|
||||||
|
// Registers a new dataSource instance
|
||||||
|
const registerDataSource = (dataSource, instanceId, refresh) => {
|
||||||
|
if (!dataSource || !instanceId || !refresh) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a list of all relevant dataSource IDs which would require that
|
||||||
|
// this dataSource is refreshed
|
||||||
|
let dataSourceIds = []
|
||||||
|
|
||||||
|
// Extract table ID
|
||||||
|
if (dataSource.type === "table" || dataSource.type === "view") {
|
||||||
|
if (dataSource.tableId) {
|
||||||
|
dataSourceIds.push(dataSource.tableId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract both table IDs from both sides of the relationship
|
||||||
|
else if (dataSource.type === "link") {
|
||||||
|
if (dataSource.rowTableId) {
|
||||||
|
dataSourceIds.push(dataSource.rowTableId)
|
||||||
|
}
|
||||||
|
if (dataSource.tableId) {
|
||||||
|
dataSourceIds.push(dataSource.tableId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the dataSource ID (not the query ID) for queries
|
||||||
|
else if (dataSource.type === "query") {
|
||||||
|
if (dataSource.dataSourceId) {
|
||||||
|
dataSourceIds.push(dataSource.dataSourceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store configs for each relevant dataSource ID
|
||||||
|
if (dataSourceIds.length) {
|
||||||
|
store.update(state => {
|
||||||
|
dataSourceIds.forEach(id => {
|
||||||
|
state.push({
|
||||||
|
dataSourceId: id,
|
||||||
|
instanceId,
|
||||||
|
refresh,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes all registered dataSource instances belonging to a particular
|
||||||
|
// instance ID
|
||||||
|
const unregisterInstance = instanceId => {
|
||||||
|
store.update(state => {
|
||||||
|
return state.filter(instance => instance.instanceId !== instanceId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidates a specific dataSource ID by refreshing all instances
|
||||||
|
// which depend on data from that dataSource
|
||||||
|
const invalidateDataSource = dataSourceId => {
|
||||||
|
const relatedInstances = get(store).filter(instance => {
|
||||||
|
return instance.dataSourceId === dataSourceId
|
||||||
|
})
|
||||||
|
if (relatedInstances?.length) {
|
||||||
|
notificationStore.blockNotifications(1000)
|
||||||
|
}
|
||||||
|
relatedInstances?.forEach(instance => {
|
||||||
|
instance.refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
actions: { registerDataSource, unregisterInstance, invalidateDataSource },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dataSourceStore = createDataSourceStore()
|
|
@ -1,84 +0,0 @@
|
||||||
import { writable, get } from "svelte/store"
|
|
||||||
import { notificationStore } from "./notification"
|
|
||||||
|
|
||||||
export const createDatasourceStore = () => {
|
|
||||||
const store = writable([])
|
|
||||||
|
|
||||||
// Registers a new datasource instance
|
|
||||||
const registerDatasource = (datasource, instanceId, refresh) => {
|
|
||||||
if (!datasource || !instanceId || !refresh) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a list of all relevant datasource IDs which would require that
|
|
||||||
// this datasource is refreshed
|
|
||||||
let datasourceIds = []
|
|
||||||
|
|
||||||
// Extract table ID
|
|
||||||
if (datasource.type === "table" || datasource.type === "view") {
|
|
||||||
if (datasource.tableId) {
|
|
||||||
datasourceIds.push(datasource.tableId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract both table IDs from both sides of the relationship
|
|
||||||
else if (datasource.type === "link") {
|
|
||||||
if (datasource.rowTableId) {
|
|
||||||
datasourceIds.push(datasource.rowTableId)
|
|
||||||
}
|
|
||||||
if (datasource.tableId) {
|
|
||||||
datasourceIds.push(datasource.tableId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the datasource ID (not the query ID) for queries
|
|
||||||
else if (datasource.type === "query") {
|
|
||||||
if (datasource.datasourceId) {
|
|
||||||
datasourceIds.push(datasource.datasourceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store configs for each relevant datasource ID
|
|
||||||
if (datasourceIds.length) {
|
|
||||||
store.update(state => {
|
|
||||||
datasourceIds.forEach(id => {
|
|
||||||
state.push({
|
|
||||||
datasourceId: id,
|
|
||||||
instanceId,
|
|
||||||
refresh,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes all registered datasource instances belonging to a particular
|
|
||||||
// instance ID
|
|
||||||
const unregisterInstance = instanceId => {
|
|
||||||
store.update(state => {
|
|
||||||
return state.filter(instance => instance.instanceId !== instanceId)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalidates a specific datasource ID by refreshing all instances
|
|
||||||
// which depend on data from that datasource
|
|
||||||
const invalidateDatasource = datasourceId => {
|
|
||||||
const relatedInstances = get(store).filter(instance => {
|
|
||||||
return instance.datasourceId === datasourceId
|
|
||||||
})
|
|
||||||
if (relatedInstances?.length) {
|
|
||||||
notificationStore.blockNotifications(1000)
|
|
||||||
}
|
|
||||||
relatedInstances?.forEach(instance => {
|
|
||||||
instance.refresh()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe: store.subscribe,
|
|
||||||
actions: { registerDatasource, unregisterInstance, invalidateDatasource },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const datasourceStore = createDatasourceStore()
|
|
|
@ -3,7 +3,7 @@ export { notificationStore } from "./notification"
|
||||||
export { routeStore } from "./routes"
|
export { routeStore } from "./routes"
|
||||||
export { screenStore } from "./screens"
|
export { screenStore } from "./screens"
|
||||||
export { builderStore } from "./builder"
|
export { builderStore } from "./builder"
|
||||||
export { datasourceStore } from "./datasource"
|
export { dataSourceStore } from "./dataSource"
|
||||||
|
|
||||||
// Context stores are layered and duplicated, so it is not a singleton
|
// Context stores are layered and duplicated, so it is not a singleton
|
||||||
export { createContextStore } from "./context"
|
export { createContextStore } from "./context"
|
||||||
|
|
|
@ -43,8 +43,8 @@ export const enrichProps = async (props, context) => {
|
||||||
// Enrich all data bindings in top level props
|
// Enrich all data bindings in top level props
|
||||||
let enrichedProps = await enrichDataBindings(validProps, totalContext)
|
let enrichedProps = await enrichDataBindings(validProps, totalContext)
|
||||||
|
|
||||||
// Enrich button actions if they exist
|
// Enrich click actions if they exist
|
||||||
if (props._component?.endsWith("/button") && enrichedProps.onClick) {
|
if (enrichedProps.onClick) {
|
||||||
enrichedProps.onClick = enrichButtonActions(
|
enrichedProps.onClick = enrichButtonActions(
|
||||||
enrichedProps.onClick,
|
enrichedProps.onClick,
|
||||||
totalContext
|
totalContext
|
||||||
|
|
|
@ -8,47 +8,6 @@
|
||||||
"transitionable": true,
|
"transitionable": true,
|
||||||
"settings": []
|
"settings": []
|
||||||
},
|
},
|
||||||
"datagrid": {
|
|
||||||
"name": "Grid",
|
|
||||||
"description": "A datagrid component with functionality to add, remove and edit rows.",
|
|
||||||
"icon": "ri-grid-line",
|
|
||||||
"styleable": true,
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"type": "datasource",
|
|
||||||
"label": "Source",
|
|
||||||
"key": "datasource"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "detailScreen",
|
|
||||||
"label": "Detail URL",
|
|
||||||
"key": "detailUrl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Editable",
|
|
||||||
"key": "editable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "select",
|
|
||||||
"label": "Theme",
|
|
||||||
"key": "theme",
|
|
||||||
"options": ["alpine", "alpine-dark", "balham", "balham-dark", "material"],
|
|
||||||
"defaultValue": "alpine"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number",
|
|
||||||
"label": "Height",
|
|
||||||
"key": "height",
|
|
||||||
"defaultValue": "500"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "boolean",
|
|
||||||
"label": "Pagination",
|
|
||||||
"key": "pagination"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"screenslot": {
|
"screenslot": {
|
||||||
"name": "Screenslot",
|
"name": "Screenslot",
|
||||||
"icon": "ri-artboard-2-line",
|
"icon": "ri-artboard-2-line",
|
||||||
|
@ -78,19 +37,17 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"list": {
|
"repeater": {
|
||||||
"name": "Repeater",
|
"name": "Repeater",
|
||||||
"description": "A configurable data list that attaches to your backend tables.",
|
"description": "A configurable data list that attaches to your backend tables.",
|
||||||
"icon": "ri-list-check-2",
|
"icon": "ri-list-check-2",
|
||||||
"styleable": true,
|
"styleable": true,
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"dataProvider": true,
|
|
||||||
"actions": ["RefreshDatasource"],
|
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -103,7 +60,10 @@
|
||||||
"label": "Filtering",
|
"label": "Filtering",
|
||||||
"key": "filter"
|
"key": "filter"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "schema"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"name": "Search",
|
"name": "Search",
|
||||||
|
@ -111,7 +71,6 @@
|
||||||
"icon": "ri-search-line",
|
"icon": "ri-search-line",
|
||||||
"styleable": true,
|
"styleable": true,
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"dataProvider": true,
|
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "table",
|
"type": "table",
|
||||||
|
@ -136,7 +95,10 @@
|
||||||
"key": "noRowsMessage",
|
"key": "noRowsMessage",
|
||||||
"defaultValue": "No rows found."
|
"defaultValue": "No rows found."
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "schema"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"stackedlist": {
|
"stackedlist": {
|
||||||
"name": "Stacked List",
|
"name": "Stacked List",
|
||||||
|
@ -369,6 +331,11 @@
|
||||||
"label": "Color",
|
"label": "Color",
|
||||||
"key": "color",
|
"key": "color",
|
||||||
"defaultValue": "#000"
|
"defaultValue": "#000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"label": "On Click",
|
||||||
|
"key": "onClick"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -438,36 +405,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rowdetail": {
|
|
||||||
"name": "Row Detail",
|
|
||||||
"description": "Loads a row, using an id from the URL, which can be used with {{ context }}, in children",
|
|
||||||
"icon": "ri-profile-line",
|
|
||||||
"styleable": true,
|
|
||||||
"hasChildren": true,
|
|
||||||
"dataProvider": true,
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"type": "table",
|
|
||||||
"label": "Table",
|
|
||||||
"key": "table"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"newrow": {
|
|
||||||
"name": "New Row",
|
|
||||||
"description": "Sets up a new row for creation, which can be used with {{ context }}, in children",
|
|
||||||
"icon": "ri-profile-line",
|
|
||||||
"hasChildren": true,
|
|
||||||
"styleable": true,
|
|
||||||
"dataProvider": true,
|
|
||||||
"settings": [
|
|
||||||
{
|
|
||||||
"type": "table",
|
|
||||||
"label": "Table",
|
|
||||||
"key": "table"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cardhorizontal": {
|
"cardhorizontal": {
|
||||||
"name": "Horizontal Card",
|
"name": "Horizontal Card",
|
||||||
"description": "A basic card component that can contain content and actions.",
|
"description": "A basic card component that can contain content and actions.",
|
||||||
|
@ -591,21 +528,21 @@
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Label Col.",
|
"label": "Label Col.",
|
||||||
"key": "labelColumn",
|
"key": "labelColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "multifield",
|
"type": "multifield",
|
||||||
"label": "Data Cols.",
|
"label": "Data Cols.",
|
||||||
"key": "valueColumns",
|
"key": "valueColumns",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -691,21 +628,21 @@
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Label Col.",
|
"label": "Label Col.",
|
||||||
"key": "labelColumn",
|
"key": "labelColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "multifield",
|
"type": "multifield",
|
||||||
"label": "Data Cols.",
|
"label": "Data Cols.",
|
||||||
"key": "valueColumns",
|
"key": "valueColumns",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -792,21 +729,21 @@
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Label Col.",
|
"label": "Label Col.",
|
||||||
"key": "labelColumn",
|
"key": "labelColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "multifield",
|
"type": "multifield",
|
||||||
"label": "Data Cols.",
|
"label": "Data Cols.",
|
||||||
"key": "valueColumns",
|
"key": "valueColumns",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -905,21 +842,21 @@
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Label Col.",
|
"label": "Label Col.",
|
||||||
"key": "labelColumn",
|
"key": "labelColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Data Col.",
|
"label": "Data Col.",
|
||||||
"key": "valueColumn",
|
"key": "valueColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -982,21 +919,21 @@
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Label Col.",
|
"label": "Label Col.",
|
||||||
"key": "labelColumn",
|
"key": "labelColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Data Col.",
|
"label": "Data Col.",
|
||||||
"key": "valueColumn",
|
"key": "valueColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -1059,39 +996,39 @@
|
||||||
"key": "title"
|
"key": "title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "datasource",
|
"type": "dataProvider",
|
||||||
"label": "Data",
|
"label": "Data",
|
||||||
"key": "datasource"
|
"key": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Date Col.",
|
"label": "Date Col.",
|
||||||
"key": "dateColumn",
|
"key": "dateColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Open Col.",
|
"label": "Open Col.",
|
||||||
"key": "openColumn",
|
"key": "openColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Close Col.",
|
"label": "Close Col.",
|
||||||
"key": "closeColumn",
|
"key": "closeColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "High Col.",
|
"label": "High Col.",
|
||||||
"key": "highColumn",
|
"key": "highColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Low Col.",
|
"label": "Low Col.",
|
||||||
"key": "lowColumn",
|
"key": "lowColumn",
|
||||||
"dependsOn": "datasource"
|
"dependsOn": "dataProvider"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -1134,13 +1071,14 @@
|
||||||
"icon": "ri-file-text-line",
|
"icon": "ri-file-text-line",
|
||||||
"styleable": true,
|
"styleable": true,
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"dataProvider": true,
|
"actions": [
|
||||||
"actions": ["ValidateForm"],
|
"ValidateForm"
|
||||||
|
],
|
||||||
"settings": [
|
"settings": [
|
||||||
{
|
{
|
||||||
"type": "schema",
|
"type": "schema",
|
||||||
"label": "Schema",
|
"label": "Schema",
|
||||||
"key": "datasource"
|
"key": "dataSource"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -1188,7 +1126,10 @@
|
||||||
"key": "disabled",
|
"key": "disabled",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "form"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"fieldgroup": {
|
"fieldgroup": {
|
||||||
"name": "Field Group",
|
"name": "Field Group",
|
||||||
|
@ -1472,5 +1413,145 @@
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"dataprovider": {
|
||||||
|
"name": "Data Provider",
|
||||||
|
"icon": "ri-database-2-line",
|
||||||
|
"styleable": false,
|
||||||
|
"hasChildren": true,
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "dataSource",
|
||||||
|
"label": "Data",
|
||||||
|
"key": "dataSource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "filter",
|
||||||
|
"label": "Filtering",
|
||||||
|
"key": "filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Sort Column",
|
||||||
|
"key": "sortColumn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Sort Order",
|
||||||
|
"key": "sortOrder",
|
||||||
|
"options": ["Ascending", "Descending"],
|
||||||
|
"defaultValue": "Descending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Limit",
|
||||||
|
"key": "limit"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "static",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"label": "Rows",
|
||||||
|
"key": "rows"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Rows Length",
|
||||||
|
"key": "rowsLength"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Schema",
|
||||||
|
"key": "schema"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Loading",
|
||||||
|
"key": "loading"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Loaded",
|
||||||
|
"key": "loaded"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"name": "Table",
|
||||||
|
"icon": "ri-table-line",
|
||||||
|
"styleable": true,
|
||||||
|
"hasChildren": true,
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "dataProvider",
|
||||||
|
"label": "Data Provider",
|
||||||
|
"key": "dataProvider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Row Count",
|
||||||
|
"key": "rowCount",
|
||||||
|
"defaultValue": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Theme",
|
||||||
|
"key": "theme",
|
||||||
|
"defaultValue": "spectrum--light",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Lightest",
|
||||||
|
"value": "spectrum--lightest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Light",
|
||||||
|
"value": "spectrum--light"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Dark",
|
||||||
|
"value": "spectrum--dark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Darkest",
|
||||||
|
"value": "spectrum--darkest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Size",
|
||||||
|
"key": "size",
|
||||||
|
"defaultValue": "spectrum--medium",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Medium",
|
||||||
|
"value": "spectrum--medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Large",
|
||||||
|
"value": "spectrum--large"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Quiet",
|
||||||
|
"key": "quiet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "multifield",
|
||||||
|
"label": "Columns",
|
||||||
|
"key": "columns",
|
||||||
|
"dependsOn": "dataProvider",
|
||||||
|
"placeholder": "All columns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Auto Cols.",
|
||||||
|
"key": "showAutoColumns",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"context": {
|
||||||
|
"type": "schema"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,21 +41,23 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.1.0",
|
"@adobe/spectrum-css-workflow-icons": "^1.1.0",
|
||||||
"@budibase/bbui": "^1.58.13",
|
"@budibase/bbui": "^1.58.13",
|
||||||
"@budibase/svelte-ag-grid": "^1.0.4",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actionbutton": "^1.0.0-beta.1",
|
"@spectrum-css/button": "^3.0.1",
|
||||||
"@spectrum-css/button": "^3.0.0-beta.6",
|
"@spectrum-css/checkbox": "^3.0.1",
|
||||||
"@spectrum-css/checkbox": "^3.0.0-beta.6",
|
"@spectrum-css/fieldlabel": "^3.0.1",
|
||||||
"@spectrum-css/fieldlabel": "^3.0.0-beta.7",
|
"@spectrum-css/icon": "^3.0.1",
|
||||||
"@spectrum-css/icon": "^3.0.0-beta.2",
|
"@spectrum-css/inputgroup": "^3.0.1",
|
||||||
"@spectrum-css/inputgroup": "^3.0.0-beta.7",
|
"@spectrum-css/label": "^2.0.9",
|
||||||
"@spectrum-css/menu": "^3.0.0-beta.5",
|
"@spectrum-css/menu": "^3.0.1",
|
||||||
"@spectrum-css/page": "^3.0.0-beta.0",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/picker": "^1.0.0-beta.3",
|
"@spectrum-css/picker": "^1.0.0",
|
||||||
"@spectrum-css/popover": "^3.0.0-beta.6",
|
"@spectrum-css/popover": "^3.0.1",
|
||||||
"@spectrum-css/stepper": "^3.0.0-beta.7",
|
"@spectrum-css/stepper": "^3.0.1",
|
||||||
"@spectrum-css/textfield": "^3.0.0-beta.6",
|
"@spectrum-css/table": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.0-beta.2",
|
"@spectrum-css/textfield": "^3.0.1",
|
||||||
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
"apexcharts": "^3.22.1",
|
"apexcharts": "^3.22.1",
|
||||||
|
"dayjs": "^1.10.4",
|
||||||
"flatpickr": "^4.6.6",
|
"flatpickr": "^4.6.6",
|
||||||
"loadicons": "^1.0.0",
|
"loadicons": "^1.0.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
export let dataSource
|
||||||
|
export let filter
|
||||||
|
export let sortColumn
|
||||||
|
export let sortOrder
|
||||||
|
export let limit
|
||||||
|
|
||||||
|
const { API, styleable, Provider, ActionTypes } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
|
||||||
|
// Loading flag every time data is being fetched
|
||||||
|
let loading = false
|
||||||
|
|
||||||
|
// Loading flag for the initial load
|
||||||
|
let loaded = false
|
||||||
|
|
||||||
|
let allRows = []
|
||||||
|
let schema = {}
|
||||||
|
|
||||||
|
$: fetchData(dataSource)
|
||||||
|
$: filteredRows = filterRows(allRows, filter)
|
||||||
|
$: sortedRows = sortRows(filteredRows, sortColumn, sortOrder)
|
||||||
|
$: rows = limitRows(sortedRows, limit)
|
||||||
|
$: getSchema(dataSource)
|
||||||
|
$: actions = [
|
||||||
|
{
|
||||||
|
type: ActionTypes.RefreshDatasource,
|
||||||
|
callback: () => fetchData(dataSource),
|
||||||
|
metadata: { dataSource },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
$: dataContext = {
|
||||||
|
rows,
|
||||||
|
schema,
|
||||||
|
rowsLength: rows.length,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async dataSource => {
|
||||||
|
loading = true
|
||||||
|
allRows = await API.fetchDatasource(dataSource)
|
||||||
|
loading = false
|
||||||
|
loaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterRows = (rows, filter) => {
|
||||||
|
if (!Object.keys(filter || {}).length) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
let filteredData = [...rows]
|
||||||
|
Object.entries(filter).forEach(([field, value]) => {
|
||||||
|
if (value != null && value !== "") {
|
||||||
|
filteredData = filteredData.filter(row => {
|
||||||
|
return row[field] === value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return filteredData
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortRows = (rows, sortColumn, sortOrder) => {
|
||||||
|
if (!sortColumn || !sortOrder) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
return rows.slice().sort((a, b) => {
|
||||||
|
const colA = a[sortColumn]
|
||||||
|
const colB = b[sortColumn]
|
||||||
|
if (sortOrder === "Descending") {
|
||||||
|
return colA > colB ? -1 : 1
|
||||||
|
} else {
|
||||||
|
return colA > colB ? 1 : -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitRows = (rows, limit) => {
|
||||||
|
const numLimit = parseFloat(limit)
|
||||||
|
if (isNaN(numLimit)) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
return rows.slice(0, numLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSchema = async dataSource => {
|
||||||
|
if (dataSource?.schema) {
|
||||||
|
schema = dataSource.schema
|
||||||
|
} else if (dataSource?.tableId) {
|
||||||
|
const definition = await API.fetchTableDefinition(dataSource.tableId)
|
||||||
|
schema = definition?.schema ?? {}
|
||||||
|
} else {
|
||||||
|
schema = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all schema fields have a name property
|
||||||
|
Object.entries(schema).forEach(([key, value]) => {
|
||||||
|
if (!value.name) {
|
||||||
|
value.name = key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Provider {actions} data={dataContext}>
|
||||||
|
<slot />
|
||||||
|
</Provider>
|
|
@ -7,6 +7,7 @@
|
||||||
export let icon = ""
|
export let icon = ""
|
||||||
export let size = "fa-lg"
|
export let size = "fa-lg"
|
||||||
export let color = "#f00"
|
export let color = "#f00"
|
||||||
|
export let onClick
|
||||||
|
|
||||||
$: styles = {
|
$: styles = {
|
||||||
...$component.styles,
|
...$component.styles,
|
||||||
|
@ -17,4 +18,4 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i use:styleable={styles} class="{icon} {size}" />
|
<i use:styleable={styles} class="{icon} {size}" on:click={onClick} />
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import { isEmpty } from "lodash/fp"
|
|
||||||
|
|
||||||
export let datasource
|
|
||||||
export let noRowsMessage
|
|
||||||
export let filter
|
|
||||||
|
|
||||||
const { API, styleable, Provider, builderStore, ActionTypes } = getContext(
|
|
||||||
"sdk"
|
|
||||||
)
|
|
||||||
const component = getContext("component")
|
|
||||||
let rows = []
|
|
||||||
let loaded = false
|
|
||||||
|
|
||||||
$: fetchData(datasource)
|
|
||||||
$: filteredRows = filterRows(rows, filter)
|
|
||||||
$: actions = [
|
|
||||||
{
|
|
||||||
type: ActionTypes.RefreshDatasource,
|
|
||||||
callback: () => fetchData(datasource),
|
|
||||||
metadata: { datasource },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const fetchData = async datasource => {
|
|
||||||
if (!isEmpty(datasource)) {
|
|
||||||
rows = await API.fetchDatasource(datasource)
|
|
||||||
}
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterRows = (rows, filter) => {
|
|
||||||
if (!Object.keys(filter || {}).length) {
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
let filteredData = [...rows]
|
|
||||||
Object.entries(filter).forEach(([field, value]) => {
|
|
||||||
if (value != null && value !== "") {
|
|
||||||
filteredData = filteredData.filter(row => {
|
|
||||||
return row[field] === value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return filteredData
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Provider {actions}>
|
|
||||||
<div use:styleable={$component.styles}>
|
|
||||||
{#if filteredRows.length > 0}
|
|
||||||
{#if $component.children === 0 && $builderStore.inBuilder}
|
|
||||||
<p><i class="ri-image-line" />Add some components to display.</p>
|
|
||||||
{:else}
|
|
||||||
{#each filteredRows as row}
|
|
||||||
<Provider data={row}>
|
|
||||||
<slot />
|
|
||||||
</Provider>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{:else if loaded && noRowsMessage}
|
|
||||||
<p><i class="ri-list-check-2" />{noRowsMessage}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</Provider>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
p {
|
|
||||||
margin: 0 var(--spacing-m);
|
|
||||||
background-color: var(--grey-2);
|
|
||||||
color: var(--grey-6);
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
padding: var(--spacing-l);
|
|
||||||
border-radius: var(--border-radius-s);
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
p i {
|
|
||||||
margin-bottom: var(--spacing-m);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: var(--grey-5);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
export let dataProvider
|
||||||
|
export let noRowsMessage
|
||||||
|
|
||||||
|
const { API, styleable, builderStore, Provider } = getContext("sdk")
|
||||||
|
const component = getContext("component")
|
||||||
|
const context = getContext("context")
|
||||||
|
|
||||||
|
$: rows = dataProvider?.rows ?? []
|
||||||
|
$: loaded = dataProvider?.loaded ?? false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div use:styleable={$component.styles}>
|
||||||
|
{#if rows.length > 0}
|
||||||
|
{#if $component.children === 0 && $builderStore.inBuilder}
|
||||||
|
<p><i class="ri-image-line" />Add some components to display.</p>
|
||||||
|
{:else}
|
||||||
|
{#each rows as row}
|
||||||
|
<Provider data={row}>
|
||||||
|
<slot />
|
||||||
|
</Provider>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{:else if loaded && noRowsMessage}
|
||||||
|
<p><i class="ri-list-check-2" />{noRowsMessage}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
margin: 0 var(--spacing-m);
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
color: var(--grey-6);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
padding: var(--spacing-l);
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
p i {
|
||||||
|
margin-bottom: var(--spacing-m);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--grey-5);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,57 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount, getContext } from "svelte"
|
|
||||||
|
|
||||||
export let table
|
|
||||||
|
|
||||||
const {
|
|
||||||
API,
|
|
||||||
screenStore,
|
|
||||||
routeStore,
|
|
||||||
Provider,
|
|
||||||
styleable,
|
|
||||||
ActionTypes,
|
|
||||||
} = getContext("sdk")
|
|
||||||
const component = getContext("component")
|
|
||||||
let headers = []
|
|
||||||
let row
|
|
||||||
|
|
||||||
const fetchFirstRow = async tableId => {
|
|
||||||
const rows = await API.fetchTableData(tableId)
|
|
||||||
return Array.isArray(rows) && rows.length ? rows[0] : { tableId }
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchData = async (rowId, tableId) => {
|
|
||||||
if (!tableId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathParts = window.location.pathname.split("/")
|
|
||||||
|
|
||||||
// if srcdoc, then we assume this is the builder preview
|
|
||||||
if ((pathParts.length === 0 || pathParts[0] === "srcdoc") && tableId) {
|
|
||||||
row = await fetchFirstRow(tableId)
|
|
||||||
} else if (rowId) {
|
|
||||||
row = await API.fetchRow({ tableId, rowId })
|
|
||||||
} else {
|
|
||||||
throw new Error("Row ID was not supplied to RowDetail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: actions = [
|
|
||||||
{
|
|
||||||
type: ActionTypes.RefreshDatasource,
|
|
||||||
callback: () => fetchData($routeStore.routeParams.id, table),
|
|
||||||
metadata: { datasource: { type: "table", tableId: table } },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
onMount(() => fetchData($routeStore.routeParams.id, table))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if row}
|
|
||||||
<Provider data={row} {actions}>
|
|
||||||
<div use:styleable={$component.styles}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Provider>
|
|
||||||
{/if}
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { chart } from "svelte-apexcharts"
|
import { chart } from "svelte-apexcharts"
|
||||||
|
|
||||||
const { styleable } = getContext("sdk")
|
const { styleable, builderStore } = getContext("sdk")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
||||||
export let options
|
export let options
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
{#if options}
|
{#if options}
|
||||||
<div use:chart={options} use:styleable={$component.styles} />
|
<div use:chart={options} use:styleable={$component.styles} />
|
||||||
{:else if options === false}
|
{:else if builderStore.inBuilder}
|
||||||
<div use:styleable={$component.styles}>
|
<div use:styleable={$component.styles}>
|
||||||
Use the settings panel to build your chart -->
|
Use the settings panel to build your chart -->
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
|
||||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||||
import ApexChart from "./ApexChart.svelte"
|
import ApexChart from "./ApexChart.svelte"
|
||||||
import { isEmpty } from "lodash/fp"
|
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let dataProvider
|
||||||
export let labelColumn
|
export let labelColumn
|
||||||
export let valueColumns
|
export let valueColumns
|
||||||
export let xAxisLabel
|
export let xAxisLabel
|
||||||
|
@ -22,28 +18,21 @@
|
||||||
export let yAxisUnits
|
export let yAxisUnits
|
||||||
export let palette
|
export let palette
|
||||||
|
|
||||||
let options
|
$: options = setUpChart(dataProvider)
|
||||||
|
|
||||||
// Fetch data on mount
|
const setUpChart = provider => {
|
||||||
onMount(async () => {
|
|
||||||
const allCols = [labelColumn, ...(valueColumns || [null])]
|
const allCols = [labelColumn, ...(valueColumns || [null])]
|
||||||
if (isEmpty(datasource) || allCols.find(x => x == null)) {
|
if (!provider || allCols.find(x => x == null)) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fatch data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const { schema, rows } = provider
|
||||||
const result = await API.fetchDatasource(datasource)
|
|
||||||
const reducer = row => (valid, column) => valid && row[column] != null
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
const data = result
|
const data = rows.filter(row => hasAllColumns(row)).slice(0, 100)
|
||||||
.filter(row => hasAllColumns(row))
|
|
||||||
.slice(0, 20)
|
|
||||||
.sort((a, b) => (a[labelColumn] > b[labelColumn] ? 1 : -1))
|
|
||||||
if (!schema || !data.length) {
|
if (!schema || !data.length) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise default chart
|
// Initialise default chart
|
||||||
|
@ -63,7 +52,7 @@
|
||||||
|
|
||||||
// Add data
|
// Add data
|
||||||
let useDates = false
|
let useDates = false
|
||||||
if (datasource.type !== "view" && schema[labelColumn]) {
|
if (schema[labelColumn]) {
|
||||||
const labelFieldType = schema[labelColumn].type
|
const labelFieldType = schema[labelColumn].type
|
||||||
builder = builder.xType(labelFieldType)
|
builder = builder.xType(labelFieldType)
|
||||||
useDates = labelFieldType === "datetime"
|
useDates = labelFieldType === "datetime"
|
||||||
|
@ -84,8 +73,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build chart options
|
// Build chart options
|
||||||
options = builder.getOptions()
|
return builder.getOptions()
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ApexChart {options} />
|
<ApexChart {options} />
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
|
||||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||||
import ApexChart from "./ApexChart.svelte"
|
import ApexChart from "./ApexChart.svelte"
|
||||||
import { isEmpty } from "lodash/fp"
|
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let dataProvider
|
||||||
export let dateColumn
|
export let dateColumn
|
||||||
export let openColumn
|
export let openColumn
|
||||||
export let highColumn
|
export let highColumn
|
||||||
|
@ -20,28 +16,22 @@
|
||||||
export let animate
|
export let animate
|
||||||
export let yAxisUnits
|
export let yAxisUnits
|
||||||
|
|
||||||
let options
|
$: options = setUpChart(dataProvider)
|
||||||
|
|
||||||
// Fetch data on mount
|
// Fetch data on mount
|
||||||
onMount(async () => {
|
const setUpChart = provider => {
|
||||||
const allCols = [dateColumn, openColumn, highColumn, lowColumn, closeColumn]
|
const allCols = [dateColumn, openColumn, highColumn, lowColumn, closeColumn]
|
||||||
if (isEmpty(datasource) || allCols.find(x => x == null)) {
|
if (!provider || allCols.find(x => x == null)) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const { schema, rows } = provider
|
||||||
const result = await API.fetchDatasource(datasource)
|
|
||||||
const reducer = row => (valid, column) => valid && row[column] != null
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
const data = result
|
const data = rows.filter(row => hasAllColumns(row))
|
||||||
.filter(row => hasAllColumns(row))
|
|
||||||
.slice(0, 100)
|
|
||||||
.sort((a, b) => (a[dateColumn] > b[dateColumn] ? 1 : -1))
|
|
||||||
if (!schema || !data.length) {
|
if (!schema || !data.length) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise default chart
|
// Initialise default chart
|
||||||
|
@ -66,8 +56,8 @@
|
||||||
builder = builder.series([{ data: chartData }])
|
builder = builder.series([{ data: chartData }])
|
||||||
|
|
||||||
// Build chart options
|
// Build chart options
|
||||||
options = builder.getOptions()
|
return builder.getOptions()
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ApexChart {options} />
|
<ApexChart {options} />
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
|
||||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||||
import ApexChart from "./ApexChart.svelte"
|
import ApexChart from "./ApexChart.svelte"
|
||||||
import { isEmpty } from "lodash/fp"
|
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
|
||||||
|
|
||||||
// Common props
|
// Common props
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let dataProvider
|
||||||
export let labelColumn
|
export let labelColumn
|
||||||
export let valueColumns
|
export let valueColumns
|
||||||
export let xAxisLabel
|
export let xAxisLabel
|
||||||
|
@ -28,28 +24,22 @@
|
||||||
export let stacked
|
export let stacked
|
||||||
export let gradient
|
export let gradient
|
||||||
|
|
||||||
let options
|
$: options = setUpChart(dataProvider)
|
||||||
|
|
||||||
// Fetch data on mount
|
// Fetch data on mount
|
||||||
onMount(async () => {
|
const setUpChart = provider => {
|
||||||
const allCols = [labelColumn, ...(valueColumns || [null])]
|
const allCols = [labelColumn, ...(valueColumns || [null])]
|
||||||
if (isEmpty(datasource) || allCols.find(x => x == null)) {
|
if (!provider || allCols.find(x => x == null)) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch, filter and sort data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const { schema, rows } = provider
|
||||||
const result = await API.fetchDatasource(datasource)
|
|
||||||
const reducer = row => (valid, column) => valid && row[column] != null
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
const data = result
|
const data = rows.filter(row => hasAllColumns(row))
|
||||||
.filter(row => hasAllColumns(row))
|
|
||||||
.slice(0, 100)
|
|
||||||
.sort((a, b) => (a[labelColumn] > b[labelColumn] ? 1 : -1))
|
|
||||||
if (!schema || !data.length) {
|
if (!schema || !data.length) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise default chart
|
// Initialise default chart
|
||||||
|
@ -71,7 +61,7 @@
|
||||||
|
|
||||||
// Add data
|
// Add data
|
||||||
let useDates = false
|
let useDates = false
|
||||||
if (datasource.type !== "view" && schema[labelColumn]) {
|
if (schema[labelColumn]) {
|
||||||
const labelFieldType = schema[labelColumn].type
|
const labelFieldType = schema[labelColumn].type
|
||||||
builder = builder.xType(labelFieldType)
|
builder = builder.xType(labelFieldType)
|
||||||
useDates = labelFieldType === "datetime"
|
useDates = labelFieldType === "datetime"
|
||||||
|
@ -92,8 +82,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build chart options
|
// Build chart options
|
||||||
options = builder.getOptions()
|
return builder.getOptions()
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ApexChart {options} />
|
<ApexChart {options} />
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
|
||||||
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||||
import ApexChart from "./ApexChart.svelte"
|
import ApexChart from "./ApexChart.svelte"
|
||||||
import { isEmpty } from "lodash/fp"
|
|
||||||
|
|
||||||
const { API } = getContext("sdk")
|
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
export let datasource
|
export let dataProvider
|
||||||
export let labelColumn
|
export let labelColumn
|
||||||
export let valueColumn
|
export let valueColumn
|
||||||
export let height
|
export let height
|
||||||
|
@ -19,25 +15,21 @@
|
||||||
export let donut
|
export let donut
|
||||||
export let palette
|
export let palette
|
||||||
|
|
||||||
let options
|
$: options = setUpChart(dataProvider)
|
||||||
|
|
||||||
// Fetch data on mount
|
// Fetch data on mount
|
||||||
onMount(async () => {
|
const setUpChart = provider => {
|
||||||
if (isEmpty(datasource) || !labelColumn || !valueColumn) {
|
if (!provider || !labelColumn || !valueColumn) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch, filter and sort data
|
// Fetch, filter and sort data
|
||||||
const schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
const { schema, rows } = provider
|
||||||
const result = await API.fetchDatasource(datasource)
|
const data = rows
|
||||||
const data = result
|
|
||||||
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
|
.filter(row => row[labelColumn] != null && row[valueColumn] != null)
|
||||||
.slice(0, 20)
|
.slice(0, 100)
|
||||||
.sort((a, b) => (a[labelColumn] > b[labelColumn] ? 1 : -1))
|
|
||||||
if (!schema || !data.length) {
|
if (!schema || !data.length) {
|
||||||
options = false
|
return null
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise default chart
|
// Initialise default chart
|
||||||
|
@ -58,8 +50,8 @@
|
||||||
builder = builder.series(series).labels(labels)
|
builder = builder.series(series).labels(labels)
|
||||||
|
|
||||||
// Build chart options
|
// Build chart options
|
||||||
options = builder.getOptions()
|
return builder.getOptions()
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ApexChart {options} />
|
<ApexChart {options} />
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { createValidatorFromConstraints } from "./validation"
|
import { createValidatorFromConstraints } from "./validation"
|
||||||
import { generateID } from "../helpers"
|
import { generateID } from "../helpers"
|
||||||
|
|
||||||
export let datasource
|
export let dataSource
|
||||||
export let theme
|
export let theme
|
||||||
export let size
|
export let size
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -143,15 +143,15 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the form schema from this form's datasource, if one exists
|
// Fetches the form schema from this form's dataSource, if one exists
|
||||||
const fetchSchema = async () => {
|
const fetchSchema = async () => {
|
||||||
if (!datasource?.tableId) {
|
if (!dataSource?.tableId) {
|
||||||
schema = {}
|
schema = {}
|
||||||
table = null
|
table = null
|
||||||
} else {
|
} else {
|
||||||
table = await API.fetchTableDefinition(datasource?.tableId)
|
table = await API.fetchTableDefinition(dataSource?.tableId)
|
||||||
if (table) {
|
if (table) {
|
||||||
if (datasource?.type === "query") {
|
if (dataSource?.type === "query") {
|
||||||
schema = {}
|
schema = {}
|
||||||
const params = table.parameters || []
|
const params = table.parameters || []
|
||||||
params.forEach(param => {
|
params.forEach(param => {
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
|
|
||||||
<Provider
|
<Provider
|
||||||
{actions}
|
{actions}
|
||||||
data={{ ...$formState.values, tableId: datasource?.tableId }}>
|
data={{ ...$formState.values, tableId: dataSource?.tableId }}>
|
||||||
<div
|
<div
|
||||||
lang="en"
|
lang="en"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
{#if fieldState}
|
{#if fieldState}
|
||||||
<button
|
<button
|
||||||
id={$fieldState.fieldId}
|
id={$fieldState.fieldId}
|
||||||
class="spectrum-Picker"
|
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||||
disabled={$fieldState.disabled}
|
disabled={$fieldState.disabled}
|
||||||
class:is-invalid={!$fieldState.valid}
|
class:is-invalid={!$fieldState.valid}
|
||||||
class:is-open={open}
|
class:is-open={open}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
<script>
|
|
||||||
import AttachmentList from "../../attachments/AttachmentList.svelte"
|
|
||||||
export let files
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<AttachmentList {files} on:delete />
|
|
|
@ -1,193 +0,0 @@
|
||||||
<script>
|
|
||||||
// Import valueSetters and custom renderers
|
|
||||||
import { number } from "./valueSetters"
|
|
||||||
import { getRenderer } from "./customRenderer"
|
|
||||||
import { isEmpty } from "lodash/fp"
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import AgGrid from "@budibase/svelte-ag-grid"
|
|
||||||
import {
|
|
||||||
TextButton as DeleteButton,
|
|
||||||
Icon,
|
|
||||||
Modal,
|
|
||||||
ModalContent,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
|
|
||||||
// These maps need to be set up to handle whatever types that are used in the tables.
|
|
||||||
const setters = new Map([["number", number]])
|
|
||||||
const SDK = getContext("sdk")
|
|
||||||
const component = getContext("component")
|
|
||||||
const { API, styleable } = SDK
|
|
||||||
|
|
||||||
export let datasource = {}
|
|
||||||
export let editable
|
|
||||||
export let theme = "alpine"
|
|
||||||
export let height = 500
|
|
||||||
export let pagination
|
|
||||||
export let detailUrl
|
|
||||||
|
|
||||||
// Add setting height as css var to allow grid to use correct height
|
|
||||||
$: gridStyles = {
|
|
||||||
...$component.styles,
|
|
||||||
normal: {
|
|
||||||
...$component.styles.normal,
|
|
||||||
["--grid-height"]: `${height}px`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
$: fetchData(datasource)
|
|
||||||
|
|
||||||
// These can never change at runtime so don't need to be reactive
|
|
||||||
let canEdit = editable && datasource && datasource.type !== "view"
|
|
||||||
let canAddDelete = editable && datasource && datasource.type === "table"
|
|
||||||
|
|
||||||
let modal
|
|
||||||
let dataLoaded = false
|
|
||||||
let data
|
|
||||||
let columnDefs
|
|
||||||
let selectedRows = []
|
|
||||||
let table
|
|
||||||
let options = {
|
|
||||||
defaultColDef: {
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 150,
|
|
||||||
filter: true,
|
|
||||||
},
|
|
||||||
rowSelection: canEdit ? "multiple" : false,
|
|
||||||
suppressFieldDotNotation: true,
|
|
||||||
suppressRowClickSelection: !canEdit,
|
|
||||||
paginationAutoPageSize: true,
|
|
||||||
pagination,
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchData(datasource) {
|
|
||||||
if (isEmpty(datasource)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = await API.fetchDatasource(datasource)
|
|
||||||
|
|
||||||
let schema
|
|
||||||
|
|
||||||
// Get schema for datasource
|
|
||||||
// Views with "Calculate" applied provide their own schema.
|
|
||||||
// For everything else, use the tableId property to pull to table schema
|
|
||||||
if (datasource.schema) {
|
|
||||||
schema = datasource.schema
|
|
||||||
} else {
|
|
||||||
schema = (await API.fetchTableDefinition(datasource.tableId)).schema
|
|
||||||
}
|
|
||||||
|
|
||||||
columnDefs = Object.keys(schema).map((key, i) => {
|
|
||||||
return {
|
|
||||||
headerCheckboxSelection: i === 0 && canEdit,
|
|
||||||
checkboxSelection: i === 0 && canEdit,
|
|
||||||
valueSetter: setters.get(schema[key].type),
|
|
||||||
headerName: key,
|
|
||||||
field: key,
|
|
||||||
hide: shouldHideField(key),
|
|
||||||
sortable: true,
|
|
||||||
editable: canEdit && schema[key].type !== "link",
|
|
||||||
cellRenderer: getRenderer(schema[key], canEdit, SDK),
|
|
||||||
autoHeight: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (detailUrl) {
|
|
||||||
columnDefs = [
|
|
||||||
...columnDefs,
|
|
||||||
{
|
|
||||||
headerName: "Detail",
|
|
||||||
field: "_id",
|
|
||||||
minWidth: 100,
|
|
||||||
width: 100,
|
|
||||||
flex: 0,
|
|
||||||
editable: false,
|
|
||||||
sortable: false,
|
|
||||||
cellRenderer: getRenderer(
|
|
||||||
{
|
|
||||||
type: "_id",
|
|
||||||
options: { detailUrl },
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
SDK
|
|
||||||
),
|
|
||||||
autoHeight: true,
|
|
||||||
pinned: "left",
|
|
||||||
filter: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
dataLoaded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldHideField = name => {
|
|
||||||
if (name.startsWith("_")) return true
|
|
||||||
// always 'row'
|
|
||||||
if (name === "type") return true
|
|
||||||
// tables are always tied to a single tableId, this is irrelevant
|
|
||||||
if (name === "tableId") return true
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpdate = ({ detail }) => {
|
|
||||||
data[detail.row] = detail.data
|
|
||||||
updateRow(detail.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateRow = async row => {
|
|
||||||
await API.updateRow(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteRows = async () => {
|
|
||||||
await API.deleteRows({ rows: selectedRows, tableId: datasource.name })
|
|
||||||
data = data.filter(row => !selectedRows.includes(row))
|
|
||||||
selectedRows = []
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container" use:styleable={gridStyles}>
|
|
||||||
{#if dataLoaded}
|
|
||||||
{#if canAddDelete}
|
|
||||||
<div class="controls">
|
|
||||||
{#if selectedRows.length > 0}
|
|
||||||
<DeleteButton text small on:click={modal.show()}>
|
|
||||||
<Icon name="addrow" />
|
|
||||||
Delete
|
|
||||||
{selectedRows.length}
|
|
||||||
row(s)
|
|
||||||
</DeleteButton>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<AgGrid
|
|
||||||
{theme}
|
|
||||||
{options}
|
|
||||||
{data}
|
|
||||||
{columnDefs}
|
|
||||||
on:update={handleUpdate}
|
|
||||||
on:select={({ detail }) => (selectedRows = detail)} />
|
|
||||||
{/if}
|
|
||||||
<Modal bind:this={modal}>
|
|
||||||
<ModalContent
|
|
||||||
title="Confirm Row Deletion"
|
|
||||||
confirmText="Delete"
|
|
||||||
onConfirm={deleteRows}>
|
|
||||||
<span>Are you sure you want to delete {selectedRows.length} row(s)?</span>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container :global(.ag-pinned-left-header .ag-header-cell-label) {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
min-height: 15px;
|
|
||||||
margin-bottom: var(--spacing-s);
|
|
||||||
display: grid;
|
|
||||||
grid-gap: var(--spacing-s);
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,37 +0,0 @@
|
||||||
<script>
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui"
|
|
||||||
import Modal from "./Modal.svelte"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
let anchor
|
|
||||||
let dropdown
|
|
||||||
|
|
||||||
export let table
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={anchor}>
|
|
||||||
<Button text small on:click={dropdown.show}>
|
|
||||||
<Icon name="addrow" />
|
|
||||||
Create New Row
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu bind:this={dropdown} {anchor} align="left">
|
|
||||||
<h5>Add New Row</h5>
|
|
||||||
<Modal
|
|
||||||
{table}
|
|
||||||
onClosed={dropdown.hide}
|
|
||||||
on:newRow={() => dispatch('newRow')} />
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,144 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext, onMount, createEventDispatcher } from "svelte"
|
|
||||||
import { Button, Label, DatePicker, RichText } from "@budibase/bbui"
|
|
||||||
import Dropzone from "../../attachments/Dropzone.svelte"
|
|
||||||
import debounce from "lodash.debounce"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const { fetchRow, saveRow, routeStore } = getContext("sdk")
|
|
||||||
|
|
||||||
const DEFAULTS_FOR_TYPE = {
|
|
||||||
string: "",
|
|
||||||
boolean: false,
|
|
||||||
number: null,
|
|
||||||
link: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
export let table
|
|
||||||
export let onClosed
|
|
||||||
|
|
||||||
let row = { tableId: table._id }
|
|
||||||
let schema = table.schema
|
|
||||||
let saved = false
|
|
||||||
let rowId
|
|
||||||
let isNew = true
|
|
||||||
let errors = {}
|
|
||||||
|
|
||||||
$: fields = schema ? Object.keys(schema) : []
|
|
||||||
|
|
||||||
$: errorMessages = Object.entries(errors).map(
|
|
||||||
([field, message]) => `${field} ${message}`
|
|
||||||
)
|
|
||||||
|
|
||||||
const save = debounce(async () => {
|
|
||||||
for (let field of fields) {
|
|
||||||
// Assign defaults to empty fields to prevent validation issues
|
|
||||||
if (!(field in row)) {
|
|
||||||
row[field] = DEFAULTS_FOR_TYPE[schema[field].type]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await saveRow(row)
|
|
||||||
|
|
||||||
if (!response.error) {
|
|
||||||
// store.update(state => {
|
|
||||||
// state[table._id] = state[table._id]
|
|
||||||
// ? [...state[table._id], json]
|
|
||||||
// : [json]
|
|
||||||
// return state
|
|
||||||
// })
|
|
||||||
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
// wipe form, if new row, otherwise update
|
|
||||||
// table to get new _rev
|
|
||||||
row = isNew ? { tableId: table._id } : response
|
|
||||||
|
|
||||||
onClosed()
|
|
||||||
dispatch("newRow")
|
|
||||||
} else {
|
|
||||||
errors = [response.error]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const routeParams = $routeStore.routeParams
|
|
||||||
rowId =
|
|
||||||
Object.keys(routeParams).length > 0 && (routeParams.id || routeParams[0])
|
|
||||||
isNew = !rowId || rowId === "new"
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
row = { tableId: table }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
row = await fetchRow({ tableId: table._id, rowId })
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
{#each errorMessages as error}
|
|
||||||
<p class="error">{error}</p>
|
|
||||||
{/each}
|
|
||||||
<form on:submit|preventDefault>
|
|
||||||
{#each fields as field}
|
|
||||||
<div class="form-item">
|
|
||||||
<Label small forAttr={'form-stacked-text'}>{field}</Label>
|
|
||||||
{#if schema[field].type === 'string' && schema[field].constraints.inclusion}
|
|
||||||
<select bind:value={row[field]}>
|
|
||||||
{#each schema[field].constraints.inclusion as opt}
|
|
||||||
<option>{opt}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{:else if schema[field].type === 'datetime'}
|
|
||||||
<DatePicker bind:value={row[field]} />
|
|
||||||
{:else if schema[field].type === 'boolean'}
|
|
||||||
<input class="input" type="checkbox" bind:checked={row[field]} />
|
|
||||||
{:else if schema[field].type === 'number'}
|
|
||||||
<input class="input" type="number" bind:value={row[field]} />
|
|
||||||
{:else if schema[field].type === 'string'}
|
|
||||||
<input class="input" type="text" bind:value={row[field]} />
|
|
||||||
{:else if schema[field].type === 'longform'}
|
|
||||||
<RichText bind:value={row[field]} />
|
|
||||||
{:else if schema[field].type === 'attachment'}
|
|
||||||
<Dropzone bind:files={row[field]} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{/each}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<div class="button-margin-3">
|
|
||||||
<Button secondary on:click={onClosed}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
<div class="button-margin-4">
|
|
||||||
<Button primary on:click={save}>Save</Button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.actions {
|
|
||||||
padding: var(--spacing-l) var(--spacing-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
padding: 20px 30px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
background: var(--grey-1);
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-3 {
|
|
||||||
grid-column-start: 3;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-margin-4 {
|
|
||||||
grid-column-start: 4;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<script>
|
|
||||||
import { DatePicker } from "@budibase/bbui"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DatePicker />
|
|
|
@ -1,75 +0,0 @@
|
||||||
<script>
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
export let columnName
|
|
||||||
export let row
|
|
||||||
export let SDK
|
|
||||||
|
|
||||||
const { API } = SDK
|
|
||||||
|
|
||||||
$: count =
|
|
||||||
row && columnName && Array.isArray(row[columnName])
|
|
||||||
? row[columnName].length
|
|
||||||
: 0
|
|
||||||
let linkedRows = []
|
|
||||||
let displayColumn
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
linkedRows = await API.fetchRelationshipData({
|
|
||||||
tableId: row.tableId,
|
|
||||||
rowId: row._id,
|
|
||||||
fieldName: columnName,
|
|
||||||
})
|
|
||||||
if (linkedRows && linkedRows.length) {
|
|
||||||
const table = await API.fetchTableDefinition(linkedRows[0].tableId)
|
|
||||||
if (table && table.primaryDisplay) {
|
|
||||||
displayColumn = table.primaryDisplay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function fetchLinkedRowsData(row, columnName) {
|
|
||||||
if (!row || !row._id) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return await API.fetchRelationshipData({
|
|
||||||
tableId: row.tableId,
|
|
||||||
rowId: row._id,
|
|
||||||
fieldName: columnName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{#if linkedRows && linkedRows.length && displayColumn}
|
|
||||||
{#each linkedRows as linkedRow}
|
|
||||||
{#if linkedRow[displayColumn] != null && linkedRow[displayColumn] !== ''}
|
|
||||||
<div class="linked-row">{linkedRow[displayColumn]}</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{:else}{count} related row(s){/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-xs);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This styling is opinionated to ensure these always look consistent */
|
|
||||||
.linked-row {
|
|
||||||
color: white;
|
|
||||||
background-color: #616161;
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
padding: var(--spacing-xs) var(--spacing-s) calc(var(--spacing-xs) + 1px)
|
|
||||||
var(--spacing-s);
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 0.8em;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,32 +0,0 @@
|
||||||
<script>
|
|
||||||
export let columnName
|
|
||||||
export let row
|
|
||||||
|
|
||||||
$: items = row?.[columnName] || []
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{#each items as item}
|
|
||||||
<div class="item">{item?.primaryDisplay ?? ''}</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-xs);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
padding: var(--spacing-xs) var(--spacing-s);
|
|
||||||
border: 1px solid var(--grey-5);
|
|
||||||
color: var(--grey-7);
|
|
||||||
line-height: normal;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,17 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Select } from "@budibase/bbui"
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
export let value
|
|
||||||
export let options
|
|
||||||
|
|
||||||
$: dispatch("change", value)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Select label={false} bind:value>
|
|
||||||
<option value="">Choose an option</option>
|
|
||||||
{#each options as option}
|
|
||||||
<option value={option}>{option}</option>
|
|
||||||
{/each}
|
|
||||||
</Select>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<script>
|
|
||||||
import { Button } from "@budibase/bbui"
|
|
||||||
|
|
||||||
export let url
|
|
||||||
export let SDK
|
|
||||||
|
|
||||||
const { linkable } = SDK
|
|
||||||
|
|
||||||
let link
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a href={url} bind:this={link} use:linkable />
|
|
||||||
<Button small translucent on:click={() => link.click()}>View</Button>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
a {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,169 +0,0 @@
|
||||||
// Custom renderers to handle special types
|
|
||||||
// https://www.ag-grid.com/javascript-grid-cell-rendering-components/
|
|
||||||
|
|
||||||
import AttachmentCell from "./AttachmentCell/Button.svelte"
|
|
||||||
import ViewDetails from "./ViewDetails/Cell.svelte"
|
|
||||||
import Select from "./Select/Wrapper.svelte"
|
|
||||||
import DatePicker from "./DateTime/Wrapper.svelte"
|
|
||||||
import RelationshipLabel from "./Relationship/RelationshipLabel.svelte"
|
|
||||||
|
|
||||||
const renderers = new Map([
|
|
||||||
["boolean", booleanRenderer],
|
|
||||||
["attachment", attachmentRenderer],
|
|
||||||
["options", optionsRenderer],
|
|
||||||
["link", linkedRowRenderer],
|
|
||||||
["_id", viewDetailsRenderer],
|
|
||||||
])
|
|
||||||
|
|
||||||
export function getRenderer(schema, editable, SDK) {
|
|
||||||
if (renderers.get(schema.type)) {
|
|
||||||
return renderers.get(schema.type)(
|
|
||||||
schema.options,
|
|
||||||
schema.constraints,
|
|
||||||
editable,
|
|
||||||
SDK
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
function booleanRenderer(options, constraints, editable, SDK) {
|
|
||||||
return params => {
|
|
||||||
const toggle = e => {
|
|
||||||
params.value = !params.value
|
|
||||||
params.setValue(e.currentTarget.checked)
|
|
||||||
}
|
|
||||||
let input = document.createElement("input")
|
|
||||||
input.style.display = "grid"
|
|
||||||
input.style.placeItems = "center"
|
|
||||||
input.style.height = "100%"
|
|
||||||
input.type = "checkbox"
|
|
||||||
input.checked = params.value
|
|
||||||
if (editable) {
|
|
||||||
input.addEventListener("click", toggle)
|
|
||||||
} else {
|
|
||||||
input.disabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
function attachmentRenderer(options, constraints, editable, SDK) {
|
|
||||||
return params => {
|
|
||||||
const container = document.createElement("div")
|
|
||||||
|
|
||||||
const attachmentInstance = new AttachmentCell({
|
|
||||||
target: container,
|
|
||||||
props: {
|
|
||||||
files: params.value || [],
|
|
||||||
SDK,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const deleteFile = event => {
|
|
||||||
const newFilesArray = params.value.filter(file => file !== event.detail)
|
|
||||||
params.setValue(newFilesArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
attachmentInstance.$on("delete", deleteFile)
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
function dateRenderer(options, constraints, editable, SDK) {
|
|
||||||
return function(params) {
|
|
||||||
const container = document.createElement("div")
|
|
||||||
const toggle = e => {
|
|
||||||
params.setValue(e.detail[0][0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options need to be passed in with minTime and maxTime! Needs bbui update.
|
|
||||||
new DatePicker({
|
|
||||||
target: container,
|
|
||||||
props: {
|
|
||||||
value: params.value,
|
|
||||||
SDK,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function optionsRenderer(options, constraints, editable, SDK) {
|
|
||||||
return params => {
|
|
||||||
if (!editable) return params.value
|
|
||||||
const container = document.createElement("div")
|
|
||||||
container.style.display = "grid"
|
|
||||||
container.style.placeItems = "center"
|
|
||||||
container.style.height = "100%"
|
|
||||||
const change = e => {
|
|
||||||
params.setValue(e.detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectInstance = new Select({
|
|
||||||
target: container,
|
|
||||||
props: {
|
|
||||||
value: params.value,
|
|
||||||
options: constraints.inclusion,
|
|
||||||
SDK,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
selectInstance.$on("change", change)
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
function linkedRowRenderer(options, constraints, editable, SDK) {
|
|
||||||
return params => {
|
|
||||||
let container = document.createElement("div")
|
|
||||||
container.style.display = "grid"
|
|
||||||
container.style.placeItems = "center"
|
|
||||||
container.style.height = "100%"
|
|
||||||
|
|
||||||
new RelationshipLabel({
|
|
||||||
target: container,
|
|
||||||
props: {
|
|
||||||
row: params.data,
|
|
||||||
columnName: params.column.colId,
|
|
||||||
SDK,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
function viewDetailsRenderer(options, constraints, editable, SDK) {
|
|
||||||
return params => {
|
|
||||||
let container = document.createElement("div")
|
|
||||||
container.style.display = "grid"
|
|
||||||
container.style.alignItems = "center"
|
|
||||||
container.style.height = "100%"
|
|
||||||
|
|
||||||
let url = "/"
|
|
||||||
if (options.detailUrl) {
|
|
||||||
url = options.detailUrl.replace(":id", params.data._id)
|
|
||||||
}
|
|
||||||
if (!url.startsWith("/")) {
|
|
||||||
url = `/${url}`
|
|
||||||
}
|
|
||||||
|
|
||||||
new ViewDetails({
|
|
||||||
target: container,
|
|
||||||
props: {
|
|
||||||
url,
|
|
||||||
SDK,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return container
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
// https://www.ag-grid.com/javascript-grid-value-setters/
|
|
||||||
// These handles values and makes sure they adhere to the data type provided by the table
|
|
||||||
export const number = params => {
|
|
||||||
params.data[params.colDef.field] = parseFloat(params.newValue)
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -13,17 +13,16 @@ import { loadSpectrumIcons } from "./spectrum-icons"
|
||||||
loadSpectrumIcons()
|
loadSpectrumIcons()
|
||||||
|
|
||||||
export { default as container } from "./Container.svelte"
|
export { default as container } from "./Container.svelte"
|
||||||
export { default as datagrid } from "./grid/Component.svelte"
|
export { default as dataprovider } from "./DataProvider.svelte"
|
||||||
export { default as screenslot } from "./ScreenSlot.svelte"
|
export { default as screenslot } from "./ScreenSlot.svelte"
|
||||||
export { default as button } from "./Button.svelte"
|
export { default as button } from "./Button.svelte"
|
||||||
export { default as list } from "./List.svelte"
|
export { default as repeater } from "./Repeater.svelte"
|
||||||
export { default as stackedlist } from "./StackedList.svelte"
|
export { default as stackedlist } from "./StackedList.svelte"
|
||||||
export { default as card } from "./Card.svelte"
|
export { default as card } from "./Card.svelte"
|
||||||
export { default as text } from "./Text.svelte"
|
export { default as text } from "./Text.svelte"
|
||||||
export { default as login } from "./Login.svelte"
|
export { default as login } from "./Login.svelte"
|
||||||
export { default as navigation } from "./Navigation.svelte"
|
export { default as navigation } from "./Navigation.svelte"
|
||||||
export { default as link } from "./Link.svelte"
|
export { default as link } from "./Link.svelte"
|
||||||
export { default as rowdetail } from "./RowDetail.svelte"
|
|
||||||
export { default as heading } from "./Heading.svelte"
|
export { default as heading } from "./Heading.svelte"
|
||||||
export { default as image } from "./Image.svelte"
|
export { default as image } from "./Image.svelte"
|
||||||
export { default as embed } from "./Embed.svelte"
|
export { default as embed } from "./Embed.svelte"
|
||||||
|
@ -34,3 +33,4 @@ export { default as search } from "./Search.svelte"
|
||||||
export { default as backgroundimage } from "./BackgroundImage.svelte"
|
export { default as backgroundimage } from "./BackgroundImage.svelte"
|
||||||
export * from "./charts"
|
export * from "./charts"
|
||||||
export * from "./forms"
|
export * from "./forms"
|
||||||
|
export * from "./table"
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const displayLimit = 5
|
||||||
|
$: attachments = value?.slice(0, displayLimit) ?? []
|
||||||
|
$: leftover = (value?.length ?? 0) - attachments.length
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each attachments as attachment}
|
||||||
|
{#if attachment.type.startsWith('image')}
|
||||||
|
<img src={attachment.url} alt={attachment.extension} />
|
||||||
|
{:else}
|
||||||
|
<div class="file">{attachment.extension}</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{#if leftover}
|
||||||
|
<div>+{leftover} more</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
height: 32px;
|
||||||
|
max-width: 64px;
|
||||||
|
}
|
||||||
|
.file {
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--spectrum-global-color-gray-800);
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
border-radius: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/checkbox/dist/index-vars.css"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label
|
||||||
|
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="spectrum-Checkbox-input"
|
||||||
|
id="checkbox-1"
|
||||||
|
disabled
|
||||||
|
checked={!!value} />
|
||||||
|
<span class="spectrum-Checkbox-box">
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true">
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true">
|
||||||
|
<use xlink:href="#spectrum-css-icon-Dash100" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spectrum-Checkbox {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.spectrum-Checkbox-box {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script>
|
||||||
|
import StringRenderer from "./StringRenderer.svelte"
|
||||||
|
import BooleanRenderer from "./BooleanRenderer.svelte"
|
||||||
|
import DateTimeRenderer from "./DateTimeRenderer.svelte"
|
||||||
|
import RelationshipRenderer from "./RelationshipRenderer.svelte"
|
||||||
|
import AttachmentRenderer from "./AttachmentRenderer.svelte"
|
||||||
|
|
||||||
|
export let schema
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const plainTypes = ["string", "options", "number", "longform"]
|
||||||
|
$: type = schema?.type ?? "string"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value != null && value !== ''}
|
||||||
|
{#if plainTypes.includes(type)}
|
||||||
|
<StringRenderer {value} />
|
||||||
|
{:else if type === 'boolean'}
|
||||||
|
<BooleanRenderer {value} />
|
||||||
|
{:else if type === 'datetime'}
|
||||||
|
<DateTimeRenderer {value} />
|
||||||
|
{:else if type === 'link'}
|
||||||
|
<RelationshipRenderer {value} />
|
||||||
|
{:else if type === 'attachment'}
|
||||||
|
<AttachmentRenderer {value} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{dayjs(value).format('MMMM D YYYY, HH:mm')}</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/label/dist/index-vars.css"
|
||||||
|
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const displayLimit = 5
|
||||||
|
$: relationships = value?.slice(0, displayLimit) ?? []
|
||||||
|
$: leftover = (value?.length ?? 0) - relationships.length
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each relationships as relationship}
|
||||||
|
{#if relationship?.primaryDisplay}
|
||||||
|
<span class="spectrum-Label spectrum-Label--grey">
|
||||||
|
{relationship.primaryDisplay}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{#if leftover}
|
||||||
|
<div>+{leftover} more</div>
|
||||||
|
{/if}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>{value}</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,290 @@
|
||||||
|
<script>
|
||||||
|
import { fade } from "svelte/transition"
|
||||||
|
import "@spectrum-css/table/dist/index-vars.css"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import CellRenderer from "./CellRenderer.svelte"
|
||||||
|
|
||||||
|
export let theme
|
||||||
|
export let size
|
||||||
|
export let dataProvider
|
||||||
|
export let columns
|
||||||
|
export let showAutoColumns
|
||||||
|
export let rowCount
|
||||||
|
export let quiet
|
||||||
|
|
||||||
|
const component = getContext("component")
|
||||||
|
const { styleable, Provider } = getContext("sdk")
|
||||||
|
|
||||||
|
// Config
|
||||||
|
const rowHeight = 55
|
||||||
|
const headerHeight = 36
|
||||||
|
const rowPreload = 5
|
||||||
|
const maxRows = 100
|
||||||
|
|
||||||
|
// Sorting state
|
||||||
|
let sortColumn
|
||||||
|
let sortOrder
|
||||||
|
|
||||||
|
// Table state
|
||||||
|
$: loaded = dataProvider?.loaded ?? false
|
||||||
|
$: rows = dataProvider?.rows ?? []
|
||||||
|
$: visibleRowCount = loaded
|
||||||
|
? Math.min(rows.length, rowCount || maxRows, maxRows)
|
||||||
|
: Math.min(8, rowCount || maxRows)
|
||||||
|
$: scroll = rows.length > visibleRowCount
|
||||||
|
$: contentStyle = getContentStyle(visibleRowCount, scroll || !loaded)
|
||||||
|
$: sortedRows = sortRows(rows, sortColumn, sortOrder)
|
||||||
|
$: schema = dataProvider?.schema ?? {}
|
||||||
|
$: fields = getFields(schema, columns, showAutoColumns)
|
||||||
|
|
||||||
|
// Scrolling state
|
||||||
|
let timeout
|
||||||
|
let nextScrollTop = 0
|
||||||
|
let scrollTop = 0
|
||||||
|
$: firstVisibleRow = calculateFirstVisibleRow(scrollTop)
|
||||||
|
$: lastVisibleRow = calculateLastVisibleRow(
|
||||||
|
firstVisibleRow,
|
||||||
|
visibleRowCount,
|
||||||
|
rows.length
|
||||||
|
)
|
||||||
|
|
||||||
|
const getContentStyle = (visibleRows, useFixedHeight) => {
|
||||||
|
if (!useFixedHeight) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return `height: ${headerHeight - 1 + visibleRows * (rowHeight + 1)}px;`
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortRows = (rows, sortColumn, sortOrder) => {
|
||||||
|
if (!sortColumn || !sortOrder) {
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
return rows.slice().sort((a, b) => {
|
||||||
|
const colA = a[sortColumn]
|
||||||
|
const colB = b[sortColumn]
|
||||||
|
if (sortOrder === "Descending") {
|
||||||
|
return colA > colB ? -1 : 1
|
||||||
|
} else {
|
||||||
|
return colA > colB ? 1 : -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortBy = field => {
|
||||||
|
if (field === sortColumn) {
|
||||||
|
sortOrder = sortOrder === "Descending" ? "Ascending" : "Descending"
|
||||||
|
} else {
|
||||||
|
sortColumn = field
|
||||||
|
sortOrder = "Descending"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFields = (schema, customColumns, showAutoColumns) => {
|
||||||
|
// Check for an invalid column selection
|
||||||
|
let invalid = false
|
||||||
|
customColumns?.forEach(column => {
|
||||||
|
if (schema[column] == null) {
|
||||||
|
invalid = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use column selection if it exists
|
||||||
|
if (!invalid && customColumns?.length) {
|
||||||
|
return customColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise generate columns
|
||||||
|
let columns = []
|
||||||
|
let autoColumns = []
|
||||||
|
Object.entries(schema).forEach(([field, fieldSchema]) => {
|
||||||
|
if (!fieldSchema?.autocolumn) {
|
||||||
|
columns.push(field)
|
||||||
|
} else if (showAutoColumns) {
|
||||||
|
autoColumns.push(field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return columns.concat(autoColumns)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScroll = event => {
|
||||||
|
nextScrollTop = event.target.scrollTop
|
||||||
|
if (timeout) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
scrollTop = nextScrollTop
|
||||||
|
timeout = null
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateFirstVisibleRow = scrollTop => {
|
||||||
|
return Math.max(Math.floor(scrollTop / (rowHeight + 1)) - rowPreload, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateLastVisibleRow = (firstRow, visibleRowCount, allRowCount) => {
|
||||||
|
return Math.min(firstRow + visibleRowCount + 2 * rowPreload, allRowCount)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !loaded}
|
||||||
|
<div class="content" style={contentStyle} />
|
||||||
|
{:else}
|
||||||
|
<div use:styleable={$component.styles}>
|
||||||
|
<div
|
||||||
|
on:scroll={onScroll}
|
||||||
|
lang="en"
|
||||||
|
dir="ltr"
|
||||||
|
class:quiet
|
||||||
|
style={`--row-height: ${rowHeight}px; --header-height: ${headerHeight}px;`}
|
||||||
|
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
|
||||||
|
<div class="content" style={contentStyle}>
|
||||||
|
<table class="spectrum-Table" class:spectrum-Table--quiet={quiet}>
|
||||||
|
<thead class="spectrum-Table-head">
|
||||||
|
<tr>
|
||||||
|
{#if $component.children}
|
||||||
|
<th class="spectrum-Table-headCell">
|
||||||
|
<div class="spectrum-Table-headCell-content" />
|
||||||
|
</th>
|
||||||
|
{/if}
|
||||||
|
{#each fields as field}
|
||||||
|
<th
|
||||||
|
class="spectrum-Table-headCell is-sortable"
|
||||||
|
class:is-sorted-desc={sortColumn === field && sortOrder === 'Descending'}
|
||||||
|
class:is-sorted-asc={sortColumn === field && sortOrder === 'Ascending'}
|
||||||
|
on:click={() => sortBy(field)}>
|
||||||
|
<div class="spectrum-Table-headCell-content">
|
||||||
|
<div class="title">{schema[field]?.name}</div>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-ArrowDown100 spectrum-Table-sortedIcon"
|
||||||
|
class:visible={sortColumn === field}
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true">
|
||||||
|
<use xlink:href="#spectrum-css-icon-Arrow100" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="spectrum-Table-body">
|
||||||
|
{#each sortedRows as row, idx}
|
||||||
|
<tr
|
||||||
|
class="spectrum-Table-row"
|
||||||
|
class:hidden={idx < firstVisibleRow || idx > lastVisibleRow}>
|
||||||
|
{#if idx >= firstVisibleRow && idx <= lastVisibleRow}
|
||||||
|
{#if $component.children}
|
||||||
|
<td
|
||||||
|
class="spectrum-Table-cell spectrum-Table-cell--divider">
|
||||||
|
<div class="spectrum-Table-cell-content">
|
||||||
|
<Provider data={row}>
|
||||||
|
<slot />
|
||||||
|
</Provider>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
|
{#each fields as field}
|
||||||
|
<td class="spectrum-Table-cell">
|
||||||
|
<div class="spectrum-Table-cell-content">
|
||||||
|
<CellRenderer
|
||||||
|
schema={schema[field]}
|
||||||
|
value={row[field]} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spectrum {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
|
||||||
|
}
|
||||||
|
.spectrum.quiet {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spectrum-Table-sortedIcon {
|
||||||
|
opacity: 0;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.spectrum-Table-sortedIcon.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.spectrum,
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
vertical-align: middle;
|
||||||
|
height: var(--header-height);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--spectrum-global-color-gray-100);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.spectrum-Table-headCell-content {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.spectrum-Table-headCell-content .title {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
tbody tr {
|
||||||
|
height: var(--row-height);
|
||||||
|
}
|
||||||
|
tbody tr.hidden {
|
||||||
|
height: calc(var(--row-height) + 1px);
|
||||||
|
}
|
||||||
|
tbody tr.offset {
|
||||||
|
background-color: red;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: none !important;
|
||||||
|
border-left: none !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-top: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
|
||||||
|
}
|
||||||
|
tr:first-child td {
|
||||||
|
border-top: none !important;
|
||||||
|
}
|
||||||
|
.spectrum:not(.quiet) td.spectrum-Table-cell--divider {
|
||||||
|
width: 1px;
|
||||||
|
border-right: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
|
||||||
|
}
|
||||||
|
.spectrum-Table-cell-content {
|
||||||
|
height: var(--row-height);
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as table } from "./Table.svelte"
|
|
@ -56,13 +56,6 @@
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
turndown "^7.0.0"
|
turndown "^7.0.0"
|
||||||
|
|
||||||
"@budibase/svelte-ag-grid@^1.0.4":
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/svelte-ag-grid/-/svelte-ag-grid-1.0.4.tgz#41cceec4bde2c4aea8b9da8f610fe36055c7709f"
|
|
||||||
integrity sha512-JZm6qujxnZpqw7Twbegr6se4sHhyWzN0Cibrk5bVBH32hBgzD6dd33fxwrjHKkWFxjys9wRT+cqYgYVlSt9E3w==
|
|
||||||
dependencies:
|
|
||||||
ag-grid-community "^24.0.0"
|
|
||||||
|
|
||||||
"@polka/url@^0.5.0":
|
"@polka/url@^0.5.0":
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31"
|
resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31"
|
||||||
|
@ -132,72 +125,82 @@
|
||||||
estree-walker "^1.0.1"
|
estree-walker "^1.0.1"
|
||||||
picomatch "^2.2.2"
|
picomatch "^2.2.2"
|
||||||
|
|
||||||
"@spectrum-css/actionbutton@^1.0.0-beta.1":
|
"@spectrum-css/actionbutton@^1.0.1":
|
||||||
version "1.0.0-beta.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.0-beta.1.tgz#a6684cac108d4a9daefe0be6df8201d3c369a0d6"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.1.tgz#9c75da37ea6915919fb574c74bd60dacc03b6577"
|
||||||
integrity sha512-QbrPMTkbkmh+dEBP66TFXmF5z3qSde+BnLR5hnlo2XMvKvnblX2VJStEbQ+hTKuSZXCRFADXyXD5o0NOYDTByQ==
|
integrity sha512-AUqtyNabHF451Aj9i3xz82TxS5Z6k1dttA68/1hMeU9kbPCSS4P6Viw3vaRGs9CSspuR8xnnhDgrq+F+zMy2Hw==
|
||||||
|
|
||||||
"@spectrum-css/button@^3.0.0-beta.6":
|
"@spectrum-css/button@^3.0.1":
|
||||||
version "3.0.0-beta.6"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0-beta.6.tgz#007919d3e7a6692e506dc9addcd46aee6b203b1a"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.1.tgz#6db8c3e851baecd0f1c2d88fef37d49d01c6e643"
|
||||||
integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg==
|
integrity sha512-YXrBtjIYisk4Vaxnp0RiE4gdElQX04P2mc4Pi2GlQ27dJKlHmufYcF+kAqGdtiyK5yjdN/vKRcC8y13aA4rusA==
|
||||||
|
|
||||||
"@spectrum-css/checkbox@^3.0.0-beta.6":
|
"@spectrum-css/checkbox@^3.0.1":
|
||||||
version "3.0.0-beta.6"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.0-beta.6.tgz#338c4e58c4570ac8023f7332794fcb45f5ae9374"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.1.tgz#6f36377d8bd556989ddd1dec2506dc295c5fcda8"
|
||||||
integrity sha512-Z0Mwu7yn2b+QcZaBqMpKhliTQiF8T/cRyKgTyaIACtJ0FAK5NBJ4h/X6SWW3iXtoUWCH4+p/Hdtq1iQHAFi1qQ==
|
integrity sha512-fI0q2Cp6yU4ORyE6JWUSMYNgEtGf6AjYViZ2Weg3UPTYBQuWdQd8J0ZTcH38pDMyARFPRdiXgQ3KnyX5Hk5huw==
|
||||||
|
|
||||||
"@spectrum-css/fieldlabel@^3.0.0-beta.7":
|
"@spectrum-css/fieldlabel@^3.0.1":
|
||||||
version "3.0.0-beta.7"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.0-beta.7.tgz#f37797565e21b3609b8fbc2dafcea8ea41ffa114"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.1.tgz#39f7c0f25cc2ff402afeff005341b0832f7c588c"
|
||||||
integrity sha512-0pseiPghqlOdALsRtidveWyt2YjfSXTZWDlSkcne/J0/QXBJOQH/7Qfy7TmROQZYRB2LqH1VzmE1zbvGwr5Aog==
|
integrity sha512-LMfwrwIq8wEEvxFLobdLvXRwKrp8o9Fty4iJ9aYl2Rj1uXkfRd8qLz9HGZjLEE1OuJgoTBgamYABl7EvoA5PLw==
|
||||||
|
|
||||||
"@spectrum-css/icon@^3.0.0-beta.2":
|
"@spectrum-css/icon@^3.0.1":
|
||||||
version "3.0.0-beta.2"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.2.tgz#2dd7258ded74501b56e5fc42d0b6f0a3f4936aeb"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.1.tgz#e300a6fc353c85c6b5d6e7a364408a940c31b177"
|
||||||
integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA==
|
integrity sha512-cGFtIrcQ/7tthdkHK1npuEFiCdYVHLqwmLxghUYQw8Tb8KgJaw3OBO1tpjgsUizexNgu26BjVRIbGxNWuBXIHQ==
|
||||||
|
|
||||||
"@spectrum-css/inputgroup@^3.0.0-beta.7":
|
"@spectrum-css/inputgroup@^3.0.1":
|
||||||
version "3.0.0-beta.7"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.0-beta.7.tgz#9829812e349bf973fb8835f0586bf013c8c38d23"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.1.tgz#8c5b257b57b3b2cf04e99355709365fa0d6838cc"
|
||||||
integrity sha512-pZDpYhtTKZUVG31Rtx7imdwK2ohLyVuTEsl+mj2yDKn+2TOwYRxr6LdbfNhFN4xd0GtSqapKYfbgKBWYpIyiSw==
|
integrity sha512-asBRa1jTlld6plkcq4ySO+xl+OJlCMSOLoAFdSSIJowcSlCV0yDy7oeOhf5YQv9mMHFWTKlWUSoAKDZTguIPxA==
|
||||||
|
|
||||||
"@spectrum-css/menu@^3.0.0-beta.5":
|
"@spectrum-css/label@^2.0.9":
|
||||||
version "3.0.0-beta.5"
|
version "2.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.0-beta.5.tgz#99d5ea7f6760b7a89d5d732f4e91b98dd3f82d74"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.9.tgz#792f34b906ba81118f4d0edcc81a18da1ecd57cb"
|
||||||
integrity sha512-jvPD5GbNdX31rdFBLxCG7KoUVGeeNYLzNXDpiGZsWme/djVTwitljgNe7bhVwCVlXZE7H20Ti/YrdafnE154Rw==
|
integrity sha512-0vXhWIZoQDTg+I6MyMpwmeJ+yQHtxkZ7lLcEqxhJ2y7JXP2ftblz2sO4+9jB11ljepeVlV+B6LF1drU8mMu82A==
|
||||||
|
|
||||||
"@spectrum-css/page@^3.0.0-beta.0":
|
"@spectrum-css/menu@^3.0.1":
|
||||||
version "3.0.0-beta.0"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0-beta.0.tgz#885ea41b44861c5dc3aac904536f9e93c9109b58"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.1.tgz#2a376f991acc24e12ec892bb6b9db2650fc41fbe"
|
||||||
integrity sha512-+OD+l3aLisykxJnHfLkdkxMS1Uj1vKGYpKil7W0r5lSWU44eHyRgb8ZK5Vri1+sUO5SSf/CTybeVwtXME9wMLA==
|
integrity sha512-Qjg0+1O0eC89sb/bRFq2AGnQ8XqhVy23TUXHyffNM8qdcMssnlny3QmhzjURCZKvx/Y5UytCpzhedPQqSpQwZg==
|
||||||
|
|
||||||
|
"@spectrum-css/page@^3.0.1":
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.1.tgz#5e1c3dd5b1a1ee591f9d636b75f03665f542d846"
|
||||||
|
integrity sha512-LAlKF8km5BlsGPpZ2SNtwKOQIHn1lz0X93aczGZVZceOg73O4gyeoT5cx4vi1z+KtBRY5VMDWx3XgGtUwwjqwA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@spectrum-css/vars" "^3.0.0-beta.2"
|
"@spectrum-css/vars" "^3.0.1"
|
||||||
|
|
||||||
"@spectrum-css/picker@^1.0.0-beta.3":
|
"@spectrum-css/picker@^1.0.0":
|
||||||
version "1.0.0-beta.3"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.0-beta.3.tgz#476593597b5a9e0105397e4e39350869cf6e7965"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.1.tgz#98991198576d26bd14160824e7b6f3c278ff930b"
|
||||||
integrity sha512-jHzFnS5Frd3JSwZ6B8ymH/sVnNqAUBo9p93Zax4VHTUDsPTtTkvxj/Vxo4POmrJEL9v3qUB2Yk13rD2BSfEzLQ==
|
integrity sha512-Rv4/UBOdNW1gs7WVBCJnPD5VFly8MqP++psDX6kcugUIcfJy0GC3acvElotmKRlCDk8Qxks2W2A0jKeSgphTmA==
|
||||||
|
|
||||||
"@spectrum-css/popover@^3.0.0-beta.6":
|
"@spectrum-css/popover@^3.0.1":
|
||||||
version "3.0.0-beta.6"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.0-beta.6.tgz#787611f020e091234e6ba7e946b0dbd0ed1a2fa2"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.1.tgz#5863c1efc53f98f9aba2de9186666780041303fc"
|
||||||
integrity sha512-dUJlwxoNpB6jOR0g/ywH2cPoUz2FVsL6xPfkm6BSsLp9ejhYy0/OFF4w0Q32Fu9qJDbWJ9qaoOlPpt7IjQ+/GQ==
|
integrity sha512-LmOSj/yCwQQ9iGmCYnHiJsJR/HfPiGqI1Jl7pkKxBOCxYBMS/5+ans9vfCN2Qnd0eK7WSbfPg72S6mjye7db2Q==
|
||||||
|
|
||||||
"@spectrum-css/stepper@^3.0.0-beta.7":
|
"@spectrum-css/stepper@^3.0.1":
|
||||||
version "3.0.0-beta.7"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.0-beta.7.tgz#fc78435ce878c5e233af13e43ed2c3e8671a2bbc"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.1.tgz#7f270f53505e7dbe082591e8ea1c4c8f397e045a"
|
||||||
integrity sha512-TQL2OBcdEgbHBwehMGgqMuWdKZZQPGcBRV5FlF0TUdOT58lEqFAO43Gajqvyte1P23lNmnX8KuMwkRfQdn0RzA==
|
integrity sha512-IvZlGFJ8QPr9tUz5xvVN4hASaTRDPdKu9IIp25q/x0ecgSrKAM55e3EBWEYWy1H1JI3h+zlPnNRuK0VLhDbCYA==
|
||||||
|
|
||||||
"@spectrum-css/textfield@^3.0.0-beta.6":
|
"@spectrum-css/table@^3.0.1":
|
||||||
version "3.0.0-beta.6"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0-beta.6.tgz#30c044ceb403d6ea82d8046fb8f767f7fe455da6"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.1.tgz#753e0e2498082c0c36b9600828516aff3ac338cd"
|
||||||
integrity sha512-U7P8C3Xx8h5X+r+dZu1qbxceIxBn7ZSmMvJyC7MPSPcU3EwdzCUepERNGX7NrQdcX91XSNlPUOF7hZUognBwhQ==
|
integrity sha512-XQ+srMTv9hK1H0nctWUtqyzitmvyb5TNR+7mjAmKRdkBRSTQQSipDhenxZp72ekzMtMoSYZVZ77kgo0Iw3Fpug==
|
||||||
|
|
||||||
"@spectrum-css/vars@^3.0.0-beta.2":
|
"@spectrum-css/textfield@^3.0.1":
|
||||||
version "3.0.0-beta.2"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.0-beta.2.tgz#f0b3a2db44aa57b1a82e47ab392c716a3056a157"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.1.tgz#e875b8e37817378ad08fc4af7d53026df38911e5"
|
||||||
integrity sha512-HpcRDUkSjKVWUi7+jf6zp33YszXs3qFljaaNVTVOf0m0mqjWWXHxgLrvYlFFlHp5ITbNXds5Cb7EgiXCKmVIpA==
|
integrity sha512-MUV5q87CVxbkNdSNoxGrFbgyKc51ft/WWf3aVEoPdPw5yBnXqFe1w1YmAit5zYDOOhhs58sCLAlUcCMlOpkgrA==
|
||||||
|
|
||||||
|
"@spectrum-css/vars@^3.0.1":
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.1.tgz#561fd69098f896a647242dd8d6108af603bfa31e"
|
||||||
|
integrity sha512-l4oRcCOqInChYXZN6OQhpe3isk6l4OE6Ys8cgdlsiKp53suNoQxyyd9p/eGRbCjZgH3xQ8nK0t4DHa7QYC0S6w==
|
||||||
|
|
||||||
"@types/color-name@^1.1.1":
|
"@types/color-name@^1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
|
@ -238,11 +241,6 @@ abstract-leveldown@~0.12.0, abstract-leveldown@~0.12.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
xtend "~3.0.0"
|
xtend "~3.0.0"
|
||||||
|
|
||||||
ag-grid-community@^24.0.0:
|
|
||||||
version "24.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-24.0.0.tgz#793a3c4a28e5148c5c8c8c6ff68e8f3c4ee6ca3d"
|
|
||||||
integrity sha512-TNNqTp918PCRicH/vE8fs1pu/hbWMc9Iem7gCjGsjlabBrGhMxCq7tYiBtguERuxdszbSImznjampnc0NvOoFg==
|
|
||||||
|
|
||||||
alphanum-sort@^1.0.0:
|
alphanum-sort@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||||
|
@ -864,6 +862,11 @@ csso@^4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
css-tree "1.0.0-alpha.39"
|
css-tree "1.0.0-alpha.39"
|
||||||
|
|
||||||
|
dayjs@^1.10.4:
|
||||||
|
version "1.10.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
|
||||||
|
integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
|
||||||
|
|
||||||
deep-equal@^1.0.1:
|
deep-equal@^1.0.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
||||||
|
|
|
@ -35,7 +35,7 @@ const HELPERS = [
|
||||||
new Helper(HelperFunctionNames.LITERAL, value => {
|
new Helper(HelperFunctionNames.LITERAL, value => {
|
||||||
const type = typeof value
|
const type = typeof value
|
||||||
const outputVal = type === "object" ? JSON.stringify(value) : value
|
const outputVal = type === "object" ? JSON.stringify(value) : value
|
||||||
return `{{-${LITERAL_MARKER}-${type}-${outputVal}-}}`
|
return `{{${LITERAL_MARKER} ${type}-${outputVal}}}`
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,12 @@ module.exports.processors = [
|
||||||
if (!statement.includes(LITERAL_MARKER)) {
|
if (!statement.includes(LITERAL_MARKER)) {
|
||||||
return statement
|
return statement
|
||||||
}
|
}
|
||||||
|
const splitMarkerIndex = statement.indexOf("-")
|
||||||
const components = statement.split("-")
|
const type = statement.substring(12, splitMarkerIndex)
|
||||||
// pop and shift remove the empty array elements from the first and last dash
|
const value = statement.substring(
|
||||||
components.pop()
|
splitMarkerIndex + 1,
|
||||||
components.shift()
|
statement.length - 2
|
||||||
const type = components[1]
|
)
|
||||||
const value = components[2]
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "string":
|
case "string":
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
const {
|
const { processString, processObject, isValid } = require("../src/index")
|
||||||
processString,
|
|
||||||
processObject,
|
|
||||||
isValid,
|
|
||||||
} = require("../src/index")
|
|
||||||
|
|
||||||
describe("test the custom helpers we have applied", () => {
|
describe("test the custom helpers we have applied", () => {
|
||||||
it("should be able to use the object helper", async () => {
|
it("should be able to use the object helper", async () => {
|
||||||
const output = await processString("object is {{ object obj }}", {
|
const output = await processString("object is {{ object obj }}", {
|
||||||
obj: { a: 1 },
|
obj: { a: 1 },
|
||||||
})
|
})
|
||||||
expect(output).toBe("object is {\"a\":1}")
|
expect(output).toBe('object is {"a":1}')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -64,9 +60,12 @@ describe("test the array helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of the filter helper", async () => {
|
it("should allow use of the filter helper", async () => {
|
||||||
const output = await processString("{{#filter array \"person\"}}THING{{else}}OTHER{{/filter}}", {
|
const output = await processString(
|
||||||
array,
|
'{{#filter array "person"}}THING{{else}}OTHER{{/filter}}',
|
||||||
})
|
{
|
||||||
|
array,
|
||||||
|
}
|
||||||
|
)
|
||||||
expect(output).toBe("THING")
|
expect(output).toBe("THING")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -78,7 +77,7 @@ describe("test the array helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of the join helper", async () => {
|
it("should allow use of the join helper", async () => {
|
||||||
const output = await processString("{{join array \"-\"}}", {
|
const output = await processString('{{join array "-"}}', {
|
||||||
array,
|
array,
|
||||||
})
|
})
|
||||||
expect(output).toBe("hi-person-how-are-you")
|
expect(output).toBe("hi-person-how-are-you")
|
||||||
|
@ -86,14 +85,14 @@ describe("test the array helpers", () => {
|
||||||
|
|
||||||
it("should allow use of the sort helper", async () => {
|
it("should allow use of the sort helper", async () => {
|
||||||
const output = await processString("{{sort array}}", {
|
const output = await processString("{{sort array}}", {
|
||||||
array: ["d", "a", "c", "e"]
|
array: ["d", "a", "c", "e"],
|
||||||
})
|
})
|
||||||
expect(output).toBe("a,c,d,e")
|
expect(output).toBe("a,c,d,e")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of the unique helper", async () => {
|
it("should allow use of the unique helper", async () => {
|
||||||
const output = await processString("{{unique array}}", {
|
const output = await processString("{{unique array}}", {
|
||||||
array: ["a", "a", "b"]
|
array: ["a", "a", "b"],
|
||||||
})
|
})
|
||||||
expect(output).toBe("a,b")
|
expect(output).toBe("a,b")
|
||||||
})
|
})
|
||||||
|
@ -102,7 +101,7 @@ describe("test the array helpers", () => {
|
||||||
describe("test the number helpers", () => {
|
describe("test the number helpers", () => {
|
||||||
it("should allow use of the addCommas helper", async () => {
|
it("should allow use of the addCommas helper", async () => {
|
||||||
const output = await processString("{{ addCommas number }}", {
|
const output = await processString("{{ addCommas number }}", {
|
||||||
number: 10000000
|
number: 10000000,
|
||||||
})
|
})
|
||||||
expect(output).toBe("10,000,000")
|
expect(output).toBe("10,000,000")
|
||||||
})
|
})
|
||||||
|
@ -132,7 +131,7 @@ describe("test the number helpers", () => {
|
||||||
describe("test the url helpers", () => {
|
describe("test the url helpers", () => {
|
||||||
const url = "http://example.com?query=1"
|
const url = "http://example.com?query=1"
|
||||||
it("should allow use of the stripQueryString helper", async () => {
|
it("should allow use of the stripQueryString helper", async () => {
|
||||||
const output = await processString('{{stripQuerystring url }}', {
|
const output = await processString("{{stripQuerystring url }}", {
|
||||||
url,
|
url,
|
||||||
})
|
})
|
||||||
expect(output).toBe("http://example.com")
|
expect(output).toBe("http://example.com")
|
||||||
|
@ -149,10 +148,12 @@ describe("test the url helpers", () => {
|
||||||
const output = await processString("{{ object ( urlParse url ) }}", {
|
const output = await processString("{{ object ( urlParse url ) }}", {
|
||||||
url,
|
url,
|
||||||
})
|
})
|
||||||
expect(output).toBe("{\"protocol\":\"http:\",\"slashes\":true,\"auth\":null,\"host\":\"example.com\"," +
|
expect(output).toBe(
|
||||||
"\"port\":null,\"hostname\":\"example.com\",\"hash\":null,\"search\":\"?query=1\"," +
|
'{"protocol":"http:","slashes":true,"auth":null,"host":"example.com",' +
|
||||||
"\"query\":\"query=1\",\"pathname\":\"/\",\"path\":\"/?query=1\"," +
|
'"port":null,"hostname":"example.com","hash":null,"search":"?query=1",' +
|
||||||
"\"href\":\"http://example.com/?query=1\"}")
|
'"query":"query=1","pathname":"/","path":"/?query=1",' +
|
||||||
|
'"href":"http://example.com/?query=1"}'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -224,19 +225,25 @@ describe("test the string helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of the startsWith helper", async () => {
|
it("should allow use of the startsWith helper", async () => {
|
||||||
const output = await processString("{{ #startsWith 'Hello' string }}Hi!{{ else }}Goodbye!{{ /startsWith }}", {
|
const output = await processString(
|
||||||
string: "Hello my name is Mike",
|
"{{ #startsWith 'Hello' string }}Hi!{{ else }}Goodbye!{{ /startsWith }}",
|
||||||
})
|
{
|
||||||
|
string: "Hello my name is Mike",
|
||||||
|
}
|
||||||
|
)
|
||||||
expect(output).toBe("Hi!")
|
expect(output).toBe("Hi!")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("test the comparison helpers", () => {
|
describe("test the comparison helpers", () => {
|
||||||
async function compare(func, a, b) {
|
async function compare(func, a, b) {
|
||||||
const output = await processString(`{{ #${func} a b }}Success{{ else }}Fail{{ /${func} }}`, {
|
const output = await processString(
|
||||||
a,
|
`{{ #${func} a b }}Success{{ else }}Fail{{ /${func} }}`,
|
||||||
b,
|
{
|
||||||
})
|
a,
|
||||||
|
b,
|
||||||
|
}
|
||||||
|
)
|
||||||
expect(output).toBe("Success")
|
expect(output).toBe("Success")
|
||||||
}
|
}
|
||||||
it("should allow use of the lt helper", async () => {
|
it("should allow use of the lt helper", async () => {
|
||||||
|
@ -256,9 +263,12 @@ describe("test the comparison helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow use of gte with a literal value", async () => {
|
it("should allow use of gte with a literal value", async () => {
|
||||||
const output = await processString(`{{ #gte a "50" }}s{{ else }}f{{ /gte }}`, {
|
const output = await processString(
|
||||||
a: 51,
|
`{{ #gte a "50" }}s{{ else }}f{{ /gte }}`,
|
||||||
})
|
{
|
||||||
|
a: 51,
|
||||||
|
}
|
||||||
|
)
|
||||||
expect(output).toBe("s")
|
expect(output).toBe("s")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -273,21 +283,31 @@ describe("Test the literal helper", () => {
|
||||||
|
|
||||||
it("should allow use of the literal specifier for an object", async () => {
|
it("should allow use of the literal specifier for an object", async () => {
|
||||||
const output = await processString(`{{literal a}}`, {
|
const output = await processString(`{{literal a}}`, {
|
||||||
a: {b: 1},
|
a: { b: 1 },
|
||||||
})
|
})
|
||||||
expect(output.b).toBe(1)
|
expect(output.b).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should allow use of the literal specifier for an object with dashes", async () => {
|
||||||
|
const output = await processString(`{{literal a}}`, {
|
||||||
|
a: { b: "i-have-dashes" },
|
||||||
|
})
|
||||||
|
expect(output.b).toBe("i-have-dashes")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Cover a few complex use cases", () => {
|
describe("Cover a few complex use cases", () => {
|
||||||
it("should allow use of three different collection helpers", async () => {
|
it("should allow use of three different collection helpers", async () => {
|
||||||
const output = await processString(`{{ join ( after ( split "My name is: Joe Smith" " " ) 3 ) " " }}`, {})
|
const output = await processString(
|
||||||
|
`{{ join ( after ( split "My name is: Joe Smith" " " ) 3 ) " " }}`,
|
||||||
|
{}
|
||||||
|
)
|
||||||
expect(output).toBe("Joe Smith")
|
expect(output).toBe("Joe Smith")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should allow a complex array case", async () => {
|
it("should allow a complex array case", async () => {
|
||||||
const output = await processString("{{ last ( sort ( unique array ) ) }}", {
|
const output = await processString("{{ last ( sort ( unique array ) ) }}", {
|
||||||
array: ["a", "a", "d", "c", "e"]
|
array: ["a", "a", "d", "c", "e"],
|
||||||
})
|
})
|
||||||
expect(output).toBe("e")
|
expect(output).toBe("e")
|
||||||
})
|
})
|
||||||
|
@ -299,7 +319,9 @@ describe("Cover a few complex use cases", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should make sure case is valid", () => {
|
it("should make sure case is valid", () => {
|
||||||
const validity = isValid("{{ avg [c355ec2b422e54f988ae553c8acd811ea].[a] [c355ec2b422e54f988ae553c8acd811ea].[b] }}")
|
const validity = isValid(
|
||||||
|
"{{ avg [c355ec2b422e54f988ae553c8acd811ea].[a] [c355ec2b422e54f988ae553c8acd811ea].[b] }}"
|
||||||
|
)
|
||||||
expect(validity).toBe(true)
|
expect(validity).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -314,7 +336,9 @@ describe("Cover a few complex use cases", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should confirm a subtraction validity", () => {
|
it("should confirm a subtraction validity", () => {
|
||||||
const validity = isValid("{{ subtract [c390c23a7f1b6441c98d2fe2a51248ef3].[total profit] [c390c23a7f1b6441c98d2fe2a51248ef3].[total revenue] }}")
|
const validity = isValid(
|
||||||
|
"{{ subtract [c390c23a7f1b6441c98d2fe2a51248ef3].[total profit] [c390c23a7f1b6441c98d2fe2a51248ef3].[total revenue] }}"
|
||||||
|
)
|
||||||
expect(validity).toBe(true)
|
expect(validity).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -344,9 +368,11 @@ describe("Cover a few complex use cases", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("getting a nice date from the user", async () => {
|
it("getting a nice date from the user", async () => {
|
||||||
const input = {text: `{{ date user.subscriptionDue "DD-MM" }}`}
|
const input = { text: `{{ date user.subscriptionDue "DD-MM" }}` }
|
||||||
const context = JSON.parse(`{"user":{"email":"test@test.com","roleId":"ADMIN","type":"user","tableId":"ta_users","subscriptionDue":"2021-01-12T12:00:00.000Z","_id":"ro_ta_users_us_test@test.com","_rev":"2-24cc794985eb54183ecb93e148563f3d"}}`)
|
const context = JSON.parse(
|
||||||
|
`{"user":{"email":"test@test.com","roleId":"ADMIN","type":"user","tableId":"ta_users","subscriptionDue":"2021-01-12T12:00:00.000Z","_id":"ro_ta_users_us_test@test.com","_rev":"2-24cc794985eb54183ecb93e148563f3d"}}`
|
||||||
|
)
|
||||||
const output = await processObject(input, context)
|
const output = await processObject(input, context)
|
||||||
expect(output.text).toBe("12-01")
|
expect(output.text).toBe("12-01")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue