1
0
Fork 0
mirror of synced 2024-07-04 05:50:57 +12:00

Merge pull request #826 from Budibase/tidy-up-store

Pages and Screens to couch as well as general store tidy up
This commit is contained in:
Michael Drury 2020-11-06 14:54:54 +00:00 committed by GitHub
commit 960ca9df8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 1289 additions and 1661 deletions

View file

@ -1,33 +0,0 @@
const buildCodeForSingleScreen = screen => {
let code = ""
const walkProps = props => {
if (props._code && props._code.trim().length > 0) {
code += buildComponentCode(props)
}
if (!props._children) return
for (let child of props._children) {
walkProps(child)
}
}
walkProps(screen.props)
return code
}
export const buildCodeForScreens = screens => {
let allfunctions = ""
for (let screen of screens) {
allfunctions += buildCodeForSingleScreen(screen)
}
return `({ ${allfunctions} });`
}
const buildComponentCode = componentProps =>
`"${componentProps._id}" : (render, context, state, route) => {
${componentProps._code}
},
`

View file

@ -1,5 +1,7 @@
import { walkProps } from "./storeUtils" import { walkProps } from "./storeUtils"
import { get_capitalised_name } from "../helpers" import { get_capitalised_name } from "../helpers"
import { get } from "svelte/store"
import { allScreens } from "builderStore"
export default function(component, state) { export default function(component, state) {
const capitalised = get_capitalised_name( const capitalised = get_capitalised_name(
@ -25,7 +27,7 @@ export default function(component, state) {
findMatches(state.currentPreviewItem.props) findMatches(state.currentPreviewItem.props)
} else { } else {
// viewing master page - need to find against all screens // viewing master page - need to find against all screens
for (let screen of state.screens) { for (let screen of get(allScreens)) {
findMatches(screen.props) findMatches(screen.props)
} }
} }
@ -33,7 +35,7 @@ export default function(component, state) {
let index = 1 let index = 1
let name let name
while (!name) { while (!name) {
const tryName = `${capitalised} ${index}` const tryName = `${capitalised || "Copy"} ${index}`
if (!matchingComponents.includes(tryName)) name = tryName if (!matchingComponents.includes(tryName)) name = tryName
index++ index++
} }

View file

@ -1,14 +1,36 @@
import { getStore } from "./store" import { getFrontendStore } from "./store/frontend"
import { getBackendUiStore } from "./store/backend" import { getBackendUiStore } from "./store/backend"
import { getAutomationStore } from "./store/automation/" import { getAutomationStore } from "./store/automation/"
import { getThemeStore } from "./store/theme" import { getThemeStore } from "./store/theme"
import { derived } from "svelte/store"
import analytics from "analytics" import analytics from "analytics"
export const store = getStore() export const store = getFrontendStore()
export const backendUiStore = getBackendUiStore() export const backendUiStore = getBackendUiStore()
export const automationStore = getAutomationStore() export const automationStore = getAutomationStore()
export const themeStore = getThemeStore() export const themeStore = getThemeStore()
export const allScreens = derived(store, $store => {
let screens = []
if ($store.pages == null) {
return screens
}
for (let page of Object.values($store.pages)) {
screens = screens.concat(page._screens)
}
return screens
})
export const currentScreens = derived(store, $store => {
const currentScreens = $store.pages[$store.currentPageName]?._screens
if (currentScreens == null) {
return []
}
return Array.isArray(currentScreens)
? currentScreens
: Object.values(currentScreens)
})
export const initialise = async () => { export const initialise = async () => {
try { try {
await analytics.activate() await analytics.activate()

View file

@ -1,17 +0,0 @@
export const insertCodeMetadata = props => {
if (props._code && props._code.length > 0) {
props._codeMeta = codeMetaData(props._code)
}
if (!props._children || props._children.length === 0) return
for (let child of props._children) {
insertCodeMetadata(child)
}
}
const codeMetaData = code => {
return {
dependsOnStore: RegExp(/(state.)/g).test(code),
}
}

View file

@ -0,0 +1,529 @@
import { get, writable } from "svelte/store"
import { cloneDeep } from "lodash/fp"
import {
createProps,
getBuiltin,
makePropsSafe,
} from "components/userInterface/pagesParsing/createProps"
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
import { allScreens, backendUiStore } from "builderStore"
import { generate_screen_css } from "../generate_css"
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
import api from "../api"
import { DEFAULT_PAGES_OBJECT } from "../../constants"
import getNewComponentName from "../getNewComponentName"
import analytics from "analytics"
import {
findChildComponentType,
generateNewIdsForComponent,
getComponentDefinition,
getParent,
} from "../storeUtils"
const INITIAL_FRONTEND_STATE = {
apps: [],
name: "",
description: "",
pages: DEFAULT_PAGES_OBJECT,
mainUi: {},
unauthenticatedUi: {},
components: [],
currentPreviewItem: null,
currentComponentInfo: null,
currentFrontEndType: "none",
currentPageName: "",
currentComponentProps: null,
errors: [],
hasAppPackage: false,
libraries: null,
appId: "",
}
export const getFrontendStore = () => {
const store = writable({ ...INITIAL_FRONTEND_STATE })
store.actions = {
// TODO: REFACTOR
initialise: async pkg => {
store.update(state => {
state.appId = pkg.application._id
return state
})
const screens = await api.get("/api/screens").then(r => r.json())
const mainScreens = screens.filter(screen =>
screen._id.includes(pkg.pages.main._id)
),
unauthScreens = screens.filter(screen =>
screen._id.includes(pkg.pages.unauthenticated._id)
)
pkg.pages = {
main: {
...pkg.pages.main,
_screens: mainScreens,
},
unauthenticated: {
...pkg.pages.unauthenticated,
_screens: unauthScreens,
},
}
// if the app has just been created
// we need to build the CSS and save
if (pkg.justCreated) {
for (let pageName of ["main", "unauthenticated"]) {
const page = pkg.pages[pageName]
store.actions.screens.regenerateCss(page)
for (let screen of page._screens) {
store.actions.screens.regenerateCss(screen)
}
await api.post(`/api/pages/${page._id}`, {
page: {
componentLibraries: pkg.application.componentLibraries,
...page,
},
screens: page._screens,
})
}
}
pkg.justCreated = false
const components = await fetchComponentLibDefinitions(pkg.application._id)
store.update(state => ({
...state,
libraries: pkg.application.componentLibraries,
components,
name: pkg.application.name,
description: pkg.application.description,
appId: pkg.application._id,
pages: pkg.pages,
hasAppPackage: true,
currentScreens: [],
builtins: [getBuiltin("##builtin/screenslot")],
appInstance: pkg.application.instance,
}))
await backendUiStore.actions.database.select(pkg.application.instance)
},
selectPageOrScreen: type => {
store.update(state => {
state.currentFrontEndType = type
const pageOrScreen =
type === "page"
? state.pages[state.currentPageName]
: state.pages[state.currentPageName]._screens[0]
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
state.currentPreviewItem = pageOrScreen
state.currentView = "detail"
return state
})
},
screens: {
select: screenName => {
store.update(state => {
const screen = getExactComponent(get(allScreens), screenName, true)
state.currentPreviewItem = screen
state.currentFrontEndType = "screen"
state.currentView = "detail"
store.actions.screens.regenerateCssForCurrentScreen()
// this.regenerateCssForCurrentScreen()
// regenerateCssForCurrentScreen(s)
const safeProps = makePropsSafe(
state.components[screen.props._component],
screen.props
)
screen.props = safeProps
state.currentComponentInfo = safeProps
return state
})
},
create: async screen => {
let savePromise
store.update(state => {
state.currentPreviewItem = screen
state.currentComponentInfo = screen.props
state.currentFrontEndType = "screen"
if (state.currentPreviewItem) {
store.actions.screens.regenerateCss(state.currentPreviewItem)
}
savePromise = store.actions.screens.save(screen)
return state
})
await savePromise
},
save: async screen => {
const storeContents = get(store)
const pageName = storeContents.currentPageName || "main"
const currentPage = storeContents.pages[pageName]
const currentPageScreens = currentPage._screens
let savePromise
const response = await api.post(
`/api/screens/${currentPage._id}`,
screen
)
const json = await response.json()
screen._rev = json.rev
screen._id = json.id
const foundScreen = currentPageScreens.findIndex(
el => el._id === screen._id
)
if (currentPageScreens !== -1) {
currentPageScreens.splice(foundScreen, 1)
}
currentPageScreens.push(screen)
// TODO: should carry out all server updates to screen in a single call
store.update(state => {
state.pages[pageName]._screens = currentPageScreens
state.currentPreviewItem = screen
const safeProps = makePropsSafe(
state.components[screen.props._component],
screen.props
)
state.currentComponentInfo = safeProps
screen.props = safeProps
savePromise = store.actions.pages.save()
return state
})
await savePromise
},
regenerateCss: screen => {
screen._css = generate_screen_css([screen.props])
},
regenerateCssForCurrentScreen: () => {
const { currentPreviewItem } = get(store)
if (currentPreviewItem) {
store.actions.screens.regenerateCss(currentPreviewItem)
}
},
delete: async (screensToDelete, pageName) => {
let deletePromise
store.update(state => {
if (pageName == null) {
pageName = state.pages.main.name
}
for (let screenToDelete of Array.isArray(screenToDelete)
? screenToDelete
: [screenToDelete]) {
// Remove screen from current page as well
// TODO: Should be done server side
state.pages[pageName]._screens = state.pages[
pageName
]._screens.filter(scr => scr.name !== screenToDelete.name)
deletePromise = api.delete(
`/api/screens/${screenToDelete._id}/${screenToDelete._rev}`
)
}
return state
})
await deletePromise
},
},
preview: {
// _saveCurrentPreviewItem
saveSelected: () => {
const state = get(store)
state.currentFrontEndType === "page"
? store.actions.pages.save()
: store.actions.screens.save(state.currentPreviewItem)
},
},
pages: {
select: pageName => {
store.update(state => {
const currentPage = state.pages[pageName]
state.currentScreens = currentPage._screens
state.currentFrontEndType = "page"
state.currentView = "detail"
state.currentPageName = pageName
// This is the root of many problems.
// Uncaught (in promise) TypeError: Cannot read property '_component' of undefined
// it appears that the currentPage sometimes has _props instead of props
// why
const safeProps = makePropsSafe(
state.components[currentPage.props._component],
currentPage.props
)
state.currentComponentInfo = safeProps
currentPage.props = safeProps
state.currentPreviewItem = state.pages[pageName]
store.actions.screens.regenerateCssForCurrentScreen()
for (let screen of get(allScreens)) {
screen._css = generate_screen_css([screen.props])
}
return state
})
},
save: async page => {
const storeContents = get(store)
const pageName = storeContents.currentPageName || "main"
const pageToSave = page || storeContents.pages[pageName]
// TODO: revisit. This sends down a very weird payload
const response = await api
.post(`/api/pages/${pageToSave._id}`, {
page: {
componentLibraries: storeContents.pages.componentLibraries,
...pageToSave,
},
screens: pageToSave._screens,
})
.then(response => response.json())
store.update(state => {
state.pages[pageName]._rev = response.rev
return state
})
},
},
components: {
select: component => {
store.update(state => {
const componentDef = component._component.startsWith("##")
? component
: state.components[component._component]
state.currentComponentInfo = makePropsSafe(componentDef, component)
state.currentView = "component"
return state
})
},
// addChildComponent
create: (componentToAdd, presetProps) => {
store.update(state => {
function findSlot(component_array) {
for (let i = 0; i < component_array.length; i += 1) {
if (component_array[i]._component === "##builtin/screenslot") {
return true
}
if (component_array[i]._children) findSlot(component_array[i])
}
return false
}
if (
componentToAdd.startsWith("##") &&
findSlot(state.pages[state.currentPageName].props._children)
) {
return state
}
const component = getComponentDefinition(state, componentToAdd)
const instanceId = get(backendUiStore).selectedDatabase._id
const instanceName = getNewComponentName(component, state)
const newComponent = createProps(component, {
...presetProps,
_instanceId: instanceId,
_instanceName: instanceName,
})
const currentComponent =
state.components[state.currentComponentInfo._component]
const targetParent = currentComponent.children
? state.currentComponentInfo
: getParent(
state.currentPreviewItem.props,
state.currentComponentInfo
)
// Don't continue if there's no parent
if (!targetParent) {
return state
}
targetParent._children = targetParent._children.concat(
newComponent.props
)
store.actions.preview.saveSelected()
state.currentView = "component"
state.currentComponentInfo = newComponent.props
analytics.captureEvent("Added Component", {
name: newComponent.props._component,
})
return state
})
},
copy: (component, cut = false) => {
store.update(state => {
state.componentToPaste = cloneDeep(component)
state.componentToPaste.isCut = cut
if (cut) {
const parent = getParent(
state.currentPreviewItem.props,
component._id
)
parent._children = parent._children.filter(
c => c._id !== component._id
)
store.actions.components.select(parent)
}
return state
})
},
paste: (targetComponent, mode) => {
store.update(state => {
if (!state.componentToPaste) return state
const componentToPaste = cloneDeep(state.componentToPaste)
// retain the same ids as things may be referencing this component
if (componentToPaste.isCut) {
// in case we paste a second time
state.componentToPaste.isCut = false
} else {
generateNewIdsForComponent(componentToPaste, state)
}
delete componentToPaste.isCut
if (mode === "inside") {
targetComponent._children.push(componentToPaste)
return state
}
const parent = getParent(
state.currentPreviewItem.props,
targetComponent
)
const targetIndex = parent._children.indexOf(targetComponent)
const index = mode === "above" ? targetIndex : targetIndex + 1
parent._children.splice(index, 0, cloneDeep(componentToPaste))
store.actions.screens.regenerateCssForCurrentScreen()
store.actions.preview.saveSelected()
store.actions.components.select(componentToPaste)
return state
})
},
updateStyle: (type, name, value) => {
store.update(state => {
if (!state.currentComponentInfo._styles) {
state.currentComponentInfo._styles = {}
}
state.currentComponentInfo._styles[type][name] = value
store.actions.screens.regenerateCssForCurrentScreen()
// save without messing with the store
store.actions.preview.saveSelected()
return state
})
},
updateProp: (name, value) => {
store.update(state => {
let current_component = state.currentComponentInfo
current_component[name] = value
state.currentComponentInfo = current_component
store.actions.preview.saveSelected()
return state
})
},
findRoute: component => {
// Gets all the components to needed to construct a path.
const tempStore = get(store)
let pathComponents = []
let parent = component
let root = false
while (!root) {
parent = getParent(tempStore.currentPreviewItem.props, parent)
if (!parent) {
root = true
} else {
pathComponents.push(parent)
}
}
// Remove root entry since it's the screen or page layout.
// Reverse array since we need the correct order of the IDs
const reversedComponents = pathComponents.reverse().slice(1)
// Add component
const allComponents = [...reversedComponents, component]
// Map IDs
const IdList = allComponents.map(c => c._id)
// Construct ID Path:
return IdList.join("/")
},
links: {
save: async (url, title) => {
let savePromise
store.update(state => {
// Try to extract a nav component from the master screen
const nav = findChildComponentType(
state.pages.main,
"@budibase/standard-components/Navigation"
)
if (nav) {
let newLink
// Clone an existing link if one exists
if (nav._children && nav._children.length) {
// Clone existing link style
newLink = cloneDeep(nav._children[0])
// Manipulate IDs to ensure uniqueness
generateNewIdsForComponent(newLink, state, false)
// Set our new props
newLink._instanceName = `${title} Link`
newLink.url = url
newLink.text = title
} else {
// Otherwise create vanilla new link
const component = getComponentDefinition(
state,
"@budibase/standard-components/link"
)
const instanceId = get(backendUiStore).selectedDatabase._id
newLink = createProps(component, {
url,
text: title,
_instanceName: `${title} Link`,
_instanceId: instanceId,
}).props
}
// Save page and regenerate all CSS because otherwise weird things happen
nav._children = [...nav._children, newLink]
state.currentPageName = "main"
store.actions.screens.regenerateCss(state.pages.main)
for (let screen of state.pages.main._screens) {
store.actions.screens.regenerateCss(screen)
}
savePromise = store.actions.pages.save()
}
return state
})
await savePromise
},
},
},
}
return store
}

View file

@ -1,607 +0,0 @@
import { cloneDeep } from "lodash/fp"
import getNewComponentName from "../getNewComponentName"
import { backendUiStore } from "builderStore"
import { writable, get } from "svelte/store"
import api from "../api"
import { DEFAULT_PAGES_OBJECT } from "../../constants"
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
import {
createProps,
makePropsSafe,
getBuiltin,
} from "components/userInterface/pagesParsing/createProps"
import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
import { buildCodeForScreens } from "../buildCodeForScreens"
import { generate_screen_css } from "../generate_css"
import { insertCodeMetadata } from "../insertCodeMetadata"
import analytics from "analytics"
import { uuid } from "../uuid"
import {
selectComponent as _selectComponent,
getParent,
walkProps,
savePage as _savePage,
saveCurrentPreviewItem as _saveCurrentPreviewItem,
saveScreenApi as _saveScreenApi,
regenerateCssForCurrentScreen,
regenerateCssForScreen,
generateNewIdsForComponent,
getComponentDefinition,
findChildComponentType,
} from "../storeUtils"
export const getStore = () => {
const initial = {
apps: [],
name: "",
description: "",
pages: DEFAULT_PAGES_OBJECT,
mainUi: {},
unauthenticatedUi: {},
components: [],
currentPreviewItem: null,
currentComponentInfo: null,
currentFrontEndType: "none",
currentPageName: "",
currentComponentProps: null,
errors: [],
hasAppPackage: false,
libraries: null,
appId: "",
}
const store = writable(initial)
store.setPackage = setPackage(store, initial)
store.saveScreen = saveScreen(store)
store.setCurrentScreen = setCurrentScreen(store)
store.deleteScreens = deleteScreens(store)
store.setCurrentPage = setCurrentPage(store)
store.createLink = createLink(store)
store.createScreen = createScreen(store)
store.addStylesheet = addStylesheet(store)
store.removeStylesheet = removeStylesheet(store)
store.savePage = savePage(store)
store.addChildComponent = addChildComponent(store)
store.selectComponent = selectComponent(store)
store.setComponentProp = setComponentProp(store)
store.setPageOrScreenProp = setPageOrScreenProp(store)
store.setComponentStyle = setComponentStyle(store)
store.setComponentCode = setComponentCode(store)
store.setScreenType = setScreenType(store)
store.getPathToComponent = getPathToComponent(store)
store.addTemplatedComponent = addTemplatedComponent(store)
store.setMetadataProp = setMetadataProp(store)
store.editPageOrScreen = editPageOrScreen(store)
store.pasteComponent = pasteComponent(store)
store.storeComponentForCopy = storeComponentForCopy(store)
return store
}
export default getStore
const setPackage = (store, initial) => async pkg => {
const [main_screens, unauth_screens] = await Promise.all([
api
.get(`/_builder/api/${pkg.application._id}/pages/main/screens`)
.then(r => r.json()),
api
.get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`)
.then(r => r.json()),
])
pkg.pages = {
main: {
...pkg.pages.main,
_screens: Object.values(main_screens),
},
unauthenticated: {
...pkg.pages.unauthenticated,
_screens: Object.values(unauth_screens),
},
}
// if the app has just been created
// we need to build the CSS and save
if (pkg.justCreated) {
const generateInitialPageCss = async name => {
const page = pkg.pages[name]
regenerateCssForScreen(page)
for (let screen of page._screens) {
regenerateCssForScreen(screen)
}
await api.post(`/_builder/api/${pkg.application._id}/pages/${name}`, {
page: {
componentLibraries: pkg.application.componentLibraries,
...page,
},
screens: page._screens,
})
}
generateInitialPageCss("main")
generateInitialPageCss("unauthenticated")
pkg.justCreated = false
}
initial.libraries = pkg.application.componentLibraries
initial.components = await fetchComponentLibDefinitions(pkg.application._id)
initial.name = pkg.application.name
initial.description = pkg.application.description
initial.appId = pkg.application._id
initial.pages = pkg.pages
initial.hasAppPackage = true
initial.screens = [
...Object.values(main_screens),
...Object.values(unauth_screens),
]
initial.builtins = [getBuiltin("##builtin/screenslot")]
initial.appInstance = pkg.application.instance
initial.appId = pkg.application._id
store.set(initial)
await backendUiStore.actions.database.select(initial.appInstance)
return initial
}
const saveScreen = store => screen => {
store.update(state => {
return _saveScreen(store, state, screen)
})
}
const _saveScreen = async (store, s, screen) => {
const pageName = s.currentPageName || "main"
const currentPageScreens = s.pages[pageName]._screens
await api
.post(`/_builder/api/${s.appId}/pages/${pageName}/screen`, screen)
.then(() => {
if (currentPageScreens.includes(screen)) return
const screens = [...currentPageScreens, screen]
store.update(innerState => {
innerState.pages[pageName]._screens = screens
innerState.screens = screens
innerState.currentPreviewItem = screen
const safeProps = makePropsSafe(
innerState.components[screen.props._component],
screen.props
)
innerState.currentComponentInfo = safeProps
screen.props = safeProps
_savePage(innerState)
return innerState
})
})
return s
}
const createScreen = store => async screen => {
let savePromise
store.update(state => {
state.currentPreviewItem = screen
state.currentComponentInfo = screen.props
state.currentFrontEndType = "screen"
regenerateCssForCurrentScreen(state)
savePromise = _saveScreen(store, state, screen)
return state
})
await savePromise
}
const createLink = store => async (url, title) => {
let savePromise
store.update(state => {
// Try to extract a nav component from the master screen
const nav = findChildComponentType(
state.pages.main,
"@budibase/standard-components/Navigation"
)
if (nav) {
let newLink
// Clone an existing link if one exists
if (nav._children && nav._children.length) {
// Clone existing link style
newLink = cloneDeep(nav._children[0])
// Manipulate IDs to ensure uniqueness
generateNewIdsForComponent(newLink, state, false)
// Set our new props
newLink._instanceName = `${title} Link`
newLink.url = url
newLink.text = title
} else {
// Otherwise create vanilla new link
const component = getComponentDefinition(
state,
"@budibase/standard-components/link"
)
const instanceId = get(backendUiStore).selectedDatabase._id
newLink = createProps(component, {
url,
text: title,
_instanceName: `${title} Link`,
_instanceId: instanceId,
}).props
}
// Save page and regenerate all CSS because otherwise weird things happen
nav._children = [...nav._children, newLink]
state.currentPageName = "main"
regenerateCssForScreen(state.pages.main)
for (let screen of state.pages.main._screens) {
regenerateCssForScreen(screen)
}
savePromise = _savePage(state)
}
return state
})
await savePromise
}
const setCurrentScreen = store => screenName => {
store.update(s => {
const screen = getExactComponent(s.screens, screenName, true)
s.currentPreviewItem = screen
s.currentFrontEndType = "screen"
s.currentView = "detail"
regenerateCssForCurrentScreen(s)
const safeProps = makePropsSafe(
s.components[screen.props._component],
screen.props
)
screen.props = safeProps
s.currentComponentInfo = safeProps
setCurrentPageFunctions(s)
return s
})
}
const deleteScreens = store => (screens, pageName = null) => {
if (!(screens instanceof Array)) {
screens = [screens]
}
store.update(state => {
if (pageName == null) {
pageName = state.pages.main.name
}
for (let screen of screens) {
state.screens = state.screens.filter(c => c.name !== screen.name)
// Remove screen from current page as well
state.pages[pageName]._screens = state.pages[pageName]._screens.filter(
scr => scr.name !== screen.name
)
api.delete(`/_builder/api/pages/${pageName}/screens/${screen.name}`)
}
return state
})
}
const savePage = store => async page => {
store.update(state => {
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
return state
}
state.pages[state.currentPageName] = page
_savePage(state)
return state
})
}
const addStylesheet = store => stylesheet => {
store.update(s => {
s.pages.stylesheets.push(stylesheet)
_savePage(s)
return s
})
}
const removeStylesheet = store => stylesheet => {
store.update(state => {
state.pages.stylesheets = state.pages.stylesheets.filter(
s => s !== stylesheet
)
_savePage(state)
return state
})
}
const setCurrentPage = store => pageName => {
store.update(state => {
const current_screens = state.pages[pageName]._screens
const currentPage = state.pages[pageName]
state.currentFrontEndType = "page"
state.currentView = "detail"
state.currentPageName = pageName
state.screens = Array.isArray(current_screens)
? current_screens
: Object.values(current_screens)
const safeProps = makePropsSafe(
state.components[currentPage.props._component],
currentPage.props
)
state.currentComponentInfo = safeProps
currentPage.props = safeProps
state.currentPreviewItem = state.pages[pageName]
regenerateCssForCurrentScreen(state)
for (let screen of state.screens) {
screen._css = generate_screen_css([screen.props])
}
setCurrentPageFunctions(state)
return state
})
}
/**
* @param {string} componentToAdd - name of the component to add to the application
* @param {string} presetName - name of the component preset if defined
*/
const addChildComponent = store => (componentToAdd, presetProps = {}) => {
store.update(state => {
function findSlot(component_array) {
for (let i = 0; i < component_array.length; i += 1) {
if (component_array[i]._component === "##builtin/screenslot") {
return true
}
if (component_array[i]._children) findSlot(component_array[i])
}
return false
}
if (
componentToAdd.startsWith("##") &&
findSlot(state.pages[state.currentPageName].props._children)
) {
return state
}
const component = getComponentDefinition(state, componentToAdd)
const instanceId = get(backendUiStore).selectedDatabase._id
const instanceName = getNewComponentName(component, state)
const newComponent = createProps(
component,
{
...presetProps,
_instanceId: instanceId,
_instanceName: instanceName,
},
state
)
const currentComponent =
state.components[state.currentComponentInfo._component]
const targetParent = currentComponent.children
? state.currentComponentInfo
: getParent(state.currentPreviewItem.props, state.currentComponentInfo)
// Don't continue if there's no parent
if (!targetParent) {
return state
}
targetParent._children = targetParent._children.concat(newComponent.props)
state.currentFrontEndType === "page"
? _savePage(state)
: _saveScreenApi(state.currentPreviewItem, state)
state.currentView = "component"
state.currentComponentInfo = newComponent.props
analytics.captureEvent("Added Component", {
name: newComponent.props._component,
})
return state
})
}
/**
* @param {string} props - props to add, as child of current component
*/
const addTemplatedComponent = store => props => {
store.update(state => {
walkProps(props, p => {
p._id = uuid()
})
state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
props
)
regenerateCssForCurrentScreen(state)
setCurrentPageFunctions(state)
_saveCurrentPreviewItem(state)
return state
})
}
const selectComponent = store => component => {
store.update(state => {
return _selectComponent(state, component)
})
}
const setComponentProp = store => (name, value) => {
store.update(state => {
let current_component = state.currentComponentInfo
current_component[name] = value
state.currentComponentInfo = current_component
_saveCurrentPreviewItem(state)
return state
})
}
const setPageOrScreenProp = store => (name, value) => {
store.update(state => {
if (name === "_instanceName" && state.currentFrontEndType === "screen") {
state.currentPreviewItem.props[name] = value
} else {
state.currentPreviewItem[name] = value
}
_saveCurrentPreviewItem(state)
return state
})
}
const setComponentStyle = store => (type, name, value) => {
store.update(state => {
if (!state.currentComponentInfo._styles) {
state.currentComponentInfo._styles = {}
}
state.currentComponentInfo._styles[type][name] = value
regenerateCssForCurrentScreen(state)
// save without messing with the store
_saveCurrentPreviewItem(state)
return state
})
}
const setComponentCode = store => code => {
store.update(state => {
state.currentComponentInfo._code = code
setCurrentPageFunctions(state)
// save without messing with the store
_saveScreenApi(state.currentPreviewItem, state)
return state
})
}
const setCurrentPageFunctions = s => {
s.currentPageFunctions = buildPageCode(s.screens, s.pages[s.currentPageName])
insertCodeMetadata(s.currentPreviewItem.props)
}
const buildPageCode = (screens, page) => buildCodeForScreens([page, ...screens])
const setScreenType = store => type => {
store.update(state => {
state.currentFrontEndType = type
const pageOrScreen =
type === "page"
? state.pages[state.currentPageName]
: state.pages[state.currentPageName]._screens[0]
state.currentComponentInfo = pageOrScreen ? pageOrScreen.props : null
state.currentPreviewItem = pageOrScreen
state.currentView = "detail"
return state
})
}
const editPageOrScreen = store => (key, value, setOnComponent = false) => {
store.update(state => {
setOnComponent
? (state.currentPreviewItem.props[key] = value)
: (state.currentPreviewItem[key] = value)
_saveCurrentPreviewItem(state)
return state
})
}
const getPathToComponent = store => component => {
// Gets all the components to needed to construct a path.
const tempStore = get(store)
let pathComponents = []
let parent = component
let root = false
while (!root) {
parent = getParent(tempStore.currentPreviewItem.props, parent)
if (!parent) {
root = true
} else {
pathComponents.push(parent)
}
}
// Remove root entry since it's the screen or page layout.
// Reverse array since we need the correct order of the IDs
const reversedComponents = pathComponents.reverse().slice(1)
// Add component
const allComponents = [...reversedComponents, component]
// Map IDs
const IdList = allComponents.map(c => c._id)
// Construct ID Path:
const path = IdList.join("/")
return path
}
const setMetadataProp = store => (name, prop) => {
store.update(s => {
s.currentPreviewItem[name] = prop
return s
})
}
const storeComponentForCopy = store => (component, cut = false) => {
store.update(s => {
const copiedComponent = cloneDeep(component)
s.componentToPaste = copiedComponent
s.componentToPaste.isCut = cut
if (cut) {
const parent = getParent(s.currentPreviewItem.props, component._id)
parent._children = parent._children.filter(c => c._id !== component._id)
selectComponent(s, parent)
}
return s
})
}
const pasteComponent = store => (targetComponent, mode) => {
store.update(s => {
if (!s.componentToPaste) return s
const componentToPaste = cloneDeep(s.componentToPaste)
// retain the same ids as things may be referencing this component
if (componentToPaste.isCut) {
// in case we paste a second time
s.componentToPaste.isCut = false
} else {
generateNewIdsForComponent(componentToPaste, s)
}
delete componentToPaste.isCut
if (mode === "inside") {
targetComponent._children.push(componentToPaste)
return s
}
const parent = getParent(s.currentPreviewItem.props, targetComponent)
const targetIndex = parent._children.indexOf(targetComponent)
const index = mode === "above" ? targetIndex : targetIndex + 1
parent._children.splice(index, 0, cloneDeep(componentToPaste))
regenerateCssForCurrentScreen(s)
_saveCurrentPreviewItem(s)
selectComponent(s, componentToPaste)
return s
})
}

View file

@ -1,21 +1,7 @@
import { import { getBuiltin } from "components/userInterface/pagesParsing/createProps"
makePropsSafe,
getBuiltin,
} from "components/userInterface/pagesParsing/createProps"
import api from "./api"
import { generate_screen_css } from "./generate_css"
import { uuid } from "./uuid" import { uuid } from "./uuid"
import getNewComponentName from "./getNewComponentName" import getNewComponentName from "./getNewComponentName"
export const selectComponent = (state, component) => {
const componentDef = component._component.startsWith("##")
? component
: state.components[component._component]
state.currentComponentInfo = makePropsSafe(componentDef, component)
state.currentView = "component"
return state
}
export const getParent = (rootProps, child) => { export const getParent = (rootProps, child) => {
let parent let parent
walkProps(rootProps, (p, breakWalk) => { walkProps(rootProps, (p, breakWalk) => {
@ -30,41 +16,6 @@ export const getParent = (rootProps, child) => {
return parent return parent
} }
export const saveCurrentPreviewItem = s =>
s.currentFrontEndType === "page"
? savePage(s)
: saveScreenApi(s.currentPreviewItem, s)
export const savePage = async s => {
const pageName = s.currentPageName || "main"
const page = s.pages[pageName]
await api.post(`/_builder/api/${s.appId}/pages/${pageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions,
screens: page._screens,
})
}
export const saveScreenApi = (screen, s) => {
api
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
.then(() => savePage(s))
}
export const renameCurrentScreen = (newname, state) => {
const oldname = state.currentPreviewItem.props._instanceName
state.currentPreviewItem.props._instanceName = 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, () => {
@ -79,21 +30,14 @@ export const walkProps = (props, action, cancelToken = null) => {
} }
} }
export const regenerateCssForScreen = screen => { export const generateNewIdsForComponent = (
screen._css = generate_screen_css([screen.props]) component,
} state,
changeName = true
export const regenerateCssForCurrentScreen = state => { ) =>
if (state.currentPreviewItem) { walkProps(component, prop => {
regenerateCssForScreen(state.currentPreviewItem) prop._id = uuid()
} if (changeName) prop._instanceName = getNewComponentName(prop, state)
return state
}
export const generateNewIdsForComponent = (c, state, changeName = true) =>
walkProps(c, p => {
p._id = uuid()
if (changeName) p._instanceName = getNewComponentName(p._component, state)
}) })
export const getComponentDefinition = (state, name) => export const getComponentDefinition = (state, name) =>

View file

@ -55,7 +55,7 @@
// Record the table that created this screen so we can link it later // Record the table that created this screen so we can link it later
screen.autoTableId = table._id screen.autoTableId = table._id
try { try {
await store.createScreen(screen) await store.actions.screens.create(screen)
} catch (_) { } catch (_) {
// TODO: this is temporary // TODO: this is temporary
// a cypress test is failing, because I added the // a cypress test is failing, because I added the
@ -70,7 +70,7 @@
const listPage = screens.find(screen => const listPage = screens.find(screen =>
screen.props._instanceName.endsWith("List") screen.props._instanceName.endsWith("List")
) )
await store.createLink(listPage.route, table.name) await store.actions.components.links.save(listPage.route, table.name)
// Navigate to new table // Navigate to new table
$goto(`./table/${table._id}`) $goto(`./table/${table._id}`)

View file

@ -1,5 +1,5 @@
<script> <script>
import { backendUiStore, store } from "builderStore" import { backendUiStore, store, allScreens } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Input } from "@budibase/bbui" import { DropdownMenu, Button, Input } from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -28,7 +28,7 @@
} }
function showModal() { function showModal() {
const screens = $store.screens const screens = $allScreens
templateScreens = screens.filter(screen => screen.autoTableId === table._id) templateScreens = screens.filter(screen => screen.autoTableId === table._id)
willBeDeleted = ["All table data"].concat( willBeDeleted = ["All table data"].concat(
templateScreens.map(screen => `Screen ${screen.props._instanceName}`) templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
@ -39,7 +39,7 @@
async function deleteTable() { async function deleteTable() {
await backendUiStore.actions.tables.delete(table) await backendUiStore.actions.tables.delete(table)
store.deleteScreens(templateScreens) store.store.actions.screens.delete(templateScreens)
await backendUiStore.actions.tables.fetch() await backendUiStore.actions.tables.fetch()
notifier.success("Table deleted") notifier.success("Table deleted")
hideEditor() hideEditor()

View file

@ -128,7 +128,7 @@
} }
} }
async function signUp() { async function createNewApp() {
submitting = true submitting = true
try { try {
// Add API key if there is none. // Add API key if there is none.
@ -154,7 +154,7 @@
if (applicationPkg.ok) { if (applicationPkg.ok) {
backendUiStore.actions.reset() backendUiStore.actions.reset()
pkg.justCreated = true pkg.justCreated = true
await store.setPackage(pkg) await store.actions.initialise(pkg)
automationStore.actions.fetch() automationStore.actions.fetch()
} else { } else {
throw new Error(pkg) throw new Error(pkg)
@ -193,10 +193,6 @@
$: checkValidity($createAppStore.values, $createAppStore.currentStep) $: checkValidity($createAppStore.values, $createAppStore.currentStep)
let onChange = () => {} let onChange = () => {}
async function _onOkay() {
await createNewApp()
}
</script> </script>
<div class="container"> <div class="container">
@ -239,7 +235,7 @@
<Button <Button
medium medium
blue blue
on:click={signUp} on:click={createNewApp}
disabled={!fullFormIsValid || submitting}> disabled={!fullFormIsValid || submitting}>
{submitting ? 'Loading...' : 'Submit'} {submitting ? 'Loading...' : 'Submit'}
</Button> </Button>

View file

@ -4,7 +4,7 @@
import { getComponentDefinition } from "builderStore/storeUtils" import { getComponentDefinition } from "builderStore/storeUtils"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp" import { last } from "lodash/fp"
import { getParent, saveCurrentPreviewItem } from "builderStore/storeUtils" import { getParent } from "builderStore/storeUtils"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -25,50 +25,50 @@
} }
const selectComponent = component => { const selectComponent = component => {
store.selectComponent(component) store.actions.components.select(component)
const path = store.getPathToComponent(component) const path = store.actions.components.findRoute(component)
$goto(`./:page/:screen/${path}`) $goto(`./:page/:screen/${path}`)
} }
const moveUpComponent = () => { const moveUpComponent = () => {
store.update(s => { store.update(state => {
const parent = getParent(s.currentPreviewItem.props, component) const parent = getParent(state.currentPreviewItem.props, component)
if (parent) { if (parent) {
const currentIndex = parent._children.indexOf(component) const currentIndex = parent._children.indexOf(component)
if (currentIndex === 0) return s if (currentIndex === 0) return state
const newChildren = parent._children.filter(c => c !== component) const newChildren = parent._children.filter(c => c !== component)
newChildren.splice(currentIndex - 1, 0, component) newChildren.splice(currentIndex - 1, 0, component)
parent._children = newChildren parent._children = newChildren
} }
s.currentComponentInfo = component state.currentComponentInfo = component
saveCurrentPreviewItem(s) store.actions.preview.saveSelected()
return s return state
}) })
} }
const moveDownComponent = () => { const moveDownComponent = () => {
store.update(s => { store.update(state => {
const parent = getParent(s.currentPreviewItem.props, component) const parent = getParent(state.currentPreviewItem.props, component)
if (parent) { if (parent) {
const currentIndex = parent._children.indexOf(component) const currentIndex = parent._children.indexOf(component)
if (currentIndex === parent._children.length - 1) return s if (currentIndex === parent._children.length - 1) return state
const newChildren = parent._children.filter(c => c !== component) const newChildren = parent._children.filter(c => c !== component)
newChildren.splice(currentIndex + 1, 0, component) newChildren.splice(currentIndex + 1, 0, component)
parent._children = newChildren parent._children = newChildren
} }
s.currentComponentInfo = component state.currentComponentInfo = component
saveCurrentPreviewItem(s) store.actions.preview.saveSelected()
return s return state
}) })
} }
const copyComponent = () => { const duplicateComponent = () => {
storeComponentForCopy(false) storeComponentForCopy(false)
pasteComponent("below") pasteComponent("below")
} }
@ -82,19 +82,19 @@
selectComponent(parent) selectComponent(parent)
} }
saveCurrentPreviewItem(state) store.actions.preview.saveSelected()
return state return state
}) })
} }
const storeComponentForCopy = (cut = false) => { const storeComponentForCopy = (cut = false) => {
// lives in store - also used by drag drop // lives in store - also used by drag drop
store.storeComponentForCopy(component, cut) store.actions.components.copy(component, cut)
} }
const pasteComponent = mode => { const pasteComponent = mode => {
// lives in store - also used by drag drop // lives in store - also used by drag drop
store.pasteComponent(component, mode) store.actions.components.paste(component, mode)
} }
</script> </script>
@ -117,7 +117,7 @@
<DropdownItem <DropdownItem
icon="ri-repeat-one-line" icon="ri-repeat-one-line"
title="Duplicate" title="Duplicate"
on:click={copyComponent} /> on:click={duplicateComponent} />
<DropdownItem <DropdownItem
icon="ri-scissors-cut-line" icon="ri-scissors-cut-line"
title="Cut" title="Cut"

View file

@ -27,7 +27,7 @@
componentPropDefinition.properties && componentPropDefinition.properties &&
componentPropDefinition.properties[selectedCategory.value] componentPropDefinition.properties[selectedCategory.value]
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.actions.components.updateStyle
$: isComponentOrScreen = $: isComponentOrScreen =
$store.currentView === "component" || $store.currentView === "component" ||
@ -58,6 +58,18 @@
return components return components
} }
function setPageOrScreenProp(name, value) {
store.update(state => {
if (name === "_instanceName" && state.currentFrontEndType === "screen") {
state.currentPreviewItem.props[name] = value
} else {
state.currentPreviewItem[name] = value
}
store.actions.preview.saveSelected()
return state
})
}
function getProps(obj, keys) { function getProps(obj, keys) {
return keys.map((key, i) => [key, obj[key], obj.props._id + i]) return keys.map((key, i) => [key, obj[key], obj.props._id + i])
} }
@ -81,8 +93,8 @@
{componentDefinition} {componentDefinition}
{panelDefinition} {panelDefinition}
displayNameField={displayName} displayNameField={displayName}
onChange={store.setComponentProp} onChange={store.actions.components.updateProp}
onScreenPropChange={store.setPageOrScreenProp} onScreenPropChange={setPageOrScreenProp}
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} /> screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
{/if} {/if}
</div> </div>

View file

@ -25,8 +25,8 @@
} }
const onComponentChosen = component => { const onComponentChosen = component => {
store.addChildComponent(component._component, component.presetProps) store.actions.components.create(component._component, component.presetProps)
const path = store.getPathToComponent($store.currentComponentInfo) const path = store.actions.components.findRoute($store.currentComponentInfo)
$goto(`./:page/:screen/${path}`) $goto(`./:page/:screen/${path}`)
close() close()
} }

