diff --git a/.github/workflows/release-singleimage-test.yml b/.github/workflows/release-singleimage-test.yml
deleted file mode 100644
index c3a14226ce..0000000000
--- a/.github/workflows/release-singleimage-test.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: Test
-
-on:
- workflow_dispatch:
-
-env:
- CI: true
- PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- REGISTRY_URL: registry.hub.docker.com
- NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
-jobs:
- build:
- name: "build"
- runs-on: ubuntu-latest
- strategy:
- matrix:
- node-version: [18.x]
- steps:
- - name: "Checkout"
- uses: actions/checkout@v4
- with:
- submodules: true
- token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
-
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
- cache: "yarn"
- - name: Setup QEMU
- uses: docker/setup-qemu-action@v3
- - name: Setup Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@v3
- - name: Run Yarn
- run: yarn
- - name: Run Yarn Build
- run: yarn build --scope @budibase/server --scope @budibase/worker
- - name: Login to Docker Hub
- uses: docker/login-action@v2
- with:
- username: ${{ secrets.DOCKER_USERNAME }}
- password: ${{ secrets.DOCKER_API_KEY }}
- - name: Get the latest release version
- id: version
- run: |
- release_version=$(cat lerna.json | jq -r '.version')
- echo $release_version
- echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
- - name: Tag and release Budibase service docker image
- uses: docker/build-push-action@v5
- with:
- context: .
- push: true
- pull: true
- platforms: linux/amd64,linux/arm64
- build-args: BUDIBASE_VERSION=0.0.0+test
- tags: budibase/budibase-test:test
- file: ./hosting/single/Dockerfile.v2
- cache-from: type=registry,ref=budibase/budibase-test:test
- cache-to: type=inline
- - name: Tag and release Budibase Azure App Service docker image
- uses: docker/build-push-action@v2
- with:
- context: .
- push: true
- platforms: linux/amd64
- build-args: |
- TARGETBUILD=aas
- BUDIBASE_VERSION=0.0.0+test
- tags: budibase/budibase-test:aas
- file: ./hosting/single/Dockerfile.v2
diff --git a/hosting/proxy/nginx.prod.conf b/hosting/proxy/nginx.prod.conf
index 365765ccbb..6da2e4a1c3 100644
--- a/hosting/proxy/nginx.prod.conf
+++ b/hosting/proxy/nginx.prod.conf
@@ -51,7 +51,7 @@ http {
proxy_buffering off;
set $csp_default "default-src 'self'";
- set $csp_script "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.budibase.net https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io";
+ set $csp_script "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.budibase.net https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io https://d2l5prqdbvm3op.cloudfront.net";
set $csp_style "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me https://maxcdn.bootstrapcdn.com";
set $csp_object "object-src 'none'";
set $csp_base_uri "base-uri 'self'";
diff --git a/lerna.json b/lerna.json
index 7d14875c97..384473120b 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.11.44",
+ "version": "2.11.45",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/package.json b/package.json
index 100a306a35..d3f4903e6c 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,6 @@
"build:sdk": "lerna run --stream build:sdk",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish from-package --yes --force-publish --no-git-tag-version --no-push --no-git-reset",
- "release:develop": "yarn release --dist-tag develop",
"restore": "yarn run clean && yarn && yarn run build",
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
"nuke:packages": "yarn run restore",
diff --git a/packages/builder/src/builderStore/componentUtils.js b/packages/builder/src/builderStore/componentUtils.js
index 16b972058e..522dbae416 100644
--- a/packages/builder/src/builderStore/componentUtils.js
+++ b/packages/builder/src/builderStore/componentUtils.js
@@ -5,6 +5,7 @@ import {
encodeJSBinding,
findHBSBlocks,
} from "@budibase/string-templates"
+import { capitalise } from "helpers"
/**
* Recursively searches for a specific component ID
@@ -235,3 +236,13 @@ export const makeComponentUnique = component => {
// Recurse on all children
return JSON.parse(definition)
}
+
+export const getComponentText = component => {
+ if (component?._instanceName) {
+ return component._instanceName
+ }
+ const type =
+ component._component.replace("@budibase/standard-components/", "") ||
+ "component"
+ return capitalise(type)
+}
diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js
index b17bd99e10..59bcd0d5e8 100644
--- a/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js
+++ b/packages/builder/src/builderStore/store/screenTemplates/rowListScreen.js
@@ -2,14 +2,14 @@ import sanitizeUrl from "./utils/sanitizeUrl"
import { Screen } from "./utils/Screen"
import { Component } from "./utils/Component"
-export default function (datasources) {
+export default function (datasources, mode = "table") {
if (!Array.isArray(datasources)) {
return []
}
return datasources.map(datasource => {
return {
name: `${datasource.label} - List`,
- create: () => createScreen(datasource),
+ create: () => createScreen(datasource, mode),
id: ROW_LIST_TEMPLATE,
resourceId: datasource.resourceId,
}
@@ -40,10 +40,24 @@ const generateTableBlock = datasource => {
return tableBlock
}
-const createScreen = datasource => {
+const generateGridBlock = datasource => {
+ const gridBlock = new Component("@budibase/standard-components/gridblock")
+ gridBlock
+ .customProps({
+ table: datasource,
+ })
+ .instanceName(`${datasource.label} - Grid block`)
+ return gridBlock
+}
+
+const createScreen = (datasource, mode) => {
return new Screen()
.route(rowListUrl(datasource))
.instanceName(`${datasource.label} - List`)
- .addChild(generateTableBlock(datasource))
+ .addChild(
+ mode === "table"
+ ? generateTableBlock(datasource)
+ : generateGridBlock(datasource)
+ )
.json()
}
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 7b51e6c839..467ae413c3 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -777,7 +777,8 @@
disabled={deleteColName !== originalName}
>
- Are you sure you wish to delete the column {originalName}?
+ Are you sure you wish to delete the column
+ (deleteColName = originalName)}>{originalName}?
Your data will be deleted and this action cannot be undone - enter the column
name to confirm.
@@ -810,4 +811,11 @@
gap: 8px;
display: flex;
}
+ b {
+ transition: color 130ms ease-out;
+ }
+ b:hover {
+ cursor: pointer;
+ color: var(--spectrum-global-color-gray-900);
+ }
diff --git a/packages/builder/src/components/design/Panel.svelte b/packages/builder/src/components/design/Panel.svelte
index 91ea3f98ad..3d5938c174 100644
--- a/packages/builder/src/components/design/Panel.svelte
+++ b/packages/builder/src/components/design/Panel.svelte
@@ -16,6 +16,7 @@
export let closeButtonIcon = "Close"
$: customHeaderContent = $$slots["panel-header-content"]
+ $: customTitleContent = $$slots["panel-title-content"]
{/if}
- {title}
+ {#if customTitleContent}
+
+ {:else}
+ {title || ""}
+ {/if}
{#if showAddButton}
@@ -134,4 +139,7 @@
.custom-content-wrap {
border-bottom: var(--border-light);
}
+ .title {
+ display: flex;
+ }
diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js
index 4c49587372..232b4bef31 100644
--- a/packages/builder/src/components/design/settings/componentSettings.js
+++ b/packages/builder/src/components/design/settings/componentSettings.js
@@ -23,6 +23,7 @@ import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte"
import BarButtonList from "./controls/BarButtonList.svelte"
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
+import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte"
import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte"
const componentMap = {
@@ -48,6 +49,7 @@ const componentMap = {
"filter/relationship": RelationshipFilterEditor,
url: URLSelect,
fieldConfiguration: FieldConfiguration,
+ buttonConfiguration: ButtonConfiguration,
columns: ColumnEditor,
"columns/basic": BasicColumnEditor,
"columns/grid": GridColumnEditor,
diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte
new file mode 100644
index 0000000000..324418511b
--- /dev/null
+++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonConfiguration.svelte
@@ -0,0 +1,134 @@
+
+
+
+ {#if buttonCount}
+ 1}
+ />
+
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte
new file mode 100644
index 0000000000..a05fd9a39b
--- /dev/null
+++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+
+
{readableText || "Button"}
+
+
+ removeButton(item._id)}
+ />
+
+
+
+
diff --git a/packages/builder/src/components/design/settings/controls/DraggableList.svelte b/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte
similarity index 82%
rename from packages/builder/src/components/design/settings/controls/DraggableList.svelte
rename to packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte
index c8395b2a1f..1992299e90 100644
--- a/packages/builder/src/components/design/settings/controls/DraggableList.svelte
+++ b/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte
@@ -1,10 +1,10 @@
@@ -79,11 +96,11 @@
bind:this={popover}
on:open={() => {
drawers = []
- $draggable.actions.select(field._id)
+ $draggable.actions.select(componentInstance._id)
}}
on:close={() => {
open = false
- if ($draggable.selected == field._id) {
+ if ($draggable.selected == componentInstance._id) {
$draggable.actions.select()
}
}}
@@ -92,33 +109,13 @@
showPopover={drawers.length == 0}
clickOutsideOverride={drawers.length > 0}
maxHeight={600}
- handlePostionUpdate={(anchorBounds, eleBounds, cfg) => {
- let { left, top } = cfg
- let percentageOffset = 30
- // left-outside
- left = anchorBounds.left - eleBounds.width - 18
-
- // shift up from the anchor, if space allows
- let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset
- let defaultTop = anchorBounds.top - offsetPos
-
- if (window.innerHeight - defaultTop < eleBounds.height) {
- top = window.innerHeight - eleBounds.height - 5
- } else {
- top = anchorBounds.top - offsetPos
- }
-
- return { ...cfg, left, top }
- }}
+ handlePostionUpdate={customPositionHandler}
>
-
-
- {field.field}
-
+
diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte
index 4169cb7d3d..6c74705ab0 100644
--- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte
+++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte
@@ -7,7 +7,7 @@
getComponentBindableProperties,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
- import DraggableList from "../DraggableList.svelte"
+ import DraggableList from "../DraggableList/DraggableList.svelte"
import { createEventDispatcher } from "svelte"
import { store, selectedScreen } from "builderStore"
import FieldSetting from "./FieldSetting.svelte"
@@ -50,7 +50,7 @@
updateSanitsedFields(sanitisedValue)
unconfigured = buildUnconfiguredOptions(schema, sanitisedFields)
fieldList = [...sanitisedFields, ...unconfigured]
- .map(buildSudoInstance)
+ .map(buildPseudoInstance)
.filter(x => x != null)
}
@@ -104,7 +104,7 @@
})
}
- const buildSudoInstance = instance => {
+ const buildPseudoInstance = instance => {
if (instance._component) {
return instance
}
diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte
index b5cfcb12d9..1d9ce733b8 100644
--- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte
+++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte
@@ -1,8 +1,11 @@
-
-
{item.label || item.field}
+ >
+
+
+ {item.field}
+
+
+
{readableText}
@@ -53,4 +81,20 @@
.list-item-body {
justify-content: space-between;
}
+ .type-icon {
+ display: flex;
+ gap: var(--spacing-m);
+ margin: var(--spacing-xl);
+ margin-bottom: 0px;
+ height: var(--spectrum-alias-item-height-m);
+ padding: 0px var(--spectrum-alias-item-padding-m);
+ border-width: var(--spectrum-actionbutton-border-size);
+ border-radius: var(--spectrum-alias-border-radius-regular);
+ border: 1px solid
+ var(
+ --spectrum-actionbutton-m-border-color,
+ var(--spectrum-alias-border-color)
+ );
+ align-items: center;
+ }
diff --git a/packages/builder/src/components/integration/RestQueryViewer.svelte b/packages/builder/src/components/integration/RestQueryViewer.svelte
index 254f65fcaf..e6913b0953 100644
--- a/packages/builder/src/components/integration/RestQueryViewer.svelte
+++ b/packages/builder/src/components/integration/RestQueryViewer.svelte
@@ -196,8 +196,36 @@
}
}
+ const validateQuery = async () => {
+ const forbiddenBindings = /{{\s?user(\.(\w|\$)*\s?|\s?)}}/g
+ const bindingError = new Error(
+ "'user' is a protected binding and cannot be used"
+ )
+
+ if (forbiddenBindings.test(url)) {
+ throw bindingError
+ }
+
+ if (forbiddenBindings.test(query.fields.requestBody ?? "")) {
+ throw bindingError
+ }
+
+ Object.values(requestBindings).forEach(bindingValue => {
+ if (forbiddenBindings.test(bindingValue)) {
+ throw bindingError
+ }
+ })
+
+ Object.values(query.fields.headers).forEach(headerValue => {
+ if (forbiddenBindings.test(headerValue)) {
+ throw bindingError
+ }
+ })
+ }
+
async function runQuery() {
try {
+ await validateQuery()
response = await queries.preview(buildQuery())
if (response.rows.length === 0) {
notifications.info("Request did not return any data")
diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte
index 414722a177..a68a782bed 100644
--- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte
@@ -53,7 +53,8 @@
}
.alert-wrap {
display: flex;
- width: 100%;
+ flex: 0 0 auto;
+ margin: -28px -40px 14px -40px;
}
.alert-wrap :global(> *) {
flex: 1;
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte
index afcada4138..affa115ca2 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsPanel.svelte
@@ -1,10 +1,12 @@
+
+
+
{#if styles?.length > 0}
{#each styles as style}
{
- if (component._instanceName) {
- return component._instanceName
- }
- const type =
- component._component.replace("@budibase/standard-components/", "") ||
- "component"
- return capitalise(type)
- }
-
const getComponentIcon = component => {
const def = store.actions.components.getDefinition(component?._component)
return def?.icon
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte
index 9a96242b30..92ed3dcfc7 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte
@@ -12,6 +12,7 @@
import { capitalise } from "helpers"
import { goto } from "@roxi/routify"
+ let mode
let pendingScreen
// Modal refs
@@ -100,14 +101,15 @@
}
// Handler for NewScreenModal
- export const show = mode => {
+ export const show = newMode => {
+ mode = newMode
selectedTemplates = null
blankScreenUrl = null
screenMode = mode
pendingScreen = null
screenAccessRole = Roles.BASIC
- if (mode === "table") {
+ if (mode === "table" || mode === "grid") {
datasourceModal.show()
} else if (mode === "blank") {
let templates = getTemplates($tables.list)
@@ -123,6 +125,7 @@
// Handler for DatasourceModal confirmation, move to screen access select
const confirmScreenDatasources = async ({ templates }) => {
+ console.log(templates)
selectedTemplates = templates
screenAccessRoleModal.show()
}
@@ -177,6 +180,7 @@
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte
index a866cd23d4..731c60a406 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/DatasourceModal.svelte
@@ -7,6 +7,7 @@
import rowListScreen from "builderStore/store/screenTemplates/rowListScreen"
import DatasourceTemplateRow from "./DatasourceTemplateRow.svelte"
+ export let mode
export let onCancel
export let onConfirm
export let initialScreens = []
@@ -24,7 +25,10 @@
screen => screen.resourceId !== resourceId
)
} else {
- selectedScreens = [...selectedScreens, rowListScreen([datasource])[0]]
+ selectedScreens = [
+ ...selectedScreens,
+ rowListScreen([datasource], mode)[0],
+ ]
}
}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png
new file mode 100644
index 0000000000..c3efa30a67
Binary files /dev/null and b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/grid.png differ
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte
index b504940ca7..6b080747b0 100644
--- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte
@@ -3,6 +3,7 @@
import CreationPage from "components/common/CreationPage.svelte"
import blankImage from "./blank.png"
import tableImage from "./table.png"
+ import gridImage from "./grid.png"
import CreateScreenModal from "./CreateScreenModal.svelte"
import { store } from "builderStore"
@@ -43,6 +44,16 @@
View, edit and delete rows on a table
+
+ createScreenModal.show("grid")}>
+
+

+
+
+ Grid
+ View and manipulate rows on a grid
+
+
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index 7094ce88e9..c8ef406472 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -258,6 +258,186 @@
"description": "Contains your app screens",
"static": true
},
+ "buttongroup": {
+ "name": "Button group",
+ "icon": "Button",
+ "hasChildren": false,
+ "settings": [
+ {
+ "section": true,
+ "name": "Buttons",
+ "settings": [
+ {
+ "type": "buttonConfiguration",
+ "key": "buttons",
+ "nested": true,
+ "defaultValue": [
+ {
+ "type": "cta",
+ "text": "Button 1"
+ },
+ {
+ "type": "primary",
+ "text": "Button 2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "section": true,
+ "name": "Layout",
+ "settings": [
+ {
+ "type": "select",
+ "label": "Direction",
+ "key": "direction",
+ "showInBar": true,
+ "barStyle": "buttons",
+ "options": [
+ {
+ "label": "Column",
+ "value": "column",
+ "barIcon": "ViewColumn",
+ "barTitle": "Column layout"
+ },
+ {
+ "label": "Row",
+ "value": "row",
+ "barIcon": "ViewRow",
+ "barTitle": "Row layout"
+ }
+ ],
+ "defaultValue": "row"
+ },
+ {
+ "type": "select",
+ "label": "Horiz. align",
+ "key": "hAlign",
+ "showInBar": true,
+ "barStyle": "buttons",
+ "options": [
+ {
+ "label": "Left",
+ "value": "left",
+ "barIcon": "AlignLeft",
+ "barTitle": "Align left"
+ },
+ {
+ "label": "Center",
+ "value": "center",
+ "barIcon": "AlignCenter",
+ "barTitle": "Align center"
+ },
+ {
+ "label": "Right",
+ "value": "right",
+ "barIcon": "AlignRight",
+ "barTitle": "Align right"
+ },
+ {
+ "label": "Stretch",
+ "value": "stretch",
+ "barIcon": "MoveLeftRight",
+ "barTitle": "Align stretched horizontally"
+ }
+ ],
+ "defaultValue": "left"
+ },
+ {
+ "type": "select",
+ "label": "Vert. align",
+ "key": "vAlign",
+ "showInBar": true,
+ "barStyle": "buttons",
+ "options": [
+ {
+ "label": "Top",
+ "value": "top",
+ "barIcon": "AlignTop",
+ "barTitle": "Align top"
+ },
+ {
+ "label": "Middle",
+ "value": "middle",
+ "barIcon": "AlignMiddle",
+ "barTitle": "Align middle"
+ },
+ {
+ "label": "Bottom",
+ "value": "bottom",
+ "barIcon": "AlignBottom",
+ "barTitle": "Align bottom"
+ },
+ {
+ "label": "Stretch",
+ "value": "stretch",
+ "barIcon": "MoveUpDown",
+ "barTitle": "Align stretched vertically"
+ }
+ ],
+ "defaultValue": "top"
+ },
+ {
+ "type": "select",
+ "label": "Size",
+ "key": "size",
+ "showInBar": true,
+ "barStyle": "buttons",
+ "options": [
+ {
+ "label": "Shrink",
+ "value": "shrink",
+ "barIcon": "Minimize",
+ "barTitle": "Shrink container"
+ },
+ {
+ "label": "Grow",
+ "value": "grow",
+ "barIcon": "Maximize",
+ "barTitle": "Grow container"
+ }
+ ],
+ "defaultValue": "shrink"
+ },
+ {
+ "type": "select",
+ "label": "Gap",
+ "key": "gap",
+ "showInBar": true,
+ "barStyle": "picker",
+ "options": [
+ {
+ "label": "None",
+ "value": "N"
+ },
+ {
+ "label": "Small",
+ "value": "S"
+ },
+ {
+ "label": "Medium",
+ "value": "M"
+ },
+ {
+ "label": "Large",
+ "value": "L"
+ }
+ ],
+ "defaultValue": "M"
+ },
+ {
+ "type": "boolean",
+ "label": "Wrap",
+ "key": "wrap",
+ "showInBar": true,
+ "barIcon": "ModernGridView",
+ "barTitle": "Wrap"
+ }
+ ]
+ }
+ ]
+ },
"button": {
"name": "Button",
"description": "A basic html button that is ready for styling",
@@ -2409,7 +2589,6 @@
"key": "disabled",
"defaultValue": false
},
-
{
"type": "text",
"label": "Initial form step",
@@ -5391,38 +5570,6 @@
"section": true,
"name": "Fields",
"settings": [
- {
- "type": "select",
- "label": "Align labels",
- "key": "labelPosition",
- "defaultValue": "left",
- "options": [
- {
- "label": "Left",
- "value": "left"
- },
- {
- "label": "Above",
- "value": "above"
- }
- ]
- },
- {
- "type": "select",
- "label": "Size",
- "key": "size",
- "options": [
- {
- "label": "Medium",
- "value": "spectrum--medium"
- },
- {
- "label": "Large",
- "value": "spectrum--large"
- }
- ],
- "defaultValue": "spectrum--medium"
- },
{
"type": "fieldConfiguration",
"key": "fields",
@@ -5442,6 +5589,40 @@
}
}
]
+ },
+ {
+ "tag": "style",
+ "type": "select",
+ "label": "Align labels",
+ "key": "labelPosition",
+ "defaultValue": "left",
+ "options": [
+ {
+ "label": "Left",
+ "value": "left"
+ },
+ {
+ "label": "Above",
+ "value": "above"
+ }
+ ]
+ },
+ {
+ "tag": "style",
+ "type": "select",
+ "label": "Size",
+ "key": "size",
+ "options": [
+ {
+ "label": "Medium",
+ "value": "spectrum--medium"
+ },
+ {
+ "label": "Large",
+ "value": "spectrum--large"
+ }
+ ],
+ "defaultValue": "spectrum--medium"
}
],
"context": [
@@ -5743,4 +5924,4 @@
}
]
}
-}
+}
\ No newline at end of file
diff --git a/packages/client/src/components/app/ButtonGroup.svelte b/packages/client/src/components/app/ButtonGroup.svelte
new file mode 100644
index 0000000000..87b0990701
--- /dev/null
+++ b/packages/client/src/components/app/ButtonGroup.svelte
@@ -0,0 +1,37 @@
+
+
+
+
+ {#each buttons as { text, type, quiet, disabled, onClick, size }}
+
+ {/each}
+
+
diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js
index 060c15a857..97df3741e1 100644
--- a/packages/client/src/components/app/index.js
+++ b/packages/client/src/components/app/index.js
@@ -19,6 +19,7 @@ export { default as dataprovider } from "./DataProvider.svelte"
export { default as divider } from "./Divider.svelte"
export { default as screenslot } from "./ScreenSlot.svelte"
export { default as button } from "./Button.svelte"
+export { default as buttongroup } from "./ButtonGroup.svelte"
export { default as repeater } from "./Repeater.svelte"
export { default as text } from "./Text.svelte"
export { default as layout } from "./Layout.svelte"
diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte
index f9cdef3756..cdaf28978a 100644
--- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte
@@ -21,6 +21,7 @@
export let invertX = false
export let invertY = false
export let contentLines = 1
+ export let hidden = false
const emptyError = writable(null)
@@ -78,6 +79,7 @@
{focused}
{selectedUser}
{readonly}
+ {hidden}
error={$error}
on:click={() => focusedCellId.set(cellId)}
on:contextmenu={e => menu.actions.open(cellId, e)}
diff --git a/packages/frontend-core/src/components/grid/cells/GridCell.svelte b/packages/frontend-core/src/components/grid/cells/GridCell.svelte
index fe4bd70ba4..dcc76b9c75 100644
--- a/packages/frontend-core/src/components/grid/cells/GridCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/GridCell.svelte
@@ -10,6 +10,7 @@
export let defaultHeight = false
export let center = false
export let readonly = false
+ export let hidden = false
$: style = getStyle(width, selectedUser)
@@ -30,6 +31,7 @@
class:error
class:center
class:readonly
+ class:hidden
class:default-height={defaultHeight}
class:selected-other={selectedUser != null}
class:alt={rowIdx % 2 === 1}
@@ -81,6 +83,9 @@
.cell.center {
align-items: center;
}
+ .cell.hidden {
+ content-visibility: hidden;
+ }
/* Cell border */
.cell.focused:after,
diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
index d6cbcb582d..38dfd0f9eb 100644
--- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
+++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte
@@ -4,6 +4,8 @@
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
import GridCell from "./GridCell.svelte"
import { getColumnIcon } from "../lib/utils"
+ import { debounce } from "../../../utils/utils"
+ import { FieldType, FormulaTypes } from "@budibase/types"
export let column
export let idx
@@ -15,7 +17,7 @@
isResizing,
rand,
sort,
- renderedColumns,
+ visibleColumns,
dispatch,
subscribe,
config,
@@ -24,23 +26,69 @@
definition,
datasource,
schema,
+ focusedCellId,
+ filter,
+ inlineFilters,
} = getContext("grid")
+ const searchableTypes = [
+ FieldType.STRING,
+ FieldType.OPTIONS,
+ FieldType.NUMBER,
+ FieldType.BIGINT,
+ FieldType.ARRAY,
+ FieldType.LONGFORM,
+ ]
+
let anchor
let open = false
let editIsOpen = false
let timeout
let popover
+ let searchValue
+ let input
$: sortedBy = column.name === $sort.column
$: canMoveLeft = orderable && idx > 0
- $: canMoveRight = orderable && idx < $renderedColumns.length - 1
- $: ascendingLabel = ["number", "bigint"].includes(column.schema?.type)
- ? "low-high"
- : "A-Z"
- $: descendingLabel = ["number", "bigint"].includes(column.schema?.type)
- ? "high-low"
- : "Z-A"
+ $: canMoveRight = orderable && idx < $visibleColumns.length - 1
+ $: sortingLabels = getSortingLabels(column.schema?.type)
+ $: searchable = isColumnSearchable(column)
+ $: resetSearchValue(column.name)
+ $: searching = searchValue != null
+ $: debouncedUpdateFilter(searchValue)
+
+ const getSortingLabels = type => {
+ switch (type) {
+ case FieldType.NUMBER:
+ case FieldType.BIGINT:
+ return {
+ ascending: "low-high",
+ descending: "high-low",
+ }
+ case FieldType.DATETIME:
+ return {
+ ascending: "old-new",
+ descending: "new-old",
+ }
+ default:
+ return {
+ ascending: "A-Z",
+ descending: "Z-A",
+ }
+ }
+ }
+
+ const resetSearchValue = name => {
+ searchValue = $inlineFilters?.find(x => x.id === `inline-${name}`)?.value
+ }
+
+ const isColumnSearchable = col => {
+ const { type, formulaType } = col.schema
+ return (
+ searchableTypes.includes(type) ||
+ (type === FieldType.FORMULA && formulaType === FormulaTypes.STATIC)
+ )
+ }
const editColumn = async () => {
editIsOpen = true
@@ -141,12 +189,46 @@
})
}
+ const startSearching = async () => {
+ $focusedCellId = null
+ searchValue = ""
+ await tick()
+ input?.focus()
+ }
+
+ const onInputKeyDown = e => {
+ if (e.key === "Enter") {
+ updateFilter()
+ } else if (e.key === "Escape") {
+ input?.blur()
+ }
+ }
+
+ const stopSearching = () => {
+ searchValue = null
+ updateFilter()
+ }
+
+ const onBlurInput = () => {
+ if (searchValue === "") {
+ searchValue = null
+ }
+ updateFilter()
+ }
+
+ const updateFilter = () => {
+ filter.actions.addInlineFilter(column, searchValue)
+ }
+ const debouncedUpdateFilter = debounce(updateFilter, 250)
+
onMount(() => subscribe("close-edit-column", cancelEdit))