Merge branch 'form-builder' of github.com:Budibase/budibase into form-builder
This commit is contained in:
commit
c7ed77381e
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -64,26 +64,25 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^1.56.0",
|
||||
"@budibase/client": "^0.7.1",
|
||||
"@budibase/colorpicker": "^1.0.1",
|
||||
"@budibase/string-templates": "^0.7.1",
|
||||
"@budibase/client": "^0.7.4",
|
||||
"@budibase/colorpicker": "1.0.1",
|
||||
"@budibase/string-templates": "^0.7.4",
|
||||
"@budibase/svelte-ag-grid": "^0.0.16",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@svelteschool/svelte-forms": "^0.7.0",
|
||||
"britecharts": "^2.16.0",
|
||||
"@svelteschool/svelte-forms": "0.7.0",
|
||||
"codemirror": "^5.59.0",
|
||||
"d3-selection": "^1.4.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"downloadjs": "^1.4.7",
|
||||
"downloadjs": "1.4.7",
|
||||
"fast-sort": "^2.2.0",
|
||||
"lodash": "^4.17.13",
|
||||
"lodash": "4.17.13",
|
||||
"posthog-js": "1.4.5",
|
||||
"remixicon": "^2.5.0",
|
||||
"shortid": "^2.2.15",
|
||||
"remixicon": "2.5.0",
|
||||
"shortid": "2.2.15",
|
||||
"svelte-loading-spinners": "^0.1.1",
|
||||
"svelte-portal": "^0.1.0",
|
||||
"uuid": "^8.3.1",
|
||||
"yup": "^0.29.2"
|
||||
"svelte-portal": "0.1.0",
|
||||
"uuid": "8.3.1",
|
||||
"yup": "0.29.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.5",
|
||||
|
|
|
@ -69,8 +69,9 @@ export const getDatasourceForProvider = component => {
|
|||
}
|
||||
|
||||
// Extract datasource from component instance
|
||||
const validSettingTypes = ["datasource", "table", "schema"]
|
||||
const datasourceSetting = def.settings.find(setting => {
|
||||
return setting.type === "datasource" || setting.type === "table"
|
||||
return validSettingTypes.includes(setting.type)
|
||||
})
|
||||
if (!datasourceSetting) {
|
||||
return null
|
||||
|
@ -80,15 +81,14 @@ export const getDatasourceForProvider = component => {
|
|||
// example an actual datasource object, or a table ID string.
|
||||
// Convert the datasource setting into a proper datasource object so that
|
||||
// we can use it properly
|
||||
if (datasourceSetting.type === "datasource") {
|
||||
return component[datasourceSetting?.key]
|
||||
} else if (datasourceSetting.type === "table") {
|
||||
if (datasourceSetting.type === "table") {
|
||||
return {
|
||||
tableId: component[datasourceSetting?.key],
|
||||
type: "table",
|
||||
}
|
||||
} else {
|
||||
return component[datasourceSetting?.key]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,21 +99,37 @@ export const getContextBindings = (rootComponent, componentId) => {
|
|||
// Extract any components which provide data contexts
|
||||
const dataProviders = getDataProviderComponents(rootComponent, componentId)
|
||||
let contextBindings = []
|
||||
|
||||
// Create bindings for each data provider
|
||||
dataProviders.forEach(component => {
|
||||
const isForm = component._component.endsWith("/form")
|
||||
const datasource = getDatasourceForProvider(component)
|
||||
if (!datasource) {
|
||||
let tableName, schema
|
||||
|
||||
// Forms are an edge case which do not need table schemas
|
||||
if (isForm) {
|
||||
schema = buildFormSchema(component)
|
||||
tableName = "Schema"
|
||||
} else {
|
||||
if (!datasource) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get schema and table for the datasource
|
||||
const info = getSchemaForDatasource(datasource, isForm)
|
||||
schema = info.schema
|
||||
tableName = info.table?.name
|
||||
|
||||
// Add _id and _rev fields for certain types
|
||||
if (datasource.type === "table" || datasource.type === "link") {
|
||||
schema["_id"] = { type: "string" }
|
||||
schema["_rev"] = { type: "string" }
|
||||
}
|
||||
}
|
||||
if (!schema || !tableName) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get schema and add _id and _rev fields for certain types
|
||||
let { schema, table } = getSchemaForDatasource(datasource)
|
||||
if (!schema || !table) {
|
||||
return
|
||||
}
|
||||
if (datasource.type === "table" || datasource.type === "link") {
|
||||
schema["_id"] = { type: "string" }
|
||||
schema["_rev"] = { type: "string " }
|
||||
}
|
||||
const keys = Object.keys(schema).sort()
|
||||
|
||||
// Create bindable properties for each schema field
|
||||
|
@ -132,11 +148,13 @@ export const getContextBindings = (rootComponent, componentId) => {
|
|||
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
|
||||
runtimeBoundKey
|
||||
)}`,
|
||||
readableBinding: `${component._instanceName}.${table.name}.${key}`,
|
||||
readableBinding: `${component._instanceName}.${tableName}.${key}`,
|
||||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
fieldSchema,
|
||||
providerId: component._id,
|
||||
tableId: datasource.tableId,
|
||||
field: key,
|
||||
// tableId: table._id,
|
||||
// field: key,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -164,10 +182,12 @@ export const getContextBindings = (rootComponent, componentId) => {
|
|||
type: "context",
|
||||
runtimeBinding: `user.${runtimeBoundKey}`,
|
||||
readableBinding: `Current User.${key}`,
|
||||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
fieldSchema,
|
||||
providerId: "user",
|
||||
tableId: TableNames.USERS,
|
||||
field: key,
|
||||
// tableId: TableNames.USERS,
|
||||
// field: key,
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -177,7 +197,7 @@ export const getContextBindings = (rootComponent, componentId) => {
|
|||
/**
|
||||
* Gets a schema for a datasource object.
|
||||
*/
|
||||
export const getSchemaForDatasource = datasource => {
|
||||
export const getSchemaForDatasource = (datasource, isForm = false) => {
|
||||
let schema, table
|
||||
if (datasource) {
|
||||
const { type } = datasource
|
||||
|
@ -191,6 +211,12 @@ export const getSchemaForDatasource = datasource => {
|
|||
if (table) {
|
||||
if (type === "view") {
|
||||
schema = cloneDeep(table.views?.[datasource.name]?.schema)
|
||||
} else if (type === "query" && isForm) {
|
||||
schema = {}
|
||||
const params = table.parameters || []
|
||||
params.forEach(param => {
|
||||
schema[param.name] = { ...param, type: "string" }
|
||||
})
|
||||
} else {
|
||||
schema = cloneDeep(table.schema)
|
||||
}
|
||||
|
@ -199,6 +225,32 @@ export const getSchemaForDatasource = datasource => {
|
|||
return { schema, table }
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a form schema given a form component.
|
||||
* A form schema is a schema of all the fields nested anywhere within a form.
|
||||
*/
|
||||
const buildFormSchema = component => {
|
||||
let schema = {}
|
||||
if (!component) {
|
||||
return schema
|
||||
}
|
||||
const def = store.actions.components.getDefinition(component._component)
|
||||
const fieldSetting = def?.settings?.find(
|
||||
setting => setting.key === "field" && setting.type.startsWith("field/")
|
||||
)
|
||||
if (fieldSetting && component.field) {
|
||||
const type = fieldSetting.type.split("field/")[1]
|
||||
if (type) {
|
||||
schema[component.field] = { name: component.field, type }
|
||||
}
|
||||
}
|
||||
component._children?.forEach(child => {
|
||||
const childSchema = buildFormSchema(child)
|
||||
schema = { ...schema, ...childSchema }
|
||||
})
|
||||
return schema
|
||||
}
|
||||
|
||||
/**
|
||||
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
|
||||
*/
|
||||
|
|
|
@ -416,7 +416,14 @@ export const getFrontendStore = () => {
|
|||
if (cut) {
|
||||
state.componentToPaste = null
|
||||
} else {
|
||||
componentToPaste._id = uuid()
|
||||
const randomizeIds = component => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
component._id = uuid()
|
||||
component._children?.forEach(randomizeIds)
|
||||
}
|
||||
randomizeIds(componentToPaste)
|
||||
}
|
||||
|
||||
if (mode === "inside") {
|
||||
|
|
|
@ -35,7 +35,7 @@ const createScreen = table => {
|
|||
const form = makeMainForm()
|
||||
.instanceName("Form")
|
||||
.customProps({
|
||||
theme: "spectrum--light",
|
||||
theme: "spectrum--lightest",
|
||||
size: "spectrum--medium",
|
||||
datasource: {
|
||||
label: table.name,
|
||||
|
|
|
@ -59,6 +59,7 @@ function generateTitleContainer(table, title, formId) {
|
|||
onClick: [
|
||||
{
|
||||
parameters: {
|
||||
providerId: formId,
|
||||
rowId: `{{ ${makePropSafe(formId)}._id }}`,
|
||||
revId: `{{ ${makePropSafe(formId)}._rev }}`,
|
||||
tableId: table._id,
|
||||
|
@ -90,7 +91,7 @@ const createScreen = table => {
|
|||
const form = makeMainForm()
|
||||
.instanceName("Form")
|
||||
.customProps({
|
||||
theme: "spectrum--light",
|
||||
theme: "spectrum--lightest",
|
||||
size: "spectrum--medium",
|
||||
datasource: {
|
||||
label: table.name,
|
||||
|
|
|
@ -169,7 +169,11 @@ export function makeTableFormComponents(tableId) {
|
|||
|
||||
export function makeQueryFormComponents(queryId) {
|
||||
const queries = get(backendUiStore).queries
|
||||
const schema = queries.find(query => query._id === queryId)?.schema ?? []
|
||||
const params = queries.find(query => query._id === queryId)?.parameters ?? []
|
||||
let schema = {}
|
||||
params.forEach(param => {
|
||||
schema[param.name] = { ...param, type: "string" }
|
||||
})
|
||||
return makeSchemaFormComponents(schema)
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,11 @@
|
|||
{:else if value.customType === 'password'}
|
||||
<Input type="password" extraThin bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'email'}
|
||||
<Input type="email" extraThin bind:value={block.inputs[key]} />
|
||||
<BindableInput
|
||||
type={'email'}
|
||||
extraThin
|
||||
bind:value={block.inputs[key]}
|
||||
{bindings} />
|
||||
{:else if value.customType === 'table'}
|
||||
<TableSelector bind:value={block.inputs[key]} />
|
||||
{:else if value.customType === 'row'}
|
||||
|
@ -75,7 +79,7 @@
|
|||
<SchemaSetup bind:value={block.inputs[key]} />
|
||||
{:else if value.type === 'string' || value.type === 'number'}
|
||||
<BindableInput
|
||||
type="string"
|
||||
type={value.customType}
|
||||
extraThin
|
||||
bind:value={block.inputs[key]}
|
||||
{bindings} />
|
||||
|
|
|
@ -20,13 +20,12 @@
|
|||
let exportFormat = FORMATS[0].key
|
||||
|
||||
async function exportView() {
|
||||
const response = await api.post(
|
||||
`/api/views/export?format=${exportFormat}`,
|
||||
view
|
||||
download(
|
||||
`/api/views/export?view=${encodeURIComponent(
|
||||
view.name
|
||||
)}&format=${exportFormat}`
|
||||
)
|
||||
const downloadInfo = await response.json()
|
||||
onClosed()
|
||||
window.location = downloadInfo.url
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
let drawer
|
||||
|
||||
export let value = {}
|
||||
export let otherSources
|
||||
|
||||
$: tables = $backendUiStore.tables.map(m => ({
|
||||
label: m.name,
|
||||
|
@ -88,7 +89,7 @@
|
|||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value?.label ? value.label : 'Choose option'}</span>
|
||||
<span>{value?.label ?? 'Choose option'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
{#if value?.type === 'query'}
|
||||
|
@ -175,6 +176,22 @@
|
|||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if otherSources?.length}
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Other</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each otherSources as source}
|
||||
<li
|
||||
class:selected={value === source}
|
||||
on:click={() => handleSelected(source)}>
|
||||
{source.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
|
@ -15,8 +15,9 @@
|
|||
)
|
||||
$: {
|
||||
// Automatically set rev and table ID based on row ID
|
||||
if (parameters.rowId) {
|
||||
parameters.revId = parameters.rowId.replace("_id", "_rev")
|
||||
if (parameters.providerId) {
|
||||
parameters.rowId = `{{ ${parameters.providerId}._id }}`
|
||||
parameters.revId = `{{ ${parameters.providerId}._rev }}`
|
||||
const providerComponent = dataProviderComponents.find(
|
||||
provider => provider._id === parameters.providerId
|
||||
)
|
||||
|
@ -37,12 +38,10 @@
|
|||
</div>
|
||||
{:else}
|
||||
<Label size="m" color="dark">Datasource</Label>
|
||||
<Select secondary bind:value={parameters.rowId}>
|
||||
<Select secondary bind:value={parameters.providerId}>
|
||||
<option value="" />
|
||||
{#each dataProviderComponents as provider}
|
||||
<option value={`{{ ${provider._id}._id }}`}>
|
||||
{provider._instanceName}
|
||||
</option>
|
||||
<option value={provider._id}>{provider._instanceName}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{/if}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<script>
|
||||
import { Select, Label } from "@budibase/bbui"
|
||||
import { currentAsset, store } from "builderStore"
|
||||
import { getDataProviderComponents } from "builderStore/dataBinding"
|
||||
|
||||
export let parameters
|
||||
|
||||
$: dataProviders = getDataProviderComponents(
|
||||
$currentAsset.props,
|
||||
$store.selectedComponentId
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="root">
|
||||
<Label size="m" color="dark">Form</Label>
|
||||
<Select secondary bind:value={parameters.componentId}>
|
||||
<option value="" />
|
||||
{#if dataProviders}
|
||||
{#each dataProviders as component}
|
||||
<option value={component._id}>{component._instanceName}</option>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.root :global(> div) {
|
||||
flex: 1;
|
||||
margin-left: var(--spacing-l);
|
||||
}
|
||||
</style>
|
|
@ -4,6 +4,7 @@ import DeleteRow from "./DeleteRow.svelte"
|
|||
import ExecuteQuery from "./ExecuteQuery.svelte"
|
||||
import TriggerAutomation from "./TriggerAutomation.svelte"
|
||||
import ValidateForm from "./ValidateForm.svelte"
|
||||
import RefreshDatasource from "./RefreshDatasource.svelte"
|
||||
|
||||
// defines what actions are available, when adding a new one
|
||||
// the component is the setup panel for the action
|
||||
|
@ -35,4 +36,8 @@ export default [
|
|||
name: "Validate Form",
|
||||
component: ValidateForm,
|
||||
},
|
||||
{
|
||||
name: "Refresh Datasource",
|
||||
component: RefreshDatasource,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import OptionSelect from "./OptionSelect.svelte"
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import {
|
||||
getDatasourceForProvider,
|
||||
getSchemaForDatasource,
|
||||
|
@ -18,7 +18,7 @@
|
|||
component => component._component === "@budibase/standard-components/form"
|
||||
)
|
||||
$: datasource = getDatasourceForProvider(form)
|
||||
$: schema = getSchemaForDatasource(datasource).schema
|
||||
$: schema = getSchemaForDatasource(datasource, true).schema
|
||||
$: options = getOptions(schema, type)
|
||||
|
||||
const getOptions = (schema, fieldType) => {
|
||||
|
@ -28,6 +28,32 @@
|
|||
}
|
||||
return entries.map(entry => entry[0])
|
||||
}
|
||||
|
||||
const handleBlur = () => onChange(value)
|
||||
</script>
|
||||
|
||||
<OptionSelect {value} {onChange} {options} />
|
||||
<div>
|
||||
<DataList
|
||||
editable
|
||||
secondary
|
||||
extraThin
|
||||
on:blur={handleBlur}
|
||||
on:change
|
||||
bind:value>
|
||||
<option value="" />
|
||||
{#each options as option}
|
||||
<option value={option}>{option}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div :global(> div) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import FieldSelect from "./FieldSelect.svelte"
|
||||
</script>
|
||||
|
||||
<FieldSelect {...$$props} multiselect />
|
|
@ -1,5 +0,0 @@
|
|||
<script>
|
||||
import TableViewFieldSelect from "./TableViewFieldSelect.svelte"
|
||||
</script>
|
||||
|
||||
<TableViewFieldSelect {...$$props} multiselect />
|
|
@ -138,7 +138,7 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding-left: var(--spacing-xs);
|
||||
padding-left: 7px;
|
||||
border-left: 1px solid var(--grey-4);
|
||||
background-color: var(--grey-2);
|
||||
border-top-right-radius: var(--border-radius-m);
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<DetailSummary name={`${name}${changed ? ' *' : ''}`} on:open show={open} thin>
|
||||
{#if open}
|
||||
<div>
|
||||
{#each properties as prop}
|
||||
{#each properties as prop (`${componentInstance._id}-${prop.key}`)}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
label={`${prop.label}${hasPropChanged(style, prop) ? ' *' : ''}`}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import DatasourceSelect from "./DatasourceSelect.svelte"
|
||||
|
||||
const otherSources = [{ name: "Custom", label: "Custom" }]
|
||||
</script>
|
||||
|
||||
<DatasourceSelect on:change {...$$props} {otherSources} />
|
|
@ -11,11 +11,12 @@
|
|||
import LayoutSelect from "./PropertyControls/LayoutSelect.svelte"
|
||||
import RoleSelect from "./PropertyControls/RoleSelect.svelte"
|
||||
import OptionSelect from "./PropertyControls/OptionSelect.svelte"
|
||||
import MultiTableViewFieldSelect from "./PropertyControls/MultiTableViewFieldSelect.svelte"
|
||||
import Checkbox from "./PropertyControls/Checkbox.svelte"
|
||||
import TableSelect from "./PropertyControls/TableSelect.svelte"
|
||||
import TableViewSelect from "./PropertyControls/TableViewSelect.svelte"
|
||||
import TableViewFieldSelect from "./PropertyControls/TableViewFieldSelect.svelte"
|
||||
import DatasourceSelect from "./PropertyControls/DatasourceSelect.svelte"
|
||||
import FieldSelect from "./PropertyControls/FieldSelect.svelte"
|
||||
import MultiFieldSelect from "./PropertyControls/MultiFieldSelect.svelte"
|
||||
import SchemaSelect from "./PropertyControls/SchemaSelect.svelte"
|
||||
import EventsEditor from "./PropertyControls/EventsEditor"
|
||||
import ScreenSelect from "./PropertyControls/ScreenSelect.svelte"
|
||||
import DetailScreenSelect from "./PropertyControls/DetailScreenSelect.svelte"
|
||||
|
@ -60,7 +61,7 @@
|
|||
const controlMap = {
|
||||
text: Input,
|
||||
select: OptionSelect,
|
||||
datasource: TableViewSelect,
|
||||
datasource: DatasourceSelect,
|
||||
screen: ScreenSelect,
|
||||
detailScreen: DetailScreenSelect,
|
||||
boolean: Checkbox,
|
||||
|
@ -69,8 +70,9 @@
|
|||
table: TableSelect,
|
||||
color: ColorPicker,
|
||||
icon: IconSelect,
|
||||
field: TableViewFieldSelect,
|
||||
multifield: MultiTableViewFieldSelect,
|
||||
field: FieldSelect,
|
||||
multifield: MultiFieldSelect,
|
||||
schema: SchemaSelect,
|
||||
"field/string": StringFieldSelect,
|
||||
"field/number": NumberFieldSelect,
|
||||
"field/options": OptionsFieldSelect,
|
||||
|
@ -78,7 +80,7 @@
|
|||
"field/longform": LongFormFieldSelect,
|
||||
"field/datetime": DateTimeFieldSelect,
|
||||
"field/attachment": AttachmentFieldSelect,
|
||||
"field/relationship": RelationshipFieldSelect,
|
||||
"field/link": RelationshipFieldSelect,
|
||||
}
|
||||
|
||||
const getControl = type => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { Popover } from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
import FeedbackIframe from "./FeedbackIframe.svelte"
|
||||
import analytics from "analytics"
|
||||
|
@ -10,9 +11,15 @@
|
|||
let iconContainer
|
||||
let popover
|
||||
|
||||
setInterval(() => {
|
||||
$store.highlightFeedbackIcon = analytics.highlightFeedbackIcon()
|
||||
}, FIVE_MINUTES)
|
||||
onMount(() => {
|
||||
const interval = setInterval(() => {
|
||||
store.update(state => {
|
||||
state.highlightFeedbackIcon = analytics.highlightFeedbackIcon()
|
||||
return state
|
||||
})
|
||||
}, FIVE_MINUTES)
|
||||
return () => clearInterval(interval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container" bind:this={iconContainer} on:click={popover.show}>
|
||||
|
|
|
@ -132,7 +132,8 @@
|
|||
|
||||
<header>
|
||||
<div class="input">
|
||||
<Input placeholder="✎ Edit Query Name" bind:value={query.name} />
|
||||
<div class="label">Enter query name:</div>
|
||||
<Input outline border bind:value={query.name} />
|
||||
</div>
|
||||
{#if config}
|
||||
<div class="props">
|
||||
|
@ -216,7 +217,9 @@
|
|||
|
||||
<style>
|
||||
.input {
|
||||
width: 300px;
|
||||
width: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.select {
|
||||
|
@ -288,4 +291,12 @@
|
|||
margin-top: -28px;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--font-sans);
|
||||
color: var(--grey-8);
|
||||
font-size: var(--font-size-s);
|
||||
margin-right: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,9 +14,12 @@
|
|||
async function exportApp() {
|
||||
appExportLoading = true
|
||||
try {
|
||||
download(`/api/backups/export?appId=${_id}`)
|
||||
download(
|
||||
`/api/backups/export?appId=${_id}&appname=${encodeURIComponent(name)}`
|
||||
)
|
||||
notifier.success("App Export Complete.")
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
notifier.danger("App Export Failed.")
|
||||
} finally {
|
||||
appExportLoading = false
|
||||
|
@ -29,13 +32,13 @@
|
|||
<Spacer medium />
|
||||
<div class="card-footer">
|
||||
<TextButton text medium blue href="/_builder/{_id}">
|
||||
Open
|
||||
{name}
|
||||
→
|
||||
Open {name} →
|
||||
</TextButton>
|
||||
{#if appExportLoading}
|
||||
<Spinner size="10" />
|
||||
{:else}<i class="ri-folder-download-line" on:click={exportApp} />{/if}
|
||||
{:else}
|
||||
<i class="ri-folder-download-line" on:click={exportApp} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -854,7 +854,7 @@
|
|||
svelte-portal "^1.0.0"
|
||||
turndown "^7.0.0"
|
||||
|
||||
"@budibase/colorpicker@^1.0.1":
|
||||
"@budibase/colorpicker@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/colorpicker/-/colorpicker-1.0.1.tgz#940c180e7ebba0cb0756c4c8ef13f5dfab58e810"
|
||||
integrity sha512-+DTHYhU0sTi5RfCyd7AAvMsLFwyF/wgs0owf7KyQU+ZILRW+YsWa7OQMz+hKQfgVAmvzwrNz8ATiBlG3Ac6Asg==
|
||||
|
@ -1257,7 +1257,7 @@
|
|||
node-fetch "^2.6.0"
|
||||
utf-8-validate "^5.0.2"
|
||||
|
||||
"@svelteschool/svelte-forms@^0.7.0":
|
||||
"@svelteschool/svelte-forms@0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@svelteschool/svelte-forms/-/svelte-forms-0.7.0.tgz#4ecba15e9a9ab2b04fad3d892931a561118a4cea"
|
||||
integrity sha512-TSt8ROqK6wq+Hav7EhZL1I0GtsZhg28aJuuDSviBzG/NG9pC0eprf8roWjl59DKHOVWIUTPTeY+T+lipb9gf8w==
|
||||
|
@ -1775,11 +1775,6 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base-64@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
|
||||
integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs=
|
||||
|
||||
base@^0.11.1:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||
|
@ -1870,15 +1865,6 @@ braces@^3.0.1, braces@~3.0.2:
|
|||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
britecharts@^2.16.0:
|
||||
version "2.17.2"
|
||||
resolved "https://registry.yarnpkg.com/britecharts/-/britecharts-2.17.2.tgz#78e7743e7c1dcaccd78ab7dacc479d37d509cdf2"
|
||||
integrity sha512-+wMG/ci+UHPRIySppTs8wQZmmlYFQHn2bCvbNiWUOYd1qAoiEQyKA/dVtgdTyR09qM+h8b9YsFofaWHJRT1mQg==
|
||||
dependencies:
|
||||
base-64 "^0.1.0"
|
||||
d3 "^5.16.0"
|
||||
lodash.assign "^4.2.0"
|
||||
|
||||
brorand@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
|
@ -2282,16 +2268,16 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@2, commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@2.17.x:
|
||||
version "2.17.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
||||
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^5.0.0, commander@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
|
@ -2536,254 +2522,11 @@ cypress@^5.1.0:
|
|||
url "^0.11.0"
|
||||
yauzl "^2.10.0"
|
||||
|
||||
d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
|
||||
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
|
||||
|
||||
d3-axis@1:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-1.0.12.tgz#cdf20ba210cfbb43795af33756886fb3638daac9"
|
||||
integrity sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==
|
||||
|
||||
d3-brush@1:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-1.1.6.tgz#b0a22c7372cabec128bdddf9bddc058592f89e9b"
|
||||
integrity sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-drag "1"
|
||||
d3-interpolate "1"
|
||||
d3-selection "1"
|
||||
d3-transition "1"
|
||||
|
||||
d3-chord@1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-1.0.6.tgz#309157e3f2db2c752f0280fedd35f2067ccbb15f"
|
||||
integrity sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==
|
||||
dependencies:
|
||||
d3-array "1"
|
||||
d3-path "1"
|
||||
|
||||
d3-collection@1:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
|
||||
integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==
|
||||
|
||||
d3-color@1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a"
|
||||
integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==
|
||||
|
||||
d3-contour@1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3"
|
||||
integrity sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==
|
||||
dependencies:
|
||||
d3-array "^1.1.1"
|
||||
|
||||
d3-dispatch@1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
|
||||
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
|
||||
|
||||
d3-drag@1:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70"
|
||||
integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-selection "1"
|
||||
|
||||
d3-dsv@1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c"
|
||||
integrity sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==
|
||||
dependencies:
|
||||
commander "2"
|
||||
iconv-lite "0.4"
|
||||
rw "1"
|
||||
|
||||
d3-ease@1:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2"
|
||||
integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==
|
||||
|
||||
d3-fetch@1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-1.2.0.tgz#15ce2ecfc41b092b1db50abd2c552c2316cf7fc7"
|
||||
integrity sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==
|
||||
dependencies:
|
||||
d3-dsv "1"
|
||||
|
||||
d3-force@1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b"
|
||||
integrity sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==
|
||||
dependencies:
|
||||
d3-collection "1"
|
||||
d3-dispatch "1"
|
||||
d3-quadtree "1"
|
||||
d3-timer "1"
|
||||
|
||||
d3-format@1:
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
|
||||
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
|
||||
|
||||
d3-geo@1:
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f"
|
||||
integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==
|
||||
dependencies:
|
||||
d3-array "1"
|
||||
|
||||
d3-hierarchy@1:
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83"
|
||||
integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==
|
||||
|
||||
d3-interpolate@1:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
|
||||
integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==
|
||||
dependencies:
|
||||
d3-color "1"
|
||||
|
||||
d3-path@1:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
||||
|
||||
d3-polygon@1:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-1.0.6.tgz#0bf8cb8180a6dc107f518ddf7975e12abbfbd38e"
|
||||
integrity sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==
|
||||
|
||||
d3-quadtree@1:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135"
|
||||
integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==
|
||||
|
||||
d3-random@1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-1.1.2.tgz#2833be7c124360bf9e2d3fd4f33847cfe6cab291"
|
||||
integrity sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==
|
||||
|
||||
d3-scale-chromatic@1:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz#54e333fc78212f439b14641fb55801dd81135a98"
|
||||
integrity sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==
|
||||
dependencies:
|
||||
d3-color "1"
|
||||
d3-interpolate "1"
|
||||
|
||||
d3-scale@2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f"
|
||||
integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==
|
||||
dependencies:
|
||||
d3-array "^1.2.0"
|
||||
d3-collection "1"
|
||||
d3-format "1"
|
||||
d3-interpolate "1"
|
||||
d3-time "1"
|
||||
d3-time-format "2"
|
||||
|
||||
d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.4.1:
|
||||
d3-selection@^1.4.1:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c"
|
||||
integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==
|
||||
|
||||
d3-shape@1:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
|
||||
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
d3-time-format@2:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850"
|
||||
integrity sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==
|
||||
dependencies:
|
||||
d3-time "1"
|
||||
|
||||
d3-time@1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
|
||||
integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
|
||||
|
||||
d3-timer@1:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
|
||||
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
|
||||
|
||||
d3-transition@1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398"
|
||||
integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==
|
||||
dependencies:
|
||||
d3-color "1"
|
||||
d3-dispatch "1"
|
||||
d3-ease "1"
|
||||
d3-interpolate "1"
|
||||
d3-selection "^1.1.0"
|
||||
d3-timer "1"
|
||||
|
||||
d3-voronoi@1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297"
|
||||
integrity sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==
|
||||
|
||||
d3-zoom@1:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a"
|
||||
integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==
|
||||
dependencies:
|
||||
d3-dispatch "1"
|
||||
d3-drag "1"
|
||||
d3-interpolate "1"
|
||||
d3-selection "1"
|
||||
d3-transition "1"
|
||||
|
||||
d3@^5.16.0:
|
||||
version "5.16.0"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877"
|
||||
integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==
|
||||
dependencies:
|
||||
d3-array "1"
|
||||
d3-axis "1"
|
||||
d3-brush "1"
|
||||
d3-chord "1"
|
||||
d3-collection "1"
|
||||
d3-color "1"
|
||||
d3-contour "1"
|
||||
d3-dispatch "1"
|
||||
d3-drag "1"
|
||||
d3-dsv "1"
|
||||
d3-ease "1"
|
||||
d3-fetch "1"
|
||||
d3-force "1"
|
||||
d3-format "1"
|
||||
d3-geo "1"
|
||||
d3-hierarchy "1"
|
||||
d3-interpolate "1"
|
||||
d3-path "1"
|
||||
d3-polygon "1"
|
||||
d3-quadtree "1"
|
||||
d3-random "1"
|
||||
d3-scale "2"
|
||||
d3-scale-chromatic "1"
|
||||
d3-selection "1"
|
||||
d3-shape "1"
|
||||
d3-time "1"
|
||||
d3-time-format "2"
|
||||
d3-timer "1"
|
||||
d3-transition "1"
|
||||
d3-voronoi "1"
|
||||
d3-zoom "1"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
|
@ -2995,7 +2738,7 @@ dotenv@^8.2.0:
|
|||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
|
||||
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
|
||||
downloadjs@^1.4.7:
|
||||
downloadjs@1.4.7:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/downloadjs/-/downloadjs-1.4.7.tgz#f69f96f940e0d0553dac291139865a3cd0101e3c"
|
||||
integrity sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=
|
||||
|
@ -3842,7 +3585,7 @@ human-signals@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||
|
||||
iconv-lite@0.4, iconv-lite@0.4.24:
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
|
@ -5047,11 +4790,6 @@ lodash-es@^4.17.11:
|
|||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
||||
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
|
||||
|
||||
lodash.assign@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
|
||||
integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
|
||||
|
||||
lodash.once@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
|
@ -5062,7 +4800,12 @@ lodash.sortby@^4.7.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19:
|
||||
lodash@4.17.13:
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
|
||||
integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==
|
||||
|
||||
lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
@ -6135,7 +5878,7 @@ relateurl@0.2.x:
|
|||
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
|
||||
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
|
||||
|
||||
remixicon@^2.5.0:
|
||||
remixicon@2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41"
|
||||
integrity sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww==
|
||||
|
@ -6436,11 +6179,6 @@ run-parallel@^1.1.9:
|
|||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
|
||||
integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==
|
||||
|
||||
rw@1:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
||||
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
|
||||
|
||||
rxjs@^6.3.3, rxjs@^6.5.5:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||
|
@ -6583,10 +6321,10 @@ shellwords@^0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
|
||||
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
|
||||
|
||||
shortid@^2.2.15:
|
||||
version "2.2.16"
|
||||
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608"
|
||||
integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==
|
||||
shortid@2.2.15:
|
||||
version "2.2.15"
|
||||
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122"
|
||||
integrity sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==
|
||||
dependencies:
|
||||
nanoid "^2.1.0"
|
||||
|
||||
|
@ -6996,7 +6734,7 @@ svelte-loading-spinners@^0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/svelte-loading-spinners/-/svelte-loading-spinners-0.1.1.tgz#a35a811b7db0389ec2a5de6904c718c58c36e1f9"
|
||||
integrity sha512-or4zs10VOdczOJo3u25IINXQOkZbLNAxMrXK0PRbzVoJtPQq/QZPNxI32383bpe+soYcEKmESbmW+JlW3MbUKQ==
|
||||
|
||||
svelte-portal@^0.1.0:
|
||||
svelte-portal@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-0.1.0.tgz#cc2821cc84b05ed5814e0218dcdfcbebc53c1742"
|
||||
integrity sha512-kef+ksXVKun224mRxat+DdO4C+cGHla+fEcZfnBAvoZocwiaceOfhf5azHYOPXSSB1igWVFTEOF3CDENPnuWxg==
|
||||
|
@ -7317,16 +7055,16 @@ util.promisify@^1.0.0:
|
|||
has-symbols "^1.0.1"
|
||||
object.getownpropertydescriptors "^2.1.0"
|
||||
|
||||
uuid@8.3.1:
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
||||
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
uuid@^8.3.1:
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
||||
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
|
@ -7586,10 +7324,10 @@ yauzl@^2.10.0:
|
|||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yup@^0.29.2:
|
||||
version "0.29.3"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
|
||||
integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==
|
||||
yup@0.29.2:
|
||||
version "0.29.2"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.2.tgz#5302abd9024cca335b987793f8df868e410b7b67"
|
||||
integrity sha512-FbAAeopli+TnpZ8Lzv2M72wltLw58iWBT7wW8FuAPFPb3CelXmSKCXQbV1o4keywpIK1BZ0ULTLv2s3w1CfOwA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.5"
|
||||
fn-name "~3.0.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"license": "MPL-2.0",
|
||||
"main": "dist/budibase-client.js",
|
||||
"module": "dist/budibase-client.js",
|
||||
|
@ -9,14 +9,14 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/string-templates": "^0.7.1",
|
||||
"@budibase/string-templates": "^0.7.4",
|
||||
"deep-equal": "^2.0.1",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@budibase/standard-components": "^0.7.1",
|
||||
"@budibase/standard-components": "^0.7.4",
|
||||
"@rollup/plugin-commonjs": "^16.0.0",
|
||||
"@rollup/plugin-node-resolve": "^10.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
|
@ -30,5 +30,5 @@
|
|||
"svelte": "^3.30.0",
|
||||
"svelte-jester": "^1.0.6"
|
||||
},
|
||||
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491"
|
||||
"gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { notificationStore } from "../store/notification"
|
||||
import { notificationStore, datasourceStore } from "../store"
|
||||
import API from "./api"
|
||||
import { fetchTableDefinition } from "./tables"
|
||||
|
||||
|
@ -6,6 +6,9 @@ import { fetchTableDefinition } from "./tables"
|
|||
* Fetches data about a certain row in a table.
|
||||
*/
|
||||
export const fetchRow = async ({ tableId, rowId }) => {
|
||||
if (!tableId || !rowId) {
|
||||
return
|
||||
}
|
||||
const row = await API.get({
|
||||
url: `/api/${tableId}/rows/${rowId}`,
|
||||
})
|
||||
|
@ -16,6 +19,9 @@ export const fetchRow = async ({ tableId, rowId }) => {
|
|||
* Creates a row in a table.
|
||||
*/
|
||||
export const saveRow = async row => {
|
||||
if (!row?.tableId) {
|
||||
return
|
||||
}
|
||||
const res = await API.post({
|
||||
url: `/api/${row.tableId}/rows`,
|
||||
body: row,
|
||||
|
@ -23,6 +29,10 @@ export const saveRow = async row => {
|
|||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row saved")
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(row.tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -30,6 +40,9 @@ export const saveRow = async row => {
|
|||
* Updates a row in a table.
|
||||
*/
|
||||
export const updateRow = async row => {
|
||||
if (!row?.tableId || !row?._id) {
|
||||
return
|
||||
}
|
||||
const res = await API.patch({
|
||||
url: `/api/${row.tableId}/rows/${row._id}`,
|
||||
body: row,
|
||||
|
@ -37,6 +50,10 @@ export const updateRow = async row => {
|
|||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row updated")
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(row.tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -44,12 +61,19 @@ export const updateRow = async row => {
|
|||
* Deletes a row from a table.
|
||||
*/
|
||||
export const deleteRow = async ({ tableId, rowId, revId }) => {
|
||||
if (!tableId || !rowId || !revId) {
|
||||
return
|
||||
}
|
||||
const res = await API.del({
|
||||
url: `/api/${tableId}/rows/${rowId}/${revId}`,
|
||||
})
|
||||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success("Row deleted")
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
@ -57,6 +81,9 @@ export const deleteRow = async ({ tableId, rowId, revId }) => {
|
|||
* Deletes many rows from a table.
|
||||
*/
|
||||
export const deleteRows = async ({ tableId, rows }) => {
|
||||
if (!tableId || !rows) {
|
||||
return
|
||||
}
|
||||
const res = await API.post({
|
||||
url: `/api/${tableId}/rows`,
|
||||
body: {
|
||||
|
@ -67,6 +94,10 @@ export const deleteRows = async ({ tableId, rows }) => {
|
|||
res.error
|
||||
? notificationStore.danger("An error has occurred")
|
||||
: notificationStore.success(`${rows.length} row(s) deleted`)
|
||||
|
||||
// Refresh related datasources
|
||||
datasourceStore.actions.invalidateDatasource(tableId)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { setContext, onMount } from "svelte"
|
||||
import Component from "./Component.svelte"
|
||||
import NotificationDisplay from "./NotificationDisplay.svelte"
|
||||
import Provider from "./Provider.svelte"
|
||||
import SDK from "../sdk"
|
||||
import {
|
||||
createContextStore,
|
||||
|
@ -27,6 +28,8 @@
|
|||
</script>
|
||||
|
||||
{#if loaded && $screenStore.activeLayout}
|
||||
<Component definition={$screenStore.activeLayout.props} />
|
||||
<NotificationDisplay />
|
||||
<Provider key="user" data={$authStore}>
|
||||
<Component definition={$screenStore.activeLayout.props} />
|
||||
<NotificationDisplay />
|
||||
</Provider>
|
||||
{/if}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import * as ComponentLibrary from "@budibase/standard-components"
|
||||
import Router from "./Router.svelte"
|
||||
import { enrichProps, propsAreSame } from "../utils/componentProps"
|
||||
import { authStore, builderStore } from "../store"
|
||||
import { builderStore } from "../store"
|
||||
import { hashString } from "../utils/hash"
|
||||
|
||||
export let definition = {}
|
||||
|
@ -32,7 +32,7 @@
|
|||
$: constructor = getComponentConstructor(definition._component)
|
||||
$: children = definition._children || []
|
||||
$: id = definition._id
|
||||
$: updateComponentProps(definition, $context, $authStore)
|
||||
$: updateComponentProps(definition, $context)
|
||||
$: styles = definition._styles
|
||||
|
||||
// Update component context
|
||||
|
@ -53,13 +53,13 @@
|
|||
}
|
||||
|
||||
// Enriches any string component props using handlebars
|
||||
const updateComponentProps = async (definition, context, user) => {
|
||||
const updateComponentProps = async (definition, context) => {
|
||||
// Record the timestamp so we can reference it after enrichment
|
||||
latestUpdateTime = Date.now()
|
||||
const enrichmentTime = latestUpdateTime
|
||||
|
||||
// Enrich props with context
|
||||
const enrichedProps = await enrichProps(definition, context, user)
|
||||
const enrichedProps = await enrichProps(definition, context)
|
||||
|
||||
// Abandon this update if a newer update has started
|
||||
if (enrichmentTime !== latestUpdateTime) {
|
||||
|
|
|
@ -1,29 +1,54 @@
|
|||
<script>
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { createContextStore } from "../store"
|
||||
import { getContext, setContext, onMount } from "svelte"
|
||||
import { datasourceStore, createContextStore } from "../store"
|
||||
import { ActionTypes } from "../constants"
|
||||
import { generate } from "shortid"
|
||||
|
||||
export let data
|
||||
export let actions
|
||||
export let key
|
||||
|
||||
// Clone and create new data context for this component tree
|
||||
const context = getContext("context")
|
||||
const component = getContext("component")
|
||||
const newContext = createContextStore($context)
|
||||
setContext("context", newContext)
|
||||
$: providerKey = key || $component.id
|
||||
|
||||
// Instance ID is unique to each instance of a provider
|
||||
let instanceId
|
||||
|
||||
// Add data context
|
||||
$: {
|
||||
if (data !== undefined) {
|
||||
newContext.actions.provideData($component.id, data)
|
||||
}
|
||||
}
|
||||
$: data !== undefined && newContext.actions.provideData(providerKey, data)
|
||||
|
||||
// Add actions context
|
||||
$: {
|
||||
actions?.forEach(({ type, callback }) => {
|
||||
newContext.actions.provideAction($component.id, type, callback)
|
||||
})
|
||||
if (instanceId) {
|
||||
actions?.forEach(({ type, callback, metadata }) => {
|
||||
newContext.actions.provideAction(providerKey, type, callback)
|
||||
|
||||
// Register any "refresh datasource" actions with a singleton store
|
||||
// so we can easily refresh data at all levels for any datasource
|
||||
if (type === ActionTypes.RefreshDatasource) {
|
||||
const { datasource } = metadata || {}
|
||||
datasourceStore.actions.registerDatasource(
|
||||
datasource,
|
||||
instanceId,
|
||||
callback
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Generate a permanent unique ID for this component and use it to register
|
||||
// any datasource actions
|
||||
instanceId = generate()
|
||||
|
||||
// Unregister all datasource instances when unmounting this provider
|
||||
return () => datasourceStore.actions.unregisterInstance(instanceId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
|
|
|
@ -4,4 +4,5 @@ export const TableNames = {
|
|||
|
||||
export const ActionTypes = {
|
||||
ValidateForm: "ValidateForm",
|
||||
RefreshDatasource: "RefreshDatasource",
|
||||
}
|
||||
|
|
|
@ -4,26 +4,28 @@ export const createContextStore = existingContext => {
|
|||
const store = writable({ ...existingContext })
|
||||
|
||||
// Adds a data context layer to the tree
|
||||
const provideData = (componentId, data) => {
|
||||
const provideData = (providerId, data) => {
|
||||
if (!providerId) {
|
||||
return
|
||||
}
|
||||
store.update(state => {
|
||||
if (componentId) {
|
||||
state[componentId] = data
|
||||
state[providerId] = data
|
||||
|
||||
// Keep track of the closest component ID so we can later hydrate a "data" prop.
|
||||
// This is only required for legacy bindings that used "data" rather than a
|
||||
// component ID.
|
||||
state.closestComponentId = componentId
|
||||
}
|
||||
// Keep track of the closest component ID so we can later hydrate a "data" prop.
|
||||
// This is only required for legacy bindings that used "data" rather than a
|
||||
// component ID.
|
||||
state.closestComponentId = providerId
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
// Adds an action context layer to the tree
|
||||
const provideAction = (componentId, actionType, callback) => {
|
||||
const provideAction = (providerId, actionType, callback) => {
|
||||
if (!providerId || !actionType) {
|
||||
return
|
||||
}
|
||||
store.update(state => {
|
||||
if (actionType && componentId) {
|
||||
state[`${componentId}_${actionType}`] = callback
|
||||
}
|
||||
state[`${providerId}_${actionType}`] = callback
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
|
||||
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") {
|
||||
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
|
||||
})
|
||||
relatedInstances?.forEach(instance => {
|
||||
instance.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: { registerDatasource, unregisterInstance, invalidateDatasource },
|
||||
}
|
||||
}
|
||||
|
||||
export const datasourceStore = createDatasourceStore()
|
|
@ -3,6 +3,7 @@ export { notificationStore } from "./notification"
|
|||
export { routeStore } from "./routes"
|
||||
export { screenStore } from "./screens"
|
||||
export { builderStore } from "./builder"
|
||||
export { datasourceStore } from "./datasource"
|
||||
|
||||
// Context stores are layered and duplicated, so it is not a singleton
|
||||
export { createContextStore } from "./context"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { get } from "svelte/store"
|
||||
import { enrichDataBinding, enrichDataBindings } from "./enrichDataBinding"
|
||||
import { routeStore, builderStore } from "../store"
|
||||
import { saveRow, deleteRow, executeQuery, triggerAutomation } from "../api"
|
||||
import { ActionTypes } from "../constants"
|
||||
|
@ -10,35 +9,26 @@ const saveRowHandler = async (action, context) => {
|
|||
let draft = context[providerId]
|
||||
if (fields) {
|
||||
for (let [key, entry] of Object.entries(fields)) {
|
||||
draft[key] = await enrichDataBinding(entry.value, context)
|
||||
draft[key] = entry.value
|
||||
}
|
||||
}
|
||||
await saveRow(draft)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteRowHandler = async (action, context) => {
|
||||
const deleteRowHandler = async action => {
|
||||
const { tableId, revId, rowId } = action.parameters
|
||||
if (tableId && revId && rowId) {
|
||||
const [enrichTable, enrichRow, enrichRev] = await Promise.all([
|
||||
enrichDataBinding(tableId, context),
|
||||
enrichDataBinding(rowId, context),
|
||||
enrichDataBinding(revId, context),
|
||||
])
|
||||
await deleteRow({
|
||||
tableId: enrichTable,
|
||||
rowId: enrichRow,
|
||||
revId: enrichRev,
|
||||
})
|
||||
await deleteRow({ tableId, rowId, revId })
|
||||
}
|
||||
}
|
||||
|
||||
const triggerAutomationHandler = async (action, context) => {
|
||||
const { fields } = action.parameters()
|
||||
const triggerAutomationHandler = async action => {
|
||||
const { fields } = action.parameters
|
||||
if (fields) {
|
||||
const params = {}
|
||||
for (let field in fields) {
|
||||
params[field] = await enrichDataBinding(fields[field].value, context)
|
||||
params[field] = fields[field].value
|
||||
}
|
||||
await triggerAutomation(action.parameters.automationId, params)
|
||||
}
|
||||
|
@ -60,14 +50,29 @@ const queryExecutionHandler = async action => {
|
|||
})
|
||||
}
|
||||
|
||||
const validateFormHandler = async (action, context) => {
|
||||
const { componentId } = action.parameters
|
||||
const fn = context[`${componentId}_${ActionTypes.ValidateForm}`]
|
||||
const executeActionHandler = async (context, componentId, actionType) => {
|
||||
const fn = context[`${componentId}_${actionType}`]
|
||||
if (fn) {
|
||||
return await fn()
|
||||
}
|
||||
}
|
||||
|
||||
const validateFormHandler = async (action, context) => {
|
||||
return await executeActionHandler(
|
||||
context,
|
||||
action.parameters.componentId,
|
||||
ActionTypes.ValidateForm
|
||||
)
|
||||
}
|
||||
|
||||
const refreshDatasourceHandler = async (action, context) => {
|
||||
return await executeActionHandler(
|
||||
context,
|
||||
action.parameters.componentId,
|
||||
ActionTypes.RefreshDatasource
|
||||
)
|
||||
}
|
||||
|
||||
const handlerMap = {
|
||||
["Save Row"]: saveRowHandler,
|
||||
["Delete Row"]: deleteRowHandler,
|
||||
|
@ -75,6 +80,7 @@ const handlerMap = {
|
|||
["Execute Query"]: queryExecutionHandler,
|
||||
["Trigger Automation"]: triggerAutomationHandler,
|
||||
["Validate Form"]: validateFormHandler,
|
||||
["Refresh Datasource"]: refreshDatasourceHandler,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => {
|
|||
* Enriches component props.
|
||||
* Data bindings are enriched, and button actions are enriched.
|
||||
*/
|
||||
export const enrichProps = async (props, context, user) => {
|
||||
export const enrichProps = async (props, context) => {
|
||||
// Exclude all private props that start with an underscore
|
||||
let validProps = {}
|
||||
Object.entries(props)
|
||||
|
@ -34,7 +34,6 @@ export const enrichProps = async (props, context, user) => {
|
|||
// Duplicate the closest context as "data" which the builder requires
|
||||
const totalContext = {
|
||||
...context,
|
||||
user,
|
||||
|
||||
// This is only required for legacy bindings that used "data" rather than a
|
||||
// component ID.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/electron.js",
|
||||
"repository": {
|
||||
|
@ -50,58 +50,58 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/client": "^0.7.1",
|
||||
"@budibase/string-templates": "^0.7.1",
|
||||
"@elastic/elasticsearch": "^7.10.0",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@sendgrid/mail": "^7.1.1",
|
||||
"@sentry/node": "^5.19.2",
|
||||
"airtable": "^0.10.1",
|
||||
"arangojs": "^7.2.0",
|
||||
"@budibase/client": "^0.7.4",
|
||||
"@budibase/string-templates": "^0.7.4",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
"@sentry/node": "5.19.2",
|
||||
"airtable": "0.10.1",
|
||||
"arangojs": "7.2.0",
|
||||
"aws-sdk": "^2.767.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chmodr": "^1.2.0",
|
||||
"csvtojson": "^2.0.10",
|
||||
"dotenv": "^8.2.0",
|
||||
"download": "^8.0.0",
|
||||
"electron-is-dev": "^1.2.0",
|
||||
"electron-unhandled": "^3.0.2",
|
||||
"electron-updater": "^4.3.1",
|
||||
"electron-util": "^0.14.2",
|
||||
"fix-path": "^3.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"jimp": "^0.16.1",
|
||||
"joi": "^17.2.1",
|
||||
"jsonschema": "^1.4.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"koa": "^2.7.0",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-compress": "^4.0.1",
|
||||
"koa-pino-logger": "^3.0.0",
|
||||
"koa-send": "^5.0.0",
|
||||
"koa-session": "^5.12.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"lodash": "^4.17.13",
|
||||
"mongodb": "^3.6.3",
|
||||
"mssql": "^6.2.3",
|
||||
"mysql": "^2.18.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"open": "^7.3.0",
|
||||
"pg": "^8.5.1",
|
||||
"pino-pretty": "^4.0.0",
|
||||
"pouchdb": "^7.2.1",
|
||||
"pouchdb-all-dbs": "^1.0.2",
|
||||
"pouchdb-replication-stream": "^1.2.9",
|
||||
"sanitize-s3-objectkey": "^0.0.1",
|
||||
"server-destroy": "^1.0.1",
|
||||
"svelte": "^3.30.0",
|
||||
"tar-fs": "^2.1.0",
|
||||
"to-json-schema": "^0.2.5",
|
||||
"uuid": "^3.3.2",
|
||||
"validate.js": "^0.13.1",
|
||||
"worker-farm": "^1.7.0",
|
||||
"yargs": "^13.2.4",
|
||||
"zlib": "^1.0.5"
|
||||
"bcryptjs": "2.4.3",
|
||||
"chmodr": "1.2.0",
|
||||
"csvtojson": "2.0.10",
|
||||
"dotenv": "8.2.0",
|
||||
"download": "8.0.0",
|
||||
"electron-is-dev": "1.2.0",
|
||||
"electron-unhandled": "3.0.2",
|
||||
"electron-updater": "4.3.1",
|
||||
"electron-util": "0.14.2",
|
||||
"fix-path": "3.0.0",
|
||||
"fs-extra": "8.1.0",
|
||||
"jimp": "0.16.1",
|
||||
"joi": "17.2.1",
|
||||
"jsonschema": "1.4.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"koa": "2.7.0",
|
||||
"koa-body": "4.2.0",
|
||||
"koa-compress": "4.0.1",
|
||||
"koa-pino-logger": "3.0.0",
|
||||
"koa-send": "5.0.0",
|
||||
"koa-session": "5.12.0",
|
||||
"koa-static": "5.0.0",
|
||||
"lodash": "4.17.13",
|
||||
"mongodb": "3.6.3",
|
||||
"mssql": "6.2.3",
|
||||
"mysql": "2.18.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"open": "7.3.0",
|
||||
"pg": "8.5.1",
|
||||
"pino-pretty": "4.0.0",
|
||||
"pouchdb": "7.2.1",
|
||||
"pouchdb-all-dbs": "1.0.2",
|
||||
"pouchdb-replication-stream": "1.2.9",
|
||||
"sanitize-s3-objectkey": "0.0.1",
|
||||
"server-destroy": "1.0.1",
|
||||
"svelte": "3.30.0",
|
||||
"tar-fs": "2.1.0",
|
||||
"to-json-schema": "0.2.5",
|
||||
"uuid": "3.3.2",
|
||||
"validate.js": "0.13.1",
|
||||
"worker-farm": "1.7.0",
|
||||
"yargs": "13.2.4",
|
||||
"zlib": "1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/test-sequencer": "^24.8.0",
|
||||
|
@ -120,5 +120,5 @@
|
|||
"./scripts/jestSetup.js"
|
||||
]
|
||||
},
|
||||
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491"
|
||||
"gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd"
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@ const fs = require("fs-extra")
|
|||
exports.exportAppDump = async function(ctx) {
|
||||
const { appId } = ctx.query
|
||||
|
||||
const appname = decodeURI(ctx.query.appname)
|
||||
|
||||
const backupsDir = path.join(os.homedir(), ".budibase", "backups")
|
||||
fs.ensureDirSync(backupsDir)
|
||||
|
||||
const backupIdentifier = `${appId} Backup: ${new Date()}.txt`
|
||||
const backupIdentifier = `${appname}Backup${new Date().getTime()}.txt`
|
||||
|
||||
await performDump({
|
||||
dir: backupsDir,
|
||||
|
@ -23,19 +25,4 @@ exports.exportAppDump = async function(ctx) {
|
|||
|
||||
ctx.attachment(backupIdentifier)
|
||||
ctx.body = fs.createReadStream(backupFile)
|
||||
// ctx.body = {
|
||||
// url: `/api/backups/download/${backupIdentifier}`,
|
||||
// }
|
||||
}
|
||||
|
||||
// exports.downloadAppDump = async function(ctx) {
|
||||
// const fileName = ctx.params.fileName
|
||||
|
||||
// const backupsDir = path.join(os.homedir(), ".budibase", "backups")
|
||||
// fs.ensureDirSync(backupsDir)
|
||||
|
||||
// const backupFile = path.join(backupsDir, fileName)
|
||||
|
||||
// ctx.attachment(fileName)
|
||||
// ctx.body = fs.createReadStream(backupFile)
|
||||
// }
|
||||
|
|
|
@ -83,23 +83,42 @@ const controller = {
|
|||
ctx.message = `View ${ctx.params.viewName} saved successfully.`
|
||||
},
|
||||
exportView: async ctx => {
|
||||
const view = ctx.query.view
|
||||
const db = new CouchDB(ctx.user.appId)
|
||||
const designDoc = await db.get("_design/database")
|
||||
|
||||
const viewName = decodeURI(ctx.query.view)
|
||||
|
||||
const view = designDoc.views[viewName]
|
||||
const format = ctx.query.format
|
||||
|
||||
// Fetch view rows
|
||||
ctx.params.viewName = view.name
|
||||
ctx.query.group = view.groupBy
|
||||
if (view.field) {
|
||||
ctx.query.stats = true
|
||||
ctx.query.field = view.field
|
||||
if (view) {
|
||||
ctx.params.viewName = viewName
|
||||
// Fetch view rows
|
||||
ctx.query = {
|
||||
group: view.meta.groupBy,
|
||||
calculation: view.meta.calculation,
|
||||
stats: !!view.meta.field,
|
||||
field: view.meta.field,
|
||||
}
|
||||
} else {
|
||||
// table all_ view
|
||||
ctx.params.viewName = viewName
|
||||
}
|
||||
|
||||
await fetchView(ctx)
|
||||
|
||||
let schema = view && view.meta && view.meta.schema
|
||||
if (!schema) {
|
||||
const tableId = ctx.params.tableId || view.meta.tableId
|
||||
const table = await db.get(tableId)
|
||||
schema = table.schema
|
||||
}
|
||||
|
||||
// Export part
|
||||
let headers = Object.keys(view.schema)
|
||||
let headers = Object.keys(schema)
|
||||
const exporter = exporters[format]
|
||||
const exportedFile = exporter(headers, ctx.body)
|
||||
const filename = `${view.name}.${format}`
|
||||
const filename = `${viewName}.${format}`
|
||||
fs.writeFileSync(join(os.tmpdir(), filename), exportedFile)
|
||||
|
||||
ctx.attachment(filename)
|
||||
|
|
|
@ -6,10 +6,5 @@ const { BUILDER } = require("../../utilities/security/permissions")
|
|||
const router = Router()
|
||||
|
||||
router.get("/api/backups/export", authorized(BUILDER), controller.exportAppDump)
|
||||
// .get(
|
||||
// "/api/backups/download/:fileName",
|
||||
// authorized(BUILDER),
|
||||
// controller.downloadAppDump
|
||||
// )
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -12,6 +12,7 @@ const usage = require("../../middleware/usageQuota")
|
|||
const router = Router()
|
||||
|
||||
router
|
||||
.get("/api/views/export", authorized(BUILDER), viewController.exportView)
|
||||
.get(
|
||||
"/api/views/:viewName",
|
||||
authorized(PermissionTypes.VIEW, PermissionLevels.READ),
|
||||
|
@ -25,6 +26,5 @@ router
|
|||
viewController.destroy
|
||||
)
|
||||
.post("/api/views", authorized(BUILDER), usage, viewController.save)
|
||||
.post("/api/views/export", authorized(BUILDER), viewController.exportView)
|
||||
|
||||
module.exports = router
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -106,6 +106,7 @@
|
|||
"styleable": true,
|
||||
"hasChildren": true,
|
||||
"dataProvider": true,
|
||||
"actions": ["RefreshDatasource"],
|
||||
"settings": [
|
||||
{
|
||||
"type": "datasource",
|
||||
|
@ -1045,12 +1046,11 @@
|
|||
"styleable": true,
|
||||
"hasChildren": true,
|
||||
"dataProvider": true,
|
||||
"datasourceSetting": "datasource",
|
||||
"actions": ["ValidateForm"],
|
||||
"settings": [
|
||||
{
|
||||
"type": "datasource",
|
||||
"label": "Data",
|
||||
"type": "schema",
|
||||
"label": "Schema",
|
||||
"key": "datasource"
|
||||
},
|
||||
{
|
||||
|
@ -1059,6 +1059,10 @@
|
|||
"key": "theme",
|
||||
"defaultValue": "spectrum--light",
|
||||
"options": [
|
||||
{
|
||||
"label": "Lightest",
|
||||
"value": "spectrum--lightest"
|
||||
},
|
||||
{
|
||||
"label": "Light",
|
||||
"value": "spectrum--light"
|
||||
|
@ -1282,7 +1286,7 @@
|
|||
"styleable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "field/relationship",
|
||||
"type": "field/link",
|
||||
"label": "Field",
|
||||
"key": "field"
|
||||
},
|
||||
|
|
|
@ -35,9 +35,9 @@
|
|||
"keywords": [
|
||||
"svelte"
|
||||
],
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"license": "MIT",
|
||||
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
|
||||
"gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd",
|
||||
"dependencies": {
|
||||
"@adobe/spectrum-css-workflow-icons": "^1.1.0",
|
||||
"@budibase/bbui": "^1.55.1",
|
||||
|
|
|
@ -2,15 +2,23 @@
|
|||
import { getContext } from "svelte"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
||||
const { API, styleable, Provider, builderStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
export let datasource = []
|
||||
|
||||
const { API, styleable, Provider, builderStore, ActionTypes } = getContext(
|
||||
"sdk"
|
||||
)
|
||||
const component = getContext("component")
|
||||
let rows = []
|
||||
let loaded = false
|
||||
|
||||
$: fetchData(datasource)
|
||||
$: actions = [
|
||||
{
|
||||
type: ActionTypes.RefreshDatasource,
|
||||
callback: () => fetchData(datasource),
|
||||
metadata: { datasource },
|
||||
},
|
||||
]
|
||||
|
||||
async function fetchData(datasource) {
|
||||
if (!isEmpty(datasource)) {
|
||||
|
@ -20,23 +28,25 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if rows.length > 0}
|
||||
<div use:styleable={$component.styles}>
|
||||
{#if $component.children === 0 && $builderStore.inBuilder}
|
||||
<p>Add some components too</p>
|
||||
{:else}
|
||||
{#each rows as row}
|
||||
<Provider data={row}>
|
||||
<slot />
|
||||
</Provider>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if loaded && $builderStore.inBuilder}
|
||||
<div use:styleable={$component.styles}>
|
||||
<p>Feed me some data</p>
|
||||
</div>
|
||||
{/if}
|
||||
<Provider {actions}>
|
||||
{#if rows.length > 0}
|
||||
<div use:styleable={$component.styles}>
|
||||
{#if $component.children === 0 && $builderStore.inBuilder}
|
||||
<p>Add some components too</p>
|
||||
{:else}
|
||||
{#each rows as row}
|
||||
<Provider data={row}>
|
||||
<slot />
|
||||
</Provider>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else if loaded && $builderStore.inBuilder}
|
||||
<div use:styleable={$component.styles}>
|
||||
<p>Feed me some data</p>
|
||||
</div>
|
||||
{/if}
|
||||
</Provider>
|
||||
|
||||
<style>
|
||||
p {
|
||||
|
|
|
@ -63,13 +63,18 @@
|
|||
.nav__menu {
|
||||
display: flex;
|
||||
margin-top: 40px;
|
||||
gap: 16px;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.nav__menu > a {
|
||||
|
||||
.nav__menu > * {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
:global(.nav__menu > a) {
|
||||
font-size: 1.5em;
|
||||
text-decoration: none;
|
||||
margin-right: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
import Dropzone from "../attachments/Dropzone.svelte"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
|
@ -21,8 +21,14 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<SpectrumField {label} {field} bind:fieldState bind:fieldApi defaultValue={[]}>
|
||||
<Field
|
||||
{label}
|
||||
{field}
|
||||
type="attachment"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
defaultValue={[]}>
|
||||
{#if mounted}
|
||||
<Dropzone bind:files={value} />
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import "@spectrum-css/checkbox/dist/index-vars.css"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
|
||||
export let field
|
||||
export let label
|
||||
|
@ -15,9 +14,10 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField
|
||||
<Field
|
||||
{label}
|
||||
{field}
|
||||
type="boolean"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
defaultValue={false}>
|
||||
|
@ -48,4 +48,4 @@
|
|||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import Flatpickr from "svelte-flatpickr"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
import "flatpickr/dist/flatpickr.css"
|
||||
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
||||
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField {label} {field} bind:fieldState bind:fieldApi>
|
||||
<Field {label} {field} type="datetime" bind:fieldState bind:fieldApi>
|
||||
{#if fieldState}
|
||||
<Flatpickr
|
||||
bind:flatpickr
|
||||
|
@ -114,7 +114,7 @@
|
|||
<div class="overlay" on:mousedown|self={flatpickr?.close} />
|
||||
{/if}
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
||||
<style>
|
||||
.spectrum-Textfield-input {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
export let fieldApi
|
||||
export let fieldSchema
|
||||
export let defaultValue
|
||||
export let type
|
||||
|
||||
// Get contexts
|
||||
const formContext = getContext("form")
|
||||
|
@ -45,6 +46,10 @@
|
|||
<Placeholder>
|
||||
Add the Field setting to start using your component
|
||||
</Placeholder>
|
||||
{:else if fieldSchema?.type && fieldSchema?.type !== type}
|
||||
<Placeholder>
|
||||
This Field setting is the wrong data type for this component
|
||||
</Placeholder>
|
||||
{:else}
|
||||
<slot />
|
||||
{#if $fieldState.error}
|
|
@ -133,7 +133,15 @@
|
|||
} else {
|
||||
table = await API.fetchTableDefinition(datasource?.tableId)
|
||||
if (table) {
|
||||
schema = table.schema || {}
|
||||
if (datasource?.type === "query") {
|
||||
schema = {}
|
||||
const params = table.parameters || []
|
||||
params.forEach(param => {
|
||||
schema[param.name] = { ...param, type: "string" }
|
||||
})
|
||||
} else {
|
||||
schema = table.schema || {}
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded = true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { RichText } from "@budibase/bbui"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
|
||||
export let field
|
||||
export let label
|
||||
|
@ -38,13 +38,19 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField {label} {field} bind:fieldState bind:fieldApi defaultValue="">
|
||||
<Field
|
||||
{label}
|
||||
{field}
|
||||
type="longform"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
defaultValue="">
|
||||
{#if mounted}
|
||||
<div>
|
||||
<RichText bind:value {options} />
|
||||
</div>
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
||||
<style>
|
||||
div {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
import Picker from "./Picker.svelte"
|
||||
|
||||
export let field
|
||||
|
@ -23,7 +23,13 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField {field} {label} bind:fieldState bind:fieldApi bind:fieldSchema>
|
||||
<Field
|
||||
{field}
|
||||
{label}
|
||||
type="options"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
bind:fieldSchema>
|
||||
{#if fieldState}
|
||||
<Picker
|
||||
bind:open
|
||||
|
@ -35,4 +41,4 @@
|
|||
isOptionSelected={option => option === $fieldState.value}
|
||||
onSelectOption={selectOption} />
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
import Picker from "./Picker.svelte"
|
||||
|
||||
const { API } = getContext("sdk")
|
||||
|
@ -62,9 +62,10 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField
|
||||
<Field
|
||||
{label}
|
||||
{field}
|
||||
type="link"
|
||||
bind:fieldState
|
||||
bind:fieldApi
|
||||
bind:fieldSchema
|
||||
|
@ -77,4 +78,4 @@
|
|||
onSelectOption={toggleOption}
|
||||
getOptionLabel={getDisplayName}
|
||||
getOptionValue={option => option._id} />
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||
import SpectrumField from "./SpectrumField.svelte"
|
||||
import Field from "./Field.svelte"
|
||||
|
||||
export let field
|
||||
export let label
|
||||
|
@ -31,7 +30,12 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<SpectrumField {label} {field} bind:fieldState bind:fieldApi>
|
||||
<Field
|
||||
{label}
|
||||
{field}
|
||||
type={type === 'number' ? 'number' : 'string'}
|
||||
bind:fieldState
|
||||
bind:fieldApi>
|
||||
{#if fieldState}
|
||||
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
|
||||
{#if !$fieldState.valid}
|
||||
|
@ -53,4 +57,4 @@
|
|||
class="spectrum-Textfield-input" />
|
||||
</div>
|
||||
{/if}
|
||||
</SpectrumField>
|
||||
</Field>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import flatpickr from "flatpickr"
|
||||
import { isEmpty } from "lodash/fp"
|
||||
|
||||
export const createValidatorFromConstraints = (constraints, field, table) => {
|
||||
let checks = []
|
||||
|
@ -14,33 +13,33 @@ export const createValidatorFromConstraints = (constraints, field, table) => {
|
|||
}
|
||||
|
||||
// String length constraint
|
||||
if (constraints.length?.maximum) {
|
||||
if (exists(constraints.length?.maximum)) {
|
||||
const length = constraints.length.maximum
|
||||
checks.push(lengthConstraint(length))
|
||||
}
|
||||
|
||||
// Min / max number constraint
|
||||
if (!isEmpty(constraints.numericality?.greaterThanOrEqualTo)) {
|
||||
if (exists(constraints.numericality?.greaterThanOrEqualTo)) {
|
||||
const min = constraints.numericality.greaterThanOrEqualTo
|
||||
checks.push(numericalConstraint(x => x >= min, `Minimum value is ${min}`))
|
||||
}
|
||||
if (!isEmpty(constraints.numericality?.lessThanOrEqualTo)) {
|
||||
if (exists(constraints.numericality?.lessThanOrEqualTo)) {
|
||||
const max = constraints.numericality.lessThanOrEqualTo
|
||||
checks.push(numericalConstraint(x => x <= max, `Maximum value is ${max}`))
|
||||
}
|
||||
|
||||
// Inclusion constraint
|
||||
if (!isEmpty(constraints.inclusion)) {
|
||||
if (exists(constraints.inclusion)) {
|
||||
const options = constraints.inclusion
|
||||
checks.push(inclusionConstraint(options))
|
||||
}
|
||||
|
||||
// Date constraint
|
||||
if (!isEmpty(constraints.datetime?.earliest)) {
|
||||
if (exists(constraints.datetime?.earliest)) {
|
||||
const limit = constraints.datetime.earliest
|
||||
checks.push(dateConstraint(limit, true))
|
||||
}
|
||||
if (!isEmpty(constraints.datetime?.latest)) {
|
||||
if (exists(constraints.datetime?.latest)) {
|
||||
const limit = constraints.datetime.latest
|
||||
checks.push(dateConstraint(limit, false))
|
||||
}
|
||||
|
@ -58,6 +57,8 @@ export const createValidatorFromConstraints = (constraints, field, table) => {
|
|||
}
|
||||
}
|
||||
|
||||
const exists = value => value != null && value !== ""
|
||||
|
||||
const presenceConstraint = value => {
|
||||
let invalid
|
||||
if (Array.isArray(value)) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import "@budibase/bbui/dist/bbui.css"
|
|||
import "@spectrum-css/vars/dist/spectrum-global.css"
|
||||
import "@spectrum-css/vars/dist/spectrum-medium.css"
|
||||
import "@spectrum-css/vars/dist/spectrum-large.css"
|
||||
import "@spectrum-css/vars/dist/spectrum-lightest.css"
|
||||
import "@spectrum-css/vars/dist/spectrum-light.css"
|
||||
import "@spectrum-css/vars/dist/spectrum-dark.css"
|
||||
import "@spectrum-css/vars/dist/spectrum-darkest.css"
|
||||
|
|
|
@ -1097,8 +1097,17 @@
|
|||
"format"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"example": "{{date now \"DD-MM-YYYY\"}}",
|
||||
"example": "{{date now \"DD-MM-YYYY\"}} -> 21-01-2021",
|
||||
"description": "<p>Format a date using moment.js date formatting.</p>\n"
|
||||
},
|
||||
"duration": {
|
||||
"args": [
|
||||
"time",
|
||||
"durationType"
|
||||
],
|
||||
"numArgs": 2,
|
||||
"example": "{{duration timeLeft \"seconds\"}} -> a few seconds",
|
||||
"description": "<p>Produce a humanized duration left/until given an amount of time and the type of time measurement.</p>\n"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.js",
|
||||
"module": "src/index.js",
|
||||
|
@ -32,5 +32,6 @@
|
|||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
},
|
||||
"gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd"
|
||||
}
|
||||
|
|
|
@ -5,18 +5,32 @@ const fs = require("fs")
|
|||
const doctrine = require("doctrine")
|
||||
const marked = require("marked")
|
||||
|
||||
const DIRECTORY = fs.existsSync("node_modules") ? "." : ".."
|
||||
|
||||
const FILENAME = `${DIRECTORY}/manifest.json`
|
||||
|
||||
/**
|
||||
* full list of supported helpers can be found here:
|
||||
* https://github.com/helpers/handlebars-helpers
|
||||
* https://github.com/budibase/handlebars-helpers
|
||||
*/
|
||||
|
||||
const DIRECTORY = fs.existsSync("node_modules") ? "." : ".."
|
||||
const COLLECTIONS = ["math", "array", "number", "url", "string", "comparison"]
|
||||
|
||||
const FILENAME = `${DIRECTORY}/manifest.json`
|
||||
const outputJSON = {}
|
||||
const ADDED_HELPERS = {
|
||||
date: {
|
||||
date: {
|
||||
args: ["datetime", "format"],
|
||||
numArgs: 2,
|
||||
example: '{{date now "DD-MM-YYYY"}} -> 21-01-2021',
|
||||
description: "Format a date using moment.js date formatting.",
|
||||
},
|
||||
duration: {
|
||||
args: ["time", "durationType"],
|
||||
numArgs: 2,
|
||||
example: '{{duration timeLeft "seconds"}} -> a few seconds',
|
||||
description:
|
||||
"Produce a humanized duration left/until given an amount of time and the type of time measurement.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function fixSpecialCases(name, obj) {
|
||||
const args = obj.args
|
||||
|
@ -134,15 +148,15 @@ function run() {
|
|||
}
|
||||
outputJSON[collection] = collectionInfo
|
||||
}
|
||||
// add the date helper
|
||||
outputJSON["date"] = {
|
||||
date: {
|
||||
args: ["datetime", "format"],
|
||||
numArgs: 2,
|
||||
example: '{{date now "DD-MM-YYYY"}}',
|
||||
description: "Format a date using moment.js date formatting.",
|
||||
},
|
||||
// add extra helpers
|
||||
for (let [collectionName, collection] of Object.entries(ADDED_HELPERS)) {
|
||||
let input = collection
|
||||
if (outputJSON[collectionName]) {
|
||||
input = Object.assign(outputJSON[collectionName], collection)
|
||||
}
|
||||
outputJSON[collectionName] = input
|
||||
}
|
||||
|
||||
// convert all markdown to HTML
|
||||
for (let collection of Object.values(outputJSON)) {
|
||||
for (let helper of Object.values(collection)) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
const dayjs = require("dayjs")
|
||||
dayjs.extend(require("dayjs/plugin/duration"))
|
||||
dayjs.extend(require("dayjs/plugin/advancedFormat"))
|
||||
dayjs.extend(require("dayjs/plugin/relativeTime"))
|
||||
|
||||
/**
|
||||
* This file was largely taken from the helper-date package - we did this for two reasons:
|
||||
|
@ -50,7 +53,7 @@ function getContext(thisArg, locals, options) {
|
|||
return context
|
||||
}
|
||||
|
||||
module.exports = function dateHelper(str, pattern, options) {
|
||||
function initialConfig(str, pattern, options) {
|
||||
if (isOptions(pattern)) {
|
||||
options = pattern
|
||||
pattern = null
|
||||
|
@ -61,18 +64,42 @@ module.exports = function dateHelper(str, pattern, options) {
|
|||
pattern = null
|
||||
str = null
|
||||
}
|
||||
return { str, pattern, options }
|
||||
}
|
||||
|
||||
function setLocale(str, pattern, options) {
|
||||
// if options is null then it'll get updated here
|
||||
const config = initialConfig(str, pattern, options)
|
||||
const defaults = { lang: "en", date: new Date(config.str) }
|
||||
const opts = getContext(this, defaults, config.options)
|
||||
|
||||
// set the language to use
|
||||
dayjs.locale(opts.lang || opts.language)
|
||||
}
|
||||
|
||||
module.exports.date = (str, pattern, options) => {
|
||||
const config = initialConfig(str, pattern, options)
|
||||
|
||||
// if no args are passed, return a formatted date
|
||||
if (str == null && pattern == null) {
|
||||
if (config.str == null && config.pattern == null) {
|
||||
dayjs.locale("en")
|
||||
return dayjs().format("MMMM DD, YYYY")
|
||||
}
|
||||
|
||||
const defaults = { lang: "en", date: new Date(str) }
|
||||
const opts = getContext(this, defaults, options)
|
||||
setLocale(config.str, config.pattern, config.options)
|
||||
|
||||
// set the language to use
|
||||
dayjs.locale(opts.lang || opts.language)
|
||||
|
||||
return dayjs(new Date(str)).format(pattern)
|
||||
return dayjs(new Date(config.str)).format(config.pattern)
|
||||
}
|
||||
|
||||
module.exports.duration = (str, pattern, format) => {
|
||||
const config = initialConfig(str, pattern)
|
||||
|
||||
setLocale(config.str, config.pattern)
|
||||
|
||||
const duration = dayjs.duration(config.str, config.pattern)
|
||||
if (!isOptions(format)) {
|
||||
return duration.format(format)
|
||||
} else {
|
||||
return duration.humanize()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const helpers = require("@budibase/handlebars-helpers")
|
||||
const dateHelper = require("./date")
|
||||
const { date, duration } = require("./date")
|
||||
const { HelperFunctionBuiltin } = require("./constants")
|
||||
|
||||
/**
|
||||
|
@ -18,10 +18,15 @@ const EXTERNAL_FUNCTION_COLLECTIONS = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
const DATE_NAME = "date"
|
||||
const ADDED_HELPERS = {
|
||||
date: date,
|
||||
duration: duration,
|
||||
}
|
||||
|
||||
exports.registerAll = handlebars => {
|
||||
handlebars.registerHelper(DATE_NAME, dateHelper)
|
||||
for (let [name, helper] of Object.entries(ADDED_HELPERS)) {
|
||||
handlebars.registerHelper(name, helper)
|
||||
}
|
||||
let externalNames = []
|
||||
for (let collection of EXTERNAL_FUNCTION_COLLECTIONS) {
|
||||
// collect information about helper
|
||||
|
@ -43,12 +48,13 @@ exports.registerAll = handlebars => {
|
|||
})
|
||||
}
|
||||
// add date external functionality
|
||||
externalNames.push(DATE_NAME)
|
||||
exports.externalHelperNames = externalNames
|
||||
exports.externalHelperNames = externalNames.concat(Object.keys(ADDED_HELPERS))
|
||||
}
|
||||
|
||||
exports.unregisterAll = handlebars => {
|
||||
handlebars.unregisterHelper(DATE_NAME)
|
||||
for (let name of Object.keys(ADDED_HELPERS)) {
|
||||
handlebars.unregisterHelper(name)
|
||||
}
|
||||
for (let name of module.exports.externalHelperNames) {
|
||||
handlebars.unregisterHelper(name)
|
||||
}
|
||||
|
|
|
@ -318,6 +318,17 @@ describe("Cover a few complex use cases", () => {
|
|||
expect(validity).toBe(true)
|
||||
})
|
||||
|
||||
it("test a very complex duration output", async () => {
|
||||
const currentTime = new Date(1612432082000).toISOString(),
|
||||
eventTime = new Date(1612432071000).toISOString()
|
||||
const input = `{{duration ( subtract (date currentTime "X")(date eventTime "X")) "seconds"}}`
|
||||
const output = await processString(input, {
|
||||
currentTime,
|
||||
eventTime,
|
||||
})
|
||||
expect(output).toBe("a few seconds")
|
||||
})
|
||||
|
||||
it("should confirm a bunch of invalid strings", () => {
|
||||
const invalids = ["{{ awd )", "{{ awdd () ", "{{ awdwad ", "{{ awddawd }"]
|
||||
for (let invalid of invalids) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/deployment",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.4",
|
||||
"description": "Budibase Deployment Server",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -34,5 +34,5 @@
|
|||
"pouchdb-all-dbs": "^1.0.2",
|
||||
"server-destroy": "^1.0.1"
|
||||
},
|
||||
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491"
|
||||
"gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue