removal of appRoot - appId comes in cookie
This commit is contained in:
parent
6cb7e995a2
commit
bf1da9e94e
|
@ -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
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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, () => {
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
|
@ -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 })
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
const pages = [
|
const pages = [
|
||||||
{
|
{
|
||||||
title: "Main",
|
title: "Private",
|
||||||
id: "main",
|
id: "main",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Login",
|
title: "Public",
|
||||||
id: "unauthenticated",
|
id: "unauthenticated",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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)",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const getAppId = () =>
|
||||||
|
document.cookie
|
||||||
|
.split(";")
|
||||||
|
.find(c => c.trim().startsWith("budibase:appid"))
|
||||||
|
.split("=")[1]
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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: "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: [
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -98,6 +98,5 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
||||||
nodeId: 0,
|
nodeId: 0,
|
||||||
},
|
},
|
||||||
componentLibraries: ["budibase-standard-components"],
|
componentLibraries: ["budibase-standard-components"],
|
||||||
appRootPath: "/testApp2",
|
|
||||||
props: {},
|
props: {},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
export let embed
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@html embed}
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default async () => {
|
||||||
const { initialisePage } = createApp(
|
const { initialisePage } = createApp(
|
||||||
window.document,
|
window.document,
|
||||||
componentLibraries,
|
componentLibraries,
|
||||||
{ appRootPath: "" },
|
{},
|
||||||
appDef,
|
appDef,
|
||||||
user,
|
user,
|
||||||
{},
|
{},
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue