diff --git a/.prettierignore b/.prettierignore index b1ee287391..b0f9f8cdbf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,4 +9,5 @@ packages/backend-core/coverage packages/builder/.routify packages/sdk/sdk packages/pro/coverage -**/*.ivm.bundle.js \ No newline at end of file +**/*.ivm.bundle.js +!**/bson-polyfills.ivm.bundle.js \ No newline at end of file diff --git a/README.md b/README.md index 26ad9f80c2..552a849581 100644 --- a/README.md +++ b/README.md @@ -54,17 +54,21 @@

+ ## ✨ Features -### Build and ship real software +### Build and ship real software + Unlike other platforms, with Budibase you build and ship single page applications. Budibase applications have performance baked in and can be designed responsively, providing users with a great experience.

### Open source and extensible + Budibase is open-source - licensed as GPL v3. This should fill you with confidence that Budibase will always be around. You can also code against Budibase or fork it and make changes as you please, providing a developer-friendly experience.

### Load data or start from scratch + Budibase pulls data from multiple sources, including MongoDB, CouchDB, PostgreSQL, MariaDB, MySQL, Airtable, S3, DynamoDB, or a REST API. And unlike other platforms, with Budibase you can start from scratch and create business apps with no data sources. [Request new datasources](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).

@@ -82,10 +86,12 @@ Budibase comes out of the box with beautifully designed, powerful components whi

### Automate processes, integrate with other tools and connect to webhooks + Save time by automating manual processes and workflows. From connecting to webhooks to automating emails, simply tell Budibase what to do and let it work for you. You can easily [create new automations for Budibase here](https://github.com/Budibase/automations) or [Request new automation](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas).

### Integrate with your favorite tools + Budibase integrates with a number of popular tools allowing you to build apps that perfectly fit your stack.

@@ -94,6 +100,7 @@ Budibase integrates with a number of popular tools allowing you to build apps th

### Deploy with confidence and security + Budibase is made to scale. With Budibase, you can self-host on your own infrastructure and globally manage users, onboarding, SMTP, apps, groups, theming and more. You can also provide users/groups with an app portal and disseminate user management to the group manager. - Checkout the promo video: https://youtu.be/xoljVpty_Kw @@ -104,15 +111,15 @@ Budibase is made to scale. With Budibase, you can self-host on your own infrastr
- ## Budibase Public API + As with anything that we build in Budibase, our new public API is simple to use, flexible, and introduces new extensibility. To summarize, the Budibase API enables: - Budibase as a backend - Interoperability - #### Docs + You can learn more about the Budibase API at the following places: - [General documentation](https://docs.budibase.com/docs/public-api): Learn how to get your API key, how to use spec, and how to use Postman @@ -132,10 +139,8 @@ Deploy Budibase using Docker, Kubernetes, and Digital Ocean on your existing inf - [Digital Ocean](https://docs.budibase.com/docs/digitalocean) - [Portainer](https://docs.budibase.com/docs/portainer) - ### [Get started with Budibase Cloud](https://budibase.com) -

## 🎓 Learning Budibase @@ -143,7 +148,6 @@ Deploy Budibase using Docker, Kubernetes, and Digital Ocean on your existing inf The Budibase documentation [lives here](https://docs.budibase.com/docs).
-

## 💬 Community @@ -152,25 +156,24 @@ If you have a question or would like to talk with other Budibase users and join


- ## ❗ Code of conduct Budibase is dedicated to providing everyone a welcoming, diverse, and harassment-free experience. We expect everyone in the Budibase community to abide by our [**Code of Conduct**](https://github.com/Budibase/budibase/blob/HEAD/docs/CODE_OF_CONDUCT.md). Please read it.
-

- ## 🙌 Contributing to Budibase From opening a bug report to creating a pull request: every contribution is appreciated and welcomed. If you're planning to implement a new feature or change the API, please create an issue first. This way, we can ensure your work is not in vain. Environment setup instructions are available [here](https://github.com/Budibase/budibase/tree/HEAD/docs/CONTRIBUTING.md). ### Not Sure Where to Start? -A good place to start contributing is the [First time issues project](https://github.com/Budibase/budibase/projects/22). + +A good place to start contributing is by looking for the [good first issue](https://github.com/Budibase/budibase/labels/good%20first%20issue) tag. ### How the repository is organized + Budibase is a monorepo managed by lerna. Lerna manages the building and publishing of the budibase packages. At a high level, here are the packages that make up Budibase. - [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contains code for the budibase builder client-side svelte application. @@ -183,7 +186,6 @@ For more information, see [CONTRIBUTING.md](https://github.com/Budibase/budibase

