Initial work on support dragging new components on to grid screens and typing improvements for client

This commit is contained in:
Andrew Kingston 2025-02-05 11:01:09 +00:00
parent 7b13b54b52
commit 73d3ce2038
No known key found for this signature in database
16 changed files with 430 additions and 236 deletions

View File

@ -13,6 +13,11 @@
import { fly } from "svelte/transition"
import { findComponentPath } from "@/helpers/components"
// Smallest possible 1x1 transparent GIF
const ghost = new Image(1, 1)
ghost.src =
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
let searchString
let searchRef
let selectedIndex
@ -217,7 +222,8 @@
}
})
const onDragStart = component => {
const onDragStart = (e, component) => {
e.dataTransfer.setDragImage(ghost, 0, 0)
previewStore.startDrag(component)
}
@ -250,7 +256,7 @@
{#each category.children as component}
<div
draggable="true"
on:dragstart={() => onDragStart(component.component)}
on:dragstart={e => onDragStart(e, component.component)}
on:dragend={onDragEnd}
class="component"
class:selected={selectedIndex === orderMap[component.component]}
@ -308,7 +314,7 @@
}
.component:hover {
background: var(--spectrum-global-color-gray-300);
cursor: pointer;
cursor: grab;
}
.component :global(.spectrum-Body) {
line-height: 1.2 !important;

View File

@ -39,7 +39,7 @@ interface AppMetaState {
appInstance: { _id: string } | null
initialised: boolean
hasAppPackage: boolean
usedPlugins: Plugin[] | null
usedPlugins: Plugin[]
automations: AutomationSettings
routes: { [key: string]: any }
version?: string
@ -76,7 +76,7 @@ export const INITIAL_APP_META_STATE: AppMetaState = {
appInstance: null,
initialised: false,
hasAppPackage: false,
usedPlugins: null,
usedPlugins: [],
automations: {},
routes: {},
}
@ -109,7 +109,7 @@ export class AppMetaStore extends BudiStore<AppMetaState> {
appInstance: app.instance,
revertableVersion: app.revertableVersion,
upgradableVersion: app.upgradableVersion,
usedPlugins: app.usedPlugins || null,
usedPlugins: app.usedPlugins || [],
icon: app.icon,
features: {
...INITIAL_APP_META_STATE.features,

View File

@ -139,10 +139,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
/**
* Retrieve the component definition object
* @param {string} componentType
* @example
* '@budibase/standard-components/container'
* @returns {object}
*/
getDefinition(componentType: string) {
if (!componentType) {
@ -151,10 +147,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
return get(this.store).components[componentType]
}
/**
*
* @returns {object}
*/
getDefaultDatasource() {
// Ignore users table
const validTables = get(tables).list.filter(x => x._id !== "ta_users")
@ -188,8 +180,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
/**
* Takes an enriched component instance and applies any required migration
* logic
* @param {object} enrichedComponent
* @returns {object} migrated Component
*/
migrateSettings(enrichedComponent: Component) {
const componentPrefix = "@budibase/standard-components"
@ -230,22 +220,15 @@ export class ComponentStore extends BudiStore<ComponentState> {
for (let setting of filterableTypes || []) {
const isLegacy = Array.isArray(enrichedComponent[setting.key])
if (isLegacy) {
const processedSetting = utils.processSearchFilters(
enrichedComponent[setting.key] = utils.processSearchFilters(
enrichedComponent[setting.key]
)
enrichedComponent[setting.key] = processedSetting
migrated = true
}
}
return migrated
}
/**
*
* @param {object} component
* @param {object} opts
* @returns
*/
enrichEmptySettings(
component: Component,
opts: { screen?: Screen; parent?: Component; useDefaultValues?: boolean }
@ -427,17 +410,10 @@ export class ComponentStore extends BudiStore<ComponentState> {
}
}
/**
*
* @param {string} componentName
* @param {object} presetProps
* @param {object} parent
* @returns
*/
createInstance(
componentType: string,
presetProps: any,
parent: any
presetProps?: Record<string, any>,
parent?: Component
): Component | null {
const screen = get(selectedScreen)
if (!screen) {
@ -463,7 +439,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
_id: Helpers.uuid(),
_component: definition.component,
_styles: {
normal: {},
normal: { ...presetProps?._styles?.normal },
hover: {},
active: {},
},
@ -512,19 +488,11 @@ export class ComponentStore extends BudiStore<ComponentState> {
}
}
/**
*
* @param {string} componentName
* @param {object} presetProps
* @param {object} parent
* @param {number} index
* @returns
*/
async create(
componentType: string,
presetProps: any,
parent: Component,
index: number
presetProps?: Record<string, any>,
parent?: Component,
index?: number
) {
const state = get(this.store)
const componentInstance = this.createInstance(
@ -611,13 +579,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
return componentInstance
}
/**
*
* @param {function} patchFn
* @param {string} componentId
* @param {string} screenId
* @returns
*/
async patch(
patchFn: (component: Component, screen: Screen) => any,
componentId?: string,
@ -652,11 +613,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
await screenStore.patch(patchScreen, screenId)
}
/**
*
* @param {object} component
* @returns
*/
async delete(component: Component) {
if (!component) {
return
@ -737,13 +693,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
})
}
/**
*
* @param {object} targetComponent
* @param {string} mode
* @param {object} targetScreen
* @returns
*/
async paste(
targetComponent: Component,
mode: string,
@ -1101,6 +1050,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
async updateStyles(styles: Record<string, string>, id: string) {
const patchFn = (component: Component) => {
delete component._placeholder
component._styles.normal = {
...component._styles.normal,
...styles,
@ -1231,7 +1181,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
}
// Create new parent instance
const newParentDefinition = this.createInstance(parentType, null, parent)
const newParentDefinition = this.createInstance(parentType)
if (!newParentDefinition) {
return
}
@ -1267,10 +1217,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
/**
* Check if the components settings have been cached
* @param {string} componentType
* @example
* '@budibase/standard-components/container'
* @returns {boolean}
*/
isCached(componentType: string) {
const settings = get(this.store).settingsCache
@ -1279,11 +1225,6 @@ export class ComponentStore extends BudiStore<ComponentState> {
/**
* Cache component settings
* @param {string} componentType
* @param {object} definition
* @example
* '@budibase/standard-components/container'
* @returns {array} the settings
*/
cacheSettings(componentType: string, definition: ComponentDefinition | null) {
let settings: ComponentSetting[] = []
@ -1313,12 +1254,7 @@ export class ComponentStore extends BudiStore<ComponentState> {
/**
* Retrieve an array of the component settings.
* These settings are cached because they cannot change at run time.
*
* Searches a component's definition for a setting matching a certain predicate.
* @param {string} componentType
* @example
* '@budibase/standard-components/container'
* @returns {Array<object>}
*/
getComponentSettings(componentType: string) {
if (!componentType) {

View File

@ -4,15 +4,13 @@ import { appStore } from "@/stores/builder"
import { BudiStore } from "../BudiStore"
import { AppNavigation, AppNavigationLink, UIObject } from "@budibase/types"
interface BuilderNavigationStore extends AppNavigation {}
export const INITIAL_NAVIGATION_STATE = {
navigation: "Top",
links: [],
textAlign: "Left",
}
export class NavigationStore extends BudiStore<BuilderNavigationStore> {
export class NavigationStore extends BudiStore<AppNavigation> {
constructor() {
super(INITIAL_NAVIGATION_STATE)
}

View File

@ -1,5 +1,7 @@
import { get } from "svelte/store"
import { BudiStore } from "../BudiStore"
import { componentStore } from "./components"
import { selectedScreen } from "./screens"
type PreviewDevice = "desktop" | "tablet" | "mobile"
type PreviewEventHandler = (name: string, payload?: any) => void
@ -54,10 +56,21 @@ export class PreviewStore extends BudiStore<PreviewState> {
}))
}
startDrag(component: any) {
async startDrag(componentType: string) {
let componentId
const gridScreen = get(selectedScreen)?.props?.layout === "grid"
if (gridScreen) {
const component = await componentStore.create(componentType, {
_placeholder: true,
_styles: { normal: { opacity: 0 } },
})
componentId = component?._id
}
this.sendEvent("dragging-new-component", {
dragging: true,
component,
componentType,
componentId,
gridScreen,
})
}

View File

@ -5,7 +5,7 @@
"module": "dist/budibase-client.js",
"main": "dist/budibase-client.js",
"type": "module",
"svelte": "src/index.js",
"svelte": "src/index.ts",
"exports": {
".": {
"import": "./dist/budibase-client.js",

View File

@ -44,6 +44,7 @@
import MaintenanceScreen from "components/MaintenanceScreen.svelte"
import SnippetsProvider from "./context/SnippetsProvider.svelte"
import EmbedProvider from "./context/EmbedProvider.svelte"
import GridNewComponentDNDHandler from "components/preview/GridNewComponentDNDHandler.svelte"
// Provide contexts
setContext("sdk", SDK)
@ -266,6 +267,7 @@
{#if $builderStore.inBuilder}
<DNDHandler />
<GridDNDHandler />
<GridNewComponentDNDHandler />
{/if}
</div>
</SnippetsProvider>

View File

@ -0,0 +1,166 @@
<script>
import { onDestroy, getContext } from "svelte"
import { builderStore, componentStore, screenStore } from "stores"
import { Utils, memo } from "@budibase/frontend-core"
import { GridRowHeight } from "constants"
import { GridParams, getGridVar, Devices } from "utils/grid"
const context = getContext("context")
// Smallest possible 1x1 transparent GIF
const ghost = new Image(1, 1)
ghost.src =
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
let dragInfo
let styles = memo()
$: placeholderID = findPlaceholderID($screenStore.activeScreen)
$: handleNewPlaceholderID(placeholderID)
// Grid CSS variables
$: device = $context.device.mobile ? Devices.Mobile : Devices.Desktop
$: vars = {
colStart: getGridVar(device, GridParams.ColStart),
colEnd: getGridVar(device, GridParams.ColEnd),
rowStart: getGridVar(device, GridParams.RowStart),
rowEnd: getGridVar(device, GridParams.RowEnd),
}
// Some memoisation of primitive types for performance
$: id = dragInfo?.id
// Set ephemeral styles
$: instance = componentStore.actions.getComponentInstance(id)
$: $instance?.setEphemeralStyles($styles)
const findPlaceholderID = screen => {
return screen?.props?._children?.find(c => c._placeholder)?._id
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const handleNewPlaceholderID = async placeholderID => {
if (!placeholderID) {
stopDragging()
return
}
await sleep(100)
onDragStart(placeholderID)
}
// Sugar for a combination of both min and max
const minMax = (value, min, max) => Math.min(max, Math.max(min, value))
const processEvent = Utils.domDebounce((mouseX, mouseY) => {
if (!dragInfo?.grid) {
return
}
const { grid, domGrid } = dragInfo
const { startX, startY, rowStart, rowEnd, colStart, colEnd } = grid
if (!domGrid) {
return
}
const cols = parseInt(domGrid.dataset.cols)
const colSize = parseInt(domGrid.dataset.colSize)
const diffX = mouseX - startX
let deltaX = Math.round(diffX / colSize)
const diffY = mouseY - startY
let deltaY = Math.round(diffY / GridRowHeight)
deltaX = minMax(deltaX, 1 - colStart, cols + 1 - colEnd)
deltaY = Math.max(deltaY, 1 - rowStart)
const newStyles = {
[vars.colStart]: colStart + deltaX,
[vars.colEnd]: colEnd + deltaX,
[vars.rowStart]: rowStart + deltaY,
[vars.rowEnd]: rowEnd + deltaY,
opacity: 1,
}
styles.set(newStyles)
})
const handleEvent = e => {
e.preventDefault()
e.stopPropagation()
processEvent(e.clientX, e.clientY)
}
// Callback when initially starting a drag on a draggable component
const onDragStart = id => {
console.log("DRAG START", id)
// Find grid parent and read from DOM
const domComponent = document.getElementsByClassName(id)[0]
const domGrid = domComponent?.closest(".grid")
if (!domGrid) {
return
}
const styles = getComputedStyle(domComponent)
const bounds = domComponent.getBoundingClientRect()
// Show as active
domComponent.classList.add("dragging")
domGrid.classList.add("highlight")
builderStore.actions.selectComponent(id)
// Update state
dragInfo = {
domComponent,
domGrid,
id,
gridId: domGrid.parentNode.dataset.id,
grid: {
startX: bounds.left + bounds.width / 2,
startY: bounds.top + bounds.height / 2,
rowStart: parseInt(styles["grid-row-start"]),
rowEnd: parseInt(styles["grid-row-end"]),
colStart: parseInt(styles["grid-column-start"]),
colEnd: parseInt(styles["grid-column-end"]),
},
}
// Add event handler to clear all drag state when dragging ends
document.addEventListener("dragover", onDrag, false)
// Add event handler to clear all drag state when dragging ends
document.addEventListener("dragEnd", stopDragging, false)
document.addEventListener("drop", stopDragging, false)
}
const onDrag = e => {
if (!dragInfo) {
return
}
handleEvent(e)
}
// Callback when drag stops (whether dropped or not)
const stopDragging = async () => {
if (!dragInfo) {
return
}
console.log("END")
const { id, domGrid, domComponent } = dragInfo
// Reset DOM
domComponent.classList.remove("dragging")
domGrid.classList.remove("highlight")
document.removeEventListener("dragover", onDrag, false)
document.removeEventListener("dragend", stopDragging, false)
document.removeEventListener("drop", stopDragging, false)
// Save changes
if ($styles) {
builderStore.actions.updateStyles($styles, id)
}
// Reset state
dragInfo = null
styles.set(null)
}
onDestroy(() => {
document.removeEventListener("dragover", onDrag, false)
document.removeEventListener("dragend", stopDragging, false)
document.removeEventListener("drop", stopDragging, false)
})
</script>

View File

@ -1,5 +0,0 @@
interface Window {
"##BUDIBASE_APP_ID##": string
"##BUDIBASE_IN_BUILDER##": string
MIGRATING_APP: boolean
}

View File

@ -1,138 +0,0 @@
import ClientApp from "./components/ClientApp.svelte"
import UpdatingApp from "./components/UpdatingApp.svelte"
import {
builderStore,
appStore,
blockStore,
componentStore,
environmentStore,
dndStore,
eventStore,
hoverStore,
stateStore,
} from "./stores"
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
import { get } from "svelte/store"
import { initWebsocket } from "./websocket.js"
// Provide svelte and svelte/internal as globals for custom components
import * as svelte from "svelte"
import * as internal from "svelte/internal"
window.svelte_internal = internal
window.svelte = svelte
// Initialise spectrum icons
loadSpectrumIcons()
let app
const loadBudibase = async () => {
// Update builder store with any builder flags
builderStore.set({
...get(builderStore),
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
previewId: window["##BUDIBASE_PREVIEW_ID##"],
theme: window["##BUDIBASE_PREVIEW_THEME##"],
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
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
// server rendered app HTML
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
// Set the flag used to determine if the app is being loaded via an iframe
appStore.actions.setAppEmbedded(
window["##BUDIBASE_APP_EMBEDDED##"] === "true"
)
if (window.MIGRATING_APP) {
new UpdatingApp({
target: window.document.body,
})
return
}
// Fetch environment info
if (!get(environmentStore)?.loaded) {
await environmentStore.actions.fetchEnvironment()
}
// Register handler for runtime events from the builder
window.handleBuilderRuntimeEvent = (type, data) => {
if (!window["##BUDIBASE_IN_BUILDER##"]) {
return
}
if (type === "event-completed") {
eventStore.actions.resolveEvent(data)
} else if (type === "eject-block") {
const block = blockStore.actions.getBlock(data)
block?.eject()
} else if (type === "dragging-new-component") {
const { dragging, component } = data
if (dragging) {
const definition =
componentStore.actions.getComponentDefinition(component)
dndStore.actions.startDraggingNewComponent({ component, definition })
} else {
dndStore.actions.reset()
}
} else if (type === "request-context") {
const { selectedComponentInstance, screenslotInstance } =
get(componentStore)
const instance = selectedComponentInstance || screenslotInstance
const context = instance?.getDataContext()
let stringifiedContext = null
try {
stringifiedContext = JSON.stringify(context)
} catch (error) {
// Ignore - invalid context
}
eventStore.actions.dispatchEvent("provide-context", {
context: stringifiedContext,
})
} else if (type === "hover-component") {
hoverStore.actions.hoverComponent(data, false)
} else if (type === "builder-meta") {
builderStore.actions.setMetadata(data)
} else if (type === "builder-state") {
const [[key, value]] = Object.entries(data)
stateStore.actions.setValue(key, value)
}
}
// Register any custom components
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
componentStore.actions.registerCustomComponent(component)
})
}
// Make a callback available for custom component bundles to register
// themselves at runtime
window.registerCustomComponent =
componentStore.actions.registerCustomComponent
// Initialise websocket
initWebsocket()
// Create app if one hasn't been created yet
if (!app) {
app = new ClientApp({
target: window.document.body,
})
}
}
// Attach to window so the HTML template can call this when it loads
window.loadBudibase = loadBudibase

View File

@ -1,6 +1,83 @@
import ClientApp from "./components/ClientApp.svelte"
import UpdatingApp from "./components/UpdatingApp.svelte"
import {
builderStore,
appStore,
blockStore,
componentStore,
environmentStore,
dndStore,
eventStore,
hoverStore,
stateStore,
} from "@/stores"
import { get } from "svelte/store"
import { initWebsocket } from "@/websocket"
import { APIClient } from "@budibase/frontend-core"
import type { ActionTypes } from "./constants"
import type { ActionTypes } from "@/constants"
import { Readable } from "svelte/store"
import {
Screen,
Layout,
Theme,
AppCustomTheme,
PreviewDevice,
AppNavigation,
Plugin,
Snippet,
UIComponentError,
CustomComponent,
} from "@budibase/types"
// Provide svelte and svelte/internal as globals for custom components
import * as svelte from "svelte"
// @ts-ignore
import * as internal from "svelte/internal"
window.svelte_internal = internal
window.svelte = svelte
// Initialise spectrum icons
// eslint-disable-next-line local-rules/no-budibase-imports
import loadSpectrumIcons from "@budibase/bbui/spectrum-icons-vite.js"
loadSpectrumIcons()
// Extend global window scope
declare global {
interface Window {
// Data from builder
"##BUDIBASE_APP_ID##"?: string
"##BUDIBASE_IN_BUILDER##"?: true
"##BUDIBASE_PREVIEW_LAYOUT##"?: Layout
"##BUDIBASE_PREVIEW_SCREEN##"?: Screen
"##BUDIBASE_SELECTED_COMPONENT_ID##"?: string
"##BUDIBASE_PREVIEW_ID##"?: number
"##BUDIBASE_PREVIEW_THEME##"?: Theme
"##BUDIBASE_PREVIEW_CUSTOM_THEME##"?: AppCustomTheme
"##BUDIBASE_PREVIEW_DEVICE##"?: PreviewDevice
"##BUDIBASE_APP_EMBEDDED##"?: string // This is a bool wrapped in a string
"##BUDIBASE_PREVIEW_NAVIGATION##"?: AppNavigation
"##BUDIBASE_HIDDEN_COMPONENT_IDS##"?: string[]
"##BUDIBASE_USED_PLUGINS##"?: Plugin[]
"##BUDIBASE_LOCATION##"?: {
protocol: string
hostname: string
port: string
}
"##BUDIBASE_SNIPPETS##"?: Snippet[]
"##BUDIBASE_COMPONENT_ERRORS##"?: Record<string, UIComponentError>[]
"##BUDIBASE_CUSTOM_COMPONENTS##"?: CustomComponent[]
// Other flags
MIGRATING_APP: boolean
// Client additions
handleBuilderRuntimeEvent: (type: string, data: any) => void
registerCustomComponent: typeof componentStore.actions.registerCustomComponent
loadBudibase: typeof loadBudibase
svelte: typeof svelte
svelte_internal: typeof internal
}
}
export interface SDK {
API: APIClient
@ -29,3 +106,119 @@ export type Component = Readable<{
}>
export type Context = Readable<{}>
let app: ClientApp
const loadBudibase = async () => {
// Update builder store with any builder flags
builderStore.set({
...get(builderStore),
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
previewId: window["##BUDIBASE_PREVIEW_ID##"],
theme: window["##BUDIBASE_PREVIEW_THEME##"],
customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"],
previewDevice: window["##BUDIBASE_PREVIEW_DEVICE##"],
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
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
// server rendered app HTML
appStore.actions.setAppId(window["##BUDIBASE_APP_ID##"])
// Set the flag used to determine if the app is being loaded via an iframe
appStore.actions.setAppEmbedded(
window["##BUDIBASE_APP_EMBEDDED##"] === "true"
)
if (window.MIGRATING_APP) {
new UpdatingApp({
target: window.document.body,
})
return
}
// Fetch environment info
if (!get(environmentStore)?.loaded) {
await environmentStore.actions.fetchEnvironment()
}
// Register handler for runtime events from the builder
window.handleBuilderRuntimeEvent = (type, data) => {
if (!window["##BUDIBASE_IN_BUILDER##"]) {
return
}
if (type === "event-completed") {
eventStore.actions.resolveEvent(data)
} else if (type === "eject-block") {
const block = blockStore.actions.getBlock(data)
block?.eject()
} else if (type === "dragging-new-component") {
const { dragging, component, componentId } = data
if (dragging) {
const definition =
componentStore.actions.getComponentDefinition(component)
dndStore.actions.startDraggingNewComponent({
component,
definition,
componentId,
})
} else {
dndStore.actions.reset()
}
} else if (type === "request-context") {
const { selectedComponentInstance, screenslotInstance } =
get(componentStore)
const instance = selectedComponentInstance || screenslotInstance
const context = instance?.getDataContext()
let stringifiedContext = null
try {
stringifiedContext = JSON.stringify(context)
} catch (error) {
// Ignore - invalid context
}
eventStore.actions.dispatchEvent("provide-context", {
context: stringifiedContext,
})
} else if (type === "hover-component") {
hoverStore.actions.hoverComponent(data, false)
} else if (type === "builder-meta") {
builderStore.actions.setMetadata(data)
} else if (type === "builder-state") {
const [[key, value]] = Object.entries(data)
stateStore.actions.setValue(key, value)
}
}
// Register any custom components
if (window["##BUDIBASE_CUSTOM_COMPONENTS##"]) {
window["##BUDIBASE_CUSTOM_COMPONENTS##"].forEach(component => {
componentStore.actions.registerCustomComponent(component)
})
}
// Make a callback available for custom component bundles to register
// themselves at runtime
window.registerCustomComponent =
componentStore.actions.registerCustomComponent
// Initialise websocket
initWebsocket()
// Create app if one hasn't been created yet
if (!app) {
app = new ClientApp({
target: window.document.body,
})
}
}
// Attach to window so the HTML template can call this when it loads
window.loadBudibase = loadBudibase

View File

@ -21,7 +21,11 @@ const createDndStore = () => {
})
}
const startDraggingNewComponent = ({ component, definition }) => {
const startDraggingNewComponent = ({
component,
definition,
componentId,
}) => {
if (!component) {
return
}
@ -38,6 +42,7 @@ const createDndStore = () => {
bounds: { height, width },
index: null,
newComponentType: component,
newComponentId: componentId,
},
})
}
@ -82,6 +87,10 @@ export const dndParent = derivedMemo(dndStore, x => x.drop?.parent)
export const dndIndex = derivedMemo(dndStore, x => x.drop?.index)
export const dndBounds = derivedMemo(dndStore, x => x.source?.bounds)
export const dndIsDragging = derivedMemo(dndStore, x => !!x.source)
export const dndNewComponentId = derivedMemo(
dndStore,
x => x.source?.newComponentId
)
export const dndIsNewComponent = derivedMemo(
dndStore,
x => x.source?.newComponentType != null

View File

@ -2,7 +2,7 @@ export { authStore } from "./auth"
export { appStore } from "./app"
export { notificationStore } from "./notification"
export { routeStore } from "./routes"
export { screenStore } from "./screens"
export { screenStore, isGridScreen } from "./screens"
export { builderStore } from "./builder"
export { dataSourceStore } from "./dataSource"
export { confirmationStore } from "./confirmation"

View File

@ -198,3 +198,7 @@ const createScreenStore = () => {
}
export const screenStore = createScreenStore()
export const isGridScreen = derived(screenStore, $screenStore => {
return $screenStore.activeScreen?.props?.layout === "grid"
})

View File

@ -20,7 +20,7 @@ export default defineConfig(({ mode }) => {
},
build: {
lib: {
entry: "src/index.js",
entry: "src/index.ts",
formats: ["iife"],
outDir: "dist",
name: "budibase_client",
@ -67,10 +67,6 @@ export default defineConfig(({ mode }) => {
find: "constants",
replacement: path.resolve("./src/constants"),
},
{
find: "@/constants",
replacement: path.resolve("./src/constants"),
},
{
find: "sdk",
replacement: path.resolve("./src/sdk"),
@ -87,6 +83,10 @@ export default defineConfig(({ mode }) => {
find: "@budibase/bbui",
replacement: path.resolve("../bbui/src"),
},
{
find: "@",
replacement: path.resolve(__dirname, "src"),
},
],
},
}

View File

@ -2,6 +2,16 @@ export * from "./sidepanel"
export * from "./codeEditor"
export * from "./errors"
export interface CustomComponent {
Component: any
schema: {
type: "component"
metadata: Record<string, any>
schema: ComponentDefinition
}
version: string
}
export interface ComponentDefinition {
component: string
name: string