Merge branch 'master' into fix/view-v1-on-tables
This commit is contained in:
commit
8b7a33c89d
|
@ -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.`,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { Component, Screen, ScreenProps } from "@budibase/types"
|
||||
import clientManifest from "@budibase/client/manifest.json"
|
||||
|
||||
export function findComponentsBySettingsType(screen: Screen, type: string) {
|
||||
const result: {
|
||||
component: Component
|
||||
setting: {
|
||||
type: string
|
||||
key: string
|
||||
}
|
||||
}[] = []
|
||||
function recurseFieldComponentsInChildren(
|
||||
component: ScreenProps,
|
||||
type: string
|
||||
) {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
const definition = getManifestDefinition(component)
|
||||
const setting =
|
||||
"settings" in definition &&
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
$: useAccountPortal = cloud && !$admin.disableAccountPortal
|
||||
|
||||
navigation.actions.init($redirect)
|
||||
navigation.init($redirect)
|
||||
|
||||
const validateTenantId = async () => {
|
||||
const host = window.location.host
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
selectedScreen,
|
||||
hoverStore,
|
||||
componentTreeNodesStore,
|
||||
screenComponentErrors,
|
||||
snippets,
|
||||
} from "@/stores/builder"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
|
@ -68,6 +69,7 @@
|
|||
port: window.location.port,
|
||||
},
|
||||
snippets: $snippets,
|
||||
componentErrors: $screenComponentErrors,
|
||||
}
|
||||
|
||||
// Refresh the preview when required
|
||||
|
|
|
@ -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 { screenComponentErrors } from "./screenComponent"
|
||||
|
||||
// Backend
|
||||
import { tables } from "./tables"
|
||||
|
@ -67,6 +68,7 @@ export {
|
|||
snippets,
|
||||
rowActions,
|
||||
appPublished,
|
||||
screenComponentErrors,
|
||||
}
|
||||
|
||||
export const reset = () => {
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import { derived } from "svelte/store"
|
||||
import { tables } from "./tables"
|
||||
import { selectedScreen } from "./screens"
|
||||
import { viewsV2 } from "./viewsV2"
|
||||
import { findComponentsBySettingsType } from "@/helpers/screen"
|
||||
import { Screen, Table, ViewV2 } from "@budibase/types"
|
||||
|
||||
export const screenComponentErrors = derived(
|
||||
[selectedScreen, tables, viewsV2],
|
||||
([$selectedScreen, $tables, $viewsV2]): Record<string, string[]> => {
|
||||
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<string, any>
|
||||
) {
|
||||
const friendlyNameByType = {
|
||||
table: "table",
|
||||
view: "view",
|
||||
viewV2: "view",
|
||||
}
|
||||
|
||||
const result: Record<string, string[]> = {}
|
||||
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]
|
||||
result[component._id!] = [
|
||||
`The ${friendlyTypeName} named "${label}" does not exist`,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const datasources = flattenTablesAndViews($tables.list, $viewsV2.list)
|
||||
return getInvalidDatasources($selectedScreen, datasources)
|
||||
}
|
||||
)
|
|
@ -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,16 +46,7 @@ describe("admin store", () => {
|
|||
ctx.writableReturn = { update: vi.fn(), subscribe: vi.fn() }
|
||||
writable.mockReturnValue(ctx.writableReturn)
|
||||
|
||||
ctx.returnedStore = createAdminStore()
|
||||
})
|
||||
|
||||
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(),
|
||||
})
|
||||
ctx.returnedStore = new AdminStore()
|
||||
})
|
||||
|
||||
describe("init method", () => {
|
||||
|
|
|
@ -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<PortalAdminStore>({
|
||||
loaded: false,
|
||||
multiTenancy: false,
|
||||
cloud: false,
|
||||
isDev: false,
|
||||
disableAccountPortal: false,
|
||||
offlineMode: false,
|
||||
maintenance: [],
|
||||
})
|
||||
export class AdminStore extends BudiStore<AdminState> {
|
||||
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()
|
||||
|
|
|
@ -13,7 +13,7 @@ interface PortalAuditLogsStore {
|
|||
logs?: SearchAuditLogsResponse
|
||||
}
|
||||
|
||||
export class AuditLogsStore extends BudiStore<PortalAuditLogsStore> {
|
||||
class AuditLogsStore extends BudiStore<PortalAuditLogsStore> {
|
||||
constructor() {
|
||||
super({})
|
||||
}
|
||||
|
|
|
@ -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<PortalNavigationStore>({
|
||||
initialisated: false,
|
||||
goto: undefined as any,
|
||||
})
|
||||
const { set, subscribe } = store
|
||||
class NavigationStore extends BudiStore<NavigationState> {
|
||||
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()
|
||||
|
|
|
@ -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()
|
|
@ -0,0 +1,16 @@
|
|||
import { API } from "@/api"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import { TemplateMetadata } from "@budibase/types"
|
||||
|
||||
class TemplateStore extends BudiStore<TemplateMetadata[]> {
|
||||
constructor() {
|
||||
super([])
|
||||
}
|
||||
|
||||
async load() {
|
||||
const templates = await API.getAppTemplates()
|
||||
this.set(templates)
|
||||
}
|
||||
}
|
||||
|
||||
export const templates = new TemplateStore()
|
|
@ -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()
|
|
@ -0,0 +1,53 @@
|
|||
import { get } from "svelte/store"
|
||||
import { BudiStore, PersistenceType } from "../BudiStore"
|
||||
|
||||
type TemporalItem = Record<string, any> & { expiry: number }
|
||||
type TemporalState = Record<string, TemporalItem>
|
||||
|
||||
class TemporalStore extends BudiStore<TemporalState> {
|
||||
constructor() {
|
||||
super(
|
||||
{},
|
||||
{
|
||||
persistence: {
|
||||
key: "bb-temporal",
|
||||
type: PersistenceType.LOCAL,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
setExpiring = (
|
||||
key: string,
|
||||
data: Record<string, any>,
|
||||
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()
|
|
@ -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()
|
|
@ -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<ThemeState, ThemeState> {
|
||||
constructor() {
|
||||
const makeDerivedStore = (store: Writable<ThemeState>) => {
|
||||
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()
|
|
@ -103,6 +103,7 @@
|
|||
let settingsDefinition
|
||||
let settingsDefinitionMap
|
||||
let missingRequiredSettings = 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.
|
||||
|
@ -137,16 +138,21 @@
|
|||
|
||||
// Derive definition properties which can all be optional, so need to be
|
||||
// coerced to booleans
|
||||
$: componentErrors = instance?._meta?.errors
|
||||
$: hasChildren = !!definition?.hasChildren
|
||||
$: showEmptyState = definition?.showEmptyState !== false
|
||||
$: hasMissingRequiredSettings = missingRequiredSettings?.length > 0
|
||||
$: editable = !!definition?.editable && !hasMissingRequiredSettings
|
||||
$: hasComponentErrors = componentErrors?.length > 0
|
||||
$: requiredAncestors = definition?.requiredAncestors || []
|
||||
$: missingRequiredAncestors = requiredAncestors.filter(
|
||||
ancestor => !$component.ancestors.includes(`${BudibasePrefix}${ancestor}`)
|
||||
)
|
||||
$: hasMissingRequiredAncestors = missingRequiredAncestors?.length > 0
|
||||
$: errorState = hasMissingRequiredSettings || hasMissingRequiredAncestors
|
||||
$: errorState =
|
||||
hasMissingRequiredSettings ||
|
||||
hasMissingRequiredAncestors ||
|
||||
hasComponentErrors
|
||||
|
||||
// Interactive components can be selected, dragged and highlighted inside
|
||||
// the builder preview
|
||||
|
@ -692,6 +698,7 @@
|
|||
<ComponentErrorState
|
||||
{missingRequiredSettings}
|
||||
{missingRequiredAncestors}
|
||||
{componentErrors}
|
||||
/>
|
||||
{:else}
|
||||
<svelte:component this={constructor} bind:this={ref} {...initialSettings}>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
| { key: string; label: string }[]
|
||||
| undefined
|
||||
export let missingRequiredAncestors: string[] | undefined
|
||||
export let componentErrors: string[] | undefined
|
||||
|
||||
const component = getContext("component")
|
||||
const { styleable, builderStore } = getContext("sdk")
|
||||
|
@ -15,6 +16,7 @@
|
|||
$: styles = { ...$component.styles, normal: {}, custom: null, empty: true }
|
||||
$: requiredSetting = missingRequiredSettings?.[0]
|
||||
$: requiredAncestor = missingRequiredAncestors?.[0]
|
||||
$: errorMessage = componentErrors?.[0]
|
||||
</script>
|
||||
|
||||
{#if $builderStore.inBuilder}
|
||||
|
@ -23,6 +25,8 @@
|
|||
<Icon name="Alert" color="var(--spectrum-global-color-static-red-600)" />
|
||||
{#if requiredAncestor}
|
||||
<MissingRequiredAncestor {requiredAncestor} />
|
||||
{:else if errorMessage}
|
||||
{errorMessage}
|
||||
{:else if requiredSetting}
|
||||
<MissingRequiredSetting {requiredSetting} />
|
||||
{/if}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,6 +19,7 @@ const createBuilderStore = () => {
|
|||
eventResolvers: {},
|
||||
metadata: null,
|
||||
snippets: null,
|
||||
componentErrors: {},
|
||||
|
||||
// Legacy - allow the builder to specify a layout
|
||||
layout: null,
|
||||
|
|
|
@ -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 || []
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue