1
0
Fork 0
mirror of synced 2024-06-28 11:00:55 +12:00

removal of appRoot - appId comes in cookie

This commit is contained in:
Michael Shanks 2020-06-12 20:42:55 +01:00
parent 6cb7e995a2
commit bf1da9e94e
44 changed files with 616 additions and 235 deletions

View file

@ -0,0 +1,21 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View file

@ -5,7 +5,6 @@ import { writable, get } from "svelte/store"
import api from "../api" import api from "../api"
import { DEFAULT_PAGES_OBJECT } from "../../constants" import { DEFAULT_PAGES_OBJECT } from "../../constants"
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents" import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
import { rename } from "components/userInterface/pagesParsing/renameScreen"
import { import {
createProps, createProps,
makePropsSafe, makePropsSafe,
@ -24,6 +23,7 @@ import {
saveCurrentPreviewItem as _saveCurrentPreviewItem, saveCurrentPreviewItem as _saveCurrentPreviewItem,
saveScreenApi as _saveScreenApi, saveScreenApi as _saveScreenApi,
regenerateCssForCurrentScreen, regenerateCssForCurrentScreen,
renameCurrentScreen,
} from "../storeUtils" } from "../storeUtils"
export const getStore = () => { export const getStore = () => {
@ -52,7 +52,6 @@ export const getStore = () => {
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store) store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
store.saveScreen = saveScreen(store) store.saveScreen = saveScreen(store)
store.renameScreen = renameScreen(store)
store.deleteScreen = deleteScreen(store) store.deleteScreen = deleteScreen(store)
store.setCurrentScreen = setCurrentScreen(store) store.setCurrentScreen = setCurrentScreen(store)
store.setCurrentPage = setCurrentPage(store) store.setCurrentPage = setCurrentPage(store)
@ -63,6 +62,7 @@ export const getStore = () => {
store.addChildComponent = addChildComponent(store) store.addChildComponent = addChildComponent(store)
store.selectComponent = selectComponent(store) store.selectComponent = selectComponent(store)
store.setComponentProp = setComponentProp(store) store.setComponentProp = setComponentProp(store)
store.setPageOrScreenProp = setPageOrScreenProp(store)
store.setComponentStyle = setComponentStyle(store) store.setComponentStyle = setComponentStyle(store)
store.setComponentCode = setComponentCode(store) store.setComponentCode = setComponentCode(store)
store.setScreenType = setScreenType(store) store.setScreenType = setScreenType(store)
@ -207,46 +207,6 @@ const deleteScreen = store => name => {
}) })
} }
const renameScreen = store => (oldname, newname) => {
store.update(s => {
const { screens, pages, error, changedScreens } = rename(
s.pages,
s.screens,
oldname,
newname
)
if (error) {
// should really do something with this
return s
}
s.screens = screens
s.pages = pages
if (s.currentPreviewItem.name === oldname)
s.currentPreviewItem.name = newname
const saveAllChanged = async () => {
for (let screenName of changedScreens) {
const changedScreen = getExactComponent(screens, screenName)
await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
}
}
api
.patch(`/_builder/api/${s.appId}/screen`, {
oldname,
newname,
})
.then(() => saveAllChanged())
.then(() => {
_savePage(s)
})
return s
})
}
const savePage = store => async page => { const savePage = store => async page => {
store.update(state => { store.update(state => {
if (state.currentFrontEndType !== "page" || !state.currentPageName) { if (state.currentFrontEndType !== "page" || !state.currentPageName) {
@ -400,6 +360,18 @@ const setComponentProp = store => (name, value) => {
}) })
} }
const setPageOrScreenProp = store => (name, value) => {
store.update(state => {
if (name === "name" && state.currentFrontEndType === "screen") {
state = renameCurrentScreen(value, state)
} else {
state.currentPreviewItem[name] = value
_saveCurrentPreviewItem(state)
}
return state
})
}
const setComponentStyle = store => (type, name, value) => { const setComponentStyle = store => (type, name, value) => {
store.update(state => { store.update(state => {
if (!state.currentComponentInfo._styles) { if (!state.currentComponentInfo._styles) {

View file

@ -45,6 +45,19 @@ export const saveScreenApi = (screen, s) => {
.then(() => savePage(s)) .then(() => savePage(s))
} }
export const renameCurrentScreen = (newname, state) => {
const oldname = state.currentPreviewItem.name
state.currentPreviewItem.name = newname
api.patch(
`/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
{
oldname,
newname,
}
)
return state
}
export const walkProps = (props, action, cancelToken = null) => { export const walkProps = (props, action, cancelToken = null) => {
cancelToken = cancelToken || { cancelled: false } cancelToken = cancelToken || { cancelled: false }
action(props, () => { action(props, () => {

View file

@ -19,9 +19,10 @@
onChange(_value) onChange(_value)
} }
$: displayValues = value && suffix $: displayValues =
? value.map(v => v.replace(new RegExp(`${suffix}$`), "")) value && suffix
: value || [] ? value.map(v => v.replace(new RegExp(`${suffix}$`), ""))
: value || []
</script> </script>
<div class="input-container"> <div class="input-container">

View file

@ -21,7 +21,7 @@
return componentName || "element" return componentName || "element"
} }
const screenPlaceholder = { const screenPlaceholder = {
name: "Screen Placeholder", name: "Screen Placeholder",
route: "*", route: "*",
props: { props: {
@ -60,9 +60,8 @@
}, },
} }
$: hasComponent = !!$store.currentPreviewItem $: hasComponent = !!$store.currentPreviewItem
$: { $: {
styles = "" styles = ""
// Apply the CSS from the currently selected page and its screens // Apply the CSS from the currently selected page and its screens
@ -88,11 +87,10 @@
libraries: $store.libraries, libraries: $store.libraries,
page: $store.pages[$store.currentPageName], page: $store.pages[$store.currentPageName],
screens: [ screens: [
$store.currentFrontEndType === "page" $store.currentFrontEndType === "page"
? screenPlaceholder ? screenPlaceholder
: $store.currentPreviewItem, : $store.currentPreviewItem,
], ],
appRootPath: "",
} }
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo) $: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
@ -102,20 +100,26 @@
: "" : ""
const refreshContent = () => { const refreshContent = () => {
iframe.contentWindow.postMessage(JSON.stringify({ iframe.contentWindow.postMessage(
styles, JSON.stringify({
stylesheetLinks, styles,
selectedComponentType, stylesheetLinks,
selectedComponentId, selectedComponentType,
frontendDefinition, selectedComponentId,
})) frontendDefinition,
appId: $store.appId,
})
)
} }
$: if(iframe) iframe.contentWindow.addEventListener("bb-ready", refreshContent, { once: true }) $: if (iframe)
iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
once: true,
})
$: if(iframe && frontendDefinition) { $: if (iframe && frontendDefinition) {
refreshContent() refreshContent()
} }
</script> </script>
<div class="component-container"> <div class="component-container">

View file

@ -0,0 +1,161 @@
<script>
import { store, backendUiStore } from "builderStore"
import { map, join } from "lodash/fp"
import iframeTemplate from "./iframeTemplate"
import { pipe } from "components/common/core"
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-z]*$/)
return componentName || "element"
}
const screenPlaceholder = {
name: "Screen Placeholder",
route: "*",
props: {
_component: "@budibase/standard-components/container",
type: "div",
_children: [
{
_component: "@budibase/standard-components/container",
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
_id: "__screenslot__text",
_code: "",
className: "",
onLoad: [],
type: "div",
_children: [
{
_component: "@budibase/standard-components/text",
_styles: {
normal: {},
hover: {},
active: {},
selected: {},
},
_id: "__screenslot__text_2",
_code: "",
text: "content",
font: "",
color: "",
textAlign: "inline",
verticalAlign: "inline",
formattingTag: "none",
},
],
},
],
},
}
$: 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: [
$store.currentFrontEndType === "page"
? screenPlaceholder
: $store.currentPreviewItem,
],
}
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
$: selectedComponentId = $store.currentComponentInfo
? $store.currentComponentInfo._id
: ""
const refreshContent = () => {
<<<<<<< HEAD
iframe.contentWindow.postMessage(JSON.stringify({
styles,
stylesheetLinks,
selectedComponentType,
selectedComponentId,
frontendDefinition,
appId: $store.appId
}))
=======
iframe.contentWindow.postMessage(
JSON.stringify({
styles,
stylesheetLinks,
selectedComponentType,
selectedComponentId,
frontendDefinition,
})
)
>>>>>>> master
}
$: if (iframe)
iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
once: true,
})
$: if (iframe && frontendDefinition) {
refreshContent()
}
</script>
<div class="component-container">
{#if hasComponent && $store.currentPreviewItem}
<iframe
style="height: 100%; width: 100%"
title="componentPreview"
bind:this={iframe}
srcdoc={iframeTemplate} />
{/if}
</div>
<style>
.component-container {
grid-row-start: middle;
grid-column-start: middle;
position: relative;
overflow: hidden;
margin: auto;
height: 100%;
}
.component-container iframe {
border: 0;
left: 0;
top: 0;
width: 100%;
}
</style>

View file

@ -44,6 +44,7 @@ export default `<html>
document.head.appendChild(styles) document.head.appendChild(styles)
styles.appendChild(document.createTextNode(data.styles)) styles.appendChild(document.createTextNode(data.styles))
document.cookie = "budibase:appid=" + data.appId
window["##BUDIBASE_FRONTEND_DEFINITION##"] = data.frontendDefinition; window["##BUDIBASE_FRONTEND_DEFINITION##"] = data.frontendDefinition;
if (clientModule) { if (clientModule) {
clientModule.loadBudibase({ window, localStorage }) clientModule.loadBudibase({ window, localStorage })

View file

@ -13,7 +13,6 @@
import CodeEditor from "./CodeEditor.svelte" import CodeEditor from "./CodeEditor.svelte"
import LayoutEditor from "./LayoutEditor.svelte" import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor" import EventsEditor from "./EventsEditor"
import panelStructure from "./temporaryPanelStructure.js" import panelStructure from "./temporaryPanelStructure.js"
import CategoryTab from "./CategoryTab.svelte" import CategoryTab from "./CategoryTab.svelte"
import DesignView from "./DesignView.svelte" import DesignView from "./DesignView.svelte"
@ -37,26 +36,11 @@
//use for getting controls for each component property //use for getting controls for each component property
c => c._component === componentInstance._component c => c._component === componentInstance._component
) || {} ) || {}
let panelDefinition = {} let panelDefinition = {}
$: { $: panelDefinition = componentPropDefinition.properties &&
if(componentPropDefinition.properties) { componentPropDefinition.properties[selectedCategory.value]
if(selectedCategory.value === "design") {
panelDefinition = componentPropDefinition.properties["design"]
}else{
let panelDef = componentPropDefinition.properties["settings"]
if($store.currentFrontEndType === "page" && $store.currentView !== "component") {
panelDefinition = [...page,...panelDef]
}else if($store.currentFrontEndType === "screen" && $store.currentView !== "component") {
panelDefinition = [...screen, ...panelDef]
}else {
panelDefinition = panelDef
}
}
}
}
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.setComponentStyle
const onPropChanged = store.setComponentProp const onPropChanged = store.setComponentProp
@ -102,7 +86,9 @@
{componentInstance} {componentInstance}
{componentDefinition} {componentDefinition}
{panelDefinition} {panelDefinition}
onChange={onPropChanged} /> onChange={onPropChanged}
onScreenPropChange={store.setPageOrScreenProp}
screenOrPageInstance={$store.currentView !== "component" && $store.currentPreviewItem} />
{:else if selectedCategory.value === 'events'} {:else if selectedCategory.value === 'events'}
<EventsEditor component={componentInstance} /> <EventsEditor component={componentInstance} />
{/if} {/if}

View file

@ -9,11 +9,11 @@
const pages = [ const pages = [
{ {
title: "Main", title: "Private",
id: "main", id: "main",
}, },
{ {
title: "Login", title: "Public",
id: "unauthenticated", id: "unauthenticated",
}, },
] ]

View file

@ -2,20 +2,60 @@
import PropertyControl from "./PropertyControl.svelte" import PropertyControl from "./PropertyControl.svelte"
import InputGroup from "../common/Inputs/InputGroup.svelte" import InputGroup from "../common/Inputs/InputGroup.svelte"
import Colorpicker from "../common/Colorpicker.svelte" import Colorpicker from "../common/Colorpicker.svelte"
import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js" import { excludeProps } from "./propertyCategories.js"
import Input from "../common/Input.svelte"
export let panelDefinition = [] export let panelDefinition = []
export let componentDefinition = {} export let componentDefinition = {}
export let componentInstance = {} export let componentInstance = {}
export let onChange = () => {} export let onChange = () => {}
export let onScreenPropChange = () => {}
export let screenOrPageInstance
const propExistsOnComponentDef = prop => prop in componentDefinition.props const propExistsOnComponentDef = prop => prop in componentDefinition.props
function handleChange(key, data) { function handleChange(key, data) {
data.target ? onChange(key, data.target.value) : onChange(key, data) data.target ? onChange(key, data.target.value) : onChange(key, data)
} }
function handleScreenPropChange (name, value) {
onScreenPropChange(name,value)
if(!isPage && name === "name") {
// screen name is changed... change URL
$goto(`./:page/${value}`)
}
}
const screenDefinition = [
{ key: "name", label: "Name", control: Input },
{ key: "description", label: "Description", control: Input },
{ key: "route", label: "Route", control: Input },
]
const pageDefinition = [
{ key: "title", label: "Title", control: Input },
{ key: "favicon", label: "Favicon", control: Input },
]
$: isPage = screenOrPageInstance && screenOrPageInstance.favicon
$: screenOrPageDefinition = isPage ? pageDefinition : screenDefinition
</script> </script>
{#if screenOrPageInstance}
{#each screenOrPageDefinition as def}
<PropertyControl
control={def.control}
label={def.label}
key={def.key}
value={screenOrPageInstance[def.key]}
onChange={handleScreenPropChange}
props={{ ...excludeProps(def, ['control', 'label']) }} />
{/each}
<hr/>
{/if}
{#if panelDefinition && panelDefinition.length > 0} {#if panelDefinition && panelDefinition.length > 0}
{#each panelDefinition as definition} {#each panelDefinition as definition}
{#if propExistsOnComponentDef(definition.key)} {#if propExistsOnComponentDef(definition.key)}

View file

@ -12,8 +12,9 @@ export const layout = [
label: "Display", label: "Display",
key: "display", key: "display",
control: OptionSelect, control: OptionSelect,
initialValue: "Flex", initialValue: "",
options: [ options: [
{ label: "", value: "" },
{ label: "Flex", value: "flex" }, { label: "Flex", value: "flex" },
{ label: "Inline Flex", value: "inline-flex" }, { label: "Inline Flex", value: "inline-flex" },
], ],
@ -39,6 +40,7 @@ export const layout = [
control: OptionSelect, control: OptionSelect,
initialValue: "Flex Start", initialValue: "Flex Start",
options: [ options: [
{ label: "", value: "" },
{ label: "Flex Start", value: "flex-start" }, { label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" }, { label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" }, { label: "Center", value: "center" },
@ -317,19 +319,31 @@ export const border = [
{ {
label: "Radius", label: "Radius",
key: "border-radius", key: "border-radius",
control: Input, control: OptionSelect,
width: "48px", defaultValue: "None",
placeholder: "px", options: [
textAlign: "center", { label: "None", value: "0" },
{ label: "small", value: "0.125rem" },
{ label: "Medium", value: "0.25rem;" },
{ label: "Large", value: "0.375rem" },
{ label: "Extra large", value: "0.5rem" },
{ label: "Full", value: "5678px" },
],
}, },
{ {
label: "Width", label: "Width",
key: "border-width", key: "border-width",
control: Input, control: OptionSelect,
width: "48px", defaultValue: "None",
placeholder: "px", options: [
textAlign: "center", { label: "None", value: "0" },
}, //custom { label: "Extra small", value: "0.5px" },
{ label: "Small", value: "1px" },
{ label: "Medium", value: "2px" },
{ label: "Large", value: "4px" },
{ label: "Extra large", value: "8px" },
],
},
{ {
label: "Color", label: "Color",
key: "border-color", key: "border-color",
@ -339,6 +353,7 @@ export const border = [
label: "Style", label: "Style",
key: "border-style", key: "border-style",
control: OptionSelect, control: OptionSelect,
defaultValue: "None",
options: [ options: [
"none", "none",
"hidden", "hidden",
@ -365,17 +380,50 @@ export const effects = [
}, },
{ {
label: "Rotate", label: "Rotate",
key: "transform", key: "transform-rotate",
control: Input, control: OptionSelect,
width: "48px", defaultValue: "0",
textAlign: "center", options: [
placeholder: "deg", "0",
"45deg",
"90deg",
"90deg",
"135deg",
"180deg",
"225deg",
"270deg",
"315dev",
],
}, //needs special control }, //needs special control
{ {
label: "Shadow", label: "Shadow",
key: "box-shadow", key: "box-shadow",
control: InputGroup, control: OptionSelect,
meta: [{ placeholder: "X" }, { placeholder: "Y" }, { placeholder: "B" }], defaultValue: "None",
options: [
{ label: "None", value: "none" },
{ label: "Extra small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
{
label: "Small",
value:
"0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)",
},
{
label: "Medium",
value:
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
},
{
label: "Large",
value:
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
},
{
label: "Extra large",
value:
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
},
],
}, },
] ]

View file

@ -11,6 +11,18 @@ export default {
name: "Basic", name: "Basic",
isCategory: true, isCategory: true,
children: [ children: [
{
_component: "@budibase/standard-components/embed",
icon: "ri-code-line",
name: "Embed",
description: "Embed content from 3rd party sources",
properties: {
design: {
...all,
},
settings: [{ label: "Embed", key: "embed", control: Input }],
},
},
{ {
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
name: "Container", name: "Container",

View file

@ -1,9 +1,9 @@
import { authenticate } from "./authenticate" import { authenticate } from "./authenticate"
import { triggerWorkflow } from "./workflow" import { triggerWorkflow } from "./workflow"
export const createApi = ({ rootPath = "", setState, getState }) => { export const createApi = ({ setState, getState }) => {
const apiCall = method => async ({ url, body }) => { const apiCall = method => async ({ url, body }) => {
const response = await fetch(`${rootPath}${url}`, { const response = await fetch(url, {
method: method, method: method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -45,7 +45,6 @@ export const createApi = ({ rootPath = "", setState, getState }) => {
const isSuccess = obj => !obj || !obj[ERROR_MEMBER] const isSuccess = obj => !obj || !obj[ERROR_MEMBER]
const apiOpts = { const apiOpts = {
rootPath,
setState, setState,
getState, getState,
isSuccess, isSuccess,

View file

@ -2,6 +2,7 @@ import { attachChildren } from "./render/attachChildren"
import { createTreeNode } from "./render/prepareRenderComponent" import { createTreeNode } from "./render/prepareRenderComponent"
import { screenRouter } from "./render/screenRouter" import { screenRouter } from "./render/screenRouter"
import { createStateManager } from "./state/stateManager" import { createStateManager } from "./state/stateManager"
import { getAppId } from "./render/getAppId"
export const createApp = ({ export const createApp = ({
componentLibraries, componentLibraries,
@ -15,11 +16,9 @@ export const createApp = ({
const onScreenSlotRendered = screenSlotNode => { const onScreenSlotRendered = screenSlotNode => {
const onScreenSelected = (screen, url) => { const onScreenSelected = (screen, url) => {
const stateManager = createStateManager({ const stateManager = createStateManager({
frontendDefinition,
componentLibraries, componentLibraries,
onScreenSlotRendered: () => {}, onScreenSlotRendered: () => {},
routeTo, routeTo,
appRootPath: frontendDefinition.appRootPath,
}) })
const getAttachChildrenParams = attachChildrenParams(stateManager) const getAttachChildrenParams = attachChildrenParams(stateManager)
screenSlotNode.props._children = [screen.props] screenSlotNode.props._children = [screen.props]
@ -36,12 +35,8 @@ export const createApp = ({
routeTo = screenRouter({ routeTo = screenRouter({
screens: frontendDefinition.screens, screens: frontendDefinition.screens,
onScreenSelected, onScreenSelected,
appRootPath: frontendDefinition.appRootPath,
}) })
const fallbackPath = window.location.pathname.replace( const fallbackPath = window.location.pathname.replace(getAppId(), "")
frontendDefinition.appRootPath,
""
)
routeTo(currentUrl || fallbackPath) routeTo(currentUrl || fallbackPath)
} }
@ -59,10 +54,8 @@ export const createApp = ({
let rootTreeNode let rootTreeNode
const pageStateManager = createStateManager({ const pageStateManager = createStateManager({
frontendDefinition,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,
appRootPath: frontendDefinition.appRootPath,
// seems weird, but the routeTo variable may not be available at this point // seems weird, but the routeTo variable may not be available at this point
routeTo: url => routeTo(url), routeTo: url => routeTo(url),
}) })

View file

@ -1,5 +1,6 @@
import { createApp } from "./createApp" import { createApp } from "./createApp"
import { builtins, builtinLibName } from "./render/builtinComponents" import { builtins, builtinLibName } from "./render/builtinComponents"
import { getAppId } from "./render/getAppId"
/** /**
* create a web application from static budibase definition files. * create a web application from static budibase definition files.
@ -8,7 +9,7 @@ import { builtins, builtinLibName } from "./render/builtinComponents"
export const loadBudibase = async opts => { export const loadBudibase = async opts => {
const _window = (opts && opts.window) || window const _window = (opts && opts.window) || window
// const _localStorage = (opts && opts.localStorage) || localStorage // const _localStorage = (opts && opts.localStorage) || localStorage
const appId = getAppId()
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"] const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
const user = {} const user = {}
@ -20,9 +21,7 @@ export const loadBudibase = async opts => {
for (let library of libraries) { for (let library of libraries) {
// fetch the JavaScript for the component libraries from the server // fetch the JavaScript for the component libraries from the server
componentLibraryModules[library] = await import( componentLibraryModules[library] = await import(
`/${frontendDefinition.appId}/componentlibrary?library=${encodeURI( `/componentlibrary?library=${encodeURI(library)}`
library
)}`
) )
} }
@ -42,7 +41,7 @@ export const loadBudibase = async opts => {
}) })
const route = _window.location const route = _window.location
? _window.location.pathname.replace(frontendDefinition.appRootPath, "") ? _window.location.pathname.replace(`${appId}/`, "").replace(appId, "")
: "" : ""
initialisePage(frontendDefinition.page, _window.document.body, route) initialisePage(frontendDefinition.page, _window.document.body, route)

View file

@ -0,0 +1,5 @@
export const getAppId = () =>
document.cookie
.split(";")
.find(c => c.trim().startsWith("budibase:appid"))
.split("=")[1]

View file

@ -1,11 +1,19 @@
import regexparam from "regexparam" import regexparam from "regexparam"
import { routerStore } from "../state/store" import { routerStore } from "../state/store"
import { getAppId } from "./getAppId"
export const screenRouter = ({ screens, onScreenSelected, appRootPath }) => { export const screenRouter = ({ screens, onScreenSelected }) => {
const makeRootedPath = url => { const makeRootedPath = url => {
if (appRootPath) { if (
if (url) return `${appRootPath}${url.startsWith("/") ? "" : "/"}${url}` window.location.hostname === "localhost" ||
return appRootPath window.location.hostname === "127.0.0.1"
) {
const appId = getAppId()
if (url) {
if (url.startsWith(appId)) return url
return `/${appId}${url.startsWith("/") ? "" : "/"}${url}`
}
return appId
} }
return url return url
} }

View file

@ -6,21 +6,9 @@ export const trimSlash = str => str.replace(/^\/+|\/+$/g, "")
export const bbFactory = ({ export const bbFactory = ({
store, store,
frontendDefinition,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,
}) => { }) => {
const relativeUrl = url => {
if (!frontendDefinition.appRootPath) return url
if (
url.startsWith("http:") ||
url.startsWith("https:") ||
url.startsWith("./")
)
return url
return frontendDefinition.appRootPath + "/" + trimSlash(url)
}
const apiCall = method => (url, body) => const apiCall = method => (url, body) =>
fetch(url, { fetch(url, {
@ -30,6 +18,7 @@ export const bbFactory = ({
"x-user-agent": "Budibase Builder", "x-user-agent": "Budibase Builder",
}, },
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
credentials: "same-origin",
}) })
const api = { const api = {
@ -63,7 +52,6 @@ export const bbFactory = ({
getContext: getContext(treeNode), getContext: getContext(treeNode),
setContext: setContext(treeNode), setContext: setContext(treeNode),
store: store, store: store,
relativeUrl,
api, api,
parent, parent,
} }

View file

@ -6,14 +6,13 @@ import { createApi } from "../api"
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType" export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
export const eventHandlers = (rootPath, routeTo) => { export const eventHandlers = routeTo => {
const handler = (parameters, execute) => ({ const handler = (parameters, execute) => ({
execute, execute,
parameters, parameters,
}) })
const api = createApi({ const api = createApi({
rootPath,
setState, setState,
getState: (path, fallback) => getState(path, fallback), getState: (path, fallback) => getState(path, fallback),
}) })

View file

@ -21,13 +21,11 @@ const isMetaProp = propName =>
propName === "_styles" propName === "_styles"
export const createStateManager = ({ export const createStateManager = ({
appRootPath,
frontendDefinition,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,
routeTo, routeTo,
}) => { }) => {
let handlerTypes = eventHandlers(appRootPath, routeTo) let handlerTypes = eventHandlers(routeTo)
let currentState let currentState
const getCurrentState = () => currentState const getCurrentState = () => currentState
@ -35,7 +33,6 @@ export const createStateManager = ({
const bb = bbFactory({ const bb = bbFactory({
store: appStore, store: appStore,
getCurrentState, getCurrentState,
frontendDefinition,
componentLibraries, componentLibraries,
onScreenSlotRendered, onScreenSlotRendered,
}) })

View file

@ -1,10 +1,9 @@
import { JSDOM } from "jsdom" import { JSDOM } from "jsdom"
import { loadBudibase } from "../src/index" import { loadBudibase } from "../src/index"
export const load = async (page, screens, url, appRootPath) => { export const load = async (page, screens, url) => {
screens = screens || [] screens = screens || []
url = url || "/" url = url || "/"
appRootPath = appRootPath || ""
const dom = new JSDOM("<!DOCTYPE html><html><body></body><html>", { const dom = new JSDOM("<!DOCTYPE html><html><body></body><html>", {
url: `http://test${url}`, url: `http://test${url}`,
}) })
@ -13,7 +12,7 @@ export const load = async (page, screens, url, appRootPath) => {
autoAssignIds(s.props) autoAssignIds(s.props)
} }
setAppDef(dom.window, page, screens) setAppDef(dom.window, page, screens)
addWindowGlobals(dom.window, page, screens, appRootPath, { addWindowGlobals(dom.window, page, screens, {
hierarchy: {}, hierarchy: {},
actions: [], actions: [],
triggers: [], triggers: [],
@ -27,11 +26,10 @@ export const load = async (page, screens, url, appRootPath) => {
return { dom, app } return { dom, app }
} }
const addWindowGlobals = (window, page, screens, appRootPath) => { const addWindowGlobals = (window, page, screens) => {
window["##BUDIBASE_FRONTEND_DEFINITION##"] = { window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
page, page,
screens, screens,
appRootPath,
} }
} }
@ -88,7 +86,6 @@ const setAppDef = (window, page, screens) => {
componentLibraries: [], componentLibraries: [],
page, page,
screens, screens,
appRootPath: "",
} }
} }

View file

@ -14,12 +14,6 @@ export default async () => {
componentLibraries["@budibase/standard-components"] = standardcomponents componentLibraries["@budibase/standard-components"] = standardcomponents
const appDef = { hierarchy: {}, actions: {} } const appDef = { hierarchy: {}, actions: {} }
const user = { name: "yeo", permissions: [] } const user = { name: "yeo", permissions: [] }
const { initialisePage } = createApp( const { initialisePage } = createApp(componentLibraries, {}, appDef, user, {})
componentLibraries,
{ appRootPath: "" },
appDef,
user,
{}
)
return initialisePage return initialisePage
} }

View file

@ -25,7 +25,7 @@
"scripts": { "scripts": {
"test": "jest routes --runInBand", "test": "jest routes --runInBand",
"test:integration": "jest workflow --runInBand", "test:integration": "jest workflow --runInBand",
"test:watch": "jest -w", "test:watch": "jest --watch",
"initialise": "node ../cli/bin/budi init -b local -q", "initialise": "node ../cli/bin/budi init -b local -q",
"budi": "node ../cli/bin/budi", "budi": "node ../cli/bin/budi",
"dev:builder": "nodemon ../cli/bin/budi run", "dev:builder": "nodemon ../cli/bin/budi run",

View file

@ -4,25 +4,27 @@ const ClientDb = require("../../db/clientDb")
const bcrypt = require("../../utilities/bcrypt") const bcrypt = require("../../utilities/bcrypt")
exports.authenticate = async ctx => { exports.authenticate = async ctx => {
if (!ctx.appId) ctx.throw(400, "No appId")
const { username, password } = ctx.request.body const { username, password } = ctx.request.body
if (!username) ctx.throw(400, "Username Required.") if (!username) ctx.throw(400, "Username Required.")
if (!password) ctx.throw(400, "Password Required") if (!password) ctx.throw(400, "Password Required")
const masterDb = new CouchDB("clientAppLookup") const masterDb = new CouchDB("clientAppLookup")
const { clientId } = await masterDb.get(ctx.params.appId)
const { clientId } = await masterDb.get(ctx.appId)
if (!clientId) { if (!clientId) {
ctx.throw(400, "ClientId not suplied") ctx.throw(400, "ClientId not suplied")
} }
// find the instance that the user is associated with // find the instance that the user is associated with
const db = new CouchDB(ClientDb.name(clientId)) const db = new CouchDB(ClientDb.name(clientId))
const appId = ctx.params.appId const app = await db.get(ctx.appId)
const app = await db.get(appId)
const instanceId = app.userInstanceMap[username] const instanceId = app.userInstanceMap[username]
if (!instanceId) if (!instanceId)
ctx.throw(500, "User is not associated with an instance of app", appId) ctx.throw(500, "User is not associated with an instance of app", ctx.appId)
// Check the user exists in the instance DB by username // Check the user exists in the instance DB by username
const instanceDb = new CouchDB(instanceId) const instanceDb = new CouchDB(instanceId)
@ -41,7 +43,7 @@ exports.authenticate = async ctx => {
const payload = { const payload = {
userId: dbUser._id, userId: dbUser._id,
accessLevelId: dbUser.accessLevelId, accessLevelId: dbUser.accessLevelId,
instanceId: instanceId, instanceId,
} }
const token = jwt.sign(payload, ctx.config.jwtSecret, { const token = jwt.sign(payload, ctx.config.jwtSecret, {

View file

@ -27,6 +27,23 @@ exports.create = async function(ctx) {
const result = await db.post(newModel) const result = await db.post(newModel)
newModel._rev = result.rev newModel._rev = result.rev
const { schema } = ctx.request.body
for (let key in schema) {
// model has a linked record
if (schema[key].type === "link") {
// create the link field in the other model
const linkedModel = await db.get(schema[key].modelId)
linkedModel.schema[newModel.name] = {
type: "link",
modelId: newModel._id,
constraints: {
type: "array",
},
}
await db.put(linkedModel)
}
}
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
designDoc.views = { designDoc.views = {
...designDoc.views, ...designDoc.views,
@ -50,7 +67,10 @@ exports.update = async function() {}
exports.destroy = async function(ctx) { exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
await db.remove(ctx.params.modelId, ctx.params.revId) const modelToDelete = await db.get(ctx.params.modelId)
await db.remove(modelToDelete)
const modelViewId = `all_${ctx.params.modelId}` const modelViewId = `all_${ctx.params.modelId}`
// Delete all records for that model // Delete all records for that model
@ -59,6 +79,16 @@ exports.destroy = async function(ctx) {
records.rows.map(record => ({ id: record.id, _deleted: true })) records.rows.map(record => ({ id: record.id, _deleted: true }))
) )
// Delete linked record fields in dependent models
for (let key in modelToDelete.schema) {
const { type, modelId } = modelToDelete.schema[key]
if (type === "link") {
const linkedModel = await db.get(modelId)
delete linkedModel.schema[modelToDelete.name]
await db.put(linkedModel)
}
}
// delete the "all" view // delete the "all" view
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
delete designDoc.views[modelViewId] delete designDoc.views[modelViewId]

View file

@ -61,7 +61,7 @@ exports.fetchView = async function(ctx) {
ctx.body = response.rows.map(row => row.doc) ctx.body = response.rows.map(row => row.doc)
} }
exports.fetchModel = async function(ctx) { exports.fetchModelRecords = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
const response = await db.query(`database/all_${ctx.params.modelId}`, { const response = await db.query(`database/all_${ctx.params.modelId}`, {
include_docs: true, include_docs: true,
@ -69,6 +69,15 @@ exports.fetchModel = async function(ctx) {
ctx.body = response.rows.map(row => row.doc) ctx.body = response.rows.map(row => row.doc)
} }
exports.search = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
const response = await db.allDocs({
include_docs: true,
...ctx.request.body,
})
ctx.body = response.rows.map(row => row.doc)
}
exports.find = async function(ctx) { exports.find = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId)
const record = await db.get(ctx.params.recordId) const record = await db.get(ctx.params.recordId)

View file

@ -20,6 +20,27 @@ exports.serveApp = async function(ctx) {
"public", "public",
ctx.isAuthenticated ? "main" : "unauthenticated" ctx.isAuthenticated ? "main" : "unauthenticated"
) )
// only set the appId cookie for /appId .. we COULD check for valid appIds
// but would like to avoid that DB hit
if (looksLikeAppId(ctx.params.appId)) {
ctx.cookies.set("budibase:appid", ctx.params.appId, {
path: "/",
httpOnly: false,
expires: new Date(2099, 1, 1),
})
}
await send(ctx, ctx.file || "index.html", { root: ctx.devPath || appPath })
}
exports.serveAppAsset = async function(ctx) {
// default to homedir
const appPath = resolve(
budibaseAppsDir(),
ctx.appId,
"public",
ctx.isAuthenticated ? "main" : "unauthenticated"
)
await send(ctx, ctx.file, { root: ctx.devPath || appPath }) await send(ctx, ctx.file, { root: ctx.devPath || appPath })
} }
@ -28,7 +49,7 @@ exports.serveComponentLibrary = async function(ctx) {
// default to homedir // default to homedir
let componentLibraryPath = resolve( let componentLibraryPath = resolve(
budibaseAppsDir(), budibaseAppsDir(),
ctx.params.appId, ctx.appId,
"node_modules", "node_modules",
decodeURI(ctx.query.library), decodeURI(ctx.query.library),
"dist" "dist"
@ -44,3 +65,10 @@ exports.serveComponentLibrary = async function(ctx) {
await send(ctx, "/index.js", { root: componentLibraryPath }) await send(ctx, "/index.js", { root: componentLibraryPath })
} }
const looksLikeAppId = appId => {
const allowedChars = "0123456789abcdef".split("")
return (
appId.length === 32 && !appId.split("").some(c => allowedChars.includes(c))
)
}

View file

@ -10,6 +10,7 @@ const {
instanceRoutes, instanceRoutes,
clientRoutes, clientRoutes,
applicationRoutes, applicationRoutes,
recordRoutes,
modelRoutes, modelRoutes,
viewRoutes, viewRoutes,
staticRoutes, staticRoutes,
@ -59,6 +60,11 @@ router.use(async (ctx, next) => {
} }
}) })
router.use(async (ctx, next) => {
ctx.appId = ctx.cookies.get("budibase:appid")
await next()
})
router.use(authRoutes.routes()) router.use(authRoutes.routes())
router.use(authRoutes.allowedMethods()) router.use(authRoutes.allowedMethods())
@ -69,6 +75,9 @@ router.use(viewRoutes.allowedMethods())
router.use(modelRoutes.routes()) router.use(modelRoutes.routes())
router.use(modelRoutes.allowedMethods()) router.use(modelRoutes.allowedMethods())
router.use(recordRoutes.routes())
router.use(recordRoutes.allowedMethods())
router.use(userRoutes.routes()) router.use(userRoutes.routes())
router.use(userRoutes.allowedMethods()) router.use(userRoutes.allowedMethods())

View file

@ -3,6 +3,6 @@ const controller = require("../controllers/auth")
const router = Router() const router = Router()
router.post("/:appId/api/authenticate", controller.authenticate) router.post("/api/authenticate", controller.authenticate)
module.exports = router module.exports = router

View file

@ -5,6 +5,7 @@ const instanceRoutes = require("./instance")
const clientRoutes = require("./client") const clientRoutes = require("./client")
const applicationRoutes = require("./application") const applicationRoutes = require("./application")
const modelRoutes = require("./model") const modelRoutes = require("./model")
const recordRoutes = require("./record")
const viewRoutes = require("./view") const viewRoutes = require("./view")
const staticRoutes = require("./static") const staticRoutes = require("./static")
const componentRoutes = require("./component") const componentRoutes = require("./component")
@ -18,6 +19,7 @@ module.exports = {
instanceRoutes, instanceRoutes,
clientRoutes, clientRoutes,
applicationRoutes, applicationRoutes,
recordRoutes,
modelRoutes, modelRoutes,
viewRoutes, viewRoutes,
staticRoutes, staticRoutes,

View file

@ -1,46 +1,10 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const modelController = require("../controllers/model") const modelController = require("../controllers/model")
const recordController = require("../controllers/record")
const authorized = require("../../middleware/authorized") const authorized = require("../../middleware/authorized")
const { const { BUILDER } = require("../../utilities/accessLevels")
READ_MODEL,
WRITE_MODEL,
BUILDER,
} = require("../../utilities/accessLevels")
const router = Router() const router = Router()
// records
router
.get(
"/api/:instanceId/:modelId/records",
authorized(READ_MODEL, ctx => ctx.params.modelId),
recordController.fetchModel
)
.get(
"/api/:instanceId/:modelId/records/:recordId",
authorized(READ_MODEL, ctx => ctx.params.modelId),
recordController.find
)
.post(
"/api/:instanceId/:modelId/records",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.save
)
.post(
"/api/:instanceId/:modelId/records/validate",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.validate
)
.delete(
"/api/:instanceId/:modelId/records/:recordId/:revId",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.destroy
)
// models
router router
.get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch) .get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch)
.get("/api/:instanceId/models/:id", authorized(BUILDER), modelController.find) .get("/api/:instanceId/models/:id", authorized(BUILDER), modelController.find)

View file

@ -0,0 +1,36 @@
const Router = require("@koa/router")
const recordController = require("../controllers/record")
const authorized = require("../../middleware/authorized")
const { READ_MODEL, WRITE_MODEL } = require("../../utilities/accessLevels")
const router = Router()
router
.get(
"/api/:instanceId/:modelId/records",
authorized(READ_MODEL, ctx => ctx.params.modelId),
recordController.fetchModelRecords
)
.get(
"/api/:instanceId/:modelId/records/:recordId",
authorized(READ_MODEL, ctx => ctx.params.modelId),
recordController.find
)
.post("/api/:instanceId/records/search", recordController.search)
.post(
"/api/:instanceId/:modelId/records",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.save
)
.post(
"/api/:instanceId/:modelId/records/validate",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.validate
)
.delete(
"/api/:instanceId/:modelId/records/:recordId/:revId",
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
recordController.destroy
)
module.exports = router

View file

@ -21,7 +21,8 @@ if (env.NODE_ENV !== "production") {
} }
router router
.get("/:appId/componentlibrary", controller.serveComponentLibrary) .get("/componentlibrary", controller.serveComponentLibrary)
.get("/:appId/:file*", controller.serveApp) .get("/assets/:file*", controller.serveAppAsset)
.get("/:appId/:path*", controller.serveApp)
module.exports = router module.exports = router

View file

@ -253,3 +253,7 @@ exports.insertDocument = async (databaseId, document) => {
exports.destroyDocument = async (databaseId, documentId) => { exports.destroyDocument = async (databaseId, documentId) => {
return await new CouchDB(databaseId).destroy(documentId) return await new CouchDB(databaseId).destroy(documentId)
} }
exports.getDocument = async (databaseId, documentId) => {
return await new CouchDB(databaseId).get(documentId)
}

View file

@ -3,9 +3,10 @@ const {
createModel, createModel,
supertest, supertest,
createClientDatabase, createClientDatabase,
createApplication , createApplication,
defaultHeaders, defaultHeaders,
builderEndpointShouldBlockNormalUsers builderEndpointShouldBlockNormalUsers,
getDocument
} = require("./couchTestUtils") } = require("./couchTestUtils")
describe("/models", () => { describe("/models", () => {
@ -97,7 +98,6 @@ describe("/models", () => {
instanceId: instance._id, instanceId: instance._id,
}) })
}) })
}); });
describe("destroy", () => { describe("destroy", () => {
@ -108,7 +108,11 @@ describe("/models", () => {
testModel = await createModel(request, instance._id, testModel) testModel = await createModel(request, instance._id, testModel)
}); });
it("returns a success response when a model is deleted.", done => { afterEach(() => {
delete testModel._rev
})
it("returns a success response when a model is deleted.", async done => {
request request
.delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`) .delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`)
.set(defaultHeaders) .set(defaultHeaders)
@ -120,6 +124,41 @@ describe("/models", () => {
}); });
}) })
it("deletes linked references to the model after deletion", async done => {
const linkedModel = await createModel(request, instance._id, {
name: "LinkedModel",
type: "model",
key: "name",
schema: {
name: {
type: "text",
constraints: {
type: "string",
},
},
TestModel: {
type: "link",
modelId: testModel._id,
constraints: {
type: "array"
}
}
},
})
request
.delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`)
.set(defaultHeaders)
.expect('Content-Type', /json/)
.expect(200)
.end(async (_, res) => {
expect(res.res.statusMessage).toEqual(`Model ${testModel._id} deleted.`);
const dependentModel = await getDocument(instance._id, linkedModel._id)
expect(dependentModel.schema.TestModel).not.toBeDefined();
done();
});
})
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
await builderEndpointShouldBlockNormalUsers({ await builderEndpointShouldBlockNormalUsers({
request, request,

View file

@ -110,6 +110,30 @@ describe("/records", () => {
expect(res.body.find(r => r.name === record.name)).toBeDefined() expect(res.body.find(r => r.name === record.name)).toBeDefined()
}) })
it("lists records when queried by their ID", async () => {
const newRecord = {
modelId: model._id,
name: "Second Contact",
status: "new"
}
const record = await createRecord()
const secondRecord = await createRecord(newRecord)
const recordIds = [record.body._id, secondRecord.body._id]
const res = await request
.post(`/api/${instance._id}/records/search`)
.set(defaultHeaders)
.send({
keys: recordIds
})
.expect('Content-Type', /json/)
.expect(200)
expect(res.body.length).toBe(2)
expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(recordIds))
})
it("load should return 404 when record does not exist", async () => { it("load should return 404 when record does not exist", async () => {
await createRecord() await createRecord()
await request await request

View file

@ -45,18 +45,16 @@ const copyClientLib = async (appPath, pageName) => {
const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => { const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
const appPublicPath = publicPath(appPath, pageName) const appPublicPath = publicPath(appPath, pageName)
const appRootPath = rootPath(config, appId)
const stylesheetUrl = s => const stylesheetUrl = s =>
s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}` s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}`
const templateObj = { const templateObj = {
title: pkg.page.title || "Budibase App", title: pkg.page.title || "Budibase App",
favicon: `${appRootPath}/${pkg.page.favicon || "/_shared/favicon.png"}`, favicon: `${pkg.page.favicon || "/_shared/favicon.png"}`,
stylesheets: (pkg.page.stylesheets || []).map(stylesheetUrl), stylesheets: (pkg.page.stylesheets || []).map(stylesheetUrl),
screenStyles: pkg.screens.filter(s => s._css).map(s => s._css), screenStyles: pkg.screens.filter(s => s._css).map(s => s._css),
pageStyle: pkg.page._css, pageStyle: pkg.page._css,
appRootPath,
} }
const indexHtmlTemplate = await readFile( const indexHtmlTemplate = await readFile(
@ -74,7 +72,6 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => { const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
const appPath = appPackageFolder(config, appId) const appPath = appPackageFolder(config, appId)
const appPublicPath = publicPath(appPath, pageName) const appPublicPath = publicPath(appPath, pageName)
const appRootPath = rootPath(config, appId)
const filename = join(appPublicPath, "clientFrontendDefinition.js") const filename = join(appPublicPath, "clientFrontendDefinition.js")
@ -89,7 +86,6 @@ const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
} }
const clientUiDefinition = JSON.stringify({ const clientUiDefinition = JSON.stringify({
appRootPath: appRootPath,
page: pkg.page, page: pkg.page,
screens: pkg.screens, screens: pkg.screens,
libraries: [ libraries: [

View file

@ -24,15 +24,15 @@
{{ /each }} {{ /each }}
{{ each(options.screenStyles) }} {{ each(options.screenStyles) }}
<link rel='stylesheet' href='{{ appRootPath }}{{ @this }}'> <link rel='stylesheet' href='/assets{{ @this }}'>
{{ /each }} {{ /each }}
{{ if(options.pageStyle) }} {{ if(options.pageStyle) }}
<link rel='stylesheet' href='{{ appRootPath }}{{ pageStyle }}'> <link rel='stylesheet' href='/assets{{ pageStyle }}'>
{{ /if }} {{ /if }}
<script src='{{ appRootPath }}/clientFrontendDefinition.js'></script> <script src='/assets/clientFrontendDefinition.js'></script>
<script src='{{ appRootPath }}/budibase-client.js'></script> <script src='/assets/budibase-client.js'></script>
</head> </head>

View file

@ -6,6 +6,13 @@
"component": "button" "component": "button"
} }
}, },
"embed": {
"name": "Embed",
"description": "Embed stuff",
"props": {
"embed": "string"
}
},
"Navigation": { "Navigation": {
"name": "Navigation", "name": "Navigation",
"description": "A basic header navigation component", "description": "A basic header navigation component",

View file

@ -98,6 +98,5 @@ window["##BUDIBASE_APPDEFINITION##"] = {
nodeId: 0, nodeId: 0,
}, },
componentLibraries: ["budibase-standard-components"], componentLibraries: ["budibase-standard-components"],
appRootPath: "/testApp2",
props: {}, props: {},
} }

View file

@ -0,0 +1,5 @@
<script>
export let embed
</script>
{@html embed}

View file

@ -13,30 +13,17 @@
let password = "" let password = ""
let loading = false let loading = false
let error = false let error = false
let _logo = ""
let _buttonClass = "" let _buttonClass = ""
let _inputClass = "" let _inputClass = ""
$: { $: {
_logo = _bb.relativeUrl(logo)
_buttonClass = buttonClass || "default-button" _buttonClass = buttonClass || "default-button"
_inputClass = inputClass || "default-input" _inputClass = inputClass || "default-input"
} }
const login = async () => { const login = async () => {
loading = true loading = true
const response = await fetch(_bb.relativeUrl("/api/authenticate"), { const response = await _bb.api.post("/api/authenticate", { username, password })
body: JSON.stringify({
username,
password,
}),
headers: {
"Content-Type": "application/json",
"x-user-agent": "Budibase Builder",
},
method: "POST",
})
if (response.status === 200) { if (response.status === 200) {
const json = await response.json() const json = await response.json()
localStorage.setItem("budibase:token", json.token) localStorage.setItem("budibase:token", json.token)
@ -51,9 +38,9 @@
<div class="root"> <div class="root">
<div class="content"> <div class="content">
{#if _logo} {#if logo}
<div class="logo-container"> <div class="logo-container">
<img src={_logo} alt="logo" /> <img src={logo} alt="logo" />
</div> </div>
{/if} {/if}

View file

@ -15,7 +15,7 @@ export default async () => {
const { initialisePage } = createApp( const { initialisePage } = createApp(
window.document, window.document,
componentLibraries, componentLibraries,
{ appRootPath: "" }, {},
appDef, appDef,
user, user,
{}, {},

View file

@ -21,3 +21,4 @@ export { default as datalist } from "./DataList.svelte"
export { default as list } from "./List.svelte" export { default as list } from "./List.svelte"
export { default as datasearch } from "./DataSearch.svelte" export { default as datasearch } from "./DataSearch.svelte"
export { default as datamap } from "./DataMap.svelte" export { default as datamap } from "./DataMap.svelte"
export { default as embed } from "./Embed.svelte"