- ## 📝 License Budibase is open-source, licensed as [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). The client and component libraries are licensed as [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - so the apps you build can be licensed however you like. @@ -202,7 +204,6 @@ If you are having issues between updates of the builder, please use the guide [h Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - diff --git a/eslint-local-rules/index.js b/eslint-local-rules/index.js index 9348706399..d9d894c33e 100644 --- a/eslint-local-rules/index.js +++ b/eslint-local-rules/index.js @@ -41,12 +41,11 @@ module.exports = { if ( /^@budibase\/[^/]+\/.*$/.test(importPath) && importPath !== "@budibase/backend-core/tests" && - importPath !== "@budibase/string-templates/test/utils" && - importPath !== "@budibase/client/manifest.json" + importPath !== "@budibase/string-templates/test/utils" ) { context.report({ node, - message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests, @budibase/string-templates/test/utils and @budibase/client/manifest.json.`, + message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests and @budibase/string-templates/test/utils.`, }) } }, diff --git a/lerna.json b/lerna.json index 8d7460f053..730d145ced 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.3.3", + "version": "3.4.4", "npmClient": "yarn", "concurrency": 20, "command": { diff --git a/packages/backend-core/src/docIds/params.ts b/packages/backend-core/src/docIds/params.ts index 016604b69b..5f1c053bde 100644 --- a/packages/backend-core/src/docIds/params.ts +++ b/packages/backend-core/src/docIds/params.ts @@ -83,11 +83,15 @@ export function isViewId(id: string): boolean { /** * Check if a given ID is that of a datasource or datasource plus. */ -export const isDatasourceId = (id: string): boolean => { +export function isDatasourceId(id: string): boolean { // this covers both datasources and datasource plus return !!id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`) } +export function isQueryId(id: string): boolean { + return !!id && id.startsWith(`${DocumentType.QUERY}${SEPARATOR}`) +} + /** * Gets parameters for retrieving workspaces. */ diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index dd8d3daa37..6833c9a306 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -1,45 +1,58 @@ import events from "events" import { newid } from "../utils" import { Queue, QueueOptions, JobOptions } from "./queue" +import { helpers } from "@budibase/shared-core" +import { Job, JobId, JobInformation } from "bull" -interface JobMessage { +function jobToJobInformation(job: Job): JobInformation { + let cron = "" + let every = -1 + let tz: string | undefined = undefined + let endDate: number | undefined = undefined + + const repeat = job.opts?.repeat + if (repeat) { + endDate = repeat.endDate ? new Date(repeat.endDate).getTime() : Date.now() + tz = repeat.tz + if ("cron" in repeat) { + cron = repeat.cron + } else { + every = repeat.every + } + } + + return { + id: job.id.toString(), + name: "", + key: job.id.toString(), + tz, + endDate, + cron, + every, + next: 0, + } +} + +interface JobMessage extends Partial> { id: string timestamp: number - queue: string + queue: Queue data: any opts?: JobOptions } /** - * Bull works with a Job wrapper around all messages that contains a lot more information about - * the state of the message, this object constructor implements the same schema of Bull jobs - * for the sake of maintaining API consistency. - * @param queue The name of the queue which the message will be carried on. - * @param message The JSON message which will be passed back to the consumer. - * @returns A new job which can now be put onto the queue, this is mostly an - * internal structure so that an in memory queue can be easily swapped for a Bull queue. - */ -function newJob(queue: string, message: any, opts?: JobOptions): JobMessage { - return { - id: newid(), - timestamp: Date.now(), - queue: queue, - data: message, - opts, - } -} - -/** - * This is designed to replicate Bull (https://github.com/OptimalBits/bull) in memory as a sort of mock. - * It is relatively simple, using an event emitter internally to register when messages are available - * to the consumers - in can support many inputs and many consumers. + * This is designed to replicate Bull (https://github.com/OptimalBits/bull) in + * memory as a sort of mock. It is relatively simple, using an event emitter + * internally to register when messages are available to the consumers - in can + * support many inputs and many consumers. */ class InMemoryQueue implements Partial { _name: string _opts?: QueueOptions _messages: JobMessage[] _queuedJobIds: Set - _emitter: NodeJS.EventEmitter + _emitter: NodeJS.EventEmitter<{ message: [JobMessage]; completed: [Job] }> _runCount: number _addCount: number @@ -69,34 +82,29 @@ class InMemoryQueue implements Partial { */ async process(concurrencyOrFunc: number | any, func?: any) { func = typeof concurrencyOrFunc === "number" ? func : concurrencyOrFunc - this._emitter.on("message", async () => { - if (this._messages.length <= 0) { - return - } - let msg = this._messages.shift() - - let resp = func(msg) + this._emitter.on("message", async message => { + let resp = func(message) async function retryFunc(fnc: any) { try { await fnc } catch (e: any) { - await new Promise(r => setTimeout(() => r(), 50)) - - await retryFunc(func(msg)) + await helpers.wait(50) + await retryFunc(func(message)) } } if (resp.then != null) { try { await retryFunc(resp) + this._emitter.emit("completed", message as Job) } catch (e: any) { console.error(e) } } this._runCount++ - const jobId = msg?.opts?.jobId?.toString() - if (jobId && msg?.opts?.removeOnComplete) { + const jobId = message.opts?.jobId?.toString() + if (jobId && message.opts?.removeOnComplete) { this._queuedJobIds.delete(jobId) } }) @@ -130,9 +138,16 @@ class InMemoryQueue implements Partial { } const pushMessage = () => { - this._messages.push(newJob(this._name, data, opts)) + const message: JobMessage = { + id: newid(), + timestamp: Date.now(), + queue: this as unknown as Queue, + data, + opts, + } + this._messages.push(message) this._addCount++ - this._emitter.emit("message") + this._emitter.emit("message", message) } const delay = opts?.delay @@ -158,13 +173,6 @@ class InMemoryQueue implements Partial { console.log(cronJobId) } - /** - * Implemented for tests - */ - async getRepeatableJobs() { - return [] - } - async removeJobs(_pattern: string) { // no-op } @@ -176,13 +184,31 @@ class InMemoryQueue implements Partial { return [] } - async getJob() { + async getJob(id: JobId) { + for (const message of this._messages) { + if (message.id === id) { + return message as Job + } + } return null } - on() { - // do nothing - return this as any + on(event: string, callback: (...args: any[]) => void): Queue { + // @ts-expect-error - this callback can be one of many types + this._emitter.on(event, callback) + return this as unknown as Queue + } + + async count() { + return this._messages.length + } + + async getCompletedCount() { + return this._runCount + } + + async getRepeatableJobs() { + return this._messages.map(job => jobToJobInformation(job as Job)) } } diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index 334f1efdd4..7791ecb28b 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -388,7 +388,7 @@ class InternalBuilder { } } - if (typeof input === "string") { + if (typeof input === "string" && schema.type === FieldType.DATETIME) { if (isInvalidISODateString(input)) { return null } diff --git a/packages/backend-core/tests/core/utilities/mocks/licenses.ts b/packages/backend-core/tests/core/utilities/mocks/licenses.ts index 5ba6fb36a1..436e915b81 100644 --- a/packages/backend-core/tests/core/utilities/mocks/licenses.ts +++ b/packages/backend-core/tests/core/utilities/mocks/licenses.ts @@ -1,5 +1,12 @@ -import { Feature, License, Quotas } from "@budibase/types" +import { + Feature, + License, + MonthlyQuotaName, + QuotaType, + QuotaUsageType, +} from "@budibase/types" import cloneDeep from "lodash/cloneDeep" +import merge from "lodash/merge" let CLOUD_FREE_LICENSE: License let UNLIMITED_LICENSE: License @@ -27,18 +34,19 @@ export function initInternal(opts: { export interface UseLicenseOpts { features?: Feature[] - quotas?: Quotas + monthlyQuotas?: [MonthlyQuotaName, number][] } // LICENSES export const useLicense = (license: License, opts?: UseLicenseOpts) => { - if (opts) { - if (opts.features) { - license.features.push(...opts.features) - } - if (opts.quotas) { - license.quotas = opts.quotas + if (opts?.features) { + license.features.push(...opts.features) + } + if (opts?.monthlyQuotas) { + for (const [name, value] of opts.monthlyQuotas) { + license.quotas[QuotaType.USAGE][QuotaUsageType.MONTHLY][name].value = + value } } @@ -57,12 +65,9 @@ export const useCloudFree = () => { // FEATURES -const useFeature = (feature: Feature) => { +const useFeature = (feature: Feature, extra?: Partial) => { const license = cloneDeep(getCachedLicense() || UNLIMITED_LICENSE) - const opts: UseLicenseOpts = { - features: [feature], - } - + const opts: UseLicenseOpts = merge({ features: [feature] }, extra) return useLicense(license, opts) } @@ -102,8 +107,12 @@ export const useAppBuilders = () => { return useFeature(Feature.APP_BUILDERS) } -export const useBudibaseAI = () => { - return useFeature(Feature.BUDIBASE_AI) +export const useBudibaseAI = (opts?: { monthlyQuota?: number }) => { + return useFeature(Feature.BUDIBASE_AI, { + monthlyQuotas: [ + [MonthlyQuotaName.BUDIBASE_AI_CREDITS, opts?.monthlyQuota || 1000], + ], + }) } export const useAICustomConfigs = () => { diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 2caad20bf6..6809f1ffa5 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -80,7 +80,7 @@ "dayjs": "^1.10.8", "easymde": "^2.16.1", "svelte-dnd-action": "^0.9.8", - "svelte-portal": "^1.0.0" + "svelte-portal": "^2.2.1" }, "resolutions": { "loader-utils": "1.4.1" diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index 2401354fbb..633023a94a 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -1,25 +1,25 @@ - + + + +