View file

@ -32,7 +32,7 @@
]) ])
const changeScreen = screen => { const changeScreen = screen => {
store.setCurrentScreen(screen.props._instanceName) store.actions.screens.select(screen.props._instanceName)
$goto(`./:page/${screen.props._instanceName}`) $goto(`./:page/${screen.props._instanceName}`)
} }
</script> </script>

View file

@ -40,10 +40,10 @@
const selectComponent = component => { const selectComponent = component => {
// Set current component // Set current component
store.selectComponent(component) store.actions.components.select(component)
// Get ID path // Get ID path
const path = store.getPathToComponent(component) const path = store.actions.components.findRoute(component)
// Go to correct URL // Go to correct URL
$goto(`./:page/:screen/${path}`) $goto(`./:page/:screen/${path}`)
@ -96,8 +96,8 @@
const drop = () => { const drop = () => {
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) { if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
store.storeComponentForCopy($dragDropStore.componentToDrop, true) store.actions.components.copy($dragDropStore.componentToDrop, true)
store.pasteComponent( store.actions.components.paste(
$dragDropStore.targetComponent, $dragDropStore.targetComponent,
$dragDropStore.dropPosition $dragDropStore.dropPosition
) )

View file

@ -1,5 +1,5 @@
<script> <script>
import { store } from "builderStore/" import { store, allScreens } from "builderStore"
import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte" import ComponentPropertiesPanel from "./ComponentPropertiesPanel.svelte"
import ComponentSelectionList from "./ComponentSelectionList.svelte" import ComponentSelectionList from "./ComponentSelectionList.svelte"
@ -18,7 +18,7 @@
</script> </script>
<div class="root"> <div class="root">
{#if $store.currentFrontEndType === 'page' || $store.screens.length} {#if $store.currentFrontEndType === 'page' || $allScreens.length}
<div class="switcher"> <div class="switcher">
<button <button
class:selected={selected === COMPONENT_SELECTION_TAB} class:selected={selected === COMPONENT_SELECTION_TAB}

View file

@ -1,7 +1,7 @@
<script> <script>
import { DataList } from "@budibase/bbui" import { DataList } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { store } from "builderStore" import { allScreens } from "builderStore"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -13,7 +13,7 @@
const getUrls = () => { const getUrls = () => {
return [ return [
...$store.screens ...$allScreens
.filter( .filter(
screen => screen =>
screen.props._component.endsWith("/rowdetail") || screen.props._component.endsWith("/rowdetail") ||

View file

@ -1,6 +1,6 @@
<script> <script>
import { Input, DataList, Select } from "@budibase/bbui" import { Input, DataList, Select } from "@budibase/bbui"
import { store, automationStore } from "builderStore" import { automationStore, allScreens } from "builderStore"
export let parameter export let parameter
@ -24,7 +24,7 @@
{:else if parameter.name === 'url'} {:else if parameter.name === 'url'}
<DataList on:change bind:value={parameter.value}> <DataList on:change bind:value={parameter.value}>
<option value="" /> <option value="" />
{#each $store.screens as screen} {#each $allScreens as screen}
<option value={screen.route}>{screen.props._instanceName}</option> <option value={screen.route}>{screen.props._instanceName}</option>
{/each} {/each}
</DataList> </DataList>

View file

@ -1,6 +1,6 @@
<script> <script>
import { DataList, Label } from "@budibase/bbui" import { DataList, Label } from "@budibase/bbui"
import { store } from "builderStore" import { allScreens } from "builderStore"
export let parameters export let parameters
</script> </script>
@ -9,7 +9,7 @@
<Label size="m" color="dark">Screen</Label> <Label size="m" color="dark">Screen</Label>
<DataList secondary bind:value={parameters.url}> <DataList secondary bind:value={parameters.url}>
<option value="" /> <option value="" />
{#each $store.screens as screen} {#each $allScreens as screen}
<option value={screen.route}>{screen.props._instanceName}</option> <option value={screen.route}>{screen.props._instanceName}</option>
{/each} {/each}
</DataList> </DataList>

View file

@ -1,5 +1,5 @@
<script> <script>
import { store } from "builderStore" import { store, currentScreens } from "builderStore"
import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte" import ComponentsHierarchy from "components/userInterface/ComponentsHierarchy.svelte"
import PageLayout from "components/userInterface/PageLayout.svelte" import PageLayout from "components/userInterface/PageLayout.svelte"
import PagesList from "components/userInterface/PagesList.svelte" import PagesList from "components/userInterface/PagesList.svelte"
@ -16,7 +16,7 @@
<PagesList /> <PagesList />
<div class="nav-items-container"> <div class="nav-items-container">
<PageLayout layout={$store.pages[$store.currentPageName]} /> <PageLayout layout={$store.pages[$store.currentPageName]} />
<ComponentsHierarchy screens={$store.screens} /> <ComponentsHierarchy screens={$currentScreens} />
</div> </div>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<NewScreenModal /> <NewScreenModal />

View file

@ -1,6 +1,6 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store, backendUiStore } from "builderStore" import { store, backendUiStore, allScreens } from "builderStore"
import { import {
Input, Input,
Button, Button,
@ -24,7 +24,7 @@
$: templates = getTemplates($store, $backendUiStore.tables) $: templates = getTemplates($store, $backendUiStore.tables)
$: route = !route && $store.screens.length === 0 ? "*" : route $: route = !route && $allScreens.length === 0 ? "*" : route
$: baseComponents = Object.values($store.components) $: baseComponents = Object.values($store.components)
.filter(componentDefinition => componentDefinition.baseComponent) .filter(componentDefinition => componentDefinition.baseComponent)
@ -71,9 +71,9 @@
draftScreen.props._component = baseComponent draftScreen.props._component = baseComponent
draftScreen.route = route draftScreen.route = route
await store.createScreen(draftScreen) await store.actions.screens.create(draftScreen)
if (createLink) { if (createLink) {
await store.createLink(route, name) await store.actions.components.links.save(route, name)
} }
if (templateIndex !== undefined) { if (templateIndex !== undefined) {
@ -87,7 +87,7 @@
} }
const routeNameExists = route => { const routeNameExists = route => {
return $store.screens.some( return $allScreens.some(
screen => screen.route.toLowerCase() === route.toLowerCase() screen => screen.route.toLowerCase() === route.toLowerCase()
) )
} }

View file

@ -22,7 +22,7 @@
} }
const setCurrentScreenToLayout = () => { const setCurrentScreenToLayout = () => {
store.setScreenType("page") store.actions.selectPageOrScreen("page")
$goto("./:page/page-layout") $goto("./:page/page-layout")
} }
</script> </script>

View file

@ -2,8 +2,8 @@
import { params, goto } from "@sveltech/routify" import { params, goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
const getPage = (s, name) => { const getPage = (state, name) => {
const props = s.pages[name] const props = state.pages[name]
return { name, props } return { name, props }
} }
@ -19,10 +19,10 @@
] ]
if (!$store.currentPageName) if (!$store.currentPageName)
store.setCurrentPage($params.page ? $params.page : "main") store.actions.pages.select($params.page ? $params.page : "main")
const changePage = id => { const changePage = id => {
store.setCurrentPage(id) store.actions.pages.select(id)
$goto(`./${id}/page-layout`) $goto(`./${id}/page-layout`)
} }
</script> </script>

View file

@ -1,6 +1,7 @@
<script> <script>
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { DropdownMenu } from "@budibase/bbui" import { DropdownMenu } from "@budibase/bbui"
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" import { DropdownContainer, DropdownItem } from "components/common/Dropdowns"
@ -12,11 +13,12 @@
let anchor let anchor
const deleteScreen = () => { const deleteScreen = () => {
store.deleteScreens(screen, $store.currentPageName) store.actions.screens.delete(screen, $store.currentPageName)
// update the page if required // update the page if required
store.update(state => { store.update(state => {
if (state.currentPreviewItem.name === screen.name) { if (state.currentPreviewItem.name === screen.name) {
store.setCurrentPage($store.currentPageName) store.actions.pages.select($store.currentPageName)
notifier.success(`Screen ${screen.name} deleted successfully.`)
$goto(`./:page/page-layout`) $goto(`./:page/page-layout`)
} }
return state return state

View file

@ -1,7 +1,7 @@
<script> <script>
import { DataList } from "@budibase/bbui" import { DataList } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { store, backendUiStore } from "builderStore" import { store, allScreens, backendUiStore } from "builderStore"
import fetchBindableProperties from "builderStore/fetchBindableProperties" import fetchBindableProperties from "builderStore/fetchBindableProperties"
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -17,7 +17,7 @@
// and substitute the :id param for the actual {{ ._id }} binding // and substitute the :id param for the actual {{ ._id }} binding
const getUrls = () => { const getUrls = () => {
const urls = [ const urls = [
...$store.screens ...$allScreens
.filter(screen => !screen.props._component.endsWith("/rowdetail")) .filter(screen => !screen.props._component.endsWith("/rowdetail"))
.map(screen => ({ .map(screen => ({
name: screen.props._instanceName, name: screen.props._instanceName,
@ -33,7 +33,7 @@
tables: $backendUiStore.tables, tables: $backendUiStore.tables,
}) })
const detailScreens = $store.screens.filter(screen => const detailScreens = $allScreens.filter(screen =>
screen.props._component.endsWith("/rowdetail") screen.props._component.endsWith("/rowdetail")
) )

View file

@ -4,7 +4,7 @@
import Input from "./PropertyPanelControls/Input.svelte" import Input from "./PropertyPanelControls/Input.svelte"
import { goto } from "@sveltech/routify" import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js" import { excludeProps } from "./propertyCategories.js"
import { store } from "builderStore" import { store, allScreens } from "builderStore"
import { walkProps } from "builderStore/storeUtils" import { walkProps } from "builderStore/storeUtils"
export let panelDefinition = [] export let panelDefinition = []
@ -67,7 +67,7 @@
lookForDuplicate($store.currentPreviewItem.props) lookForDuplicate($store.currentPreviewItem.props)
} else { } else {
// viewing master page - need to dedupe against all screens // viewing master page - need to dedupe against all screens
for (let screen of $store.screens) { for (let screen of $allScreens) {
lookForDuplicate(screen.props) lookForDuplicate(screen.props)
} }
} }

View file

@ -59,6 +59,11 @@ export const createProps = (componentDefinition, derivedFromProps) => {
} }
export const makePropsSafe = (componentDefinition, props) => { export const makePropsSafe = (componentDefinition, props) => {
if (!componentDefinition) {
console.error(
"No component definition passed to makePropsSafe. Please check the component definition is being passed correctly."
)
}
const safeProps = createProps(componentDefinition, props).props const safeProps = createProps(componentDefinition, props).props
for (let propName in safeProps) { for (let propName in safeProps) {
props[propName] = safeProps[propName] props[propName] = safeProps[propName]

View file

@ -29,8 +29,8 @@ export const searchAllComponents = (components, phrase) => {
} }
export const getExactComponent = (components, name, isScreen = false) => { export const getExactComponent = (components, name, isScreen = false) => {
return components.find(c => return components.find(comp =>
isScreen ? c.props._instanceName === name : c._instanceName === name isScreen ? comp.props._instanceName === name : comp._instanceName === name
) )
} }

View file

@ -1,19 +1,15 @@
export const DEFAULT_PAGES_OBJECT = { export const DEFAULT_PAGES_OBJECT = {
main: { main: {
_props: {}, props: {
_screens: {}, _component: "@budibase/standard-components/container",
index: {
_component: "./components/indexHtml",
}, },
appBody: "bbapp.main.json", _screens: {},
}, },
unauthenticated: { unauthenticated: {
_props: {}, props: {
_screens: {}, _component: "@budibase/standard-components/container",
index: {
_component: "./components/indexHtml",
}, },
appBody: "bbapp.unauthenticated.json", _screens: {},
}, },
componentLibraries: [], componentLibraries: [],
stylesheets: [], stylesheets: [],

View file

@ -18,7 +18,7 @@
if (res.ok) { if (res.ok) {
backendUiStore.actions.reset() backendUiStore.actions.reset()
await store.setPackage(pkg) await store.actions.initialise(pkg)
await automationStore.actions.fetch() await automationStore.actions.fetch()
return pkg return pkg
} else { } else {

View file

@ -1,4 +1,4 @@
<script> <script>
import { params } from "@sveltech/routify" import { params } from "@sveltech/routify"
store.setCurrentPage($params.page) store.actions.pages.select($params.page)
</script> </script>

View file

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { params, leftover, goto } from "@sveltech/routify" import { params, leftover, goto } from "@sveltech/routify"
import { store } from "builderStore" import { store, allScreens } from "builderStore"
// Get any leftover params not caught by Routifys params store. // Get any leftover params not caught by Routifys params store.
const componentIds = $leftover.split("/").filter(id => id !== "") const componentIds = $leftover.split("/").filter(id => id !== "")
@ -10,17 +10,17 @@
if ($params.screen !== "page-layout") { if ($params.screen !== "page-layout") {
const currentScreenName = decodeURI($params.screen) const currentScreenName = decodeURI($params.screen)
const validScreen = const validScreen =
$store.screens.findIndex( $allScreens.findIndex(
screen => screen.props._instanceName === currentScreenName screen => screen.props._instanceName === currentScreenName
) !== -1 ) !== -1
if (!validScreen) { if (!validScreen) {
// Go to main layout if URL set to invalid screen // Go to main layout if URL set to invalid screen
store.setCurrentPage("main") store.actions.pages.select("main")
$goto("../../main") $goto("../../main")
} else { } else {
// Otherwise proceed to set screen // Otherwise proceed to set screen
store.setCurrentScreen(currentScreenName) store.actions.screens.select(currentScreenName)
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it. // There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
if ($leftover) { if ($leftover) {
@ -35,7 +35,7 @@
} }
} else { } else {
// It's a page, so set the screentype to page. // It's a page, so set the screentype to page.
store.setScreenType("page") store.actions.selectPageOrScreen("page")
// There are leftover stuff, like IDs, so navigate the components and find the ID and select it. // There are leftover stuff, like IDs, so navigate the components and find the ID and select it.
if ($leftover) { if ($leftover) {
@ -64,7 +64,7 @@
}) })
// Select Component! // Select Component!
if (componentToSelect) store.selectComponent(componentToSelect) if (componentToSelect) store.actions.components.select(componentToSelect)
} }
</script> </script>

View file

@ -2,7 +2,7 @@
import { params } from "@sveltech/routify" import { params } from "@sveltech/routify"
import { store } from "builderStore" import { store } from "builderStore"
store.setCurrentPage($params.page) store.actions.pages.select($params.page)
</script> </script>
<slot /> <slot />

View file

@ -28,12 +28,12 @@
<Link <Link
icon={CommunityIcon} icon={CommunityIcon}
title="Community" title="Community"
href="https://forum.budibase.com/" /> href="https://github.com/Budibase/budibase/discussions" />
<Link <Link
icon={BugIcon} icon={BugIcon}
title="Raise an issue" title="Raise an issue"
href="https://github.com/Budibase/budibase" /> href="https://github.com/Budibase/budibase/issues/new/choose" />
</div> </div>
</div> </div>

View file

@ -1,63 +0,0 @@
import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"
describe("buildCodeForScreen", () => {
it("should package _code into runnable function, for simple screen props", () => {
const screen = {
props: {
_id: "1234",
_code: "render('render argument');",
},
}
let renderArg
const render = arg => {
renderArg = arg
}
const uiFunctions = getFunctions(screen)
const targetfunction = uiFunctions[screen.props._id]
expect(targetfunction).toBeDefined()
targetfunction(render)
expect(renderArg).toBe("render argument")
})
it("should package _code into runnable function, for _children ", () => {
const screen = {
props: {
_id: "parent",
_code: "render('parent argument');",
_children: [
{
_id: "child1",
_code: "render('child 1 argument');",
},
{
_id: "child2",
_code: "render('child 2 argument');",
},
],
},
}
let renderArg
const render = arg => {
renderArg = arg
}
const uiFunctions = getFunctions(screen)
const targetfunction = uiFunctions["child2"]
expect(targetfunction).toBeDefined()
targetfunction(render)
expect(renderArg).toBe("child 2 argument")
})
})
const getFunctions = screen => {
const code = buildCodeForScreens([screen])
const func = new Function(`return ${code}`)()
return func
}

View file

@ -23,6 +23,12 @@ yargs
}, },
async args => { async args => {
console.log("Exporting app..") console.log("Exporting app..")
if (args.name == null || args.appId == null) {
console.error(
"Unable to export without a name and app ID being specified, check help for more info."
)
return
}
const exportPath = await exportTemplateFromApp({ const exportPath = await exportTemplateFromApp({
templateName: args.name, templateName: args.name,
appId: args.appId, appId: args.appId,

View file

@ -1,21 +1,29 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const { getPackageForBuilder, buildPage } = require("../../utilities/builder") const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage")
const env = require("../../environment") const env = require("../../environment")
const { copy, existsSync, readFile, writeFile } = require("fs-extra") const { existsSync } = require("fs-extra")
const { budibaseAppsDir } = require("../../utilities/budibaseDir") const { budibaseAppsDir } = require("../../utilities/budibaseDir")
const sqrl = require("squirrelly")
const setBuilderToken = require("../../utilities/builder/setBuilderToken") const setBuilderToken = require("../../utilities/builder/setBuilderToken")
const fs = require("fs-extra") const fs = require("fs-extra")
const { join, resolve } = require("../../utilities/centralPath") const { join, resolve } = require("../../utilities/centralPath")
const { promisify } = require("util")
const chmodr = require("chmodr")
const packageJson = require("../../../package.json") const packageJson = require("../../../package.json")
const { createLinkView } = require("../../db/linkedRows") const { createLinkView } = require("../../db/linkedRows")
const { downloadTemplate } = require("../../utilities/templates") const { downloadTemplate } = require("../../utilities/templates")
const { generateAppID, DocumentTypes, SEPARATOR } = require("../../db/utils") const {
generateAppID,
DocumentTypes,
SEPARATOR,
getPageParams,
generatePageID,
generateScreenID,
} = require("../../db/utils")
const { const {
downloadExtractComponentLibraries, downloadExtractComponentLibraries,
} = require("../../utilities/createAppPackage") } = require("../../utilities/createAppPackage")
const { MAIN, UNAUTHENTICATED, PageTypes } = require("../../constants/pages")
const { HOME_SCREEN } = require("../../constants/screens")
const { cloneDeep } = require("lodash/fp")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
async function createInstance(template) { async function createInstance(template) {
@ -60,13 +68,31 @@ exports.fetch = async function(ctx) {
exports.fetchAppPackage = async function(ctx) { exports.fetchAppPackage = async function(ctx) {
const db = new CouchDB(ctx.params.appId) const db = new CouchDB(ctx.params.appId)
const application = await db.get(ctx.params.appId) const application = await db.get(ctx.params.appId)
ctx.body = await getPackageForBuilder(ctx.config, application)
let pages = await db.allDocs(
getPageParams(null, {
include_docs: true,
})
)
pages = pages.rows.map(row => row.doc)
const mainPage = pages.find(page => page.name === PageTypes.MAIN)
const unauthPage = pages.find(page => page.name === PageTypes.UNAUTHENTICATED)
ctx.body = {
application,
pages: {
main: mainPage,
unauthenticated: unauthPage,
},
}
await setBuilderToken(ctx, ctx.params.appId, application.version) await setBuilderToken(ctx, ctx.params.appId, application.version)
} }
exports.create = async function(ctx) { exports.create = async function(ctx) {
const instance = await createInstance(ctx.request.body.template) const instance = await createInstance(ctx.request.body.template)
const appId = instance._id const appId = instance._id
const version = packageJson.version
const newApplication = { const newApplication = {
_id: appId, _id: appId,
type: "app", type: "app",
@ -84,6 +110,7 @@ exports.create = async function(ctx) {
await downloadExtractComponentLibraries(newAppFolder) await downloadExtractComponentLibraries(newAppFolder)
} }
await setBuilderToken(ctx, appId, version)
ctx.status = 200 ctx.status = 200
ctx.body = newApplication ctx.body = newApplication
ctx.message = `Application ${ctx.request.body.name} created successfully` ctx.message = `Application ${ctx.request.body.name} created successfully`
@ -120,99 +147,38 @@ exports.delete = async function(ctx) {
} }
const createEmptyAppPackage = async (ctx, app) => { const createEmptyAppPackage = async (ctx, app) => {
const templateFolder = resolve(
__dirname,
"..",
"..",
"utilities",
"appDirectoryTemplate"
)
const appsFolder = budibaseAppsDir() const appsFolder = budibaseAppsDir()
const newAppFolder = resolve(appsFolder, app._id) const newAppFolder = resolve(appsFolder, app._id)
const db = new CouchDB(app._id)
if (existsSync(newAppFolder)) { if (existsSync(newAppFolder)) {
ctx.throw(400, "App folder already exists for this application") ctx.throw(400, "App folder already exists for this application")
} }
await fs.ensureDir(join(newAppFolder, "pages", "main", "screens"), 0o777) fs.mkdirpSync(newAppFolder)
await fs.ensureDir(
join(newAppFolder, "pages", "unauthenticated", "screens"),
0o777
)
await copy(templateFolder, newAppFolder) const mainPage = cloneDeep(MAIN)
mainPage._id = generatePageID()
mainPage.title = app.name
// this line allows full permission on copied files const unauthPage = cloneDeep(UNAUTHENTICATED)
// we have an unknown problem without this, whereby the unauthPage._id = generatePageID()
// files get weird permissions and cant be written to :( unauthPage.title = app.name
const chmodrPromise = promisify(chmodr) unauthPage.props._children[0].title = `Log in to ${app.name}`
await chmodrPromise(newAppFolder, 0o777)
await updateJsonFile(join(appsFolder, app._id, "package.json"), { const homeScreen = cloneDeep(HOME_SCREEN)
name: npmFriendlyAppName(app.name), homeScreen._id = generateScreenID(mainPage._id)
await db.bulkDocs([mainPage, unauthPage, homeScreen])
await compileStaticAssetsForPage(app._id, "main", {
page: mainPage,
screens: [homeScreen],
}) })
await compileStaticAssetsForPage(app._id, "unauthenticated", {
// if this app is being created from a template, page: unauthPage,
// copy the frontend page definition files from screens: [],
// the template directory.
if (app.template) {
const templatePageDefinitions = join(
appsFolder,
"templates",
app.template.key,
"pages"
)
await copy(templatePageDefinitions, join(appsFolder, app._id, "pages"))
}
const mainJson = await updateJsonFile(
join(appsFolder, app._id, "pages", "main", "page.json"),
app
)
await buildPage(ctx.config, app._id, "main", {
page: mainJson,
screens: await loadScreens(newAppFolder, "main"),
})
const unauthenticatedJson = await updateJsonFile(
join(appsFolder, app._id, "pages", "unauthenticated", "page.json"),
app
)
await buildPage(ctx.config, app._id, "unauthenticated", {
page: unauthenticatedJson,
screens: await loadScreens(newAppFolder, "unauthenticated"),
}) })
return newAppFolder return newAppFolder
} }
const loadScreens = async (appFolder, page) => {
const screensFolder = join(appFolder, "pages", page, "screens")
const screenFiles = (await fs.readdir(screensFolder)).filter(s =>
s.endsWith(".json")
)
let screens = []
for (let file of screenFiles) {
screens.push(await fs.readJSON(join(screensFolder, file)))
}
return screens
}
const updateJsonFile = async (filePath, app) => {
const json = await readFile(filePath, "utf8")
const newJson = sqrl.Render(json, app)
await writeFile(filePath, newJson, "utf8")
return JSON.parse(newJson)
}
const npmFriendlyAppName = name =>
name
.replace(/_/g, "")
.replace(/./g, "")
.replace(/ /g, "")
.toLowerCase()

View file

@ -42,6 +42,13 @@ exports.isInvalidationComplete = async function(
return resp.Invalidation.Status === "Completed" return resp.Invalidation.Status === "Completed"
} }
/**
* Finalises the deployment, updating the quota for the user API key
* The verification process returns the levels to update to.
* Calls the "deployment-success" lambda.
* @param {object} quota The usage quota levels returned from the verifyDeploy
* @returns {Promise<object>} The usage has been updated against the user API key.
*/
exports.updateDeploymentQuota = async function(quota) { exports.updateDeploymentQuota = async function(quota) {
const DEPLOYMENT_SUCCESS_URL = const DEPLOYMENT_SUCCESS_URL =
env.DEPLOYMENT_CREDENTIALS_URL + "deploy/success" env.DEPLOYMENT_CREDENTIALS_URL + "deploy/success"
@ -67,7 +74,8 @@ exports.updateDeploymentQuota = async function(quota) {
/** /**
* Verifies the users API key and * Verifies the users API key and
* Verifies that the deployment fits within the quota of the user, * Verifies that the deployment fits within the quota of the user
* Links to the "check-api-key" lambda.
* @param {String} appId - appId being deployed * @param {String} appId - appId being deployed
* @param {String} appId - appId being deployed * @param {String} appId - appId being deployed
* @param {quota} quota - current quota being changed with this application * @param {quota} quota - current quota being changed with this application

View file

@ -0,0 +1,19 @@
const CouchDB = require("../../db/client")
const { generatePageID } = require("../../db/utils")
const compileStaticAssetsForPage = require("../../utilities/builder/compileStaticAssetsForPage")
exports.save = async function(ctx) {
const db = new CouchDB(ctx.user.appId)
const appPackage = ctx.request.body
const page = await db.get(ctx.params.pageId)
await compileStaticAssetsForPage(ctx.user.appId, page.name, ctx.request.body)
// remove special doc props which couch will complain about
delete appPackage.page._css
delete appPackage.page._screens
appPackage.page._id = appPackage.page._id || generatePageID()
ctx.body = await db.put(appPackage.page)
ctx.status = 200
}

View file

@ -1,17 +1,48 @@
/** const CouchDB = require("../../db")
* This controller is not currently fully implemented. Screens are const { getScreenParams, generateScreenID } = require("../../db/utils")
* currently managed as part of the pages API, please look in api/routes/page.js
* for routes and controllers.
*/
exports.fetch = async ctx => { exports.fetch = async ctx => {
ctx.throw(501) const db = new CouchDB(ctx.user.appId)
const screens = await db.allDocs(
getScreenParams(null, {
include_docs: true,
})
)
ctx.body = screens.rows.map(element => element.doc)
}
exports.find = async ctx => {
const db = new CouchDB(ctx.user.appId)
const screens = await db.allDocs(
getScreenParams(ctx.params.pageId, {
include_docs: true,
})
)
ctx.body = screens.response.rows
} }
exports.save = async ctx => { exports.save = async ctx => {
ctx.throw(501) const appId = ctx.user.appId
const db = new CouchDB(appId)
const screen = ctx.request.body
if (!screen._id) {
screen._id = generateScreenID(ctx.params.pageId)
}
delete screen._css
const response = await db.put(screen)
ctx.message = `Screen ${screen.name} saved.`
ctx.body = response
} }
exports.destroy = async ctx => { exports.destroy = async ctx => {
ctx.throw(501) const db = new CouchDB(ctx.user.appId)
await db.remove(ctx.params.screenId, ctx.params.revId)
ctx.message = "Screen deleted successfully"
ctx.status = 200
} }

View file

@ -2,25 +2,34 @@ const fetch = require("node-fetch")
const { const {
downloadTemplate, downloadTemplate,
exportTemplateFromApp, exportTemplateFromApp,
getLocalTemplates,
} = require("../../utilities/templates") } = require("../../utilities/templates")
const env = require("../../environment")
// development flag, can be used to test against templates exported locally
const DEFAULT_TEMPLATES_BUCKET = const DEFAULT_TEMPLATES_BUCKET =
"prod-budi-templates.s3-eu-west-1.amazonaws.com" "prod-budi-templates.s3-eu-west-1.amazonaws.com"
exports.fetch = async function(ctx) { exports.fetch = async function(ctx) {
const { type = "app" } = ctx.query const { type = "app" } = ctx.query
const response = await fetch( if (env.LOCAL_TEMPLATES) {
`https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json` ctx.body = Object.values(getLocalTemplates()[type])
) } else {
const json = await response.json() const response = await fetch(
ctx.body = Object.values(json.templates[type]) `https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json`
)
const json = await response.json()
ctx.body = Object.values(json.templates[type])
}
} }
exports.downloadTemplate = async function(ctx) { exports.downloadTemplate = async function(ctx) {
const { type, name } = ctx.params const { type, name } = ctx.params
await downloadTemplate(type, name) if (!env.LOCAL_TEMPLATES) {
await downloadTemplate(type, name)
}
ctx.body = { ctx.body = {
message: `template ${type}:${name} downloaded successfully.`, message: `template ${type}:${name} downloaded successfully.`,

View file

@ -37,15 +37,22 @@ exports.create = async function(ctx) {
accessLevelId, accessLevelId,
} }
const response = await db.post(user) try {
const response = await db.post(user)
ctx.status = 200 ctx.status = 200
ctx.message = "User created successfully." ctx.message = "User created successfully."
ctx.userId = response._id ctx.userId = response._id
ctx.body = { ctx.body = {
_rev: response.rev, _rev: response.rev,
username, username,
name, name,
}
} catch (err) {
if (err.status === 409) {
ctx.throw(400, "User exists already")
} else {
ctx.throw(err.status, err)
}
} }
} }

View file

@ -7,6 +7,7 @@ const { isDev } = require("../utilities")
const { const {
authRoutes, authRoutes,
pageRoutes, pageRoutes,
screenRoutes,
userRoutes, userRoutes,
deployRoutes, deployRoutes,
applicationRoutes, applicationRoutes,
@ -97,6 +98,9 @@ router.use(templatesRoutes.allowedMethods())
router.use(pageRoutes.routes()) router.use(pageRoutes.routes())
router.use(pageRoutes.allowedMethods()) router.use(pageRoutes.allowedMethods())
router.use(screenRoutes.routes())
router.use(screenRoutes.allowedMethods())
router.use(applicationRoutes.routes()) router.use(applicationRoutes.routes())
router.use(applicationRoutes.allowedMethods()) router.use(applicationRoutes.allowedMethods())

View file

@ -1,5 +1,6 @@
const authRoutes = require("./auth") const authRoutes = require("./auth")
const pageRoutes = require("./pages") const pageRoutes = require("./pages")
const screenRoutes = require("./screen")
const userRoutes = require("./user") const userRoutes = require("./user")
const applicationRoutes = require("./application") const applicationRoutes = require("./application")
const tableRoutes = require("./table") const tableRoutes = require("./table")
@ -19,6 +20,7 @@ module.exports = {
deployRoutes, deployRoutes,
authRoutes, authRoutes,
pageRoutes, pageRoutes,
screenRoutes,
userRoutes, userRoutes,
applicationRoutes, applicationRoutes,
rowRoutes, rowRoutes,

View file

@ -1,117 +1,10 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const StatusCodes = require("../../utilities/statusCodes")
const joiValidator = require("../../middleware/joi-validator")
const Joi = require("joi")
const {
listScreens,
saveScreen,
buildPage,
renameScreen,
deleteScreen,
} = require("../../utilities/builder")
const authorized = require("../../middleware/authorized") const authorized = require("../../middleware/authorized")
const { BUILDER } = require("../../utilities/accessLevels") const { BUILDER } = require("../../utilities/accessLevels")
const controller = require("../controllers/page")
const router = Router() const router = Router()
function generateSaveValidation() { router.post("/api/pages/:pageId", authorized(BUILDER), controller.save)
// prettier-ignore
return joiValidator.body(Joi.object({
_css: Joi.string().allow(""),
name: Joi.string().required(),
route: Joi.string().required(),
props: Joi.object({
_id: Joi.string().required(),
_component: Joi.string().required(),
_children: Joi.array().required(),
_instanceName: Joi.string().required(),
_styles: Joi.object().required(),
type: Joi.string().optional(),
table: Joi.string().optional(),
}).required().unknown(true),
}).unknown(true))
}
function generatePatchValidation() {
return joiValidator.body(
Joi.object({
oldname: Joi.string().required(),
newname: Joi.string().required(),
}).unknown(true)
)
}
router.post(
"/_builder/api/:appId/pages/:pageName",
authorized(BUILDER),
async ctx => {
await buildPage(
ctx.config,
ctx.params.appId,
ctx.params.pageName,
ctx.request.body
)
ctx.response.status = StatusCodes.OK
}
)
router.get(
"/_builder/api/:appId/pages/:pagename/screens",
authorized(BUILDER),
async ctx => {
ctx.body = await listScreens(
ctx.config,
ctx.params.appId,
ctx.params.pagename
)
ctx.response.status = StatusCodes.OK
}
)
router.post(
"/_builder/api/:appId/pages/:pagename/screen",
authorized(BUILDER),
generateSaveValidation(),
async ctx => {
ctx.body = await saveScreen(
ctx.config,
ctx.params.appId,
ctx.params.pagename,
ctx.request.body
)
ctx.response.status = StatusCodes.OK
}
)
router.patch(
"/_builder/api/:appname/pages/:pagename/screen",
authorized(BUILDER),
generatePatchValidation(),
async ctx => {
await renameScreen(
ctx.config,
ctx.params.appname,
ctx.params.pagename,
ctx.request.body.oldname,
ctx.request.body.newname
)
ctx.response.status = StatusCodes.OK
}
)
router.delete(
"/_builder/api/pages/:pagename/screens/:id",
authorized(BUILDER),
async ctx => {
await deleteScreen(
ctx.config,
ctx.user.appId,
ctx.params.pagename,
ctx.params.id
)
ctx.response.status = StatusCodes.OK
}
)
module.exports = router module.exports = router

View file

@ -2,12 +2,42 @@ const Router = require("@koa/router")
const controller = require("../controllers/screen") const controller = require("../controllers/screen")
const authorized = require("../../middleware/authorized") const authorized = require("../../middleware/authorized")
const { BUILDER } = require("../../utilities/accessLevels") const { BUILDER } = require("../../utilities/accessLevels")
const joiValidator = require("../../middleware/joi-validator")
const Joi = require("joi")
const router = Router() const router = Router()
function generateSaveValidation() {
// prettier-ignore
return joiValidator.body(Joi.object({
_css: Joi.string().allow(""),
name: Joi.string().required(),
route: Joi.string().required(),
props: Joi.object({
_id: Joi.string().required(),
_component: Joi.string().required(),
_children: Joi.array().required(),
_instanceName: Joi.string().required(),
_styles: Joi.object().required(),
type: Joi.string().optional(),
table: Joi.string().optional(),
}).required().unknown(true),
}).unknown(true))
}
router router
.get("/api/screens", authorized(BUILDER), controller.fetch) .get("/api/screens", authorized(BUILDER), controller.fetch)
.post("/api/screens", authorized(BUILDER), controller.save) .get("/api/screens/:pageId", authorized(BUILDER), controller.find)
.delete("/api/:screenId/:revId", authorized(BUILDER), controller.destroy) .post(
"/api/screens/:pageId",
authorized(BUILDER),
generateSaveValidation(),
controller.save
)
.delete(
"/api/screens/:screenId/:revId",
authorized(BUILDER),
controller.destroy
)
module.exports = router module.exports = router

View file

@ -0,0 +1,221 @@
const PageTypes = {
MAIN: "main",
UNAUTHENTICATED: "unauthenticated",
}
const MAIN = {
componentLibraries: ["@budibase/standard-components"],
title: "{{ name }}",
favicon: "./_shared/favicon.png",
stylesheets: [],
name: PageTypes.MAIN,
props: {
_id: "private-master-root",
_component: "@budibase/standard-components/container",
_children: [
{
_id: "c74f07266980c4b6eafc33e2a6caa783d",
_component: "@budibase/standard-components/container",
_styles: {
normal: {
display: "flex",
"flex-direction": "row",
"justify-content": "flex-start",
"align-items": "flex-start",
background: "#fff",
width: "100%",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
},
hover: {},
active: {},
selected: {},
},
_code: "",
className: "",
onLoad: [],
type: "div",
_appId: "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf",
_instanceName: "Header",
_children: [
{
_id: "49e0e519-9e5e-4127-885a-ee6a0a49e2c1",
_component: "@budibase/standard-components/Navigation",
_styles: {
normal: {
"max-width": "1400px",
"margin-left": "auto",
"margin-right": "auto",
padding: "20px",
color: "#757575",
"font-weight": "400",
"font-size": "16px",
flex: "1 1 auto",
},
hover: {},
active: {},
selected: {},
},
_code: "",
logoUrl:
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
title: "",
backgroundColor: "",
color: "",
borderWidth: "",
borderColor: "",
borderStyle: "",
_appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
_instanceName: "Navigation",
_children: [
{
_id: "48b35328-4c91-4343-a6a3-1a1fd77b3386",
_component: "@budibase/standard-components/link",
_styles: {
normal: {
"font-family": "Inter",
"font-weight": "500",
color: "#000000",
"text-decoration-line": "none",
"font-size": "16px",
},
hover: {
color: "#4285f4",
},
active: {},
selected: {},
},
_code: "",
url: "/",
openInNewTab: false,
text: "Home",
color: "",
hoverColor: "",
underline: false,
fontSize: "",
fontFamily: "initial",
_appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
_instanceName: "Home Link",
_children: [],
},
],
},
],
},
{
_id: "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
_component: "##builtin/screenslot",
_styles: {
normal: {
flex: "1 1 auto",
display: "flex",
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
"max-width": "100%",
"margin-left": "20px",
"margin-right": "20px",
width: "1400px",
padding: "20px",
},
hover: {},
active: {},
selected: {},
},
_code: "",
_children: [],
},
],
type: "div",
_styles: {
active: {},
hover: {},
normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "flex-start",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%",
"background-image":
"linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);",
},
selected: {},
},
_code: "",
className: "",
onLoad: [],
},
}
const UNAUTHENTICATED = {
componentLibraries: ["@budibase/standard-components"],
title: "{{ name }}",
favicon: "./_shared/favicon.png",
stylesheets: [],
name: PageTypes.UNAUTHENTICATED,
props: {
_id: "public-master-root",
_component: "@budibase/standard-components/container",
_children: [
{
_id: "686c252d-dbf2-4e28-9078-414ba4719759",
_component: "@budibase/standard-components/login",
_styles: {
normal: {
padding: "64px",
background: "rgba(255, 255, 255, 0.4)",
"border-radius": "0.5rem",
"margin-top": "0px",
margin: "0px",
"line-height": "1",
"box-shadow":
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
"font-size": "16px",
"font-family": "Inter",
flex: "0 1 auto",
transform: "0",
},
hover: {},
active: {},
selected: {},
},
_code: "",
loginRedirect: "",
usernameLabel: "Username",
passwordLabel: "Password",
loginButtonLabel: "Login",
buttonClass: "",
_instanceName: "Login",
inputClass: "",
_children: [],
title: "Log in to {{ name }}",
buttonText: "Log In",
logo:
"https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
},
],
type: "div",
_styles: {
active: {},
hover: {},
normal: {
display: "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "center",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%",
"background-image":
"linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);",
},
selected: {},
},
_code: "",
className: "",
onLoad: [],
},
}
module.exports = { MAIN, UNAUTHENTICATED, PageTypes }

View file

@ -0,0 +1,103 @@
exports.HOME_SCREEN = {
description: "",
url: "",
props: {
_id: "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
_component: "@budibase/standard-components/container",
_styles: {
normal: {
flex: "1 1 auto",
display: "flex",
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
},
hover: {},
active: {},
selected: {},
},
_code: "",
className: "",
onLoad: [],
type: "div",
_children: [
{
_id: "ef60083f-4a02-4df3-80f3-a0d3d16847e7",
_component: "@budibase/standard-components/heading",
_styles: {
normal: {
"text-align": "left",
},
hover: {},
active: {},
selected: {},
},
_code: "",
className: "",
text: "Welcome to your Budibase App 👋",
type: "h2",
_appId: "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
_instanceName: "Heading",
_children: [],
},
{
_id: "cbbf41b27c2b44d1abba38bb694880c6a",
_component: "@budibase/standard-components/container",
_styles: {
normal: {
display: "flex",
"flex-direction": "column",
"justify-content": "center",
"align-items": "stretch",
flex: "1 1 auto",
"border-width": "4px",
"border-style": "Dashed",
"margin-bottom": "32px",
},
hover: {},
active: {},
selected: {},
},
_code: "",
className: "",
onLoad: [],
type: "div",
_appId: "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
_instanceName: "Video Container",
_children: [
{
_id: "c07d752cb3e544b418088fa9be84ba2e4",
_component: "@budibase/standard-components/embed",
_styles: {
normal: {
width: "100%",
flex: "1 1 auto",
opacity: "0",
"transition-property": "Opacity",
"transition-duration": "1s",
"transition-timing-function:": "ease-in",
},
hover: {
"transition-property": "Opacity",
"transition-duration": "1s",
"transition-timing-function:": "ease-out",
opacity: "1",
},
active: {},
selected: {},
},
_code: "",
embed:
'<iframe width="560" height="315" src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
_appId: "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
_instanceName: "Rick Astley Video",
_children: [],
},
],
},
],
_instanceName: "Home",
},
route: "/",
name: "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
}

View file

@ -26,4 +26,18 @@ const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS)
allDbs(Pouch) allDbs(Pouch)
// replicate your local levelDB pouch to a running HTTP compliant couch or pouchdb server.
// eslint-disable-next-line no-unused-vars
function replicateLocal() {
Pouch.allDbs().then(dbs => {
for (let db of dbs) {
new Pouch(db).sync(
new PouchDB(`http://127.0.0.1:5984/${db}`, { live: true })
)
}
})
}
replicateLocal()
module.exports = Pouch module.exports = Pouch

View file

@ -13,6 +13,8 @@ const DocumentTypes = {
ACCESS_LEVEL: "ac", ACCESS_LEVEL: "ac",
WEBHOOK: "wh", WEBHOOK: "wh",
INSTANCE: "inst", INSTANCE: "inst",
PAGE: "page",
SCREEN: "screen",
} }
exports.DocumentTypes = DocumentTypes exports.DocumentTypes = DocumentTypes
@ -175,6 +177,36 @@ exports.generateWebhookID = () => {
return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}` return `${DocumentTypes.WEBHOOK}${SEPARATOR}${newid()}`
} }
/**
* Generates a new page ID.
* @returns {string} The new page ID which the page doc can be stored under.
*/
exports.generatePageID = () => {
return `${DocumentTypes.PAGE}${SEPARATOR}${newid()}`
}
/**
* Gets parameters for retrieving pages, this is a utility function for the getDocParams function.
*/
exports.getPageParams = (pageId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.PAGE, pageId, otherProps)
}
/**
* Generates a new screen ID.
* @returns {string} The new screen ID which the screen doc can be stored under.
*/
exports.generateScreenID = pageId => {
return `${DocumentTypes.SCREEN}${SEPARATOR}${pageId}${SEPARATOR}${newid()}`
}
/**
* Gets parameters for retrieving screens for a particular page, this is a utility function for the getDocParams function.
*/
exports.getScreenParams = (pageId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.SCREEN, pageId, otherProps)
}
/** /**
* Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function. * Gets parameters for retrieving a webhook, this is a utility function for the getDocParams function.
*/ */

View file

@ -34,6 +34,7 @@ module.exports = {
USERID_API_KEY: process.env.USERID_API_KEY, USERID_API_KEY: process.env.USERID_API_KEY,
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL, DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES,
_set(key, value) { _set(key, value) {
process.env[key] = value process.env[key] = value
module.exports[key] = value module.exports[key] = value

View file

@ -1,144 +0,0 @@
{
"componentLibraries": [
"@budibase/standard-components"
],
"title": "{{ name }}",
"favicon": "./_shared/favicon.png",
"stylesheets": [],
"props": {
"_id": "private-master-root",
"_component": "@budibase/standard-components/container",
"_children": [
{
"_id": "c74f07266980c4b6eafc33e2a6caa783d",
"_component": "@budibase/standard-components/container",
"_styles": {
"normal": {
"display": "flex",
"flex-direction": "row",
"justify-content": "flex-start",
"align-items": "flex-start",
"background": "#fff",
"width": "100%",
"box-shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"className": "",
"onLoad": [],
"type": "div",
"_appId": "inst_app_80b_f158d4057d2c4bedb0042d42fda8abaf",
"_instanceName": "Header",
"_children": [
{
"_id": "49e0e519-9e5e-4127-885a-ee6a0a49e2c1",
"_component": "@budibase/standard-components/Navigation",
"_styles": {
"normal": {
"max-width": "1400px",
"margin-left": "auto",
"margin-right": "auto",
"padding": "20px",
"color": "#757575",
"font-weight": "400",
"font-size": "16px",
"flex": "1 1 auto"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"logoUrl": "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
"title": "",
"backgroundColor": "",
"color": "",
"borderWidth": "",
"borderColor": "",
"borderStyle": "",
"_appId": "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
"_instanceName": "Navigation",
"_children": [
{
"_id": "48b35328-4c91-4343-a6a3-1a1fd77b3386",
"_component": "@budibase/standard-components/link",
"_styles": {
"normal": {
"font-family": "Inter",
"font-weight": "500",
"color": "#000000",
"text-decoration-line": "none",
"font-size": "16px"
},
"hover": {
"color": "#4285f4"
},
"active": {},
"selected": {}
},
"_code": "",
"url": "/",
"openInNewTab": false,
"text": "Home",
"color": "",
"hoverColor": "",
"underline": false,
"fontSize": "",
"fontFamily": "initial",
"_appId": "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
"_instanceName": "Home Link",
"_children": []
}
]
}
]
},
{
"_id": "7fcf11e4-6f5b-4085-8e0d-9f3d44c98967",
"_component": "##builtin/screenslot",
"_styles": {
"normal": {
"flex": "1 1 auto",
"display": "flex",
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch",
"max-width": "100%",
"margin-left": "20px",
"margin-right": "20px",
"width": "1400px",
"padding": "20px"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"_children": []
}
],
"type": "div",
"_styles": {
"active": {},
"hover": {},
"normal": {
"display": "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "flex-start",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%",
"background-image": "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);"
},
"selected": {}
},
"_code": "",
"className": "",
"onLoad": []
},
"uiFunctions": ""
}

View file

@ -1,102 +0,0 @@
{
"description": "",
"url": "",
"props": {
"_id": "d834fea2-1b3e-4320-ab34-f9009f5ecc59",
"_component": "@budibase/standard-components/container",
"_styles": {
"normal": {
"flex": "1 1 auto",
"display": "flex",
"flex-direction": "column",
"justify-content": "flex-start",
"align-items": "stretch"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"className": "",
"onLoad": [],
"type": "div",
"_children": [
{
"_id": "ef60083f-4a02-4df3-80f3-a0d3d16847e7",
"_component": "@budibase/standard-components/heading",
"_styles": {
"normal": {
"text-align": "left"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"className": "",
"text": "Welcome to your Budibase App 👋",
"type": "h2",
"_appId": "inst_cf8ace4_69efc0d72e6f443db2d4c902c14d9394",
"_instanceName": "Heading",
"_children": []
},
{
"_id": "cbbf41b27c2b44d1abba38bb694880c6a",
"_component": "@budibase/standard-components/container",
"_styles": {
"normal": {
"display": "flex",
"flex-direction": "column",
"justify-content": "center",
"align-items": "stretch",
"flex": "1 1 auto",
"border-width": "4px",
"border-style": "Dashed",
"margin-bottom": "32px"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"className": "",
"onLoad": [],
"type": "div",
"_appId": "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
"_instanceName": "Video Container",
"_children": [
{
"_id": "c07d752cb3e544b418088fa9be84ba2e4",
"_component": "@budibase/standard-components/embed",
"_styles": {
"normal": {
"width": "100%",
"flex": "1 1 auto",
"opacity": "0",
"transition-property": "Opacity",
"transition-duration": "1s",
"transition-timing-function:": "ease-in"
},
"hover": {
"transition-property": "Opacity",
"transition-duration": "1s",
"transition-timing-function:": "ease-out",
"opacity": "1"
},
"active": {},
"selected": {}
},
"_code": "",
"embed": "<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/dQw4w9WgXcQ\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>",
"_appId": "inst_app_2cc_ca3383f896034e9295345c05f7dfca0c",
"_instanceName": "Rick Astley Video",
"_children": []
}
]
}
],
"_instanceName": "Home"
},
"route": "/",
"name": "d834fea2-1b3e-4320-ab34-f9009f5ecc59"
}

View file

@ -1,68 +0,0 @@
{
"componentLibraries": [
"@budibase/standard-components"
],
"title": "{{ name }}",
"favicon": "./_shared/favicon.png",
"stylesheets": [],
"props": {
"_id": "public-master-root",
"_component": "@budibase/standard-components/container",
"_children": [
{
"_id": "686c252d-dbf2-4e28-9078-414ba4719759",
"_component": "@budibase/standard-components/login",
"_styles": {
"normal": {
"padding": "64px",
"background": "rgba(255, 255, 255, 0.4)",
"border-radius": "0.5rem",
"margin-top": "0px",
"margin": "0px",
"line-height": "1",
"box-shadow": "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
"font-size": "16px",
"font-family": "Inter",
"flex": "0 1 auto",
"transform": "0"
},
"hover": {},
"active": {},
"selected": {}
},
"_code": "",
"loginRedirect": "",
"usernameLabel": "Username",
"passwordLabel": "Password",
"loginButtonLabel": "Login",
"buttonClass": "",
"_instanceName": "Login",
"inputClass": "",
"_children": [],
"title": "Log in to {{ name }}",
"buttonText": "Log In",
"logo": "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg"
}
],
"type": "div",
"_styles": {
"active": {},
"hover": {},
"normal": {
"display": "flex",
"flex-direction": "column",
"align-items": "center",
"justify-content": "center",
"margin-right": "auto",
"margin-left": "auto",
"min-height": "100%",
"background-image": "linear-gradient(135deg, rgba(252,215,212,1) 20%, rgba(207,218,255,1) 100%);"
},
"selected": {}
},
"_code": "",
"className": "",
"onLoad": []
},
"uiFunctions": ""
}

View file

@ -1 +0,0 @@
module.exports = () => ({})

View file

@ -1,35 +1,24 @@
const { appPackageFolder } = require("../createAppPackage") const { constants, copyFile, writeFile, readFile } = require("fs-extra")
const {
constants,
copyFile,
writeFile,
readFile,
writeJSON,
} = require("fs-extra")
const { join, resolve } = require("../centralPath") const { join, resolve } = require("../centralPath")
const sqrl = require("squirrelly") const sqrl = require("squirrelly")
const { convertCssToFiles } = require("./convertCssToFiles") const { convertCssToFiles } = require("./convertCssToFiles")
const publicPath = require("./publicPath") const publicPath = require("./publicPath")
const deleteCodeMeta = require("./deleteCodeMeta") const { budibaseAppsDir } = require("../budibaseDir")
module.exports = async (config, appId, pageName, pkg) => { module.exports = async (appId, pageName, pkg) => {
const appPath = appPackageFolder(config, appId) const appPath = join(budibaseAppsDir(), appId)
pkg.screens = pkg.screens || [] pkg.screens = pkg.screens || []
await convertCssToFiles(publicPath(appPath, pageName), pkg) await convertCssToFiles(publicPath(appPath, pageName), pkg)
await buildIndexHtml(config, appId, pageName, appPath, pkg) await buildIndexHtml(appId, pageName, appPath, pkg)
await buildFrontendAppDefinition(config, appId, pageName, pkg, appPath) await buildFrontendAppDefinition(appId, pageName, pkg, appPath)
await copyClientLib(appPath, pageName) await copyClientLib(appPath, pageName)
await savePageJson(appPath, pageName, pkg)
} }
const rootPath = (config, appId) => (config.useAppRootPath ? `/${appId}` : "")
const copyClientLib = async (appPath, pageName) => { const copyClientLib = async (appPath, pageName) => {
const sourcepath = require.resolve("@budibase/client") const sourcepath = require.resolve("@budibase/client")
const destPath = join(publicPath(appPath, pageName), "budibase-client.js") const destPath = join(publicPath(appPath, pageName), "budibase-client.js")
@ -43,11 +32,10 @@ const copyClientLib = async (appPath, pageName) => {
) )
} }
const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => { const buildIndexHtml = async (appId, pageName, appPath, pkg) => {
const appPublicPath = publicPath(appPath, pageName) const appPublicPath = publicPath(appPath, pageName)
const stylesheetUrl = s => const stylesheetUrl = s => (s.startsWith("http") ? s : `/${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",
@ -77,15 +65,13 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
await writeFile(deployableHtmlPath, deployableHtml, { flag: "w+" }) await writeFile(deployableHtmlPath, deployableHtml, { flag: "w+" })
} }
const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => { const buildFrontendAppDefinition = async (appId, pageName, pkg) => {
const appPath = appPackageFolder(config, appId) const appPath = join(budibaseAppsDir(), appId)
const appPublicPath = publicPath(appPath, pageName) const appPublicPath = publicPath(appPath, pageName)
const filename = join(appPublicPath, "clientFrontendDefinition.js") const filename = join(appPublicPath, "clientFrontendDefinition.js")
if (pkg.page._css) { delete pkg.page._css
delete pkg.page._css
}
for (let screen of pkg.screens) { for (let screen of pkg.screens) {
if (screen._css) { if (screen._css) {
@ -106,25 +92,3 @@ const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
` `
) )
} }
const savePageJson = async (appPath, pageName, pkg) => {
const pageFile = join(appPath, "pages", pageName, "page.json")
if (pkg.page._css) {
delete pkg.page._css
}
if (pkg.page.name) {
delete pkg.page.name
}
if (pkg.page._screens) {
delete pkg.page._screens
}
deleteCodeMeta(pkg.page.props)
await writeJSON(pageFile, pkg.page, {
spaces: 2,
})
}

View file

@ -1,9 +0,0 @@
module.exports = props => {
if (props._codeMeta) {
delete props._codeMeta
}
for (let child of props._children || []) {
module.exports(child)
}
}

View file

@ -1,20 +0,0 @@
const { readJSON, readdir } = require("fs-extra")
const { join } = require("../centralPath")
module.exports = async appPath => {
const pages = {}
const pageFolders = await readdir(join(appPath, "pages"))
for (let pageFolder of pageFolders) {
try {
pages[pageFolder] = await readJSON(
join(appPath, "pages", pageFolder, "page.json")
)
pages[pageFolder].name = pageFolder
} catch (_) {
// ignore error
}
}
return pages
}

View file

@ -1,98 +0,0 @@
const { appPackageFolder } = require("../createAppPackage")
const {
readJSON,
writeJSON,
readdir,
ensureDir,
rename,
unlink,
rmdir,
} = require("fs-extra")
const { join, resolve } = require("../centralPath")
const { dirname } = require("path")
const buildPage = require("./buildPage")
const getPages = require("./getPages")
const listScreens = require("./listScreens")
const deleteCodeMeta = require("./deleteCodeMeta")
module.exports.buildPage = buildPage
module.exports.listScreens = listScreens
const getAppDefinition = async appPath =>
await readJSON(`${appPath}/appDefinition.json`)
module.exports.getPackageForBuilder = async (config, application) => {
const appPath = resolve(config.latestPackagesFolder, application._id)
const pages = await getPages(appPath)
return {
pages,
application,
}
}
const screenPath = (appPath, pageName, name) =>
join(appPath, "pages", pageName, "screens", name + ".json")
module.exports.saveScreen = async (config, appname, pagename, screen) => {
const appPath = appPackageFolder(config, appname)
const compPath = screenPath(appPath, pagename, screen.props._id)
await ensureDir(dirname(compPath))
if (screen._css) {
delete screen._css
}
deleteCodeMeta(screen.props)
await writeJSON(compPath, screen, {
encoding: "utf8",
flag: "w",
spaces: 2,
})
return screen
}
module.exports.renameScreen = async (
config,
appname,
pagename,
oldName,
newName
) => {
const appPath = appPackageFolder(config, appname)
const oldComponentPath = screenPath(appPath, pagename, oldName)
const newComponentPath = screenPath(appPath, pagename, newName)
await ensureDir(dirname(newComponentPath))
await rename(oldComponentPath, newComponentPath)
}
module.exports.deleteScreen = async (config, appId, pagename, name) => {
const appPath = appPackageFolder(config, appId)
const componentFile = screenPath(appPath, pagename, name)
await unlink(componentFile)
const dir = dirname(componentFile)
if ((await readdir(dir)).length === 0) {
await rmdir(dir)
}
}
module.exports.savePage = async (config, appname, pagename, page) => {
const appPath = appPackageFolder(config, appname)
const pageDir = join(appPath, "pages", pagename)
await ensureDir(pageDir)
await writeJSON(join(pageDir, "page.json"), page, {
encoding: "utf8",
flag: "w",
space: 2,
})
const appDefinition = await getAppDefinition(appPath)
await buildPage(config, appname, appDefinition, pagename, page)
}

View file

@ -1,48 +0,0 @@
const { appPackageFolder } = require("../createAppPackage")
const { readJSON, readdir, stat } = require("fs-extra")
const { join } = require("../centralPath")
const { keyBy } = require("lodash/fp")
module.exports = async (config, appname, pagename) => {
const appPath = appPackageFolder(config, appname)
return keyBy("name")(await fetchscreens(appPath, pagename))
}
const fetchscreens = async (appPath, pagename, relativePath = "") => {
const currentDir = join(appPath, "pages", pagename, "screens", relativePath)
const contents = await readdir(currentDir)
const screens = []
for (let item of contents) {
const itemRelativePath = join(relativePath, item)
const itemFullPath = join(currentDir, item)
const stats = await stat(itemFullPath)
if (stats.isFile()) {
if (!item.endsWith(".json")) continue
const component = await readJSON(itemFullPath)
component.name = itemRelativePath
.substring(0, itemRelativePath.length - 5)
.replace(/\\/g, "/")
component.props = component.props || {}
screens.push(component)
} else {
const childComponents = await fetchscreens(
appPath,
join(relativePath, item)
)
for (let c of childComponents) {
screens.push(c)
}
}
}
return screens
}

View file

@ -21,6 +21,7 @@ module.exports = async (ctx, appId, version) => {
// set the builder token // set the builder token
setCookie(ctx, "builder", token) setCookie(ctx, "builder", token)
setCookie(ctx, "currentapp", appId)
// need to clear all app tokens or else unable to use the app in the builder // need to clear all app tokens or else unable to use the app in the builder
let allDbNames = await CouchDB.allDbs() let allDbNames = await CouchDB.allDbs()
allDbNames.map(dbName => { allDbNames.map(dbName => {

View file

@ -1,5 +1,3 @@
const { resolve } = require("./centralPath")
const { cwd } = require("process")
const stream = require("stream") const stream = require("stream")
const fetch = require("node-fetch") const fetch = require("node-fetch")
const tar = require("tar-fs") const tar = require("tar-fs")
@ -9,9 +7,6 @@ const packageJson = require("../../package.json")
const streamPipeline = promisify(stream.pipeline) const streamPipeline = promisify(stream.pipeline)
exports.appPackageFolder = (config, appname) =>
resolve(cwd(), config.latestPackagesFolder, appname)
exports.downloadExtractComponentLibraries = async appFolder => { exports.downloadExtractComponentLibraries = async appFolder => {
const LIBRARIES = ["standard-components"] const LIBRARIES = ["standard-components"]

View file

@ -8,12 +8,35 @@ const zlib = require("zlib")
const { promisify } = require("util") const { promisify } = require("util")
const streamPipeline = promisify(stream.pipeline) const streamPipeline = promisify(stream.pipeline)
const { budibaseAppsDir } = require("./budibaseDir") const { budibaseAppsDir } = require("./budibaseDir")
const env = require("../environment")
const CouchDB = require("../db") const CouchDB = require("../db")
const { DocumentTypes } = require("../db/utils")
const DEFAULT_TEMPLATES_BUCKET = const DEFAULT_TEMPLATES_BUCKET =
"prod-budi-templates.s3-eu-west-1.amazonaws.com" "prod-budi-templates.s3-eu-west-1.amazonaws.com"
exports.getLocalTemplates = function() {
const templatesDir = join(os.homedir(), ".budibase", "templates", "app")
const templateObj = { app: {} }
fs.ensureDirSync(templatesDir)
const templateNames = fs.readdirSync(templatesDir)
for (let name of templateNames) {
templateObj.app[name] = {
name,
category: "local",
description: "local template",
type: "app",
key: `app/${name}`,
}
}
return templateObj
}
exports.downloadTemplate = async function(type, name) { exports.downloadTemplate = async function(type, name) {
const dirName = join(budibaseAppsDir(), "templates", type, name)
if (env.LOCAL_TEMPLATES) {
return dirName
}
const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz`
const response = await fetch(templateUrl) const response = await fetch(templateUrl)
@ -30,26 +53,27 @@ exports.downloadTemplate = async function(type, name) {
tar.extract(join(budibaseAppsDir(), "templates", type)) tar.extract(join(budibaseAppsDir(), "templates", type))
) )
return join(budibaseAppsDir(), "templates", type, name) return dirName
} }
exports.exportTemplateFromApp = async function({ templateName, appId }) { exports.exportTemplateFromApp = async function({ templateName, appId }) {
// Copy frontend files // Copy frontend files
const appToExport = join(os.homedir(), ".budibase", appId, "pages") const templatesDir = join(
const templatesDir = join(os.homedir(), ".budibase", "templates") os.homedir(),
fs.ensureDirSync(templatesDir) ".budibase",
"templates",
const templateOutputPath = join(templatesDir, templateName) "app",
fs.copySync(appToExport, join(templateOutputPath, "pages")) templateName,
"db"
fs.ensureDirSync(join(templateOutputPath, "db"))
const writeStream = fs.createWriteStream(
join(templateOutputPath, "db", "dump.txt")
) )
fs.ensureDirSync(templatesDir)
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt"))
// perform couch dump // perform couch dump
const instanceDb = new CouchDB(appId) const instanceDb = new CouchDB(appId)
await instanceDb.dump(writeStream, {
await instanceDb.dump(writeStream) filter: doc => {
return templateOutputPath return !doc._id.startsWith(DocumentTypes.USER)
},
})
return templatesDir
} }