From 4acb8fae99b39ef03a1817af83ecfcda1926c858 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:09:22 +0000 Subject: [PATCH 01/27] Convert portal templates store --- packages/builder/src/stores/portal/templates.js | 16 ---------------- packages/builder/src/stores/portal/templates.ts | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 packages/builder/src/stores/portal/templates.js create mode 100644 packages/builder/src/stores/portal/templates.ts diff --git a/packages/builder/src/stores/portal/templates.js b/packages/builder/src/stores/portal/templates.js deleted file mode 100644 index 2ff23866ab..0000000000 --- a/packages/builder/src/stores/portal/templates.js +++ /dev/null @@ -1,16 +0,0 @@ -import { writable } from "svelte/store" -import { API } from "@/api" - -export function templatesStore() { - const { subscribe, set } = writable([]) - - return { - subscribe, - load: async () => { - const templates = await API.getAppTemplates() - set(templates) - }, - } -} - -export const templates = templatesStore() diff --git a/packages/builder/src/stores/portal/templates.ts b/packages/builder/src/stores/portal/templates.ts new file mode 100644 index 0000000000..caf1a54ced --- /dev/null +++ b/packages/builder/src/stores/portal/templates.ts @@ -0,0 +1,16 @@ +import { API } from "@/api" +import { BudiStore } from "../BudiStore" +import { TemplateMetadata } from "@budibase/types" + +class TemplateStore extends BudiStore { + constructor() { + super([]) + } + + async load() { + const templates = await API.getAppTemplates() + this.set(templates) + } +} + +export const templates = new TemplateStore() From 085617a5220be1dcbc2d31cde6cb645709b6cf67 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:17:11 +0000 Subject: [PATCH 02/27] Convert portal temporal store to TS --- .../portal/licensing/LicensingOverlays.svelte | 4 +- .../portal/licensing/licensingBanners.js | 4 +- .../builder/src/stores/portal/temporal.js | 45 ---------------- .../builder/src/stores/portal/temporal.ts | 53 +++++++++++++++++++ 4 files changed, 57 insertions(+), 49 deletions(-) delete mode 100644 packages/builder/src/stores/portal/temporal.js create mode 100644 packages/builder/src/stores/portal/temporal.ts diff --git a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte index 3de2b464e0..8280251839 100644 --- a/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte +++ b/packages/builder/src/components/portal/licensing/LicensingOverlays.svelte @@ -20,7 +20,7 @@ const processModals = () => { const defaultCacheFn = key => { - temporalStore.actions.setExpiring(key, {}, oneDayInSeconds) + temporalStore.setExpiring(key, {}, oneDayInSeconds) } const dismissableModals = [ @@ -50,7 +50,7 @@ }, ] return dismissableModals.filter(modal => { - return !temporalStore.actions.getExpiring(modal.key) && modal.criteria() + return !temporalStore.getExpiring(modal.key) && modal.criteria() }) } diff --git a/packages/builder/src/components/portal/licensing/licensingBanners.js b/packages/builder/src/components/portal/licensing/licensingBanners.js index 230b9fe6f6..62ca6caa1b 100644 --- a/packages/builder/src/components/portal/licensing/licensingBanners.js +++ b/packages/builder/src/components/portal/licensing/licensingBanners.js @@ -6,7 +6,7 @@ import { BANNER_TYPES } from "@budibase/bbui" const oneDayInSeconds = 86400 const defaultCacheFn = key => { - temporalStore.actions.setExpiring(key, {}, oneDayInSeconds) + temporalStore.setExpiring(key, {}, oneDayInSeconds) } const upgradeAction = key => { @@ -148,7 +148,7 @@ export const getBanners = () => { buildUsersAboveLimitBanner(ExpiringKeys.LICENSING_USERS_ABOVE_LIMIT_BANNER), ].filter(licensingBanner => { return ( - !temporalStore.actions.getExpiring(licensingBanner.key) && + !temporalStore.getExpiring(licensingBanner.key) && licensingBanner.criteria() ) }) diff --git a/packages/builder/src/stores/portal/temporal.js b/packages/builder/src/stores/portal/temporal.js deleted file mode 100644 index 96b47d1c7f..0000000000 --- a/packages/builder/src/stores/portal/temporal.js +++ /dev/null @@ -1,45 +0,0 @@ -import { createLocalStorageStore } from "@budibase/frontend-core" -import { get } from "svelte/store" - -export const createTemporalStore = () => { - const initialValue = {} - - const localStorageKey = `bb-temporal` - const store = createLocalStorageStore(localStorageKey, initialValue) - - const setExpiring = (key, data, duration) => { - const updated = { - ...data, - expiry: Date.now() + duration * 1000, - } - - store.update(state => ({ - ...state, - [key]: updated, - })) - } - - const getExpiring = key => { - const entry = get(store)[key] - if (!entry) { - return - } - const currentExpiry = entry.expiry - if (currentExpiry < Date.now()) { - store.update(state => { - delete state[key] - return state - }) - return null - } else { - return entry - } - } - - return { - subscribe: store.subscribe, - actions: { setExpiring, getExpiring }, - } -} - -export const temporalStore = createTemporalStore() diff --git a/packages/builder/src/stores/portal/temporal.ts b/packages/builder/src/stores/portal/temporal.ts new file mode 100644 index 0000000000..acbe6feff2 --- /dev/null +++ b/packages/builder/src/stores/portal/temporal.ts @@ -0,0 +1,53 @@ +import { get } from "svelte/store" +import { BudiStore, PersistenceType } from "../BudiStore" + +type TemporalItem = Record & { expiry: number } +type TemporalState = Record + +class TemporalStore extends BudiStore { + constructor() { + super( + {}, + { + persistence: { + key: "bb-temporal", + type: PersistenceType.LOCAL, + }, + } + ) + } + + setExpiring = ( + key: string, + data: Record, + durationSeconds: number + ) => { + const updated: TemporalItem = { + ...data, + expiry: Date.now() + durationSeconds * 1000, + } + this.update(state => ({ + ...state, + [key]: updated, + })) + } + + getExpiring(key: string) { + const entry = get(this.store)[key] + if (!entry) { + return null + } + const currentExpiry = entry.expiry + if (currentExpiry < Date.now()) { + this.update(state => { + delete state[key] + return state + }) + return null + } else { + return entry + } + } +} + +export const temporalStore = new TemporalStore() From 5f3825118f175b66c03c2ac2147c0364ca8f838f Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:23:09 +0000 Subject: [PATCH 03/27] Convert portal theme store to TS --- packages/builder/src/stores/portal/theme.js | 37 ----------------- packages/builder/src/stores/portal/theme.ts | 45 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 37 deletions(-) delete mode 100644 packages/builder/src/stores/portal/theme.js create mode 100644 packages/builder/src/stores/portal/theme.ts diff --git a/packages/builder/src/stores/portal/theme.js b/packages/builder/src/stores/portal/theme.js deleted file mode 100644 index 11a87845e1..0000000000 --- a/packages/builder/src/stores/portal/theme.js +++ /dev/null @@ -1,37 +0,0 @@ -import { createLocalStorageStore } from "@budibase/frontend-core" -import { derived } from "svelte/store" -import { - DefaultBuilderTheme, - ensureValidTheme, - getThemeClassNames, - ThemeOptions, - ThemeClassPrefix, -} from "@budibase/shared-core" - -export const getThemeStore = () => { - const themeElement = document.documentElement - const initialValue = { - theme: DefaultBuilderTheme, - } - const store = createLocalStorageStore("bb-theme", initialValue) - const derivedStore = derived(store, $store => ({ - ...$store, - theme: ensureValidTheme($store.theme, DefaultBuilderTheme), - })) - - // Update theme class when store changes - derivedStore.subscribe(({ theme }) => { - const classNames = getThemeClassNames(theme).split(" ") - ThemeOptions.forEach(option => { - const className = `${ThemeClassPrefix}${option.id}` - themeElement.classList.toggle(className, classNames.includes(className)) - }) - }) - - return { - ...store, - subscribe: derivedStore.subscribe, - } -} - -export const themeStore = getThemeStore() diff --git a/packages/builder/src/stores/portal/theme.ts b/packages/builder/src/stores/portal/theme.ts new file mode 100644 index 0000000000..5198555024 --- /dev/null +++ b/packages/builder/src/stores/portal/theme.ts @@ -0,0 +1,45 @@ +import { derived, Writable } from "svelte/store" +import { + DefaultBuilderTheme, + ensureValidTheme, + getThemeClassNames, + ThemeOptions, + ThemeClassPrefix, +} from "@budibase/shared-core" +import { Theme } from "@budibase/types" +import { DerivedBudiStore, PersistenceType } from "../BudiStore" + +interface ThemeState { + theme: Theme +} + +class ThemeStore extends DerivedBudiStore { + constructor() { + const makeDerivedStore = (store: Writable) => { + return derived(store, $store => ({ + ...$store, + theme: ensureValidTheme($store.theme, DefaultBuilderTheme), + })) + } + super({ theme: DefaultBuilderTheme }, makeDerivedStore, { + persistence: { + key: "bb-theme", + type: PersistenceType.LOCAL, + }, + }) + + // Update theme class when store changes + this.subscribe(({ theme }) => { + const classNames = getThemeClassNames(theme).split(" ") + ThemeOptions.forEach(option => { + const className = `${ThemeClassPrefix}${option.id}` + document.documentElement.classList.toggle( + className, + classNames.includes(className) + ) + }) + }) + } +} + +export const themeStore = new ThemeStore() From 65bd89250de002307ce62c1c2845ba1efa3e48ad Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:24:04 +0000 Subject: [PATCH 04/27] Convert portal barrel file to TS --- packages/builder/src/stores/portal/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/builder/src/stores/portal/{index.js => index.ts} (100%) diff --git a/packages/builder/src/stores/portal/index.js b/packages/builder/src/stores/portal/index.ts similarity index 100% rename from packages/builder/src/stores/portal/index.js rename to packages/builder/src/stores/portal/index.ts From 12b283d41ed8b7be2e41b099321adcf47efe1ad0 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:28:04 +0000 Subject: [PATCH 05/27] Convert portal navigation store to BudiStore --- .../builder/src/pages/builder/_layout.svelte | 2 +- .../builder/src/stores/portal/navigation.ts | 31 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 209cf2746d..7e765d7366 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -18,7 +18,7 @@ $: useAccountPortal = cloud && !$admin.disableAccountPortal - navigation.actions.init($redirect) + navigation.init($redirect) const validateTenantId = async () => { const host = window.location.host diff --git a/packages/builder/src/stores/portal/navigation.ts b/packages/builder/src/stores/portal/navigation.ts index 4eb50bc84f..f289c86bf9 100644 --- a/packages/builder/src/stores/portal/navigation.ts +++ b/packages/builder/src/stores/portal/navigation.ts @@ -1,38 +1,31 @@ -import { writable } from "svelte/store" +import { BudiStore } from "../BudiStore" type GotoFuncType = (path: string) => void -interface PortalNavigationStore { +interface NavigationState { initialisated: boolean goto: GotoFuncType } -export function createNavigationStore() { - const store = writable({ - initialisated: false, - goto: undefined as any, - }) - const { set, subscribe } = store +class NavigationStore extends BudiStore { + constructor() { + super({ + initialisated: false, + goto: undefined as any, + }) + } - const init = (gotoFunc: GotoFuncType) => { + init(gotoFunc: GotoFuncType) { if (typeof gotoFunc !== "function") { throw new Error( `gotoFunc must be a function, found a "${typeof gotoFunc}" instead` ) } - - set({ + this.set({ initialisated: true, goto: gotoFunc, }) } - - return { - subscribe, - actions: { - init, - }, - } } -export const navigation = createNavigationStore() +export const navigation = new NavigationStore() From 502c1605307c3efdcb4c6ea27e5a5a2e08a9ccff Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:31:44 +0000 Subject: [PATCH 06/27] Convert admin store to BudiStore --- packages/builder/src/stores/portal/admin.ts | 71 ++++++++++----------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/packages/builder/src/stores/portal/admin.ts b/packages/builder/src/stores/portal/admin.ts index f262d54bff..1cb0be1313 100644 --- a/packages/builder/src/stores/portal/admin.ts +++ b/packages/builder/src/stores/portal/admin.ts @@ -1,4 +1,4 @@ -import { writable, get } from "svelte/store" +import { get } from "svelte/store" import { API } from "@/api" import { auth } from "@/stores/portal" import { banner } from "@budibase/bbui" @@ -7,42 +7,44 @@ import { GetEnvironmentResponse, SystemStatusResponse, } from "@budibase/types" +import { BudiStore } from "../BudiStore" -interface PortalAdminStore extends GetEnvironmentResponse { +interface AdminState extends GetEnvironmentResponse { loaded: boolean checklist?: ConfigChecklistResponse status?: SystemStatusResponse } -export function createAdminStore() { - const admin = writable({ - loaded: false, - multiTenancy: false, - cloud: false, - isDev: false, - disableAccountPortal: false, - offlineMode: false, - maintenance: [], - }) +class AdminStore extends BudiStore { + constructor() { + super({ + loaded: false, + multiTenancy: false, + cloud: false, + isDev: false, + disableAccountPortal: false, + offlineMode: false, + maintenance: [], + }) + } - async function init() { - await getChecklist() - await getEnvironment() + async init() { + await this.getChecklist() + await this.getEnvironment() // enable system status checks in the cloud - if (get(admin).cloud) { - await getSystemStatus() - checkStatus() + if (get(this.store).cloud) { + await this.getSystemStatus() + this.checkStatus() } - - admin.update(store => { + this.update(store => { store.loaded = true return store }) } - async function getEnvironment() { + async getEnvironment() { const environment = await API.getEnvironment() - admin.update(store => { + this.update(store => { store.multiTenancy = environment.multiTenancy store.cloud = environment.cloud store.disableAccountPortal = environment.disableAccountPortal @@ -56,43 +58,36 @@ export function createAdminStore() { }) } - const checkStatus = async () => { - const health = get(admin)?.status?.health + async checkStatus() { + const health = get(this.store).status?.health if (!health?.passing) { await banner.showStatus() } } - async function getSystemStatus() { + async getSystemStatus() { const status = await API.getSystemStatus() - admin.update(store => { + this.update(store => { store.status = status return store }) } - async function getChecklist() { + async getChecklist() { const tenantId = get(auth).tenantId const checklist = await API.getChecklist(tenantId) - admin.update(store => { + this.update(store => { store.checklist = checklist return store }) } - function unload() { - admin.update(store => { + unload() { + this.update(store => { store.loaded = false return store }) } - - return { - subscribe: admin.subscribe, - init, - unload, - getChecklist, - } } -export const admin = createAdminStore() +export const admin = new AdminStore() From 153d905921c48be839c8aa1a74d76743017fe8b3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 11:33:39 +0000 Subject: [PATCH 07/27] Update exports --- packages/builder/src/stores/portal/admin.test.js | 4 ++-- packages/builder/src/stores/portal/admin.ts | 2 +- packages/builder/src/stores/portal/auditLogs.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/stores/portal/admin.test.js b/packages/builder/src/stores/portal/admin.test.js index 1528042630..261e94eb6f 100644 --- a/packages/builder/src/stores/portal/admin.test.js +++ b/packages/builder/src/stores/portal/admin.test.js @@ -1,5 +1,5 @@ import { it, expect, describe, beforeEach, vi } from "vitest" -import { createAdminStore } from "./admin" +import { AdminStore } from "./admin" import { writable, get } from "svelte/store" import { API } from "@/api" import { auth } from "@/stores/portal" @@ -46,7 +46,7 @@ describe("admin store", () => { ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn() } writable.mockReturnValue(ctx.writableReturn) - ctx.returnedStore = createAdminStore() + ctx.returnedStore = new AdminStore() }) it("returns the created store", ctx => { diff --git a/packages/builder/src/stores/portal/admin.ts b/packages/builder/src/stores/portal/admin.ts index 1cb0be1313..90e3a5cdc9 100644 --- a/packages/builder/src/stores/portal/admin.ts +++ b/packages/builder/src/stores/portal/admin.ts @@ -15,7 +15,7 @@ interface AdminState extends GetEnvironmentResponse { status?: SystemStatusResponse } -class AdminStore extends BudiStore { +export class AdminStore extends BudiStore { constructor() { super({ loaded: false, diff --git a/packages/builder/src/stores/portal/auditLogs.ts b/packages/builder/src/stores/portal/auditLogs.ts index ff29f0cd1b..6f11f228d6 100644 --- a/packages/builder/src/stores/portal/auditLogs.ts +++ b/packages/builder/src/stores/portal/auditLogs.ts @@ -13,7 +13,7 @@ interface PortalAuditLogsStore { logs?: SearchAuditLogsResponse } -export class AuditLogsStore extends BudiStore { +class AuditLogsStore extends BudiStore { constructor() { super({}) } From 9e10921e2981b73b8ca497354d0b55c5089e7fff Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 13 Jan 2025 12:01:29 +0000 Subject: [PATCH 08/27] Update tests --- packages/builder/src/stores/portal/admin.test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/builder/src/stores/portal/admin.test.js b/packages/builder/src/stores/portal/admin.test.js index 261e94eb6f..8924a5e6fb 100644 --- a/packages/builder/src/stores/portal/admin.test.js +++ b/packages/builder/src/stores/portal/admin.test.js @@ -49,15 +49,6 @@ describe("admin store", () => { ctx.returnedStore = new AdminStore() }) - it("returns the created store", ctx => { - expect(ctx.returnedStore).toEqual({ - subscribe: expect.toBe(ctx.writableReturn.subscribe), - init: expect.toBeFunc(), - unload: expect.toBeFunc(), - getChecklist: expect.toBeFunc(), - }) - }) - describe("init method", () => { beforeEach(async ctx => { let getMockIndex = 0 From 0d1f5c698e529664e1a9ed0e30dce9eb52d0e778 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 09:54:12 +0100 Subject: [PATCH 09/27] Add basic datasource validation error --- packages/client/manifest.json | 3 ++- .../client/src/components/Component.svelte | 24 ++++++++++++++++++- .../error-states/ComponentErrorState.svelte | 4 ++++ .../client/src/utils/componentsValidator.ts | 13 ++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/utils/componentsValidator.ts diff --git a/packages/client/manifest.json b/packages/client/manifest.json index c236dd1ad9..7da7c60abe 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -7206,7 +7206,8 @@ { "type": "table", "label": "Data", - "key": "dataSource" + "key": "dataSource", + "validator": "checkValidDatasource" }, { "type": "radio", diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 79b4ca6f68..4926965e45 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -40,6 +40,7 @@ getActionDependentContextKeys, } from "../utils/buttonActions.js" import { gridLayout } from "utils/grid" + import { validateComponentSetting } from "utils/componentsValidator" export let instance = {} export let parent = null @@ -103,6 +104,7 @@ let settingsDefinition let settingsDefinitionMap let missingRequiredSettings = false + let invalidSettings = false // Temporary styles which can be added in the app preview for things like // DND. We clear these whenever a new instance is received. @@ -141,12 +143,16 @@ $: showEmptyState = definition?.showEmptyState !== false $: hasMissingRequiredSettings = missingRequiredSettings?.length > 0 $: editable = !!definition?.editable && !hasMissingRequiredSettings + $: hasInvalidSettings = invalidSettings?.length > 0 $: requiredAncestors = definition?.requiredAncestors || [] $: missingRequiredAncestors = requiredAncestors.filter( ancestor => !$component.ancestors.includes(`${BudibasePrefix}${ancestor}`) ) $: hasMissingRequiredAncestors = missingRequiredAncestors?.length > 0 - $: errorState = hasMissingRequiredSettings || hasMissingRequiredAncestors + $: errorState = + hasMissingRequiredSettings || + hasMissingRequiredAncestors || + hasInvalidSettings // Interactive components can be selected, dragged and highlighted inside // the builder preview @@ -338,6 +344,21 @@ return missing }) + // Check for invalid settings + invalidSettings = settingsDefinition.reduce((invalidSettings, setting) => { + if (setting.validator) { + const error = validateComponentSetting( + setting.validator, + instance[setting.key] + ) + if (error) { + invalidSettings.push(error) + } + } + + return invalidSettings + }, []) + // When considering bindings we can ignore children, so we remove that // before storing the reference stringified version const noChildren = JSON.stringify({ ...instance, _children: null }) @@ -692,6 +713,7 @@ {:else} diff --git a/packages/client/src/components/error-states/ComponentErrorState.svelte b/packages/client/src/components/error-states/ComponentErrorState.svelte index b846eaa230..ba55e3cdc1 100644 --- a/packages/client/src/components/error-states/ComponentErrorState.svelte +++ b/packages/client/src/components/error-states/ComponentErrorState.svelte @@ -9,6 +9,7 @@ | { key: string; label: string }[] | undefined export let missingRequiredAncestors: string[] | undefined + export let invalidSettings: string[] | undefined const component = getContext("component") const { styleable, builderStore } = getContext("sdk") @@ -16,6 +17,7 @@ $: styles = { ...$component.styles, normal: {}, custom: null, empty: true } $: requiredSetting = missingRequiredSettings?.[0] $: requiredAncestor = missingRequiredAncestors?.[0] + $: invalidSetting = invalidSettings?.[0] {#if $builderStore.inBuilder} @@ -24,6 +26,8 @@ {#if requiredAncestor} + {:else if invalidSetting} + {invalidSetting} {:else if requiredSetting} {/if} diff --git a/packages/client/src/utils/componentsValidator.ts b/packages/client/src/utils/componentsValidator.ts new file mode 100644 index 0000000000..6609f92ed6 --- /dev/null +++ b/packages/client/src/utils/componentsValidator.ts @@ -0,0 +1,13 @@ +const validators = { + checkValidDatasource: (a: any) => { + return `Ups... "${a.label}" not found` + }, +} + +export function validateComponentSetting( + key: keyof typeof validators, + value: any +) { + const validator = validators[key] + return validator(value) +} From ba2a61841f292276169bc3c4decc659a58c98f61 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 15:55:00 +0100 Subject: [PATCH 10/27] Allow importing @budibase/client/manifest.json --- eslint-local-rules/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eslint-local-rules/index.js b/eslint-local-rules/index.js index d9d894c33e..9348706399 100644 --- a/eslint-local-rules/index.js +++ b/eslint-local-rules/index.js @@ -41,11 +41,12 @@ module.exports = { if ( /^@budibase\/[^/]+\/.*$/.test(importPath) && importPath !== "@budibase/backend-core/tests" && - importPath !== "@budibase/string-templates/test/utils" + importPath !== "@budibase/string-templates/test/utils" && + importPath !== "@budibase/client/manifest.json" ) { context.report({ node, - message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests and @budibase/string-templates/test/utils.`, + message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests, @budibase/string-templates/test/utils and @budibase/client/manifest.json.`, }) } }, From 7e029386190964097cdf1582f91568b7a694e7ef Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 15:55:17 +0100 Subject: [PATCH 11/27] Screen helpers, findComponentsBySettingsType --- packages/builder/src/helpers/screen.ts | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/builder/src/helpers/screen.ts diff --git a/packages/builder/src/helpers/screen.ts b/packages/builder/src/helpers/screen.ts new file mode 100644 index 0000000000..e83805a511 --- /dev/null +++ b/packages/builder/src/helpers/screen.ts @@ -0,0 +1,30 @@ +import { Component, Screen, ScreenProps } from "@budibase/types" +import clientManifest from "@budibase/client/manifest.json" + +export function findComponentsBySettingsType(screen: Screen, type: string) { + const result: Component[] = [] + function recurseFieldComponentsInChildren( + component: ScreenProps, + type: string + ) { + if (!component) { + return + } + + const componentType = component._component.split("/").slice(-1)[0] + const definition = + clientManifest[componentType as keyof typeof clientManifest] + if ( + "settings" in definition && + definition.settings.some((s: any) => s.type === type) + ) { + result.push(component) + } + component._children?.forEach(child => { + recurseFieldComponentsInChildren(child, type) + }) + } + + recurseFieldComponentsInChildren(screen?.props, type) + return result +} From 3c4ac9ad5a7610645b83c8b8f3cdeb3b8c52c70a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 16:04:39 +0100 Subject: [PATCH 12/27] Errors from builder store --- packages/client/manifest.json | 3 +-- .../client/src/components/Component.svelte | 20 ++++--------------- packages/client/src/stores/builder.js | 3 +++ .../src/stores/derived/componentErrors.ts | 6 ++++++ packages/client/src/stores/derived/index.js | 1 + .../client/src/utils/componentsValidator.ts | 13 ------------ 6 files changed, 15 insertions(+), 31 deletions(-) create mode 100644 packages/client/src/stores/derived/componentErrors.ts delete mode 100644 packages/client/src/utils/componentsValidator.ts diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 7da7c60abe..c236dd1ad9 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -7206,8 +7206,7 @@ { "type": "table", "label": "Data", - "key": "dataSource", - "validator": "checkValidDatasource" + "key": "dataSource" }, { "type": "radio", diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 4926965e45..7f825c0601 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -23,6 +23,7 @@ appStore, dndComponentPath, dndIsDragging, + componentErrors, } from "stores" import { Helpers } from "@budibase/bbui" import { getActiveConditions, reduceConditionActions } from "utils/conditions" @@ -40,7 +41,6 @@ getActionDependentContextKeys, } from "../utils/buttonActions.js" import { gridLayout } from "utils/grid" - import { validateComponentSetting } from "utils/componentsValidator" export let instance = {} export let parent = null @@ -344,21 +344,6 @@ return missing }) - // Check for invalid settings - invalidSettings = settingsDefinition.reduce((invalidSettings, setting) => { - if (setting.validator) { - const error = validateComponentSetting( - setting.validator, - instance[setting.key] - ) - if (error) { - invalidSettings.push(error) - } - } - - return invalidSettings - }, []) - // When considering bindings we can ignore children, so we remove that // before storing the reference stringified version const noChildren = JSON.stringify({ ...instance, _children: null }) @@ -389,6 +374,9 @@ } } + // Check for invalid settings + $: invalidSettings = $componentErrors[id] + // Extracts a map of all context keys which are required by action settings // to provide the functions to evaluate at runtime. This needs done manually // as the action definitions themselves do not specify bindings for action diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index faa37eddca..62123c07e5 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -19,6 +19,9 @@ const createBuilderStore = () => { eventResolvers: {}, metadata: null, snippets: null, + componentErrors: { + c5ea93132725c48b2a365fcc1facaee86: ["Ups...!"], + }, // TODO // Legacy - allow the builder to specify a layout layout: null, diff --git a/packages/client/src/stores/derived/componentErrors.ts b/packages/client/src/stores/derived/componentErrors.ts new file mode 100644 index 0000000000..48185de9c3 --- /dev/null +++ b/packages/client/src/stores/derived/componentErrors.ts @@ -0,0 +1,6 @@ +import { derived } from "svelte/store" +import { builderStore } from "../builder.js" + +export const componentErrors = derived([builderStore], ([$builderStore]) => { + return $builderStore.componentErrors as Record +}) diff --git a/packages/client/src/stores/derived/index.js b/packages/client/src/stores/derived/index.js index 337c73831f..e7e70d8952 100644 --- a/packages/client/src/stores/derived/index.js +++ b/packages/client/src/stores/derived/index.js @@ -5,3 +5,4 @@ export { currentRole } from "./currentRole.js" export { dndComponentPath } from "./dndComponentPath.js" export { devToolsEnabled } from "./devToolsEnabled.js" export { snippets } from "./snippets.js" +export { componentErrors } from "./componentErrors" diff --git a/packages/client/src/utils/componentsValidator.ts b/packages/client/src/utils/componentsValidator.ts deleted file mode 100644 index 6609f92ed6..0000000000 --- a/packages/client/src/utils/componentsValidator.ts +++ /dev/null @@ -1,13 +0,0 @@ -const validators = { - checkValidDatasource: (a: any) => { - return `Ups... "${a.label}" not found` - }, -} - -export function validateComponentSetting( - key: keyof typeof validators, - value: any -) { - const validator = validators[key] - return validator(value) -} From 75dab572e956e7669ba8282c56c74be4e235f474 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 20 Jan 2025 20:06:02 +0100 Subject: [PATCH 13/27] Move data to builder --- .../design/[screenId]/_components/AppPreview.svelte | 3 +++ packages/client/src/index.js | 1 + packages/client/src/stores/builder.js | 4 +--- .../server/src/api/controllers/static/templates/preview.hbs | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) 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 55a4dc4de4..661e985194 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 @@ -68,6 +68,9 @@ port: window.location.port, }, snippets: $snippets, + componentErrors: { + c5ea93132725c48b2a365fcc1facaee86: ["Ups...!"], + }, // TODO } // Refresh the preview when required diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 9cef52bb1e..7cb9ed4430 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -43,6 +43,7 @@ const loadBudibase = async () => { usedPlugins: window["##BUDIBASE_USED_PLUGINS##"], location: window["##BUDIBASE_LOCATION##"], snippets: window["##BUDIBASE_SNIPPETS##"], + componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"], }) // Set app ID - this window flag is set by both the preview and the real diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 62123c07e5..1ae7d3a670 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -19,9 +19,7 @@ const createBuilderStore = () => { eventResolvers: {}, metadata: null, snippets: null, - componentErrors: { - c5ea93132725c48b2a365fcc1facaee86: ["Ups...!"], - }, // TODO + componentErrors: {}, // Legacy - allow the builder to specify a layout layout: null, diff --git a/packages/server/src/api/controllers/static/templates/preview.hbs b/packages/server/src/api/controllers/static/templates/preview.hbs index 87b9ad6ea3..750a780897 100644 --- a/packages/server/src/api/controllers/static/templates/preview.hbs +++ b/packages/server/src/api/controllers/static/templates/preview.hbs @@ -73,7 +73,8 @@ hiddenComponentIds, usedPlugins, location, - snippets + snippets, + componentErrors } = parsed // Set some flags so the app knows we're in the builder @@ -91,6 +92,7 @@ window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins window["##BUDIBASE_LOCATION##"] = location window["##BUDIBASE_SNIPPETS##"] = snippets + window['##BUDIBASE_COMPONENT_ERRORS##'] = componentErrors // Initialise app try { From d1294c8d44e5ef9fac098ed823ae0b17ec37b24f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 12:38:31 +0100 Subject: [PATCH 14/27] Errors from store --- .../[screenId]/_components/AppPreview.svelte | 5 +- packages/builder/src/stores/builder/index.js | 2 + .../src/stores/builder/screenComponent.ts | 61 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 packages/builder/src/stores/builder/screenComponent.ts 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 661e985194..fc0b67f63d 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 @@ -11,6 +11,7 @@ selectedScreen, hoverStore, componentTreeNodesStore, + screenComponentStore, snippets, } from "@/stores/builder" import ConfirmDialog from "@/components/common/ConfirmDialog.svelte" @@ -68,9 +69,7 @@ port: window.location.port, }, snippets: $snippets, - componentErrors: { - c5ea93132725c48b2a365fcc1facaee86: ["Ups...!"], - }, // TODO + componentErrors: $screenComponentStore.errors, } // Refresh the preview when required diff --git a/packages/builder/src/stores/builder/index.js b/packages/builder/src/stores/builder/index.js index 08d87bebf5..23491996d1 100644 --- a/packages/builder/src/stores/builder/index.js +++ b/packages/builder/src/stores/builder/index.js @@ -16,6 +16,7 @@ import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js" import { deploymentStore } from "./deployments.js" import { contextMenuStore } from "./contextMenu.js" import { snippets } from "./snippets" +import { screenComponentStore } from "./screenComponent" // Backend import { tables } from "./tables" @@ -67,6 +68,7 @@ export { snippets, rowActions, appPublished, + screenComponentStore, } export const reset = () => { diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts new file mode 100644 index 0000000000..b97bb7ba98 --- /dev/null +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -0,0 +1,61 @@ +import { derived } from "svelte/store" +import { tables, selectedScreen } from "@/stores/builder" +import { DerivedBudiStore } from "../BudiStore" +import { findComponentsBySettingsType } from "@/helpers/screen" +import { Screen } from "@budibase/types" + +interface BuilderScreenComponentStore {} + +interface DerivedScreenComponentStore extends BuilderScreenComponentStore { + errors: Record +} + +export class ScreenComponentStore extends DerivedBudiStore< + BuilderScreenComponentStore, + DerivedScreenComponentStore +> { + constructor() { + const makeDerivedStore = () => { + return derived( + [selectedScreen, tables], + ([$selectedScreen, $tables]): DerivedScreenComponentStore => { + function getErrors() { + const datasources = $tables.list.reduce( + (list, table) => ({ + ...list, + [table._id!]: table, + }), + {} + ) + return { + ...getInvalidDatasources($selectedScreen, datasources), + } + } + + return { + errors: getErrors(), + } + } + ) + } + + super({}, makeDerivedStore) + } +} + +export const screenComponentStore = new ScreenComponentStore() + +function getInvalidDatasources( + screen: Screen, + datasources: Record +) { + const result: Record = {} + for (const component of findComponentsBySettingsType(screen, "table")) { + const { resourceId, type, label } = component.dataSource + if (!datasources[resourceId]) { + result[component._id!] = [`The ${type} named "${label}" was removed`] + } + } + + return result +} From d5f34970ad9c632c06b8b9266bd247872584e8c3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 12:48:28 +0100 Subject: [PATCH 15/27] Use friendly name --- .../builder/src/stores/builder/screenComponent.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index b97bb7ba98..cb7e26bf93 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -53,9 +53,19 @@ function getInvalidDatasources( for (const component of findComponentsBySettingsType(screen, "table")) { const { resourceId, type, label } = component.dataSource if (!datasources[resourceId]) { - result[component._id!] = [`The ${type} named "${label}" was removed`] + const friendlyTypeName = + friendlyNameByType[type as keyof typeof friendlyNameByType] + result[component._id!] = [ + `The ${friendlyTypeName} named "${label}" was removed`, + ] } } return result } + +const friendlyNameByType = { + table: "table", + view: "view", + viewV2: "view", +} From 42e86554c881c01bc3f5fdc49bb83303af7158d7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 15:18:31 +0100 Subject: [PATCH 16/27] Validate views --- .../src/stores/builder/screenComponent.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index cb7e26bf93..dd2f7a8b1c 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -1,5 +1,5 @@ import { derived } from "svelte/store" -import { tables, selectedScreen } from "@/stores/builder" +import { tables, selectedScreen, viewsV2 } from "@/stores/builder" import { DerivedBudiStore } from "../BudiStore" import { findComponentsBySettingsType } from "@/helpers/screen" import { Screen } from "@budibase/types" @@ -17,16 +17,25 @@ export class ScreenComponentStore extends DerivedBudiStore< constructor() { const makeDerivedStore = () => { return derived( - [selectedScreen, tables], - ([$selectedScreen, $tables]): DerivedScreenComponentStore => { + [selectedScreen, tables, viewsV2], + ([$selectedScreen, $tables, $viewsV2]): DerivedScreenComponentStore => { function getErrors() { - const datasources = $tables.list.reduce( - (list, table) => ({ - ...list, - [table._id!]: table, - }), - {} - ) + const datasources = { + ...$tables.list.reduce( + (list, table) => ({ + ...list, + [table._id!]: table, + }), + {} + ), + ...$viewsV2.list.reduce( + (list, view) => ({ + ...list, + [view.id]: view, + }), + {} + ), + } return { ...getInvalidDatasources($selectedScreen, datasources), } From ec930372412b4ffc47878ddbc8de2d16aa2691a5 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 15:19:45 +0100 Subject: [PATCH 17/27] Change error message --- packages/builder/src/stores/builder/screenComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index dd2f7a8b1c..4b591cf0cf 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -65,7 +65,7 @@ function getInvalidDatasources( const friendlyTypeName = friendlyNameByType[type as keyof typeof friendlyNameByType] result[component._id!] = [ - `The ${friendlyTypeName} named "${label}" was removed`, + `The ${friendlyTypeName} named "${label}" does not exist`, ] } } From f32910b033863216393ef6466e6f73d5bdfa47bb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 15:30:04 +0100 Subject: [PATCH 18/27] Clean code --- .../src/stores/builder/screenComponent.ts | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 4b591cf0cf..baa6520832 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -2,7 +2,7 @@ import { derived } from "svelte/store" import { tables, selectedScreen, viewsV2 } from "@/stores/builder" import { DerivedBudiStore } from "../BudiStore" import { findComponentsBySettingsType } from "@/helpers/screen" -import { Screen } from "@budibase/types" +import { Screen, Table, ViewV2 } from "@budibase/types" interface BuilderScreenComponentStore {} @@ -20,22 +20,10 @@ export class ScreenComponentStore extends DerivedBudiStore< [selectedScreen, tables, viewsV2], ([$selectedScreen, $tables, $viewsV2]): DerivedScreenComponentStore => { function getErrors() { - const datasources = { - ...$tables.list.reduce( - (list, table) => ({ - ...list, - [table._id!]: table, - }), - {} - ), - ...$viewsV2.list.reduce( - (list, view) => ({ - ...list, - [view.id]: view, - }), - {} - ), - } + const datasources = flattenTablesAndViews( + $tables.list, + $viewsV2.list + ) return { ...getInvalidDatasources($selectedScreen, datasources), } @@ -54,10 +42,35 @@ export class ScreenComponentStore extends DerivedBudiStore< export const screenComponentStore = new ScreenComponentStore() +function flattenTablesAndViews(tables: Table[], views: ViewV2[]) { + return { + ...tables.reduce( + (list, table) => ({ + ...list, + [table._id!]: table, + }), + {} + ), + ...views.reduce( + (list, view) => ({ + ...list, + [view.id]: view, + }), + {} + ), + } +} + function getInvalidDatasources( screen: Screen, datasources: Record ) { + const friendlyNameByType = { + table: "table", + view: "view", + viewV2: "view", + } + const result: Record = {} for (const component of findComponentsBySettingsType(screen, "table")) { const { resourceId, type, label } = component.dataSource @@ -72,9 +85,3 @@ function getInvalidDatasources( return result } - -const friendlyNameByType = { - table: "table", - view: "view", - viewV2: "view", -} From e1cc8da9dde735579b7f751b4513cdd33784782f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 15:31:16 +0100 Subject: [PATCH 19/27] Clean types --- packages/client/src/stores/derived/componentErrors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/stores/derived/componentErrors.ts b/packages/client/src/stores/derived/componentErrors.ts index 48185de9c3..68e87a061d 100644 --- a/packages/client/src/stores/derived/componentErrors.ts +++ b/packages/client/src/stores/derived/componentErrors.ts @@ -2,5 +2,5 @@ import { derived } from "svelte/store" import { builderStore } from "../builder.js" export const componentErrors = derived([builderStore], ([$builderStore]) => { - return $builderStore.componentErrors as Record + return $builderStore.componentErrors }) From fc599767c21c23abe1d3aa14445fe5c614b42bea Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 21 Jan 2025 16:06:21 +0100 Subject: [PATCH 20/27] Fix imports --- .../src/stores/builder/screenComponent.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index baa6520832..b4eb01a3b7 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -1,5 +1,7 @@ import { derived } from "svelte/store" -import { tables, selectedScreen, viewsV2 } from "@/stores/builder" +import { tables } from "./tables" +import { selectedScreen } from "./screens" +import { viewsV2 } from "./viewsV2" import { DerivedBudiStore } from "../BudiStore" import { findComponentsBySettingsType } from "@/helpers/screen" import { Screen, Table, ViewV2 } from "@budibase/types" @@ -19,18 +21,9 @@ export class ScreenComponentStore extends DerivedBudiStore< return derived( [selectedScreen, tables, viewsV2], ([$selectedScreen, $tables, $viewsV2]): DerivedScreenComponentStore => { - function getErrors() { - const datasources = flattenTablesAndViews( - $tables.list, - $viewsV2.list - ) - return { - ...getInvalidDatasources($selectedScreen, datasources), - } - } - + const datasources = flattenTablesAndViews($tables.list, $viewsV2.list) return { - errors: getErrors(), + errors: getInvalidDatasources($selectedScreen, datasources), } } ) From cad6a08bf87570c2485cafc9538460a2cfee8f7e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 11:14:50 +0100 Subject: [PATCH 21/27] Move reactiviness --- packages/client/src/components/Component.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 7f825c0601..57daf8cd12 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -139,6 +139,7 @@ // Derive definition properties which can all be optional, so need to be // coerced to booleans + $: invalidSettings = $componentErrors[instance._id] $: hasChildren = !!definition?.hasChildren $: showEmptyState = definition?.showEmptyState !== false $: hasMissingRequiredSettings = missingRequiredSettings?.length > 0 @@ -374,9 +375,6 @@ } } - // Check for invalid settings - $: invalidSettings = $componentErrors[id] - // Extracts a map of all context keys which are required by action settings // to provide the functions to evaluate at runtime. This needs done manually // as the action definitions themselves do not specify bindings for action From 4d85006c35e4d9b2643192902d077147069c6de8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 11:53:50 +0100 Subject: [PATCH 22/27] server nodemon, watch .hbs --- packages/server/nodemon.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/nodemon.json b/packages/server/nodemon.json index ac8c38ccb2..9871b9339e 100644 --- a/packages/server/nodemon.json +++ b/packages/server/nodemon.json @@ -7,7 +7,7 @@ "../shared-core/src", "../string-templates/src" ], - "ext": "js,ts,json,svelte", + "ext": "js,ts,json,svelte,hbs", "ignore": [ "**/*.spec.ts", "**/*.spec.js", From 2213cd56c46c8f8204c98cd961dbd39482b57271 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 12:18:55 +0100 Subject: [PATCH 23/27] Remove unnecessary derived --- packages/client/src/components/Component.svelte | 3 +-- packages/client/src/stores/derived/componentErrors.ts | 6 ------ packages/client/src/stores/derived/index.js | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 packages/client/src/stores/derived/componentErrors.ts diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 57daf8cd12..8d79de2ac4 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -23,7 +23,6 @@ appStore, dndComponentPath, dndIsDragging, - componentErrors, } from "stores" import { Helpers } from "@budibase/bbui" import { getActiveConditions, reduceConditionActions } from "utils/conditions" @@ -139,7 +138,7 @@ // Derive definition properties which can all be optional, so need to be // coerced to booleans - $: invalidSettings = $componentErrors[instance._id] + $: invalidSettings = $builderStore.componentErrors[instance._id] $: hasChildren = !!definition?.hasChildren $: showEmptyState = definition?.showEmptyState !== false $: hasMissingRequiredSettings = missingRequiredSettings?.length > 0 diff --git a/packages/client/src/stores/derived/componentErrors.ts b/packages/client/src/stores/derived/componentErrors.ts deleted file mode 100644 index 68e87a061d..0000000000 --- a/packages/client/src/stores/derived/componentErrors.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { derived } from "svelte/store" -import { builderStore } from "../builder.js" - -export const componentErrors = derived([builderStore], ([$builderStore]) => { - return $builderStore.componentErrors -}) diff --git a/packages/client/src/stores/derived/index.js b/packages/client/src/stores/derived/index.js index e7e70d8952..337c73831f 100644 --- a/packages/client/src/stores/derived/index.js +++ b/packages/client/src/stores/derived/index.js @@ -5,4 +5,3 @@ export { currentRole } from "./currentRole.js" export { dndComponentPath } from "./dndComponentPath.js" export { devToolsEnabled } from "./devToolsEnabled.js" export { snippets } from "./snippets.js" -export { componentErrors } from "./componentErrors" From d3b22e461e3f0f328bab54e4b2b58eb6d833e28e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 16:18:29 +0100 Subject: [PATCH 24/27] Handle errors as a part of the instance to avoid extra refreshes --- packages/client/src/components/Component.svelte | 2 +- packages/client/src/stores/screens.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 8d79de2ac4..a86d43c676 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -138,7 +138,7 @@ // Derive definition properties which can all be optional, so need to be // coerced to booleans - $: invalidSettings = $builderStore.componentErrors[instance._id] + $: invalidSettings = instance?._meta?.errors $: hasChildren = !!definition?.hasChildren $: showEmptyState = definition?.showEmptyState !== false $: hasMissingRequiredSettings = missingRequiredSettings?.length > 0 diff --git a/packages/client/src/stores/screens.js b/packages/client/src/stores/screens.js index bc87216660..491d8d4236 100644 --- a/packages/client/src/stores/screens.js +++ b/packages/client/src/stores/screens.js @@ -42,6 +42,14 @@ const createScreenStore = () => { if ($builderStore.layout) { activeLayout = $builderStore.layout } + + // Attach meta + const errors = $builderStore.componentErrors || {} + const attachComponentMeta = component => { + component._meta = { errors: errors[component._id] || [] } + component._children?.forEach(attachComponentMeta) + } + attachComponentMeta(activeScreen.props) } else { // Find the correct screen by matching the current route screens = $appStore.screens || [] From 84c8507bad7e3ff8df572cf389e004f546be264b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 16:20:33 +0100 Subject: [PATCH 25/27] Renames --- packages/client/src/components/Component.svelte | 10 +++++----- .../components/error-states/ComponentErrorState.svelte | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index a86d43c676..236bcb7c7e 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -103,7 +103,7 @@ let settingsDefinition let settingsDefinitionMap let missingRequiredSettings = false - let invalidSettings = false + let componentErrors = false // Temporary styles which can be added in the app preview for things like // DND. We clear these whenever a new instance is received. @@ -138,12 +138,12 @@ // Derive definition properties which can all be optional, so need to be // coerced to booleans - $: invalidSettings = instance?._meta?.errors + $: componentErrors = instance?._meta?.errors $: hasChildren = !!definition?.hasChildren $: showEmptyState = definition?.showEmptyState !== false $: hasMissingRequiredSettings = missingRequiredSettings?.length > 0 $: editable = !!definition?.editable && !hasMissingRequiredSettings - $: hasInvalidSettings = invalidSettings?.length > 0 + $: hasComponentErrors = componentErrors?.length > 0 $: requiredAncestors = definition?.requiredAncestors || [] $: missingRequiredAncestors = requiredAncestors.filter( ancestor => !$component.ancestors.includes(`${BudibasePrefix}${ancestor}`) @@ -152,7 +152,7 @@ $: errorState = hasMissingRequiredSettings || hasMissingRequiredAncestors || - hasInvalidSettings + hasComponentErrors // Interactive components can be selected, dragged and highlighted inside // the builder preview @@ -698,7 +698,7 @@ {:else} diff --git a/packages/client/src/components/error-states/ComponentErrorState.svelte b/packages/client/src/components/error-states/ComponentErrorState.svelte index 8cf1ad6dfb..9eace07018 100644 --- a/packages/client/src/components/error-states/ComponentErrorState.svelte +++ b/packages/client/src/components/error-states/ComponentErrorState.svelte @@ -8,7 +8,7 @@ | { key: string; label: string }[] | undefined export let missingRequiredAncestors: string[] | undefined - export let invalidSettings: string[] | undefined + export let componentErrors: string[] | undefined const component = getContext("component") const { styleable, builderStore } = getContext("sdk") @@ -16,7 +16,7 @@ $: styles = { ...$component.styles, normal: {}, custom: null, empty: true } $: requiredSetting = missingRequiredSettings?.[0] $: requiredAncestor = missingRequiredAncestors?.[0] - $: invalidSetting = invalidSettings?.[0] + $: errorMessage = componentErrors?.[0] {#if $builderStore.inBuilder} @@ -25,8 +25,8 @@ {#if requiredAncestor} - {:else if invalidSetting} - {invalidSetting} + {:else if errorMessage} + {errorMessage} {:else if requiredSetting} {/if} From c9feae9665a33466e9486c7ca97a2f42b067142c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 16:26:09 +0100 Subject: [PATCH 26/27] Simplify derived screenComponentErrors --- .../[screenId]/_components/AppPreview.svelte | 4 +- packages/builder/src/stores/builder/index.js | 4 +- .../src/stores/builder/screenComponent.ts | 112 +++++++----------- 3 files changed, 49 insertions(+), 71 deletions(-) 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 fc0b67f63d..3951c0e902 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 @@ -11,7 +11,7 @@ selectedScreen, hoverStore, componentTreeNodesStore, - screenComponentStore, + screenComponentErrors, snippets, } from "@/stores/builder" import ConfirmDialog from "@/components/common/ConfirmDialog.svelte" @@ -69,7 +69,7 @@ port: window.location.port, }, snippets: $snippets, - componentErrors: $screenComponentStore.errors, + componentErrors: $screenComponentErrors, } // Refresh the preview when required diff --git a/packages/builder/src/stores/builder/index.js b/packages/builder/src/stores/builder/index.js index 23491996d1..892b72c7ab 100644 --- a/packages/builder/src/stores/builder/index.js +++ b/packages/builder/src/stores/builder/index.js @@ -16,7 +16,7 @@ import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js" import { deploymentStore } from "./deployments.js" import { contextMenuStore } from "./contextMenu.js" import { snippets } from "./snippets" -import { screenComponentStore } from "./screenComponent" +import { screenComponentErrors } from "./screenComponent" // Backend import { tables } from "./tables" @@ -68,7 +68,7 @@ export { snippets, rowActions, appPublished, - screenComponentStore, + screenComponentErrors, } export const reset = () => { diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index b4eb01a3b7..19bafeade3 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -2,79 +2,57 @@ import { derived } from "svelte/store" import { tables } from "./tables" import { selectedScreen } from "./screens" import { viewsV2 } from "./viewsV2" -import { DerivedBudiStore } from "../BudiStore" import { findComponentsBySettingsType } from "@/helpers/screen" import { Screen, Table, ViewV2 } from "@budibase/types" -interface BuilderScreenComponentStore {} +export const screenComponentErrors = derived( + [selectedScreen, tables, viewsV2], + ([$selectedScreen, $tables, $viewsV2]): Record => { + function flattenTablesAndViews(tables: Table[], views: ViewV2[]) { + return { + ...tables.reduce( + (list, table) => ({ + ...list, + [table._id!]: table, + }), + {} + ), + ...views.reduce( + (list, view) => ({ + ...list, + [view.id]: view, + }), + {} + ), + } + } -interface DerivedScreenComponentStore extends BuilderScreenComponentStore { - errors: Record -} + function getInvalidDatasources( + screen: Screen, + datasources: Record + ) { + const friendlyNameByType = { + table: "table", + view: "view", + viewV2: "view", + } -export class ScreenComponentStore extends DerivedBudiStore< - BuilderScreenComponentStore, - DerivedScreenComponentStore -> { - constructor() { - const makeDerivedStore = () => { - return derived( - [selectedScreen, tables, viewsV2], - ([$selectedScreen, $tables, $viewsV2]): DerivedScreenComponentStore => { - const datasources = flattenTablesAndViews($tables.list, $viewsV2.list) - return { - errors: getInvalidDatasources($selectedScreen, datasources), - } + const result: Record = {} + for (const component of findComponentsBySettingsType(screen, "table")) { + const { resourceId, type, label } = component.dataSource + if (!datasources[resourceId]) { + const friendlyTypeName = + friendlyNameByType[type as keyof typeof friendlyNameByType] + result[component._id!] = [ + `The ${friendlyTypeName} named "${label}" does not exist`, + ] } - ) + } + + return result } - super({}, makeDerivedStore) + const datasources = flattenTablesAndViews($tables.list, $viewsV2.list) + return getInvalidDatasources($selectedScreen, datasources) } -} - -export const screenComponentStore = new ScreenComponentStore() - -function flattenTablesAndViews(tables: Table[], views: ViewV2[]) { - return { - ...tables.reduce( - (list, table) => ({ - ...list, - [table._id!]: table, - }), - {} - ), - ...views.reduce( - (list, view) => ({ - ...list, - [view.id]: view, - }), - {} - ), - } -} - -function getInvalidDatasources( - screen: Screen, - datasources: Record -) { - const friendlyNameByType = { - table: "table", - view: "view", - viewV2: "view", - } - - const result: Record = {} - for (const component of findComponentsBySettingsType(screen, "table")) { - const { resourceId, type, label } = component.dataSource - if (!datasources[resourceId]) { - const friendlyTypeName = - friendlyNameByType[type as keyof typeof friendlyNameByType] - result[component._id!] = [ - `The ${friendlyTypeName} named "${label}" does not exist`, - ] - } - } - - return result -} +) From 5c9cc915ff9c7e1da7538e282c02b6503bd006e3 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 22 Jan 2025 17:32:34 +0100 Subject: [PATCH 27/27] Remove magic string for settings --- packages/builder/src/helpers/screen.ts | 30 ++++++++++++++----- .../src/stores/builder/screenComponent.ts | 7 +++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/helpers/screen.ts b/packages/builder/src/helpers/screen.ts index e83805a511..71623844de 100644 --- a/packages/builder/src/helpers/screen.ts +++ b/packages/builder/src/helpers/screen.ts @@ -2,7 +2,13 @@ import { Component, Screen, ScreenProps } from "@budibase/types" import clientManifest from "@budibase/client/manifest.json" export function findComponentsBySettingsType(screen: Screen, type: string) { - const result: Component[] = [] + const result: { + component: Component + setting: { + type: string + key: string + } + }[] = [] function recurseFieldComponentsInChildren( component: ScreenProps, type: string @@ -11,14 +17,15 @@ export function findComponentsBySettingsType(screen: Screen, type: string) { return } - const componentType = component._component.split("/").slice(-1)[0] - const definition = - clientManifest[componentType as keyof typeof clientManifest] - if ( + const definition = getManifestDefinition(component) + const setting = "settings" in definition && - definition.settings.some((s: any) => s.type === type) - ) { - result.push(component) + definition.settings.find((s: any) => s.type === type) + if (setting && "type" in setting) { + result.push({ + component, + setting: { type: setting.type!, key: setting.key! }, + }) } component._children?.forEach(child => { recurseFieldComponentsInChildren(child, type) @@ -28,3 +35,10 @@ export function findComponentsBySettingsType(screen: Screen, type: string) { recurseFieldComponentsInChildren(screen?.props, type) 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/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index 19bafeade3..a061158e6a 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -38,8 +38,11 @@ export const screenComponentErrors = derived( } const result: Record = {} - for (const component of findComponentsBySettingsType(screen, "table")) { - const { resourceId, type, label } = component.dataSource + for (const { component, setting } of findComponentsBySettingsType( + screen, + "table" + )) { + const { resourceId, type, label } = component[setting.key] if (!datasources[resourceId]) { const friendlyTypeName = friendlyNameByType[type as keyof typeof friendlyNameByType]