diff --git a/packages/bbui/src/Form/Input.svelte b/packages/bbui/src/Form/Input.svelte index 47962720af..b1a8669135 100644 --- a/packages/bbui/src/Form/Input.svelte +++ b/packages/bbui/src/Form/Input.svelte @@ -1,24 +1,24 @@ - + + + export interface PopoverAPI { + show: () => void + hide: () => void + } + + - - - - -
- -
- -
Add New View
- -
- - -
-
-
- - -
- -
- -
Add New Row
-
- - - -
-
- - -
-
-
- - -
- -
- -
Add New Filter
-
-

Where

- - - - -
- -
-
diff --git a/packages/bbui/src/StatusLight/StatusLight.svelte b/packages/bbui/src/StatusLight/StatusLight.svelte index f3ef1f62de..e15bed42dd 100644 --- a/packages/bbui/src/StatusLight/StatusLight.svelte +++ b/packages/bbui/src/StatusLight/StatusLight.svelte @@ -1,25 +1,25 @@ - diff --git a/packages/bbui/src/Tooltip/AbsTooltip.svelte b/packages/bbui/src/Tooltip/AbsTooltip.svelte index a887db4102..528ea84efd 100644 --- a/packages/bbui/src/Tooltip/AbsTooltip.svelte +++ b/packages/bbui/src/Tooltip/AbsTooltip.svelte @@ -1,38 +1,24 @@ - - - - - - - Screens{screenCount ? `: ${screenCount}` : ""} - - - {#if !connectedScreens.length} - There aren't any screens connected to this data. - {:else} - The following screens are connected to this data. - - {#each connectedScreens as screen} - - {/each} - - {/if} -
+ + -
-
+ + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte index d538a9b1b7..16423335a6 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavItem/DatasourceNavItem.svelte @@ -7,7 +7,7 @@ import IntegrationIcon from "@/components/backend/DatasourceNavigator/IntegrationIcon.svelte" import { Icon } from "@budibase/bbui" import UpdateDatasourceModal from "@/components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte" - import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte" + import DeleteDataConfirmModal from "@/components/backend/modals/DeleteDataConfirmationModal.svelte" export let datasource @@ -71,7 +71,10 @@ {/if} - + diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte deleted file mode 100644 index 183a803902..0000000000 --- a/packages/builder/src/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte +++ /dev/null @@ -1,175 +0,0 @@ - - - -
-

- Are you sure you wish to delete the table - - - - {table.name} - ? - -

- -

All table data will be deleted{viewsMessage}.

-

This action cannot be undone.

- - {#if screensPossiblyAffected.length > 0} -
- -
    - {#each screensPossiblyAffected as item} -
  • - {item.text} -
  • - {/each} -
-
-
- {/if} -

Please enter the table name below to confirm.

- -
-
- - diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte index f3deccb3c9..fe9d02af40 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavItem/TableNavItem.svelte @@ -8,7 +8,7 @@ import NavItem from "@/components/common/NavItem.svelte" import { isActive } from "@roxi/routify" import EditModal from "./EditModal.svelte" - import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte" + import DeleteConfirmationModal from "../../modals/DeleteDataConfirmationModal.svelte" import { Icon } from "@budibase/bbui" import { DB_TYPE_EXTERNAL } from "@/constants/backend" @@ -65,4 +65,4 @@ {/if} - + diff --git a/packages/builder/src/components/backend/modals/DeleteDataConfirmationModal.svelte b/packages/builder/src/components/backend/modals/DeleteDataConfirmationModal.svelte new file mode 100644 index 0000000000..82271bd066 --- /dev/null +++ b/packages/builder/src/components/backend/modals/DeleteDataConfirmationModal.svelte @@ -0,0 +1,226 @@ + + + +
+ {#if sourceType} +

+ {buildMessage(sourceType)} + {#if affectedScreens.length > 0} + + {#each affectedScreens as item, idx} + {item.text}{idx !== affectedScreens.length - 1 + ? "," + : ""} + {/each} + + {/if} +

+ {/if} +

+ This action cannot be undone. +

+
+
+ + diff --git a/packages/builder/src/components/common/ConfirmDialog.svelte b/packages/builder/src/components/common/ConfirmDialog.svelte index ece20fe102..f864b60fc6 100644 --- a/packages/builder/src/components/common/ConfirmDialog.svelte +++ b/packages/builder/src/components/common/ConfirmDialog.svelte @@ -8,7 +8,7 @@ export let onOk = undefined export let onCancel = undefined export let warning = true - export let disabled + export let disabled = false let modal diff --git a/packages/builder/src/components/common/DetailPopover.svelte b/packages/builder/src/components/common/DetailPopover.svelte index f1e81a6340..eca2afaab1 100644 --- a/packages/builder/src/components/common/DetailPopover.svelte +++ b/packages/builder/src/components/common/DetailPopover.svelte @@ -1,14 +1,20 @@ - + + + diff --git a/packages/builder/src/components/common/JSONViewer.svelte b/packages/builder/src/components/common/JSONViewer.svelte new file mode 100644 index 0000000000..55f6252117 --- /dev/null +++ b/packages/builder/src/components/common/JSONViewer.svelte @@ -0,0 +1,281 @@ + + + + + + +
+ {#if label != null} +
+
+ {#if expandable} + (expanded = !expanded)} + /> + {/if} +
+
(expanded = !expanded)} + on:click={() => dispatch("click-label", clickContext)} + > + {label} +
+
(valueExpanded = !valueExpanded)} + on:click={() => dispatch("click-value", clickContext)} + > + {displayValue} +
+ {#if showCopyIcon} +
+ dispatch("click-copy", clickContext)} + /> +
+ {/if} +
+ {/if} + {#if expandable && (expanded || label == null)} +
+ {#each keys as key} + + {/each} +
+ {/if} +
+ + diff --git a/packages/builder/src/components/common/ScreensPopover.svelte b/packages/builder/src/components/common/ScreensPopover.svelte new file mode 100644 index 0000000000..2fcb86f991 --- /dev/null +++ b/packages/builder/src/components/common/ScreensPopover.svelte @@ -0,0 +1,58 @@ + + + + + + Screens{showCount && screens.length ? `: ${screens.length}` : ""} + + + + {#if !screens.length} + There aren't any screens connected to this data. + {:else} + The following screens are connected to this data. + + {#each screens as screen} + + {/each} + + {/if} + + + diff --git a/packages/builder/src/components/common/UpdateAppTopNav.svelte b/packages/builder/src/components/common/UpdateAppTopNav.svelte index f4a76c4576..967bf1dfdf 100644 --- a/packages/builder/src/components/common/UpdateAppTopNav.svelte +++ b/packages/builder/src/components/common/UpdateAppTopNav.svelte @@ -25,7 +25,7 @@
+ diff --git a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte index c47840ea83..1e951b00cb 100644 --- a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte @@ -19,7 +19,7 @@ $: success = !error && !empty $: highlightedResult = highlight(expressionResult) $: highlightedLogs = expressionLogs.map(l => ({ - log: highlight(l.log.join(", ")), + log: l.log.map(part => highlight(part)).join(", "), line: l.line, type: l.type, })) @@ -95,7 +95,9 @@ {#if empty} Your expression will be evaluated here {:else if error} - {formatError(expressionError)} +
+ {formatError(expressionError)} +
{:else}
{#each highlightedLogs as logLine} @@ -118,13 +120,17 @@ {@html logLine.log}
{#if logLine.line} - :{logLine.line} + :{logLine.line} {/if} {/each}
- - {@html highlightedResult} +
+ + {@html highlightedResult} +
{/if} @@ -169,29 +175,33 @@ .header.error::before { background: var(--error-bg); } + .error-msg { + padding-top: var(--spacing-m); + } .body { flex: 1 1 auto; - padding: var(--spacing-m) var(--spacing-l); font-family: var(--font-mono); + margin: 0 var(--spacing-m); font-size: 12px; overflow-y: auto; overflow-x: hidden; - white-space: pre-line; - word-wrap: break-word; + word-wrap: anywhere; height: 0; } .output-lines { display: flex; flex-direction: column; - gap: var(--spacing-xs); } .line { - border-bottom: var(--border-light); display: flex; flex-direction: row; justify-content: space-between; align-items: end; - padding: var(--spacing-s); + padding: var(--spacing-m) 0; + word-wrap: anywhere; + } + .line:not(:first-of-type) { + border-top: var(--border-light); } .icon-log { display: flex; diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte index 46aea2a6c4..fb3856d517 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/GridColumnConfiguration.svelte @@ -61,6 +61,7 @@ anchor={primaryDisplayColumnAnchor} item={columns.primary} on:change={e => columns.update(e.detail)} + {bindings} /> diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte index c4c2b3eafb..b9f7ab976b 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/PrimaryColumnFieldSetting.svelte @@ -8,6 +8,7 @@ export let item export let anchor + export let bindings let draggableStore = writable({ selected: null, @@ -48,6 +49,7 @@ componentInstance={item} {parseSettings} on:change + {bindings} >
diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js index d3f081756d..638c41e0ec 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js @@ -69,6 +69,7 @@ const toGridFormat = draggableListColumns => { active: entry.active, width: entry.width, conditions: entry.conditions, + format: entry.format, })) } @@ -85,6 +86,7 @@ const toDraggableListFormat = (gridFormatColumns, createComponent, schema) => { columnType: column.columnType || schema[column.field].type, width: column.width, conditions: column.conditions, + format: column.format, }, {} ) diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index 65b3ed9395..8ba32c47cf 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -5,7 +5,6 @@ runtimeToReadableBinding, } from "@/dataBinding" import { builderStore } from "@/stores/builder" - import { onDestroy } from "svelte" export let label = "" export let labelHidden = false @@ -26,16 +25,16 @@ export let wide let highlightType + let domElement $: highlightedProp = $builderStore.highlightedSetting $: allBindings = getAllBindings(bindings, componentBindings, nested) $: safeValue = getSafeValue(value, defaultValue, allBindings) $: replaceBindings = val => readableToRuntimeBinding(allBindings, val) - $: if (!Array.isArray(value)) { - highlightType = - highlightedProp?.key === key ? `highlighted-${highlightedProp?.type}` : "" - } + $: isHighlighted = highlightedProp?.key === key + + $: highlightType = isHighlighted ? `highlighted-${highlightedProp?.type}` : "" const getAllBindings = (bindings, componentBindings, nested) => { if (!nested) { @@ -76,14 +75,18 @@ : enriched } - onDestroy(() => { - if (highlightedProp) { - builderStore.highlightSetting(null) - } - }) + function scrollToElement(element) { + element?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + + $: highlightedProp && isHighlighted && scrollToElement(domElement)
+ import { onMount } from "svelte" + + import { screenStore } from "@/stores/builder" + import ScreensPopover from "@/components/common/ScreensPopover.svelte" + import type { ScreenUsage } from "@budibase/types" + + export let sourceId: string + + let screens: ScreenUsage[] = [] + let popover: any + + export function show() { + popover?.show() + } + + export function hide() { + popover?.hide() + } + + onMount(async () => { + let response = await screenStore.usageInScreens(sourceId) + screens = response?.screens + }) + + + diff --git a/packages/builder/src/components/integration/QueryViewer.svelte b/packages/builder/src/components/integration/QueryViewer.svelte index 0f2ed24177..7a1410e53e 100644 --- a/packages/builder/src/components/integration/QueryViewer.svelte +++ b/packages/builder/src/components/integration/QueryViewer.svelte @@ -23,6 +23,7 @@ import ExtraQueryConfig from "./ExtraQueryConfig.svelte" import QueryViewerSavePromptModal from "./QueryViewerSavePromptModal.svelte" import { Utils } from "@budibase/frontend-core" + import ConnectedQueryScreens from "./ConnectedQueryScreens.svelte" export let query let queryHash @@ -170,6 +171,7 @@
+ (query.flags.urlName = false)} on:save={saveQuery} /> -
- - +
+ +
+ + +
@@ -825,6 +829,12 @@ justify-content: space-between; } + .controls { + display: flex; + align-items: center; + gap: var(--spacing-m); + } + .access { display: flex; gap: var(--spacing-m); diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte index dabbde098b..e39d6bc853 100644 --- a/packages/builder/src/components/portal/onboarding/TourPopover.svelte +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -96,8 +96,8 @@ maxWidth={300} dismissible={false} offset={12} - handlePostionUpdate={tourStep?.positionHandler} - customZindex={3} + handlePositionUpdate={tourStep?.positionHandler} + customZIndex={3} >
diff --git a/packages/builder/src/dataBinding.js b/packages/builder/src/dataBinding.js index 1272c9eb6d..739ecc9494 100644 --- a/packages/builder/src/dataBinding.js +++ b/packages/builder/src/dataBinding.js @@ -1159,10 +1159,17 @@ export const buildFormSchema = (component, asset) => { * Returns an array of the keys of any state variables which are set anywhere * in the app. */ -export const getAllStateVariables = () => { - // Find all button action settings in all components +export const getAllStateVariables = screen => { + let assets = [] + if (screen) { + // only include state variables from a specific screen + assets.push(screen) + } else { + // otherwise include state variables from all screens + assets = getAllAssets() + } let eventSettings = [] - getAllAssets().forEach(asset => { + assets.forEach(asset => { findAllMatchingComponents(asset.props, component => { const settings = componentStore.getComponentSettings(component._component) const nestedTypes = [ @@ -1214,11 +1221,17 @@ export const getAllStateVariables = () => { }) // Add on load settings from screens - get(screenStore).screens.forEach(screen => { + if (screen) { if (screen.onLoad) { eventSettings.push(screen.onLoad) } - }) + } else { + get(screenStore).screens.forEach(screen => { + if (screen.onLoad) { + eventSettings.push(screen.onLoad) + } + }) + } // Extract all state keys from any "update state" actions in each setting let bindingSet = new Set() diff --git a/packages/builder/src/helpers/duplicate.ts b/packages/builder/src/helpers/duplicate.ts index b4740b3e52..eb705c6525 100644 --- a/packages/builder/src/helpers/duplicate.ts +++ b/packages/builder/src/helpers/duplicate.ts @@ -76,13 +76,15 @@ export const getSequentialName = ( { getName, numberFirstItem, + separator = "", }: { getName?: (item: T) => string numberFirstItem?: boolean + separator?: string } = {} ) => { if (!prefix?.length) { - return null + return "" } const trimmedPrefix = prefix.trim() const firstName = numberFirstItem ? `${prefix}1` : trimmedPrefix @@ -107,5 +109,5 @@ export const getSequentialName = ( max = num } }) - return max === 0 ? firstName : `${prefix}${max + 1}` + return max === 0 ? firstName : `${prefix}${separator}${max + 1}` } diff --git a/packages/builder/src/helpers/screen.ts b/packages/builder/src/helpers/screen.ts deleted file mode 100644 index 296a597adb..0000000000 --- a/packages/builder/src/helpers/screen.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Component, Screen, ScreenProps } from "@budibase/types" -import clientManifest from "@budibase/client/manifest.json" - -export function findComponentsBySettingsType( - screen: Screen, - type: string | string[] -) { - const typesArray = Array.isArray(type) ? type : [type] - - const result: { - component: Component - setting: { - type: string - key: string - } - }[] = [] - function recurseFieldComponentsInChildren(component: ScreenProps) { - if (!component) { - return - } - - const definition = getManifestDefinition(component) - const setting = - "settings" in definition && - definition.settings.find((s: any) => typesArray.includes(s.type)) - if (setting && "type" in setting) { - result.push({ - component, - setting: { type: setting.type!, key: setting.key! }, - }) - } - component._children?.forEach(child => { - recurseFieldComponentsInChildren(child) - }) - } - - recurseFieldComponentsInChildren(screen?.props) - return result -} - -function getManifestDefinition(component: Component) { - const componentType = component._component.split("/").slice(-1)[0] - const definition = - clientManifest[componentType as keyof typeof clientManifest] - return definition -} diff --git a/packages/builder/src/helpers/tests/duplicate.test.ts b/packages/builder/src/helpers/tests/duplicate.test.ts index 131e76a6c2..5e956d7b1c 100644 --- a/packages/builder/src/helpers/tests/duplicate.test.ts +++ b/packages/builder/src/helpers/tests/duplicate.test.ts @@ -49,7 +49,7 @@ describe("getSequentialName", () => { it("handles nullish prefix", async () => { const name = getSequentialName([], null) - expect(name).toBe(null) + expect(name).toBe("") }) it("handles just the prefix", async () => { diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/DeleteViewModal.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/DeleteViewModal.svelte deleted file mode 100644 index 5a23c976f8..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/DeleteViewModal.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte index e9c5745cc6..f32767451a 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/_components/ViewNavBar.svelte @@ -10,9 +10,8 @@ import { Icon, ActionButton, ActionMenu, MenuItem } from "@budibase/bbui" import { params, url } from "@roxi/routify" import EditViewModal from "./EditViewModal.svelte" - import DeleteViewModal from "./DeleteViewModal.svelte" import EditTableModal from "@/components/backend/TableNavigator/TableNavItem/EditModal.svelte" - import DeleteTableModal from "@/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte" + import DeleteConfirmationModal from "@/components/backend/modals/DeleteDataConfirmationModal.svelte" import { UserAvatars } from "@budibase/frontend-core" import { DB_TYPE_EXTERNAL } from "@/constants/backend" import { TableNames } from "@/constants" @@ -314,12 +313,12 @@ {#if table && tableEditable} - + {/if} {#if editableView} - + {/if} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/CustomStylesSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/CustomStylesSection.svelte index 30e448774b..230f6ebc60 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/CustomStylesSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/CustomStylesSection.svelte @@ -16,6 +16,7 @@ readableToRuntimeBinding, runtimeToReadableBinding, } from "@/dataBinding" + import { builderStore } from "@/stores/builder" export let componentInstance export let componentDefinition @@ -32,6 +33,8 @@ $: icon = componentDefinition?.icon + $: highlighted = $builderStore.highlightedSetting?.key === "_styles" + const openDrawer = () => { tempValue = runtimeToReadableBinding( bindings, @@ -55,7 +58,7 @@ name={`Custom CSS${componentInstance?._styles?.custom ? " *" : ""}`} collapsible={false} > -
+
Edit custom CSS
@@ -97,4 +100,12 @@ align-items: center; gap: var(--spacing-m); } + + .highlighted { + background: var(--spectrum-global-color-gray-300); + border-left: 4px solid var(--spectrum-semantic-informative-color-background); + transition: background 130ms ease-out, border-color 130ms ease-out; + margin: -4px calc(-1 * var(--spacing-xl)); + padding: 4px var(--spacing-xl) 4px calc(var(--spacing-xl) - 4px); + } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte index d2fed41655..be7d48f88f 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte @@ -33,7 +33,7 @@ {/each}
- + {#if activeTab === "theme"} {:else} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte index 16fe7d9c2f..f5dec6371f 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_layout.svelte @@ -30,7 +30,7 @@ if (id === `${$screenStore.selectedScreenId}-screen`) return true if (id === `${$screenStore.selectedScreenId}-navigation`) return true - return !!findComponent($selectedScreen.props, id) + return !!findComponent($selectedScreen?.props, id) } // Keep URL and state in sync for selected component ID diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index 29e2ce03ff..9dd7aab640 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -3,6 +3,8 @@ import AppPreview from "./AppPreview.svelte" import { screenStore, appStore } from "@/stores/builder" import UndoRedoControl from "@/components/common/UndoRedoControl.svelte" + import ScreenErrorsButton from "./ScreenErrorsButton.svelte" + import { Divider } from "@budibase/bbui"
@@ -15,6 +17,8 @@ {#if $appStore.clientFeatures.devicePreview} {/if} + +
@@ -50,6 +54,9 @@ margin-bottom: 9px; } + .header-left { + display: flex; + } .header-left :global(div) { border-right: none; } @@ -59,7 +66,7 @@ flex-direction: row; justify-content: flex-start; align-items: center; - gap: var(--spacing-xl); + gap: var(--spacing-l); } .content { flex: 1 1 auto; diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 3951c0e902..ba600c8eef 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -183,16 +183,6 @@ toggleAddComponent() } else if (type === "highlight-setting") { builderStore.highlightSetting(data.setting, "error") - - // Also scroll setting into view - const selector = `#${data.setting}-prop-control` - const element = document.querySelector(selector)?.parentElement - if (element) { - element.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } } else if (type === "eject-block") { const { id, definition } = data await componentStore.handleEjectBlock(id, definition) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/BindingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/BindingsPanel.svelte new file mode 100644 index 0000000000..fb222905bf --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/BindingsPanel.svelte @@ -0,0 +1,83 @@ + + +
+ +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentKeyHandler.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentKeyHandler.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/ComponentKeyHandler.svelte rename to packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentKeyHandler.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte index eb5a57ec9c..7636d923de 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ComponentList/index.svelte @@ -1,6 +1,5 @@ - -
-
- Components -
- -
-
- +
    openScreenContextMenu(e, false)} @@ -159,7 +135,6 @@
-
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/StatePanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/StatePanel.svelte new file mode 100644 index 0000000000..522ab9adc3 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/StatePanel.svelte @@ -0,0 +1,336 @@ + + +
+