Merge branch 'develop' of github.com:Budibase/budibase into feature/query-rbac-timeouts
This commit is contained in:
commit
ad68b895e1
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/auth",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"description": "Authentication middlewares for budibase builder and apps",
|
||||
"main": "src/index.js",
|
||||
"author": "Budibase",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"license": "AGPL-3.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
|
|
@ -36,5 +36,7 @@
|
|||
{getOptionLabel}
|
||||
{getOptionValue}
|
||||
on:change={onChange}
|
||||
on:pick
|
||||
on:type
|
||||
/>
|
||||
</Field>
|
||||
|
|
|
@ -40,8 +40,15 @@
|
|||
open = false
|
||||
}
|
||||
|
||||
const onChange = e => {
|
||||
selectOption(e.target.value)
|
||||
const onType = e => {
|
||||
const value = e.target.value
|
||||
dispatch("type", value)
|
||||
selectOption(value)
|
||||
}
|
||||
|
||||
const onPick = value => {
|
||||
dispatch("pick", value)
|
||||
selectOption(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -62,7 +69,7 @@
|
|||
type="text"
|
||||
on:focus={() => (focus = true)}
|
||||
on:blur={() => (focus = false)}
|
||||
on:change={onChange}
|
||||
on:change={onType}
|
||||
value={value || ""}
|
||||
placeholder={placeholder || ""}
|
||||
{disabled}
|
||||
|
@ -99,7 +106,7 @@
|
|||
role="option"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
on:click={() => selectOption(getOptionValue(option))}
|
||||
on:click={() => onPick(getOptionValue(option))}
|
||||
>
|
||||
<span class="spectrum-Menu-itemLabel"
|
||||
>{getOptionLabel(option)}</span
|
||||
|
|
|
@ -11,7 +11,8 @@ it("should rename an unpublished application", () => {
|
|||
renameApp(appRename)
|
||||
cy.searchForApplication(appRename)
|
||||
cy.get(".appGrid").find(".wrapper").should("have.length", 1)
|
||||
})
|
||||
cy.deleteApp(appRename)
|
||||
})
|
||||
|
||||
xit("Should rename a published application", () => {
|
||||
// It is not possible to rename a published application
|
||||
|
|
|
@ -43,24 +43,26 @@ Cypress.Commands.add("createApp", name => {
|
|||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("deleteApp", () => {
|
||||
Cypress.Commands.add("deleteApp", appName => {
|
||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||
cy.wait(1000)
|
||||
cy.request(`localhost:${Cypress.env("PORT")}/api/applications?status=all`)
|
||||
.its("body")
|
||||
.then(val => {
|
||||
console.log(val)
|
||||
if (val.length > 0) {
|
||||
cy.get(".title > :nth-child(3) > .spectrum-Icon").click()
|
||||
cy.contains("Delete").click()
|
||||
cy.get(".spectrum-Button--warning").click()
|
||||
cy.get(".spectrum-Modal").within(() => {
|
||||
cy.get("input").type(appName)
|
||||
cy.get(".spectrum-Button--warning").click()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("createTestApp", () => {
|
||||
const appName = "Cypress Tests"
|
||||
cy.deleteApp()
|
||||
cy.deleteApp(appName)
|
||||
cy.createApp(appName, "This app is used for Cypress testing.")
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -65,10 +65,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.180-alpha.1",
|
||||
"@budibase/client": "^0.9.180-alpha.1",
|
||||
"@budibase/bbui": "^0.9.184",
|
||||
"@budibase/client": "^0.9.184",
|
||||
"@budibase/colorpicker": "1.1.2",
|
||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.184",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -12,7 +12,7 @@ export default class PosthogClient {
|
|||
|
||||
posthog.init(this.token, {
|
||||
autocapture: false,
|
||||
capture_pageview: false,
|
||||
capture_pageview: true,
|
||||
api_host: this.url,
|
||||
})
|
||||
posthog.set_config({ persistence: "cookie" })
|
||||
|
|
|
@ -31,11 +31,11 @@ export const getBindableProperties = (asset, componentId) => {
|
|||
const deviceBindings = getDeviceBindings()
|
||||
const stateBindings = getStateBindings()
|
||||
return [
|
||||
...stateBindings,
|
||||
...deviceBindings,
|
||||
...urlBindings,
|
||||
...contextBindings,
|
||||
...urlBindings,
|
||||
...stateBindings,
|
||||
...userBindings,
|
||||
...deviceBindings,
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -217,18 +217,8 @@ const getProviderContextBindings = (asset, dataProviders) => {
|
|||
keys.forEach(key => {
|
||||
const fieldSchema = schema[key]
|
||||
|
||||
// Make safe runtime binding and replace certain bindings with a
|
||||
// new property to help display components
|
||||
let runtimeBoundKey = key
|
||||
if (fieldSchema.type === "link") {
|
||||
runtimeBoundKey = `${key}_text`
|
||||
} else if (fieldSchema.type === "attachment") {
|
||||
runtimeBoundKey = `${key}_first`
|
||||
}
|
||||
|
||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(
|
||||
runtimeBoundKey
|
||||
)}`
|
||||
// Make safe runtime binding
|
||||
const runtimeBinding = `${safeComponentId}.${makePropSafe(key)}`
|
||||
|
||||
// Optionally use a prefix with readable bindings
|
||||
let readableBinding = component._instanceName
|
||||
|
@ -267,17 +257,9 @@ const getUserBindings = () => {
|
|||
const safeUser = makePropSafe("user")
|
||||
keys.forEach(key => {
|
||||
const fieldSchema = schema[key]
|
||||
// Replace certain bindings with a new property to help display components
|
||||
let runtimeBoundKey = key
|
||||
if (fieldSchema.type === "link") {
|
||||
runtimeBoundKey = `${key}_text`
|
||||
} else if (fieldSchema.type === "attachment") {
|
||||
runtimeBoundKey = `${key}_first`
|
||||
}
|
||||
|
||||
bindings.push({
|
||||
type: "context",
|
||||
runtimeBinding: `${safeUser}.${makePropSafe(runtimeBoundKey)}`,
|
||||
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
|
||||
readableBinding: `Current User.${key}`,
|
||||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
|
|
|
@ -45,6 +45,7 @@ const INITIAL_FRONTEND_STATE = {
|
|||
state: false,
|
||||
customThemes: false,
|
||||
devicePreview: false,
|
||||
messagePassing: false,
|
||||
},
|
||||
currentFrontEndType: "none",
|
||||
selectedScreenId: "",
|
||||
|
|
|
@ -202,7 +202,7 @@
|
|||
display: inline-block;
|
||||
}
|
||||
.block {
|
||||
width: 360px;
|
||||
width: 480px;
|
||||
font-size: 16px;
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
|
|
|
@ -234,7 +234,8 @@
|
|||
<Editor
|
||||
mode="javascript"
|
||||
on:change={e => {
|
||||
onChange(e, key)
|
||||
// need to pass without the value inside
|
||||
onChange({ detail: e.detail.value }, key)
|
||||
inputData[key] = e.detail.value
|
||||
}}
|
||||
value={inputData[key]}
|
||||
|
|
|
@ -18,6 +18,11 @@
|
|||
FIELDS,
|
||||
AUTO_COLUMN_SUB_TYPES,
|
||||
RelationshipTypes,
|
||||
ALLOWABLE_STRING_OPTIONS,
|
||||
ALLOWABLE_NUMBER_OPTIONS,
|
||||
ALLOWABLE_STRING_TYPES,
|
||||
ALLOWABLE_NUMBER_TYPES,
|
||||
SWITCHABLE_TYPES,
|
||||
} from "constants/backend"
|
||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
@ -92,6 +97,9 @@
|
|||
opt.type === table.type &&
|
||||
table.sourceId === opt.sourceId
|
||||
)
|
||||
$: typeEnabled =
|
||||
!originalName ||
|
||||
(originalName && SWITCHABLE_TYPES.indexOf(field.type) !== -1)
|
||||
|
||||
async function saveColumn() {
|
||||
if (field.type === AUTO_TYPE) {
|
||||
|
@ -204,7 +212,14 @@
|
|||
}
|
||||
|
||||
function getAllowedTypes() {
|
||||
if (!external) {
|
||||
if (originalName && ALLOWABLE_STRING_TYPES.indexOf(field.type) !== -1) {
|
||||
return ALLOWABLE_STRING_OPTIONS
|
||||
} else if (
|
||||
originalName &&
|
||||
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
||||
) {
|
||||
return ALLOWABLE_NUMBER_OPTIONS
|
||||
} else if (!external) {
|
||||
return [
|
||||
...Object.values(fieldDefinitions),
|
||||
{ name: "Auto Column", type: AUTO_TYPE },
|
||||
|
@ -259,7 +274,7 @@
|
|||
/>
|
||||
|
||||
<Select
|
||||
disabled={originalName}
|
||||
disabled={!typeEnabled}
|
||||
label="Type"
|
||||
bind:value={field.type}
|
||||
on:change={handleTypeChange}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let onOk = undefined
|
||||
export let onCancel = undefined
|
||||
export let warning = true
|
||||
export let disabled
|
||||
|
||||
let modal
|
||||
|
||||
|
@ -26,6 +27,7 @@
|
|||
confirmText={okText}
|
||||
{cancelText}
|
||||
{warning}
|
||||
{disabled}
|
||||
>
|
||||
<Body size="S">
|
||||
{body}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
export let disabled = false
|
||||
export let options
|
||||
export let allowJS = true
|
||||
export let appendBindingsAsOptions = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let bindingDrawer
|
||||
|
@ -24,15 +25,30 @@
|
|||
$: readableValue = runtimeToReadableBinding(bindings, value)
|
||||
$: tempValue = readableValue
|
||||
$: isJS = isJSBinding(value)
|
||||
$: allOptions = buildOptions(options, bindings, appendBindingsAsOptions)
|
||||
|
||||
const handleClose = () => {
|
||||
onChange(tempValue)
|
||||
bindingDrawer.hide()
|
||||
}
|
||||
|
||||
const onChange = value => {
|
||||
const onChange = (value, optionPicked) => {
|
||||
// Add HBS braces if picking binding
|
||||
if (optionPicked && !options?.includes(value)) {
|
||||
value = `{{ ${value} }}`
|
||||
}
|
||||
|
||||
dispatch("change", readableToRuntimeBinding(bindings, value))
|
||||
}
|
||||
|
||||
const buildOptions = (options, bindings, appendBindingsAsOptions) => {
|
||||
if (!appendBindingsAsOptions) {
|
||||
return options
|
||||
}
|
||||
return []
|
||||
.concat(options || [])
|
||||
.concat(bindings?.map(binding => binding.readableBinding) || [])
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="control">
|
||||
|
@ -40,12 +56,17 @@
|
|||
{label}
|
||||
{disabled}
|
||||
value={isJS ? "(JavaScript function)" : readableValue}
|
||||
on:change={event => onChange(event.detail)}
|
||||
on:type={e => onChange(e.detail, false)}
|
||||
on:pick={e => onChange(e.detail, true)}
|
||||
{placeholder}
|
||||
{options}
|
||||
options={allOptions}
|
||||
/>
|
||||
{#if !disabled}
|
||||
<div class="icon" on:click={bindingDrawer.show}>
|
||||
<div
|
||||
class="icon"
|
||||
on:click={bindingDrawer.show}
|
||||
data-cy="text-binding-button"
|
||||
>
|
||||
<Icon size="S" name="FlashOn" />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
<script>
|
||||
import { Icon, Modal, notifications, ModalContent } from "@budibase/bbui"
|
||||
import {
|
||||
Icon,
|
||||
Input,
|
||||
Modal,
|
||||
notifications,
|
||||
ModalContent,
|
||||
} from "@budibase/bbui"
|
||||
import { store } from "builderStore"
|
||||
import api from "builderStore/api"
|
||||
|
||||
let revertModal
|
||||
let appName
|
||||
|
||||
$: appId = $store.appId
|
||||
|
||||
|
@ -33,10 +40,17 @@
|
|||
|
||||
<Icon name="Revert" hoverable on:click={revertModal.show} />
|
||||
<Modal bind:this={revertModal}>
|
||||
<ModalContent title="Revert Changes" confirmText="Revert" onConfirm={revert}>
|
||||
<ModalContent
|
||||
title="Revert Changes"
|
||||
confirmText="Revert"
|
||||
onConfirm={revert}
|
||||
disabled={appName !== $store.name}
|
||||
>
|
||||
<span
|
||||
>The changes you have made will be deleted and the application reverted
|
||||
back to its production state.</span
|
||||
>
|
||||
<span>Please enter your app name to continue.</span>
|
||||
<Input bind:value={appName} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
theme: $store.theme,
|
||||
customTheme: $store.customTheme,
|
||||
previewDevice: $store.previewDevice,
|
||||
messagePassing: $store.clientFeatures.messagePassing
|
||||
}
|
||||
|
||||
// Saving pages and screens to the DB causes them to have _revs.
|
||||
|
@ -94,10 +95,12 @@
|
|||
const handlers = {
|
||||
[MessageTypes.READY]: () => {
|
||||
// Initialise the app when mounted
|
||||
if ($store.clientFeatures.messagePassing) {
|
||||
if (!loading) return
|
||||
}
|
||||
|
||||
// Display preview immediately if the intelligent loading feature
|
||||
// is not supported
|
||||
if (!loading) return
|
||||
|
||||
if (!$store.clientFeatures.intelligentLoading) {
|
||||
loading = false
|
||||
}
|
||||
|
@ -117,17 +120,34 @@
|
|||
|
||||
onMount(() => {
|
||||
window.addEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.addEventListener("ready", () => {
|
||||
receiveMessage({ data: { type: MessageTypes.READY }})
|
||||
}, { once: true })
|
||||
iframe.contentWindow.addEventListener("error", event => {
|
||||
receiveMessage({ data: { type: MessageTypes.ERROR, error: event.detail }})
|
||||
}, { once: true })
|
||||
// Add listener for events sent by client library in preview
|
||||
iframe.contentWindow.addEventListener("bb-event", handleBudibaseEvent)
|
||||
iframe.contentWindow.addEventListener("keydown", handleKeydownEvent)
|
||||
}
|
||||
})
|
||||
|
||||
// Remove all iframe event listeners on component destroy
|
||||
onDestroy(() => {
|
||||
if (iframe.contentWindow) {
|
||||
window.removeEventListener("message", receiveMessage) //
|
||||
window.removeEventListener("message", receiveMessage)
|
||||
if (!$store.clientFeatures.messagePassing) {
|
||||
// Legacy - remove in later versions of BB
|
||||
iframe.contentWindow.removeEventListener("bb-event", handleBudibaseEvent)
|
||||
iframe.contentWindow.removeEventListener("keydown", handleKeydownEvent)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleBudibaseEvent = event => {
|
||||
const { type, data } = event.data
|
||||
const { type, data } = event.data || event.detail
|
||||
if (type === "select-component" && data.id) {
|
||||
store.actions.components.select({ _id: data.id })
|
||||
} else if (type === "update-prop") {
|
||||
|
@ -164,7 +184,7 @@
|
|||
}
|
||||
|
||||
const handleKeydownEvent = event => {
|
||||
const { key } = event.data
|
||||
const { key } = event.data || event
|
||||
if (
|
||||
(key === "Delete" || key === "Backspace") &&
|
||||
selectedComponentId &&
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"name": "Blocks",
|
||||
"icon": "Article",
|
||||
"children": [
|
||||
"tablewithsearch",
|
||||
"cardlistwithsearch"
|
||||
"tableblock",
|
||||
"cardsblock"
|
||||
]
|
||||
},
|
||||
"section",
|
||||
|
|
|
@ -97,6 +97,7 @@ export default `
|
|||
window.addEventListener("keydown", evt => {
|
||||
window.parent.postMessage({ type: "keydown", key: event.key })
|
||||
})
|
||||
|
||||
window.parent.postMessage({ type: "ready" })
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
<DetailSummary name={section.name} collapsible={false}>
|
||||
{#if idx === 0 && !componentInstance._component.endsWith("/layout")}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={Input}
|
||||
label="Name"
|
||||
key="_instanceName"
|
||||
|
|
|
@ -13,9 +13,10 @@
|
|||
import { generate } from "shortid"
|
||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||
import { OperatorOptions, getValidOperatorsForType } from "constants/lucene"
|
||||
import { selectedComponent, store } from "builderStore"
|
||||
import { selectedComponent } from "builderStore"
|
||||
import { getComponentForSettingType } from "./componentSettings"
|
||||
import PropertyControl from "./PropertyControl.svelte"
|
||||
import { getComponentSettings } from "builderStore/storeUtils"
|
||||
|
||||
export let conditions = []
|
||||
export let bindings = []
|
||||
|
@ -55,15 +56,11 @@
|
|||
]
|
||||
|
||||
let dragDisabled = true
|
||||
$: definition = store.actions.components.getDefinition(
|
||||
$selectedComponent?._component
|
||||
)
|
||||
$: settings = (definition?.settings ?? []).map(setting => {
|
||||
return {
|
||||
label: setting.label,
|
||||
value: setting.key,
|
||||
}
|
||||
})
|
||||
$: settings = getComponentSettings($selectedComponent?._component)
|
||||
$: settingOptions = settings.map(setting => ({
|
||||
label: setting.label,
|
||||
value: setting.key,
|
||||
}))
|
||||
$: conditions.forEach(link => {
|
||||
if (!link.id) {
|
||||
link.id = generate()
|
||||
|
@ -71,9 +68,7 @@
|
|||
})
|
||||
|
||||
const getSettingDefinition = key => {
|
||||
return definition?.settings?.find(setting => {
|
||||
return setting.key === key
|
||||
})
|
||||
return settings.find(setting => setting.key === key)
|
||||
}
|
||||
|
||||
const getComponentForSetting = key => {
|
||||
|
@ -175,7 +170,10 @@
|
|||
bind:value={condition.action}
|
||||
/>
|
||||
{#if condition.action === "update"}
|
||||
<Select options={settings} bind:value={condition.setting} />
|
||||
<Select
|
||||
options={settingOptions}
|
||||
bind:value={condition.setting}
|
||||
/>
|
||||
<div>TO</div>
|
||||
{#if getSettingDefinition(condition.setting)}
|
||||
<PropertyControl
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<script>
|
||||
import { Input } from "@budibase/bbui"
|
||||
import { isJSBinding } from "@budibase/string-templates"
|
||||
|
||||
export let value
|
||||
|
||||
$: isJS = isJSBinding(value)
|
||||
</script>
|
||||
|
||||
<Input
|
||||
{...$$props}
|
||||
value={isJS ? "(JavaScript function)" : value}
|
||||
readonly={isJS}
|
||||
on:change
|
||||
/>
|
|
@ -1,11 +1,9 @@
|
|||
<script>
|
||||
import { Button, Icon, Drawer, Label } from "@budibase/bbui"
|
||||
import { Label } from "@budibase/bbui"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/dataBinding"
|
||||
import BindingPanel from "components/common/bindings/BindingPanel.svelte"
|
||||
import { capitalise } from "helpers"
|
||||
|
||||
export let label = ""
|
||||
export let bindable = true
|
||||
|
@ -20,10 +18,6 @@
|
|||
export let componentBindings = []
|
||||
export let nested = false
|
||||
|
||||
let bindingDrawer
|
||||
let anchor
|
||||
let valid
|
||||
|
||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||
$: safeValue = getSafeValue(value, props.defaultValue, allBindings)
|
||||
$: tempValue = safeValue
|
||||
|
@ -33,12 +27,7 @@
|
|||
if (!nested) {
|
||||
return bindings
|
||||
}
|
||||
return [...(bindings || []), ...(componentBindings || [])]
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
handleChange(tempValue)
|
||||
bindingDrawer.hide()
|
||||
return [...(componentBindings || []), ...(bindings || [])]
|
||||
}
|
||||
|
||||
// Handle a value change of any type
|
||||
|
@ -74,7 +63,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="property-control" bind:this={anchor} data-cy={`setting-${key}`}>
|
||||
<div class="property-control" data-cy={`setting-${key}`}>
|
||||
{#if type !== "boolean" && label}
|
||||
<div class="label">
|
||||
<Label>{label}</Label>
|
||||
|
@ -94,31 +83,6 @@
|
|||
{type}
|
||||
{...props}
|
||||
/>
|
||||
{#if bindable && !key.startsWith("_") && type === "text"}
|
||||
<div
|
||||
class="icon"
|
||||
data-cy={`${key}-binding-button`}
|
||||
on:click={bindingDrawer.show}
|
||||
>
|
||||
<Icon size="S" name="FlashOn" />
|
||||
</div>
|
||||
<Drawer bind:this={bindingDrawer} title={capitalise(key)}>
|
||||
<svelte:fragment slot="description">
|
||||
Add the objects on the left to enrich your text.
|
||||
</svelte:fragment>
|
||||
<Button cta slot="buttons" disabled={!valid} on:click={handleClose}>
|
||||
Save
|
||||
</Button>
|
||||
<BindingPanel
|
||||
slot="body"
|
||||
bind:valid
|
||||
value={safeValue}
|
||||
on:change={e => (tempValue = e.detail)}
|
||||
bindableProperties={allBindings}
|
||||
allowJS
|
||||
/>
|
||||
</Drawer>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -130,40 +94,10 @@
|
|||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-bottom: var(--spectrum-global-dimension-size-65);
|
||||
}
|
||||
|
||||
.control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
border-left: 1px solid var(--spectrum-alias-border-color);
|
||||
border-top-right-radius: var(--spectrum-alias-border-radius-regular);
|
||||
border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
|
||||
width: 31px;
|
||||
color: var(--spectrum-alias-text-color);
|
||||
background-color: var(--spectrum-global-color-gray-75);
|
||||
transition: background-color
|
||||
var(--spectrum-global-animation-duration-100, 130ms),
|
||||
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
||||
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
||||
}
|
||||
.icon:hover {
|
||||
cursor: pointer;
|
||||
color: var(--spectrum-alias-text-color-hover);
|
||||
background-color: var(--spectrum-global-color-gray-50);
|
||||
border-color: var(--spectrum-alias-border-color-hover);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,4 +10,10 @@
|
|||
.filter(x => x != null)
|
||||
</script>
|
||||
|
||||
<DrawerBindableCombobox {value} {bindings} on:change options={urlOptions} />
|
||||
<DrawerBindableCombobox
|
||||
{value}
|
||||
{bindings}
|
||||
on:change
|
||||
options={urlOptions}
|
||||
appendBindingsAsOptions={false}
|
||||
/>
|
||||
|
|
|
@ -15,10 +15,10 @@ import URLSelect from "./URLSelect.svelte"
|
|||
import OptionsEditor from "./OptionsEditor/OptionsEditor.svelte"
|
||||
import FormFieldSelect from "./FormFieldSelect.svelte"
|
||||
import ValidationEditor from "./ValidationEditor/ValidationEditor.svelte"
|
||||
import Input from "./Input.svelte"
|
||||
import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCombobox.svelte"
|
||||
|
||||
const componentMap = {
|
||||
text: Input,
|
||||
text: DrawerBindableCombobox,
|
||||
select: Select,
|
||||
dataSource: DataSourceSelect,
|
||||
dataProvider: DataProviderSelect,
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
<DetailSummary name="Screen" collapsible={false}>
|
||||
{#each screenSettings as def (`${componentInstance._id}-${def.key}`)}
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)}
|
||||
<div style="grid-column: {prop.column || 'auto'}">
|
||||
<PropertyControl
|
||||
bindable={false}
|
||||
label={`${prop.label}${hasPropChanged(style, prop) ? " *" : ""}`}
|
||||
control={prop.control}
|
||||
key={prop.key}
|
||||
|
|
|
@ -9,7 +9,6 @@ export const margin = {
|
|||
label: "Top",
|
||||
key: "margin-top",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -30,7 +29,6 @@ export const margin = {
|
|||
label: "Right",
|
||||
key: "margin-right",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -51,7 +49,6 @@ export const margin = {
|
|||
label: "Bottom",
|
||||
key: "margin-bottom",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -72,7 +69,6 @@ export const margin = {
|
|||
label: "Left",
|
||||
key: "margin-left",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -100,7 +96,6 @@ export const padding = {
|
|||
label: "Top",
|
||||
key: "padding-top",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -121,7 +116,6 @@ export const padding = {
|
|||
label: "Right",
|
||||
key: "padding-right",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -142,7 +136,6 @@ export const padding = {
|
|||
label: "Bottom",
|
||||
key: "padding-bottom",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
@ -163,7 +156,6 @@ export const padding = {
|
|||
label: "Left",
|
||||
key: "padding-left",
|
||||
control: Select,
|
||||
bindable: false,
|
||||
placeholder: "None",
|
||||
options: [
|
||||
{ label: "4px", value: "4px" },
|
||||
|
|
|
@ -157,6 +157,11 @@
|
|||
}
|
||||
return title
|
||||
}
|
||||
|
||||
async function onCancel() {
|
||||
template = null
|
||||
await auth.setInitInfo({})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showTemplateSelection}
|
||||
|
@ -186,7 +191,7 @@
|
|||
title={getModalTitle()}
|
||||
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||
onConfirm={createNewApp}
|
||||
onCancel={inline ? () => (template = null) : null}
|
||||
onCancel={inline ? onCancel : null}
|
||||
cancelText={inline ? "Back" : undefined}
|
||||
showCloseIcon={!inline}
|
||||
disabled={!valid}
|
||||
|
|
|
@ -37,33 +37,33 @@
|
|||
<p class="detail">{template?.category?.toUpperCase()}</p>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: rgb(50, 50, 50); color: white;`}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Heading size="XS">Start from scratch</Heading>
|
||||
<p class="detail">BLANK</p>
|
||||
</div>
|
||||
<div
|
||||
class="template import"
|
||||
on:click={() => onSelect(null, { useImport: true })}
|
||||
>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: rgb(50, 50, 50); color: white;`}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Heading size="XS">Import an app</Heading>
|
||||
<p class="detail">BLANK</p>
|
||||
</div>
|
||||
</div>
|
||||
{:catch err}
|
||||
<h1 style="color:red">{err}</h1>
|
||||
{/await}
|
||||
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: rgb(50, 50, 50); color: white;`}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Heading size="XS">Start from scratch</Heading>
|
||||
<p class="detail">BLANK</p>
|
||||
</div>
|
||||
<div
|
||||
class="template import"
|
||||
on:click={() => onSelect(null, { useImport: true })}
|
||||
>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: rgb(50, 50, 50); color: white;`}
|
||||
>
|
||||
<Icon name="Add" />
|
||||
</div>
|
||||
<Heading size="XS">Import an app</Heading>
|
||||
<p class="detail">BLANK</p>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -138,3 +138,19 @@ export const RelationshipTypes = {
|
|||
ONE_TO_MANY: "one-to-many",
|
||||
MANY_TO_ONE: "many-to-one",
|
||||
}
|
||||
|
||||
export const ALLOWABLE_STRING_OPTIONS = [FIELDS.STRING, FIELDS.OPTIONS]
|
||||
|
||||
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
||||
opt => opt.type
|
||||
)
|
||||
|
||||
export const ALLOWABLE_NUMBER_OPTIONS = [FIELDS.NUMBER, FIELDS.BOOLEAN]
|
||||
|
||||
export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
|
||||
opt => opt.type
|
||||
)
|
||||
|
||||
export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat(
|
||||
ALLOWABLE_STRING_TYPES
|
||||
)
|
||||
|
|
|
@ -28,9 +28,13 @@
|
|||
}
|
||||
|
||||
if (user && user.tenantId) {
|
||||
// no tenant in the url - send to account portal to fix this
|
||||
if (!urlTenantId) {
|
||||
window.location.href = $admin.accountPortalUrl
|
||||
// redirect to correct tenantId subdomain
|
||||
if (!window.location.host.includes("localhost")) {
|
||||
let redirectUrl = window.location.href
|
||||
redirectUrl = redirectUrl.replace("://", `://${user.tenantId}.`)
|
||||
window.location.href = redirectUrl
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
ActionButton,
|
||||
ActionGroup,
|
||||
ButtonGroup,
|
||||
Input,
|
||||
Select,
|
||||
Modal,
|
||||
Page,
|
||||
|
@ -36,6 +37,7 @@
|
|||
let loaded = false
|
||||
let searchTerm = ""
|
||||
let cloud = $admin.cloud
|
||||
let appName = ""
|
||||
|
||||
$: enrichedApps = enrichApps($apps, $auth.user, sortBy)
|
||||
$: filteredApps = enrichedApps.filter(app =>
|
||||
|
@ -296,8 +298,12 @@
|
|||
title="Confirm deletion"
|
||||
okText="Delete app"
|
||||
onOk={confirmDeleteApp}
|
||||
disabled={appName !== selectedApp?.name}
|
||||
>
|
||||
Are you sure you want to delete the app <b>{selectedApp?.name}</b>?
|
||||
|
||||
<p>Please enter the app name below to confirm.</p>
|
||||
<Input bind:value={appName} data-cy="delete-app-confirmation" />
|
||||
</ConfirmDialog>
|
||||
<ConfirmDialog
|
||||
bind:this={unpublishModal}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { writable, get } from "svelte/store"
|
|||
import { views, queries, datasources } from "./"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import api from "builderStore/api"
|
||||
import { SWITCHABLE_TYPES } from "../../constants/backend"
|
||||
|
||||
export function createTablesStore() {
|
||||
const store = writable({})
|
||||
|
@ -47,7 +48,11 @@ export function createTablesStore() {
|
|||
const field = updatedTable.schema[key]
|
||||
const oldField = oldTable?.schema[key]
|
||||
// if the type has changed then revert back to the old field
|
||||
if (oldField != null && oldField?.type !== field.type) {
|
||||
if (
|
||||
oldField != null &&
|
||||
oldField?.type !== field.type &&
|
||||
SWITCHABLE_TYPES.indexOf(oldField?.type) === -1
|
||||
) {
|
||||
updatedTable.schema[key] = oldField
|
||||
}
|
||||
// field has been renamed
|
||||
|
|
|
@ -57,11 +57,11 @@ export function createAuthStore() {
|
|||
analytics.showChat({
|
||||
email: user.email,
|
||||
created_at: (user.createdAt || Date.now()) / 1000,
|
||||
name: user.name,
|
||||
name: user.account?.name,
|
||||
user_id: user._id,
|
||||
tenant: user.tenantId,
|
||||
"Company size": user.size,
|
||||
"Job role": user.profession,
|
||||
"Company size": user.account?.size,
|
||||
"Job role": user.account?.profession,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -80,16 +80,30 @@ export function createAuthStore() {
|
|||
}
|
||||
}
|
||||
|
||||
async function setInitInfo(info) {
|
||||
await api.post(`/api/global/auth/init`, info)
|
||||
auth.update(store => {
|
||||
store.initInfo = info
|
||||
return store
|
||||
})
|
||||
return info
|
||||
}
|
||||
|
||||
async function getInitInfo() {
|
||||
const response = await api.get(`/api/global/auth/init`)
|
||||
const json = response.json()
|
||||
auth.update(store => {
|
||||
store.initInfo = json
|
||||
return store
|
||||
})
|
||||
return json
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
setOrganisation: setOrganisation,
|
||||
getInitInfo: async () => {
|
||||
const response = await api.get(`/api/global/auth/init`)
|
||||
return await response.json()
|
||||
},
|
||||
setInitInfo: async info => {
|
||||
await api.post(`/api/global/auth/init`, info)
|
||||
},
|
||||
setOrganisation,
|
||||
getInitInfo,
|
||||
setInitInfo,
|
||||
checkQueryString: async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
if (urlParams.has("tenantId")) {
|
||||
|
@ -129,6 +143,7 @@ export function createAuthStore() {
|
|||
throw "Unable to create logout"
|
||||
}
|
||||
await response.json()
|
||||
await setInitInfo({})
|
||||
setUser(null)
|
||||
},
|
||||
updateSelf: async fields => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"deviceAwareness": true,
|
||||
"state": true,
|
||||
"customThemes": true,
|
||||
"devicePreview": true
|
||||
"devicePreview": true,
|
||||
"messagePassing": true
|
||||
},
|
||||
"layout": {
|
||||
"name": "Layout",
|
||||
|
@ -2595,6 +2596,11 @@
|
|||
"key": "linkURL",
|
||||
"label": "Link URL"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "linkPeek",
|
||||
"label": "Open link in modal"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "horizontal",
|
||||
|
@ -2617,9 +2623,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"tablewithsearch": {
|
||||
"tableblock": {
|
||||
"block": true,
|
||||
"name": "Table with search",
|
||||
"name": "Table block",
|
||||
"icon": "Table",
|
||||
"styles": ["size"],
|
||||
"info": "Only the first 3 search columns will be used.",
|
||||
|
@ -2730,18 +2736,23 @@
|
|||
{
|
||||
"type": "boolean",
|
||||
"key": "showTitleButton",
|
||||
"label": "Show button",
|
||||
"label": "Show link button",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Open link in modal",
|
||||
"key": "titleButtonPeek"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "titleButtonText",
|
||||
"label": "Button text"
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"label": "Button action",
|
||||
"key": "titleButtonOnClick"
|
||||
"type": "url",
|
||||
"label": "Button link",
|
||||
"key": "titleButtonURL"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2759,9 +2770,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"cardlistwithsearch": {
|
||||
"cardsblock": {
|
||||
"block": true,
|
||||
"name": "Card list with search",
|
||||
"name": "Cards block",
|
||||
"icon": "Table",
|
||||
"styles": ["size"],
|
||||
"info": "Only the first 3 search columns will be used.",
|
||||
|
@ -2838,7 +2849,22 @@
|
|||
"key": "cardImageURL",
|
||||
"label": "Image URL",
|
||||
"nested": true
|
||||
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "linkCardTitle",
|
||||
"label": "Link card title"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "cardPeek",
|
||||
"label": "Open link in modal"
|
||||
},
|
||||
{
|
||||
"type": "url",
|
||||
"label": "Link screen",
|
||||
"key": "cardURL",
|
||||
"nested": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
|
@ -2855,7 +2881,6 @@
|
|||
"key": "cardButtonText",
|
||||
"label": "Button text",
|
||||
"nested": true
|
||||
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
|
@ -2872,7 +2897,12 @@
|
|||
{
|
||||
"type": "boolean",
|
||||
"key": "showTitleButton",
|
||||
"label": "Show button"
|
||||
"label": "Show link button"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Open link in modal",
|
||||
"key": "titleButtonPeek"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -2880,9 +2910,21 @@
|
|||
"label": "Button text"
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"label": "Button action",
|
||||
"key": "titleButtonOnClick"
|
||||
"type": "url",
|
||||
"label": "Button link",
|
||||
"key": "titleButtonURL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Advanced",
|
||||
"settings": [
|
||||
{
|
||||
"type": "field",
|
||||
"label": "ID column for linking (appended to URL)",
|
||||
"key": "linkColumn",
|
||||
"placeholder": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"license": "MPL-2.0",
|
||||
"module": "dist/budibase-client.js",
|
||||
"main": "dist/budibase-client.js",
|
||||
|
@ -19,9 +19,9 @@
|
|||
"dev:builder": "rollup -cw"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "^0.9.180-alpha.1",
|
||||
"@budibase/bbui": "^0.9.184",
|
||||
"@budibase/standard-components": "^0.9.139",
|
||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.184",
|
||||
"regexparam": "^1.3.0",
|
||||
"shortid": "^2.2.15",
|
||||
"svelte-spa-router": "^3.0.5"
|
||||
|
|
|
@ -108,6 +108,8 @@ export const deleteRows = async ({ tableId, rows }) => {
|
|||
/**
|
||||
* Enriches rows which contain certain field types so that they can
|
||||
* be properly displayed.
|
||||
* The ability to create these bindings has been removed, but they will still
|
||||
* exist in client apps to support backwards compatibility.
|
||||
*/
|
||||
export const enrichRows = async (rows, tableId) => {
|
||||
if (!Array.isArray(rows)) {
|
||||
|
|
|
@ -8,15 +8,22 @@
|
|||
export let description
|
||||
export let imageURL
|
||||
export let linkURL
|
||||
export let linkPeek
|
||||
export let horizontal
|
||||
export let showButton
|
||||
export let buttonText
|
||||
export let buttonOnClick
|
||||
|
||||
const { styleable, linkable } = getContext("sdk")
|
||||
const { styleable, routeStore } = getContext("sdk")
|
||||
const component = getContext("component")
|
||||
|
||||
$: external = linkURL && !linkURL.startsWith("/")
|
||||
const handleLink = e => {
|
||||
if (!linkURL) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
routeStore.actions.navigate(linkURL, linkPeek)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -37,16 +44,10 @@
|
|||
<div class="spectrum-Card-header">
|
||||
<div
|
||||
class="spectrum-Card-title spectrum-Heading spectrum-Heading--sizeXS"
|
||||
on:click={handleLink}
|
||||
class:link={linkURL}
|
||||
>
|
||||
{#if linkURL}
|
||||
{#if external}
|
||||
<a href={linkURL}>{title || "Card Title"}</a>
|
||||
{:else}
|
||||
<a use:linkable href={linkURL}>{title || "Card Title"}</a>
|
||||
{/if}
|
||||
{:else}
|
||||
{title || "Card Title"}
|
||||
{/if}
|
||||
{title || "Card Title"}
|
||||
</div>
|
||||
</div>
|
||||
{#if subtitle}
|
||||
|
@ -88,11 +89,12 @@
|
|||
.spectrum-Card-container {
|
||||
padding: var(--spectrum-global-dimension-size-50) 0;
|
||||
}
|
||||
.spectrum-Card-title :global(a) {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
.spectrum-Card-title.link {
|
||||
transition: color 130ms ease-in-out;
|
||||
}
|
||||
.spectrum-Card-title.link:hover {
|
||||
cursor: pointer;
|
||||
color: var(--spectrum-link-primary-m-text-color-hover);
|
||||
}
|
||||
.spectrum-Card-subtitle {
|
||||
text-overflow: ellipsis;
|
||||
|
@ -103,14 +105,6 @@
|
|||
word-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
a {
|
||||
transition: color 130ms ease-in-out;
|
||||
color: var(--spectrum-alias-text-color);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--spectrum-link-primary-m-text-color-hover);
|
||||
}
|
||||
|
||||
.horizontal .spectrum-Card-coverPhoto {
|
||||
flex: 0 0 160px;
|
||||
height: auto;
|
||||
|
|
|
@ -14,15 +14,20 @@
|
|||
export let limit
|
||||
export let showTitleButton
|
||||
export let titleButtonText
|
||||
export let titleButtonOnClick
|
||||
export let titleButtonURL
|
||||
export let titleButtonPeek
|
||||
export let cardTitle
|
||||
export let cardSubtitle
|
||||
export let cardDescription
|
||||
export let cardImageURL
|
||||
export let linkCardTitle
|
||||
export let cardURL
|
||||
export let cardPeek
|
||||
export let cardHorizontal
|
||||
export let showCardButton
|
||||
export let cardButtonText
|
||||
export let cardButtonOnClick
|
||||
export let linkColumn
|
||||
|
||||
const { API, styleable } = getContext("sdk")
|
||||
const context = getContext("context")
|
||||
|
@ -37,11 +42,27 @@
|
|||
|
||||
let formId
|
||||
let dataProviderId
|
||||
let repeaterId
|
||||
let schema
|
||||
|
||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||
$: cardWidth = cardHorizontal ? 420 : 300
|
||||
$: fullCardURL = buildFullCardUrl(
|
||||
linkCardTitle,
|
||||
cardURL,
|
||||
repeaterId,
|
||||
linkColumn
|
||||
)
|
||||
$: titleButtonAction = [
|
||||
{
|
||||
"##eventHandlerType": "Navigate To",
|
||||
parameters: {
|
||||
peek: titleButtonPeek,
|
||||
url: titleButtonURL,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// Enrich the default filter with the specified search fields
|
||||
const enrichFilter = (filter, columns, formId) => {
|
||||
|
@ -49,7 +70,7 @@
|
|||
columns?.forEach(column => {
|
||||
enrichedFilter.push({
|
||||
field: column.name,
|
||||
operator: "equal",
|
||||
operator: column.type === "string" ? "string" : "equal",
|
||||
type: "string",
|
||||
valueType: "Binding",
|
||||
value: `{{ [${formId}].[${column.name}] }}`,
|
||||
|
@ -68,12 +89,23 @@
|
|||
enrichedColumns.push({
|
||||
name: column,
|
||||
componentType,
|
||||
type: schemaType,
|
||||
})
|
||||
}
|
||||
})
|
||||
return enrichedColumns.slice(0, 3)
|
||||
}
|
||||
|
||||
// Builds a full details page URL for the card title
|
||||
const buildFullCardUrl = (link, url, repeaterId, linkColumn) => {
|
||||
if (!link || !url || !repeaterId) {
|
||||
return null
|
||||
}
|
||||
const col = linkColumn || "_id"
|
||||
const split = url.split("/:")
|
||||
return `${split[0]}/{{ [${repeaterId}].[${col}] }}`
|
||||
}
|
||||
|
||||
// Load the datasource schema on mount so we can determine column types
|
||||
onMount(async () => {
|
||||
if (dataSource) {
|
||||
|
@ -113,7 +145,7 @@
|
|||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonOnClick,
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
|
@ -136,6 +168,7 @@
|
|||
>
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
bind:id={repeaterId}
|
||||
context="repeater"
|
||||
props={{
|
||||
dataProvider: `{{ literal [${dataProviderId}] }}`,
|
||||
|
@ -161,6 +194,8 @@
|
|||
showButton: showCardButton,
|
||||
buttonText: cardButtonText,
|
||||
buttonOnClick: cardButtonOnClick,
|
||||
linkURL: fullCardURL,
|
||||
linkPeek: cardPeek,
|
||||
}}
|
||||
styles={{
|
||||
width: "auto",
|
|
@ -22,7 +22,8 @@
|
|||
export let linkPeek
|
||||
export let showTitleButton
|
||||
export let titleButtonText
|
||||
export let titleButtonOnClick
|
||||
export let titleButtonURL
|
||||
export let titleButtonPeek
|
||||
|
||||
const { API, styleable } = getContext("sdk")
|
||||
const context = getContext("context")
|
||||
|
@ -41,6 +42,15 @@
|
|||
|
||||
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
|
||||
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId)
|
||||
$: titleButtonAction = [
|
||||
{
|
||||
"##eventHandlerType": "Navigate To",
|
||||
parameters: {
|
||||
peek: titleButtonPeek,
|
||||
url: titleButtonURL,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// Enrich the default filter with the specified search fields
|
||||
const enrichFilter = (filter, columns, formId) => {
|
||||
|
@ -48,7 +58,7 @@
|
|||
columns?.forEach(column => {
|
||||
enrichedFilter.push({
|
||||
field: column.name,
|
||||
operator: "equal",
|
||||
operator: column.type === "string" ? "string" : "equal",
|
||||
type: "string",
|
||||
valueType: "Binding",
|
||||
value: `{{ [${formId}].[${column.name}] }}`,
|
||||
|
@ -67,6 +77,7 @@
|
|||
enrichedColumns.push({
|
||||
name: column,
|
||||
componentType,
|
||||
type: schemaType,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -112,7 +123,7 @@
|
|||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonOnClick,
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
|
@ -1,2 +1,2 @@
|
|||
export { default as tablewithsearch } from "./TableWithSearch.svelte"
|
||||
export { default as cardlistwithsearch } from "./CardListWithSearch.svelte"
|
||||
export { default as tableblock } from "./TableBlock.svelte"
|
||||
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||
|
|
|
@ -4,6 +4,9 @@ import { builderStore } from "stores"
|
|||
|
||||
export const linkable = (node, href) => {
|
||||
if (get(builderStore).inBuilder) {
|
||||
node.onclick = e => {
|
||||
e.preventDefault()
|
||||
}
|
||||
return
|
||||
}
|
||||
link(node, href)
|
||||
|
|
|
@ -21,15 +21,18 @@ module PgMock {
|
|||
function Pool() {
|
||||
}
|
||||
|
||||
const on = jest.fn()
|
||||
Pool.prototype.query = query
|
||||
Pool.prototype.connect = jest.fn(() => {
|
||||
// @ts-ignore
|
||||
return new Client()
|
||||
})
|
||||
Pool.prototype.on = on
|
||||
|
||||
pg.Client = Client
|
||||
pg.Pool = Pool
|
||||
pg.queryMock = query
|
||||
pg.on = on
|
||||
|
||||
module.exports = pg
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -68,9 +68,9 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.180-alpha.1",
|
||||
"@budibase/client": "^0.9.180-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
||||
"@budibase/auth": "^0.9.184",
|
||||
"@budibase/client": "^0.9.184",
|
||||
"@budibase/string-templates": "^0.9.184",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
|
|
|
@ -198,7 +198,7 @@ exports.fetchAppPackage = async ctx => {
|
|||
application,
|
||||
screens,
|
||||
layouts,
|
||||
clientLibPath: clientLibraryPath(ctx.params.appId),
|
||||
clientLibPath: clientLibraryPath(ctx.params.appId, application.version),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,7 +324,7 @@ exports.delete = async ctx => {
|
|||
ctx.body = result
|
||||
}
|
||||
|
||||
exports.sync = async ctx => {
|
||||
exports.sync = async (ctx, next) => {
|
||||
const appId = ctx.params.appId
|
||||
if (!isDevAppID(appId)) {
|
||||
ctx.throw(400, "This action cannot be performed for production apps")
|
||||
|
@ -332,6 +332,20 @@ exports.sync = async ctx => {
|
|||
|
||||
// replicate prod to dev
|
||||
const prodAppId = getDeployedAppID(appId)
|
||||
|
||||
try {
|
||||
const prodDb = new CouchDB(prodAppId, { skip_setup: true })
|
||||
const info = await prodDb.info()
|
||||
if (info.error) throw info.error
|
||||
} catch (err) {
|
||||
// the database doesn't exist. Don't replicate
|
||||
ctx.status = 200
|
||||
ctx.body = {
|
||||
message: "App sync not required, app not deployed.",
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
const replication = new Replication({
|
||||
source: prodAppId,
|
||||
target: appId,
|
||||
|
|
|
@ -82,6 +82,13 @@ exports.revert = async ctx => {
|
|||
const db = new CouchDB(productionAppId, { skip_setup: true })
|
||||
const info = await db.info()
|
||||
if (info.error) throw info.error
|
||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||
if (
|
||||
!deploymentDoc.history ||
|
||||
Object.keys(deploymentDoc.history).length === 0
|
||||
) {
|
||||
throw new Error("No deployments for app")
|
||||
}
|
||||
} catch (err) {
|
||||
return ctx.throw(400, "App has not yet been deployed")
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ exports.serveApp = async function (ctx) {
|
|||
title: appInfo.name,
|
||||
production: env.isProd(),
|
||||
appId,
|
||||
clientLibPath: clientLibraryPath(appId),
|
||||
clientLibPath: clientLibraryPath(appId, appInfo.version),
|
||||
})
|
||||
|
||||
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
|
||||
|
|
|
@ -8,6 +8,7 @@ const {
|
|||
generateForeignKey,
|
||||
generateJunctionTableName,
|
||||
foreignKeyStructure,
|
||||
hasTypeChanged,
|
||||
} = require("./utils")
|
||||
const {
|
||||
DataSourceOperation,
|
||||
|
@ -172,6 +173,10 @@ exports.save = async function (ctx) {
|
|||
oldTable = await getTable(appId, ctx.request.body._id)
|
||||
}
|
||||
|
||||
if (hasTypeChanged(tableToSave, oldTable)) {
|
||||
ctx.throw(400, "A column type has changed.")
|
||||
}
|
||||
|
||||
const db = new CouchDB(appId)
|
||||
const datasource = await db.get(datasourceId)
|
||||
const oldTables = cloneDeep(datasource.entities)
|
||||
|
|
|
@ -2,7 +2,7 @@ const CouchDB = require("../../../db")
|
|||
const linkRows = require("../../../db/linkedRows")
|
||||
const { getRowParams, generateTableID } = require("../../../db/utils")
|
||||
const { FieldTypes } = require("../../../constants")
|
||||
const { TableSaveFunctions } = require("./utils")
|
||||
const { TableSaveFunctions, hasTypeChanged } = require("./utils")
|
||||
|
||||
exports.save = async function (ctx) {
|
||||
const appId = ctx.appId
|
||||
|
@ -21,6 +21,10 @@ exports.save = async function (ctx) {
|
|||
oldTable = await db.get(ctx.request.body._id)
|
||||
}
|
||||
|
||||
if (hasTypeChanged(tableToSave, oldTable)) {
|
||||
ctx.throw(400, "A column type has changed.")
|
||||
}
|
||||
|
||||
// saving a table is a complex operation, involving many different steps, this
|
||||
// has been broken out into a utility to make it more obvious/easier to manipulate
|
||||
const tableSaveFunctions = new TableSaveFunctions({
|
||||
|
|
|
@ -8,7 +8,7 @@ const {
|
|||
const { isEqual } = require("lodash/fp")
|
||||
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
||||
const { inputProcessing } = require("../../../utilities/rowProcessor")
|
||||
const { USERS_TABLE_SCHEMA } = require("../../../constants")
|
||||
const { USERS_TABLE_SCHEMA, SwitchableTypes } = require("../../../constants")
|
||||
const {
|
||||
isExternalTable,
|
||||
breakExternalTableId,
|
||||
|
@ -335,4 +335,21 @@ exports.foreignKeyStructure = (keyName, meta = null) => {
|
|||
return structure
|
||||
}
|
||||
|
||||
exports.hasTypeChanged = (table, oldTable) => {
|
||||
if (!oldTable) {
|
||||
return false
|
||||
}
|
||||
for (let [key, field] of Object.entries(oldTable.schema)) {
|
||||
const oldType = field.type
|
||||
if (!table.schema[key]) {
|
||||
continue
|
||||
}
|
||||
const newType = table.schema[key].type
|
||||
if (oldType !== newType && SwitchableTypes.indexOf(oldType) === -1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
exports.TableSaveFunctions = TableSaveFunctions
|
||||
|
|
|
@ -45,6 +45,13 @@ exports.FieldTypes = {
|
|||
INTERNAL: "internal",
|
||||
}
|
||||
|
||||
exports.SwitchableTypes = [
|
||||
exports.FieldTypes.STRING,
|
||||
exports.FieldTypes.OPTIONS,
|
||||
exports.FieldTypes.NUMBER,
|
||||
exports.FieldTypes.BOOLEAN,
|
||||
]
|
||||
|
||||
exports.RelationshipTypes = {
|
||||
ONE_TO_MANY: "one-to-many",
|
||||
MANY_TO_ONE: "many-to-one",
|
||||
|
|
|
@ -30,7 +30,7 @@ function generateSchema(
|
|||
// skip things that are already correct
|
||||
const oldColumn = oldTable ? oldTable.schema[key] : null
|
||||
if (
|
||||
(oldColumn && oldColumn.type === column.type) ||
|
||||
(oldColumn && oldColumn.type) ||
|
||||
(primaryKey === key && !isJunction)
|
||||
) {
|
||||
continue
|
||||
|
|
|
@ -28,6 +28,7 @@ module PostgresModule {
|
|||
database: string
|
||||
user: string
|
||||
password: string
|
||||
schema: string
|
||||
ssl?: boolean
|
||||
ca?: string
|
||||
rejectUnauthorized?: boolean
|
||||
|
@ -65,6 +66,11 @@ module PostgresModule {
|
|||
default: "root",
|
||||
required: true,
|
||||
},
|
||||
schema: {
|
||||
type: DatasourceFieldTypes.STRING,
|
||||
default: "public",
|
||||
required: true,
|
||||
},
|
||||
ssl: {
|
||||
type: DatasourceFieldTypes.BOOLEAN,
|
||||
default: false,
|
||||
|
@ -124,8 +130,7 @@ module PostgresModule {
|
|||
public tables: Record<string, Table> = {}
|
||||
public schemaErrors: Record<string, string> = {}
|
||||
|
||||
COLUMNS_SQL =
|
||||
"select * from information_schema.columns where not table_schema = 'information_schema' and not table_schema = 'pg_catalog'"
|
||||
COLUMNS_SQL!: string
|
||||
|
||||
PRIMARY_KEYS_SQL = `
|
||||
select tc.table_schema, tc.table_name, kc.column_name as primary_key
|
||||
|
@ -155,6 +160,17 @@ module PostgresModule {
|
|||
}
|
||||
|
||||
this.client = this.pool
|
||||
this.setSchema()
|
||||
}
|
||||
|
||||
setSchema() {
|
||||
if (!this.config.schema) {
|
||||
this.config.schema = 'public'
|
||||
}
|
||||
this.client.on('connect', (client: any) => {
|
||||
client.query(`SET search_path TO ${this.config.schema}`);
|
||||
});
|
||||
this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,6 +15,10 @@ describe("Postgres Integration", () => {
|
|||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
it("calls the connection callback", async () => {
|
||||
expect(pg.on).toHaveBeenCalledWith('connect', expect.anything())
|
||||
})
|
||||
|
||||
it("calls the create method with the correct params", async () => {
|
||||
const sql = "insert into users (name, age) values ('Joe', 123);"
|
||||
await config.integration.create({
|
||||
|
|
|
@ -51,11 +51,16 @@ exports.objectStoreUrl = () => {
|
|||
* @return {string} The URL to be inserted into appPackage response or server rendered
|
||||
* app index file.
|
||||
*/
|
||||
exports.clientLibraryPath = appId => {
|
||||
exports.clientLibraryPath = (appId, version) => {
|
||||
if (env.isProd()) {
|
||||
return `${exports.objectStoreUrl()}/${sanitizeKey(
|
||||
let url = `${exports.objectStoreUrl()}/${sanitizeKey(
|
||||
appId
|
||||
)}/budibase-client.js`
|
||||
// append app version to bust the cache
|
||||
if (version) {
|
||||
url += `?v=${version}`
|
||||
}
|
||||
return url
|
||||
} else {
|
||||
return `/api/assets/client`
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "0.9.180-alpha.1",
|
||||
"version": "0.9.184",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
|
@ -29,8 +29,8 @@
|
|||
"author": "Budibase",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@budibase/auth": "^0.9.180-alpha.1",
|
||||
"@budibase/string-templates": "^0.9.180-alpha.1",
|
||||
"@budibase/auth": "^0.9.184",
|
||||
"@budibase/string-templates": "^0.9.184",
|
||||
"@koa/router": "^8.0.0",
|
||||
"@sentry/node": "^6.0.0",
|
||||
"@techpass/passport-openidconnect": "^0.3.0",
|
||||
|
|
|
@ -27,7 +27,7 @@ exports.syncUserInApps = async userId => {
|
|||
"POST",
|
||||
{}
|
||||
)
|
||||
if (response.status !== 200) {
|
||||
if (response && response.status !== 200) {
|
||||
throw "Unable to sync user."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue