Merge branch 'component-sdk' of github.com:Budibase/budibase into feature/page-refactor
This commit is contained in:
commit
d8b794cb97
|
@ -24,7 +24,7 @@ import { cloneDeep, difference } from "lodash/fp"
|
|||
* @returns {Array.<BindableProperty>}
|
||||
*/
|
||||
export default function({ componentInstanceId, screen, components, tables }) {
|
||||
const walkResult = walk({
|
||||
const result = walk({
|
||||
// cloning so we are free to mutate props (e.g. by adding _contexts)
|
||||
instance: cloneDeep(screen.props),
|
||||
targetId: componentInstanceId,
|
||||
|
@ -33,13 +33,10 @@ export default function({ componentInstanceId, screen, components, tables }) {
|
|||
})
|
||||
|
||||
return [
|
||||
...walkResult.bindableInstances
|
||||
.filter(isInstanceInSharedContext(walkResult))
|
||||
.map(componentInstanceToBindable(walkResult)),
|
||||
|
||||
...(walkResult.target?._contexts
|
||||
.map(contextToBindables(tables, walkResult))
|
||||
.flat() ?? []),
|
||||
...result.bindableInstances
|
||||
.filter(isInstanceInSharedContext(result))
|
||||
.map(componentInstanceToBindable),
|
||||
...(result.target?._contexts.map(contextToBindables(tables)).flat() ?? []),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -53,26 +50,18 @@ const isInstanceInSharedContext = walkResult => i =>
|
|||
|
||||
// turns a component instance prop into binding expressions
|
||||
// used by the UI
|
||||
const componentInstanceToBindable = walkResult => i => {
|
||||
const lastContext =
|
||||
i.instance._contexts.length &&
|
||||
i.instance._contexts[i.instance._contexts.length - 1]
|
||||
const contextParentPath = lastContext
|
||||
? getParentPath(walkResult, lastContext)
|
||||
: ""
|
||||
|
||||
const componentInstanceToBindable = i => {
|
||||
return {
|
||||
type: "instance",
|
||||
instance: i.instance,
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: `${contextParentPath}${i.instance._id}.${i.prop}`,
|
||||
runtimeBinding: `${i.instance._id}`,
|
||||
// how the binding exressions looks to the user of the builder
|
||||
readableBinding: `${i.instance._instanceName}`,
|
||||
}
|
||||
}
|
||||
|
||||
const contextToBindables = (tables, walkResult) => context => {
|
||||
const contextParentPath = getParentPath(walkResult, context)
|
||||
const contextToBindables = tables => context => {
|
||||
const tableId = context.table?.tableId ?? context.table
|
||||
const table = tables.find(table => table._id === tableId)
|
||||
let schema =
|
||||
|
@ -98,7 +87,7 @@ const contextToBindables = (tables, walkResult) => context => {
|
|||
fieldSchema,
|
||||
instance: context.instance,
|
||||
// how the binding expression persists, and is used in the app at runtime
|
||||
runtimeBinding: `${contextParentPath}data.${runtimeBoundKey}`,
|
||||
runtimeBinding: `${context.instance._id}.${runtimeBoundKey}`,
|
||||
// how the binding expressions looks to the user of the builder
|
||||
readableBinding: `${context.instance._instanceName}.${table.name}.${key}`,
|
||||
// table / view info
|
||||
|
@ -118,20 +107,6 @@ const contextToBindables = (tables, walkResult) => context => {
|
|||
)
|
||||
}
|
||||
|
||||
const getParentPath = (walkResult, context) => {
|
||||
// describes the number of "parent" in the path
|
||||
// clone array first so original array is not mtated
|
||||
const contextParentNumber = [...walkResult.target._contexts]
|
||||
.reverse()
|
||||
.indexOf(context)
|
||||
|
||||
return (
|
||||
new Array(contextParentNumber).fill("parent").join(".") +
|
||||
// trailing . if has parents
|
||||
(contextParentNumber ? "." : "")
|
||||
)
|
||||
}
|
||||
|
||||
const walk = ({ instance, targetId, components, tables, result }) => {
|
||||
if (!result) {
|
||||
result = {
|
||||
|
|
|
@ -12,10 +12,7 @@ export function readableToRuntimeBinding(bindableProperties, textWithBindings) {
|
|||
return boundValue === `{{ ${readableBinding} }}`
|
||||
})
|
||||
if (binding) {
|
||||
result = textWithBindings.replace(
|
||||
boundValue,
|
||||
`{{ ${binding.runtimeBinding} }}`
|
||||
)
|
||||
result = result.replace(boundValue, `{{ ${binding.runtimeBinding} }}`)
|
||||
}
|
||||
})
|
||||
return result
|
||||
|
|
|
@ -1,28 +1,13 @@
|
|||
<script>
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import { map, join } from "lodash/fp"
|
||||
import { onMount } from "svelte"
|
||||
import { store } from "builderStore"
|
||||
import iframeTemplate from "./iframeTemplate"
|
||||
import { pipe } from "../../../helpers"
|
||||
import { Screen } from "../../../builderStore/store/screenTemplates/utils/Screen"
|
||||
import { Component } from "../../../builderStore/store/screenTemplates/utils/Component"
|
||||
import { Screen } from "builderStore/store/screenTemplates/utils/Screen"
|
||||
import { Component } from "builderStore/store/screenTemplates/utils/Component"
|
||||
|
||||
let iframe
|
||||
let styles = ""
|
||||
|
||||
function transform_component(comp) {
|
||||
const props = comp.props || comp
|
||||
if (props && props._children && props._children.length) {
|
||||
props._children = props._children.map(transform_component)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
const getComponentTypeName = component => {
|
||||
let [componentName] = component._component.match(/[a-zA-Z]*$/)
|
||||
return componentName || "element"
|
||||
}
|
||||
|
||||
// Styles for screenslot placeholder
|
||||
const headingStyle = {
|
||||
width: "500px",
|
||||
padding: "8px",
|
||||
|
@ -70,71 +55,39 @@
|
|||
// TODO: this ID is attached to how the screen slot is rendered, confusing, would be better a type etc
|
||||
screenPlaceholder.props._id = "screenslot-placeholder"
|
||||
|
||||
$: hasComponent = !!$store.currentPreviewItem
|
||||
|
||||
$: {
|
||||
styles = ""
|
||||
// Apply the CSS from the currently selected page and its screens
|
||||
const currentPage = $store.pages[$store.currentPageName]
|
||||
styles += currentPage._css
|
||||
for (let screen of currentPage._screens) {
|
||||
styles += screen._css
|
||||
}
|
||||
styles = styles
|
||||
}
|
||||
|
||||
$: stylesheetLinks = pipe($store.pages.stylesheets, [
|
||||
map(s => `<link rel="stylesheet" href="${s}"/>`),
|
||||
join("\n"),
|
||||
])
|
||||
|
||||
$: screensExist =
|
||||
$store.currentPreviewItem._screens &&
|
||||
$store.currentPreviewItem._screens.length > 0
|
||||
|
||||
$: frontendDefinition = {
|
||||
appId: $store.appId,
|
||||
libraries: $store.libraries,
|
||||
page: $store.pages[$store.currentPageName],
|
||||
screens: [
|
||||
// Extract data to pass to the iframe
|
||||
$: page = $store.pages[$store.currentPageName]
|
||||
$: screen =
|
||||
$store.currentFrontEndType === "page"
|
||||
? screenPlaceholder
|
||||
: $store.currentPreviewItem,
|
||||
],
|
||||
}
|
||||
|
||||
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
|
||||
|
||||
$: selectedComponentId = $store.currentComponentInfo
|
||||
? $store.currentComponentInfo._id
|
||||
: ""
|
||||
|
||||
const refreshContent = () => {
|
||||
iframe.contentWindow.postMessage(
|
||||
JSON.stringify({
|
||||
styles,
|
||||
stylesheetLinks,
|
||||
selectedComponentType,
|
||||
: $store.currentPreviewItem
|
||||
$: selectedComponentId = $store.currentComponentInfo?._id ?? ""
|
||||
$: previewData = {
|
||||
page,
|
||||
screen,
|
||||
selectedComponentId,
|
||||
frontendDefinition,
|
||||
appId: $store.appId,
|
||||
instanceId: $backendUiStore.selectedDatabase._id,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
$: if (iframe)
|
||||
// Update the iframe with the builder info to render the correct preview
|
||||
const refreshContent = () => {
|
||||
if (iframe) {
|
||||
iframe.contentWindow.postMessage(JSON.stringify(previewData))
|
||||
}
|
||||
}
|
||||
|
||||
// Refrech the preview when required
|
||||
$: refreshContent(previewData)
|
||||
|
||||
// Initialise the app when mounted
|
||||
onMount(() => {
|
||||
iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
|
||||
once: true,
|
||||
})
|
||||
|
||||
$: if (iframe && frontendDefinition) {
|
||||
refreshContent()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="component-container">
|
||||
{#if hasComponent && $store.currentPreviewItem}
|
||||
{#if $store.currentPreviewItem}
|
||||
<iframe
|
||||
style="height: 100%; width: 100%"
|
||||
title="componentPreview"
|
||||
|
@ -152,7 +105,6 @@
|
|||
margin: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.component-container iframe {
|
||||
border: 0;
|
||||
left: 0;
|
||||
|
|
|
@ -11,7 +11,7 @@ export default `<html>
|
|||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
[data-bb-id="container-screenslot-placeholder"] {
|
||||
[data-bb-id="screenslot-placeholder"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -23,7 +23,7 @@ export default `<html>
|
|||
background-color: rgba(0, 0, 0, 0.05);
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
[data-bb-id="container-screenslot-placeholder"] span {
|
||||
[data-bb-id="screenslot-placeholder"] span {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -31,45 +31,46 @@ export default `<html>
|
|||
<script src='/assets/budibase-client.js'></script>
|
||||
<script>
|
||||
function receiveMessage(event) {
|
||||
if (!event.data) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!event.data) return
|
||||
// Extract data from message
|
||||
const { selectedComponentId, page, screen } = JSON.parse(event.data)
|
||||
|
||||
const data = JSON.parse(event.data)
|
||||
|
||||
try {
|
||||
if (styles) document.head.removeChild(styles)
|
||||
} catch(_) { }
|
||||
|
||||
try {
|
||||
if (selectedComponentStyle) document.head.removeChild(selectedComponentStyle)
|
||||
} catch(_) { }
|
||||
|
||||
selectedComponentStyle = document.createElement('style');
|
||||
// Update selected component style
|
||||
if (selectedComponentStyle) {
|
||||
document.head.removeChild(selectedComponentStyle)
|
||||
}
|
||||
selectedComponentStyle = document.createElement("style");
|
||||
document.head.appendChild(selectedComponentStyle)
|
||||
var selectedCss = '[data-bb-id="' + data.selectedComponentType + '-' + data.selectedComponentId + '"]' + '{border:2px solid #0055ff !important;}'
|
||||
var selectedCss = '[data-bb-id="' + selectedComponentId + '"]' + '{border:2px solid #0055ff !important;}'
|
||||
selectedComponentStyle.appendChild(document.createTextNode(selectedCss))
|
||||
|
||||
styles = document.createElement('style')
|
||||
document.head.appendChild(styles)
|
||||
styles.appendChild(document.createTextNode(data.styles))
|
||||
|
||||
// Set some flags so the app knows we're in the builder
|
||||
window["##BUDIBASE_IN_BUILDER##"] = true;
|
||||
window["##BUDIBASE_PREVIEW_PAGE##"] = page;
|
||||
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen;
|
||||
|
||||
// Initialise app
|
||||
if (window.loadBudibase) {
|
||||
loadBudibase()
|
||||
}
|
||||
}
|
||||
let styles
|
||||
|
||||
let selectedComponentStyle
|
||||
|
||||
document.addEventListener("click", function(e) {
|
||||
// Ignore clicks
|
||||
["click", "mousedown"].forEach(type => {
|
||||
document.addEventListener(type, function(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
return false;
|
||||
return false
|
||||
}, true)
|
||||
})
|
||||
|
||||
window.addEventListener('message', receiveMessage)
|
||||
window.dispatchEvent(new Event('bb-ready'))
|
||||
|
||||
window.addEventListener("message", receiveMessage)
|
||||
window.dispatchEvent(new Event("bb-ready"))
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -30,15 +30,13 @@
|
|||
// Extract component definition info
|
||||
const componentName = extractComponentName(definition._component)
|
||||
const constructor = getComponentConstructor(componentName)
|
||||
const id = `${componentName}-${definition._id}`
|
||||
const componentProps = extractValidProps(definition)
|
||||
const dataContext = getContext("data")
|
||||
const enrichedProps = dataContext.actions.enrichDataBindings(componentProps)
|
||||
const children = definition._children
|
||||
|
||||
// Set style context to be consumed by component
|
||||
setContext("style", { ...definition._styles, id })
|
||||
$: console.log("Rendering: " + componentName)
|
||||
// Set contexts to be consumed by component
|
||||
setContext("style", { ...definition._styles, id: definition._id })
|
||||
</script>
|
||||
|
||||
{#if constructor}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
// Get current contexts
|
||||
const dataContext = getContext("data")
|
||||
const { id } = getContext("style")
|
||||
|
||||
// Clone current context to this context
|
||||
const newDataContext = createDataContextStore($dataContext)
|
||||
|
@ -14,7 +15,7 @@
|
|||
// Add additional layer to context
|
||||
let loaded = false
|
||||
onMount(() => {
|
||||
newDataContext.actions.addContext(row)
|
||||
newDataContext.actions.addContext(row, id)
|
||||
loaded = true
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
// Keep route params up to date
|
||||
export let params
|
||||
$: routeStore.actions.setRouteParams(params)
|
||||
$: routeStore.actions.setRouteParams(params || {})
|
||||
|
||||
// Get the screen definition for the current route
|
||||
$: screenDefinition = $screenStore.activeScreen?.props
|
||||
|
|
|
@ -3,7 +3,6 @@ import { enrichDataBinding } from "../utils"
|
|||
import { cloneDeep } from "lodash/fp"
|
||||
|
||||
const initialValue = {
|
||||
parent: null,
|
||||
data: null,
|
||||
}
|
||||
|
||||
|
@ -12,15 +11,12 @@ export const createDataContextStore = existingContext => {
|
|||
const store = writable(initial)
|
||||
|
||||
// Adds a context layer to the data context tree
|
||||
const addContext = row => {
|
||||
const addContext = (row, componentId) => {
|
||||
store.update(state => {
|
||||
if (state.data) {
|
||||
state.parent = {
|
||||
parent: state.parent,
|
||||
data: state.data,
|
||||
}
|
||||
}
|
||||
if (row && componentId) {
|
||||
state[componentId] = row
|
||||
state.data = row
|
||||
}
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
|
|
@ -24,11 +24,20 @@ const createScreenStore = () => {
|
|||
})
|
||||
|
||||
const fetchScreens = async () => {
|
||||
let screens
|
||||
let page
|
||||
const inBuilder = !!window["##BUDIBASE_IN_BUILDER##"]
|
||||
if (inBuilder) {
|
||||
// Load screen and page from the window object if in the builder
|
||||
screens = [window["##BUDIBASE_PREVIEW_SCREEN##"]]
|
||||
page = window["##BUDIBASE_PREVIEW_PAGE##"]
|
||||
} else {
|
||||
// Otherwise load from API
|
||||
const appDefinition = await API.fetchAppDefinition(getAppId())
|
||||
config.set({
|
||||
screens: appDefinition.screens,
|
||||
page: appDefinition.page,
|
||||
})
|
||||
screens = appDefinition.screens
|
||||
page = appDefinition.page
|
||||
}
|
||||
config.set({ screens, page })
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -28,8 +28,5 @@ export const enrichDataBinding = (input, context) => {
|
|||
if (!looksLikeMustache.test(input)) {
|
||||
return input
|
||||
}
|
||||
console.log("====================================")
|
||||
console.log(input)
|
||||
console.log(context)
|
||||
return mustache.render(input, context)
|
||||
}
|
||||
|
|
|
@ -130,15 +130,6 @@
|
|||
"form"
|
||||
]
|
||||
},
|
||||
"select": {
|
||||
"name": "Select",
|
||||
"bindable": "value",
|
||||
"description": "An HTML <select> (dropdown)",
|
||||
"props": {
|
||||
"value": "string",
|
||||
"className": "string"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
"name": "Option",
|
||||
"description": "An HTML <option>, to be used with <select>",
|
||||
|
@ -181,28 +172,6 @@
|
|||
"value": "string"
|
||||
}
|
||||
},
|
||||
"checkbox": {
|
||||
"name": "Checkbox",
|
||||
"bindable": "value",
|
||||
"description": "A selectable checkbox component",
|
||||
"props": {
|
||||
"label": "string",
|
||||
"checked": "bool",
|
||||
"value": "string",
|
||||
"onchange": "event"
|
||||
}
|
||||
},
|
||||
"radiobutton": {
|
||||
"name": "Radiobutton",
|
||||
"bindable": "value",
|
||||
"description": "A selectable radiobutton component",
|
||||
"props": {
|
||||
"label": "string",
|
||||
"checked": "bool",
|
||||
"value": "string",
|
||||
"onchange": "event"
|
||||
}
|
||||
},
|
||||
"icon": {
|
||||
"description": "A HTML icon tag",
|
||||
"props": {
|
||||
|
@ -687,7 +656,8 @@
|
|||
"description": "Date Picker",
|
||||
"bindable": "value",
|
||||
"props": {
|
||||
"placeholder": "string"
|
||||
"placeholder": "string",
|
||||
"value": "string"
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
// if srcdoc, then we assume this is the builder preview
|
||||
if ((pathParts.length === 0 || pathParts[0] === "srcdoc") && table) {
|
||||
console.log("getting first row")
|
||||
row = await fetchFirstRow()
|
||||
} else if (routeParamId) {
|
||||
row = await API.fetchRow({ tableId: table, rowId: routeParamId })
|
||||
|
|
|
@ -89,4 +89,4 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<ApexChart {options} {styles} />
|
||||
<ApexChart {options} />
|
||||
|
|
|
@ -71,4 +71,4 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<ApexChart {options} {styles} />
|
||||
<ApexChart {options} />
|
||||
|
|
|
@ -97,4 +97,4 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<ApexChart {options} {styles} />
|
||||
<ApexChart {options} />
|
||||
|
|
|
@ -63,4 +63,4 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<ApexChart {options} {styles} />
|
||||
<ApexChart {options} />
|
||||
|
|
Loading…
Reference in New Issue