Merge remote-tracking branch 'origin/develop' into feature/qr-barcode-reader
This commit is contained in:
commit
91e63ca2c9
|
@ -1,12 +1,15 @@
|
|||
## Dev Environment on Debian 11
|
||||
|
||||
### Install Node
|
||||
### Install NVM & Node 14
|
||||
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
|
||||
|
||||
Budibase requires a recent version of node (14+):
|
||||
Install NVM
|
||||
```
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
|
||||
apt -y install nodejs
|
||||
node -v
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||
```
|
||||
Install Node 14
|
||||
```
|
||||
nvm install 14
|
||||
```
|
||||
|
||||
### Install npm requirements
|
||||
|
@ -31,7 +34,7 @@ This setup process was tested on Debian 11 (bullseye) with version numbers show
|
|||
|
||||
- Docker: 20.10.5
|
||||
- Docker-Compose: 1.29.2
|
||||
- Node: v16.15.1
|
||||
- Node: v14.20.1
|
||||
- Yarn: 1.22.19
|
||||
- Lerna: 5.1.4
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ through brew.
|
|||
|
||||
### Install Node
|
||||
|
||||
Budibase requires a recent version of node (14+):
|
||||
Budibase requires a recent version of node 14:
|
||||
```
|
||||
brew install node npm
|
||||
node -v
|
||||
|
@ -38,7 +38,7 @@ This setup process was tested on Mac OSX 12 (Monterey) with version numbers show
|
|||
|
||||
- Docker: 20.10.14
|
||||
- Docker-Compose: 2.6.0
|
||||
- Node: 18.3.0
|
||||
- Node: 14.20.1
|
||||
- Yarn: 1.22.19
|
||||
- Lerna: 5.1.4
|
||||
|
||||
|
@ -60,3 +60,6 @@ http://127.0.0.1:10000/builder/admin
|
|||
|
||||
| **NOTE**: If you are working on a M1 Apple Silicon, you will need to uncomment `# platform: linux/amd64` line in
|
||||
[hosting/docker-compose-dev.yaml](../hosting/docker-compose.dev.yaml)
|
||||
|
||||
### Troubleshooting
|
||||
If there are errors with the `yarn setup` command, you can try installing nvm and node 14. This is the same as the instructions for Debian 11.
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
## Dev Environment on Windows 10/11 (WSL2)
|
||||
|
||||
|
||||
### Install WSL with Ubuntu LTS
|
||||
|
||||
Enable WSL 2 on Windows 10/11 for docker support.
|
||||
```
|
||||
wsl --set-default-version 2
|
||||
```
|
||||
Install Ubuntu LTS.
|
||||
```
|
||||
wsl --install Ubuntu
|
||||
```
|
||||
|
||||
Or follow the instruction here:
|
||||
https://learn.microsoft.com/en-us/windows/wsl/install
|
||||
|
||||
### Install Docker in windows
|
||||
Download the installer from docker and install it.
|
||||
|
||||
Check this url for more detailed instructions:
|
||||
https://docs.docker.com/desktop/install/windows-install/
|
||||
|
||||
You should follow the next steps from within the Ubuntu terminal.
|
||||
|
||||
### Install NVM & Node 14
|
||||
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
|
||||
|
||||
Install NVM
|
||||
```
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||
```
|
||||
Install Node 14
|
||||
```
|
||||
nvm install 14
|
||||
```
|
||||
|
||||
|
||||
### Install npm requirements
|
||||
|
||||
```
|
||||
npm install -g yarn jest lerna
|
||||
```
|
||||
|
||||
### Clone the repo
|
||||
```
|
||||
git clone https://github.com/Budibase/budibase.git
|
||||
```
|
||||
|
||||
### Check Versions
|
||||
|
||||
This setup process was tested on Windows 11 with version numbers show below. Your mileage may vary using anything else.
|
||||
|
||||
- Docker: 20.10.7
|
||||
- Docker-Compose: 2.10.2
|
||||
- Node: v14.20.1
|
||||
- Yarn: 1.22.19
|
||||
- Lerna: 5.5.4
|
||||
|
||||
### Build
|
||||
|
||||
```
|
||||
cd budibase
|
||||
yarn setup
|
||||
```
|
||||
The yarn setup command runs several build steps i.e.
|
||||
```
|
||||
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
|
||||
```
|
||||
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
|
||||
|
||||
The dev version will be available on port 10000 i.e.
|
||||
|
||||
http://127.0.0.1:10000/builder/admin
|
||||
|
||||
### Working with the code
|
||||
Here are the instructions to work on the application from within Visual Studio Code (in Windows) through the WSL. All the commands and files are within the Ubuntu system and it should run as if you were working on a Linux machine.
|
||||
|
||||
https://code.visualstudio.com/docs/remote/wsl
|
||||
|
||||
Note you will be able to run the application from within the WSL terminal and you will be able to access the application from the a browser in Windows.
|
|
@ -19,8 +19,8 @@ ADD packages/worker .
|
|||
RUN node /pinVersions.js && yarn && yarn build && /cleanup.sh
|
||||
|
||||
FROM couchdb:3.2.1
|
||||
# TARGETARCH can be amd64 or arm e.g. docker build --build-arg TARGETARCH=amd64
|
||||
ARG TARGETARCH=amd64
|
||||
ARG TARGETARCH
|
||||
ENV TARGETARCH $TARGETARCH
|
||||
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
||||
# e.g. docker build --build-arg TARGETBUILD=aas ....
|
||||
ARG TARGETBUILD=single
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/backend-core",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase backend core libraries used in server and worker",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"test:watch": "jest --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/types": "2.0.14-alpha.4",
|
||||
"@budibase/types": "2.0.24-alpha.3",
|
||||
"@shopify/jest-koa-mocks": "5.0.1",
|
||||
"@techpass/passport-openidconnect": "0.3.2",
|
||||
"aws-sdk": "2.1030.0",
|
||||
|
|
|
@ -214,6 +214,31 @@ export = class RedisWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
async bulkGet(keys: string[]) {
|
||||
const db = this._db
|
||||
const prefixedKeys = keys.map(key => addDbPrefix(db, key))
|
||||
let response = await this.getClient().mget(prefixedKeys)
|
||||
if (Array.isArray(response)) {
|
||||
let final: any = {}
|
||||
let count = 0
|
||||
for (let result of response) {
|
||||
if (result) {
|
||||
let parsed
|
||||
try {
|
||||
parsed = JSON.parse(result)
|
||||
} catch (err) {
|
||||
parsed = result
|
||||
}
|
||||
final[keys[count]] = parsed
|
||||
}
|
||||
count++
|
||||
}
|
||||
return final
|
||||
} else {
|
||||
throw new Error(`Invalid response: ${response}`)
|
||||
}
|
||||
}
|
||||
|
||||
async store(key: string, value: any, expirySeconds: number | null = null) {
|
||||
const db = this._db
|
||||
if (typeof value === "object") {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/bbui",
|
||||
"description": "A UI solution used in the different Budibase projects.",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"module": "dist/bbui.es.js",
|
||||
|
@ -38,7 +38,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||
"@budibase/string-templates": "2.0.14-alpha.4",
|
||||
"@budibase/string-templates": "2.0.24-alpha.3",
|
||||
"@spectrum-css/actionbutton": "^1.0.1",
|
||||
"@spectrum-css/actiongroup": "^1.0.1",
|
||||
"@spectrum-css/avatar": "^3.0.2",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import filterTests from "../support/filterTests"
|
||||
const interact = require('../support/interact')
|
||||
const interact = require("../support/interact")
|
||||
|
||||
filterTests(["smoke", "all"], () => {
|
||||
context("Query Level Transformers", () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/builder",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -71,10 +71,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.0.14-alpha.4",
|
||||
"@budibase/client": "2.0.14-alpha.4",
|
||||
"@budibase/frontend-core": "2.0.14-alpha.4",
|
||||
"@budibase/string-templates": "2.0.14-alpha.4",
|
||||
"@budibase/bbui": "2.0.24-alpha.3",
|
||||
"@budibase/client": "2.0.24-alpha.3",
|
||||
"@budibase/frontend-core": "2.0.24-alpha.3",
|
||||
"@budibase/string-templates": "2.0.24-alpha.3",
|
||||
"@sentry/browser": "5.19.1",
|
||||
"@spectrum-css/page": "^3.0.1",
|
||||
"@spectrum-css/vars": "^3.0.1",
|
||||
|
|
|
@ -185,43 +185,42 @@ export const makeComponentUnique = component => {
|
|||
// Replace component ID
|
||||
const oldId = component._id
|
||||
const newId = Helpers.uuid()
|
||||
component._id = newId
|
||||
let definition = JSON.stringify(component)
|
||||
|
||||
if (component._children?.length) {
|
||||
let children = JSON.stringify(component._children)
|
||||
// Replace all instances of this ID in HBS bindings
|
||||
definition = definition.replace(new RegExp(oldId, "g"), newId)
|
||||
|
||||
// Replace all instances of this ID in child HBS bindings
|
||||
children = children.replace(new RegExp(oldId, "g"), newId)
|
||||
// Replace all instances of this ID in JS bindings
|
||||
const bindings = findHBSBlocks(definition)
|
||||
bindings.forEach(binding => {
|
||||
// JSON.stringify will have escaped double quotes, so we need
|
||||
// to account for that
|
||||
let sanitizedBinding = binding.replace(/\\"/g, '"')
|
||||
|
||||
// Replace all instances of this ID in child JS bindings
|
||||
const bindings = findHBSBlocks(children)
|
||||
bindings.forEach(binding => {
|
||||
// JSON.stringify will have escaped double quotes, so we need
|
||||
// to account for that
|
||||
let sanitizedBinding = binding.replace(/\\"/g, '"')
|
||||
// Check if this is a valid JS binding
|
||||
let js = decodeJSBinding(sanitizedBinding)
|
||||
if (js != null) {
|
||||
// Replace ID inside JS binding
|
||||
js = js.replace(new RegExp(oldId, "g"), newId)
|
||||
|
||||
// Check if this is a valid JS binding
|
||||
let js = decodeJSBinding(sanitizedBinding)
|
||||
if (js != null) {
|
||||
// Replace ID inside JS binding
|
||||
js = js.replace(new RegExp(oldId, "g"), newId)
|
||||
// Create new valid JS binding
|
||||
let newBinding = encodeJSBinding(js)
|
||||
|
||||
// Create new valid JS binding
|
||||
let newBinding = encodeJSBinding(js)
|
||||
// Replace escaped double quotes
|
||||
newBinding = newBinding.replace(/"/g, '\\"')
|
||||
|
||||
// Replace escaped double quotes
|
||||
newBinding = newBinding.replace(/"/g, '\\"')
|
||||
// Insert new JS back into binding.
|
||||
// A single string replace here is better than a regex as
|
||||
// the binding contains special characters, and we only need
|
||||
// to replace a single instance.
|
||||
definition = definition.replace(binding, newBinding)
|
||||
}
|
||||
})
|
||||
|
||||
// Insert new JS back into binding.
|
||||
// A single string replace here is better than a regex as
|
||||
// the binding contains special characters, and we only need
|
||||
// to replace a single instance.
|
||||
children = children.replace(binding, newBinding)
|
||||
}
|
||||
})
|
||||
|
||||
// Recurse on all children
|
||||
component._children = JSON.parse(children)
|
||||
component._children.forEach(makeComponentUnique)
|
||||
// Recurse on all children
|
||||
component = JSON.parse(definition)
|
||||
return {
|
||||
...component,
|
||||
_children: component._children?.map(makeComponentUnique),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,12 @@ export const getComponentBindableProperties = (asset, componentId) => {
|
|||
/**
|
||||
* Gets all data provider components above a component.
|
||||
*/
|
||||
export const getContextProviderComponents = (asset, componentId, type) => {
|
||||
export const getContextProviderComponents = (
|
||||
asset,
|
||||
componentId,
|
||||
type,
|
||||
options = { includeSelf: false }
|
||||
) => {
|
||||
if (!asset || !componentId) {
|
||||
return []
|
||||
}
|
||||
|
@ -177,7 +182,9 @@ export const getContextProviderComponents = (asset, componentId, type) => {
|
|||
// Get the component tree leading up to this component, ignoring the component
|
||||
// itself
|
||||
const path = findComponentPath(asset.props, componentId)
|
||||
path.pop()
|
||||
if (!options?.includeSelf) {
|
||||
path.pop()
|
||||
}
|
||||
|
||||
// Filter by only data provider components
|
||||
return path.filter(component => {
|
||||
|
@ -396,19 +403,17 @@ export const getUserBindings = () => {
|
|||
|
||||
bindings = keys.reduce((acc, key) => {
|
||||
const fieldSchema = schema[key]
|
||||
if (fieldSchema.type !== "link") {
|
||||
acc.push({
|
||||
type: "context",
|
||||
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
|
||||
readableBinding: `Current User.${key}`,
|
||||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
fieldSchema,
|
||||
providerId: "user",
|
||||
category: "Current User",
|
||||
icon: "User",
|
||||
})
|
||||
}
|
||||
acc.push({
|
||||
type: "context",
|
||||
runtimeBinding: `${safeUser}.${makePropSafe(key)}`,
|
||||
readableBinding: `Current User.${key}`,
|
||||
// Field schema and provider are required to construct relationship
|
||||
// datasource options, based on bindable properties
|
||||
fieldSchema,
|
||||
providerId: "user",
|
||||
category: "Current User",
|
||||
icon: "User",
|
||||
})
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
|
@ -800,6 +805,17 @@ export const buildFormSchema = component => {
|
|||
if (!component) {
|
||||
return schema
|
||||
}
|
||||
|
||||
// If this is a form block, simply use the fields setting
|
||||
if (component._component.endsWith("formblock")) {
|
||||
let schema = {}
|
||||
component.fields?.forEach(field => {
|
||||
schema[field] = { type: "string" }
|
||||
})
|
||||
return schema
|
||||
}
|
||||
|
||||
// Otherwise find all field component children
|
||||
const settings = getComponentSettings(component._component)
|
||||
const fieldSetting = settings.find(
|
||||
setting => setting.key === "field" && setting.type.startsWith("field/")
|
||||
|
|
|
@ -330,6 +330,16 @@ export const getFrontendStore = () => {
|
|||
return state
|
||||
})
|
||||
},
|
||||
sendEvent: (name, payload) => {
|
||||
const { previewEventHandler } = get(store)
|
||||
previewEventHandler?.(name, payload)
|
||||
},
|
||||
registerEventHandler: handler => {
|
||||
store.update(state => {
|
||||
state.previewEventHandler = handler
|
||||
return state
|
||||
})
|
||||
},
|
||||
},
|
||||
layouts: {
|
||||
select: layoutId => {
|
||||
|
@ -611,7 +621,7 @@ export const getFrontendStore = () => {
|
|||
|
||||
// Make new component unique if copying
|
||||
if (!cut) {
|
||||
makeComponentUnique(componentToPaste)
|
||||
componentToPaste = makeComponentUnique(componentToPaste)
|
||||
}
|
||||
newComponentId = componentToPaste._id
|
||||
|
||||
|
@ -891,6 +901,50 @@ export const getFrontendStore = () => {
|
|||
component[name] = value
|
||||
})
|
||||
},
|
||||
requestEjectBlock: componentId => {
|
||||
store.actions.preview.sendEvent("eject-block", componentId)
|
||||
},
|
||||
handleEjectBlock: async (componentId, ejectedDefinition) => {
|
||||
let nextSelectedComponentId
|
||||
|
||||
await store.actions.screens.patch(screen => {
|
||||
const block = findComponent(screen.props, componentId)
|
||||
const parent = findComponentParent(screen.props, componentId)
|
||||
|
||||
// Sanity check
|
||||
if (!block || !parent?._children?.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Attach block children back into ejected definition, using the
|
||||
// _containsSlot flag to know where to insert them
|
||||
const slotContainer = findAllMatchingComponents(
|
||||
ejectedDefinition,
|
||||
x => x._containsSlot
|
||||
)[0]
|
||||
if (slotContainer) {
|
||||
delete slotContainer._containsSlot
|
||||
slotContainer._children = [
|
||||
...(slotContainer._children || []),
|
||||
...(block._children || []),
|
||||
]
|
||||
}
|
||||
|
||||
// Replace block with ejected definition
|
||||
ejectedDefinition = makeComponentUnique(ejectedDefinition)
|
||||
const index = parent._children.findIndex(x => x._id === componentId)
|
||||
parent._children[index] = ejectedDefinition
|
||||
nextSelectedComponentId = ejectedDefinition._id
|
||||
})
|
||||
|
||||
// Select new root component
|
||||
if (nextSelectedComponentId) {
|
||||
store.update(state => {
|
||||
state.selectedComponentId = nextSelectedComponentId
|
||||
return state
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
links: {
|
||||
save: async (url, title) => {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
export let key
|
||||
export let actions
|
||||
export let bindings = []
|
||||
export let nested
|
||||
|
||||
$: showAvailableActions = !actions?.length
|
||||
|
||||
|
@ -187,6 +188,7 @@
|
|||
this={selectedActionComponent}
|
||||
parameters={selectedAction.parameters}
|
||||
bindings={allBindings}
|
||||
{nested}
|
||||
/>
|
||||
</div>
|
||||
{/key}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
export let value = []
|
||||
export let name
|
||||
export let bindings
|
||||
export let nested
|
||||
|
||||
let drawer
|
||||
let tmpValue
|
||||
|
@ -90,6 +91,7 @@
|
|||
eventType={name}
|
||||
{bindings}
|
||||
{key}
|
||||
{nested}
|
||||
/>
|
||||
</Drawer>
|
||||
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
|
||||
export let parameters
|
||||
export let bindings = []
|
||||
export let nested
|
||||
|
||||
$: formComponents = getContextProviderComponents(
|
||||
$currentAsset,
|
||||
$store.selectedComponentId,
|
||||
"form"
|
||||
"form",
|
||||
{ includeSelf: nested }
|
||||
)
|
||||
$: schemaComponents = getContextProviderComponents(
|
||||
$currentAsset,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import { ActionButton } from "@budibase/bbui"
|
||||
|
||||
const eject = () => {
|
||||
document.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { key: "e", ctrlKey: true })
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<ActionButton secondary on:click={eject}>Eject block</ActionButton>
|
||||
</div>
|
|
@ -20,6 +20,7 @@
|
|||
export let componentBindings = []
|
||||
export let nested = false
|
||||
export let highlighted = false
|
||||
export let info = null
|
||||
|
||||
$: nullishValue = value == null || value === ""
|
||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||
|
@ -94,11 +95,15 @@
|
|||
bindings={allBindings}
|
||||
name={key}
|
||||
text={label}
|
||||
{nested}
|
||||
{key}
|
||||
{type}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{#if info}
|
||||
<div class="text">{@html info}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -123,4 +128,9 @@
|
|||
.control {
|
||||
position: relative;
|
||||
}
|
||||
.text {
|
||||
margin-top: var(--spectrum-global-dimension-size-65);
|
||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||
color: var(--grey-6);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
export let value
|
||||
export let bindings
|
||||
export let placeholder
|
||||
|
||||
$: urlOptions = $store.screens
|
||||
.map(screen => screen.routing?.route)
|
||||
|
@ -13,6 +14,7 @@
|
|||
<DrawerBindableCombobox
|
||||
{value}
|
||||
{bindings}
|
||||
{placeholder}
|
||||
on:change
|
||||
options={urlOptions}
|
||||
appendBindingsAsOptions={false}
|
||||
|
|
|
@ -98,11 +98,21 @@
|
|||
`./components/${$selectedComponent?._id}/new`
|
||||
)
|
||||
|
||||
// Register handler to send custom to the preview
|
||||
$: store.actions.preview.registerEventHandler((name, payload) => {
|
||||
iframe?.contentWindow.postMessage(
|
||||
JSON.stringify({
|
||||
name,
|
||||
payload,
|
||||
isBudibaseEvent: true,
|
||||
runtimeEvent: true,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// Update the iframe with the builder info to render the correct preview
|
||||
const refreshContent = message => {
|
||||
if (iframe) {
|
||||
iframe.contentWindow.postMessage(message)
|
||||
}
|
||||
iframe?.contentWindow.postMessage(message)
|
||||
}
|
||||
|
||||
const receiveMessage = message => {
|
||||
|
@ -198,6 +208,9 @@
|
|||
block: "center",
|
||||
})
|
||||
}
|
||||
} else if (type === "eject-block") {
|
||||
const { id, definition } = data
|
||||
await store.actions.components.handleEjectBlock(id, definition)
|
||||
} else if (type === "reload-plugin") {
|
||||
await store.actions.components.refreshDefinitions()
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
export let component
|
||||
|
||||
$: definition = store.actions.components.getDefinition(component?._component)
|
||||
$: noPaste = !$store.componentToPaste
|
||||
$: isBlock = definition?.block === true
|
||||
|
||||
const keyboardEvent = (key, ctrlKey = false) => {
|
||||
document.dispatchEvent(
|
||||
|
@ -30,6 +32,15 @@
|
|||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
{#if isBlock}
|
||||
<MenuItem
|
||||
icon="Export"
|
||||
keyBind="Ctrl+E"
|
||||
on:click={() => keyboardEvent("e", true)}
|
||||
>
|
||||
Eject block
|
||||
</MenuItem>
|
||||
{/if}
|
||||
<MenuItem
|
||||
icon="ChevronUp"
|
||||
keyBind="Ctrl+!ArrowUp"
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
||||
let confirmDeleteDialog
|
||||
let confirmEjectDialog
|
||||
let componentToDelete
|
||||
let componentToEject
|
||||
|
||||
const keyHandlers = {
|
||||
["^ArrowUp"]: async component => {
|
||||
|
@ -29,6 +31,10 @@
|
|||
store.actions.components.copy(component)
|
||||
await store.actions.components.paste(component, "below")
|
||||
},
|
||||
["^e"]: component => {
|
||||
componentToEject = component
|
||||
confirmEjectDialog.show()
|
||||
},
|
||||
["^Enter"]: () => {
|
||||
$goto("./new")
|
||||
},
|
||||
|
@ -124,3 +130,10 @@
|
|||
okText="Delete Component"
|
||||
onOk={() => store.actions.components.delete(componentToDelete)}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
bind:this={confirmEjectDialog}
|
||||
title="Eject block"
|
||||
body={`Ejecting a block breaks it down into multiple components and cannot be undone. Are you sure you want to eject "${componentToEject?._instanceName}"?`}
|
||||
onOk={() => store.actions.components.requestEjectBlock(componentToEject?._id)}
|
||||
okText="Eject block"
|
||||
/>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { store } from "builderStore"
|
||||
import PropertyControl from "components/design/settings/controls/PropertyControl.svelte"
|
||||
import ResetFieldsButton from "components/design/settings/controls/ResetFieldsButton.svelte"
|
||||
import EjectBlockButton from "components/design/settings/controls/EjectBlockButton.svelte"
|
||||
import { getComponentForSetting } from "components/design/settings/componentSettings"
|
||||
|
||||
export let componentDefinition
|
||||
|
@ -12,20 +13,29 @@
|
|||
export let componentBindings
|
||||
export let isScreen = false
|
||||
|
||||
$: sections = getSections(componentDefinition)
|
||||
$: sections = getSections(componentInstance, componentDefinition, isScreen)
|
||||
|
||||
const getSections = definition => {
|
||||
const getSections = (instance, definition, isScreen) => {
|
||||
const settings = definition?.settings ?? []
|
||||
const generalSettings = settings.filter(setting => !setting.section)
|
||||
const customSections = settings.filter(setting => setting.section)
|
||||
return [
|
||||
let sections = [
|
||||
{
|
||||
name: "General",
|
||||
info: componentDefinition?.info,
|
||||
settings: generalSettings,
|
||||
},
|
||||
...(customSections || []),
|
||||
]
|
||||
|
||||
// Filter out settings which shouldn't be rendered
|
||||
sections.forEach(section => {
|
||||
section.settings.forEach(setting => {
|
||||
setting.visible = canRenderControl(instance, setting, isScreen)
|
||||
})
|
||||
section.visible = section.settings.some(setting => setting.visible)
|
||||
})
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
const updateSetting = async (key, value) => {
|
||||
|
@ -36,7 +46,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
const canRenderControl = (setting, isScreen) => {
|
||||
const canRenderControl = (instance, setting, isScreen) => {
|
||||
// Prevent rendering on click setting for screens
|
||||
if (setting?.type === "event" && isScreen) {
|
||||
return false
|
||||
|
@ -51,6 +61,7 @@
|
|||
if (setting.dependsOn) {
|
||||
let dependantSetting = setting.dependsOn
|
||||
let dependantValue = null
|
||||
let invert = !!setting.dependsOn.invert
|
||||
if (typeof setting.dependsOn === "object") {
|
||||
dependantSetting = setting.dependsOn.setting
|
||||
dependantValue = setting.dependsOn.value
|
||||
|
@ -62,7 +73,7 @@
|
|||
// If no specific value is depended upon, check if a value exists at all
|
||||
// for the dependent setting
|
||||
if (dependantValue == null) {
|
||||
const currentValue = componentInstance[dependantSetting]
|
||||
const currentValue = instance[dependantSetting]
|
||||
if (currentValue === false) {
|
||||
return false
|
||||
}
|
||||
|
@ -73,7 +84,11 @@
|
|||
}
|
||||
|
||||
// Otherwise check the value matches
|
||||
return componentInstance[dependantSetting] === dependantValue
|
||||
if (invert) {
|
||||
return instance[dependantSetting] !== dependantValue
|
||||
} else {
|
||||
return instance[dependantSetting] === dependantValue
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -81,60 +96,54 @@
|
|||
</script>
|
||||
|
||||
{#each sections as section, idx (section.name)}
|
||||
<DetailSummary name={section.name} collapsible={false}>
|
||||
{#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen}
|
||||
<PropertyControl
|
||||
control={Input}
|
||||
label="Name"
|
||||
key="_instanceName"
|
||||
value={componentInstance._instanceName}
|
||||
onChange={val => updateSetting("_instanceName", val)}
|
||||
/>
|
||||
{/if}
|
||||
{#each section.settings as setting (setting.key)}
|
||||
{#if canRenderControl(setting, isScreen)}
|
||||
{#if section.visible}
|
||||
<DetailSummary name={section.name} collapsible={false}>
|
||||
{#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen}
|
||||
<PropertyControl
|
||||
type={setting.type}
|
||||
control={getComponentForSetting(setting)}
|
||||
label={setting.label}
|
||||
key={setting.key}
|
||||
value={componentInstance[setting.key]}
|
||||
defaultValue={setting.defaultValue}
|
||||
nested={setting.nested}
|
||||
onChange={val => updateSetting(setting.key, val)}
|
||||
highlighted={$store.highlightedSettingKey === setting.key}
|
||||
props={{
|
||||
// Generic settings
|
||||
placeholder: setting.placeholder || null,
|
||||
|
||||
// Select settings
|
||||
options: setting.options || [],
|
||||
|
||||
// Number fields
|
||||
min: setting.min || null,
|
||||
max: setting.max || null,
|
||||
}}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
control={Input}
|
||||
label="Name"
|
||||
key="_instanceName"
|
||||
value={componentInstance._instanceName}
|
||||
onChange={val => updateSetting("_instanceName", val)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||
<ResetFieldsButton {componentInstance} />
|
||||
{/if}
|
||||
{#if section?.info}
|
||||
<div class="text">
|
||||
{@html section.info}
|
||||
</div>
|
||||
{/if}
|
||||
</DetailSummary>
|
||||
{/each}
|
||||
{#each section.settings as setting (setting.key)}
|
||||
{#if setting.visible}
|
||||
<PropertyControl
|
||||
type={setting.type}
|
||||
control={getComponentForSetting(setting)}
|
||||
label={setting.label}
|
||||
key={setting.key}
|
||||
value={componentInstance[setting.key]}
|
||||
defaultValue={setting.defaultValue}
|
||||
nested={setting.nested}
|
||||
onChange={val => updateSetting(setting.key, val)}
|
||||
highlighted={$store.highlightedSettingKey === setting.key}
|
||||
info={setting.info}
|
||||
props={{
|
||||
// Generic settings
|
||||
placeholder: setting.placeholder || null,
|
||||
|
||||
<style>
|
||||
.text {
|
||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||
color: var(--grey-6);
|
||||
}
|
||||
</style>
|
||||
// Select settings
|
||||
options: setting.options || [],
|
||||
|
||||
// Number fields
|
||||
min: setting.min || null,
|
||||
max: setting.max || null,
|
||||
}}
|
||||
{bindings}
|
||||
{componentBindings}
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")}
|
||||
<ResetFieldsButton {componentInstance} />
|
||||
{/if}
|
||||
{#if idx === 0 && componentDefinition?.block}
|
||||
<EjectBlockButton />
|
||||
{/if}
|
||||
</DetailSummary>
|
||||
{/if}
|
||||
{/each}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"children": [
|
||||
"tableblock",
|
||||
"cardsblock",
|
||||
"repeaterblock"
|
||||
"repeaterblock",
|
||||
"formblock"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
let duplicateScreen = Helpers.cloneDeep(screen)
|
||||
delete duplicateScreen._id
|
||||
delete duplicateScreen._rev
|
||||
makeComponentUnique(duplicateScreen.props)
|
||||
duplicateScreen.props = makeComponentUnique(duplicateScreen.props)
|
||||
|
||||
// Attach the new name and URL
|
||||
duplicateScreen.routing.route = sanitizeUrl(screenUrl)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/cli",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
|
@ -26,9 +26,9 @@
|
|||
"outputPath": "build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "2.0.14-alpha.4",
|
||||
"@budibase/string-templates": "2.0.14-alpha.4",
|
||||
"@budibase/types": "2.0.14-alpha.4",
|
||||
"@budibase/backend-core": "2.0.24-alpha.3",
|
||||
"@budibase/string-templates": "2.0.24-alpha.3",
|
||||
"@budibase/types": "2.0.24-alpha.3",
|
||||
"axios": "0.21.2",
|
||||
"chalk": "4.1.0",
|
||||
"cli-progress": "3.11.2",
|
||||
|
|
|
@ -3493,7 +3493,6 @@
|
|||
},
|
||||
"s3upload": {
|
||||
"name": "S3 File Upload",
|
||||
"info": "This component can't be used with S3 datasources that use custom endpoints.",
|
||||
"icon": "UploadToCloud",
|
||||
"styles": [
|
||||
"size"
|
||||
|
@ -3514,7 +3513,8 @@
|
|||
{
|
||||
"type": "dataSource/s3",
|
||||
"label": "S3 Datasource",
|
||||
"key": "datasourceId"
|
||||
"key": "datasourceId",
|
||||
"info": "This component can't be used with S3 datasources that use custom endpoints"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -3552,7 +3552,6 @@
|
|||
},
|
||||
"dataprovider": {
|
||||
"name": "Data Provider",
|
||||
"info": "Pagination is only available for data stored in tables.",
|
||||
"icon": "Data",
|
||||
"illegalChildren": [
|
||||
"section"
|
||||
|
@ -3598,7 +3597,8 @@
|
|||
"type": "boolean",
|
||||
"label": "Paginate",
|
||||
"key": "paginate",
|
||||
"defaultValue": true
|
||||
"defaultValue": true,
|
||||
"info": "Pagination is only available for data stored in tables"
|
||||
}
|
||||
],
|
||||
"context": {
|
||||
|
@ -3640,7 +3640,6 @@
|
|||
],
|
||||
"hasChildren": true,
|
||||
"showEmptyState": false,
|
||||
"info": "Row selection is only compatible with internal or SQL tables",
|
||||
"settings": [
|
||||
{
|
||||
"type": "dataProvider",
|
||||
|
@ -3697,7 +3696,8 @@
|
|||
"type": "boolean",
|
||||
"label": "Allow row selection",
|
||||
"key": "allowSelectRows",
|
||||
"defaultValue": false
|
||||
"defaultValue": false,
|
||||
"info": "Row selection is only compatible with internal or SQL tables"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
|
@ -3738,13 +3738,13 @@
|
|||
"size"
|
||||
],
|
||||
"hasChildren": false,
|
||||
"info": "Your data provider will be automatically filtered to the given date range.",
|
||||
"settings": [
|
||||
{
|
||||
"type": "dataProvider",
|
||||
"label": "Provider",
|
||||
"key": "dataProvider",
|
||||
"required": true
|
||||
"required": true,
|
||||
"info": "Your data provider will be automatically filtered to the given date range."
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
|
@ -3879,7 +3879,6 @@
|
|||
"styles": [
|
||||
"size"
|
||||
],
|
||||
"info": "Only the first 3 search columns will be used.",
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -3896,7 +3895,8 @@
|
|||
"type": "searchfield",
|
||||
"label": "Search Columns",
|
||||
"key": "searchColumns",
|
||||
"placeholder": "Choose search columns"
|
||||
"placeholder": "Choose search columns",
|
||||
"info": "Only the first 5 search columns will be used"
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
|
@ -3943,7 +3943,6 @@
|
|||
{
|
||||
"section": true,
|
||||
"name": "Table",
|
||||
"info": "Row selection is only compatible with internal or SQL tables",
|
||||
"settings": [
|
||||
{
|
||||
"type": "number",
|
||||
|
@ -3977,7 +3976,8 @@
|
|||
{
|
||||
"type": "boolean",
|
||||
"label": "Allow row selection",
|
||||
"key": "allowSelectRows"
|
||||
"key": "allowSelectRows",
|
||||
"info": "Row selection is only compatible with internal or SQL tables"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
|
@ -4044,7 +4044,6 @@
|
|||
"styles": [
|
||||
"size"
|
||||
],
|
||||
"info": "Only the first 3 search columns will be used.",
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -4061,7 +4060,8 @@
|
|||
"type": "searchfield",
|
||||
"label": "Search Columns",
|
||||
"key": "searchColumns",
|
||||
"placeholder": "Choose search columns"
|
||||
"placeholder": "Choose search columns",
|
||||
"info": "Only the first 5 search columns will be used"
|
||||
},
|
||||
{
|
||||
"type": "filter",
|
||||
|
@ -4208,6 +4208,7 @@
|
|||
}
|
||||
},
|
||||
"repeaterblock": {
|
||||
"block": true,
|
||||
"name": "Repeater block",
|
||||
"icon": "ViewList",
|
||||
"illegalChildren": [
|
||||
|
@ -4445,5 +4446,145 @@
|
|||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"formblock": {
|
||||
"name": "Form Block",
|
||||
"icon": "Form",
|
||||
"styles": ["size"],
|
||||
"block": true,
|
||||
"info": "Form blocks are only compatible with internal or SQL tables",
|
||||
"settings": [
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Type",
|
||||
"key": "actionType",
|
||||
"options": ["Create", "Update", "View"],
|
||||
"defaultValue": "Create"
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"label": "Table",
|
||||
"key": "dataSource"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Row ID",
|
||||
"key": "rowId",
|
||||
"nested": true,
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "Create",
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Title",
|
||||
"key": "title",
|
||||
"nested": true
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Size",
|
||||
"key": "size",
|
||||
"options": [
|
||||
{
|
||||
"label": "Medium",
|
||||
"value": "spectrum--medium"
|
||||
},
|
||||
{
|
||||
"label": "Large",
|
||||
"value": "spectrum--large"
|
||||
}
|
||||
],
|
||||
"defaultValue": "spectrum--medium"
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Fields",
|
||||
"settings": [
|
||||
{
|
||||
"type": "multifield",
|
||||
"label": "Fields",
|
||||
"key": "fields"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"label": "Field labels",
|
||||
"key": "labelPosition",
|
||||
"defaultValue": "left",
|
||||
"options": [
|
||||
{
|
||||
"label": "Left",
|
||||
"value": "left"
|
||||
},
|
||||
{
|
||||
"label": "Above",
|
||||
"value": "above"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Disabled",
|
||||
"key": "disabled",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "View",
|
||||
"invert": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": true,
|
||||
"name": "Buttons",
|
||||
"settings": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Show save button",
|
||||
"key": "showSaveButton",
|
||||
"defaultValue": true,
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "View",
|
||||
"invert": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"label": "Show delete button",
|
||||
"key": "showDeleteButton",
|
||||
"defaultValue": false,
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "Update"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "url",
|
||||
"label": "Navigate after button press",
|
||||
"key": "actionUrl",
|
||||
"placeholder": "Choose a screen",
|
||||
"dependsOn": {
|
||||
"setting": "actionType",
|
||||
"value": "View",
|
||||
"invert": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"type": "form",
|
||||
"suffix": "form"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"suffix": "repeater"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/client",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"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": "2.0.14-alpha.4",
|
||||
"@budibase/frontend-core": "2.0.14-alpha.4",
|
||||
"@budibase/string-templates": "2.0.14-alpha.4",
|
||||
"@budibase/bbui": "2.0.24-alpha.3",
|
||||
"@budibase/frontend-core": "2.0.24-alpha.3",
|
||||
"@budibase/string-templates": "2.0.24-alpha.3",
|
||||
"@spectrum-css/button": "^3.0.3",
|
||||
"@spectrum-css/card": "^3.0.3",
|
||||
"@spectrum-css/divider": "^1.0.3",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { createAPIClient } from "@budibase/frontend-core"
|
||||
import { notificationStore, authStore, devToolsStore } from "../stores"
|
||||
import { notificationStore } from "../stores/notification.js"
|
||||
import { authStore } from "../stores/auth.js"
|
||||
import { devToolsStore } from "../stores/devTools.js"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export const API = createAPIClient({
|
||||
|
|
|
@ -1,12 +1,92 @@
|
|||
<script>
|
||||
import { getContext, setContext } from "svelte"
|
||||
import { getContext, onDestroy, onMount, setContext } from "svelte"
|
||||
import { builderStore } from "stores/builder.js"
|
||||
import { blockStore } from "stores/blocks.js"
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable } = getContext("sdk")
|
||||
|
||||
// We need to set a block context to know we're inside a block, but also
|
||||
// to be able to reference the actual component ID of the block from
|
||||
// any depth
|
||||
setContext("block", { id: $component.id })
|
||||
let structureLookupMap = {}
|
||||
|
||||
const registerBlockComponent = (id, order, parentId, instance) => {
|
||||
// Ensure child array exists
|
||||
if (!structureLookupMap[parentId]) {
|
||||
structureLookupMap[parentId] = {}
|
||||
}
|
||||
// Add this instance in this order, overwriting any existing instance in
|
||||
// this order in case of repeaters
|
||||
structureLookupMap[parentId][order] = instance
|
||||
}
|
||||
|
||||
const eject = () => {
|
||||
// Start the new structure with the root component
|
||||
let definition = structureLookupMap[$component.id][0]
|
||||
|
||||
// Copy styles from block to root component
|
||||
definition._styles = {
|
||||
...definition._styles,
|
||||
normal: {
|
||||
...definition._styles?.normal,
|
||||
...$component.styles?.normal,
|
||||
},
|
||||
custom:
|
||||
definition._styles?.custom || "" + $component.styles?.custom || "",
|
||||
}
|
||||
|
||||
// Create component tree
|
||||
attachChildren(definition, structureLookupMap)
|
||||
builderStore.actions.ejectBlock($component.id, definition)
|
||||
}
|
||||
|
||||
const attachChildren = (rootComponent, map) => {
|
||||
// Transform map into children array
|
||||
let id = rootComponent._id
|
||||
const children = Object.entries(map[id] || {}).map(([order, instance]) => ({
|
||||
order,
|
||||
instance,
|
||||
}))
|
||||
if (!children.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort children by order
|
||||
children.sort((a, b) => (a.order < b.order ? -1 : 1))
|
||||
|
||||
// Attach all children of this component
|
||||
rootComponent._children = children.map(x => x.instance)
|
||||
|
||||
// Recurse for each child
|
||||
rootComponent._children.forEach(child => {
|
||||
attachChildren(child, map)
|
||||
})
|
||||
}
|
||||
|
||||
setContext("block", {
|
||||
// We need to set a block context to know we're inside a block, but also
|
||||
// to be able to reference the actual component ID of the block from
|
||||
// any depth
|
||||
id: $component.id,
|
||||
|
||||
// We register block components with their raw props so that we can eject
|
||||
// blocks later on
|
||||
registerComponent: registerBlockComponent,
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
// We register and unregister blocks to the block store when inside the
|
||||
// builder preview to allow for block ejection
|
||||
if ($builderStore.inBuilder) {
|
||||
blockStore.actions.registerBlock($component.id, { eject })
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if ($builderStore.inBuilder) {
|
||||
blockStore.actions.unregisterBlock($component.id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<div use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import { generate } from "shortid"
|
||||
import { builderStore } from "../stores/builder.js"
|
||||
import Component from "components/Component.svelte"
|
||||
|
||||
export let type
|
||||
export let props
|
||||
export let styles
|
||||
export let context
|
||||
export let order = 0
|
||||
export let containsSlot = false
|
||||
|
||||
// ID is only exposed as a prop so that it can be bound to from parent
|
||||
// block components
|
||||
export let id
|
||||
|
||||
const component = getContext("component")
|
||||
const block = getContext("block")
|
||||
const rand = generate()
|
||||
|
||||
|
@ -21,13 +25,22 @@
|
|||
$: instance = {
|
||||
_component: `@budibase/standard-components/${type}`,
|
||||
_id: id,
|
||||
_instanceName: type[0].toUpperCase() + type.slice(1),
|
||||
_styles: {
|
||||
normal: {
|
||||
...styles,
|
||||
},
|
||||
...styles,
|
||||
normal: styles?.normal || {},
|
||||
},
|
||||
_containsSlot: containsSlot,
|
||||
...props,
|
||||
}
|
||||
|
||||
// Register this block component if we're inside the builder so it can be
|
||||
// ejected later
|
||||
$: {
|
||||
if ($builderStore.inBuilder) {
|
||||
block.registerComponent(id, order ?? 0, $component?.id, instance)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Component {instance} isBlock>
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
themeStore,
|
||||
appStore,
|
||||
devToolsStore,
|
||||
environmentStore,
|
||||
} from "stores"
|
||||
import NotificationDisplay from "components/overlay/NotificationDisplay.svelte"
|
||||
import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte"
|
||||
|
@ -48,8 +47,6 @@
|
|||
!$builderStore.inBuilder &&
|
||||
$devToolsStore.enabled &&
|
||||
!$routeStore.queryParams?.peek
|
||||
$: objectStoreUrl = $environmentStore.cloud ? "https://cdn.budi.live" : ""
|
||||
$: pluginsUrl = `${objectStoreUrl}/plugins`
|
||||
|
||||
// Handle no matching route
|
||||
$: {
|
||||
|
@ -95,8 +92,7 @@
|
|||
<svelte:head>
|
||||
{#if $builderStore.usedPlugins?.length}
|
||||
{#each $builderStore.usedPlugins as plugin (plugin.hash)}
|
||||
<script
|
||||
src={`${pluginsUrl}/${plugin.jsUrl}?r=${plugin.hash || ""}`}></script>
|
||||
<script src={`${plugin.jsUrl}?r=${plugin.hash || ""}`}></script>
|
||||
{/each}
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { getContext } from "svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
||||
|
||||
|
@ -31,9 +30,7 @@
|
|||
export let cardButtonOnClick
|
||||
export let linkColumn
|
||||
|
||||
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
||||
const context = getContext("context")
|
||||
const component = getContext("component")
|
||||
const { fetchDatasourceSchema } = getContext("sdk")
|
||||
|
||||
let formId
|
||||
let dataProviderId
|
||||
|
@ -84,163 +81,132 @@
|
|||
|
||||
{#if schemaLoaded}
|
||||
<Block>
|
||||
<div class="card-list" use:styleable={$component.styles}>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<div class="header" class:mobile={$context.device.mobile}>
|
||||
<div class="title">
|
||||
<Heading>{title || ""}</Heading>
|
||||
</div>
|
||||
<div class="controls">
|
||||
{#if enrichedSearchColumns?.length}
|
||||
<div
|
||||
class="search"
|
||||
style="--cols:{enrichedSearchColumns?.length}"
|
||||
>
|
||||
{#each enrichedSearchColumns as column}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
type="container"
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-bottom": "20px",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
bind:id={repeaterId}
|
||||
context="repeater"
|
||||
type="heading"
|
||||
props={{
|
||||
text: title,
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
hAlign: "left",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
noRowsMessage: "No rows found",
|
||||
}}
|
||||
styles={{
|
||||
display: "grid",
|
||||
"grid-template-columns": `repeat(auto-fill, minmax(min(${cardWidth}px, 100%), 1fr))`,
|
||||
wrap: true,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="spectrumcard"
|
||||
props={{
|
||||
title: cardTitle,
|
||||
subtitle: cardSubtitle,
|
||||
description: cardDescription,
|
||||
imageURL: cardImageURL,
|
||||
horizontal: cardHorizontal,
|
||||
showButton: showCardButton,
|
||||
buttonText: cardButtonText,
|
||||
buttonOnClick: cardButtonOnClick,
|
||||
linkURL: fullCardURL,
|
||||
linkPeek: cardPeek,
|
||||
}}
|
||||
styles={{
|
||||
width: "auto",
|
||||
}}
|
||||
/>
|
||||
{#if enrichedSearchColumns?.length}
|
||||
{#each enrichedSearchColumns as column, idx}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
order={idx}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "192px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
order={enrichedSearchColumns?.length ?? 0}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
bind:id={repeaterId}
|
||||
context="repeater"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
gap: "M",
|
||||
noRowsMessage: "No rows found",
|
||||
}}
|
||||
styles={{
|
||||
custom: `display: grid;\ngrid-template-columns: repeat(auto-fill, minmax(min(${cardWidth}px, 100%), 1fr));`,
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="spectrumcard"
|
||||
props={{
|
||||
title: cardTitle,
|
||||
subtitle: cardSubtitle,
|
||||
description: cardDescription,
|
||||
imageURL: cardImageURL,
|
||||
horizontal: cardHorizontal,
|
||||
showButton: showCardButton,
|
||||
buttonText: cardButtonText,
|
||||
buttonOnClick: cardButtonOnClick,
|
||||
linkURL: fullCardURL,
|
||||
linkPeek: cardPeek,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "auto",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
</div>
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow: hidden;
|
||||
}
|
||||
.title :global(.spectrum-Heading) {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 0 1 auto;
|
||||
gap: 10px;
|
||||
max-width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols), minmax(120px, 200px));
|
||||
}
|
||||
.search :global(.spectrum-InputGroup) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
.mobile {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.mobile .controls {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.mobile .search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
import BlockComponent from "../../BlockComponent.svelte"
|
||||
import Block from "../../Block.svelte"
|
||||
import Placeholder from "../Placeholder.svelte"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
|
||||
export let actionType
|
||||
export let dataSource
|
||||
export let size
|
||||
export let disabled
|
||||
export let fields
|
||||
export let labelPosition
|
||||
export let title
|
||||
export let showSaveButton
|
||||
export let showDeleteButton
|
||||
export let rowId
|
||||
export let actionUrl
|
||||
|
||||
const { fetchDatasourceSchema, builderStore } = getContext("sdk")
|
||||
const FieldTypeToComponentMap = {
|
||||
string: "stringfield",
|
||||
number: "numberfield",
|
||||
options: "optionsfield",
|
||||
array: "multifieldselect",
|
||||
boolean: "booleanfield",
|
||||
longform: "longformfield",
|
||||
datetime: "datetimefield",
|
||||
attachment: "attachmentfield",
|
||||
link: "relationshipfield",
|
||||
json: "jsonfield",
|
||||
}
|
||||
|
||||
let schema
|
||||
let formId
|
||||
let providerId
|
||||
let repeaterId
|
||||
|
||||
$: fetchSchema(dataSource)
|
||||
$: onSave = [
|
||||
{
|
||||
"##eventHandlerType": "Save Row",
|
||||
parameters: {
|
||||
providerId: formId,
|
||||
tableId: dataSource?.tableId,
|
||||
},
|
||||
},
|
||||
{
|
||||
"##eventHandlerType": "Close Screen Modal",
|
||||
},
|
||||
{
|
||||
"##eventHandlerType": "Navigate To",
|
||||
parameters: {
|
||||
url: actionUrl,
|
||||
},
|
||||
},
|
||||
]
|
||||
$: onDelete = [
|
||||
{
|
||||
"##eventHandlerType": "Delete Row",
|
||||
parameters: {
|
||||
confirm: true,
|
||||
tableId: dataSource?.tableId,
|
||||
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
"##eventHandlerType": "Close Screen Modal",
|
||||
},
|
||||
{
|
||||
"##eventHandlerType": "Navigate To",
|
||||
parameters: {
|
||||
url: actionUrl,
|
||||
},
|
||||
},
|
||||
]
|
||||
$: filter = [
|
||||
{
|
||||
field: "_id",
|
||||
operator: "equal",
|
||||
type: "string",
|
||||
value: rowId,
|
||||
valueType: "Binding",
|
||||
},
|
||||
]
|
||||
// If we're using an "update" form, use the real data provider. If we're
|
||||
// using a create form, we just want a fake array so that our repeater
|
||||
// will actually render the form, but data doesn't matter.
|
||||
$: dataProvider =
|
||||
actionType !== "Create"
|
||||
? `{{ literal ${safe(providerId)} }}`
|
||||
: { rows: [{}] }
|
||||
$: renderDeleteButton = showDeleteButton && actionType === "Update"
|
||||
$: renderSaveButton = showSaveButton && actionType !== "View"
|
||||
$: renderButtons = renderDeleteButton || renderSaveButton
|
||||
$: renderHeader = renderButtons || title
|
||||
|
||||
const fetchSchema = async () => {
|
||||
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||
}
|
||||
|
||||
const getComponentForField = field => {
|
||||
if (!field || !schema?.[field]) {
|
||||
return null
|
||||
}
|
||||
const type = schema[field].type
|
||||
return FieldTypeToComponentMap[type]
|
||||
}
|
||||
</script>
|
||||
|
||||
<Block>
|
||||
{#if fields?.length}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
context="provider"
|
||||
bind:id={providerId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter,
|
||||
limit: rowId ? 1 : $builderStore.inBuilder ? 1 : 0,
|
||||
paginate: false,
|
||||
}}
|
||||
>
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
context="repeater"
|
||||
bind:id={repeaterId}
|
||||
props={{
|
||||
dataProvider,
|
||||
noRowsMessage: "We couldn't find a row to display",
|
||||
}}
|
||||
>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
props={{
|
||||
actionType: actionType === "Create" ? "Create" : "Update",
|
||||
dataSource,
|
||||
size,
|
||||
disabled: disabled || actionType === "View",
|
||||
}}
|
||||
context="form"
|
||||
bind:id={formId}
|
||||
>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "column",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
gap: "M",
|
||||
}}
|
||||
>
|
||||
{#if renderHeader}
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="heading"
|
||||
props={{ text: title || "" }}
|
||||
order={0}
|
||||
/>
|
||||
{#if renderButtons}
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
{#if renderDeleteButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
text: "Delete",
|
||||
onClick: onDelete,
|
||||
quiet: true,
|
||||
type: "secondary",
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
{/if}
|
||||
{#if renderSaveButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
text: "Save",
|
||||
onClick: onSave,
|
||||
type: "cta",
|
||||
}}
|
||||
order={1}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="fieldgroup"
|
||||
props={{ labelPosition }}
|
||||
order={1}
|
||||
>
|
||||
{#each fields as field, idx}
|
||||
{#if getComponentForField(field)}
|
||||
<BlockComponent
|
||||
type={getComponentForField(field)}
|
||||
props={{
|
||||
field,
|
||||
label: field,
|
||||
placeholder: field,
|
||||
disabled,
|
||||
}}
|
||||
order={idx}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
{:else}
|
||||
<Placeholder
|
||||
text="Choose your table and add some fields to your form to get started"
|
||||
/>
|
||||
{/if}
|
||||
</Block>
|
|
@ -17,45 +17,43 @@
|
|||
export let vAlign
|
||||
export let gap
|
||||
|
||||
let providerId
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable } = getContext("sdk")
|
||||
|
||||
let providerId
|
||||
</script>
|
||||
|
||||
<Block>
|
||||
<div use:styleable={$component.styles}>
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
context="provider"
|
||||
bind:id={providerId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
limit,
|
||||
paginate,
|
||||
}}
|
||||
>
|
||||
{#if $component.empty}
|
||||
<Placeholder />
|
||||
{:else}
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
context="repeater"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(providerId)} }}`,
|
||||
noRowsMessage,
|
||||
direction,
|
||||
hAlign,
|
||||
vAlign,
|
||||
gap,
|
||||
}}
|
||||
>
|
||||
<slot />
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</div>
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
context="provider"
|
||||
bind:id={providerId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
limit,
|
||||
paginate,
|
||||
}}
|
||||
>
|
||||
{#if $component.empty}
|
||||
<Placeholder />
|
||||
{:else}
|
||||
<BlockComponent
|
||||
type="repeater"
|
||||
context="repeater"
|
||||
containsSlot
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(providerId)} }}`,
|
||||
noRowsMessage,
|
||||
direction,
|
||||
hAlign,
|
||||
vAlign,
|
||||
gap,
|
||||
}}
|
||||
>
|
||||
<slot />
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { getContext } from "svelte"
|
||||
import Block from "components/Block.svelte"
|
||||
import BlockComponent from "components/BlockComponent.svelte"
|
||||
import { Heading } from "@budibase/bbui"
|
||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||
import { enrichSearchColumns, enrichFilter } from "utils/blocks.js"
|
||||
|
||||
|
@ -29,9 +28,7 @@
|
|||
export let titleButtonURL
|
||||
export let titleButtonPeek
|
||||
|
||||
const { fetchDatasourceSchema, styleable } = getContext("sdk")
|
||||
const context = getContext("context")
|
||||
const component = getContext("component")
|
||||
const { fetchDatasourceSchema } = getContext("sdk")
|
||||
|
||||
let formId
|
||||
let dataProviderId
|
||||
|
@ -64,145 +61,116 @@
|
|||
|
||||
{#if schemaLoaded}
|
||||
<Block>
|
||||
<div class={size} use:styleable={$component.styles}>
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{ dataSource, disableValidation: true, editAutoColumns: true }}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<div class="header" class:mobile={$context.device.mobile}>
|
||||
<div class="title">
|
||||
<Heading>{title || ""}</Heading>
|
||||
</div>
|
||||
<div class="controls">
|
||||
{#if enrichedSearchColumns?.length}
|
||||
<div
|
||||
class="search"
|
||||
style="--cols:{enrichedSearchColumns?.length}"
|
||||
>
|
||||
{#each enrichedSearchColumns as column}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="form"
|
||||
bind:id={formId}
|
||||
props={{
|
||||
dataSource,
|
||||
disableValidation: true,
|
||||
editAutoColumns: true,
|
||||
size,
|
||||
}}
|
||||
>
|
||||
{#if title || enrichedSearchColumns?.length || showTitleButton}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
type="container"
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit: rowCount,
|
||||
direction: "row",
|
||||
hAlign: "stretch",
|
||||
vAlign: "middle",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
"margin-bottom": "20px",
|
||||
},
|
||||
}}
|
||||
order={0}
|
||||
>
|
||||
<BlockComponent
|
||||
type="table"
|
||||
context="table"
|
||||
type="heading"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
columns: tableColumns,
|
||||
showAutoColumns,
|
||||
rowCount,
|
||||
quiet,
|
||||
compact,
|
||||
allowSelectRows,
|
||||
size,
|
||||
linkRows,
|
||||
linkURL,
|
||||
linkColumn,
|
||||
linkPeek,
|
||||
text: title,
|
||||
}}
|
||||
order={0}
|
||||
/>
|
||||
<BlockComponent
|
||||
type="container"
|
||||
props={{
|
||||
direction: "row",
|
||||
hAlign: "left",
|
||||
vAlign: "center",
|
||||
gap: "M",
|
||||
wrap: true,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
{#if enrichedSearchColumns?.length}
|
||||
{#each enrichedSearchColumns as column, idx}
|
||||
<BlockComponent
|
||||
type={column.componentType}
|
||||
props={{
|
||||
field: column.name,
|
||||
placeholder: column.name,
|
||||
text: column.name,
|
||||
autoWidth: true,
|
||||
}}
|
||||
styles={{
|
||||
normal: {
|
||||
width: "192px",
|
||||
},
|
||||
}}
|
||||
order={idx}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if showTitleButton}
|
||||
<BlockComponent
|
||||
type="button"
|
||||
props={{
|
||||
onClick: titleButtonAction,
|
||||
text: titleButtonText,
|
||||
type: "cta",
|
||||
}}
|
||||
order={enrichedSearchColumns?.length ?? 0}
|
||||
/>
|
||||
{/if}
|
||||
</BlockComponent>
|
||||
</BlockComponent>
|
||||
{/if}
|
||||
<BlockComponent
|
||||
type="dataprovider"
|
||||
bind:id={dataProviderId}
|
||||
props={{
|
||||
dataSource,
|
||||
filter: enrichedFilter,
|
||||
sortColumn,
|
||||
sortOrder,
|
||||
paginate,
|
||||
limit: rowCount,
|
||||
}}
|
||||
order={1}
|
||||
>
|
||||
<BlockComponent
|
||||
type="table"
|
||||
context="table"
|
||||
props={{
|
||||
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
|
||||
columns: tableColumns,
|
||||
showAutoColumns,
|
||||
rowCount,
|
||||
quiet,
|
||||
compact,
|
||||
allowSelectRows,
|
||||
size,
|
||||
linkRows,
|
||||
linkURL,
|
||||
linkColumn,
|
||||
linkPeek,
|
||||
}}
|
||||
/>
|
||||
</BlockComponent>
|
||||
</div>
|
||||
</BlockComponent>
|
||||
</Block>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow: hidden;
|
||||
}
|
||||
.title :global(.spectrum-Heading) {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search {
|
||||
flex: 0 1 auto;
|
||||
gap: 10px;
|
||||
max-width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols), minmax(120px, 200px));
|
||||
}
|
||||
.search :global(.spectrum-InputGroup) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Mobile styles */
|
||||
.mobile {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.mobile .controls {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
.mobile .search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { default as tableblock } from "./TableBlock.svelte"
|
||||
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
||||
export { default as formblock } from "./FormBlock.svelte"
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
export class ApexOptionsBuilder {
|
||||
formatters = {
|
||||
["Default"]: val => (isNaN(val) ? val : Math.round(val * 100) / 100),
|
||||
["Thousands"]: val => `${Math.round(val / 1000)}K`,
|
||||
["Millions"]: val => `${Math.round(val / 1000000)}M`,
|
||||
}
|
||||
options = {
|
||||
series: [],
|
||||
legend: {
|
||||
show: false,
|
||||
position: "top",
|
||||
horizontalAlign: "right",
|
||||
showForSingleSeries: true,
|
||||
showForNullSeries: true,
|
||||
showForZeroSeries: true,
|
||||
},
|
||||
chart: {
|
||||
toolbar: {
|
||||
constructor() {
|
||||
this.formatters = {
|
||||
["Default"]: val => (isNaN(val) ? val : Math.round(val * 100) / 100),
|
||||
["Thousands"]: val => `${Math.round(val / 1000)}K`,
|
||||
["Millions"]: val => `${Math.round(val / 1000000)}M`,
|
||||
}
|
||||
this.options = {
|
||||
series: [],
|
||||
legend: {
|
||||
show: false,
|
||||
position: "top",
|
||||
horizontalAlign: "right",
|
||||
showForSingleSeries: true,
|
||||
showForNullSeries: true,
|
||||
showForZeroSeries: true,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
chart: {
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
zoom: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
labels: {
|
||||
formatter: this.formatters.Default,
|
||||
xaxis: {
|
||||
labels: {
|
||||
formatter: this.formatters.Default,
|
||||
},
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: this.formatters.Default,
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: this.formatters.Default,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
setOption(path, value) {
|
||||
|
|
|
@ -48,36 +48,7 @@
|
|||
|
||||
// Fetches the form schema from this form's dataSource
|
||||
const fetchSchema = async dataSource => {
|
||||
if (!dataSource) {
|
||||
schema = {}
|
||||
}
|
||||
|
||||
// If the datasource is a query, then we instead use a schema of the query
|
||||
// parameters rather than the output schema
|
||||
else if (
|
||||
dataSource.type === "query" &&
|
||||
dataSource._id &&
|
||||
actionType === "Create"
|
||||
) {
|
||||
try {
|
||||
const query = await API.fetchQueryDefinition(dataSource._id)
|
||||
let paramSchema = {}
|
||||
const params = query.parameters || []
|
||||
params.forEach(param => {
|
||||
paramSchema[param.name] = { ...param, type: "string" }
|
||||
})
|
||||
schema = paramSchema
|
||||
} catch (error) {
|
||||
schema = {}
|
||||
}
|
||||
}
|
||||
|
||||
// For all other cases, just grab the normal schema
|
||||
else {
|
||||
const dataSourceSchema = await fetchDatasourceSchema(dataSource)
|
||||
schema = dataSourceSchema || {}
|
||||
}
|
||||
|
||||
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||
if (!loaded) {
|
||||
loaded = true
|
||||
}
|
||||
|
@ -95,7 +66,7 @@
|
|||
|
||||
$: initialValues = getInitialValues(actionType, dataSource, $context)
|
||||
$: resetKey = Helpers.hashString(
|
||||
JSON.stringify(initialValues) + JSON.stringify(schema)
|
||||
JSON.stringify(initialValues) + JSON.stringify(schema) + disabled
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import ClientApp from "./components/ClientApp.svelte"
|
||||
import {
|
||||
componentStore,
|
||||
builderStore,
|
||||
appStore,
|
||||
devToolsStore,
|
||||
blockStore,
|
||||
componentStore,
|
||||
environmentStore,
|
||||
} from "./stores"
|
||||
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-rollup.js"
|
||||
|
@ -50,6 +51,17 @@ const loadBudibase = async () => {
|
|||
const enableDevTools = !get(builderStore).inBuilder && get(appStore).isDevApp
|
||||
devToolsStore.actions.setEnabled(enableDevTools)
|
||||
|
||||
// Register handler for runtime events from the builder
|
||||
window.handleBuilderRuntimeEvent = (name, payload) => {
|
||||
if (!window["##BUDIBASE_IN_BUILDER##"]) {
|
||||
return
|
||||
}
|
||||
if (name === "eject-block") {
|
||||
const block = blockStore.actions.getBlock(payload)
|
||||
block?.eject()
|
||||
}
|
||||
}
|
||||
|
||||
// Register any custom components
|
||||
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
|
||||
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { get, writable } from "svelte/store"
|
||||
|
||||
const createBlockStore = () => {
|
||||
const store = writable({})
|
||||
|
||||
const registerBlock = (id, instance) => {
|
||||
store.update(state => ({
|
||||
...state,
|
||||
[id]: instance,
|
||||
}))
|
||||
}
|
||||
|
||||
const unregisterBlock = id => {
|
||||
store.update(state => {
|
||||
delete state[id]
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const getBlock = id => {
|
||||
return get(store)[id]
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
actions: {
|
||||
registerBlock,
|
||||
unregisterBlock,
|
||||
getBlock,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const blockStore = createBlockStore()
|
|
@ -85,6 +85,9 @@ const createBuilderStore = () => {
|
|||
highlightSetting: setting => {
|
||||
dispatchEvent("highlight-setting", { setting })
|
||||
},
|
||||
ejectBlock: (id, definition) => {
|
||||
dispatchEvent("eject-block", { id, definition })
|
||||
},
|
||||
updateUsedPlugin: (name, hash) => {
|
||||
// Check if we used this plugin
|
||||
const used = get(store)?.usedPlugins?.find(x => x.name === name)
|
||||
|
|
|
@ -17,6 +17,7 @@ export { devToolsStore } from "./devTools"
|
|||
export { componentStore } from "./components"
|
||||
export { uploadStore } from "./uploads.js"
|
||||
export { rowSelectionStore } from "./rowSelection.js"
|
||||
export { blockStore } from "./blocks.js"
|
||||
export { environmentStore } from "./environment"
|
||||
|
||||
// Context stores are layered and duplicated, so it is not a singleton
|
||||
|
|
|
@ -16,7 +16,7 @@ import JSONArrayFetch from "@budibase/frontend-core/src/fetch/JSONArrayFetch.js"
|
|||
*/
|
||||
export const fetchDatasourceSchema = async (
|
||||
datasource,
|
||||
options = { enrichRelationships: false }
|
||||
options = { enrichRelationships: false, formSchema: false }
|
||||
) => {
|
||||
const handler = {
|
||||
table: TableFetch,
|
||||
|
@ -34,7 +34,17 @@ export const fetchDatasourceSchema = async (
|
|||
|
||||
// Get the datasource definition and then schema
|
||||
const definition = await instance.getDefinition(datasource)
|
||||
let schema = instance.getSchema(datasource, definition)
|
||||
|
||||
// Get the normal schema as long as we aren't wanting a form schema
|
||||
let schema
|
||||
if (datasource?.type !== "query" || !options?.formSchema) {
|
||||
schema = instance.getSchema(datasource, definition)
|
||||
} else if (definition.parameters?.length) {
|
||||
schema = {}
|
||||
definition.parameters.forEach(param => {
|
||||
schema[param.name] = { ...param, type: "string" }
|
||||
})
|
||||
}
|
||||
if (!schema) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@budibase/frontend-core",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase frontend core libraries used in builder and client",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
"svelte": "src/index.js",
|
||||
"dependencies": {
|
||||
"@budibase/bbui": "2.0.14-alpha.4",
|
||||
"@budibase/bbui": "2.0.24-alpha.3",
|
||||
"lodash": "^4.17.21",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
|
|
|
@ -14,52 +14,52 @@ import { convertJSONSchemaToTableSchema } from "../utils/json"
|
|||
* For other types of datasource, this class is overridden and extended.
|
||||
*/
|
||||
export default class DataFetch {
|
||||
// API client
|
||||
API = null
|
||||
|
||||
// Feature flags
|
||||
featureStore = writable({
|
||||
supportsSearch: false,
|
||||
supportsSort: false,
|
||||
supportsPagination: false,
|
||||
})
|
||||
|
||||
// Config
|
||||
options = {
|
||||
datasource: null,
|
||||
limit: 10,
|
||||
|
||||
// Search config
|
||||
filter: null,
|
||||
query: null,
|
||||
|
||||
// Sorting config
|
||||
sortColumn: null,
|
||||
sortOrder: "ascending",
|
||||
sortType: null,
|
||||
|
||||
// Pagination config
|
||||
paginate: true,
|
||||
}
|
||||
|
||||
// State of the fetch
|
||||
store = writable({
|
||||
rows: [],
|
||||
info: null,
|
||||
schema: null,
|
||||
loading: false,
|
||||
loaded: false,
|
||||
query: null,
|
||||
pageNumber: 0,
|
||||
cursor: null,
|
||||
cursors: [],
|
||||
})
|
||||
|
||||
/**
|
||||
* Constructs a new DataFetch instance.
|
||||
* @param opts the fetch options
|
||||
*/
|
||||
constructor(opts) {
|
||||
// API client
|
||||
this.API = null
|
||||
|
||||
// Feature flags
|
||||
this.featureStore = writable({
|
||||
supportsSearch: false,
|
||||
supportsSort: false,
|
||||
supportsPagination: false,
|
||||
})
|
||||
|
||||
// Config
|
||||
this.options = {
|
||||
datasource: null,
|
||||
limit: 10,
|
||||
|
||||
// Search config
|
||||
filter: null,
|
||||
query: null,
|
||||
|
||||
// Sorting config
|
||||
sortColumn: null,
|
||||
sortOrder: "ascending",
|
||||
sortType: null,
|
||||
|
||||
// Pagination config
|
||||
paginate: true,
|
||||
}
|
||||
|
||||
// State of the fetch
|
||||
this.store = writable({
|
||||
rows: [],
|
||||
info: null,
|
||||
schema: null,
|
||||
loading: false,
|
||||
loaded: false,
|
||||
query: null,
|
||||
pageNumber: 0,
|
||||
cursor: null,
|
||||
cursors: [],
|
||||
})
|
||||
|
||||
// Merge options with their default values
|
||||
this.API = opts?.API
|
||||
this.options = {
|
||||
|
|
|
@ -121,7 +121,12 @@ export const buildLuceneQuery = filter => {
|
|||
query.allOr = true
|
||||
return
|
||||
}
|
||||
if (type === "datetime" && !isHbs) {
|
||||
if (
|
||||
type === "datetime" &&
|
||||
!isHbs &&
|
||||
operator !== "empty" &&
|
||||
operator !== "notEmpty"
|
||||
) {
|
||||
// Ensure date value is a valid date and parse into correct format
|
||||
if (!value) {
|
||||
return
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/sdk",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase Public API SDK",
|
||||
"author": "Budibase",
|
||||
"license": "MPL-2.0",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/server",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase Web Server",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -77,11 +77,11 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.3",
|
||||
"@budibase/backend-core": "2.0.14-alpha.4",
|
||||
"@budibase/client": "2.0.14-alpha.4",
|
||||
"@budibase/pro": "2.0.14-alpha.4",
|
||||
"@budibase/string-templates": "2.0.14-alpha.4",
|
||||
"@budibase/types": "2.0.14-alpha.4",
|
||||
"@budibase/backend-core": "2.0.24-alpha.3",
|
||||
"@budibase/client": "2.0.24-alpha.3",
|
||||
"@budibase/pro": "2.0.24-alpha.3",
|
||||
"@budibase/string-templates": "2.0.24-alpha.3",
|
||||
"@budibase/types": "2.0.24-alpha.3",
|
||||
"@bull-board/api": "3.7.0",
|
||||
"@bull-board/koa": "3.9.4",
|
||||
"@elastic/elasticsearch": "7.10.0",
|
||||
|
|
|
@ -32,7 +32,7 @@ const {
|
|||
import { USERS_TABLE_SCHEMA } from "../../constants"
|
||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||
import { clientLibraryPath, stringToReadStream } from "../../utilities"
|
||||
import { getAllLocks } from "../../utilities/redis"
|
||||
import { getLocksById } from "../../utilities/redis"
|
||||
import {
|
||||
updateClientLibrary,
|
||||
backupClientLibrary,
|
||||
|
@ -45,11 +45,11 @@ import { cleanupAutomations } from "../../automations/utils"
|
|||
import { context } from "@budibase/backend-core"
|
||||
import { checkAppMetadata } from "../../automations/logging"
|
||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { quotas, groups } from "@budibase/pro"
|
||||
import { errors, events, migrations } from "@budibase/backend-core"
|
||||
import { App, Layout, Screen, MigrationType } from "@budibase/types"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||
import { groups } from "@budibase/pro"
|
||||
import { enrichPluginURLs } from "../../utilities/plugins"
|
||||
|
||||
const URL_REGEX_SLASH = /\/|\\/g
|
||||
|
||||
|
@ -171,16 +171,16 @@ export const fetch = async (ctx: any) => {
|
|||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||
const apps = await getAllApps({ dev, all })
|
||||
|
||||
const appIds = apps
|
||||
.filter((app: any) => app.status === "development")
|
||||
.map((app: any) => app.appId)
|
||||
// get the locks for all the dev apps
|
||||
if (dev || all) {
|
||||
const locks = await getAllLocks()
|
||||
const locks = await getLocksById(appIds)
|
||||
for (let app of apps) {
|
||||
if (app.status !== "development") {
|
||||
continue
|
||||
}
|
||||
const lock = locks.find((lock: any) => lock.appId === app.appId)
|
||||
const lock = locks[app.appId]
|
||||
if (lock) {
|
||||
app.lockedBy = lock.user
|
||||
app.lockedBy = lock
|
||||
} else {
|
||||
// make sure its definitely not present
|
||||
delete app.lockedBy
|
||||
|
@ -208,10 +208,13 @@ export const fetchAppDefinition = async (ctx: any) => {
|
|||
|
||||
export const fetchAppPackage = async (ctx: any) => {
|
||||
const db = context.getAppDB()
|
||||
const application = await db.get(DocumentType.APP_METADATA)
|
||||
let application = await db.get(DocumentType.APP_METADATA)
|
||||
const layouts = await getLayouts()
|
||||
let screens = await getScreens()
|
||||
|
||||
// Enrich plugin URLs
|
||||
application.usedPlugins = enrichPluginURLs(application.usedPlugins)
|
||||
|
||||
// Only filter screens if the user is not a builder
|
||||
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
||||
const userRoleId = getUserRoleId(ctx)
|
||||
|
|
|
@ -68,6 +68,7 @@ exports.buildSchemaFromDb = async function (ctx) {
|
|||
datasource.entities = tables
|
||||
}
|
||||
|
||||
setDefaultDisplayColumns(datasource)
|
||||
const dbResp = await db.put(datasource)
|
||||
datasource._rev = dbResp.rev
|
||||
|
||||
|
@ -78,6 +79,24 @@ exports.buildSchemaFromDb = async function (ctx) {
|
|||
ctx.body = response
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all datasource entities have a display name selected
|
||||
*/
|
||||
const setDefaultDisplayColumns = datasource => {
|
||||
//
|
||||
for (let entity of Object.values(datasource.entities)) {
|
||||
if (entity.primaryDisplay) {
|
||||
continue
|
||||
}
|
||||
const notAutoColumn = Object.values(entity.schema).find(
|
||||
schema => !schema.autocolumn
|
||||
)
|
||||
if (notAutoColumn) {
|
||||
entity.primaryDisplay = notAutoColumn.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for variables that have been updated or removed and invalidate them.
|
||||
*/
|
||||
|
@ -155,6 +174,7 @@ exports.save = async function (ctx) {
|
|||
const { tables, error } = await buildSchemaHelper(datasource)
|
||||
schemaError = error
|
||||
datasource.entities = tables
|
||||
setDefaultDisplayColumns(datasource)
|
||||
}
|
||||
|
||||
const dbResp = await db.put(datasource)
|
||||
|
@ -238,19 +258,6 @@ const buildSchemaHelper = async datasource => {
|
|||
const connector = new Connector(datasource.config)
|
||||
await connector.buildSchema(datasource._id, datasource.entities)
|
||||
|
||||
// make sure they all have a display name selected
|
||||
for (let entity of Object.values(datasource.entities ?? {})) {
|
||||
if (entity.primaryDisplay) {
|
||||
continue
|
||||
}
|
||||
const notAutoColumn = Object.values(entity.schema).find(
|
||||
schema => !schema.autocolumn
|
||||
)
|
||||
if (notAutoColumn) {
|
||||
entity.primaryDisplay = notAutoColumn.name
|
||||
}
|
||||
}
|
||||
|
||||
const errors = connector.schemaErrors
|
||||
let error = null
|
||||
if (errors && Object.keys(errors).length > 0) {
|
||||
|
|
|
@ -103,7 +103,7 @@ exports.revert = async ctx => {
|
|||
target: appId,
|
||||
})
|
||||
try {
|
||||
if (!env.isTest()) {
|
||||
if (!env.isCypress()) {
|
||||
// in-memory db stalls on rollback
|
||||
await replication.rollback()
|
||||
}
|
||||
|
|
|
@ -52,14 +52,19 @@ export async function read(ctx: any, next: any) {
|
|||
}
|
||||
|
||||
export async function update(ctx: any, next: any) {
|
||||
ctx.request.body = await addRev(fixRow(ctx.request.body, ctx.params))
|
||||
const { tableId } = ctx.params
|
||||
ctx.request.body = await addRev(fixRow(ctx.request.body, ctx.params), tableId)
|
||||
await rowController.save(ctx)
|
||||
await next()
|
||||
}
|
||||
|
||||
export async function destroy(ctx: any, next: any) {
|
||||
const { tableId } = ctx.params
|
||||
// set the body as expected, with the _id and _rev fields
|
||||
ctx.request.body = await addRev(fixRow({ _id: ctx.params.rowId }, ctx.params))
|
||||
ctx.request.body = await addRev(
|
||||
fixRow({ _id: ctx.params.rowId }, ctx.params),
|
||||
tableId
|
||||
)
|
||||
await rowController.destroy(ctx)
|
||||
// destroy controller doesn't currently return the row as the body, need to adjust this
|
||||
// in the public API to be correct
|
||||
|
|
|
@ -22,7 +22,7 @@ export async function addRev(
|
|||
}
|
||||
|
||||
/**
|
||||
* Performs a case insensitive search on the provided documents, using the
|
||||
* Performs a case in-sensitive search on the provided documents, using the
|
||||
* provided key and value. This will be a string based search, using the
|
||||
* startsWith function.
|
||||
*/
|
||||
|
|
|
@ -240,6 +240,10 @@ async function execute(
|
|||
const { rows, pagination, extra } = await quotas.addQuery(runFn, {
|
||||
datasourceId: datasource._id,
|
||||
})
|
||||
// remove the raw from execution incase transformer being used to hide data
|
||||
if (extra?.raw) {
|
||||
delete extra.raw
|
||||
}
|
||||
if (opts && opts.rowsOnly) {
|
||||
ctx.body = rows
|
||||
} else {
|
||||
|
|
|
@ -145,7 +145,7 @@ class QueryBuilder {
|
|||
* @param options The preprocess options
|
||||
* @returns {string|*}
|
||||
*/
|
||||
preprocess(value, { escape, lowercase, wrap } = {}) {
|
||||
preprocess(value, { escape, lowercase, wrap, type } = {}) {
|
||||
const hasVersion = !!this.version
|
||||
// Determine if type needs wrapped
|
||||
const originalType = typeof value
|
||||
|
@ -157,8 +157,11 @@ class QueryBuilder {
|
|||
if (escape && originalType === "string") {
|
||||
value = `${value}`.replace(/[ #+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
|
||||
}
|
||||
|
||||
// Wrap in quotes
|
||||
if (hasVersion && wrap) {
|
||||
if (originalType === "string" && !isNaN(value) && !type) {
|
||||
value = `"${value}"`
|
||||
} else if (hasVersion && wrap) {
|
||||
value = originalType === "number" ? value : `"${value}"`
|
||||
}
|
||||
return value
|
||||
|
@ -253,6 +256,7 @@ class QueryBuilder {
|
|||
value = builder.preprocess(value, {
|
||||
escape: true,
|
||||
lowercase: true,
|
||||
type: "string",
|
||||
})
|
||||
return `${key}:${value}*`
|
||||
})
|
||||
|
@ -281,6 +285,7 @@ class QueryBuilder {
|
|||
value = builder.preprocess(value, {
|
||||
escape: true,
|
||||
lowercase: true,
|
||||
type: "fuzzy",
|
||||
})
|
||||
return `${key}:${value}~`
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { enrichPluginURLs } from "../../../utilities/plugins"
|
||||
|
||||
require("svelte/register")
|
||||
|
||||
const send = require("koa-send")
|
||||
|
@ -107,12 +109,13 @@ export const serveApp = async function (ctx: any) {
|
|||
|
||||
if (!env.isJest()) {
|
||||
const App = require("./templates/BudibaseApp.svelte").default
|
||||
const plugins = enrichPluginURLs(appInfo.usedPlugins)
|
||||
const { head, html, css } = App.render({
|
||||
title: appInfo.name,
|
||||
production: env.isProd(),
|
||||
appId,
|
||||
clientLibPath: clientLibraryPath(appId, appInfo.version, ctx),
|
||||
usedPlugins: appInfo.usedPlugins,
|
||||
usedPlugins: plugins,
|
||||
})
|
||||
|
||||
const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
|
||||
|
|
|
@ -88,9 +88,7 @@
|
|||
<!-- But before loadBudibase is called -->
|
||||
{#if usedPlugins?.length}
|
||||
{#each usedPlugins as plugin}
|
||||
<script
|
||||
type="application/javascript"
|
||||
src={`/plugins/${plugin.jsUrl}`}></script>
|
||||
<script type="application/javascript" src={plugin.jsUrl}></script>
|
||||
{/each}
|
||||
{/if}
|
||||
<script type="application/javascript">
|
||||
|
|
|
@ -56,6 +56,16 @@
|
|||
return
|
||||
}
|
||||
|
||||
// If this is a custom event, try and handle it
|
||||
if (parsed.runtimeEvent) {
|
||||
const { name, payload } = parsed
|
||||
if (window.handleBuilderRuntimeEvent) {
|
||||
window.handleBuilderRuntimeEvent(name, payload)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise this is a full reload message
|
||||
// Extract data from message
|
||||
const {
|
||||
selectedComponentId,
|
||||
|
|
|
@ -14,8 +14,10 @@ import {
|
|||
fixAutoColumnSubType,
|
||||
} from "../../../utilities/rowProcessor"
|
||||
import { runStaticFormulaChecks } from "./bulkFormula"
|
||||
import { Table } from "../../../definitions/common"
|
||||
import { Table } from "@budibase/types"
|
||||
import { quotas } from "@budibase/pro"
|
||||
import { isEqual } from "lodash"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
|
||||
function checkAutoColumns(table: Table, oldTable: Table) {
|
||||
if (!table.schema) {
|
||||
|
@ -123,10 +125,16 @@ export async function save(ctx: any) {
|
|||
if (updatedRows && updatedRows.length !== 0) {
|
||||
await db.bulkDocs(updatedRows)
|
||||
}
|
||||
const result = await db.put(tableToSave)
|
||||
let result = await db.put(tableToSave)
|
||||
tableToSave._rev = result.rev
|
||||
const savedTable = cloneDeep(tableToSave)
|
||||
|
||||
tableToSave = await tableSaveFunctions.after(tableToSave)
|
||||
// the table may be updated as part of the table save after functionality - need to write it
|
||||
if (!isEqual(savedTable, tableToSave)) {
|
||||
result = await db.put(tableToSave)
|
||||
tableToSave._rev = result.rev
|
||||
}
|
||||
// has to run after, make sure it has _id
|
||||
await runStaticFormulaChecks(tableToSave, { oldTable, deletion: null })
|
||||
return tableToSave
|
||||
|
|
|
@ -247,7 +247,7 @@ class TableSaveFunctions {
|
|||
// after saving
|
||||
async after(table: any) {
|
||||
table = await handleSearchIndexes(table)
|
||||
await handleDataImport(this.user, table, this.dataImport)
|
||||
table = await handleDataImport(this.user, table, this.dataImport)
|
||||
return table
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
jest.mock("../../../utilities/redis", () => ({
|
||||
init: jest.fn(),
|
||||
getAllLocks: () => {
|
||||
return []
|
||||
getLocksById: () => {
|
||||
return {}
|
||||
},
|
||||
doesUserHaveLock: () => {
|
||||
return true
|
||||
|
|
|
@ -173,4 +173,24 @@ describe("internal search", () => {
|
|||
}, PARAMS)
|
||||
checkLucene(response, `*:* AND NOT column:(a AND b AND c)`, PARAMS)
|
||||
})
|
||||
|
||||
it("test equal without version query", async () => {
|
||||
PARAMS.version = null
|
||||
const response = await search.paginatedSearch({
|
||||
equal: {
|
||||
"column": "1",
|
||||
}
|
||||
}, PARAMS)
|
||||
|
||||
const query = response.rows[0].query
|
||||
const json = JSON.parse(query)
|
||||
if (PARAMS.sort) {
|
||||
expect(json.sort).toBe(`${PARAMS.sort}<${PARAMS.sortType}>`)
|
||||
}
|
||||
if (PARAMS.bookmark) {
|
||||
expect(json.bookmark).toBe(PARAMS.bookmark)
|
||||
}
|
||||
expect(json.include_docs).toBe(true)
|
||||
expect(json.q).toBe(`(*:* AND column:"1") AND tableId:${PARAMS.tableId}`)
|
||||
})
|
||||
})
|
|
@ -252,7 +252,7 @@ class Orchestrator {
|
|||
let loopStepNumber: any = undefined
|
||||
let loopSteps: LoopStep[] | undefined = []
|
||||
let metadata
|
||||
|
||||
let wasLoopStep = false
|
||||
// check if this is a recurring automation,
|
||||
if (isProdAppID(this._appId) && isRecurring(automation)) {
|
||||
metadata = await this.getMetadata()
|
||||
|
@ -267,6 +267,7 @@ class Orchestrator {
|
|||
let input,
|
||||
iterations = 1,
|
||||
iterationCount = 0
|
||||
|
||||
if (step.stepId === LOOP_STEP_ID) {
|
||||
loopStep = step
|
||||
loopStepNumber = stepCount
|
||||
|
@ -277,10 +278,8 @@ class Orchestrator {
|
|||
input = await processObject(loopStep.inputs, this._context)
|
||||
iterations = getLoopIterations(loopStep as LoopStep, input)
|
||||
}
|
||||
|
||||
for (let index = 0; index < iterations; index++) {
|
||||
let originalStepInput = cloneDeep(step.inputs)
|
||||
|
||||
// Handle if the user has set a max iteration count or if it reaches the max limit set by us
|
||||
if (loopStep && input.binding) {
|
||||
let newInput = await processObject(
|
||||
|
@ -313,7 +312,6 @@ class Orchestrator {
|
|||
} else {
|
||||
item = loopStep.inputs.binding
|
||||
}
|
||||
|
||||
this._context.steps[loopStepNumber] = {
|
||||
currentItem: item[index],
|
||||
}
|
||||
|
@ -331,6 +329,16 @@ class Orchestrator {
|
|||
innerValue,
|
||||
`steps.${loopStepNumber}`
|
||||
)
|
||||
} else if (typeof value === "object") {
|
||||
for (let [innerObject, innerValue] of Object.entries(
|
||||
originalStepInput[key][innerKey]
|
||||
)) {
|
||||
originalStepInput[key][innerKey][innerObject] =
|
||||
automationUtils.substituteLoopStep(
|
||||
innerValue,
|
||||
`steps.${loopStepNumber}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -386,6 +394,7 @@ class Orchestrator {
|
|||
let stepFn = await this.getStepFunctionality(step.stepId)
|
||||
let inputs = await processObject(originalStepInput, this._context)
|
||||
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
||||
|
||||
try {
|
||||
// appId is always passed
|
||||
const outputs = await stepFn({
|
||||
|
@ -394,6 +403,7 @@ class Orchestrator {
|
|||
emitter: this._emitter,
|
||||
context: this._context,
|
||||
})
|
||||
|
||||
this._context.steps[stepCount] = outputs
|
||||
// if filter causes us to stop execution don't break the loop, set a var
|
||||
// so that we can finish iterating through the steps and record that it stopped
|
||||
|
@ -419,6 +429,7 @@ class Orchestrator {
|
|||
console.error(`Automation error - ${step.stepId} - ${err}`)
|
||||
return err
|
||||
}
|
||||
|
||||
if (loopStep) {
|
||||
iterationCount++
|
||||
if (index === iterations - 1) {
|
||||
|
@ -429,6 +440,13 @@ class Orchestrator {
|
|||
}
|
||||
}
|
||||
|
||||
// Delete the step after the loop step as it's irrelevant, since information is included
|
||||
// in the loop step
|
||||
if (wasLoopStep) {
|
||||
this._context.steps.splice(loopStepNumber + 1, 1)
|
||||
wasLoopStep = false
|
||||
}
|
||||
|
||||
if (loopSteps && loopSteps.length) {
|
||||
let tempOutput = {
|
||||
success: true,
|
||||
|
@ -441,9 +459,10 @@ class Orchestrator {
|
|||
outputs: tempOutput,
|
||||
inputs: step.inputs,
|
||||
})
|
||||
this._context.steps[loopStepNumber] = tempOutput
|
||||
|
||||
this._context.steps.splice(loopStepNumber, 0, tempOutput)
|
||||
loopSteps = undefined
|
||||
wasLoopStep = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
const env = require("../environment")
|
||||
const { plugins: ProPlugins } = require("@budibase/pro")
|
||||
const { objectStore } = require("@budibase/backend-core")
|
||||
|
||||
exports.enrichPluginURLs = plugins => {
|
||||
if (!plugins || !plugins.length) {
|
||||
return []
|
||||
}
|
||||
return plugins.map(plugin => {
|
||||
const cloud = !env.SELF_HOSTED
|
||||
const bucket = objectStore.ObjectStoreBuckets.PLUGINS
|
||||
const jsFileName = "plugin.min.js"
|
||||
|
||||
// In self host we need to prefix the path, as the bucket name is not part
|
||||
// of the bucket path. In cloud, it's already part of the bucket path.
|
||||
let jsUrl = cloud ? "https://cdn.budi.live/" : `/${bucket}/`
|
||||
jsUrl += ProPlugins.getBucketPath(plugin.name)
|
||||
jsUrl += jsFileName
|
||||
return { ...plugin, jsUrl }
|
||||
})
|
||||
}
|
|
@ -34,12 +34,8 @@ exports.doesUserHaveLock = async (devAppId, user) => {
|
|||
return expected === userId
|
||||
}
|
||||
|
||||
exports.getAllLocks = async () => {
|
||||
const locks = await devAppClient.scan()
|
||||
return locks.map(lock => ({
|
||||
appId: lock.key,
|
||||
user: lock.value,
|
||||
}))
|
||||
exports.getLocksById = async appIds => {
|
||||
return await devAppClient.bulkGet(appIds)
|
||||
}
|
||||
|
||||
exports.updateLock = async (devAppId, user) => {
|
||||
|
|
|
@ -1094,12 +1094,12 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@budibase/backend-core@2.0.14-alpha.4":
|
||||
version "2.0.14-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.14-alpha.4.tgz#213446a9e04385fb38ab8785742cdb8b4eaf2cac"
|
||||
integrity sha512-ma1Ipst4AQVi4sx+ULs3bX9xT4eHqavCvM/BXtZzV23SOTdjJTi9wX2KWbIvAUT5xo2NgN0uRf5zf7B6CBRuXw==
|
||||
"@budibase/backend-core@2.0.24-alpha.3":
|
||||
version "2.0.24-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.24-alpha.3.tgz#49a92082d1ca6bed0eb82519a6c02d7d14ead751"
|
||||
integrity sha512-Zm/ddRDMzMuXCoEXZa0CzA/B1SnpQ+yjZCNAPH7Y4yYTIKfeE/DQ6COLlUixQxVMAUN1L5+GXML+px6fRigA5w==
|
||||
dependencies:
|
||||
"@budibase/types" "2.0.14-alpha.4"
|
||||
"@budibase/types" "2.0.24-alpha.3"
|
||||
"@shopify/jest-koa-mocks" "5.0.1"
|
||||
"@techpass/passport-openidconnect" "0.3.2"
|
||||
aws-sdk "2.1030.0"
|
||||
|
@ -1180,13 +1180,13 @@
|
|||
svelte-flatpickr "^3.2.3"
|
||||
svelte-portal "^1.0.0"
|
||||
|
||||
"@budibase/pro@2.0.14-alpha.4":
|
||||
version "2.0.14-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.14-alpha.4.tgz#9796271365f4ffb875655f86df84a2b4111b793a"
|
||||
integrity sha512-I5QIv04hfoUPEYHPkCD5L/5WU7cJurmfhjy62ZMTNbJzFzhIGDzQlOjM0MnQO5kJvGv1NWl2PQvSty1rYzXGsw==
|
||||
"@budibase/pro@2.0.24-alpha.3":
|
||||
version "2.0.24-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.24-alpha.3.tgz#16605029663b07d0e3bcf0c08893878b542b6752"
|
||||
integrity sha512-gBdqJzvtEAMJkhB2YTAyVYKoCANA25F1wT/K7kQtTOy5LrjDmcXah7CZoetfZQGbsKxUYEAZwJzxLSXmIxvWmQ==
|
||||
dependencies:
|
||||
"@budibase/backend-core" "2.0.14-alpha.4"
|
||||
"@budibase/types" "2.0.14-alpha.4"
|
||||
"@budibase/backend-core" "2.0.24-alpha.3"
|
||||
"@budibase/types" "2.0.24-alpha.3"
|
||||
"@koa/router" "8.0.8"
|
||||
joi "17.6.0"
|
||||
node-fetch "^2.6.1"
|
||||
|
@ -1209,10 +1209,10 @@
|
|||
svelte-apexcharts "^1.0.2"
|
||||
svelte-flatpickr "^3.1.0"
|
||||
|
||||
"@budibase/types@2.0.14-alpha.4":
|
||||
version "2.0.14-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.14-alpha.4.tgz#37b1ff362d2551fd978e8c8153a4cec357eda0b0"
|
||||
integrity sha512-l4dfMh5it1N56nc+jCtdXklrZA1cMt6WSVBoKCXBedZGpguJr18Wo/cfn1YWryuUMuSlpFVDTrlQQXCZ+ku92g==
|
||||
"@budibase/types@2.0.24-alpha.3":
|
||||
version "2.0.24-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.24-alpha.3.tgz#84eec5f991a2cfaf48d968b07a075b343f847289"
|
||||
integrity sha512-f9PhtqzmqPI76ITXttuvxsvqMUJtkrDYf/4MHlI2v5ssNL9r0C/hbQEXllff3L3JqViEHWxkKFmfvfnDTV8rRQ==
|
||||
|
||||
"@bull-board/api@3.7.0":
|
||||
version "3.7.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/string-templates",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Handlebars wrapper for Budibase templating.",
|
||||
"main": "src/index.cjs",
|
||||
"module": "dist/bundle.mjs",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@budibase/types",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase types",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@budibase/worker",
|
||||
"email": "hi@budibase.com",
|
||||
"version": "2.0.14-alpha.4",
|
||||
"version": "2.0.24-alpha.3",
|
||||
"description": "Budibase background service",
|
||||
"main": "src/index.ts",
|
||||
"repository": {
|
||||
|
@ -36,10 +36,10 @@
|
|||
"author": "Budibase",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@budibase/backend-core": "2.0.14-alpha.4",
|
||||
"@budibase/pro": "2.0.14-alpha.4",
|
||||
"@budibase/string-templates": "2.0.14-alpha.4",
|
||||
"@budibase/types": "2.0.14-alpha.4",
|
||||
"@budibase/backend-core": "2.0.24-alpha.3",
|
||||
"@budibase/pro": "2.0.24-alpha.3",
|
||||
"@budibase/string-templates": "2.0.24-alpha.3",
|
||||
"@budibase/types": "2.0.24-alpha.3",
|
||||
"@koa/router": "8.0.8",
|
||||
"@sentry/node": "6.17.7",
|
||||
"@techpass/passport-openidconnect": "0.3.2",
|
||||
|
|
|
@ -291,12 +291,12 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@budibase/backend-core@2.0.14-alpha.4":
|
||||
version "2.0.14-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.14-alpha.4.tgz#213446a9e04385fb38ab8785742cdb8b4eaf2cac"
|
||||
integrity sha512-ma1Ipst4AQVi4sx+ULs3bX9xT4eHqavCvM/BXtZzV23SOTdjJTi9wX2KWbIvAUT5xo2NgN0uRf5zf7B6CBRuXw==
|
||||
"@budibase/backend-core@2.0.24-alpha.3":
|
||||
version "2.0.24-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.0.24-alpha.3.tgz#49a92082d1ca6bed0eb82519a6c02d7d14ead751"
|
||||
integrity sha512-Zm/ddRDMzMuXCoEXZa0CzA/B1SnpQ+yjZCNAPH7Y4yYTIKfeE/DQ6COLlUixQxVMAUN1L5+GXML+px6fRigA5w==
|
||||
dependencies:
|
||||
"@budibase/types" "2.0.14-alpha.4"
|
||||
"@budibase/types" "2.0.24-alpha.3"
|
||||
"@shopify/jest-koa-mocks" "5.0.1"
|
||||
"@techpass/passport-openidconnect" "0.3.2"
|
||||
aws-sdk "2.1030.0"
|
||||
|
@ -327,21 +327,21 @@
|
|||
uuid "8.3.2"
|
||||
zlib "1.0.5"
|
||||
|
||||
"@budibase/pro@2.0.14-alpha.4":
|
||||
version "2.0.14-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.14-alpha.4.tgz#9796271365f4ffb875655f86df84a2b4111b793a"
|
||||
integrity sha512-I5QIv04hfoUPEYHPkCD5L/5WU7cJurmfhjy62ZMTNbJzFzhIGDzQlOjM0MnQO5kJvGv1NWl2PQvSty1rYzXGsw==
|
||||
"@budibase/pro@2.0.24-alpha.3":
|
||||
version "2.0.24-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.0.24-alpha.3.tgz#16605029663b07d0e3bcf0c08893878b542b6752"
|
||||
integrity sha512-gBdqJzvtEAMJkhB2YTAyVYKoCANA25F1wT/K7kQtTOy5LrjDmcXah7CZoetfZQGbsKxUYEAZwJzxLSXmIxvWmQ==
|
||||
dependencies:
|
||||
"@budibase/backend-core" "2.0.14-alpha.4"
|
||||
"@budibase/types" "2.0.14-alpha.4"
|
||||
"@budibase/backend-core" "2.0.24-alpha.3"
|
||||
"@budibase/types" "2.0.24-alpha.3"
|
||||
"@koa/router" "8.0.8"
|
||||
joi "17.6.0"
|
||||
node-fetch "^2.6.1"
|
||||
|
||||
"@budibase/types@2.0.14-alpha.4":
|
||||
version "2.0.14-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.14-alpha.4.tgz#37b1ff362d2551fd978e8c8153a4cec357eda0b0"
|
||||
integrity sha512-l4dfMh5it1N56nc+jCtdXklrZA1cMt6WSVBoKCXBedZGpguJr18Wo/cfn1YWryuUMuSlpFVDTrlQQXCZ+ku92g==
|
||||
"@budibase/types@2.0.24-alpha.3":
|
||||
version "2.0.24-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.0.24-alpha.3.tgz#84eec5f991a2cfaf48d968b07a075b343f847289"
|
||||
integrity sha512-f9PhtqzmqPI76ITXttuvxsvqMUJtkrDYf/4MHlI2v5ssNL9r0C/hbQEXllff3L3JqViEHWxkKFmfvfnDTV8rRQ==
|
||||
|
||||
"@cspotcode/source-map-consumer@0.8.0":
|
||||
version "0.8.0"
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#!/bin/bash
|
||||
if [[ $TARGETARCH == arm* ]] ;
|
||||
then
|
||||
echo "INSTALLING ARM64 MINIO"
|
||||
wget https://dl.min.io/server/minio/release/linux-arm64/minio
|
||||
else
|
||||
echo "INSTALLING AMD64 MINIO"
|
||||
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
||||
fi
|
||||
chmod +x minio
|
||||
|
|
|
@ -18,6 +18,11 @@ git clone https://$PERSONAL_ACCESS_TOKEN@github.com/Budibase/budibase-pro.git
|
|||
if [[ -d "budibase-pro" ]]; then
|
||||
cd budibase-pro
|
||||
|
||||
if [[ -z "${BRANCH}" ]]; then
|
||||
echo Using GITHUB_REF_NAME: $GITHUB_REF_NAME
|
||||
export BRANCH=$GITHUB_REF_NAME
|
||||
fi
|
||||
|
||||
# Try to checkout the matching pro branch
|
||||
git checkout $BRANCH
|
||||
|
||||
|
|
Loading…
Reference in New Issue